🌜
🌞
chrono-node

chrono-node

v2.3.8

A natural language date parser in Javascript

npm install chrono-node

README

Chrono (v2)

A natural language date parser in Javascript.

Build Status Coverage Status

It is designed to handle most date/time format and extract information from any given text:

  • Today, Tomorrow, Yesterday, Last Friday, etc
  • 17 August 2013 - 19 August 2013
  • This Friday from 13:00 - 16.00
  • 5 days ago
  • 2 weeks from now
  • Sat Aug 17 2013 18:40:39 GMT+0900 (JST)
  • 2014-11-30T08:15:30-05:30

Installation

With npm:

$ npm install --save chrono-node
import * as chrono from 'chrono-node';

chrono.parseDate('An appointment on Sep 12-13'); 

For Node.js:

const chrono = require('chrono-node');

// or `import chrono from 'chrono-node'` for ECMAScript

What's changed in the v2

For Users

  • Chrono’s default now handles only international English. While in the previous version, it tried to parse with all known languages.
  • The current fully supported languages are en, ja, fr, and nl (de, pt, and zh.hant are partially supported).

For contributors and advanced users

  • The project is rewritten in TypeScript
  • New Parser and Refiner interface
  • New source code structure. All parsers, refiners, and configuration should be under a locale directory (See. locales/en)

Note: v1.x.x will still be supported for the time being.

Usage

Simply pass a string to functions chrono.parseDate or chrono.parse.

import * as chrono from 'chrono-node';

chrono.parseDate('An appointment on Sep 12-13');
// Fri Sep 12 2014 12:00:00 GMT-0500 (CDT)
    
chrono.parse('An appointment on Sep 12-13');
/* [{ 
    index: 18,
    text: 'Sep 12-13',
    start: ...
}] */

For more advanced usage, here is the typescript definition of the parse function:

parse(text: string, ref?: ParsingReference, option?: ParsingOption): ParsedResult[] {...}

Parsing Reference (Date / Timezone)

Today's "Friday" is different from last month's "Friday". The meaning of the referenced dates depends on when and where they are mentioned. Chrono lets you define the reference as Date or ParsingReference object:

// (Note: the exmaples run on JST timezone)

chrono.parseDate('Friday', new Date(2012, 8 - 1, 23)); 
// Fri Aug 24 2012 12:00:00 GMT+0900 (JST)

chrono.parseDate('Friday', new Date(2012, 8 - 1, 1)); 
// Fri Aug 03 2012 12:00:00 GMT+0900 (JST)

chrono.parseDate("Friday at 4pm", {
    // Wed Jun 09 2021 21:00:00 GMT+0900 (JST)
    // = Wed Jun 09 2021 07:00:00 GMT-0500 (CDT)
    instant: new Date(1623240000000), 
    timezone: "CDT",
})
// Sat Jun 12 2021 06:00:00 GMT+0900 (JST)
// = Fri Jun 11 2021 16:00:00 GMT-0500 (CDT)

ParsingReference

  • instance?: Date The instant when the input is written or mentioned
  • timezone?: string | number The timezone where the input is written or mentioned. Support minute-offset (number) and timezone name (e.g. "GMT", "CDT")

Parsing Options

forwardDate (boolean) to assume the results should happen after the reference date (forward into the future)

const referenceDate = new Date(2012, 7, 25);
// Sat Aug 25 2012 00:00:00 GMT+0900 -- The reference date was Saturday

chrono.parseDate('Friday', referenceDate);
// Fri Aug 24 2012 12:00:00 GMT+0900 (JST) -- The day before was Friday

chrono.parseDate('Friday', referenceDate, { forwardDate: true });
// Fri Aug 31 2012 12:00:00 GMT+0900 (JST) -- The following Friday

Parsed Results and Components

ParsedResult

  • refDate: Date The reference date of this result
  • index: number The location within the input text of this result
  • text: string The text this result that appears in the input
  • start: ParsedComponents The parsed date components as a ParsedComponents object
  • end?: ParsedComponents Similar to start
  • date: () => Date Create a javascript Date

ParsedComponents

  • get: (c: Component) => number | null Get known or implied value for the component
  • isCertain: (c: Component) => boolean Check if the component has a known value
  • date: () => Date Create a javascript Date

For example:

const results = chrono.parse('I have an appointment tomorrow from 10 to 11 AM');

results[0].index;     // 22
results[0].text;      // 'tomorrow from 10 to 11 AM'
results[0].refDate;   // Sat Dec 13 2014 21:50:14 GMT-0600 (CST)

