Skip to content
RR0Public

About

EDTF parsing and programmatic API

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Repository files navigation

@rr0/time CircleCI

Zero-dependency JavaScript API for managing fuzzy dates.

This package implements EDTF (levels 0, 1 and 2), which extended over the ISO-8601 standard to add fuzziness, and was eventually integrated in ISO 8601-2019.

By fuzziness, we mean:

  • uncertainty (?), like "maybe June 2025"
  • (im)precision (X), to express that some date has missing component(s) info
  • approximation (~), like "around June 2025"
  • both (%), like "maybe around June 2025"
EDTF ISO Standard Contents
Level 0 (2012) ISO-8601-1 (Basic rules) Dates, intervals, but no fuzziness
Level 1 (2016) ISO-8601-2 (Extensions) Date part fuzziness, open intervals, seasons, big years
Level 2 (2019) ISO-8601-2 (Extensions) Per-component date fuzziness, digits uncertainty, extended seasons w/ quarters, dates sets, exponential years
Level 3 ISO-8601-3 Seasons intervals

Accordingly, this API is split in level-specific sub-packages.

You can use Level0Date only for instance, but using Level2Date will implicitly rely on Level1Date which implicitly rely on Level0Date, as each of those standards are extending one on another.

Data types

This package supports fuzziness/no fuzziness for:

Features

Each data type can be:

  • instantiated programmatically (like new EdtfDate({year: 2024, month: 8, day: 25, uncertain: true}));
  • parsed from EDTF strings by default (like EdtfDate.fromString("2024-08-25~")), but you can use your own parser;
  • rendered in EDTF format by default (like edtfDate.toString()), but you can use your own renderer, to render in some language words for instance.

Bundle size

Data classes (Level2Date, Level2Duration, etc.) do not depend on parsers, and the package declares its side effects, so bundlers only include what you import.

Import Minified Gzipped
Level0Duration from @rr0/time/core 6.5 kB 1.9 kB
Level0Date from @rr0/time/core 11.0 kB 2.9 kB
Level2Duration from @rr0/time/core 15.6 kB 3.2 kB
Level2Date from @rr0/time/core 19.7 kB 4.0 kB
Level2Date + Level2Duration from @rr0/time/core 21.6 kB 4.4 kB
Level2Duration + Level2DurationParser 30.3 kB 6.3 kB
Level2Date + Level2DateParser 30.7 kB 6.5 kB
Level2Date + Level2Duration + their parsers 35.8 kB 7.3 kB
Level2Date from @rr0/time/level2 + @rr0/time/level2/defaults 39.2 kB 8.1 kB
Level2Date from @rr0/time 44.7 kB 9.2 kB
Everything from @rr0/time 45.6 kB 9.5 kB

Sizes are measured with esbuild (--bundle --minify) and gzip, importing the named classes only.

Entry points

Entry point Contents
@rr0/time Everything, with default parsers installed: fromString() just works.
@rr0/time/core All levels' data classes, without any parser.
@rr0/time/level0, level1, level2 The data classes of one level (and the levels it extends).
@rr0/time/parsers All parser classes.
@rr0/time/defaults Installs the default parser of every data class.
@rr0/time/level0/defaults, level1/defaults, level2/defaults Installs the default parsers of one level.

Parsing without the default parsers

fromString() uses the parser registered for its class. With @rr0/time/core, none is registered: pass the parser you need.

import { Level2Date } from "@rr0/time/core"
import { Level2DateParser } from "@rr0/time/parsers"

Level2Date.fromString("2024-08-25~", new Level2DateParser())

Without a parser, fromString() throws an EDTFError. Default parsers can be installed with import "@rr0/time/level2/defaults", or replaced with DefaultParsers.register(Level2Date, () => new MyParser()).

Examples

The examples below apply for both JavaScript and TypeScript.

Parsing

Dates, date components (year, month, etc.), intervals or durations can be instantiated fromString.

Milliseconds

