new DiaryStandard(file, serialiseropt)
Parameters:
Name | Type | Attributes | Description | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
file |
Object | file contents, or object containing records
Properties
|
|||||||||||||||||
serialiser |
function |
<optional> |
function to serialise output |
- Source:
Example
let diary = new_sleep_diary(contents_of_my_file));
// print the minimum expected day duration in milliseconds:
console.log(diary.settings.minimum_day_duration);
-> 12345
// print the maximum expected day duration in milliseconds:
console.log(diary.settings.maximum_day_duration);
-> 23456
// Print the complete list of records
console.log(diary.records);
-> [
{
// DiaryStandardRecordStatus value, usually "awake" or "asleep"
status: "awake",
// start and end time (in milliseconds past the Unix epoch), estimated if the user forgot to log some data:
start: 12345678,
end: 23456789,
start_timezone: "Etc/GMT-1",
end_timezone: "Europe/Paris",
duration: 11111111, // or missing if duration is unknown
// tags associated with this period:
tags: [
"tag 1",
"tag 2",
...
],
// comments recorded during this period:
comments: [
"comment with no associated timestamp",
{ time: 23456543, text: "timestamped comment" },
...
],
// (estimated) day this record is assigned to:
day_number: 1,
// true if the current day number is greater than the previous record's day number:
start_of_new_day: true,
// whether this value is the primary sleep for the current day number:
is_primary_sleep: false,
// this is set if it looks like the user forgot to log some data:
missing_record_after: true
},
...
]
// Print the user's current sleep/wake status:
console.log(diary.latest_sleep_status());
-> "awake"
// Print the user's sleep statistics:
console.log( diary.summarise_records( record => record.status == "asleep" ) );
-> {
average : 12345.678,
mean : 12356.789,
interquartile_mean : 12345.678,
standard_deviation: 12.56,
interquartile_standard_deviation: 12.45,
median : 12345,
interquartile_range : 12,
durations : [ undefined, 12345, undefined, ... ],
interquartile_durations : [ 10000, 10001 ... 19998, 19999 ],
}
// Print the user's day length statistics for the past 14 days:
let cutoff = new Date().getTime() - 1000*60*60*24*14;
console.log( diary.summarise_days( record => record.start > cutoff ) );
-> {
average : 12345.678,
mean : 12356.789,
interquartile_mean : 12345.678,
standard_deviation: 12.56,
interquartile_standard_deviation: 12.45,
median : 12345,
interquartile_range : 12,
durations : [ undefined, 12345, undefined, ... ],
interquartile_durations : [ 10000, 10001 ... 19998, 19999 ],
}
// Print the user's daily schedule on a 24-hour clock:
console.log( diary.summarise_schedule();
-> {
sleep: { // time (GMT) when the user falls asleep:
average : 12345.678,
mean : 12356.789,
interquartile_mean : 12345.678,
standard_deviation: 12.56,
interquartile_standard_deviation: 12.45,
median : 12345,
interquartile_range : 12,
durations : [ undefined, 12345, undefined, ... ],
interquartile_durations : [ 10000, 10001 ... 19998, 19999 ],
},
wake: { // time (GMT) when the user wakes up:
average : 12345.678,
mean : 12356.789,
interquartile_mean : 12345.678,
standard_deviation: 12.56,
interquartile_standard_deviation: 12.45,
median : 12345,
interquartile_range : 12,
durations : [ undefined, 12345, undefined, ... ],
interquartile_durations : [ 10000, 10001 ... 19998, 19999 ],
},
}
// Print the user's daily schedule on a 24-hour clock for the past 14 days:
let cutoff = new Date().getTime() - 1000*60*60*24*14;
console.log( diary.summarise_schedule( record => record.start > cutoff ) );
-> {
sleep: { // time (GMT) when the user falls asleep:
average : 12345.678,
mean : 12356.789,
interquartile_mean : 12345.678,
standard_deviation: 12.56,
interquartile_standard_deviation: 12.45,
median : 12345,
interquartile_range : 12,
durations : [ undefined, 12345, undefined, ... ],
interquartile_durations : [ 10000, 10001 ... 19998, 19999 ],
},
wake: { // time (GMT) when the user wakes up:
average : 12345.678,
mean : 12356.789,
interquartile_mean : 12345.678,
standard_deviation: 12.56,
interquartile_standard_deviation: 12.45,
median : 12345,
interquartile_range : 12,
durations : [ undefined, 12345, undefined, ... ],
interquartile_durations : [ 10000, 10001 ... 19998, 19999 ],
},
}
// Print the user's daily schedule on a 25-hour clock, defaulting to Cairo's timezone:
console.log( diary.summarise_schedule( null, 25*60*60*1000, "Africa/Cairo" ) );
-> {
sleep: { // time (Cairo) when the user falls asleep:
average : 12345.678,
mean : 12356.789,
interquartile_mean : 12345.678,
standard_deviation: 12.56,
interquartile_standard_deviation: 12.45,
median : 12345,
interquartile_range : 12,
durations : [ undefined, 12345, undefined, ... ],
interquartile_durations : [ 10000, 10001 ... 19998, 19999 ],
},
wake: { // time (Cairo) when the user wakes up:
average : 12345.678,
mean : 12356.789,
interquartile_mean : 12345.678,
standard_deviation: 12.56,
interquartile_standard_deviation: 12.45,
median : 12345,
interquartile_range : 12,
durations : [ undefined, 12345, undefined, ... ],
interquartile_durations : [ 10000, 10001 ... 19998, 19999 ],
},
}
Extends
Members
"records" :Array.<DiaryStandardRecord>
Type:
- Array.<DiaryStandardRecord>
- Source:
(protected) "spreadsheet" :Spreadsheet
Type:
- Source:
serialiser :function
Type:
- function
- Overrides:
- Source:
Methods
clone()
- Overrides:
- Source:
(protected) corrupt(file, message)
Parameters:
Name | Type | Description |
---|---|---|
file |
string | Object | file contents, or filename/contents pairs (for archive files) |
message |
string | optional error message |
- Overrides:
- Source:
daily_activities(timezoneopt, day_startopt, day_strideopt, segment_strideopt)
It can be useful to display a diary as a series of columns or rows, each containing the activities for that day. This function returns a structure that accounts for the following issues:
Multi-day records - the function returns a list of days, which contain activities - parts of a record confined within that day.
Daylight savings time - if the timezone observes daylight savings time (e.g. `Europe/London` instead of `Etc/GMT`), the function will modify days to account for DST changes (so a 24-hour diary will start at the same time every day, even if that means having a 23- or 25-hour day).
Missing days - if a user skips a day, or stops recording altogether for months or even years, the function will leave a gap in the array for each missing day.
Not including workarounds for a few edge cases, the start and end times for days are calculated like this:
- Find the start of the first day:
- find the earliest date in the list of records
- find the last time at or before that date where the time equals `day_start` in `timezone`
- if that date would be invalid (because a DST change causes that time to be skipped), move backwards by the DST change duration.
- Find the start of the next day:
- move forwards in time by `day_stride`
- move backwards by the difference between the start and end timezone offsets
(so a 24-hour `day_stride` will always start at the same time of day)
- Create a new day object with the start of the current and next day
- Continue moving forward one day at a time until we reach the final record
An activity represents the fraction of a record that exists within the current day. For example, a record that lasted two days would have two associated records. Activity times are decided like this:
- if a record has neither a `start` nor `end` time, no activities are created
- if a record has exactly one `start` or `end` time, one activity is created. The activity has a `time` equal to whichever was defined.
- if a record has both `start` and `end`, and both are within the same day, one activity is created. The activity has a `time` halfway between the two.
- if a record has both `start` and `end` that span multiple days, one activity is created for each day the record is active. The first activity has a `time` halfway between `start` and the end of that day. middle activities have a `time` equal to the middle of the day. The final activity has a `time` halfway between the start of that day and `end`
Each activity includes a `type`, which is one of:
- `start-end` - complete duration of an event
- `start-mid` - start of an event that spans multiple days
- `mid-mid` - middle of an event that spans multiple days
- `mid-end` - end of an event that spans multiple days
- `start-unknown` - start of an event with an undefined end time
- `unknown-end` - end of an event with an undefined start time
If a `segment_stride` argument is passed, segments for a day are calculated like this:
- create a segment that starts at the `start` time
- check whether the DST offset is the same at the start and end of the day
- if not, create segments `segment_stride` apart
- otherwise...
- continue forwards until the current segment's end time is greater than the `end` time, or the timezone's DST status changes
- stop unless a DST change was crossed
- create a new segment that ends at the `end` time
- continue backwards until the current segment's start time is less than or equal to the last segment from amove
- sort all segments by start time
The algorithm above should produce intuitive dates in most cases, but produces unexpected behaviour if there is more than one DST change in a single day.
Be aware that some timezones have [a 45-minute offset from UTC](https://en.wikipedia.org/wiki/UTC%2B05:45), and [even more esoteric timezones](https://en.wikipedia.org/wiki/UTC%E2%88%9200:25:21), existed in the 20th century. You may need to test your program carefully to avoid incorrect behaviour in some cases.
Parameters:
Name | Type | Attributes | Default | Description |
---|---|---|---|---|
timezone |
string |
<optional> |
system_timezone | display dates in this timezone |
day_start |
number |
<optional> |
64800000 | start the first new day at this time (usually 6pm) |
day_stride |
number |
<optional> |
86400000 | amount of time to advance each day (usually 24 hours) |
segment_stride |
number |
<optional> |
amount of time to advance each segment |
- Source:
Example
console.log( diary.daily_activities() );
-> [
{
"start" : 123456789, // Unix time when the day starts
"end" : 234567890, // start + day_stride - dst_change
"duration": 11111111, // end - start
"id" : "2020-03-30T18:00:00.000 Etc/GMT" // ISO 8601 equivalent of "start"
"year" : 2020,
"month" : 2, // zero-based month number
"day" : 29, // zero-based day of month
"activities": [
{
"start" : 123459999, // time when the event started (optional)
"end" : 234560000, // time when the event ended (optional)
"time" : 200000000, // time associated with the event (required)
"offset_start": 0.1, // ( start - day.start ) / day.duration
"offset_end" : 0.9, // ( end - day.start ) / day.duration
"offset_time" : 0.5, // ( time - day.start ) / day.duration
"type" : "start-end" // see above for list of types
"record" : { ... }, // associated record
"index" : 0, // this is the nth activity for this record
},
],
"activity_summaries": {
"asleep": {
"first_start": "2020-03-30T20:00:00.000 Etc/GMT", // start of first activity
"first_end" : "2020-03-31T06:00:00.000 Etc/GMT", // end of first activity
"last_start": "2020-03-31T14:00:00.000 Etc/GMT", // start of last activity
"last_end" : "2020-03-31T14:30:00.000 Etc/GMT", // end of last activity
"duration" : 37800000, // total milliseconds (NaN if some activities have undefined duration)
},
...
},
"segments": [
{
"dst_state": "on" // or "off" or "change-forward" or "change-back"
"year" : 2020,
"month" : 2, // zero-based month number
"day" : 29, // zero-based day of month
"hour" : 18,
"minute": 0,
"second": 0,
"id" : "2020-03-30T18:00:00.000 Etc/GMT"
},
...
],
},
...
]
(protected) invalid(file)
Parameters:
Name | Type | Description |
---|---|---|
file |
string | Object | file contents, or filename/contents pairs (for archive files) |
- Overrides:
- Source:
latest_daytime_midpoint() → {number|undefined}
- Source:
Returns:
- Type
- number | undefined
latest_sleep_status() → {string}
- Source:
Returns:
- Type
- string
merge(other)
Parameters:
Name | Type | Description |
---|---|---|
other |
DiaryBase | diary to merge in |
- Overrides:
- Source:
Example
diary.merge(my_data);
reset_to_timezone(timezoneopt)
Parameters:
Name | Type | Attributes | Default | Description |
---|---|---|---|---|
timezone |
string |
<optional> |
system_timezone | originally intended timezone |
- Source:
(protected) serialise()
- Overrides:
- Source:
software_version() → {string}
- Overrides:
- Source:
Returns:
- Type
- string
summarise_days(filteropt)
Similar to DiaryStandard#summarise_records, but groups records by day_number.
Parameters:
Name | Type | Attributes | Description |
---|---|---|---|
filter |
function |
<optional> |
only examine records that match this filter |
- Source:
- Tutorials:
- See:
Returns:
Example
console.log( diary.summarise_days( record => record.start > cutoff ) );
-> {
average : 12345.678,
mean : 12356.789,
interquartile_mean : 12345.678,
standard_deviation: 12.56,
interquartile_standard_deviation: 12.45,
median : 12345,
interquartile_range : 12,
durations : [ undefined, 12345, undefined, ... ],
interquartile_durations : [ 10000, 10001 ... 19998, 19999 ],
}
summarise_records(filteropt)
Because real-world data tends to be quite messy, and because different users have different requirements, we provide several summaries for the data:
- average is the best guess at what the user would intuitively consider the average duration of a record. The exact calculation is chosen from the list below, and may change in future. It is currently the trimmed_mean. If you don't have any specific requirements, you should use this and ignore the others.
- mean and standard_deviation are traditional summary statistics for the duration, but are not recommended because real-world data tends to skew these values higher than one would expect.
- interquartile_mean and interquartile_standard_deviation produce more robust values in cases like ours, because they ignore the highest and lowest few records.
- median and interquartile_range produce more robust results, but tend to be less representative when there are only a few outliers in the data.
- durations and interquartile_durations are the raw values the other statistics were created from.
Parameters:
Name | Type | Attributes | Description |
---|---|---|---|
filter |
function |
<optional> |
only examine records that match this filter |
- Source:
Returns:
Example
console.log( diary.summarise_records( record => record.status == "asleep" ) );
-> {
average : 12345.678,
mean : 12356.789,
interquartile_mean : 12345.678,
standard_deviation: 12.56,
interquartile_standard_deviation: 12.45,
median : 12345,
interquartile_range : 12,
durations : [ undefined, 12345, undefined, ... ],
interquartile_durations : [ 10000, 10001 ... 19998, 19999 ],
}
summarise_schedule(filteropt, day_lengthopt, timezoneopt) → {Object}
Somewhat similar to DiaryStandard#summarise_records.
Calculates the time of day when the user is likey to wake up or go to sleep.
Sleep/wake times are currently calculated based on the beginning/end time for each day's primary sleep, although this may change in future.
Times are calculated according to the associated timezone. For example, say you woke up in New York at 8am, flew to Los Angeles, went to bed and woke up again at 8am local time. You would be counted as waking up at 8am both days, even though 27 hours had passed between wake events.
Records without a timezone are treated as if they had the environment's default timezone
Parameters:
Name | Type | Attributes | Default | Description |
---|---|---|---|---|
filter |
function |
<optional> |
null | only examine records that match this filter |
day_length |
number |
<optional> |
86400000 | times of day are calculated relative to this amount of time |
timezone |
string |
<optional> |
system_timezone | default timezone for records |
- Source:
- See:
Returns:
- Type
- Object
Example
console.log( diary.summarise_schedule() );
-> {
sleep: { // time when the user falls asleep:
average : 12345.678,
mean : 12356.789,
interquartile_mean : 12345.678,
standard_deviation: 12.56,
interquartile_standard_deviation: 12.45,
median : 12345,
interquartile_range : 12,
durations : [ undefined, 12345, undefined, ... ],
interquartile_durations : [ 10000, 10001 ... 19998, 19999 ],
},
wake: { // time when the user wakes up:
average : 12345.678,
mean : 12356.789,
interquartile_mean : 12345.678,
standard_deviation: 12.56,
interquartile_standard_deviation: 12.45,
median : 12345,
interquartile_range : 12,
durations : [ undefined, 12345, undefined, ... ],
interquartile_durations : [ 10000, 10001 ... 19998, 19999 ],
},
}
timezones() → {Array.<string>}
- Overrides:
- Source:
Returns:
- Type
- Array.<string>
to(to_format) → {*}
Supported formats:
- url - contents serialised for inclusion in a URL
- json - contents serialised to JSON
- storage-line - contents serialised for inclusion in a newline-separated list of diaries
- Standard - Standard format
- (other formats) - the name of any other diary format
to_async() supports more formats and should be used where possible. You should only call this function directly if you want to guarantee synchronous execution.
Parameters:
Name | Type | Description |
---|---|---|
to_format |
string | requested format |
- Overrides:
- Source:
Returns:
- Type
- *
Example
console.log( diary.to("NewFormat") );
to_async(to_format) → {Promise|Object}
Supported formats:
- output - contents serialised for output (e.g. to a file)
- spreadsheet - binary data that can be loaded by a spreadsheet program
- (formats supported by to())
See also to(), a lower-level function that supports formats that can be generated synchronously. You can use that function if a Promise interface would be cumbersome or unnecessary in a given piece of code.
Parameters:
Name | Type | Description |
---|---|---|
to_format |
string | requested format |
- Overrides:
- Source:
Returns:
- Type
- Promise | Object
Example
diary.to_async("NewFormat").then( reformatted => console.log( reformatted_diary ) );
total_per_day(record_filteropt, day_filteropt)
Similar to DiaryStandard#summarise_days, but looks at totals instead of sums.
The summarise_* functions examine sums, so missing values are treated as undefined. This function examines totals, so missing values are treated as 0. The record_filter and day_filter parameters allow you to exclude days and records separately.
Parameters:
Name | Type | Attributes | Description |
---|---|---|---|
record_filter |
function |
<optional> |
only examine records that match this filter |
day_filter |
function |
<optional> |
only examine days that match this filter |
- Source:
- See:
Returns:
Example
console.log( diary.total_per_day(
record => record.status == "asleep", // only count sleep records
record => record.start > cutoff // ignore old records
) );
-> {
average : 1.234,
mean : 1.345,
interquartile_mean : 1.234,
standard_deviation: 0.123,
interquartile_standard_deviation: 0.012,
median : 1,
interquartile_range : 1,
counts : [ undefined, 1, undefined, ... ],
interquartile_counts : [ 1, 1, 2, 1, 1, 0, ... ],
// included for compatibility with summarise_* functions:
durations : [ undefined, 1, undefined, ... ],
interquartile_durations : [ 1, 1, 2, 1, 1, 0, ... ],
}
update_timezone(timezoneopt)
Parameters:
Name | Type | Attributes | Default | Description |
---|---|---|---|---|
timezone |
string |
<optional> |
system_timezone | new timezone |
- Source: