Spacetime

A lightweight javascript timezone library

README

spacetime logo

Isn't it weird how we can do math in our head, but not date math?

- how many days until the end of the year?
-what time was it, 11 hours ago?
-is it lunchtime in france?

and worse - there is no real _date calculator_.

people end up asking google, and going to weird websites.
that's bad.
spacetime is a date-calculator,

It's very small, and very handy.

  1. ``` js
  2. let s = spacetime.now()

  3. s.diff(s.endOf('year'), 'days')
  4. // 292

  5. s.minus(11, 'hours').time()
  6. // 6:50am

  7. s = s.now('Europe/Paris')
  8. s.isAfter(s.time('11:00am'))
  9. // true 🥐
  10. ```


- calculate time in remote timezones
- support daylight savings, leap years, and hemispheres
- Moment-like API _(but immutable)_
- Orient time by quarter, season, month, week..
- _Zero Dependencies_ - (no _Intl API_)
- weighs about 40kb.
- has a cool _plugin thing_.



  1. ``` html
  2. <script src="https://unpkg.com/spacetime"></script>
  3. <script>
  4.   var d = spacetime('March 1 2012', 'America/New_York')
  5.   //set the time
  6.   d = d.time('4:20pm')
  7.   d = d.goto('America/Los_Angeles')
  8.   d.time()
  9.   //'1:20pm'
  10. </script>
  11. ```



npm install spacetime

  1. ``` js
  2. const spacetime = require('spacetime')
  3. let d = spacetime.now('Europe/Paris')
  4. d.dayName()
  5. //'Wednesday'
  6. d.isAsleep()
  7. //true
  8. ```

typescript / babel / deno:

  1. ```ts
  2. import spacetime from 'spacetime'
  3. let d = spacetime.now()
  4. d.format('nice')
  5. //'Apr 1st, 4:32pm'
  6. ```

ts docs

      Demo
        •    
      Full API


plugins:

spacetime-geospacetime-daylightspacetime-age
spacetime-calendarweek-of-monthweek-start

Date Inputs:


we can parse _all the normal stuff_, and some fancy stuff:

  1. ``` js
  2. //epoch
  3. s = spacetime(1489520157124)

  4. //array [yyyy, m, d] (zero-based months, 1-based days)
  5. s = spacetime([2017, 5, 2])

  6. //iso
  7. s = spacetime('July 2, 2017 5:01:00')

  8. // All inputs accept a timezone, as 2nd param:
  9. s = spacetime(1489520157124, 'Canada/Pacific')
  10. s = spacetime('2019/05/15', 'Canada/Pacific')

  11. // or set the offset right in the date-string (ISO-8601)
  12. s = spacetime('2017-04-03T08:00:00-0700')
  13. // 'Etc/GMT-7'

  14. // Some helpers
  15. s = spacetime.now()
  16. s = spacetime.today() // This morning
  17. s = spacetime.tomorrow() // Tomorrow morning
  18. s = spacetime.min() // the earliest-possible date (271,821 bc)
  19. s = spacetime.max() // the furthest-possible future date (27k years from now)

  20. // To get the native Date object back
  21. // NOTE: this returns the date in the local browsers timezone
  22. jsDate = spacetimeDate.toNativeDate()
  23. ```

for fancier natural-language inputs, use compromise-dates.


Get & Set dates:


you can whip things around, but stay intuitive

  1. ``` js
  2. s.date() // 14
  3. s.year() // 2017
  4. s.season() // Spring
  5. s = s.hour(5) // Change to 5am
  6. s = s.date(15) // Change to the 15th

  7. s = s.day('monday') // Change to (this week's) monday
  8. s = s.day('monday', true) // go forward to monday
  9. s = s.day('monday', false) // go backward to monday

  10. s = s.month('march') // Change to (this year's) March 1st
  11. s = s.quarter(2) // Change to April 1st
  12. s.era() // 'BC'/'AD'
  13. s.decade() // 2000
  14. s.century() // 21

  15. // Percentage-based information
  16. s.progress().month = 0.23 // We're a quarter way through the month
  17. s.progress().day = 0.48 // Almost noon
  18. s.progress().hour = 0.99 // 59 minutes and 59 seconds

  19. // Add/subtract methods
  20. s = s.add(1, 'week')
  21. s = s.add(3, 'quarters')
  22. s = s.subtract(2, 'months').add(1, 'day')

  23. // start-of/end-of
  24. s = s.startOf('day') // 12:00am
  25. s = s.startOf('month') // 12:00am, April 1st
  26. s = s.endOf('quarter') // 11:59:59pm, June 30th

  27. s = s.nearest('hour') //round up/down to the hour
  28. s = s.nearest('quarter-hour') //5:15, 5:30, 5:45..
  29. s = s.next('month') //start of the next month
  30. s = s.last('year') //start of the last year

  31. // fill-in all dates between a range
  32. s.every('week', 'Jan 1st 2020') // (in tz of starting-date)

  33. //utilities:
  34. s.clone() // Make a copy
  35. s.isValid() // Sept 32nd → false
  36. s.isAwake() // it's between 8am → 10pm
  37. s.json() // get values in every unit as key-val object
  38. ```

