spencermountain/spacetime
{ "createdAt": "2017-02-22T15:31:34Z", "defaultBranch": "master", "description": "A lightweight javascript timezone library", "fullName": "spencermountain/spacetime", "homepage": "http://spacetime.how/", "language": "JavaScript", "name": "spacetime", "pushedAt": "2025-09-29T16:53:36Z", "stargazersCount": 4107, "topics": [ "date-formatting", "daylight-savings", "time-formatting", "timezone", "timezone-conversion", "timezones" ], "updatedAt": "2025-11-26T10:04:45Z", "url": "https://github.com/spencermountain/spacetime"}
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.
spacetime is a date-calculator,
It's very small, and very handy.
let s = spacetime.now()
s.diff(s.endOf('year'), 'days')// 292
s.substract(11, 'hours').time()// 6:50am
s = s.goto('Europe/Paris')s.isAfter(s.time('11:00am'))// true 🥐
- 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.
- frequent updates for approaching DST changes
- support for upcoming Temporal standards
<script src="https://unpkg.com/spacetime"></script><script> var d = spacetime('March 1 2012', 'America/New_York') //set the time d = d.time('4:20pm')
d = d.goto('America/Los_Angeles') d.time() //'1:20pm'</script>
npm install spacetime
const spacetime = require('spacetime')let d = spacetime.now('Europe/Paris')d.dayName()//'Wednesday'd.isAsleep()//truetypescript / babel / deno:
import spacetime from 'spacetime'let d = spacetime.now()d.format('nice')//'Apr 1st, 4:32pm'
plugins:
Date Inputs:
Section titled “Date Inputs:”we can parse all the normal stuff, and some fancy stuff:
//epochs = spacetime(1489520157124)
//array [yyyy, m, d] (zero-based months, 1-based days)s = spacetime([2017, 5, 2])
//isos = spacetime('July 2, 2017 5:01:00')
// All inputs accept a timezone, as 2nd param:s = spacetime(1489520157124, 'Canada/Pacific')s = spacetime('2019/05/15', 'Canada/Pacific')
// or set the offset right in the date-string (ISO-8601)s = spacetime('2017-04-03T08:00:00-0700')// 'Etc/GMT-7'
// Some helperss = spacetime.now()s = spacetime.today() // This mornings = spacetime.tomorrow() // Tomorrow mornings = spacetime.min() // the earliest-possible date (271,821 bc)s = spacetime.max() // the furthest-possible future date (27k years from now)
// To get the native Date object back// (this bails back to the local timezone)jsDate = spacetimeDate.toNativeDate()for fancier natural-language inputs, use compromise-dates.
Get & Set dates:
Section titled “Get & Set dates:”you can whip things around, but stay intuitive
s.date() // 14s.year() // 2017s.season() // Springs = s.hour(5) // Change to 5ams = s.date(15) // Change to the 15th
s = s.day('monday') // Change to (this week's) mondays = s.day('monday', true) // go forward to mondays = s.day('monday', false) // go backward to monday
s = s.month('march') // Change to (this year's) March 1sts = s.quarter(2) // Change to April 1sts.era() // 'BC'/'AD's.decade() // 2000s.century() // 21
// Percentage-based informations.progress().month = 0.23 // We're a quarter way through the months.progress().day = 0.48 // Almost noons.progress().hour = 0.99 // 59 minutes and 59 seconds
// Add/subtract methodss = s.add(1, 'week')s = s.add(3, 'quarters')s = s.subtract(2, 'months').add(1, 'day')
// start-of/end-ofs = s.startOf('day') // 12:00ams = s.startOf('month') // 12:00am, April 1sts = s.endOf('quarter') // 11:59:59pm, June 30th
s = s.nearest('hour') //round up/down to the hours = s.nearest('quarter-hour') //5:15, 5:30, 5:45..s = s.next('month') //start of the next months = s.last('year') //start of the last year
// fill-in all dates between a ranges.every('week', 'Jan 1st 2020') // (in tz of starting-date)
//utilities:s.clone() // Make a copys.isValid() // Sept 32nd → falses.isAwake() // it's between 8am → 10pms.json() // get values in every unit as key-val objectif 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.
s = s.time('4:00pm') // 4pm todays = s.time('4:00pm', true) // the next 4pm in the futures = s.time('4:00pm', false) // the most-recent 4pm
s = s.set('march 5th 2020')s = s.set('march 4th') // 2020 (same year)s = s.set('march 4th', true) // 2021s = s.set('march 6th', false) // 2019it’s actually a little surprising how helpful this is.
Comparisons:
Section titled “Comparisons:”let s = spacetime([2017, 5, 2])let start = s.subtract(1, 'milliseconds')let end = s.add(1, 'milliseconds')
// gt/lt/equalss.isAfter(d) // Trues.isEqual(d) // Falses.isBefore(d) // Falses.isBetween(start, end, inclusive?) // True
// Comparison by units.isSame(d, 'year') // Trues.isSame(d, 'date') // Falses.diff(d, 'day') // 5s.diff(d, 'month') // 0
//make a human-readable difflet before = spacetime([2018, 3, 28])let now = spacetime([2017, 3, 28]) //one year laternow.since(before)// {diff: { months: 11, days: 30, ...}, rounded: 'in 12 months' }all comparisons are done with sensitivity of timezone - 8am EST is < 8am PST.
Timezones:
Section titled “Timezones:”the best way to describe a timezone is an IANA code:
// Roll into a new timezone, at the same moments = s.goto('Australia/Brisbane')if you want to support relaxed timezone names like 'EST', Eastern time, use timezone-soft
spacetime.extend(require('timezone-soft'))
s = s.goto('milwaukee') // 'America/Chicago's = s.goto('-7h') // UTC-7s = s.goto('GMT+8') // -8h!// (these should be used with some caution)play-around with timezones, and their DST-changes:
//list timezones by their current timespacetime.whereIts('8:30pm', '9:30pm') // ['America/Winnipeg', 'America/Yellowknife'... ]spacetime.whereIts('9am') //(within this hour)
// Timezone metadatas.timezone().name // 'Canada/Eastern' (either inferred or explicit)s.hemisphere() // Norths.timezone().current.offset // -4 (in hours)s.hasDST() // Trues.isDST() // True
//list all timezonesspacetime.timezones()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.
spacetime().time('4:30pm').goto('Europe/Paris').goto(null).time()// 4:30pmIf, for some reason, you want to change the timezone without changing the date, you can do this via the .timezone(str) setter:
let s = spacetime('2023-01-01T5:30[America/Denver]')s = s.timezone('Europe/Zagreb') // hot-swapconsole.log(s.isoFull())// '2023-01-01T05:30:00.000+01:00[Europe/Zagreb]' (same time, new tz)
Date Formatting:
Section titled “Date Formatting:”it’s a pretty-sensible process to create nice-looking dates:
// Date + time formattings.format('time') // '5:01am's.format('numeric-uk') // 02/03/2017s.format('month') // 'April's.format('month-short') // 'Apr's.format('month-pad') // '03's.format('iso-month') // '04'
//if you want more complex formats, use {}'ss.format('{year}-{date-pad}-{month-pad}') // '2018-02-02's.format("{hour} o'clock") // '2 o'clock's.format('{time}{ampm} sharp') // '2:30pm sharp'
//if you prefer, you can also use unix-formattings.unixFmt('yyyy.MM.dd h:mm a') // '2017.Nov.16 11:34 AM'
// support for the new Temporal ISO formats.format('iso-full') // '2011-12-03T10:15:30.010+01:00[Europe/Paris]'
// support for the SQL ISO 9075 formats.format('sql') // '2011-12-03 10:15:30'
Limitations & caveats
Section titled “Limitations & caveats”◆ Historical timezone info
Section titled “◆ 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
Section titled “◆ 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
Section titled “◆ 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 …
Section titled “◆ 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.
see more considerations and gotchas
Daylight-savings gotchas
Section titled “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:
Section titled “Config:”Ambiguity warnings:
Section titled “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:
let s = spacetime(123456, 'UTC', { silent: false})s.log() // "Jan 1st, 12:02am"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.
let s = spacetime('2017-04-03T08:00:00-0700', 'Canada/Eastern', { silent: false})s.timezone().name // "Etc/GMT-7"Configure ‘today’ context:
Section titled “Configure ‘today’ context:”spacetime makes some assumptions about some string inputs:
// assumes start of monthlet s = spacetime('June 1992')s.date() // 1
// assumes current yearlet s = spacetime('June 5th')s.year() // 2020 (or whatever it is now)
// assumes Jan 1stlet s = spacetime('2030')s.month() // 'January'you can configure this assumed date (usually for testing) by passing it in as an option:
let today = { month: 3, date: 4, year: 1996}let s = spacetime('June 5th', null, { today: today })s.year() // 1996it also works for spacetime.now(tz, {today:today}) and others.
Extending/Plugins:
Section titled “Extending/Plugins:”you can throw any methods onto the Spacetime class you want, with spacetime.extend():
spacetime.extend({ isHappyHour: function () { return this.hour() === 16 }})
let s = spacetime.now('Australia/Adelaide')s.isHappyHour()//false
s = s.time('4:30pm')s.isHappyHour()//trueDD/MM/YYY interpretation:
Section titled “DD/MM/YYY interpretation:”by default spacetime uses the American interpretation of ambiguous date formats, like javascript does:
spacetime('12/01/2018') //dec 1st
// unless it's clear (>12):spacetime('13/01/2018') //jan 13thyou can change this behaviour by passing in a dmy option, like this:
spacetime('12/01/2018', null, { dmy: true }) //jan 12ththis format is more common in britain, and south america.
Custom languages:
Section titled “Custom languages:”let s = spacetime.now()s.i18n({ days: { long: ['domingo', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado'], short: ['dom', 'lun', 'mar', 'mié', 'jue', 'vie', 'sáb'] }, months: { long: [...], short: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'], }, ampm: { am: ' a. m.', pm: ' a. m.' }, distance: { past: 'past', future: 'future', present: 'present', now: 'now', almost: 'almost', over: 'over', pastDistance: (value) => `${value} ago`, futureDistance: (value) => `in ${value}` }, units: { second: 'second', seconds: 'seconds', minute: 'minute', minutes: 'minutes', hour: 'hour', hours: 'hours', day: 'day', days: 'seconds', month: 'month', months: 'months', year: 'year', years: 'years', }, useTitleCase: true // automatically in .format()});s.format('day') //'Sábado'Configure start of week:
Section titled “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
let s = spacetime.now()s = s.weekStart('sunday')
s = s.startOf('week')s.dayName()//sunday
s = s.endOf('week')s.dayName()//saturday
See also:
Section titled “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
- BurntSushi/Jiff - Rust/wasm date library
thank you to the amazing timeanddate.com
Apache 2.0