// `start` is Sat Dec 14 2014 10:00:00
results[0].start.get('day');    // 14 (the 14th, the day after refDate)
results[0].start.get('month');  // 12 (or December)
results[0].start.get('hour');   // 10 
results[0].start.date();        // Sun Dec 14 2014 10:00:00 GMT-0600 (CST)

...
results[0].end.date();  // Sun Dec 14 2014 11:00:00 GMT-0600 (CST)

Strict vs Casual configuration

Chrono comes with strict mode that parse only formal date patterns.

// 'strict' mode
chrono.strict.parseDate('Today');       // null
chrono.strict.parseDate('Friday');      // null
chrono.strict.parseDate('2016-07-01');  // Fri Jul 01 2016 12:00:00 ...
chrono.strict.parseDate('Jul 01 2016'); // Fri Jul 01 2016 12:00:00 ...

// 'casual' mode (default) 
chrono.parseDate('Today');              // Thu Jun 30 2016 12:00:00 ...
chrono.casual.parseDate('Friday');      // Fri Jul 01 2016 12:00:00 ...
chrono.casual.parseDate('2016-07-01');  // Fri Jul 01 2016 12:00:00 ...
chrono.casual.parseDate('Jul 01 2016'); // Fri Jul 01 2016 12:00:00 ...

Locales

By default, Chrono is configured to handle only international English. This differs from the previous version of Chrono that would try all locales by default.

There are several locales supported contributed by multiple developers under ./locales directory.

// default English (US)
chrono.parseDate('6/10/2018');    

chrono.en.parseDate('6/10/2018'); 
chrono.ja.parseDate('昭和64年1月7日'); 

Current supported locale options are: en, ja

Customize Chrono

Chrono’s extraction pipeline configuration consists of parsers: Parser[] and refiners: Refiner[].

  • First, each parser independently extracts patterns from input text input and create parsing results (ParsingResult).
  • Then, the parsing results are combined, sorted, and refined with the refiners. In the refining phase, the results can be filtered-out, merged, or attached with additional information.

Parser

interface Parser {
    pattern: (context: ParsingContext) => RegExp,
    extract: (context: ParsingContext, match: RegExpMatchArray) =>
        (ParsingComponents | ParsingResult | {[c in Component]?: number} | null)
}

Parser is a module for low-level pattern-based parsing. Ideally, each parser should be designed to handle a single specific date format.

User can create a new parser for supporting new date formats or languages by providing RegExp pattern pattern() and extracting result or components from the RegExp match extract().

const custom = chrono.casual.clone();
custom.parsers.push({
    pattern: () => { return /\bChristmas\b/i },
    extract: (context, match) => {
        return {
            day: 25, month: 12
        }
    }
});

custom.parseDate("I'll arrive at 2.30AM on Christmas night");
// Wed Dec 25 2013 02:30:00 GMT+0900 (JST)
// 'at 2.30AM on Christmas'

Refiner

interface Refiner {
    refine: (context: ParsingContext, results: ParsingResult[]) => ParsingResult[]
}

Refiner is a higher level module for improving or manipulating the results. User can add a new type of refiner to customize Chrono's results or to add some custom logic to Chrono.

const custom = chrono.casual.clone();
custom.refiners.push({
    refine: (context, results) => {
        // If there is no AM/PM (meridiem) specified,
        //  let all time between 1:00 - 4:00 be PM (13.00 - 16.00)
        results.forEach((result) => {
            if (!result.start.isCertain('meridiem') &&
                result.start.get('hour') >= 1 && result.start.get('hour') < 4) {

                result.start.assign('meridiem', 1);
                result.start.assign('hour', result.start.get('hour') + 12);
            }
        });
        return results;
    }
});

// This will be parsed as PM.
// > Tue Dec 16 2014 14:30:00 GMT-0600 (CST) 
custom.parseDate("This is at 2.30");

// Unless the 'AM' part is specified
// > Tue Dec 16 2014 02:30:00 GMT-0600 (CST)
custom.parseDate("This is at 2.30 AM");

In the example, the custom refiner assigns PM to parsing results with ambiguous meridiem. The refine method of the refiner class will be called with parsing results (from parsers or other previous refiners). The method must return an array of the new results (which, in this case, we modified those results in place).

More documentation

Checkout the Typescript Documentation in the project's Github page.

Development Guides

This guide explains how to set up chrono project for prospective contributors.

# Clone and install library
$ git clone https://github.com/wanasit/chrono.git chrono
$ cd chrono
$ npm install

Parsing date from text is complicated. A small change can have effects on unexpected places. So, Chrono is a heavily tested library. Commits that break a test shouldn't be allowed in any condition.

Chrono's unit testing is based-on Jest.

# Run the test
$ npm run test

# Run the test in watch mode
$ npm run watch

Chrono's source files is in src directory. The built bundle (dist/*) is created by running Webpack via the following command