if it's **_9am on tuesday_**, and you add a week, it will still be 9am on tuesday.
... even if some crazy changes happen.

setter methods also support a handy 2nd param that controls whether it should be set forward, or backward.

  1. ``` js
  2. s = s.time('4:00pm') // 4pm today
  3. s = s.time('4:00pm', true) // the next 4pm in the future
  4. s = s.time('4:00pm', false) // the most-recent 4pm

  5. s = s.set('march 5th 2020')
  6. s = s.set('march 4th') // 2020 (same year)
  7. s = s.set('march 4th', true) // 2021
  8. s = s.set('march 6th', false) // 2019
  9. ```

it's actually a little surprising how helpful this is.


Comparisons:


  1. ``` js
  2. let s = spacetime([2017, 5, 2])
  3. let start = s.subtract(1, 'milliseconds')
  4. let end = s.add(1, 'milliseconds')

  5. // gt/lt/equals
  6. s.isAfter(d) // True
  7. s.isEqual(d) // False
  8. s.isBefore(d) // False
  9. s.isBetween(start, end, inclusive?) // True

  10. // Comparison by unit
  11. s.isSame(d, 'year') // True
  12. s.isSame(d, 'date') // False
  13. s.diff(d, 'day') // 5
  14. s.diff(d, 'month') // 0

  15. //make a human-readable diff
  16. let before = spacetime([2018, 3, 28])
  17. let now = spacetime([2017, 3, 28]) //one year later
  18. now.since(before)
  19. // {diff: { months: 11, days: 30, ...},  rounded: 'in 12 months'  }
  20. ```

all comparisons are done with sensitivity of timezone - _8am EST_ is < _8am PST_.


Timezones:


the best way to describe a timezone is an IANA code:

  1. ``` js
  2. // Roll into a new timezone, at the same moment
  3. s = s.goto('Australia/Brisbane')
  4. ```

if you want to support relaxed timezone names like 'EST', Eastern time, use timezone-soft

  1. ``` js
  2. spacetime.extend(require('timezone-soft'))

  3. s = s.goto('milwaukee') // 'America/Chicago'
  4. s = s.goto('-7h') // UTC-7
  5. s = s.goto('GMT+8') // -8h!
  6. // (these should be used with some caution)
  7. ```

play-around with timezones, and their DST-changes:

  1. ``` js
  2. //list timezones by their current time
  3. spacetime.whereIts('8:30pm', '9:30pm') // ['America/Winnipeg', 'America/Yellowknife'... ]
  4. spacetime.whereIts('9am') //(within this hour)

  5. // Timezone metadata
  6. s.timezone().name // 'Canada/Eastern' (either inferred or explicit)
  7. s.hemisphere() // North
  8. s.timezone().current.offset // -4 (in hours)
  9. s.hasDST() // True
  10. s.isDST() // True

  11. //list all timezones
  12. spacetime.timezones()
  13. ```

you can flip-around the world pretty quick.

spacetime will use your local timezone, by default:

.goto(null) will pluck your current tz safely from your browser or computer.

  1. ``` js
  2. spacetime().time('4:30pm').goto('Europe/Paris').goto(null).time()
  3. // 4:30pm
  4. ```


Date Formatting:


it's _a pretty-sensible process_ to create nice-looking dates:

  1. ``` js
  2. // Date + time formatting
  3. s.format('time') // '5:01am'
  4. s.format('numeric-uk') // 02/03/2017
  5. s.format('month') // 'April'
  6. s.format('month-short') // 'Apr'
  7. s.format('month-pad') // '03'
  8. s.format('iso-month') // '04'

  9. //if you want more complex formats, use {}'s
  10. s.format('{year}-{date-pad}-{month-pad}') // '2018-02-02'
  11. s.format("{hour} o'clock") // '2 o'clock'
  12. s.format('{time}{ampm} sharp') // '2:30pm sharp'

  13. //if you prefer, you can also use unix-formatting
  14. s.unixFmt('yyyy.MM.dd h:mm a') // '2017.Nov.16 11:34 AM'
  15. ```



Limitations & caveats


◆ Historical timezone info


DST changes move around all the time, and timezones pop-in and out of existence.
We store and use only the latest DST information, and apply it to historical dates.

◆ International date line


.goto() never crosses the date-line. This is mostly the intuitive behaviour.

