@tubular/time
v3.9.2
Published
Date/time, IANA timezones, leap seconds, TAI/UTC conversions, calendar with settable Julian/Gregorian switchover
Downloads
1,537
Maintainers
Readme
@tubular/time
Not all days are 24 hours. Some are 23 hours, or 25, or even 23.5 or 24.5 or 47 hours. Some minutes are 61 seconds long. How about a Thursday followed directly by a Saturday, giving Friday the slip? Or a September only 19 days long? This is a date/time library for handling both day-to-day situations (so to speak) and some weird ones too.
Key features
- Mutable and immutable DateTime objects supporting the Gregorian and Julian calendar systems, with settable crossover.
- IANA timezone support, with features beyond formatting using timezones, such as parsing, accessible listings of all available timezones (single-array list, grouped by UTC offset, or grouped by region), and live updates of timezone definitions.
- Supports leap seconds and conversions between TAI (International Atomic Time) and UTC (Universal Coordinated Time).
- Supports and recognizes negative Daylight Saving Time.
- Extensive date/time manipulation and calculation capabilities.
- Many features available using a familiar Moment.js-style API.
- Astronomical time conversions among TDT (Terrestrial Dynamic Time), UT1, UTC and TAI.
- Local mean time, by geographic longitude, to one minute (of time) resolution.
- Astronomical time conversions among TDT (Terrestrial Dynamic Time), UT1, UTC and TAI, as well as local mean time, by geographic longitude, to one minute (of time) resolution.
- Internationalization via JavaScript’s
Intl
Internationalization API, with additional built-in i18n support for issues not covered byIntl
, and US-English fallback for environments withoutIntl
support. - Package suitable for tree shaking and Angular optimization.
- Full TypeScript typing support.
@tubular/time is a collection of date and time classes and functions, providing extensive internationalized date/time parsing and formatting capabilities, date/time manipulations such as field-specific add/subtract, set, and roll; calendar computations; support for live-updatable IANA time zones; and a settable Julian/Gregorian calendar switchover date.
This library was originally developed for an astronomy website, https://skyviewcafe.com, and has some features of particular interest for astronomy and historical events, but has been expanded to provide many features similar to the now-legacy-status Moment.js.
Unlike Moment.js, IANA timezone handling is built in, not a separate module, with a compact set of timezone definitions that reach roughly five years into the past and five years into the future, expanded into the past and future using Daylight Saving Time rules and/or values extracted from Intl.DateTimeFormat
. Unlike the Intl
API, the full list of available timezones is exposed, facilitating the creation of timezone selection interfaces.
Two alternate large timezone definition sets, of approximately 280K each, are available, each serving slightly different purposes. These definitions can be bundled at compile time, or loaded dynamically at run time. You can also download live updates when the IANA Time Zone Database is updated.
- Installation
- Basic usage
- Formatting output
- Format string tokens
- Moment.js-style localized formats
- @tubular/time
Intl.DateTimeFormat
shorthand string formats - Pre-defined formats
- Parsing with a format string, and optionally a locale
- Converting timezones
- Converting locales
- Defining and updating timezones
- The
YMDDate
andDateAndTime
objects - Reading individual
DateTime
fields - Modifying
DateTime
values - Time value
- Timezone offsets from UTC
- Validation
- Comparison and sorting
- Monthly calendar generation
- Dealing with weird months
- Dealing with weird days
- Leap Seconds, TAI, and Julian Dates
- Global default settings
- The
DateTime
class - The
Calendar
class - The
Timezone
class - Other functions available on
ttime
- Constants available on
ttime
Installation
Via npm
npm install @tubular/time
import { ttime, DateTime, Timezone
...} from '@tubular/time'; // ESM
...or...
const { ttime, DateTime, Timezone
...} = require('@tubular/time/cjs'); // CommonJS
Documentation examples will assume @tubular/time has been imported as above.
Via <script>
tag
To remotely download the full code as an ES module:
<script type="module">
import('https://unpkg.com/@tubular/time/dist/fesm2015/index.mjs').then(pkg => {
const { ttime, DateTime, Timezone} = pkg;
// ...
});
</script>
For the old-fashioned UMD approach (which can save you from about 560K of extra data):
<script src="https://unpkg.com/@tubular/time/dist/data/timezone-large-alt.js"></script>
<script src="https://unpkg.com/@tubular/time/dist/umd/index.js"></script>
(Or .../data/timezone-large.js"
)
The script element just above the index.js
URL is an example of optionally loading extended timezone definitions. Such a script element, if used, should precede the index.js
script.
The @tubular/time package will be available via the global variable tbTime
. tbTime.ttime
is the default function, and other functions, classes, and constants will also be available on this variable, such as tbTime.DateTime
, tbTime.julianDay
, tbTime.TIME_MS
, etc.
Basic usage
While there are a wide range of functions and classes available from @tubular/time, the workhorse is the ttime()
function, which produces immutable instances of the DateTime
class.
function ttime(initialTime?: number | string | DateAndTime | Date | number[] | null, format?: string, locale?: string | string[]): DateTime
Creating immutable DateTime
instances with ttime()
DateTime
instances can be created in many ways. The simplest way is to create a current-time instance, done by passing no arguments at all. Dates and times can also be expressed as strings, objects, and arrays of numbers.
| | | .toString() |
|---------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ttime()
| Current time. | DateTime<2021‑01‑28T03:29:12.040 ‑05:00>
|
| ttime('1969‑07‑12T20:17')
ttime('1969‑07‑12T20:17Z')
ttime('20210704T0945-03')
ttime('2021‑W04‑4')
| DateTime from an ISO-8601 date/time string.A trailing Z
causes the time to be parsed as UTC. Without it, your default timezone is assumed. | DateTime<1969‑07‑12T20:17:00.000 ‑04:00§>
DateTime<1969-07-12T20:17:00.000 +00:00>
DateTime<2021-07-04T09:45:00.000 -03:00>
DateTime<2021-01-28T00:00:00.000 -05:00>
|
| ttime('2021-w05-5')
| DateTime from an ISO-8601-like date/time variant (lowercase w
instead of uppercase W
) for locale-based week numbering. | DateTime<2021-01-28T00:00:00.000 -05:00>
|
| ttime('2017‑03‑02 14:45 Europe/Paris')
| From an ISO-8601 date/time (variant with space instead of T
) and IANA timezone. | DateTime<2017-03-02T14:45:00.000 +01:00>
|
| ttime('20:17:15')
| Dateless time from an ISO-8601 time string. | DateTime<20:17:15.000>
|
| ttime(1200848400000)
| From a millisecond timestamp. | DateTime<2008-01-20T12:00:00.000 -05:00>
|
| ttime({ tai: 1200848400000 })
| From a TAI millisecond timestamp. | DateTime<2008-01-20T12:00:00.000 -05:00>
|
| ttime({ y: 2008, m: 1, d: 20, hrs: 12, min: 0 })
| From a DateAndTime
object, short-style field names. | DateTime<2008-01-20T12:00:00.000 -05:00>
|
| ttime({ year: 2008, month: 1, day: 20, hour: 12, minute: 0 })
| From a DateAndTime
object, long-style field names. | DateTime<2008-01-20T12:00:00.000 -05:00>
|
| ttime([2013, 12, 11, 10, 9, 8, 765])
| From a numeric array: year, month, day, (hour (0-23), minute, second, millisecond), in that order. | DateTime<2013-12-11T10:09:08.765 -05:00>
|
| ttime(new Date(2008, 0, 20, 12, 0))
| From a JavaScript Date
object. | DateTime<2008-01-20T12:00:00.000 -05:00>
|
| ttime('Feb 26 2021 11:00:00 GMT‑0500')
| From an ECMA-262 string(Parsing performed by JavaScript Date('
time_string')
). | DateTime<2021-02-26T11:00:00.000 ‑05:00>
|
| ttime.unix(1318781876.721)
| From a Unix timestamp. | DateTime<2011-10-16T12:17:56.721 -04:00§>
|
| ttime.unix(1318781876.721, 'UTC')
| From a Unix timestamp, with timezone. | DateTime<2011-10-16T16:17:56.721 +00:00>
|
When dealing with Daylight Saving Time, and days when clocks are turned backward, some hour/minute combinations are repeated. The time might be 1:59, go back to 1:00, then forward again to 1:59, and only after hitting 1:59 for this second time during the day, move forward to 2:00.
By default, any ambiguous time is treated as the earlier time, the first occurrence of that time during a day. You can, however, use either an explicit UTC offset, or a subscript 2 (₂), to indicate the later time.
ttime('11/7/2021 1:25 AM America/Denver', 'MM/DD/YYYY h:m a z').toString()
→DateTime<2021-11-07T01:25:00.000 -06:00§>
ttime('11/7/2021 1:25₂ AM America/Denver', 'MM/DD/YYYY h:m a z').toString()
→DateTime<2021-11-07T01:25:00.000₂-07:00>
ttime('2021-11-07 01:25 -07:00 America/Denver').toString()
→DateTime<2021-11-07T01:25:00.000₂-07:00>
Formatting output
Dates and times can be formatted in many ways, using a broad selection of format tokens, described in the table below.
For the greatest adherence to localized formats for dates and times, you can use the IXX format strings, which call directly upon Intl.DateTimeFormat
(if available) to create localized dates, times, and combined date/times.
You can also produce more customized, flexible formatting, specifying the order, positioning, and style (text vs. number, fully spelled out or abbreviated, with or without leading zeros) of each date/time field, with embedded punctuation and text as desired.
For example:
ttime().format('ddd MMM D, y N [at] h:mm A z')
→Wed Feb 3, 2021 AD at 8:59 PM EST
ttime().toLocale('de').format('ddd MMM D, y N [at] h:mm A z')
→Mi 02 3, 2021 n. Chr. at 9:43 PM GMT-5
Please note that most unaccented Latin letters (a-z, A-Z) are interpreted as special formatting characters, as well as the tilde (~
), so when using those characters as literal text they should be surrounded with square brackets, as with the word “at” in the example above.
Special CJK date formatting options
A few of the formatting tokens below can have an optional trailing tilde (~
) added. This is for special handling of Chinese, Japanese, and Korean (CJK) date notation. The ~
is replaced, where appropriate, with 年
, 月
, or 日
for Chinese and Japanese, and with 년
, 월
, or 일
for Korean. Korean formatting also adds a space character when the following character is a letter or digit, but not when punctuation or the end of the format string comes next.
For all other languages, ~
is replaced with a space character when the following character is a letter or digit, or simply removed when followed by punctuation or the end of the format string.
For example:
ttime().toLocale('zh').format('MMM~YYYY~')
→8月2021年
ttime().toLocale('es').format('MMM~YYYY~')
→ago 2021
Format string tokens
| | Token | Output |
|-----------------------------------|------------------------------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Era | NNNNNNNN, NN, N | BC ADAbbreviated era (no distinction between narrow and abbreviated, as in Moment.js). |
| | NNNN | Before Christ, Anno DominiLong-form era. |
| | n | BCAbbreviated era, only shows for BC, not AD. When year is AD, leading space before n
token is removed. |
| Year | YYYYYY | -001970 -001971 ... +001907 +001971Always-signed years, padded to six digits. |
| | YYYY YYYY~ | 1970 1971 ... 2029 2030Padded to at least four digits. With ~
, 年
or 년
is added when needed for CJK locales, otherwise replaced by a space character or empty string. |
| | YY | 70 71 ... 29 30Padded to two digits with leading zero if necessary. |
| | Y Y~ | 1970 1971 ... 9999 +10000 +10001Padded to at least four digits, +
sign shown when over 9999. With ~
, 年
or 년
is added when needed for CJK locales, otherwise ~
is replaced by a space character or empty string. |
| | y y~ | 1 2 ... 2020 ...Era year, for use with BC/AD, never 0 or negative. With ~
, 年
or 년
is added when needed for CJK locales, otherwise ~
is replaced by a space character or empty string. |
| Week year (ISO) | GGGG | 1970 1971 ... 2029 2030, +
sign shown when over 9999. |
| | GG | 70 71 ... 29 30Padded to two digits with leading zero if necessary. |
| Week year (locale) | gggg | 1970 1971 ... 2029 2030, +
sign shown when over 9999. |
| | gg | 70 71 ... 29 30Padded to two digits with leading zero if necessary. |
| Quarter | Qo | 1st 2nd 3rd 4th |
| | Q | 1 2 3 4 |
| Month | MMMM MMMM~ | January February ... November December1月 2月 ... 11月 12月 • 一月 二月 ... 十一月 十二月 • 1월 2월 ... 11월 12월For CJK locales, 月
or 월
is added when using either the MMMM
and MMMM~
token, but using MMMM~
allows the position of the ~
to be replaced with a blank, when appropriate, for other languages. |
| | MMM MMM~ | Jan Feb ... Nov Dec1月 2月 ... 11月 12月 • 1월 2월 ... 11월 12월With ~
, 月
or 월
is added when needed for CJK locales, otherwise ~
is replaced by a space character or empty string. |
| | MM MM~ | 01 02 ... 11 1201月 02月 ... 11月 12月 • 01월 02월 ... 11월 12월With ~
, 月
or 월
is added when needed for CJK locales, otherwise ~
is replaced by a space character or empty string. |
| | M M~ | 1 2 ... 11 121月 2月 ... 11月 12月 • 1월 2월 ... 11월 12월With ~
, 月
or 월
is added when needed for CJK locales, otherwise ~
is replaced by a space character or empty string. |
| | Mo | 1st 2nd ... 11th 12th |
| Week (ISO) | WW | 01 02 ... 52 53 |
| | W | 1 2 ... 52 53 |
| Week (locale) | ww | 01 02 ... 52 53 |
| | w | 1 2 ... 52 53 |
| Day of month | DD DD~ | 01 02 ... 30 31With ~
, 日
or 일
is added when needed for CJK locales, otherwise ~
is replaced by a space character or empty string. |
| | D D~ | 1 2 ... 30 31With ~
, 日
or 일
is added when needed for CJK locales, otherwise ~
is replaced by a space character or empty string. |
| | Do | 1st 2nd ... 30th 31st |
| Day of year | DDDD | 001 002 ... 364 365 366 |
| | DDD | 1 2 ... 364 365 366 |
| Day of week | dddd | Sunday Monday ... Friday Saturday |
| | ddd | Sun Mon ... Fri Sat |
| | dd | Su Mo ... Fr Sa |
| | d | 0 1 ... 5 6 |
| | do | 0th 1st ... 5th 6th |
| Day of Week (ISO) | E | 1 2 ... 6 7 |
| Day of Week (locale) | e | 1 2 ... 6 7Note: this is 1-based, not 0-based, as in Moment.js. |
| Hour | HH | 00-23 |
| | H | 0-23 |
| | hh | 01-12, for use with AM/PM |
| | h | 1-12, for use with AM/PM |
| | KK | 00-11, for use with AM/PM |
| | K | 0-11, for use with AM/PM |
| | kk | 01-24 |
| | k | 1-24 |
| Day period | A | AM PM |
| | a | am pm |
| Minute | mm | 00-59 |
| | m | 0-59 |
| Second | ss | 00-59 |
| | s | 0-59 |
| Fractional seconds | S | 0-9 (tenths of a second) |
| | SS | 00-99 (hundredths of a second) |
| | SSS | 000-999 (milliseconds) |
| | SSSS... | Additional zeros after milliseconds. |
| Timezone | ZZZ | America/New_York, Europe/Paris, etc.IANA timezone, if available. |
| | zzz | Australian Central Standard Time, Pacific Daylight Time, etc.Long form names are only for output — cannot be parsed. |
| | ZZ | -0700 -0600 ... +0600 +0700If used with a TAI time, the displayed offset will be the difference between TAI and UTC at a given moment in time. Outside of the well-defined span of officially-declared leap seconds, this offset might be displayed with millisecond precision. |
| | zz, z | EST, CDT, MST, PDT, AEST, etc.Please note that timezones in this format are not internationalized, and are not unambiguous when parsed. |
| | Z | -07:00 -06:00 ... +06:00 +07:00 |
| Unix timestamp, UTC | X | 1360013296 |
| Unix millisecond timestamp, UTC | x | 1360013296123 |
| Unix timestamp, epoch | XX | 1360013296 |
| Unix millisecond timestamp, epoch | xx | 1360013296123 |
| Unix timestamp, TAI | X | 1360013331 |
| Unix millisecond timestamp, TAI | x | 1360013331123 |
| Daylight Saving Time indicator | V | § # ^ ~ ❄Symbol indicating DST is in effect.This is typically §, meaning the clock has been turned forward one hour.# means two hours forward, ^ means half an hour, ~ is any other forward amount.❄ is negative DST, i.e. “Winter Time”.Renders one blank space when DST is not in effect. |
| | v | Same as above, but no blank space when DST is not in effect. |
| Occurrence indicator | R | 1:00 , 1:01 ... 1:58 , 1:59 , 1:00₂, 1:01₂ ... 1:58₂, 1:59₂, 2:00 , 2:01A subscript 2 (₂) that denotes the second occurrence of the same clock time during a day when clocks are turned back for Daylight Saving Time. |
| | r | Same as above, but no blank space when subscript isn’t needed. |
Moment.js formats not supported by @tubular/time: DDDo, Wo, wo, yo
@tubular/time formats not supported by Moment.js: KK, K, kk, k, ZZZ, V, v, R, r, n, IXX (IFF, IFL, IFM... IxM, IxS)
Moment.js-style localized formats
| | Token | Output | | -------|------:|-----------------------------------------| | Month name, day of month, day of week, year, time | LLLL | Thursday, September 4, 1986 at 8:30 PM | | | llll | Thu, Sep 4, 1986 8:30 PM | | Month name, day of month, year, time | LLL | September 4, 1986 8:30 PM | | | lll | Sep 4, 1986 8:30 PM | | Month name, day of month, year | LL | September 4, 1986 | | | ll | Sep 4, 1986 | | Month numeral, day of month, year | L | 09/04/1986 | | | l | 9/4/1986 | | Time with seconds | LTS | 8:30:25 PM | | Time | LT | 8:30 PM |
@tubular/time Intl.DateTimeFormat
shorthand string formats
These start with a capital letter I
, followed by one letter for the date format, which corresponds to the dateStyle
option of Intl.DateTimeFormat
, and one letter for the time format, corresponding to the timeStyle
option.
The capital letters F
, L
, M
, and S
correspond to the option values 'full'
, 'long'
, 'medium'
, and 'short'
. ILS
thus specifies a long style date and a short style time. IL
is a long style date alone, without time. IxS
is a short style time without a date.
Examples
| Format | Output |
|---|---|
| IFF | Thursday, September 4, 1986 at 8:30:00 PM Eastern Daylight Time
|
| ILM | September 4, 1986 at 8:30:00 PM
|
| IS | 9/4/86
|
| IxL | 8:30:00 PM EDT
|
You can also augment these formats with brace-enclosed Intl.DateTimeFormatOptions
, such as:
IMM{hourCycle:23h}
...which will start with whatever the localized time formatting is and force it into 24-hour time, whether the standard localized form is a 12- or 24-hour format. Note that no quotes are placed around the option values, as they would be in JavaScript/TypeScript code.
Pre-defined formats
ttime.DATETIME_LOCAL = 'Y-MM-DD[T]HH:mm';
ttime.DATETIME_LOCAL_SECONDS = 'Y-MM-DD[T]HH:mm:ss';
ttime.DATETIME_LOCAL_MS = 'Y-MM-DD[T]HH:mm:ss.SSS';
ttime.DATE = 'Y-MM-DD';
ttime.TIME = 'HH:mm';
ttime.TIME_SECONDS = 'HH:mm:ss';
ttime.TIME_MS = 'HH:mm:ss.SSS';
ttime.WEEK = 'GGGG-[W]WW';
ttime.WEEK_AND_DAY = 'GGGG-[W]WW-E';
ttime.WEEK_LOCALE = 'gggg-[w]ww';
ttime.WEEK_AND_DAY_LOCALE = 'gggg-[w]ww-e';
ttime.MONTH = 'Y-MM';
Parsing with a format string, and optionally a locale
(As viewed via formatted output)
| | .format('IMM') |
|---|---|
| ttime('02/03/32', 'MM-DD-YY')
| Feb 3, 2032, 12:00:00 AM
|
| ttime('02/03/32', 'DD-MM-YY')
| Mar 2, 2032, 12:00:00 AM
|
| ttime('02/03/32 4:30 pm', 'DD-MM-YY hh:mm a', 'fr')
| 2 mars 2032 à 16:30:00
|
| ttime('02/03/32', 'DD-MM-YYYY')
| Mar 2, 0032, 12:00:00 AM
|
| ttime('2032-03-02T16:30', null, 'ru')
| 2 мар. 2032 г., 16:30:00
|
| ttime('2032-03-02T16:30', null, 'ar-sa')
| ٠٢/٠٣/٢٠٣٢ ٤:٣٠:٠٠ م
|
| ttime('2032-03-02T16:30', null, 'zh-cn')
| 2032年3月2日 下午4:30:00
|
Converting timezones
ttime('2005-10-10 16:30 America/Los_Angeles').tz('Europe/Warsaw').toString()
→DateTime<2005-10-11T01:30:00.000 +02:00>
Please note that if you pass a second argument of true
, the timezone is changed, but the wall time stays the same. This same option to preserve wall time is available for the utc()
and local()
methods, where the optional boolean value will be the one and only argument.
ttime('2005-10-10 16:30 America/Los_Angeles').tz('Europe/Warsaw', true).toString()
→DateTime<2005-10-10T16:30:00.000 +02:00>
ttime('2005-10-10 16:30 America/Los_Angeles').utc().toString()
→DateTime<2005-10-10T23:30:00.000 +00:00>
ttime('2005-10-10 16:30 America/Los_Angeles').utc().toString(true)
→DateTime<2005-10-10T16:30:00.000 +00:00>
// Local zone is America/New_York
ttime('2005-10-10 16:30 America/Los_Angeles').local().toString()
→DateTime<2005-10-10T19:30:00.000 +04:00>
Converting locales
ttime('7. helmikuuta 2021', 'IL', 'fi').toLocale('de').format('IL')
→7. Februar 2021
Defining and updating timezones
These functions define the size and behavior of the IANA timezone definitions used by @tubular/time:
ttime.initTimezoneSmall();
ttime.initTimezoneLarge();
ttime.initTimezoneLargeAlt();
By default, @tubular/time is set up using initTimezoneSmall()
. This covers explicitly-defined timezone information for roughly the release date of the version of @tubular/time you’re using, +/- five years, supplemented by rules-based extensions (i.e. knowing that for a particular timezone, say, “DST starts on the last Sunday of March and ends on the last Sunday of October”), and further supplemented by information extracted from Intl
, when available.
With proper tree-shaking, the code footprint of @tubular/time should be less than 150K when using the small timezone definitions.
Using initTimezoneLarge()
provides the full IANA timezone database. Using this will increase code size by about 280K, presuming that your build process is smart enough to have otherwise excluded unused code in the first place.
initTimezoneLargeAlt()
provides a slight variant of the full IANA timezone database, and is also roughly 280K. This variant rounds all timezone offsets to full minutes, and adjusts a small number of fairly old historical changes by a few hours so that only the time-of-day ever goes backward, never the calendar date. It’s generally more than enough trouble for software to cope with missing and/or repeated hours during a day; initTimezoneLargeAlt()
makes sure the date/time can’t be, say, the 19th of the month, then the 18th, and then the 19th again, as happens with the unmodified America/Juneau timezone during October 1867.
For browser-based inclusion of timezone definitions, if not relying on a tool like webpack to handle such issues for you, you can also include full timezone definitions this way:
<script src="https://unpkg.com/@tubular/time/dist/data/timezone-large.js"></script>
...or...
<script src="https://unpkg.com/@tubular/time/dist/data/timezone-large-alt.js"></script>
Either of these should appear before the script tag that loads @tubular/time itself.
Live timezone updates
Timezone definitions can be updated live as well. Different polling methods are needed for Node.js code or browser-hosted code, since both environments access web resources in very different ways (and browsers have CORS issues, which Node.js does not).
To be informed when a live timezone update takes place, add and remove update listeners using these functions:
function addZonesUpdateListener(listener: (result: boolean | Error) => void): void;
function removeZonesUpdateListener(listener: (result: boolean | Error) => void): void;
function clearZonesUpdateListeners(): void
The result received by a callback is true
if an update was successful, and caused changes in timezone definitions, false
if successful, but no changes occurred, or an instance of Error
, indicating an error (probably an HTTP failure) has occurred.
For example:
const listener = result => console.log(result); // Keep in a variable if removal is needed later
ttime.addZonesUpdateListener(listener);
// Later on in the code...
ttime.removeZonesUpdateListener(listener);
Why use a listener? Because you might want to recalculate previously calculated times, which possibly have changed due to timezone definition changes. For example, imagine you have a video meeting scheduled for 10:00 in a client’s timezone, which, when you first schedule it, was going to be 15:00 in your timezone. Between the time you scheduled the meeting, however, and when the meeting actually takes place, the switch to Daylight Saving Time is cancelled for the client’s timezone. If you still intend to talk to your client at 10:00 their time, you have to meet at 16:00 in your timezone instead.
To poll for for timezone updates at a regular interval, use:
function pollForTimezoneUpdates(zonePoller: IZonePoller | false, name: ZoneOptions = 'small', intervalDays = 1): void;
zonePoller
: EitherzonePollerBrowser
(fromtbTime.zonePollerBrowser
) orzonePollerNode
(usingimport
orrequire
, from'@tubular/time'
). If you pass the boolean valuefalse
, polling ceases.name
: One of'small'
,'large'
, or'large-alt'
. Defaults to'small'
.intervalDays
: Frequency of polling, in days. Defaults to 1 day. The fastest allowed rate is once per hour (~0.04167 days).
You can also do a one-off request:
function getTimezones(zonePoller: IZonePoller | false, name: ZoneOptions = 'small'): Promise<boolean>;
zonePoller
and name
are the same as above. Any periodic polling done by pollForTimezoneUpdates()
is canceled. You can get a response via registered listeners, but this function also returns a Promise
. The promise either resolves to a boolean value, or is rejected with an Error
.
The YMDDate
and DateAndTime
objects
YMDate
:
{
y: 2021, // short for year
q: 1, // short for quarter
m: 2, // short for month
d: 4, // short for day
dow: 4, // short for dayOfWeek (output only)
dowmi: 1, // dayOfWeekMonthIndex (output only)
dy: 35, // short for dayOfYear
n: 18662, // short for epochDay
j: false, // short for isJulian
year: 2021,
quarter: 1, // quarter of the year 1-4
month: 2,
day: 4,
dayOfWeek: 4, // Day of week as 0-6 for Sunday-Saturday (output only)
dayOfWeekMonthIndex: 1, // Day of week month index, 1-5, e.g. 2 for 2nd Tuesday of the month (output only)
dayOfYear: 35,
epochDay: 18662, // days since January, 1 1970
isJulian: false, // true if a Julian calendar date instead of a Gregorian date
yw: 2021, // short for yearByWeek
w: 5, // short for week
dw: 4, // short for dayByWeek
yearByWeek: 2021, // year that accompanies an ISO year/week/day-of-week style date
week: 5, // week that accompanies an ISO year/week/day-of-week style date
dayByWeek: 4, // day that accompanies an ISO year/week/day-of-week style date
ywl: 2021, // short for yearByWeekLocale
wl: 6, // short for weekLocale
dwl: 5, // short for dayByWeekLocale
yearByWeekLocale: 2021, // year that accompanies a locale-specific year/week/day-of-week style date
weekLocale: 6, // week that accompanies a locale-specific year/week/day-of-week style date
dayByWeekLocale: 5, // day that accompanies a locale-specific year/week/day-of-week style date
error: 'Error description if applicable, otherwise undefined'
}
DateAndTime
, which extends the YMDDate
interface:
{
hrs: 0, // short for hour
min: 18, // short for minute
sec: 32, // short for second
hour: 0,
minute: 18,
second: 32,
millis: 125, // 0-999 milliseconds part of time
utcOffset: -18000, // offset (in seconds) from UTC, negative west from 0°, including DST offset when applicable
dstOffset: 0, // DST offset, in minutes - usually positive, but can be negative (output only)
occurrence: 1, // usually 1, but can be 2 for the second occurrence of the same wall clock time during a single day, caused by clock being turned back for DST
deltaTai: 37, // How much (in seconds) TAI exceeds UTC or UT1 at given moment in time (output only)
/* In the well-defined range for UTC, deltaTai is always an integer value.
Outside that range it can be a non-integer with millisecond precision. */
jde: 2459249.722008264, // Julian days, ephemeris
mjde: 59249.22200826416, // Modified Julian days, ephemeris
jdu: 2459249.7212051502, // Julian days, UT
mjdu: 59249.22120515024 // Modified Julian days, UT
}
When using a YMDDate
or DateAndTime
object to create a DateTime
instance, you need only set a minimal number of fields to specify the date and/or time you are trying to specify. You can use either short or long names for fields (if you use both, the short form takes priority).
At minimum, you must specify a date or a time. If you only specify a date, the time will be treated as midnight at the start of that date. If you only specify a time, you can create a special dateless time instance. You can also, of course, specify both date and time together.
In specifying a date, the date fields have the following priority:
n
/epochDay
: Number of days before/after epoch day 0, which is January 1, 1970.y
/year
: A normal calendar year. Along with the year, you can specify:- Nothing more, in which case the date is treated as January 1 of that year.
m
/month
: The month (a normal 1-12 month, not the weird 0-11 month the JavaScriptDate
uses!).- If nothing more is given, the date is treated as the first of the month.
d
/day
: The date of the month.
dy
/dayOfYear
: The 1-based number of days into the year, such that 32 means February 1.
yw
/yearByWeek
: An ISO week-based calendar year, where each week starts on Monday. This year is the same as the normal calendar year for most of the calendar year, except for, possibly, a few days at the beginning and end of the year. Week 1 is the first week which contains January 4. Along with this style of year, you can specify:- Nothing more, in which case the date is treated as the first day of the first week of the year.
w
/week
: The 1-based week number.- If nothing more, the date is treated as the first day of the given week.
dw
/dayByWeek
: The 1-based day of the given week.
ywl
/yearByWeekLocale
, etc.: These fields work the same asyw
/yearByWeek
, etc., except that they apply to locale-specific rules for the day of the week on which each week starts, and for the definition of the first week of the year.
In specifying a time, the minimum needed is a 0-23 value for hrs
/ hour
. All other unspecified time fields will be treated as 0.
Astronomical time fields will supersede any of the above date fields.
As discussed earlier, concerning parsing time strings, ambiguous times due to Daylight Saving Time default to the earlier of two times. You can, however, use occurrence: 2
to explicitly specify the later time. An explicit utcOffset
can also accomplish this disambiguation.
Reading individual DateTime
fields
As an output from a DateTime
instance, such as what you get from ttime().wallTime
, all DateAndTime
fields will be filled in with synchronized values. ttime().wallTime.hour
provides the hour value, ttime().wallTime.utcOffset
provides the UTC offset in seconds for the given time, etc.
ttime().wallTimeShort
returns a DateAndTime
object with all available short-form field names, and ttime().wallTimeLong
only long-form field names. ttime().wallTimeSparse
returns a DateAndTime
object with a minimal set of short-form field names: y
, m
, d
, hrs
, min
, sec
, millis
.
Modifying DateTime
values
There are six main methods for modifying a DateTime
value:
add(field: DateTimeField | DateTimeFieldName, amount: number, variableDays = true): DateTime
subtract(field: DateTimeField | DateTimeFieldName, amount: number, variableDays = true): DateTime
roll(field: DateTimeField | DateTimeFieldName, amount: number, minYear = 1900, maxYear = 2099)
set(field: DateTimeField | DateTimeFieldName, value: number, loose = false): DateTime
startOf(field: DateTimeField | DateTimeFieldName): DateTime
endOf(field: DateTimeField | DateTimeFieldName): DateTime
Before going further, it needs to be mentioned that
DateTime
instances can be either locked, and thus immutable, or unlocked. Instances generated usingttime(
...)
are locked. Instances created using theDateTime
constructor (covered later in this document) are created unlocked, but can be locked after creation.
When you use the add/subtract/roll/set methods on a locked instance, a new modified and locked instance is returned. When used on an unlocked instance, these methods modify that instance itself, and a reference to the modified instance is returned.
Using add
(and subtract
)
subtract()
is nothing more than a convenience method which negates the amount being added, and then callsadd()
. The documentation that follows is in terms of theadd()
method alone, but applies, with this negation, to thesubtract()
method as well.
An example of using add()
:
ttime().add('year', 1)
or ttime().add(DateTimeField.YEAR, 1)
The above produces a date one year later than the current time. In most cases, this means that the resulting date has the same month and date, but in the case of a leap day:
ttime('2024-02-29').add('year', 1).toIsoString(10)
→ 2025-02-28
...the date is pinned to 28 so that an invalid date is not created. Similarly, when adding months, invalid dates are prevented:
ttime('2021-01-31').add(DateTimeField.MONTH, 1).toIsoString(10)
→ 2021-02-28
You can add
using the following fields: MILLI
, SECOND
, MINUTE
, HOUR
, DAY
, WEEK
, MONTH
, QUARTER
, YEAR
, YEAR_WEEK
, and YEAR_WEEK_LOCALE
, as provided by the DateTimeField
enum, or their string equivalents ('milli'
, 'millis'
, 'millisecond'
, 'milliseconds'
... 'day'
, 'days'
, 'date'
, 'month'
, 'months'
, etc.).
(There are further fields defined for dealing with leap seconds and TAI, described later.)
For fields MILLI
through HOUR
, fixed units of time, multiplied by the amount
you pass, are applied. When dealing with months, quarters, and years, the variable lengths of months, quarters, and years apply.
DAY
amounts can be handled either way, as variable in length (due to possible effects of Daylight Saving Time), or as fixed units of 24 hours. The default for variableDays
is true
.
DST can alter the duration of days, typically adding or subtracting an hour, but other amounts of change are possible (like the half-hour shift used by Australia’s Lord Howe Island), so adding days can possibly cause the hour (and even minute) fields to change:
ttime('2021-02-28T07:00 Europe/London', false).add('days', 100).toIsoString()
→2021-06-08T08:00:00.000+01:00
(note shift from 7:00 to 8:00)
ttime('2021-02-28T07:00 Australia/Lord_Howe, false').add('days', 100).toIsoString()
→
2021-06-08T06:30:00.000+10:30` (note shift from 7:00 to 6:30)
By default, however, hour and minute fields remain unchanged.
ttime('2021-02-28T07:00 Australia/Lord_Howe').add('days', 100).toIsoString()
→2021-06-08T07:00:00.000+10:30
Even with the default behavior, however, it is still possibl