$ npm run build

Release Notes

2.3.8
By Wanasit Tanakitrungruang • Published on March 6, 2022
  • (new) Added parsing for quarter f4964a6 (by @PriyankaSand)
  • (new, DE) Improve German casual relative expression parsing 668b5fe (by @georgd)
  • (new, DE) Other German improvement (month name, era, time parsing)

https://github.com/wanasit/chrono/compare/v2.3.7...v2.3.8

2.3.7
By Wanasit Tanakitrungruang • Published on February 13, 2022
  • New: Recognize relative time from an absolute date (e.g. "2 weeks before 2020-02-13") from @liamcain (PR #430)
  • Fix: MSK (Moscow Time) from UTC+4:00 to UTC+3:00 from @DylanFrese (PR #421)
  • Fix: "after" and "after this" reference 0d1c9bd

https://github.com/wanasit/chrono/compare/v2.3.6...v2.3.7

2.3.6
By Wanasit Tanakitrungruang • Published on January 30, 2022
  • New: Chinese Hans support (chrono.zh.hans) support from @QingWei-Li (PR #427)
  • Fix: Ambiguous timezone names when time is not mentioned afbba58

https://github.com/wanasit/chrono/compare/v2.3.5...v2.3.6

2.3.5
By Wanasit Tanakitrungruang • Published on December 28, 2021
  • New: Chinese Hant support (chrono.zh.hant) support from @DingWeizhe (MR #417)
  • Improvement: also assign meridium on relative time parsing c4cd375

https://github.com/wanasit/chrono/compare/v2.3.4...v2.3.5

2.3.4
By Wanasit Tanakitrungruang • Published on November 24, 2021
  • Fix: improve timezone adjustment (dayligth-saving related) 543c3bc
  • Fix: incorrect date when ref timezone is unknown 801ca4e

https://github.com/wanasit/chrono/compare/v2.3.3...v2.3.4

2.3.3
By Wanasit Tanakitrungruang • Published on November 18, 2021
  • New: Allow null reference timezone; result timezone will not be implied or assigned unless one is read from a parsed date d337a53
  • Fix: Require a word boundary after short tokens that can be interpreted as numbers 8efe94c

https://github.com/wanasit/chrono/compare/v2.3.2...v2.3.3

2.3.2
By Wanasit Tanakitrungruang • Published on September 25, 2021
  • Fix: ForwardRefiner to activate on the same weekday 699c871

https://github.com/wanasit/chrono/compare/v2.3.1...v2.3.2

2.3.1
By Wanasit Tanakitrungruang • Published on July 18, 2021
  • Fix: (performance) Regex backtracking in consecutive space patterns #399
  • New: Enhance Dutch (NL) Support 0cfef4e

https://github.com/wanasit/chrono/compare/v2.3.0...v2.3.1

2.3.0
By Wanasit Tanakitrungruang • Published on June 14, 2021
  • New: Date/Timezone Reference API (see. Parsing Reference (Date / Timezone) in Readme)

https://github.com/wanasit/chrono/compare/v2.2.7...v2.3.0

2.2.7
By Wanasit Tanakitrungruang • Published on May 30, 2021
  • Improvement: Apply case-sensitive timezone extraction on date expressions f036345
  • Fix: Time parsing ending with "a" or "p" 77fbd83
  • New: BCE/CE year label support dde6103
  • New: "tmrw" as abbr for "tomorrow" b9c02f5

https://github.com/wanasit/chrono/compare/v2.2.6...v2.2.7

General

License
MIT
Typescript Types
Built-in
Tree-shakeable
No

Popularity

GitHub Stargazers
2,830
Community Interest
2,902
Number of Forks
320

Maintenance

Commits
10/219/22020
Last Commit
Open Issues
98
Closed Issues
230
Open Pull Requests
8
Closed Pull Requests
30

Versions

Versions Released
10/219/2202
Latest Version Released
Mar 6, 2022
Current Tags
latest2.3.8

Contributors

wanasit
wanasit
Commits: 390
JeroenVdb
JeroenVdb
Commits: 20
matthias-christen
matthias-christen
Commits: 11
jayaddison-collabora
jayaddison-collabora
Commits: 10
gecko655
gecko655
Commits: 9
gimenete
gimenete
Commits: 9
victor36max
victor36max
Commits: 6
gregeinfrank
gregeinfrank
Commits: 6
penne12
penne12
Commits: 5
mvolz
mvolz
Commits: 5
guillegette
guillegette
Commits: 5
lostfictions
lostfictions
Commits: 5
benaubin
benaubin
Commits: 5
denouche
denouche
Commits: 4
jonshaffer
jonshaffer
Commits: 4