Class: DiaryStandard

DiaryStandard(file, serialiseropt)

new DiaryStandard(file, serialiseropt)

Parameters:
Name Type Attributes Description
file Object file contents, or object containing records
Properties
Name Type Attributes Description
records Array <optional>
individual records from the sleep diary
minimum_day_duration number <optional>
minimum expected day duration in milliseconds
maximum_day_duration number <optional>
maximum expected day duration in milliseconds
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>

Individual records from the sleep diary
Type:
Source:

(protected) "spreadsheet" :Spreadsheet

Spreadsheet manager
Type:
Source:

serialiser :function

Serialise a value for output
Type:
  • function
Overrides:
Source:

Methods

clone()

Create a deep copy of the current object
Overrides:
Source:

(protected) corrupt(file, message)

Indicates the file is a corrupt file in the specified format
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)

List of activities, grouped by day

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:

  1. Find the start of the first day:
    1. find the earliest date in the list of records
    2. find the last time at or before that date where the time equals `day_start` in `timezone`
    3. if that date would be invalid (because a DST change causes that time to be skipped), move backwards by the DST change duration.
  2. Find the start of the next day:
    1. move forwards in time by `day_stride`
    2. 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)
  3. Create a new day object with the start of the current and next day
  4. 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:

    1. create a segment that starts at the `start` time
    2. 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)

Indicates the file is not valid in our file format
Parameters:
Name Type Description
file string | Object file contents, or filename/contents pairs (for archive files)
Overrides:
Source:

latest_daytime_midpoint() → {number|undefined}

Midpoint between primary wake and sleep events on the most recent day
Source:
Returns:
Unix time
Type
number | undefined

latest_sleep_status() → {string}

Latest sleep/wake status
Source:
Returns:
"awake", "asleep" or "" (for an empty diary)
Type
string

merge(other)

Merge another diary into this one
Parameters:
Name Type Description
other DiaryBase diary to merge in
Overrides:
Source:
Example
diary.merge(my_data);

reset_to_timezone(timezoneopt)

Reinterpret all records as having actually been in the specified timezone Consider a spreadsheet with an event starting at "2020-01-01 00:00". With no further information, we would have to assume that time was UTC, even if a user actually intended it to be in some other timezone. This function reinterprets times to correct that mistake.
Parameters:
Name Type Attributes Default Description
timezone string <optional>
system_timezone originally intended timezone
Source:

(protected) serialise()

Serialise data for output
Overrides:
Source:

software_version() → {string}

Version ID for this package
Overrides:
Source:
Returns:
Type
string

summarise_days(filteropt)

Summary statistics (based on records grouped by day_number)

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:
MaybeDiaryStandardStatistics
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)

Summary statistics (based on individual records)

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:
MaybeDiaryStandardStatistics
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}

Summary statistics about daily events

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>}

Complete list of allowed timezones
Overrides:
Source:
Returns:
Type
Array.<string>

to(to_format) → {*}

Convert a value to some other 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:
diary data in new format
Type
*
Example
console.log( diary.to("NewFormat") );

to_async(to_format) → {Promise|Object}

Convert a value to some other format

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:
Promise that returns the converted diary
Type
Promise | Object
Example
diary.to_async("NewFormat").then( reformatted => console.log( reformatted_diary ) );

total_per_day(record_filteropt, day_filteropt)

Summary statistics about the number of times an event occurs per day

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:
MaybeDiaryStandardStatistics
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)

Assign a new timezone to all records, leaving times unchanged
Parameters:
Name Type Attributes Default Description
timezone string <optional>
system_timezone new timezone
Source: