Если вы не слышали об этом, Chrono (https://github.com/wanasit/chrono) - это синтаксический анализатор даты на естественном языке в JavaScript.

Я написал первую фиксацию еще в 2012 году (вспомните jQuery и Bootstrap v1). Примерно в то же время я переезжал в Токио, чтобы учиться в аспирантуре, и еще до того, как начал работать профессиональным разработчиком программного обеспечения. С тех пор было много изменений как в JavaScript (ES6, TypeScript, React,…), так и в моем опыте программирования. Этим летом у меня наконец-то есть время побыть дома и поработать над обновлением библиотеки.

Chrono, v2, представляет собой почти полностью переписанный проект на TypeScript с учетом следующих целей.

  • Сохранение знакомых API
  • Уменьшите количество повторяющегося кода и шаблонов

Сохранение знакомых API

Chrono делает только одно - разбирает дату из текста. Таким образом, он должен быть максимально простым и понятным для людей, которые хотят использовать его для этого. Скрывая под собой глубокий уровень сложности синтаксического анализа, Chrono всегда поддерживал минимальный интерфейс, а именно свою parse() функцию.

За исключением нескольких важных отличий (в первую очередь, обработки локалей), Chrono v2 имеет те же минималистичные API.

import * as chrono from ‘chrono-node’;
chrono.parseDate(‘Today at 5 PM’) // Return a javascript Date object
chrono.parse(’Today at 5 PM’)     // Return more detailed results

Вы можете настроить Chrono, клонировав базовую настройку и добавив / удалив parsers или refiners:

import * as chrono from ‘chrono-node’;
const customChrono = new chrono.Chrono();
// or const customChrono = chrono.en.clone();
customChrono.parsers.push(...)

Если вы использовали Chrono, изменение должно быть несложным. В противном случае начать работу не составит труда.

В то же время для проектов TypeScript вы получаете бесплатные преимущества недавно усовершенствованной системы типов Chrono.

class Chrono {
    parsers: Array<Parser>
    refiners: Array<Refiner>
    parse(
        text: string, ref?: Date, option?: ParsingOption
    ): ParsedResult[] { 
        ... 
    }
}

Уменьшите количество повторяющегося кода и шаблонов

До того, как class был представлен в ECMAScript 2015, проекты JavaScript должны были полагаться на наследование на основе прототипов.

В Chrono дублированный код также усугубляется отсутствием хорошей независимой от языка абстракции (ни для парсеров, ни для уточнений). Участникам предлагается скопировать и вставить весь код с английского языка.

exports.Refiner = function ENMergeDateTimeRefiner() {
    Refiner.call(this);
    this.refine = function(text, results, opt) {
        // check if there are results with unknown date and time
        // check if the words between them meaningful
        // merge them
    }
}
exports.Refiner = function FRMergeDateTimeRefiner() {
    Refiner.call(this);
    this.refine = function(text, results, opt) {
        // mostly same code, but check French words
    }
}

В версии 2 общая логика (например, календарь и обработка даты и времени) была организована в не зависящие от языка общие классы, в то время как обработка ключевых слов и шаблонов была разделена и содержалась в их собственных модулях (см. `/ Common /…` и `/ locales / en /… `).

Тот же английский уточнение в v2 будет выглядеть так:

export default class ENMergeDateTimeRefiner extends AbstractMergeDateTimeRefiner {
    patternBetween(): RegExp {
        return /^\s*(T|at|after|before|on|of|,|-)?\s*$/i;
    }
}

Новая абстракция также должна упростить настройку Chrono.

В v1 это пример Readme о том, как добавить собственный синтаксический анализатор:

var christmasParser = new chrono.Parser();
christmasParser.pattern = function () { return /\bChristmas\b/i; };
christmasParser.extract = function(text, ref, match, opt) { 
    return new chrono.ParsedResult({
        ref: ref,
        text: match[0],
        index: match.index,
        start: {    
            day: 25, 
            month: 12, 
        }
    });
};
var custom = new chrono.Chrono();
custom.parsers.push(christmasParser);

Теперь есть:

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

Еще одно важное изменение

›… Очень мало причин, по которым мы должны пытаться анализировать сразу несколько языков по умолчанию. Это приводит к множеству угловых случаев, например - # 319- # 318. Многоязычные приложения уже должны отслеживать язык пользователя, поэтому я не уверен, что действительно нужны наши попытки автоматического определения.

- Бен Обин, участник Chrono № 318

По умолчанию Chrono v1 применяет все шаблоны / ключевые слова даты со всех поддерживаемых языков, и это оказалось плохим дизайнерским решением.

Вначале это работало хорошо, когда Chrono поддерживал синтаксический анализ только на английском и японском языках. Однако после анализа большего количества языков (7 языков в v1.3.7, благодаря большому количеству участников), применение всех шаблонов даты приводит к множеству неверных или неожиданных результатов (например, смешивание немецких сокращений с английскими), и становится необходимым обслуживание и исправление ошибок. все сложнее и сложнее.

Итак, начиная с версии 2, Chrono по умолчанию будет пытаться анализировать только английский (международный).

chrono.parseDate('6/10/2018');    
chrono.en.parseDate('6/10/2018');

Конечно, вы можете использовать синтаксический анализ Chrono на других языках через те же простые в использовании API.

chrono.ja.parseDate('令和2年8月10日');

Итак, теперь, когда мы можем поддерживать несколько языков или более шаблонов дат более устойчивыми способами, мы, безусловно, приветствуем любые ваши предложения или отзывы.

Пожалуйста, проверьте Chrono на Github!