But if you're in Fiji (just west of the date line), and you go to Midway (just east of the date line), .goto() will subtract a bunch of hours, instead of just adding one.

◆ Destructive changes


if it's 2:30pm and you add a month, it should still be 2:30pm. Some changes are more destructive than others. Many of thse choices are subjective, but also sensible.

◆ 0-based vs 1-based ...


for better or worse we copy the JavaScript spec for 0-based months, and 1-based dates.

ISO-formatting is different, so keep on your toes.


Daylight-savings gotchas


We've written in detail about how spacetime handles Daylight-savings changes here

Fall DST changes have an hour that is repeated twice. There are a lot of tricky situations that come from this.
Add 10 minutes at 1:55am, and a spacetime diff may show -50mins. Within an hour of this change, some spacetime methods may be off-by-one hour.

Springtime DST changes are generally smoother than Fall ones.



Config:


Ambiguity warnings:


javascript dates use millisecond-epochs, instead of second-epochs, like some other languages.
This is a common bug, and spacetime can warn if you set an epoch within January 1970.
to enable:

  1. ``` js
  2. let s = spacetime(123456, 'UTC', {
  3.   silent: false
  4. })
  5. s.log() // "Jan 1st, 12:02am"
  6. ```

There is another situation where you may see a console.warn - if you give it a timezone, but then set a ISO-date string with a different offset, like 2017-04-03T08:00:00-0700 (-7hrs UTC offset).
It sets the timezone to UTC-7, but also gives a warning.

  1. ``` js
  2. let s = spacetime('2017-04-03T08:00:00-0700', 'Canada/Eastern', {
  3.   silent: false
  4. })
  5. s.timezone().name // "Etc/GMT-7"
  6. ```

Configure 'today' context:


spacetime makes some assumptions about some string inputs:

  1. ``` js
  2. // assumes start of month
  3. let s = spacetime('June 1992')
  4. s.date() // 1

  5. // assumes current year
  6. let s = spacetime('June 5th')
  7. s.year() // 2020 (or whatever it is now)

  8. // assumes Jan 1st
  9. let s = spacetime('2030')
  10. s.month() // 'January'
  11. ```

you can configure this assumed date (usually for testing) by passing it in as an option:

  1. ``` js
  2. let today = {
  3.   month: 3,
  4.   date: 4,
  5.   year: 1996
  6. }
  7. let s = spacetime('June 5th', null, { today: today })
  8. s.year() // 1996
  9. ```

it also works for spacetime.now(tz, {today:today}) and others.

Extending/Plugins:


you can throw any methods onto the Spacetime class you want, with spacetime.extend():

  1. ``` js
  2. spacetime.extend({
  3.   isHappyHour: function () {
  4.     return this.hour() === 16
  5.   }
  6. })

  7. let s = spacetime.now('Australia/Adelaide')
  8. s.isHappyHour()
  9. //false

  10. s = s.time('4:30pm')
  11. s.isHappyHour()
  12. //true
  13. ```

DD/MM/YYY interpretation:


by default spacetime uses the American interpretation of ambiguous date formats, like javascript does:

  1. ``` js
  2. spacetime('12/01/2018') //dec 1st

  3. // unless it's clear (>12):
  4. spacetime('13/01/2018') //jan 13th
  5. ```

you can change this behaviour by passing in a dmy option, like this:

  1. ``` js
  2. spacetime('12/01/2018', null, { dmy: true }) //jan 12th
  3. ```

this format is more common in britain, and south america.

Custom languages:


  1. ``` js
  2. let s = spacetime.now()
  3. s.i18n({
  4.   days: {
  5.     long: ['domingo', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado'],
  6.     short: ['dom', 'lun', 'mar', 'mié', 'jue', 'vie', 'sáb']
  7.   },
  8.   months: {
  9.     long: [...],
  10.     short: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'],
  11.   },
  12.   ampm: {
  13.     am: ' a. m.',
  14.     pm: ' a. m.'
  15.   },
  16.   useTitleCase: true // automatically in .format()
  17. });
  18. s.format('day') //'Sábado'
  19. ```

Configure start of week:


by default, the start of the week is monday.

You can determine the week by the official country setting, with spacetime-week

  1. ``` js
  2. let s = spacetime.now()
  3. s = s.weekStart('sunday')

  4. s = s.startOf('week')
  5. s.dayName()
  6. //sunday

  7. s = s.endOf('week')
  8. s.dayName()
  9. //saturday
  10. ```



See also:


- luxon - a small library from the clever moment people
- date-fns - an battle-hardened client-side Date utility
- sugarjs/dates - well-made date fns + timezone math
- Intl.DateTimeFormat - some _sorta-green_ in-browser date utilities

thank you to the amazing timeanddate.com

Apache 2.0