The decimal fraction of seconds is read as a millisecond (2023-03-14T09:12:33.123Z, with . or ,): date.millisecond.value === 123. Fractions are right-padded or truncated to 3 digits (.5 is 500 ms, .123999 is 123 ms), and milliseconds are rendered as 3 digits by toString(). They are not an EDTF component of their own, so they carry no uncertainty/approximation flags.

Time zones

A date can end with a time zone, either an offset (+01, -05:30, Z) or a name (21:45EST, 00:55GMT).

PT, MT, CT and ET (Pacific, Mountain, Central and Eastern Time) stand for the standard or the daylight time, so what they are depends on the date: 2009-07-19 05:54PT is -07 (PDT) and 2009-12-01 05:54PT is -08 (PST). They follow the daylight saving rules of the United States since 1967 (the second Sunday of March to the first Sunday of November since 2007, the transitions occurring at 02:00); before 1967, there was no rule common to the states, so they are standard times. A time zone that depends on the date cannot be read alone: Level2Timeshift.fromString("PT") throws an EDTFError.

Uncertainty and approximation of dates

In level 2 dates, the scope of a qualification (? uncertain, ~ approximate, % both) depends on where it is written:

Written Applies to Read from
~2004-06 the year only date.year.approximateComponent
2004-~06 the month, and the year at the group level date.month.approximateComponent, date.year.approximateGroup
2004-06~ the whole date, at the group level date.month.approximateGroup, date.year.approximateGroup

approximate and uncertain are true for both levels. toString() writes each qualification where it applies, and reading the result gives the same scopes (2004-~06 is not rendered 2004~-06~), except that a qualification deduced from another one is not written (2004~-06~ is rendered 2004-06~).

Strict parsing

Parsers require the whole string to match, so 1948abc is rejected (an EDTFError is thrown), rather than parsed as 1948 with abc silently dropped. This applies to dates, intervals, timeshifts and durations alike, so strings that are not entirely EDTF have to be cleaned before they are read.

Before 2.0.0, parsers only required the beginning of the string to match, and there was no way to know that something had been dropped. Upgrading from 1.x, strings that were read with a tail dropped (a time zone in parentheses, a trailing word, a date followed by a duration) now throw: clean them before reading them.

Durations

In level 2 durations, M is minutes (P2M30S, P225H15M3S, PT30M), and MM is months (P2MM). For ISO 8601 compatibility, a M that comes before the T that introduces the time part is months (P1Y2MT30M, P1Y2M3DT4H), as is a M followed by days (P1Y2M3D), since minutes cannot precede days. Hence, P1Y2M is 1 year and 2 minutes, and 1 year and 2 months is P1Y2MM.

Durations are rendered with MM for months and a bare M for minutes, so what is rendered can be parsed back.

Uncertainty and approximation

As in dates, a qualification (? uncertain, ~ approximate, % both) has a scope that depends on where it is written:

Written Applies to Read from
~P1Y2MM the whole duration duration.approximateDuration
P1Y2MM~ the whole duration (same as above) duration.approximateDuration
P~1Y2MM the year only duration.components.years.approximateComponent
P1Y~2MM the months, and the year at the group level components.months.approximateComponent, components.years.approximate

duration.approximate (and uncertain) is true as soon as the duration or any of its components is qualified. A duration parsed from a string keeps its components as they were written, and toSpec() returns them, without normalizing the units (P~150S stays 150 seconds, instead of 2 minutes and 30 seconds) as this would lose which component is qualified. Durations without any qualified component are still normalized (P150S is rendered P2M30S). Rendering writes the qualification back where it applies, except that a suffix is rendered as a prefix (P10M~ is rendered ~P10M).

Decimal fractions (P1.5Y) and weeks (P2W) are not supported yet. Levels 0 and 1 only know the former notation (MM for months, bare M for minutes, no T).

Programmatic API

Dates, date components (year, month, etc.), intervals or durations can be instantiated through their own constructors.

See details in the Wiki.

About

EDTF parsing and programmatic API

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Used by

Contributors

Languages