diff --git a/.eslintrc.js b/.eslintrc.js index decaf20..0446e09 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,11 +1,11 @@ const OFF = 0, - WARN = 1, - ERROR = 2; + WARN = 1, + ERROR = 2; -module.exports = { - extends: ['prettier'], - env: { - node: true, - es6: true - }, +export default { + extends: ['prettier'], + env: { + node: true, + es6: true, + }, }; diff --git a/index.d.ts b/index.d.ts deleted file mode 100644 index 3cfa1bd..0000000 --- a/index.d.ts +++ /dev/null @@ -1,686 +0,0 @@ -declare module "src/data/normalizeLocale" { - export = normalizeLocale; - /** - * Given a locale string from an operating system or process env, normalize the name - * @param {String} name A name such as fr_FR, en-US, en-us.utf-8 - * @returns {String} - * @see https://github.com/sindresorhus/os-locale/blob/main/index.js for similar code - */ - function normalizeLocale(name: string): string; -} -declare module "src/data/defaultLocale" { - const _exports: string; - export = _exports; -} -declare module "src/data/twoDigitYears" { - export = twoDigitYears; - const twoDigitYears: {}; -} -declare module "src/data/timezoneNames" { - export = timezoneNames; - const timezoneNames: { - 'Eastern Daylight Time': number; - 'Eastern Standard Time': number; - 'Central Daylight Time': number; - 'Central Standard Time': number; - 'Mountain Daylight Time': number; - 'Mountain Standard Time': number; - 'Pacific Daylight Time': number; - 'Pacific Standard Time': number; - ACDT: number; - ACST: number; - ACT: number; - ADT: number; - AEDT: number; - AEST: number; - AFT: number; - AKDT: number; - AKST: number; - AMST: number; - AMT: number; - ART: number; - AST: number; - AWDT: number; - AWST: number; - AZOST: number; - AZT: number; - BDT: number; - BIOT: number; - BIT: number; - BOT: number; - BRST: number; - BRT: number; - BTT: number; - CAT: number; - CCT: number; - CDT: number; - CEDT: number; - CEST: number; - CET: number; - CHADT: number; - CHAST: number; - CHOT: number; - ChST: number; - CHUT: number; - CIST: number; - CIT: number; - CKT: number; - CLST: number; - CLT: number; - COST: number; - COT: number; - CST: number; - CT: number; - CVT: number; - CXT: number; - DAVT: number; - DDUT: number; - DFT: number; - EASST: number; - EAST: number; - EAT: number; - ECT: number; - EDT: number; - EEDT: number; - EEST: number; - EET: number; - EGST: number; - EGT: number; - EIT: number; - EST: number; - FET: number; - FJT: number; - FKST: number; - FKT: number; - FNT: number; - GALT: number; - GAMT: number; - GET: number; - GFT: number; - GILT: number; - GIT: number; - GMT: number; - GST: number; - GYT: number; - HADT: number; - HAEC: number; - HAST: number; - HKT: number; - HMT: number; - HOVT: number; - HST: number; - IBST: number; - ICT: number; - IDT: number; - IOT: number; - IRDT: number; - IRKT: number; - IRST: number; - IST: number; - JST: number; - KGT: number; - KOST: number; - KRAT: number; - KST: number; - LHST: number; - LINT: number; - MAGT: number; - MART: number; - MAWT: number; - MDT: number; - MET: number; - MEST: number; - MHT: number; - MIST: number; - MIT: number; - MMT: number; - MSK: number; - MST: number; - MUT: number; - MVT: number; - MYT: number; - NCT: number; - NDT: number; - NFT: number; - NPT: number; - NST: number; - NT: number; - NUT: number; - NZDT: number; - NZST: number; - OMST: number; - ORAT: number; - PDT: number; - PET: number; - PETT: number; - PGT: number; - PHOT: number; - PKT: number; - PMDT: number; - PMST: number; - PONT: number; - PST: number; - PYST: number; - PYT: number; - RET: number; - ROTT: number; - SAKT: number; - SAMT: number; - SAST: number; - SBT: number; - SCT: number; - SGT: number; - SLST: number; - SRET: number; - SRT: number; - SST: number; - SYOT: number; - TAHT: number; - THA: number; - TFT: number; - TJT: number; - TKT: number; - TLT: number; - TMT: number; - TOT: number; - TVT: number; - UCT: number; - ULAT: number; - USZ1: number; - UTC: number; - UYST: number; - UYT: number; - UZT: number; - VET: number; - VLAT: number; - VOLT: number; - VOST: number; - VUT: number; - WAKT: number; - WAST: number; - WAT: number; - WEDT: number; - WEST: number; - WET: number; - WIT: number; - WST: number; - YAKT: number; - YEKT: number; - Z: number; - }; -} -declare module "src/data/baseLookups" { - import timezoneNames = require("src/data/timezoneNames"); - import twoDigitYears = require("src/data/twoDigitYears"); - export const meridiem: { - am: number; - pm: number; - 'a.m.': number; - 'p.m.': number; - }; - export namespace month { - const january: number; - const jan: number; - const february: number; - const feb: number; - const march: number; - const mar: number; - const april: number; - const apr: number; - const may: number; - const june: number; - const jun: number; - const july: number; - const jul: number; - const august: number; - const aug: number; - const september: number; - const sep: number; - const october: number; - const oct: number; - const november: number; - const nov: number; - const december: number; - const dec: number; - } - export namespace dayname { - const sunday: number; - const sun: number; - const monday: number; - const mon: number; - const tuesday: number; - const tue: number; - const wednesday: number; - const wed: number; - const thursday: number; - const thu: number; - const friday: number; - const fri: number; - const saturday: number; - const sat: number; - } - export const digit: {}; - export { timezoneNames as zone, twoDigitYears as year }; -} -declare module "src/data/templates" { - export namespace latn { - const MONTHNAME: string; - const DAYNAME: string; - const ZONE: string; - const MERIDIEM: string; - const ORDINAL: string; - const YEAR: string; - const MONTH: string; - const MONTH2: string; - const DAY: string; - const DAY2: string; - const OFFSET: string; - const H24: string; - const H12: string; - const MIN: string; - const SEC: string; - const MS: string; - const SPACE: string; - } - export namespace other { - const YEAR_1: string; - export { YEAR_1 as YEAR }; - const MONTH_1: string; - export { MONTH_1 as MONTH }; - const MONTH2_1: string; - export { MONTH2_1 as MONTH2 }; - const DAY_1: string; - export { DAY_1 as DAY }; - const DAY2_1: string; - export { DAY2_1 as DAY2 }; - const OFFSET_1: string; - export { OFFSET_1 as OFFSET }; - const H24_1: string; - export { H24_1 as H24 }; - const H12_1: string; - export { H12_1 as H12 }; - const MIN_1: string; - export { MIN_1 as MIN }; - const SEC_1: string; - export { SEC_1 as SEC }; - const MS_1: string; - export { MS_1 as MS }; - } -} -declare module "src/data/numberingSystems" { - export const chineseGroup: "[0123456789〇一二三四五六七八九\\d]"; - export const defaultLookup: { - 0: number; - 1: number; - 2: number; - 3: number; - 4: number; - 5: number; - 6: number; - 7: number; - 8: number; - 9: number; - '\uFF10': number; - '\uFF11': number; - '\uFF12': number; - '\uFF13': number; - '\uFF14': number; - '\uFF15': number; - '\uFF16': number; - '\uFF17': number; - '\uFF18': number; - '\uFF19': number; - 〇: number; - 一: number; - 二: number; - 三: number; - 四: number; - 五: number; - 六: number; - 七: number; - 八: number; - 九: number; - }; - export namespace startCodes { - const arab: number; - const arabext: number; - const bali: number; - const beng: number; - const deva: number; - const fullwide: number; - const gujr: number; - const khmr: number; - const knda: number; - const laoo: number; - const limb: number; - const mlym: number; - const mong: number; - const mymr: number; - const orya: number; - const tamldec: number; - const telu: number; - const thai: number; - const tibt: number; - } - export function buildDigits(nsName: any): any; -} -declare module "src/data/units" { - export = units; - const units: string[]; -} -declare module "src/LocaleHelper/LocaleHelper" { - export = LocaleHelper; - class LocaleHelper { - /** - * Get a singleton instance with the given locale - * @param {String} locale such as en, en-US, es, fr-FR, etc. - * @returns {LocaleHelper} - */ - static factory(locale?: string): LocaleHelper; - /** - * Create a new instance with the given locale - * @param {String} locale such as en, en-US, es, fr-FR, etc. - */ - constructor(locale?: string); - /** - * The locale string - * @type {String} - */ - locale: string; - /** - * Lookups for zone, year, meridiem, month, dayname, digit - * @type {Object} lookups - */ - lookups: any; - /** - * Template variables including MONTHNAME, MONTH, ZONE, etc. - * @type {Object} vars - */ - vars: any; - /** - * The numbering system to use (latn=standard arabic digits) - * @type {String} numberingSystem - */ - numberingSystem: string; - /** - * Cast a string to an integer, minding numbering system - * @param {String|Number} digitString Such as "2020" or "二〇二〇" - * @returns {Number} - */ - toInt(digitString: string | number): number; - /** - * Build lookups for digits, month names, day names, and meridiems based on the locale - */ - build(): void; - /** - * Build lookups for digits - */ - buildNumbers(): void; - /** - * Build lookup for month names - */ - buildMonthNames(): void; - /** - * Build lookup for day name - */ - buildDaynames(): void; - /** - * Build lookup for meridiems (e.g. AM/PM) - */ - buildMeridiems(): void; - /** - * Given a list of unit names and matches, build result object - * @param {Array} units Unit names such as "year", "month" and "millisecond" - * @param {Array} matches The values matched by a Format's RegExp - * @returns {Object} - */ - getObject(units: any[], matches: any[]): any; - /** - * Take a response object and cast each unit to Number - * @param {Object} object An object with one or more units - * @returns {Object} An object with same units but Numeric - */ - castObject(object: any): any; - /** - * Convert an offset string to Numeric minutes (e.g. "-0500", "+5", "+03:30") - * @param {String} offsetString - * @returns {Number} - */ - offsetToMinutes(offsetString: string): number; - /** - * Compile template into a RegExp and return it - * @param {String} template The template string - * @returns {RegExp} - */ - compile(template: string): RegExp; - } -} -declare module "src/Format/Format" { - export = Format; - /** - * Represents a parsable date format - */ - class Format { - /** - * Given a definition, create a parsable format - * @param {Object} definition The format definition - * @property {String} template A template for RegExp that can handle multiple languages - * @property {RegExp} matcher An actual RegExp to match against - * @property {Array} units If the template or RegExp match exact units, you can define the units - * @property {Function} handler A flexible alternative to units; must return an object - * @property {Array} locales A list of locales that this format should be restricted to - */ - constructor({ template, matcher, units, handler, locales, }: any); - /** - * @type {String} template A template for RegExp that can handle multiple languages - */ - template: string; - /** - * @type {Array} units If the template or RegExp match exact units, you can define the units - */ - units: any[]; - /** - * @type {RegExp} matcher An actual RegExp to match against - */ - matcher: RegExp; - /** - * @type {Function} handler A flexible alternative to units; must return an object - */ - handler: Function; - /** - * @type {String[]} locales A list of locales that this format should be restricted to - */ - locales: string[]; - /** - * A cache of RegExp indexed by locale name - * @type {Object} - */ - regexByLocale: any; - /** - * Build the RegExp from the template for a given locale - * @param {String} locale The language locale such as en-US, pt-BR, zh, es, etc. - * @returns {RegExp} A RegExp that matches when this format is recognized - */ - getRegExp(locale?: string): RegExp; - /** - * Run this format's RegExp against the given string - * @param {String} string The date string - * @param {String} locale The language locale such as en-US, pt-BR, zh, es, etc. - * @returns {Array|null} Array of matches or null on non-match - */ - getMatches(string: string, locale?: string): any[] | null; - /** - * Given matches against this RegExp, convert to object - * @param {String[]} matches An array of matched parts - * @param {String} locale The language locale such as en-US, pt-BR, zh, es, etc. - * @returns {Object} Object which may contain year, month, day, hour, minute, second, millisecond, offset, invalid - */ - toDateTime(matches: string[], locale?: string): any; - /** - * Attempt to parse a string in this format - * @param {String} string The date string - * @param {String} locale The language locale such as en-US, pt-BR, zh, es, etc. - * @returns {Object|null} Null if format can't handle this string, Object for result or error - */ - attempt(string: string, locale?: string): any | null; - /** - * Return the current date (used to support unit testing) - * @returns {Date} - */ - now(): Date; - } -} -declare module "src/fromString/fromString" { - export = fromString; - function fromString(parser: any, defaultLocale: any): (string: any, locale?: any) => any; -} -declare module "src/fromAny/fromAny" { - export = fromAny; - function fromAny(fromString: any): (any: any, locale: any) => any; -} -declare module "src/Parser/Parser" { - export = Parser; - class Parser { - formats: any[]; - /** - * Register a format object representing a parseable date format - * @param {Format} format The Format to add - * @returns {Parser} - * @chainable - */ - addFormat(format: Format): Parser; - /** - * Register multiple formats - * @param {Format[]} formats The array of Formats to add - * @returns {Parser} - * @chainable - */ - addFormats(formats: Format[]): Parser; - /** - * Unregister a format - * @param {Format} format The Format to remove - * @returns {Boolean} true if format was found and removed, false if it wasn't registered - */ - removeFormat(format: Format): boolean; - /** - * Attempt to parse a date string - * @param {String} date A parseable date string - * @param {String} locale The name of the locale - * @returns {Object} - */ - attempt(date: string, locale?: string): any; - /** - * Export this parser as a single function that takes a string - * @param {String} locale The default locale it should use - * @returns {Function} - */ - exportAsFunction(locale?: string): Function; - /** - * Export this parser as a single function that takes a string or Date - * @param {String} locale The default locale it should use - * @returns {Function} - */ - exportAsFunctionAny(locale?: string): Function; - } - import Format = require("src/Format/Format"); -} -declare module "src/formats/atSeconds/atSeconds" { - export = atSeconds; - const atSeconds: Format; - import Format = require("src/Format/Format"); -} -declare module "src/formats/microsoftJson/microsoftJson" { - export = microsoftJson; - const microsoftJson: Format; - import Format = require("src/Format/Format"); -} -declare module "src/data/unitShortcuts" { - export const y: string; - export const M: string; - export const d: string; - export const w: string; - export const h: string; - export const m: string; - export const s: string; - export const ms: string; -} -declare module "src/formats/ago/ago" { - export = ago; - const ago: Format; - import Format = require("src/Format/Format"); -} -declare module "src/formats/chinese/chinese" { - export = chinese; - const chinese: Format; - import Format = require("src/Format/Format"); -} -declare module "src/formats/dayMonth/dayMonth" { - export = dayMonth; - const dayMonth: Format; - import Format = require("src/Format/Format"); -} -declare module "src/formats/dayMonthname/dayMonthname" { - export = dayMonthname; - const dayMonthname: Format; - import Format = require("src/Format/Format"); -} -declare module "src/formats/dayMonthnameYear/dayMonthnameYear" { - export = dayMonthnameYear; - const dayMonthnameYear: Format; - import Format = require("src/Format/Format"); -} -declare module "src/formats/dayMonthYear/dayMonthYear" { - export = dayMonthYear; - const dayMonthYear: Format; - import Format = require("src/Format/Format"); -} -declare module "src/formats/monthDay/monthDay" { - export = monthDay; - const monthDay: Format; - import Format = require("src/Format/Format"); -} -declare module "src/formats/monthDayYear/monthDayYear" { - export = monthDayYear; - const monthDayYear: Format; - import Format = require("src/Format/Format"); -} -declare module "src/formats/monthnameDay/monthnameDay" { - export = monthnameDay; - const monthnameDay: Format; - import Format = require("src/Format/Format"); -} -declare module "src/formats/monthnameDayYear/monthnameDayYear" { - export = monthnameDayYear; - const monthnameDayYear: Format; - import Format = require("src/Format/Format"); -} -declare module "src/formats/time12Hours/time12Hours" { - export = time12Hours; - const time12Hours: Format; - import Format = require("src/Format/Format"); -} -declare module "src/formats/time24Hours/time24Hours" { - export = time24Hours; - const time24Hours: Format; - import Format = require("src/Format/Format"); -} -declare module "src/formats/today/today" { - export = today; - const today: Format; - import Format = require("src/Format/Format"); -} -declare module "src/formats/twitter/twitter" { - export = twitter; - const twitter: Format; - import Format = require("src/Format/Format"); -} -declare module "src/formats/yearMonthDay/yearMonthDay" { - export = yearMonthDay; - const yearMonthDay: Format; - import Format = require("src/Format/Format"); -} -declare module "index" { - export = parser; - const parser: Parser; - import Parser = require("src/Parser/Parser"); -} diff --git a/index.js b/index.js deleted file mode 100644 index d7ae768..0000000 --- a/index.js +++ /dev/null @@ -1,75 +0,0 @@ -// import our main modules -const Parser = require('./src/Parser/Parser.js'); -const Format = require('./src/Format/Format.js'); -const LocaleHelper = require('./src/LocaleHelper/LocaleHelper.js'); -// import our formats -const ago = require('./src/formats/ago/ago.js'); -const atSeconds = require('./src/formats/atSeconds/atSeconds.js'); -const chinese = require('./src/formats/chinese/chinese.js'); -const dayMonth = require('./src/formats/dayMonth/dayMonth.js'); -const dayMonthname = require('./src/formats/dayMonthname/dayMonthname.js'); -const dayMonthnameYear = require('./src/formats/dayMonthnameYear/dayMonthnameYear.js'); -const dayMonthYear = require('./src/formats/dayMonthYear/dayMonthYear.js'); -const defaultLocale = require('./src/data/defaultLocale.js'); -const fuzzy = require('./src/formats/fuzzy/fuzzy.js'); -const korean = require('./src/formats/korean/korean.js'); -const microsoftJson = require('./src/formats/microsoftJson/microsoftJson.js'); -const monthDay = require('./src/formats/monthDay/monthDay.js'); -const monthDayYear = require('./src/formats/monthDayYear/monthDayYear.js'); -const monthnameDay = require('./src/formats/monthnameDay/monthnameDay.js'); -const monthnameDayYear = require('./src/formats/monthnameDayYear/monthnameDayYear.js'); -const time12Hours = require('./src/formats/time12Hours/time12Hours.js'); -const time24Hours = require('./src/formats/time24Hours/time24Hours.js'); -const today = require('./src/formats/today/today.js'); -const twitter = require('./src/formats/twitter/twitter.js'); -const yearMonthDay = require('./src/formats/yearMonthDay/yearMonthDay.js'); -const yearMonthDayWithSlashes = require('./src/formats/yearMonthDayWithSlashes/yearMonthDayWithSlashes.js'); -const yearMonthDayWithDots = require('./src/formats/yearMonthDayWithDots/yearMonthDayWithDots.js'); -const yearMonthnameDay = require('./src/formats/yearMonthnameDay/yearMonthnameDay.js'); - -// create a default parser instance and register all the default formats -const parser = new Parser(); -parser - // all formats can have time strings at the end - .addFormats([ - time24Hours, - time12Hours, - // from most unambiguous and popular to least - yearMonthDayWithDots, - yearMonthDay, - dayMonthnameYear, - monthnameDayYear, - monthDayYear, - dayMonthYear, - chinese, - korean, - twitter, - today, - ago, - monthnameDay, - dayMonthname, - monthDay, - dayMonth, - yearMonthnameDay, - yearMonthDayWithSlashes, - atSeconds, - microsoftJson, - fuzzy, - ]); - -// make it easy to consume our other main modules and functions -parser.Parser = Parser; -parser.Format = Format; -parser.LocaleHelper = LocaleHelper; -parser.defaultLocale = defaultLocale; - -// create functions on Date -parser.fromString = Date.fromString = parser.exportAsFunction(); -parser.fromAny = Date.fromAny = parser.exportAsFunctionAny(); - -if (typeof window !== 'undefined') { - window.anyDateParser = parser; -} - -// export our default parser -module.exports = parser; diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..664790b --- /dev/null +++ b/index.ts @@ -0,0 +1,75 @@ +// import our main modules +import Format from './src/Format/Format'; +import LocaleHelper from './src/LocaleHelper/LocaleHelper.js'; +import Parser from './src/Parser/Parser'; +// import our formats +import defaultLocale from './src/data/defaultLocale'; +import ago from './src/formats/ago/ago'; +import atSeconds from './src/formats/atSeconds/atSeconds'; +import chinese from './src/formats/chinese/chinese'; +import dayMonth from './src/formats/dayMonth/dayMonth'; +import dayMonthname from './src/formats/dayMonthname/dayMonthname'; +import dayMonthnameYear from './src/formats/dayMonthnameYear/dayMonthnameYear'; +import dayMonthYear from './src/formats/dayMonthYear/dayMonthYear'; +import fuzzy from './src/formats/fuzzy/fuzzy'; +import korean from './src/formats/korean/korean'; +import microsoftJson from './src/formats/microsoftJson/microsoftJson'; +import monthDay from './src/formats/monthDay/monthDay'; +import monthDayYear from './src/formats/monthDayYear/monthDayYear'; +import monthnameDay from './src/formats/monthnameDay/monthnameDay'; +import monthnameDayYear from './src/formats/monthnameDayYear/monthnameDayYear'; +import time12Hours from './src/formats/time12Hours/time12Hours'; +import time24Hours from './src/formats/time24Hours/time24Hours'; +import today from './src/formats/today/today'; +import twitter from './src/formats/twitter/twitter'; +import yearMonthDay from './src/formats/yearMonthDay/yearMonthDay'; +import yearMonthDayWithDots from './src/formats/yearMonthDayWithDots/yearMonthDayWithDots'; +import yearMonthDayWithSlashes from './src/formats/yearMonthDayWithSlashes/yearMonthDayWithSlashes'; +import yearMonthnameDay from './src/formats/yearMonthnameDay/yearMonthnameDay'; + +// create a default parser instance and register all the default formats +const parser = new Parser(); +parser + // all formats can have time strings at the end + .addFormats([ + time24Hours, + time12Hours, + // from most unambiguous and popular to least + yearMonthDayWithDots, + yearMonthDay, + dayMonthnameYear, + monthnameDayYear, + monthDayYear, + dayMonthYear, + chinese, + korean, + twitter, + today, + ago, + monthnameDay, + dayMonthname, + monthDay, + dayMonth, + yearMonthnameDay, + yearMonthDayWithSlashes, + atSeconds, + microsoftJson, + fuzzy, + ]); + +// make it easy to consume our other main modules and functions +parser.Parser = Parser; +parser.Format = Format; +parser.LocaleHelper = LocaleHelper; +parser.defaultLocale = defaultLocale; + +// create functions on Date +parser.fromString = Date.fromString = parser.exportAsFunction(); +parser.fromAny = Date.fromAny = parser.exportAsFunctionAny(); + +if (typeof window !== 'undefined') { + window.anyDateParser = parser; +} + +// export our default parser +export default parser; diff --git a/scripts/transform-require-to-import.js b/scripts/transform-require-to-import.js index 8641796..c99bc59 100644 --- a/scripts/transform-require-to-import.js +++ b/scripts/transform-require-to-import.js @@ -1,4 +1,4 @@ -module.exports = function (fileInfo, api) { +export default function (fileInfo, api) { const j = api.jscodeshift; const root = j(fileInfo.source); @@ -15,4 +15,4 @@ module.exports = function (fileInfo, api) { }); return root.toSource(); -}; +} diff --git a/src/Format/Format.spec.ts b/src/Format/Format.spec.ts index 92be27c..9aba43b 100644 --- a/src/Format/Format.spec.ts +++ b/src/Format/Format.spec.ts @@ -1,70 +1,71 @@ -const Format = require('./Format.js'); +import { describe, expect, it } from 'vitest'; +import Format from './Format'; describe('Format', () => { - it('should require units or handler', () => { - function missingUnitsAndHandler() { - return new Format({}); - } - expect(missingUnitsAndHandler).toThrowError(); - }); - it('should require template or matcher', () => { - function missingTemplateAndRegex() { - return new Format({ units: [] }); - } - expect(missingTemplateAndRegex).toThrowError(); - }); - it('should build RegExp from template', () => { - const format = new Format({ handler: () => {}, template: 'year:_YEAR_' }); - const regex = format.getRegExp(); - expect(regex).toEqual(/year:\d{4}|\d{2}/i); - }); - it('should getMatches()', () => { - const format = new Format({ handler: () => {}, matcher: /foo:(\d)(\d)/ }); - const matches = [...format.getMatches('foo:42')]; - expect(matches).toEqual(['foo:42', '4', '2']); - }); - it('should convert numeric matches to Object', () => { - const format = new Format({ - units: ['year', 'month', 'day'], - matcher: /./, - }); - const actual = format.toDateTime([null, '2020', '10', '13']); - expect(actual).toEqual({ year: 2020, month: 10, day: 13 }); - }); - it('should convert monthname matches to Object', () => { - const format = new Format({ - units: ['year', 'month', 'day'], - matcher: /./, - }); - const actual = format.toDateTime([null, '2020', 'oct', '13']); - expect(actual).toEqual({ year: 2020, month: 10, day: 13 }); - }); - it('should convert 2-digit years', () => { - const format = new Format({ - units: ['year', 'month', 'day', 'minute'], - matcher: /./, - }); - const actual = format.toDateTime([null, '20', 'october', '13', '59']); - expect(actual).toEqual({ year: 2020, month: 10, day: 13, minute: 59 }); - }); - it('should attempt to parse', () => { - const format = new Format({ - matcher: /(\d+)m (\d+)s/, - units: ['minute', 'second'], - }); - const actual = format.attempt('56m 22s'); - expect(actual).toEqual({ minute: 56, second: 22 }); - }); - it('should trim()', () => { - const format = new Format({ - matcher: /^(\d+)s (\d+)ms$/, - units: ['second', 'millisecond'], - }); - const actual = format.attempt(' 56s 813ms\t'); - expect(actual).toEqual({ second: 56, millisecond: 813 }); - }); - it('should return now()', () => { - const format = new Format({ units: [], matcher: /./ }); - expect(format.now()).toBeInstanceOf(Date); - }); + it('should require units or handler', () => { + function missingUnitsAndHandler() { + return new Format({}); + } + expect(missingUnitsAndHandler).toThrowError(); + }); + it('should require template or matcher', () => { + function missingTemplateAndRegex() { + return new Format({ units: [] }); + } + expect(missingTemplateAndRegex).toThrowError(); + }); + it('should build RegExp from template', () => { + const format = new Format({ handler: () => {}, template: 'year:_YEAR_' }); + const regex = format.getRegExp(); + expect(regex).toEqual(/year:\d{4}|\d{2}/i); + }); + it('should getMatches()', () => { + const format = new Format({ handler: () => {}, matcher: /foo:(\d)(\d)/ }); + const matches = [...format.getMatches('foo:42')]; + expect(matches).toEqual(['foo:42', '4', '2']); + }); + it('should convert numeric matches to Object', () => { + const format = new Format({ + units: ['year', 'month', 'day'], + matcher: /./, + }); + const actual = format.toDateTime([null, '2020', '10', '13']); + expect(actual).toEqual({ year: 2020, month: 10, day: 13 }); + }); + it('should convert monthname matches to Object', () => { + const format = new Format({ + units: ['year', 'month', 'day'], + matcher: /./, + }); + const actual = format.toDateTime([null, '2020', 'oct', '13']); + expect(actual).toEqual({ year: 2020, month: 10, day: 13 }); + }); + it('should convert 2-digit years', () => { + const format = new Format({ + units: ['year', 'month', 'day', 'minute'], + matcher: /./, + }); + const actual = format.toDateTime([null, '20', 'october', '13', '59']); + expect(actual).toEqual({ year: 2020, month: 10, day: 13, minute: 59 }); + }); + it('should attempt to parse', () => { + const format = new Format({ + matcher: /(\d+)m (\d+)s/, + units: ['minute', 'second'], + }); + const actual = format.attempt('56m 22s'); + expect(actual).toEqual({ minute: 56, second: 22 }); + }); + it('should trim()', () => { + const format = new Format({ + matcher: /^(\d+)s (\d+)ms$/, + units: ['second', 'millisecond'], + }); + const actual = format.attempt(' 56s 813ms\t'); + expect(actual).toEqual({ second: 56, millisecond: 813 }); + }); + it('should return now()', () => { + const format = new Format({ units: [], matcher: /./ }); + expect(format.now()).toBeInstanceOf(Date); + }); }); diff --git a/src/Format/Format.ts b/src/Format/Format.ts index bcf156d..bcf03bb 100644 --- a/src/Format/Format.ts +++ b/src/Format/Format.ts @@ -1,172 +1,170 @@ -const LocaleHelper = require('../LocaleHelper/LocaleHelper.js'); -const defaultLocale = require('../data/defaultLocale.js'); -const removeFillerWords = require('../removeFillerWords/removeFillerWords.js'); +import LocaleHelper from '../LocaleHelper/LocaleHelper'; +import defaultLocale from '../data/defaultLocale'; +import removeFillerWords from '../removeFillerWords/removeFillerWords'; /** * Represents a parsable date format */ -class Format { - /** - * Given a definition, create a parsable format - * @param {Object} definition The format definition - * @property {String} template A template for RegExp that can handle multiple languages - * @property {RegExp} matcher An actual RegExp to match against - * @property {Array} units If the template or RegExp match exact units, you can define the units - * @property {Function} handler A flexible alternative to units; must return an object - * @property {Array} locales A list of locales that this format should be restricted to - */ - constructor({ - template = null, - matcher = null, - units = null, - handler = null, - locales = null, - }) { - if (!Array.isArray(units) && typeof handler !== 'function') { - throw new Error( - 'new Format must receive a "units" array or "handler" function' - ); - } - if (typeof template !== 'string' && !(matcher instanceof RegExp)) { - throw new Error( - 'new Format must receive a "template" string or "matcher" RegExp' - ); - } - /** - * @type {String} template A template for RegExp that can handle multiple languages - */ - this.template = template; +export default class Format { + /** + * Given a definition, create a parsable format + * @param {Object} definition The format definition + * @property {String} template A template for RegExp that can handle multiple languages + * @property {RegExp} matcher An actual RegExp to match against + * @property {Array} units If the template or RegExp match exact units, you can define the units + * @property {Function} handler A flexible alternative to units; must return an object + * @property {Array} locales A list of locales that this format should be restricted to + */ + constructor({ + template = null, + matcher = null, + units = null, + handler = null, + locales = null, + }) { + if (!Array.isArray(units) && typeof handler !== 'function') { + throw new Error( + 'new Format must receive a "units" array or "handler" function' + ); + } + if (typeof template !== 'string' && !(matcher instanceof RegExp)) { + throw new Error( + 'new Format must receive a "template" string or "matcher" RegExp' + ); + } + /** + * @type {String} template A template for RegExp that can handle multiple languages + */ + this.template = template; - /** - * @type {Array} units If the template or RegExp match exact units, you can define the units - */ - this.units = units; + /** + * @type {Array} units If the template or RegExp match exact units, you can define the units + */ + this.units = units; - /** - * @type {RegExp} matcher An actual RegExp to match against - */ - this.matcher = matcher; + /** + * @type {RegExp} matcher An actual RegExp to match against + */ + this.matcher = matcher; - /** - * @type {Function} handler A flexible alternative to units; must return an object - */ - this.handler = handler; + /** + * @type {Function} handler A flexible alternative to units; must return an object + */ + this.handler = handler; - /** - * @type {String[]} locales A list of locales that this format should be restricted to - */ - this.locales = locales; + /** + * @type {String[]} locales A list of locales that this format should be restricted to + */ + this.locales = locales; - /** - * A cache of RegExp indexed by locale name - * @type {Object} - */ - this.regexByLocale = {}; - } + /** + * A cache of RegExp indexed by locale name + * @type {Object} + */ + this.regexByLocale = {}; + } - /** - * Build the RegExp from the template for a given locale - * @param {String} locale The language locale such as en-US, pt-BR, zh, es, etc. - * @returns {RegExp} A RegExp that matches when this format is recognized - */ - getRegExp(locale = defaultLocale) { - if (this.template) { - if (!this.regexByLocale[locale]) { - this.regexByLocale[locale] = LocaleHelper.factory(locale).compile( - this.template - ); - //console.log([locale, this.regexByLocale[locale]]); - } - // if (locale.slice(0, 2) === 'zh') { - // console.log(this.template, this.regexByLocale[locale]); - // } - return this.regexByLocale[locale]; - } - return this.matcher; - } + /** + * Build the RegExp from the template for a given locale + * @param {String} locale The language locale such as en-US, pt-BR, zh, es, etc. + * @returns {RegExp} A RegExp that matches when this format is recognized + */ + getRegExp(locale = defaultLocale) { + if (this.template) { + if (!this.regexByLocale[locale]) { + this.regexByLocale[locale] = LocaleHelper.factory(locale).compile( + this.template + ); + //console.log([locale, this.regexByLocale[locale]]); + } + // if (locale.slice(0, 2) === 'zh') { + // console.log(this.template, this.regexByLocale[locale]); + // } + return this.regexByLocale[locale]; + } + return this.matcher; + } - /** - * Run this format's RegExp against the given string - * @param {String} string The date string - * @param {String} locale The language locale such as en-US, pt-BR, zh, es, etc. - * @returns {Array|null} Array of matches or null on non-match - */ - getMatches(string, locale = defaultLocale) { - return string.match(this.getRegExp(locale)); - } + /** + * Run this format's RegExp against the given string + * @param {String} string The date string + * @param {String} locale The language locale such as en-US, pt-BR, zh, es, etc. + * @returns {Array|null} Array of matches or null on non-match + */ + getMatches(string, locale = defaultLocale) { + return string.match(this.getRegExp(locale)); + } - /** - * Given matches against this RegExp, convert to object - * @param {String[]} matches An array of matched parts - * @param {String} locale The language locale such as en-US, pt-BR, zh, es, etc. - * @returns {Object} Object which may contain year, month, day, hour, minute, second, millisecond, offset, invalid - */ - toDateTime(matches, locale = defaultLocale) { - const locHelper = LocaleHelper.factory(locale); - if (this.units) { - return locHelper.getObject(this.units, matches); - } - const dt = this.handler(matches, locale); - if (!dt || dt.invalid) { - return dt; - } - return locHelper.castObject(dt); - } + /** + * Given matches against this RegExp, convert to object + * @param {String[]} matches An array of matched parts + * @param {String} locale The language locale such as en-US, pt-BR, zh, es, etc. + * @returns {Object} Object which may contain year, month, day, hour, minute, second, millisecond, offset, invalid + */ + toDateTime(matches, locale = defaultLocale) { + const locHelper = LocaleHelper.factory(locale); + if (this.units) { + return locHelper.getObject(this.units, matches); + } + const dt = this.handler(matches, locale); + if (!dt || dt.invalid) { + return dt; + } + return locHelper.castObject(dt); + } - /** - * Attempt to parse a string in this format - * @param {String} string The date string - * @param {String} locale The language locale such as en-US, pt-BR, zh, es, etc. - * @returns {Object|null} Null if format can't handle this string, Object for result or error - */ - attempt(string, locale = defaultLocale) { - let effectiveLocale = locale; - const bounds = { - lower: '0001-01-01T00:00:00', - upper: '9999-12-31T23:59:59', - inclusive: true, - strict: false, - }; - if (typeof locale === 'object') { - effectiveLocale = locale.locale || defaultLocale; - Object.assign(bounds, locale.bounds || {}); - } - string = removeFillerWords(String(string), locale).trim(); - const matches = this.getMatches(string, locale); - if (matches) { - const dt = this.toDateTime(matches, locale); - const dtDate = this.dt; - if ( - dtDate instanceof Date && - !this.isInRange(dtDate, bounds) && - bounds.strict - ) { - const inclusive = bounds.inclusive ? 'inclusive' : 'not inclusive'; - return { - invalid: `Date not in range ${bounds.lower} to ${bounds.upper} ${inclusive}`, - bounds, - }; - } - return dt || null; - } - return null; - } + /** + * Attempt to parse a string in this format + * @param {String} string The date string + * @param {String} locale The language locale such as en-US, pt-BR, zh, es, etc. + * @returns {Object|null} Null if format can't handle this string, Object for result or error + */ + attempt(string, locale = defaultLocale) { + let effectiveLocale = locale; + const bounds = { + lower: '0001-01-01T00:00:00', + upper: '9999-12-31T23:59:59', + inclusive: true, + strict: false, + }; + if (typeof locale === 'object') { + effectiveLocale = locale.locale || defaultLocale; + Object.assign(bounds, locale.bounds || {}); + } + string = removeFillerWords(String(string), locale).trim(); + const matches = this.getMatches(string, locale); + if (matches) { + const dt = this.toDateTime(matches, locale); + const dtDate = this.dt; + if ( + dtDate instanceof Date && + !this.isInRange(dtDate, bounds) && + bounds.strict + ) { + const inclusive = bounds.inclusive ? 'inclusive' : 'not inclusive'; + return { + invalid: `Date not in range ${bounds.lower} to ${bounds.upper} ${inclusive}`, + bounds, + }; + } + return dt || null; + } + return null; + } - isInRange(date, bounds) { - const dateStr = date.toJSON(); - if (bounds.inclusive) { - return dateStr >= bounds.lower && dateStr <= bounds.upper; - } - return dateStr > bounds.lower && dateStr < bounds.upper; - } + isInRange(date, bounds) { + const dateStr = date.toJSON(); + if (bounds.inclusive) { + return dateStr >= bounds.lower && dateStr <= bounds.upper; + } + return dateStr > bounds.lower && dateStr < bounds.upper; + } - /** - * Return the current date (used to support unit testing) - * @returns {Date} - */ - now() { - return new Date(); - } + /** + * Return the current date (used to support unit testing) + * @returns {Date} + */ + now() { + return new Date(); + } } - -module.exports = Format; diff --git a/src/LocaleHelper/LocaleHelper.spec.ts b/src/LocaleHelper/LocaleHelper.spec.ts index de74304..7c80893 100644 --- a/src/LocaleHelper/LocaleHelper.spec.ts +++ b/src/LocaleHelper/LocaleHelper.spec.ts @@ -1,170 +1,171 @@ -const LocaleHelper = require('./LocaleHelper.js'); +import { describe, expect, it } from 'vitest'; +import LocaleHelper from './LocaleHelper'; describe('LocaleHelper general', () => { - it('should use singleton pattern', () => { - const l1 = LocaleHelper.factory('en'); - const l2 = LocaleHelper.factory('en'); - expect(l1).toBe(l2); - }); - it('should use singleton pattern (case insensitive)', () => { - const l1 = LocaleHelper.factory('en'); - const l2 = LocaleHelper.factory('En'); - expect(l1).toBe(l2); - }); - it('should factory default to system default', () => { - const l1 = LocaleHelper.factory(); - expect(l1.locale).toBeTruthy(); - }); - it('should instance default to system default', () => { - const l1 = new LocaleHelper(); - expect(l1.locale).toBeTruthy(); - }); - it('should store locale name', () => { - const l = new LocaleHelper('en-GB'); - expect(l.locale).toBe('en-GB'); - }); - it('should build objects from numbers', () => { - const l = new LocaleHelper('en'); - const units = ['year', 'month', 'offset']; - const matches = [null, '2020', '10', '+07:30']; - const expected = { year: 2020, month: 10, offset: 450 }; - expect(l.getObject(units, matches)).toEqual(expected); - }); - it('should build objects from month name', () => { - const l = new LocaleHelper('en'); - const units = ['month', 'minute']; - const matches = [null, 'september', '59']; - const expected = { month: 9, minute: 59 }; - expect(l.getObject(units, matches)).toEqual(expected); - }); - it('should build objects from short month name', () => { - const l = new LocaleHelper('en'); - const units = ['month', 'hour']; - const matches = [null, 'sep', '23']; - const expected = { month: 9, hour: 23 }; - expect(l.getObject(units, matches)).toEqual(expected); - }); - it('should build objects from short month name with period', () => { - const l = new LocaleHelper('en'); - const units = ['month', 'second']; - const matches = [null, 'sep.', '00']; - const expected = { month: 9, second: 0 }; - expect(l.getObject(units, matches)).toEqual(expected); - }); - it('should build objects from "deva" numbers', () => { - const l = new LocaleHelper('ar'); - const units = ['year', 'month']; - const matches = [null, '٢٠١٧', '٦']; - const expected = { year: 2017, month: 6 }; - expect(l.getObject(units, matches)).toEqual(expected); - }); - it('should handle invalid offsets', () => { - const l = new LocaleHelper('en'); - const actual = l.offsetToMinutes('foo'); - expect(actual).toEqual(0); - }); - it('should error on bad templates', () => { - const l = new LocaleHelper('ar'); - function invalidFoobar() { - return l.compile('_FOOBAR_'); - } - expect(invalidFoobar).toThrowError( - 'Template string contains invalid variable _FOOBAR_' - ); - }); + it('should use singleton pattern', () => { + const l1 = LocaleHelper.factory('en'); + const l2 = LocaleHelper.factory('en'); + expect(l1).toBe(l2); + }); + it('should use singleton pattern (case insensitive)', () => { + const l1 = LocaleHelper.factory('en'); + const l2 = LocaleHelper.factory('En'); + expect(l1).toBe(l2); + }); + it('should factory default to system default', () => { + const l1 = LocaleHelper.factory(); + expect(l1.locale).toBeTruthy(); + }); + it('should instance default to system default', () => { + const l1 = new LocaleHelper(); + expect(l1.locale).toBeTruthy(); + }); + it('should store locale name', () => { + const l = new LocaleHelper('en-GB'); + expect(l.locale).toBe('en-GB'); + }); + it('should build objects from numbers', () => { + const l = new LocaleHelper('en'); + const units = ['year', 'month', 'offset']; + const matches = [null, '2020', '10', '+07:30']; + const expected = { year: 2020, month: 10, offset: 450 }; + expect(l.getObject(units, matches)).toEqual(expected); + }); + it('should build objects from month name', () => { + const l = new LocaleHelper('en'); + const units = ['month', 'minute']; + const matches = [null, 'september', '59']; + const expected = { month: 9, minute: 59 }; + expect(l.getObject(units, matches)).toEqual(expected); + }); + it('should build objects from short month name', () => { + const l = new LocaleHelper('en'); + const units = ['month', 'hour']; + const matches = [null, 'sep', '23']; + const expected = { month: 9, hour: 23 }; + expect(l.getObject(units, matches)).toEqual(expected); + }); + it('should build objects from short month name with period', () => { + const l = new LocaleHelper('en'); + const units = ['month', 'second']; + const matches = [null, 'sep.', '00']; + const expected = { month: 9, second: 0 }; + expect(l.getObject(units, matches)).toEqual(expected); + }); + it('should build objects from "deva" numbers', () => { + const l = new LocaleHelper('ar'); + const units = ['year', 'month']; + const matches = [null, '٢٠١٧', '٦']; + const expected = { year: 2017, month: 6 }; + expect(l.getObject(units, matches)).toEqual(expected); + }); + it('should handle invalid offsets', () => { + const l = new LocaleHelper('en'); + const actual = l.offsetToMinutes('foo'); + expect(actual).toEqual(0); + }); + it('should error on bad templates', () => { + const l = new LocaleHelper('ar'); + function invalidFoobar() { + return l.compile('_FOOBAR_'); + } + expect(invalidFoobar).toThrowError( + 'Template string contains invalid variable _FOOBAR_' + ); + }); }); describe('LocaleHelper numbering systems', () => { - it('should cast digit string to number (latn)', () => { - const l = new LocaleHelper('en'); - expect(l.toInt('1234567890')).toBe(1234567890); - }); - it('should cast digit string to number (invalid)', () => { - const l = new LocaleHelper('en-u-nu-invalid'); - expect(l.toInt('1234567890')).toBe(1234567890); - }); - it('should cast digit string to number (arab)', () => { - const l = new LocaleHelper('en-u-nu-arab'); - expect(l.toInt('١٢٣٤٥٦٧٨٩٠')).toBe(1234567890); - }); - it('should cast digit string to number (arabext)', () => { - const l = new LocaleHelper('en-u-nu-arabext'); - expect(l.toInt('۱۲۳۴۵۶۷۸۹۰')).toBe(1234567890); - }); - it('should cast digit string to number (bali)', () => { - const l = new LocaleHelper('en-u-nu-bali'); - expect(l.toInt('᭑᭒᭓᭔᭕᭖᭗᭘᭙᭐')).toBe(1234567890); - }); - it('should cast digit string to number (beng)', () => { - const l = new LocaleHelper('en-u-nu-beng'); - expect(l.toInt('১২৩৪৫৬৭৮৯০')).toBe(1234567890); - }); - it('should cast digit string to number (deva)', () => { - const l = new LocaleHelper('en-u-nu-deva'); - expect(l.toInt('१२३४५६७८९०')).toBe(1234567890); - }); - it('should cast digit string to number (fullwide)', () => { - const l = new LocaleHelper('en-u-nu-fullwide'); - expect(l.toInt('1234567890')).toBe(1234567890); - expect(l.toInt('一二三四五六七八九〇')).toBe(1234567890); - expect(l.toInt('1234567890')).toBe(1234567890); - }); - it('should cast digit string to number (gujr)', () => { - const l = new LocaleHelper('en-u-nu-gujr'); - expect(l.toInt('૧૨૩૪૫૬૭૮૯૦')).toBe(1234567890); - }); - it('should cast digit string to number (hanidec)', () => { - const l = new LocaleHelper('en-u-nu-hanidec'); - expect(l.toInt('1234567890')).toBe(1234567890); - expect(l.toInt('一二三四五六七八九〇')).toBe(1234567890); - expect(l.toInt('1234567890')).toBe(1234567890); - }); - it('should cast digit string to number (khmr)', () => { - const l = new LocaleHelper('en-u-nu-khmr'); - expect(l.toInt('១២៣៤៥៦៧៨៩០')).toBe(1234567890); - }); - it('should cast digit string to number (knda)', () => { - const l = new LocaleHelper('en-u-nu-knda'); - expect(l.toInt('೧೨೩೪೫೬೭೮೯೦')).toBe(1234567890); - }); - it('should cast digit string to number (laoo)', () => { - const l = new LocaleHelper('en-u-nu-laoo'); - expect(l.toInt('໑໒໓໔໕໖໗໘໙໐')).toBe(1234567890); - }); - it('should cast digit string to number (limb)', () => { - const l = new LocaleHelper('en-u-nu-limb'); - expect(l.toInt('᥇᥈᥉᥊᥋᥌᥍᥎᥏᥆')).toBe(1234567890); - }); - it('should cast digit string to number (mlym)', () => { - const l = new LocaleHelper('en-u-nu-mlym'); - expect(l.toInt('൧൨൩൪൫൬൭൮൯൦')).toBe(1234567890); - }); - it('should cast digit string to number (mong)', () => { - const l = new LocaleHelper('en-u-nu-mong'); - expect(l.toInt('᠑᠒᠓᠔᠕᠖᠗᠘᠙᠐')).toBe(1234567890); - }); - it('should cast digit string to number (mymr)', () => { - const l = new LocaleHelper('en-u-nu-mymr'); - expect(l.toInt('၁၂၃၄၅၆၇၈၉၀')).toBe(1234567890); - }); - it('should cast digit string to number (orya)', () => { - const l = new LocaleHelper('en-u-nu-orya'); - expect(l.toInt('୧୨୩୪୫୬୭୮୯୦')).toBe(1234567890); - }); - it('should cast digit string to number (tamldec)', () => { - const l = new LocaleHelper('en-u-nu-tamldec'); - expect(l.toInt('௧௨௩௪௫௬௭௮௯௦')).toBe(1234567890); - }); - it('should cast digit string to number (telu)', () => { - const l = new LocaleHelper('en-u-nu-telu'); - expect(l.toInt('౧౨౩౪౫౬౭౮౯౦')).toBe(1234567890); - }); - it('should cast digit string to number (thai)', () => { - const l = new LocaleHelper('en-u-nu-thai'); - expect(l.toInt('๑๒๓๔๕๖๗๘๙๐')).toBe(1234567890); - }); - it('should cast digit string to number (tibt)', () => { - const l = new LocaleHelper('en-u-nu-tibt'); - expect(l.toInt('༡༢༣༤༥༦༧༨༩༠')).toBe(1234567890); - }); + it('should cast digit string to number (latn)', () => { + const l = new LocaleHelper('en'); + expect(l.toInt('1234567890')).toBe(1234567890); + }); + it('should cast digit string to number (invalid)', () => { + const l = new LocaleHelper('en-u-nu-invalid'); + expect(l.toInt('1234567890')).toBe(1234567890); + }); + it('should cast digit string to number (arab)', () => { + const l = new LocaleHelper('en-u-nu-arab'); + expect(l.toInt('١٢٣٤٥٦٧٨٩٠')).toBe(1234567890); + }); + it('should cast digit string to number (arabext)', () => { + const l = new LocaleHelper('en-u-nu-arabext'); + expect(l.toInt('۱۲۳۴۵۶۷۸۹۰')).toBe(1234567890); + }); + it('should cast digit string to number (bali)', () => { + const l = new LocaleHelper('en-u-nu-bali'); + expect(l.toInt('᭑᭒᭓᭔᭕᭖᭗᭘᭙᭐')).toBe(1234567890); + }); + it('should cast digit string to number (beng)', () => { + const l = new LocaleHelper('en-u-nu-beng'); + expect(l.toInt('১২৩৪৫৬৭৮৯০')).toBe(1234567890); + }); + it('should cast digit string to number (deva)', () => { + const l = new LocaleHelper('en-u-nu-deva'); + expect(l.toInt('१२३४५६७८९०')).toBe(1234567890); + }); + it('should cast digit string to number (fullwide)', () => { + const l = new LocaleHelper('en-u-nu-fullwide'); + expect(l.toInt('1234567890')).toBe(1234567890); + expect(l.toInt('一二三四五六七八九〇')).toBe(1234567890); + expect(l.toInt('1234567890')).toBe(1234567890); + }); + it('should cast digit string to number (gujr)', () => { + const l = new LocaleHelper('en-u-nu-gujr'); + expect(l.toInt('૧૨૩૪૫૬૭૮૯૦')).toBe(1234567890); + }); + it('should cast digit string to number (hanidec)', () => { + const l = new LocaleHelper('en-u-nu-hanidec'); + expect(l.toInt('1234567890')).toBe(1234567890); + expect(l.toInt('一二三四五六七八九〇')).toBe(1234567890); + expect(l.toInt('1234567890')).toBe(1234567890); + }); + it('should cast digit string to number (khmr)', () => { + const l = new LocaleHelper('en-u-nu-khmr'); + expect(l.toInt('១២៣៤៥៦៧៨៩០')).toBe(1234567890); + }); + it('should cast digit string to number (knda)', () => { + const l = new LocaleHelper('en-u-nu-knda'); + expect(l.toInt('೧೨೩೪೫೬೭೮೯೦')).toBe(1234567890); + }); + it('should cast digit string to number (laoo)', () => { + const l = new LocaleHelper('en-u-nu-laoo'); + expect(l.toInt('໑໒໓໔໕໖໗໘໙໐')).toBe(1234567890); + }); + it('should cast digit string to number (limb)', () => { + const l = new LocaleHelper('en-u-nu-limb'); + expect(l.toInt('᥇᥈᥉᥊᥋᥌᥍᥎᥏᥆')).toBe(1234567890); + }); + it('should cast digit string to number (mlym)', () => { + const l = new LocaleHelper('en-u-nu-mlym'); + expect(l.toInt('൧൨൩൪൫൬൭൮൯൦')).toBe(1234567890); + }); + it('should cast digit string to number (mong)', () => { + const l = new LocaleHelper('en-u-nu-mong'); + expect(l.toInt('᠑᠒᠓᠔᠕᠖᠗᠘᠙᠐')).toBe(1234567890); + }); + it('should cast digit string to number (mymr)', () => { + const l = new LocaleHelper('en-u-nu-mymr'); + expect(l.toInt('၁၂၃၄၅၆၇၈၉၀')).toBe(1234567890); + }); + it('should cast digit string to number (orya)', () => { + const l = new LocaleHelper('en-u-nu-orya'); + expect(l.toInt('୧୨୩୪୫୬୭୮୯୦')).toBe(1234567890); + }); + it('should cast digit string to number (tamldec)', () => { + const l = new LocaleHelper('en-u-nu-tamldec'); + expect(l.toInt('௧௨௩௪௫௬௭௮௯௦')).toBe(1234567890); + }); + it('should cast digit string to number (telu)', () => { + const l = new LocaleHelper('en-u-nu-telu'); + expect(l.toInt('౧౨౩౪౫౬౭౮౯౦')).toBe(1234567890); + }); + it('should cast digit string to number (thai)', () => { + const l = new LocaleHelper('en-u-nu-thai'); + expect(l.toInt('๑๒๓๔๕๖๗๘๙๐')).toBe(1234567890); + }); + it('should cast digit string to number (tibt)', () => { + const l = new LocaleHelper('en-u-nu-tibt'); + expect(l.toInt('༡༢༣༤༥༦༧༨༩༠')).toBe(1234567890); + }); }); diff --git a/src/LocaleHelper/LocaleHelper.ts b/src/LocaleHelper/LocaleHelper.ts index 56e1883..3911791 100644 --- a/src/LocaleHelper/LocaleHelper.ts +++ b/src/LocaleHelper/LocaleHelper.ts @@ -1,314 +1,317 @@ -const baseLookups = require('../data/baseLookups.js'); -const { latn, other } = require('../data/templates.js'); -const buildDigits = require('../buildDigits/buildDigits.js'); -const defaultLocale = require('../data/defaultLocale.js'); -const units = require('../data/units.js'); +import buildDigits from '../buildDigits/buildDigits'; +import baseLookups from '../data/baseLookups'; +import defaultLocale from '../data/defaultLocale'; +import { latn, other } from '../data/templates.js'; +import units from '../data/units'; // keep track of singletons by locale name const cache = {}; -class LocaleHelper { - /** - * Get a singleton instance with the given locale - * @param {String} locale such as en, en-US, es, fr-FR, etc. - * @returns {LocaleHelper} - */ - static factory(locale = defaultLocale) { - if (!cache[locale.toLowerCase()]) { - cache[locale.toLowerCase()] = new LocaleHelper(locale); - } - return cache[locale.toLowerCase()]; - } +export default class LocaleHelper { + locale: string; + lookups: Record; + vars: Record; + numberingSystem: string; - /** - * Create a new instance with the given locale - * @param {String} locale such as en, en-US, es, fr-FR, etc. - */ - constructor(locale = defaultLocale) { - /** - * The locale string - * @type {String} - */ - this.locale = locale; - /** - * Lookups for zone, year, meridiem, month, dayname, digit - * @type {Object} lookups - */ - this.lookups = { ...baseLookups }; - /** - * Template variables including MONTHNAME, MONTH, ZONE, etc. - * @type {Object} vars - */ - this.vars = { ...latn }; - const fmt = new Intl.NumberFormat(this.locale); - /** - * The numbering system to use (latn=standard arabic digits) - * @type {String} numberingSystem - */ - this.numberingSystem = fmt.resolvedOptions().numberingSystem; - this.build(); - if (locale.startsWith('bnz')) { - console.log({ - locale, - vars: this.vars, - numberingSystem: this.numberingSystem, - month: this.lookups.month, - dayname: this.lookups.dayname, - digit: this.lookups.digit, - // MONTHNAME: this.vars.MONTHNAME, - // DAYNAME: this.vars.DAYNAME, - }); - } - } + /** + * Get a singleton instance with the given locale + * @param {String} locale such as en, en-US, es, fr-FR, etc. + * @returns {LocaleHelper} + */ + static factory(locale = defaultLocale) { + if (!cache[locale.toLowerCase()]) { + cache[locale.toLowerCase()] = new LocaleHelper(locale); + } + return cache[locale.toLowerCase()]; + } - /** - * Cast a string to an integer, minding numbering system - * @param {String|Number} digitString Such as "2020" or "二〇二〇" - * @returns {Number} - */ - toInt(digitString) { - if (typeof digitString === 'number') { - return digitString; - } - if (this.numberingSystem === 'latn') { - return parseInt(digitString, 10); - } - let latnDigitString = ''; - for (let i = 0; i < digitString.length; i++) { - latnDigitString += String(this.lookups.digit[digitString[i]]); - } - return parseInt(latnDigitString, 10); - } + /** + * Create a new instance with the given locale + * @param {String} locale such as en, en-US, es, fr-FR, etc. + */ + constructor(locale = defaultLocale) { + /** + * The locale string + * @type {String} + */ + this.locale = locale; + /** + * Lookups for zone, year, meridiem, month, dayname, digit + * @type {Object} lookups + */ + this.lookups = { ...baseLookups }; + /** + * Template variables including MONTHNAME, MONTH, ZONE, etc. + * @type {Object} vars + */ + this.vars = { ...latn }; + const fmt = new Intl.NumberFormat(this.locale); + /** + * The numbering system to use (latn=standard arabic digits) + * @type {String} numberingSystem + */ + this.numberingSystem = fmt.resolvedOptions().numberingSystem; + this.build(); + if (locale.startsWith('bnz')) { + console.log({ + locale, + vars: this.vars, + numberingSystem: this.numberingSystem, + month: this.lookups.month, + dayname: this.lookups.dayname, + digit: this.lookups.digit, + // MONTHNAME: this.vars.MONTHNAME, + // DAYNAME: this.vars.DAYNAME, + }); + } + } - /** - * Build lookups for digits, month names, day names, and meridiems based on the locale - */ - build() { - if (this.numberingSystem !== 'latn') { - this.buildNumbers(); - } - if (!/^en/i.test(this.locale)) { - this.buildMonthNames(); - this.buildDaynames(); - this.buildMeridiems(); - } - } + /** + * Cast a string to an integer, minding numbering system + * @param {String|Number} digitString Such as "2020" or "二〇二〇" + * @returns {Number} + */ + toInt(digitString) { + if (typeof digitString === 'number') { + return digitString; + } + if (this.numberingSystem === 'latn') { + return parseInt(digitString, 10); + } + let latnDigitString = ''; + for (let i = 0; i < digitString.length; i++) { + latnDigitString += String(this.lookups.digit[digitString[i]]); + } + return parseInt(latnDigitString, 10); + } - /** - * Build lookups for digits - */ - buildNumbers() { - const nsName = this.numberingSystem; - const { group, lookup } = buildDigits(nsName); - this.lookups.digit = lookup; - for (const name in other) { - /* istanbul ignore next */ - if (!other.hasOwnProperty(name)) { - continue; - } - this.vars[name] = other[name].replace(/\*/g, group); - } - } + /** + * Build lookups for digits, month names, day names, and meridiems based on the locale + */ + build() { + if (this.numberingSystem !== 'latn') { + this.buildNumbers(); + } + if (!/^en/i.test(this.locale)) { + this.buildMonthNames(); + this.buildDaynames(); + this.buildMeridiems(); + } + } - /** - * Build lookup for month names - */ - buildMonthNames() { - const vars = {}; - const lookup = {}; - if (/^fi/i.test(this.locale)) { - const months = - 'tammi|helmi|maalis|huhti|touko|kesä|heinä|elo|syys|loka|marras|joulu'; - months.split('|').forEach((month, idx) => { - ['', 'k', 'kuu', 'kuuta'].forEach((suffix, i) => { - const maybePeriod = i < 2 ? '\\.?' : ''; - vars[month + suffix + maybePeriod] = true; - lookup[month + suffix] = idx + 1; - }); - }); - } else { - const dates = []; - const findMonth = item => item.type === 'month'; - for (let monthIdx = 0; monthIdx < 12; monthIdx++) { - dates.push(new Date(2017, monthIdx, 1)); - } - const dateStyles = ['full', 'long', 'medium']; - for (const dateStyle of dateStyles) { - const format = Intl.DateTimeFormat(this.locale, { dateStyle }); - for (let monthIdx = 0; monthIdx < 12; monthIdx++) { - const parts = format.formatToParts(dates[monthIdx]); - let text = parts.find(findMonth).value.toLocaleLowerCase(this.locale); - if (/^ko/i.test(this.locale)) { - // Korean word for month is sometimes used - text += '월'; - } - if (dateStyle === 'medium') { - // some languages (including arabic and chinese) don't have a 'medium' size - if (/^ar|zh/i.test(this.locale)) { - return; - } - text = text.replace(/\.$/, ''); - vars[`${text}\\.?`] = true; - } else { - vars[text] = true; - } - lookup[text] = monthIdx + 1; - } - } - const format = Intl.DateTimeFormat(this.locale, { month: 'short' }); - for (let monthIdx = 0; monthIdx < 12; monthIdx++) { - const parts = format.formatToParts(dates[monthIdx]); - let text = parts.find(findMonth).value.toLocaleLowerCase(this.locale); - text = text.replace(/\.$/, ''); - vars[`${text}\\.?`] = true; - lookup[text] = monthIdx + 1; - } - } - this.vars.MONTHNAME = Object.keys(vars).join('|'); - this.lookups.month = lookup; - } + /** + * Build lookups for digits + */ + buildNumbers() { + const nsName = this.numberingSystem; + const { group, lookup } = buildDigits(nsName); + this.lookups.digit = lookup; + for (const name in other) { + /* istanbul ignore next */ + if (!other.hasOwnProperty(name)) { + continue; + } + this.vars[name] = other[name].replace(/\*/g, group); + } + } - /** - * Build lookup for day name - */ - buildDaynames() { - const dates = []; - const findDay = item => item.type === 'weekday'; - for (let dayIndex = 0; dayIndex < 7; dayIndex++) { - // Jan 2017 starts on a sunday - dates.push(new Date(2017, 0, dayIndex + 1)); - } - const weekdays = ['long', 'short']; - const list = []; - const lookup = {}; - for (const weekday of weekdays) { - const format = Intl.DateTimeFormat(this.locale, { weekday }); - for (let dayIndex = 0; dayIndex < 7; dayIndex++) { - const parts = format.formatToParts(dates[dayIndex]); - let text = parts.find(findDay).value.toLocaleLowerCase(this.locale); - if (weekday === 'short') { - text = text.replace(/\.$/, ''); - list.push(`${text}\\.?`); - } else { - list.push(text); - } - lookup[text] = dayIndex; - } - } - this.vars.DAYNAME = list.join('|'); - this.lookups.dayname = lookup; - } + /** + * Build lookup for month names + */ + buildMonthNames() { + const vars = {}; + const lookup = {}; + if (/^fi/i.test(this.locale)) { + const months = + 'tammi|helmi|maalis|huhti|touko|kesä|heinä|elo|syys|loka|marras|joulu'; + months.split('|').forEach((month, idx) => { + ['', 'k', 'kuu', 'kuuta'].forEach((suffix, i) => { + const maybePeriod = i < 2 ? '\\.?' : ''; + vars[month + suffix + maybePeriod] = true; + lookup[month + suffix] = idx + 1; + }); + }); + } else { + const dates = []; + const findMonth = item => item.type === 'month'; + for (let monthIdx = 0; monthIdx < 12; monthIdx++) { + dates.push(new Date(2017, monthIdx, 1)); + } + const dateStyles = ['full', 'long', 'medium']; + for (const dateStyle of dateStyles) { + const format = Intl.DateTimeFormat(this.locale, { dateStyle }); + for (let monthIdx = 0; monthIdx < 12; monthIdx++) { + const parts = format.formatToParts(dates[monthIdx]); + let text = parts.find(findMonth).value.toLocaleLowerCase(this.locale); + if (/^ko/i.test(this.locale)) { + // Korean word for month is sometimes used + text += '월'; + } + if (dateStyle === 'medium') { + // some languages (including arabic and chinese) don't have a 'medium' size + if (/^ar|zh/i.test(this.locale)) { + return; + } + text = text.replace(/\.$/, ''); + vars[`${text}\\.?`] = true; + } else { + vars[text] = true; + } + lookup[text] = monthIdx + 1; + } + } + const format = Intl.DateTimeFormat(this.locale, { month: 'short' }); + for (let monthIdx = 0; monthIdx < 12; monthIdx++) { + const parts = format.formatToParts(dates[monthIdx]); + let text = parts.find(findMonth).value.toLocaleLowerCase(this.locale); + text = text.replace(/\.$/, ''); + vars[`${text}\\.?`] = true; + lookup[text] = monthIdx + 1; + } + } + this.vars.MONTHNAME = Object.keys(vars).join('|'); + this.lookups.month = lookup; + } - /** - * Build lookup for meridiems (e.g. AM/PM) - */ - buildMeridiems() { - const dates = [new Date(2017, 0, 1), new Date(2017, 0, 1, 23, 0, 0)]; - const findDayPeriod = item => item.type === 'dayPeriod'; - const list = []; - const lookup = {}; - const format = Intl.DateTimeFormat(this.locale, { timeStyle: 'long' }); - for (let i = 0; i < 2; i++) { - const parts = format.formatToParts(dates[i]); - const dayPeriod = parts.find(findDayPeriod); - if (!dayPeriod) { - // this locale does not use AM/PM - return; - } - const text = dayPeriod.value.toLocaleLowerCase(this.locale); - list.push(text); - lookup[text] = i * 12; - } - this.vars.MERIDIEM = list.join('|'); - this.lookups.meridiem = lookup; - } + /** + * Build lookup for day name + */ + buildDaynames() { + const dates = []; + const findDay = item => item.type === 'weekday'; + for (let dayIndex = 0; dayIndex < 7; dayIndex++) { + // Jan 2017 starts on a sunday + dates.push(new Date(2017, 0, dayIndex + 1)); + } + const weekdays = ['long', 'short']; + const list = []; + const lookup = {}; + for (const weekday of weekdays) { + const format = Intl.DateTimeFormat(this.locale, { weekday }); + for (let dayIndex = 0; dayIndex < 7; dayIndex++) { + const parts = format.formatToParts(dates[dayIndex]); + let text = parts.find(findDay).value.toLocaleLowerCase(this.locale); + if (weekday === 'short') { + text = text.replace(/\.$/, ''); + list.push(`${text}\\.?`); + } else { + list.push(text); + } + lookup[text] = dayIndex; + } + } + this.vars.DAYNAME = list.join('|'); + this.lookups.dayname = lookup; + } - /** - * Given a list of unit names and matches, build result object - * @param {Array} units Unit names such as "year", "month" and "millisecond" - * @param {Array} matches The values matched by a Format's RegExp - * @returns {Object} - */ - getObject(units, matches) { - const object = {}; - units.forEach((unit, i) => { - if (!unit) { - return; - } - let match = matches[i + 1]; - match = match.toLocaleLowerCase(this.locale); - match = match.replace(/\.$/, ''); - if (unit === 'offset') { - object.offset = this.offsetToMinutes(match); - } else if (this.lookups[unit]) { - object[unit] = this.lookups[unit][match] || this.toInt(match); - } else { - object[unit] = this.toInt(match); - } - }); - return object; - } + /** + * Build lookup for meridiems (e.g. AM/PM) + */ + buildMeridiems() { + const dates = [new Date(2017, 0, 1), new Date(2017, 0, 1, 23, 0, 0)]; + const findDayPeriod = item => item.type === 'dayPeriod'; + const list = []; + const lookup = {}; + const format = Intl.DateTimeFormat(this.locale, { timeStyle: 'long' }); + for (let i = 0; i < 2; i++) { + const parts = format.formatToParts(dates[i]); + const dayPeriod = parts.find(findDayPeriod); + if (!dayPeriod) { + // this locale does not use AM/PM + return; + } + const text = dayPeriod.value.toLocaleLowerCase(this.locale); + list.push(text); + lookup[text] = i * 12; + } + this.vars.MERIDIEM = list.join('|'); + this.lookups.meridiem = lookup; + } - /** - * Take a response object and cast each unit to Number - * @param {Object} object An object with one or more units - * @returns {Object} An object with same units but Numeric - */ - castObject(object) { - // if (/^bn/.test(this.locale)) { - // console.log({ casting: object }); - // } - const casted = {}; - units.forEach(unit => { - if (unit in object) { - casted[unit] = this.toInt(object[unit]); - } - }); - if (typeof object.offset === 'string') { - casted.offset = this.offsetToMinutes(object.offset); - } else if (typeof object.offset === 'number') { - casted.offset = object.offset; - } - return casted; - } + /** + * Given a list of unit names and matches, build result object + * @param {Array} units Unit names such as "year", "month" and "millisecond" + * @param {Array} matches The values matched by a Format's RegExp + * @returns {Object} + */ + getObject(units, matches) { + const object = {}; + units.forEach((unit, i) => { + if (!unit) { + return; + } + let match = matches[i + 1]; + match = match.toLocaleLowerCase(this.locale); + match = match.replace(/\.$/, ''); + if (unit === 'offset') { + object.offset = this.offsetToMinutes(match); + } else if (this.lookups[unit]) { + object[unit] = this.lookups[unit][match] || this.toInt(match); + } else { + object[unit] = this.toInt(match); + } + }); + return object; + } - /** - * Convert an offset string to Numeric minutes (e.g. "-0500", "+5", "+03:30") - * @param {String} offsetString - * @returns {Number} - */ - offsetToMinutes(offsetString) { - const captured = offsetString.match(/^([+-])(..?):?(..)?$/); - if (captured) { - const [, sign, hours, minutes] = captured; - return ( - (sign === '-' ? -1 : 1) * - (this.toInt(hours) * 60 + this.toInt(minutes || 0)) - ); - } - return 0; - } + /** + * Take a response object and cast each unit to Number + * @param {Object} object An object with one or more units + * @returns {Object} An object with same units but Numeric + */ + castObject(object) { + // if (/^bn/.test(this.locale)) { + // console.log({ casting: object }); + // } + const casted = {}; + units.forEach(unit => { + if (unit in object) { + casted[unit] = this.toInt(object[unit]); + } + }); + if (typeof object.offset === 'string') { + casted.offset = this.offsetToMinutes(object.offset); + } else if (typeof object.offset === 'number') { + casted.offset = object.offset; + } + return casted; + } - /** - * Compile template into a RegExp and return it - * @param {String} template The template string - * @returns {RegExp} - */ - compile(template) { - const regexString = template.replace(/_([A-Z0-9]+)_/g, ($0, $1) => { - if (!this.vars[$1]) { - throw new Error(`Template string contains invalid variable _${$1}_`); - } - return this.vars[$1]; - }); - // if ( - // /^bnz/i.test(this.locale) || - // template === '^(_DAY_)(_GAP_)(_MONTH_)\\2(_YEAR_)$' - // ) { - // console.log([this.locale, template, new RegExp(regexString, 'i')]); - // } - return new RegExp(regexString, 'i'); - } -} + /** + * Convert an offset string to Numeric minutes (e.g. "-0500", "+5", "+03:30") + * @param {String} offsetString + * @returns {Number} + */ + offsetToMinutes(offsetString) { + const captured = offsetString.match(/^([+-])(..?):?(..)?$/); + if (captured) { + const [, sign, hours, minutes] = captured; + return ( + (sign === '-' ? -1 : 1) * + (this.toInt(hours) * 60 + this.toInt(minutes || 0)) + ); + } + return 0; + } -module.exports = LocaleHelper; + /** + * Compile template into a RegExp and return it + * @param {String} template The template string + * @returns {RegExp} + */ + compile(template) { + const regexString = template.replace(/_([A-Z0-9]+)_/g, ($0, $1) => { + if (!this.vars[$1]) { + throw new Error(`Template string contains invalid variable _${$1}_`); + } + return this.vars[$1]; + }); + // if ( + // /^bnz/i.test(this.locale) || + // template === '^(_DAY_)(_GAP_)(_MONTH_)\\2(_YEAR_)$' + // ) { + // console.log([this.locale, template, new RegExp(regexString, 'i')]); + // } + return new RegExp(regexString, 'i'); + } +} diff --git a/src/Parser/Parser.spec.ts b/src/Parser/Parser.spec.ts index 7f091a1..f490932 100644 --- a/src/Parser/Parser.spec.ts +++ b/src/Parser/Parser.spec.ts @@ -1,52 +1,53 @@ -const Parser = require('./Parser.js'); +import { describe, expect, it } from 'vitest'; +import Parser from './Parser'; describe('Parser', () => { - it('should set a parser prop on the format', () => { - const format = {}; - const parser = new Parser(); - parser.addFormat(format); - expect(format.parser).toBe(parser); - expect(parser.formats).toEqual([format]); - }); - it('should remove format', () => { - const format = {}; - const parser = new Parser(); - parser.addFormat(format); - const result = parser.removeFormat(format); - expect(result).toBe(true); - expect(format.parser).toBe(null); - expect(parser.formats).toEqual([]); - }); - it('should fail to remove unadded format', () => { - const format = {}; - const parser = new Parser(); - const result = parser.removeFormat(format); - expect(result).toBe(false); - }); - it('should attempt() a single format', () => { - const format = { attempt: jest.fn(() => 'foo') }; - const parser = new Parser(); - parser.addFormat(format); - const result = parser.attempt('date', 'locale'); - expect(result).toBe('foo'); - expect(format.attempt).toHaveBeenCalledWith('date', 'locale'); - }); - it('should attempt() 2 formats', () => { - const format1 = { attempt: jest.fn(() => null) }; - const format2 = { attempt: jest.fn(() => 'foo') }; - const parser = new Parser(); - parser.addFormat(format1); - parser.addFormat(format2); - const result = parser.attempt('date', 'locale'); - expect(result).toBe('foo'); - expect(format1.attempt).toHaveBeenCalledWith('date', 'locale'); - expect(format2.attempt).toHaveBeenCalledWith('date', 'locale'); - }); - it('should return invalid when all attempt()s fail', () => { - const format = { attempt: jest.fn(() => null) }; - const parser = new Parser(); - parser.addFormat(format); - const result = parser.attempt('baddate', 'locale'); - expect(result).toEqual({ invalid: 'Unable to parse baddate' }); - }); + it('should set a parser prop on the format', () => { + const format = {}; + const parser = new Parser(); + parser.addFormat(format); + expect(format.parser).toBe(parser); + expect(parser.formats).toEqual([format]); + }); + it('should remove format', () => { + const format = {}; + const parser = new Parser(); + parser.addFormat(format); + const result = parser.removeFormat(format); + expect(result).toBe(true); + expect(format.parser).toBe(null); + expect(parser.formats).toEqual([]); + }); + it('should fail to remove unadded format', () => { + const format = {}; + const parser = new Parser(); + const result = parser.removeFormat(format); + expect(result).toBe(false); + }); + it('should attempt() a single format', () => { + const format = { attempt: jest.fn(() => 'foo') }; + const parser = new Parser(); + parser.addFormat(format); + const result = parser.attempt('date', 'locale'); + expect(result).toBe('foo'); + expect(format.attempt).toHaveBeenCalledWith('date', 'locale'); + }); + it('should attempt() 2 formats', () => { + const format1 = { attempt: jest.fn(() => null) }; + const format2 = { attempt: jest.fn(() => 'foo') }; + const parser = new Parser(); + parser.addFormat(format1); + parser.addFormat(format2); + const result = parser.attempt('date', 'locale'); + expect(result).toBe('foo'); + expect(format1.attempt).toHaveBeenCalledWith('date', 'locale'); + expect(format2.attempt).toHaveBeenCalledWith('date', 'locale'); + }); + it('should return invalid when all attempt()s fail', () => { + const format = { attempt: jest.fn(() => null) }; + const parser = new Parser(); + parser.addFormat(format); + const result = parser.attempt('baddate', 'locale'); + expect(result).toEqual({ invalid: 'Unable to parse baddate' }); + }); }); diff --git a/src/Parser/Parser.ts b/src/Parser/Parser.ts index a7ca9a8..fe532c4 100644 --- a/src/Parser/Parser.ts +++ b/src/Parser/Parser.ts @@ -1,102 +1,101 @@ -const defaultLocale = require('../data/defaultLocale.js'); -const Format = require('../Format/Format.js'); // required to generate index.d.ts -const fromString = require('../fromString/fromString.js'); -const fromAny = require('../fromAny/fromAny.js'); -const removeFillerWords = require('../removeFillerWords/removeFillerWords.js'); +import defaultLocale from '../data/defaultLocale'; +import Format from '../Format/Format'; +import fromAny from '../fromAny/fromAny'; +import fromString from '../fromString/fromString'; -class Parser { - /** - * Initialize an object with an empty array of registered formats - */ - constructor() { - this.formats = []; - } +export default class Parser { + formats: Format[]; - /** - * Register a format object representing a parseable date format - * @param {Format} format The Format to add - * @returns {Parser} - * @chainable - */ - addFormat(format) { - this.formats.push(format); - format.parser = this; - return this; - } + /** + * Initialize an object with an empty array of registered formats + */ + constructor() { + this.formats = []; + } - /** - * Register multiple formats - * @param {Format[]} formats The array of Formats to add - * @returns {Parser} - * @chainable - */ - addFormats(formats) { - formats.forEach(format => this.addFormat(format)); - return this; - } + /** + * Register a format object representing a parseable date format + * @param {Format} format The Format to add + * @returns {Parser} + * @chainable + */ + addFormat(format) { + this.formats.push(format); + format.parser = this; + return this; + } - /** - * Unregister a format - * @param {Format} format The Format to remove - * @returns {Boolean} true if format was found and removed, false if it wasn't registered - */ - removeFormat(format) { - const idx = this.formats.indexOf(format); - if (idx > -1) { - const old = this.formats[idx]; - this.formats.splice(idx, 1); - old.parser = null; - return true; - } - return false; - } + /** + * Register multiple formats + * @param {Format[]} formats The array of Formats to add + * @returns {Parser} + * @chainable + */ + addFormats(formats) { + formats.forEach(format => this.addFormat(format)); + return this; + } - /** - * Attempt to parse a date string - * @param {String} date A parseable date string - * @param {String} locale The name of the locale - * @returns {Object} - */ - attempt(date, locale = defaultLocale) { - for (const format of this.formats) { - if ( - Array.isArray(format.locales) && - format.locales.length > 0 && - !format.locales.includes(new Intl.Locale(locale).baseName) - ) { - // some formats only make sense for certain locales, e.g. month/day/year - continue; - } - const dt = format.attempt(date, locale); - if (dt) { - return dt; - } - } - // Uh Oh! We don't know that one - let string = String(date).slice(0, 200); - if (string === '') { - string = 'empty string'; - } - return { invalid: `Unable to parse ${string}` }; - } + /** + * Unregister a format + * @param {Format} format The Format to remove + * @returns {Boolean} true if format was found and removed, false if it wasn't registered + */ + removeFormat(format) { + const idx = this.formats.indexOf(format); + if (idx > -1) { + const old = this.formats[idx]; + this.formats.splice(idx, 1); + old.parser = null; + return true; + } + return false; + } - /** - * Export this parser as a single function that takes a string - * @param {String} locale The default locale it should use - * @returns {Function} - */ - exportAsFunction(locale = defaultLocale) { - return fromString(this, locale); - } + /** + * Attempt to parse a date string + * @param {String} date A parseable date string + * @param {String} locale The name of the locale + * @returns {Object} + */ + attempt(date, locale = defaultLocale) { + for (const format of this.formats) { + if ( + Array.isArray(format.locales) && + format.locales.length > 0 && + !format.locales.includes(new Intl.Locale(locale).baseName) + ) { + // some formats only make sense for certain locales, e.g. month/day/year + continue; + } + const dt = format.attempt(date, locale); + if (dt) { + return dt; + } + } + // Uh Oh! We don't know that one + let string = String(date).slice(0, 200); + if (string === '') { + string = 'empty string'; + } + return { invalid: `Unable to parse ${string}` }; + } - /** - * Export this parser as a single function that takes a string or Date - * @param {String} locale The default locale it should use - * @returns {Function} - */ - exportAsFunctionAny(locale = defaultLocale) { - return fromAny(fromString(this, locale)); - } -} + /** + * Export this parser as a single function that takes a string + * @param {String} locale The default locale it should use + * @returns {Function} + */ + exportAsFunction(locale = defaultLocale) { + return fromString(this, locale); + } -module.exports = Parser; + /** + * Export this parser as a single function that takes a string or Date + * @param {String} locale The default locale it should use + * @returns {Function} + */ + exportAsFunctionAny(locale = defaultLocale) { + return fromAny(fromString(this, locale)); + } +} diff --git a/src/buildDigits/buildDigits.spec.ts b/src/buildDigits/buildDigits.spec.ts index b3d6ee1..a5aee7c 100644 --- a/src/buildDigits/buildDigits.spec.ts +++ b/src/buildDigits/buildDigits.spec.ts @@ -1,426 +1,427 @@ -const buildDigits = require('./buildDigits.js'); +import { describe, expect, it } from 'vitest'; +import buildDigits from './buildDigits'; describe('unicode numbering systems', () => { - it('should build "arab"', () => { - const actual = buildDigits('arab'); - const expected = { - group: '[٠-٩]', - lookup: { - '٠': 0, - '١': 1, - '٢': 2, - '٣': 3, - '٤': 4, - '٥': 5, - '٦': 6, - '٧': 7, - '٨': 8, - '٩': 9, - }, - }; - expect(actual).toEqual(expected); - }); - it('should build "arabext"', () => { - const actual = buildDigits('arabext'); - const expected = { - group: '[۰-۹]', - lookup: { - '۰': 0, - '۱': 1, - '۲': 2, - '۳': 3, - '۴': 4, - '۵': 5, - '۶': 6, - '۷': 7, - '۸': 8, - '۹': 9, - }, - }; - expect(actual).toEqual(expected); - }); - it('should build "bali"', () => { - const actual = buildDigits('bali'); - const expected = { - group: '[᭐-᭙]', - lookup: { - '᭐': 0, - '᭑': 1, - '᭒': 2, - '᭓': 3, - '᭔': 4, - '᭕': 5, - '᭖': 6, - '᭗': 7, - '᭘': 8, - '᭙': 9, - }, - }; - expect(actual).toEqual(expected); - }); - it('should build "beng"', () => { - const actual = buildDigits('beng'); - const expected = { - group: '[০-৯]', - lookup: { - '০': 0, - '১': 1, - '২': 2, - '৩': 3, - '৪': 4, - '৫': 5, - '৬': 6, - '৭': 7, - '৮': 8, - '৯': 9, - }, - }; - expect(actual).toEqual(expected); - }); - it('should build "deva"', () => { - const actual = buildDigits('deva'); - const expected = { - group: '[०-९]', - lookup: { - '०': 0, - '१': 1, - '२': 2, - '३': 3, - '४': 4, - '५': 5, - '६': 6, - '७': 7, - '८': 8, - '९': 9, - }, - }; - expect(actual).toEqual(expected); - }); - it('should build "fullwide" and "hanidec"', () => { - const actual = buildDigits('fullwide'); - const hanidec = buildDigits('hanidec'); - const expected = { - group: '[1234567890一二三四五六七八九〇\\d]', - lookup: { - 0: 0, - 1: 1, - 2: 2, - 3: 3, - 4: 4, - 5: 5, - 6: 6, - 7: 7, - 8: 8, - 9: 9, - '0': 0, - '1': 1, - '2': 2, - '3': 3, - '4': 4, - '5': 5, - '6': 6, - '7': 7, - '8': 8, - '9': 9, - 〇: 0, - 一: 1, - 二: 2, - 三: 3, - 四: 4, - 五: 5, - 六: 6, - 七: 7, - 八: 8, - 九: 9, - }, - }; - expect(actual).toEqual(expected); - expect(hanidec).toEqual(expected); - }); - it('should build "gujr"', () => { - const actual = buildDigits('gujr'); - const expected = { - group: '[૦-૯]', - lookup: { - '૦': 0, - '૧': 1, - '૨': 2, - '૩': 3, - '૪': 4, - '૫': 5, - '૬': 6, - '૭': 7, - '૮': 8, - '૯': 9, - }, - }; - expect(actual).toEqual(expected); - }); - it('should build "khmr"', () => { - const actual = buildDigits('khmr'); - const expected = { - group: '[០-៩]', - lookup: { - '០': 0, - '១': 1, - '២': 2, - '៣': 3, - '៤': 4, - '៥': 5, - '៦': 6, - '៧': 7, - '៨': 8, - '៩': 9, - }, - }; - expect(actual).toEqual(expected); - }); - it('should build "knda"', () => { - const actual = buildDigits('knda'); - const expected = { - group: '[೦-೯]', - lookup: { - '೦': 0, - '೧': 1, - '೨': 2, - '೩': 3, - '೪': 4, - '೫': 5, - '೬': 6, - '೭': 7, - '೮': 8, - '೯': 9, - }, - }; - expect(actual).toEqual(expected); - }); - it('should build "laoo"', () => { - const actual = buildDigits('laoo'); - const expected = { - group: '[໐-໙]', - lookup: { - '໐': 0, - '໑': 1, - '໒': 2, - '໓': 3, - '໔': 4, - '໕': 5, - '໖': 6, - '໗': 7, - '໘': 8, - '໙': 9, - }, - }; - expect(actual).toEqual(expected); - }); - it('should build "latn"', () => { - const actual = buildDigits('latn'); - const expected = { - group: '\\d', - lookup: { - 0: 0, - 1: 1, - 2: 2, - 3: 3, - 4: 4, - 5: 5, - 6: 6, - 7: 7, - 8: 8, - 9: 9, - '0': 0, - '1': 1, - '2': 2, - '3': 3, - '4': 4, - '5': 5, - '6': 6, - '7': 7, - '8': 8, - '9': 9, - 〇: 0, - 一: 1, - 二: 2, - 三: 3, - 四: 4, - 五: 5, - 六: 6, - 七: 7, - 八: 8, - 九: 9, - }, - }; - expect(actual).toEqual(expected); - }); - it('should build "limb"', () => { - const actual = buildDigits('limb'); - const expected = { - group: '[᥆-᥏]', - lookup: { - '᥆': 0, - '᥇': 1, - '᥈': 2, - '᥉': 3, - '᥊': 4, - '᥋': 5, - '᥌': 6, - '᥍': 7, - '᥎': 8, - '᥏': 9, - }, - }; - expect(actual).toEqual(expected); - }); - it('should build "mlym"', () => { - const actual = buildDigits('mlym'); - const expected = { - group: '[൦-൯]', - lookup: { - '൦': 0, - '൧': 1, - '൨': 2, - '൩': 3, - '൪': 4, - '൫': 5, - '൬': 6, - '൭': 7, - '൮': 8, - '൯': 9, - }, - }; - expect(actual).toEqual(expected); - }); - it('should build "mong"', () => { - const actual = buildDigits('mong'); - const expected = { - group: '[᠐-᠙]', - lookup: { - '᠐': 0, - '᠑': 1, - '᠒': 2, - '᠓': 3, - '᠔': 4, - '᠕': 5, - '᠖': 6, - '᠗': 7, - '᠘': 8, - '᠙': 9, - }, - }; - expect(actual).toEqual(expected); - }); - it('should build "mymr"', () => { - const actual = buildDigits('mymr'); - const expected = { - group: '[၀-၉]', - lookup: { - '၀': 0, - '၁': 1, - '၂': 2, - '၃': 3, - '၄': 4, - '၅': 5, - '၆': 6, - '၇': 7, - '၈': 8, - '၉': 9, - }, - }; - expect(actual).toEqual(expected); - }); - it('should build "orya"', () => { - const actual = buildDigits('orya'); - const expected = { - group: '[୦-୯]', - lookup: { - '୦': 0, - '୧': 1, - '୨': 2, - '୩': 3, - '୪': 4, - '୫': 5, - '୬': 6, - '୭': 7, - '୮': 8, - '୯': 9, - }, - }; - expect(actual).toEqual(expected); - }); - it('should build "tamldec"', () => { - const actual = buildDigits('tamldec'); - const expected = { - group: '[௦-௯]', - lookup: { - '௦': 0, - '௧': 1, - '௨': 2, - '௩': 3, - '௪': 4, - '௫': 5, - '௬': 6, - '௭': 7, - '௮': 8, - '௯': 9, - }, - }; - expect(actual).toEqual(expected); - }); - it('should build "telu"', () => { - const actual = buildDigits('telu'); - const expected = { - group: '[౦-౯]', - lookup: { - '౦': 0, - '౧': 1, - '౨': 2, - '౩': 3, - '౪': 4, - '౫': 5, - '౬': 6, - '౭': 7, - '౮': 8, - '౯': 9, - }, - }; - expect(actual).toEqual(expected); - }); - it('should build "thai"', () => { - const actual = buildDigits('thai'); - const expected = { - group: '[๐-๙]', - lookup: { - '๐': 0, - '๑': 1, - '๒': 2, - '๓': 3, - '๔': 4, - '๕': 5, - '๖': 6, - '๗': 7, - '๘': 8, - '๙': 9, - }, - }; - expect(actual).toEqual(expected); - }); - it('should build "tibt"', () => { - const actual = buildDigits('tibt'); - const expected = { - group: '[༠-༩]', - lookup: { - '༠': 0, - '༡': 1, - '༢': 2, - '༣': 3, - '༤': 4, - '༥': 5, - '༦': 6, - '༧': 7, - '༨': 8, - '༩': 9, - }, - }; - expect(actual).toEqual(expected); - }); + it('should build "arab"', () => { + const actual = buildDigits('arab'); + const expected = { + group: '[٠-٩]', + lookup: { + '٠': 0, + '١': 1, + '٢': 2, + '٣': 3, + '٤': 4, + '٥': 5, + '٦': 6, + '٧': 7, + '٨': 8, + '٩': 9, + }, + }; + expect(actual).toEqual(expected); + }); + it('should build "arabext"', () => { + const actual = buildDigits('arabext'); + const expected = { + group: '[۰-۹]', + lookup: { + '۰': 0, + '۱': 1, + '۲': 2, + '۳': 3, + '۴': 4, + '۵': 5, + '۶': 6, + '۷': 7, + '۸': 8, + '۹': 9, + }, + }; + expect(actual).toEqual(expected); + }); + it('should build "bali"', () => { + const actual = buildDigits('bali'); + const expected = { + group: '[᭐-᭙]', + lookup: { + '᭐': 0, + '᭑': 1, + '᭒': 2, + '᭓': 3, + '᭔': 4, + '᭕': 5, + '᭖': 6, + '᭗': 7, + '᭘': 8, + '᭙': 9, + }, + }; + expect(actual).toEqual(expected); + }); + it('should build "beng"', () => { + const actual = buildDigits('beng'); + const expected = { + group: '[০-৯]', + lookup: { + '০': 0, + '১': 1, + '২': 2, + '৩': 3, + '৪': 4, + '৫': 5, + '৬': 6, + '৭': 7, + '৮': 8, + '৯': 9, + }, + }; + expect(actual).toEqual(expected); + }); + it('should build "deva"', () => { + const actual = buildDigits('deva'); + const expected = { + group: '[०-९]', + lookup: { + '०': 0, + '१': 1, + '२': 2, + '३': 3, + '४': 4, + '५': 5, + '६': 6, + '७': 7, + '८': 8, + '९': 9, + }, + }; + expect(actual).toEqual(expected); + }); + it('should build "fullwide" and "hanidec"', () => { + const actual = buildDigits('fullwide'); + const hanidec = buildDigits('hanidec'); + const expected = { + group: '[1234567890一二三四五六七八九〇\\d]', + lookup: { + 0: 0, + 1: 1, + 2: 2, + 3: 3, + 4: 4, + 5: 5, + 6: 6, + 7: 7, + 8: 8, + 9: 9, + '0': 0, + '1': 1, + '2': 2, + '3': 3, + '4': 4, + '5': 5, + '6': 6, + '7': 7, + '8': 8, + '9': 9, + 〇: 0, + 一: 1, + 二: 2, + 三: 3, + 四: 4, + 五: 5, + 六: 6, + 七: 7, + 八: 8, + 九: 9, + }, + }; + expect(actual).toEqual(expected); + expect(hanidec).toEqual(expected); + }); + it('should build "gujr"', () => { + const actual = buildDigits('gujr'); + const expected = { + group: '[૦-૯]', + lookup: { + '૦': 0, + '૧': 1, + '૨': 2, + '૩': 3, + '૪': 4, + '૫': 5, + '૬': 6, + '૭': 7, + '૮': 8, + '૯': 9, + }, + }; + expect(actual).toEqual(expected); + }); + it('should build "khmr"', () => { + const actual = buildDigits('khmr'); + const expected = { + group: '[០-៩]', + lookup: { + '០': 0, + '១': 1, + '២': 2, + '៣': 3, + '៤': 4, + '៥': 5, + '៦': 6, + '៧': 7, + '៨': 8, + '៩': 9, + }, + }; + expect(actual).toEqual(expected); + }); + it('should build "knda"', () => { + const actual = buildDigits('knda'); + const expected = { + group: '[೦-೯]', + lookup: { + '೦': 0, + '೧': 1, + '೨': 2, + '೩': 3, + '೪': 4, + '೫': 5, + '೬': 6, + '೭': 7, + '೮': 8, + '೯': 9, + }, + }; + expect(actual).toEqual(expected); + }); + it('should build "laoo"', () => { + const actual = buildDigits('laoo'); + const expected = { + group: '[໐-໙]', + lookup: { + '໐': 0, + '໑': 1, + '໒': 2, + '໓': 3, + '໔': 4, + '໕': 5, + '໖': 6, + '໗': 7, + '໘': 8, + '໙': 9, + }, + }; + expect(actual).toEqual(expected); + }); + it('should build "latn"', () => { + const actual = buildDigits('latn'); + const expected = { + group: '\\d', + lookup: { + 0: 0, + 1: 1, + 2: 2, + 3: 3, + 4: 4, + 5: 5, + 6: 6, + 7: 7, + 8: 8, + 9: 9, + '0': 0, + '1': 1, + '2': 2, + '3': 3, + '4': 4, + '5': 5, + '6': 6, + '7': 7, + '8': 8, + '9': 9, + 〇: 0, + 一: 1, + 二: 2, + 三: 3, + 四: 4, + 五: 5, + 六: 6, + 七: 7, + 八: 8, + 九: 9, + }, + }; + expect(actual).toEqual(expected); + }); + it('should build "limb"', () => { + const actual = buildDigits('limb'); + const expected = { + group: '[᥆-᥏]', + lookup: { + '᥆': 0, + '᥇': 1, + '᥈': 2, + '᥉': 3, + '᥊': 4, + '᥋': 5, + '᥌': 6, + '᥍': 7, + '᥎': 8, + '᥏': 9, + }, + }; + expect(actual).toEqual(expected); + }); + it('should build "mlym"', () => { + const actual = buildDigits('mlym'); + const expected = { + group: '[൦-൯]', + lookup: { + '൦': 0, + '൧': 1, + '൨': 2, + '൩': 3, + '൪': 4, + '൫': 5, + '൬': 6, + '൭': 7, + '൮': 8, + '൯': 9, + }, + }; + expect(actual).toEqual(expected); + }); + it('should build "mong"', () => { + const actual = buildDigits('mong'); + const expected = { + group: '[᠐-᠙]', + lookup: { + '᠐': 0, + '᠑': 1, + '᠒': 2, + '᠓': 3, + '᠔': 4, + '᠕': 5, + '᠖': 6, + '᠗': 7, + '᠘': 8, + '᠙': 9, + }, + }; + expect(actual).toEqual(expected); + }); + it('should build "mymr"', () => { + const actual = buildDigits('mymr'); + const expected = { + group: '[၀-၉]', + lookup: { + '၀': 0, + '၁': 1, + '၂': 2, + '၃': 3, + '၄': 4, + '၅': 5, + '၆': 6, + '၇': 7, + '၈': 8, + '၉': 9, + }, + }; + expect(actual).toEqual(expected); + }); + it('should build "orya"', () => { + const actual = buildDigits('orya'); + const expected = { + group: '[୦-୯]', + lookup: { + '୦': 0, + '୧': 1, + '୨': 2, + '୩': 3, + '୪': 4, + '୫': 5, + '୬': 6, + '୭': 7, + '୮': 8, + '୯': 9, + }, + }; + expect(actual).toEqual(expected); + }); + it('should build "tamldec"', () => { + const actual = buildDigits('tamldec'); + const expected = { + group: '[௦-௯]', + lookup: { + '௦': 0, + '௧': 1, + '௨': 2, + '௩': 3, + '௪': 4, + '௫': 5, + '௬': 6, + '௭': 7, + '௮': 8, + '௯': 9, + }, + }; + expect(actual).toEqual(expected); + }); + it('should build "telu"', () => { + const actual = buildDigits('telu'); + const expected = { + group: '[౦-౯]', + lookup: { + '౦': 0, + '౧': 1, + '౨': 2, + '౩': 3, + '౪': 4, + '౫': 5, + '౬': 6, + '౭': 7, + '౮': 8, + '౯': 9, + }, + }; + expect(actual).toEqual(expected); + }); + it('should build "thai"', () => { + const actual = buildDigits('thai'); + const expected = { + group: '[๐-๙]', + lookup: { + '๐': 0, + '๑': 1, + '๒': 2, + '๓': 3, + '๔': 4, + '๕': 5, + '๖': 6, + '๗': 7, + '๘': 8, + '๙': 9, + }, + }; + expect(actual).toEqual(expected); + }); + it('should build "tibt"', () => { + const actual = buildDigits('tibt'); + const expected = { + group: '[༠-༩]', + lookup: { + '༠': 0, + '༡': 1, + '༢': 2, + '༣': 3, + '༤': 4, + '༥': 5, + '༦': 6, + '༧': 7, + '༨': 8, + '༩': 9, + }, + }; + expect(actual).toEqual(expected); + }); }); diff --git a/src/buildDigits/buildDigits.ts b/src/buildDigits/buildDigits.ts index d119955..2fd65bb 100644 --- a/src/buildDigits/buildDigits.ts +++ b/src/buildDigits/buildDigits.ts @@ -1,36 +1,34 @@ -const { - chineseGroup, - defaultLookup, - startCodes, -} = require('../data/numberingSystems.js'); +import { + chineseGroup, + defaultLookup, + startCodes, +} from '../data/numberingSystems'; const cache = {}; -function buildDigits(nsName) { - if (cache[nsName]) { - return cache[nsName]; - } - if (nsName === 'fullwide' || nsName === 'hanidec') { - return { group: chineseGroup, lookup: { ...defaultLookup } }; - } - const startCode = startCodes[nsName]; - /* istanbul ignore next */ - if (!startCode) { - // unknown numbering system; treat like latn - return { group: '\\d', lookup: { ...defaultLookup } }; - } - const start = String.fromCharCode(startCode); - const end = String.fromCharCode(startCode + 9); - const lookup = {}; - for (let i = 0; i < 10; i++) { - lookup[String.fromCharCode(startCode + i)] = i; - } - // console.log({ nsName, start, end, lookup }); - cache[nsName] = { - group: `[${start}-${end}]`, - lookup, - }; - return cache[nsName]; +export default function buildDigits(nsName) { + if (cache[nsName]) { + return cache[nsName]; + } + if (nsName === 'fullwide' || nsName === 'hanidec') { + return { group: chineseGroup, lookup: { ...defaultLookup } }; + } + const startCode = startCodes[nsName]; + /* istanbul ignore next */ + if (!startCode) { + // unknown numbering system; treat like latn + return { group: '\\d', lookup: { ...defaultLookup } }; + } + const start = String.fromCharCode(startCode); + const end = String.fromCharCode(startCode + 9); + const lookup = {}; + for (let i = 0; i < 10; i++) { + lookup[String.fromCharCode(startCode + i)] = i; + } + // console.log({ nsName, start, end, lookup }); + cache[nsName] = { + group: `[${start}-${end}]`, + lookup, + }; + return cache[nsName]; } - -module.exports = buildDigits; diff --git a/src/data/baseLookups.ts b/src/data/baseLookups.ts index fe11140..efbe174 100644 --- a/src/data/baseLookups.ts +++ b/src/data/baseLookups.ts @@ -1,52 +1,52 @@ -const twoDigitYears = require('./twoDigitYears.js'); -const timezoneNames = require('./timezoneNames.js'); +import timezoneNames from './timezoneNames'; +import twoDigitYears from './twoDigitYears'; const baseLookups = { - zone: timezoneNames, - year: twoDigitYears, - meridiem: { am: 0, pm: 12, 'a.m.': 0, 'p.m.': 12 }, - month: { - january: 1, - jan: 1, - february: 2, - feb: 2, - march: 3, - mar: 3, - april: 4, - apr: 4, - may: 5, - june: 6, - jun: 6, - july: 7, - jul: 7, - august: 8, - aug: 8, - september: 9, - sep: 9, - october: 10, - oct: 10, - november: 11, - nov: 11, - december: 12, - dec: 12, - }, - dayname: { - sunday: 0, - sun: 0, - monday: 1, - mon: 1, - tuesday: 2, - tue: 2, - wednesday: 3, - wed: 3, - thursday: 4, - thu: 4, - friday: 5, - fri: 5, - saturday: 6, - sat: 6, - }, - digit: {}, + zone: timezoneNames, + year: twoDigitYears, + meridiem: { am: 0, pm: 12, 'a.m.': 0, 'p.m.': 12 }, + month: { + january: 1, + jan: 1, + february: 2, + feb: 2, + march: 3, + mar: 3, + april: 4, + apr: 4, + may: 5, + june: 6, + jun: 6, + july: 7, + jul: 7, + august: 8, + aug: 8, + september: 9, + sep: 9, + october: 10, + oct: 10, + november: 11, + nov: 11, + december: 12, + dec: 12, + }, + dayname: { + sunday: 0, + sun: 0, + monday: 1, + mon: 1, + tuesday: 2, + tue: 2, + wednesday: 3, + wed: 3, + thursday: 4, + thu: 4, + friday: 5, + fri: 5, + saturday: 6, + sat: 6, + }, + digit: {}, }; -module.exports = baseLookups; +export default baseLookups; diff --git a/src/data/defaultLocale.ts b/src/data/defaultLocale.ts index c9c76a0..5f13e7c 100644 --- a/src/data/defaultLocale.ts +++ b/src/data/defaultLocale.ts @@ -1,21 +1,23 @@ -const normalizeLocale = require('../normalizeLocale/normalizeLocale.js'); +import normalizeLocale from '../normalizeLocale/normalizeLocale'; let defaultLocale; /* istanbul ignore next */ if (typeof navigator !== 'undefined') { - // browser: locale is on navigator object - const nav = navigator; - defaultLocale = Array.isArray(nav.languages) - ? nav.languages[0] - : nav.language; + // browser: locale is on navigator object + const nav = navigator; + defaultLocale = Array.isArray(nav.languages) + ? nav.languages[0] + : nav.language; } else if (typeof process !== 'undefined') { - // node: locale is an env var - const env = process.env; - defaultLocale = env.LC_ALL || env.LC_MESSAGES || env.LANG || env.LANGUAGE; + // node: locale is an env var + const env = process.env; + defaultLocale = env.LC_ALL || env.LC_MESSAGES || env.LANG || env.LANGUAGE; } /* istanbul ignore next */ if (!defaultLocale) { - defaultLocale = 'en-US'; + defaultLocale = 'en-US'; } -module.exports = normalizeLocale(defaultLocale); +defaultLocale = normalizeLocale(defaultLocale); + +export default defaultLocale; diff --git a/src/data/fillerWords.ts b/src/data/fillerWords.ts index 53be83e..512a718 100644 --- a/src/data/fillerWords.ts +++ b/src/data/fillerWords.ts @@ -4,43 +4,43 @@ // ]; const fillerWords = { - zh: [ - // in Chinese, "PM" comes before the digits - [/下午([\d:]+)/, '$1pm'], - // Chinese "time" - [/\[.+?時間]/, ''], - ], - he: [[/ב/g, '']], - // "of" in various languages - de: [[/ um /, '']], - pt: [[/de /g, '']], - es: [[/de /g, '']], - da: [[/den /g, '']], - // Russian symbol after years - ru: [[/ г\./g, '']], - th: [ - // Thai "at/on" - [/ที่/g, ''], - // Thai Buddhist Calendar is 543 years ahead - [ - /พ.ศ.?(\d{4})/, - ($0, year) => { - let intYear = parseInt(year); - intYear -= 543; - return String(intYear); - }, - ], - [ - /\d{4}/, - year => { - let intYear = parseInt(year); - if (intYear >= 2443) { - intYear -= 543; - } - return String(intYear); - }, - ], - ], + zh: [ + // in Chinese, "PM" comes before the digits + [/下午([\d:]+)/, '$1pm'], + // Chinese "time" + [/\[.+?時間]/, ''], + ], + he: [[/ב/g, '']], + // "of" in various languages + de: [[/ um /, '']], + pt: [[/de /g, '']], + es: [[/de /g, '']], + da: [[/den /g, '']], + // Russian symbol after years + ru: [[/ г\./g, '']], + th: [ + // Thai "at/on" + [/ที่/g, ''], + // Thai Buddhist Calendar is 543 years ahead + [ + /พ.ศ.?(\d{4})/, + ($0, year) => { + let intYear = parseInt(year); + intYear -= 543; + return String(intYear); + }, + ], + [ + /\d{4}/, + year => { + let intYear = parseInt(year); + if (intYear >= 2443) { + intYear -= 543; + } + return String(intYear); + }, + ], + ], }; -module.exports = fillerWords; +export default fillerWords; diff --git a/src/data/numberingSystems.ts b/src/data/numberingSystems.ts index bebd0c2..c50d1af 100644 --- a/src/data/numberingSystems.ts +++ b/src/data/numberingSystems.ts @@ -1,59 +1,57 @@ -const startCodes = { - arab: 1632, - arabext: 1776, - bali: 6992, - beng: 2534, - deva: 2406, - fullwide: 65296, - gujr: 2790, - khmr: 6112, - knda: 3302, - laoo: 3792, - limb: 6470, - mlym: 3430, - mong: 6160, - mymr: 4160, - orya: 2918, - tamldec: 3046, - telu: 3174, - thai: 3664, - tibt: 3872, +export const startCodes = { + arab: 1632, + arabext: 1776, + bali: 6992, + beng: 2534, + deva: 2406, + fullwide: 65296, + gujr: 2790, + khmr: 6112, + knda: 3302, + laoo: 3792, + limb: 6470, + mlym: 3430, + mong: 6160, + mymr: 4160, + orya: 2918, + tamldec: 3046, + telu: 3174, + thai: 3664, + tibt: 3872, }; // full-width numbers, hanidec numbers, latin numbers (\d) -const chineseGroup = '[1234567890一二三四五六七八九〇\\d]'; +export const chineseGroup = '[1234567890一二三四五六七八九〇\\d]'; -const defaultLookup = { - 0: 0, - 1: 1, - 2: 2, - 3: 3, - 4: 4, - 5: 5, - 6: 6, - 7: 7, - 8: 8, - 9: 9, - '0': 0, - '1': 1, - '2': 2, - '3': 3, - '4': 4, - '5': 5, - '6': 6, - '7': 7, - '8': 8, - '9': 9, - 〇: 0, - 一: 1, - 二: 2, - 三: 3, - 四: 4, - 五: 5, - 六: 6, - 七: 7, - 八: 8, - 九: 9, +export const defaultLookup = { + 0: 0, + 1: 1, + 2: 2, + 3: 3, + 4: 4, + 5: 5, + 6: 6, + 7: 7, + 8: 8, + 9: 9, + '0': 0, + '1': 1, + '2': 2, + '3': 3, + '4': 4, + '5': 5, + '6': 6, + '7': 7, + '8': 8, + '9': 9, + 〇: 0, + 一: 1, + 二: 2, + 三: 3, + 四: 4, + 五: 5, + 六: 6, + 七: 7, + 八: 8, + 九: 9, }; - -module.exports = { chineseGroup, defaultLookup, startCodes }; diff --git a/src/data/templates.ts b/src/data/templates.ts index 36e4b22..a1302f1 100644 --- a/src/data/templates.ts +++ b/src/data/templates.ts @@ -1,43 +1,41 @@ -const timezoneNames = require('./timezoneNames.js'); +import timezoneNames from './timezoneNames'; -const latn = { - MONTHNAME: - 'january|february|march|april|may|june|july|august|september|october|november|december|jan\\.?|feb\\.?|mar\\.?|apr\\.?|may\\.?|jun\\.?|jul\\.?|aug\\.?|sep\\.?|oct\\.?|nov\\.?|dec\\.?', - DAYNAME: - 'sunday|monday|tuesday|wednesday|thursday|friday|saturday|sun\\.?|mon\\.?|tue\\.?|wed\\.?|thu\\.?|fri\\.?|sat\\.?', - ZONE: '\\(?(' + Object.keys(timezoneNames).join('|') + ')\\)?', - MERIDIEM: '[ap]\\.?m?\\.?', - ORDINAL: 'st|nd|rd|th|\\.', - YEAR: '\\d{4}|\\d{2}', - YEAR4: '\\d{4}', - MONTH: '1[0-2]|0?[1-9]', - MONTH2: '1[0-2]|0[1-9]', - DAY: '3[01]|[12]\\d|0?[1-9]', - DAY2: '3[01]|[12]\\d|0[1-9]', - OFFSET: '[+-][01]?\\d?\\:?(?:[0-5]\\d)?', - H24: '[01]\\d|2[0-3]', - H12: '0?[1-9]|1[012]', - MIN: '[0-5]\\d', - // Leap seconds cause 60 seconds in a minute - SEC: '[0-5]\\d|60', - MS: '\\d{9}|\\d{6}|\\d{1,3}', - GAP: '[\\s/.,-]{1,}', +export const latn = { + MONTHNAME: + 'january|february|march|april|may|june|july|august|september|october|november|december|jan\\.?|feb\\.?|mar\\.?|apr\\.?|may\\.?|jun\\.?|jul\\.?|aug\\.?|sep\\.?|oct\\.?|nov\\.?|dec\\.?', + DAYNAME: + 'sunday|monday|tuesday|wednesday|thursday|friday|saturday|sun\\.?|mon\\.?|tue\\.?|wed\\.?|thu\\.?|fri\\.?|sat\\.?', + ZONE: '\\(?(' + Object.keys(timezoneNames).join('|') + ')\\)?', + MERIDIEM: '[ap]\\.?m?\\.?', + ORDINAL: 'st|nd|rd|th|\\.', + YEAR: '\\d{4}|\\d{2}', + YEAR4: '\\d{4}', + MONTH: '1[0-2]|0?[1-9]', + MONTH2: '1[0-2]|0[1-9]', + DAY: '3[01]|[12]\\d|0?[1-9]', + DAY2: '3[01]|[12]\\d|0[1-9]', + OFFSET: '[+-][01]?\\d?\\:?(?:[0-5]\\d)?', + H24: '[01]\\d|2[0-3]', + H12: '0?[1-9]|1[012]', + MIN: '[0-5]\\d', + // Leap seconds cause 60 seconds in a minute + SEC: '[0-5]\\d|60', + MS: '\\d{9}|\\d{6}|\\d{1,3}', + GAP: '[\\s/.,-]{1,}', }; -const other = { - ...latn, - YEAR: '*{4}|*{2}', - YEAR4: '*{4}|*{2}', - MONTH: '*{1,2}', - MONTH2: '*{2}', - DAY: '*{1,2}', - DAY2: '*{2}', - OFFSET: '[+-]*{1,2}\\:?*{0,2}', - H24: '*{2}', - H12: '*{1,2}', - MIN: '*{2}', - SEC: '*{2}', - MS: '*{9}|*{6}|*{3}', +export const other = { + ...latn, + YEAR: '*{4}|*{2}', + YEAR4: '*{4}|*{2}', + MONTH: '*{1,2}', + MONTH2: '*{2}', + DAY: '*{1,2}', + DAY2: '*{2}', + OFFSET: '[+-]*{1,2}\\:?*{0,2}', + H24: '*{2}', + H12: '*{1,2}', + MIN: '*{2}', + SEC: '*{2}', + MS: '*{9}|*{6}|*{3}', }; - -module.exports = { latn, other }; diff --git a/src/data/timezoneNames.ts b/src/data/timezoneNames.ts index 588ed3a..70f730f 100644 --- a/src/data/timezoneNames.ts +++ b/src/data/timezoneNames.ts @@ -1,193 +1,193 @@ // some hand-picked common timezone names const timezoneNames = { - 'Eastern Daylight Time': -240, - 'Eastern Standard Time': -300, - 'Central Daylight Time': -300, - 'Central Standard Time': -360, - 'Mountain Daylight Time': -360, - 'Mountain Standard Time': -420, - 'Pacific Daylight Time': -420, - 'Pacific Standard Time': -480, - ACDT: 630, // Australian Central Daylight Savings Time - ACST: 570, // Australian Central Standard Time - ACT: 480, // ASEAN Common Time - ADT: -180, // Atlantic Daylight Time - AEDT: 660, // Australian Eastern Daylight Savings Time - AEST: 600, // Australian Eastern Standard Time - AFT: 270, // Afghanistan Time - AKDT: -480, // Alaska Daylight Time - AKST: -540, // Alaska Standard Time - AMST: -180, // Amazon Summer Time (Brazil) - AMT: -240, // Amazon Time (Brazil) - ART: -180, // Argentina Time - AST: 180, // Arabia Standard Time - AWDT: 540, // Australian Western Daylight Time - AWST: 480, // Australian Western Standard Time - AZOST: -60, // Azores Standard Time - AZT: 240, // Azerbaijan Time - BDT: 360, // Bangladesh Daylight Time (Bangladesh Daylight saving time keeps UTC+06 offset) - BIOT: 360, // British Indian Ocean Time - BIT: -720, // Baker Island Time - BOT: -240, // Bolivia Time - BRST: -120, // Brasilia Summer Time - BRT: -180, // Brasilia Time - BTT: 360, // Bhutan Time - CAT: 120, // Central Africa Time - CCT: 390, // Cocos Islands Time - CDT: -300, // Central Daylight Time (North America) - CEDT: 120, // Central European Daylight Time - CEST: 120, // Central European Summer Time (Cf. HAEC) - CET: 60, // Central European Time - CHADT: 825, // Chatham Daylight Time - CHAST: 765, // Chatham Standard Time - CHOT: 480, // Choibalsan - ChST: 600, // Chamorro Standard Time - CHUT: 600, // Chuuk Time - CIST: -480, // Clipperton Island Standard Time - CIT: 480, // Central Indonesia Time - CKT: -600, // Cook Island Time - CLST: -180, // Chile Summer Time - CLT: -240, // Chile Standard Time - COST: -240, // Colombia Summer Time - COT: -300, // Colombia Time - CST: -360, // Central Standard Time (North America) - CT: 480, // China time - CVT: -60, // Cape Verde Time - CXT: 420, // Christmas Island Time - DAVT: 420, // Davis Time - DDUT: 600, // Dumont d'Urville Time - DFT: 60, // AIX specific equivalent of Central European Time - EASST: -300, // Easter Island Standard Summer Time - EAST: -360, // Easter Island Standard Time - EAT: 180, // East Africa Time - ECT: -300, // Ecuador Time - EDT: -240, // Eastern Daylight Time (North America) - EEDT: 180, // Eastern European Daylight Time - EEST: 180, // Eastern European Summer Time - EET: 120, // Eastern European Time - EGST: 0, // Eastern Greenland Summer Time - EGT: -60, // Eastern Greenland Time - EIT: 540, // Eastern Indonesian Time - EST: -300, // Eastern Standard Time (North America) - FET: 180, // Further-eastern European Time - FJT: 720, // Fiji Time - FKST: -180, // Falkland Islands Standard Time - FKT: -240, // Falkland Islands Time - FNT: -120, // Fernando de Noronha Time - GALT: -360, // Galapagos Time - GAMT: -540, // Gambier Islands - GET: 240, // Georgia Standard Time - GFT: -180, // French Guiana Time - GILT: 720, // Gilbert Island Time - GIT: -540, // Gambier Island Time - GMT: 0, // Greenwich Mean Time - GST: -120, // South Georgia and the South Sandwich Islands - GYT: -240, // Guyana Time - HADT: -540, // Hawaii-Aleutian Daylight Time - HAEC: 120, // Heure Avancée d'Europe Centrale francised name for CEST - HAST: -600, // Hawaii-Aleutian Standard Time - HKT: 480, // Hong Kong Time - HMT: 300, // Heard and McDonald Islands Time - HOVT: 420, // Khovd Time - HST: -600, // Hawaii Standard Time - IBST: 0, // International Business Standard Time - ICT: 420, // Indochina Time - IDT: 180, // Israel Daylight Time - IOT: 180, // Indian Ocean Time - IRDT: 270, // Iran Daylight Time - IRKT: 480, // Irkutsk Time - IRST: 210, // Iran Standard Time - IST: 120, // Israel Standard Time - JST: 540, // Japan Standard Time - KGT: 360, // Kyrgyzstan time - KOST: 660, // Kosrae Time - KRAT: 420, // Krasnoyarsk Time - KST: 540, // Korea Standard Time - LHST: 630, // Lord Howe Standard Time - LINT: 840, // Line Islands Time - MAGT: 720, // Magadan Time - MART: -510, // Marquesas Islands Time - MAWT: 300, // Mawson Station Time - MDT: -360, // Mountain Daylight Time (North America) - MET: 60, // Middle European Time Same zone as CET - MEST: 120, // Middle European Summer Time Same zone as CEST - MHT: 720, // Marshall Islands - MIST: 660, // Macquarie Island Station Time - MIT: -510, // Marquesas Islands Time - MMT: 390, // Myanmar Time - MSK: 180, // Moscow Time - MST: -420, // Mountain Standard Time (North America) - MUT: 240, // Mauritius Time - MVT: 300, // Maldives Time - MYT: 480, // Malaysia Time - NCT: 660, // New Caledonia Time - NDT: -90, // Newfoundland Daylight Time - NFT: 660, // Norfolk Time - NPT: 345, // Nepal Time - NST: -150, // Newfoundland Standard Time - NT: -150, // Newfoundland Time - NUT: -660, // Niue Time - NZDT: 780, // New Zealand Daylight Time - NZST: 720, // New Zealand Standard Time - OMST: 360, // Omsk Time - ORAT: 300, // Oral Time - PDT: -420, // Pacific Daylight Time (North America) - PET: -300, // Peru Time - PETT: 720, // Kamchatka Time - PGT: 600, // Papua New Guinea Time - PHOT: 780, // Phoenix Island Time - PKT: 300, // Pakistan Standard Time - PMDT: -120, // Saint Pierre and Miquelon Daylight time - PMST: -180, // Saint Pierre and Miquelon Standard Time - PONT: 660, // Pohnpei Standard Time - PST: -480, // Pacific Standard Time (North America) - PYST: -180, // Paraguay Summer Time (South America) - PYT: -240, // Paraguay Time (South America) - RET: 240, // Réunion Time - ROTT: -180, // Rothera Research Station Time - SAKT: 660, // Sakhalin Island time - SAMT: 240, // Samara Time - SAST: 120, // South African Standard Time - SBT: 660, // Solomon Islands Time - SCT: 240, // Seychelles Time - SGT: 480, // Singapore Time - SLST: 330, // Sri Lanka Standard Time - SRET: 660, // Srednekolymsk Time - SRT: -180, // Suriname Time - SST: 480, // Singapore Standard Time - SYOT: 180, // Showa Station Time - TAHT: -600, // Tahiti Time - THA: 420, // Thailand Standard Time - TFT: 300, // Indian/Kerguelen - TJT: 300, // Tajikistan Time - TKT: 780, // Tokelau Time - TLT: 540, // Timor Leste Time - TMT: 300, // Turkmenistan Time - TOT: 780, // Tonga Time - TVT: 720, // Tuvalu Time - UCT: 0, // Coordinated Universal Time - ULAT: 480, // Ulaanbaatar Time - USZ1: 120, // Kaliningrad Time - UTC: 0, // Coordinated Universal Time - UYST: -120, // Uruguay Summer Time - UYT: -180, // Uruguay Standard Time - UZT: 300, // Uzbekistan Time - VET: -240, // Venezuelan Standard Time - VLAT: 600, // Vladivostok Time - VOLT: 240, // Volgograd Time - VOST: 360, // Vostok Station Time - VUT: 660, // Vanuatu Time - WAKT: 720, // Wake Island Time - WAST: 120, // West Africa Summer Time - WAT: 60, // West Africa Time - WEDT: 60, // Western European Daylight Time - WEST: 60, // Western European Summer Time - WET: 0, // Western European Time - WIT: 420, // Western Indonesian Time - WST: 480, // Western Standard Time - YAKT: 540, // Yakutsk Time - YEKT: 300, // Yekaterinburg Time - Z: 0, // Zulu Time (Coordinated Universal Time) + 'Eastern Daylight Time': -240, + 'Eastern Standard Time': -300, + 'Central Daylight Time': -300, + 'Central Standard Time': -360, + 'Mountain Daylight Time': -360, + 'Mountain Standard Time': -420, + 'Pacific Daylight Time': -420, + 'Pacific Standard Time': -480, + ACDT: 630, // Australian Central Daylight Savings Time + ACST: 570, // Australian Central Standard Time + ACT: 480, // ASEAN Common Time + ADT: -180, // Atlantic Daylight Time + AEDT: 660, // Australian Eastern Daylight Savings Time + AEST: 600, // Australian Eastern Standard Time + AFT: 270, // Afghanistan Time + AKDT: -480, // Alaska Daylight Time + AKST: -540, // Alaska Standard Time + AMST: -180, // Amazon Summer Time (Brazil) + AMT: -240, // Amazon Time (Brazil) + ART: -180, // Argentina Time + AST: 180, // Arabia Standard Time + AWDT: 540, // Australian Western Daylight Time + AWST: 480, // Australian Western Standard Time + AZOST: -60, // Azores Standard Time + AZT: 240, // Azerbaijan Time + BDT: 360, // Bangladesh Daylight Time (Bangladesh Daylight saving time keeps UTC+06 offset) + BIOT: 360, // British Indian Ocean Time + BIT: -720, // Baker Island Time + BOT: -240, // Bolivia Time + BRST: -120, // Brasilia Summer Time + BRT: -180, // Brasilia Time + BTT: 360, // Bhutan Time + CAT: 120, // Central Africa Time + CCT: 390, // Cocos Islands Time + CDT: -300, // Central Daylight Time (North America) + CEDT: 120, // Central European Daylight Time + CEST: 120, // Central European Summer Time (Cf. HAEC) + CET: 60, // Central European Time + CHADT: 825, // Chatham Daylight Time + CHAST: 765, // Chatham Standard Time + CHOT: 480, // Choibalsan + ChST: 600, // Chamorro Standard Time + CHUT: 600, // Chuuk Time + CIST: -480, // Clipperton Island Standard Time + CIT: 480, // Central Indonesia Time + CKT: -600, // Cook Island Time + CLST: -180, // Chile Summer Time + CLT: -240, // Chile Standard Time + COST: -240, // Colombia Summer Time + COT: -300, // Colombia Time + CST: -360, // Central Standard Time (North America) + CT: 480, // China time + CVT: -60, // Cape Verde Time + CXT: 420, // Christmas Island Time + DAVT: 420, // Davis Time + DDUT: 600, // Dumont d'Urville Time + DFT: 60, // AIX specific equivalent of Central European Time + EASST: -300, // Easter Island Standard Summer Time + EAST: -360, // Easter Island Standard Time + EAT: 180, // East Africa Time + ECT: -300, // Ecuador Time + EDT: -240, // Eastern Daylight Time (North America) + EEDT: 180, // Eastern European Daylight Time + EEST: 180, // Eastern European Summer Time + EET: 120, // Eastern European Time + EGST: 0, // Eastern Greenland Summer Time + EGT: -60, // Eastern Greenland Time + EIT: 540, // Eastern Indonesian Time + EST: -300, // Eastern Standard Time (North America) + FET: 180, // Further-eastern European Time + FJT: 720, // Fiji Time + FKST: -180, // Falkland Islands Standard Time + FKT: -240, // Falkland Islands Time + FNT: -120, // Fernando de Noronha Time + GALT: -360, // Galapagos Time + GAMT: -540, // Gambier Islands + GET: 240, // Georgia Standard Time + GFT: -180, // French Guiana Time + GILT: 720, // Gilbert Island Time + GIT: -540, // Gambier Island Time + GMT: 0, // Greenwich Mean Time + GST: -120, // South Georgia and the South Sandwich Islands + GYT: -240, // Guyana Time + HADT: -540, // Hawaii-Aleutian Daylight Time + HAEC: 120, // Heure Avancée d'Europe Centrale francised name for CEST + HAST: -600, // Hawaii-Aleutian Standard Time + HKT: 480, // Hong Kong Time + HMT: 300, // Heard and McDonald Islands Time + HOVT: 420, // Khovd Time + HST: -600, // Hawaii Standard Time + IBST: 0, // International Business Standard Time + ICT: 420, // Indochina Time + IDT: 180, // Israel Daylight Time + IOT: 180, // Indian Ocean Time + IRDT: 270, // Iran Daylight Time + IRKT: 480, // Irkutsk Time + IRST: 210, // Iran Standard Time + IST: 120, // Israel Standard Time + JST: 540, // Japan Standard Time + KGT: 360, // Kyrgyzstan time + KOST: 660, // Kosrae Time + KRAT: 420, // Krasnoyarsk Time + KST: 540, // Korea Standard Time + LHST: 630, // Lord Howe Standard Time + LINT: 840, // Line Islands Time + MAGT: 720, // Magadan Time + MART: -510, // Marquesas Islands Time + MAWT: 300, // Mawson Station Time + MDT: -360, // Mountain Daylight Time (North America) + MET: 60, // Middle European Time Same zone as CET + MEST: 120, // Middle European Summer Time Same zone as CEST + MHT: 720, // Marshall Islands + MIST: 660, // Macquarie Island Station Time + MIT: -510, // Marquesas Islands Time + MMT: 390, // Myanmar Time + MSK: 180, // Moscow Time + MST: -420, // Mountain Standard Time (North America) + MUT: 240, // Mauritius Time + MVT: 300, // Maldives Time + MYT: 480, // Malaysia Time + NCT: 660, // New Caledonia Time + NDT: -90, // Newfoundland Daylight Time + NFT: 660, // Norfolk Time + NPT: 345, // Nepal Time + NST: -150, // Newfoundland Standard Time + NT: -150, // Newfoundland Time + NUT: -660, // Niue Time + NZDT: 780, // New Zealand Daylight Time + NZST: 720, // New Zealand Standard Time + OMST: 360, // Omsk Time + ORAT: 300, // Oral Time + PDT: -420, // Pacific Daylight Time (North America) + PET: -300, // Peru Time + PETT: 720, // Kamchatka Time + PGT: 600, // Papua New Guinea Time + PHOT: 780, // Phoenix Island Time + PKT: 300, // Pakistan Standard Time + PMDT: -120, // Saint Pierre and Miquelon Daylight time + PMST: -180, // Saint Pierre and Miquelon Standard Time + PONT: 660, // Pohnpei Standard Time + PST: -480, // Pacific Standard Time (North America) + PYST: -180, // Paraguay Summer Time (South America) + PYT: -240, // Paraguay Time (South America) + RET: 240, // Réunion Time + ROTT: -180, // Rothera Research Station Time + SAKT: 660, // Sakhalin Island time + SAMT: 240, // Samara Time + SAST: 120, // South African Standard Time + SBT: 660, // Solomon Islands Time + SCT: 240, // Seychelles Time + SGT: 480, // Singapore Time + SLST: 330, // Sri Lanka Standard Time + SRET: 660, // Srednekolymsk Time + SRT: -180, // Suriname Time + SST: 480, // Singapore Standard Time + SYOT: 180, // Showa Station Time + TAHT: -600, // Tahiti Time + THA: 420, // Thailand Standard Time + TFT: 300, // Indian/Kerguelen + TJT: 300, // Tajikistan Time + TKT: 780, // Tokelau Time + TLT: 540, // Timor Leste Time + TMT: 300, // Turkmenistan Time + TOT: 780, // Tonga Time + TVT: 720, // Tuvalu Time + UCT: 0, // Coordinated Universal Time + ULAT: 480, // Ulaanbaatar Time + USZ1: 120, // Kaliningrad Time + UTC: 0, // Coordinated Universal Time + UYST: -120, // Uruguay Summer Time + UYT: -180, // Uruguay Standard Time + UZT: 300, // Uzbekistan Time + VET: -240, // Venezuelan Standard Time + VLAT: 600, // Vladivostok Time + VOLT: 240, // Volgograd Time + VOST: 360, // Vostok Station Time + VUT: 660, // Vanuatu Time + WAKT: 720, // Wake Island Time + WAST: 120, // West Africa Summer Time + WAT: 60, // West Africa Time + WEDT: 60, // Western European Daylight Time + WEST: 60, // Western European Summer Time + WET: 0, // Western European Time + WIT: 420, // Western Indonesian Time + WST: 480, // Western Standard Time + YAKT: 540, // Yakutsk Time + YEKT: 300, // Yekaterinburg Time + Z: 0, // Zulu Time (Coordinated Universal Time) }; -module.exports = timezoneNames; +export default timezoneNames; diff --git a/src/data/twoDigitYears.ts b/src/data/twoDigitYears.ts index 0bb0750..fa6e5aa 100644 --- a/src/data/twoDigitYears.ts +++ b/src/data/twoDigitYears.ts @@ -1,8 +1,8 @@ // 2-digit years: 1951 through 2050 const twoDigitYears = {}; for (let i = 1; i < 100; i++) { - const key = (i < 9 ? '0' : '') + i; - twoDigitYears[key] = i + (i > 51 ? 1900 : 2000); + const key = (i < 9 ? '0' : '') + i; + twoDigitYears[key] = i + (i > 51 ? 1900 : 2000); } -module.exports = twoDigitYears; +export default twoDigitYears; diff --git a/src/data/unitShortcuts.ts b/src/data/unitShortcuts.ts index b164d7e..63073e5 100644 --- a/src/data/unitShortcuts.ts +++ b/src/data/unitShortcuts.ts @@ -1,12 +1,12 @@ const unitShortcuts = { - y: 'year', - M: 'month', - d: 'day', - w: 'week', - h: 'hour', - m: 'minute', - s: 'second', - ms: 'millisecond', + y: 'year', + M: 'month', + d: 'day', + w: 'week', + h: 'hour', + m: 'minute', + s: 'second', + ms: 'millisecond', }; -module.exports = unitShortcuts; +export default unitShortcuts; diff --git a/src/data/units.ts b/src/data/units.ts index 4d7969b..a44b26e 100644 --- a/src/data/units.ts +++ b/src/data/units.ts @@ -1,11 +1,11 @@ const units = [ - 'year', - 'month', - 'day', - 'hour', - 'minute', - 'second', - 'millisecond', + 'year', + 'month', + 'day', + 'hour', + 'minute', + 'second', + 'millisecond', ]; -module.exports = units; +export default units; diff --git a/src/formats/ago/ago.spec.ts b/src/formats/ago/ago.spec.ts index 61fafc1..f9a1544 100644 --- a/src/formats/ago/ago.spec.ts +++ b/src/formats/ago/ago.spec.ts @@ -1,557 +1,558 @@ -const parser = require('../../../index.js'); -const Format = require('../../Format/Format.js'); +import { beforeAll, describe, expect, it } from 'vitest'; +import parser from '../../../index'; +import Format from '../../Format/Format'; describe('(amount) (unit) ago', () => { - beforeAll(() => { - Format.prototype.now = () => - new Date(Date.UTC(2020, 2 /* march */, 1, 15, 16, 0, 200)); - }); - it('should handle "8 years ago"', () => { - const actual = parser.attempt('8 years ago'); - const expected = { - year: 2012, - month: 3, - day: 1, - hour: 15, - minute: 16, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "7 months ago"', () => { - const actual = parser.attempt('7 months ago'); - const expected = { - year: 2019, - month: 8, - day: 1, - hour: 15, - minute: 16, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "2 weeks"', () => { - const actual = parser.attempt('2 weeks ago'); - const expected = { - year: 2020, - month: 2, - day: 16, - hour: 15, - minute: 16, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "2 days ago"', () => { - const actual = parser.attempt('2 days ago'); - const expected = { - year: 2020, - month: 2, - day: 28, - hour: 15, - minute: 16, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "18 hours ago"', () => { - const actual = parser.attempt('18 hours ago'); - const expected = { - year: 2020, - month: 2, - day: 29, - hour: 21, - minute: 16, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "1 minute ago"', () => { - const actual = parser.attempt('1 minute ago'); - const expected = { - year: 2020, - month: 3, - day: 1, - hour: 15, - minute: 15, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "30 seconds ago"', () => { - const actual = parser.attempt('30 seconds ago'); - const expected = { - year: 2020, - month: 3, - day: 1, - hour: 15, - minute: 15, - second: 30, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "199 milliseconds ago"', () => { - const actual = parser.attempt('199 milliseconds ago'); - const expected = { - year: 2020, - month: 3, - day: 1, - hour: 15, - minute: 16, - second: 0, - millisecond: 1, - }; - expect(actual).toEqual(expected); - }); + beforeAll(() => { + Format.prototype.now = () => + new Date(Date.UTC(2020, 2 /* march */, 1, 15, 16, 0, 200)); + }); + it('should handle "8 years ago"', () => { + const actual = parser.attempt('8 years ago'); + const expected = { + year: 2012, + month: 3, + day: 1, + hour: 15, + minute: 16, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "7 months ago"', () => { + const actual = parser.attempt('7 months ago'); + const expected = { + year: 2019, + month: 8, + day: 1, + hour: 15, + minute: 16, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "2 weeks"', () => { + const actual = parser.attempt('2 weeks ago'); + const expected = { + year: 2020, + month: 2, + day: 16, + hour: 15, + minute: 16, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "2 days ago"', () => { + const actual = parser.attempt('2 days ago'); + const expected = { + year: 2020, + month: 2, + day: 28, + hour: 15, + minute: 16, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "18 hours ago"', () => { + const actual = parser.attempt('18 hours ago'); + const expected = { + year: 2020, + month: 2, + day: 29, + hour: 21, + minute: 16, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "1 minute ago"', () => { + const actual = parser.attempt('1 minute ago'); + const expected = { + year: 2020, + month: 3, + day: 1, + hour: 15, + minute: 15, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "30 seconds ago"', () => { + const actual = parser.attempt('30 seconds ago'); + const expected = { + year: 2020, + month: 3, + day: 1, + hour: 15, + minute: 15, + second: 30, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "199 milliseconds ago"', () => { + const actual = parser.attempt('199 milliseconds ago'); + const expected = { + year: 2020, + month: 3, + day: 1, + hour: 15, + minute: 16, + second: 0, + millisecond: 1, + }; + expect(actual).toEqual(expected); + }); }); describe('-(amount) (unit)', () => { - beforeAll(() => { - Format.prototype.now = () => - new Date(Date.UTC(2020, 2 /* march */, 1, 15, 16, 0, 200)); - }); - it('should handle "-8 years"', () => { - const actual = parser.attempt('-8 years'); - const expected = { - year: 2012, - month: 3, - day: 1, - hour: 15, - minute: 16, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "-7 months"', () => { - const actual = parser.attempt('-7 months'); - const expected = { - year: 2019, - month: 8, - day: 1, - hour: 15, - minute: 16, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "-2 days"', () => { - const actual = parser.attempt('-2 days'); - const expected = { - year: 2020, - month: 2, - day: 28, - hour: 15, - minute: 16, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "-2 weeks"', () => { - const actual = parser.attempt('-2 weeks'); - const expected = { - year: 2020, - month: 2, - day: 16, - hour: 15, - minute: 16, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "-18 hours"', () => { - const actual = parser.attempt('-18 hours'); - const expected = { - year: 2020, - month: 2, - day: 29, - hour: 21, - minute: 16, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "-1 minute"', () => { - const actual = parser.attempt('-1 minute'); - const expected = { - year: 2020, - month: 3, - day: 1, - hour: 15, - minute: 15, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "-30 seconds"', () => { - const actual = parser.attempt('-30 seconds'); - const expected = { - year: 2020, - month: 3, - day: 1, - hour: 15, - minute: 15, - second: 30, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "-199 milliseconds"', () => { - const actual = parser.attempt('-199 milliseconds'); - const expected = { - year: 2020, - month: 3, - day: 1, - hour: 15, - minute: 16, - second: 0, - millisecond: 1, - }; - expect(actual).toEqual(expected); - }); + beforeAll(() => { + Format.prototype.now = () => + new Date(Date.UTC(2020, 2 /* march */, 1, 15, 16, 0, 200)); + }); + it('should handle "-8 years"', () => { + const actual = parser.attempt('-8 years'); + const expected = { + year: 2012, + month: 3, + day: 1, + hour: 15, + minute: 16, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "-7 months"', () => { + const actual = parser.attempt('-7 months'); + const expected = { + year: 2019, + month: 8, + day: 1, + hour: 15, + minute: 16, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "-2 days"', () => { + const actual = parser.attempt('-2 days'); + const expected = { + year: 2020, + month: 2, + day: 28, + hour: 15, + minute: 16, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "-2 weeks"', () => { + const actual = parser.attempt('-2 weeks'); + const expected = { + year: 2020, + month: 2, + day: 16, + hour: 15, + minute: 16, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "-18 hours"', () => { + const actual = parser.attempt('-18 hours'); + const expected = { + year: 2020, + month: 2, + day: 29, + hour: 21, + minute: 16, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "-1 minute"', () => { + const actual = parser.attempt('-1 minute'); + const expected = { + year: 2020, + month: 3, + day: 1, + hour: 15, + minute: 15, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "-30 seconds"', () => { + const actual = parser.attempt('-30 seconds'); + const expected = { + year: 2020, + month: 3, + day: 1, + hour: 15, + minute: 15, + second: 30, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "-199 milliseconds"', () => { + const actual = parser.attempt('-199 milliseconds'); + const expected = { + year: 2020, + month: 3, + day: 1, + hour: 15, + minute: 16, + second: 0, + millisecond: 1, + }; + expect(actual).toEqual(expected); + }); }); describe('in (amount) (unit)', () => { - beforeAll(() => { - Format.prototype.now = () => - new Date(Date.UTC(2020, 2 /* march */, 1, 15, 16, 0, 200)); - }); - it('should handle "in 8 years"', () => { - const actual = parser.attempt('in 8 years'); - const expected = { - year: 2028, - month: 3, - day: 1, - hour: 15, - minute: 16, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "in 17 months"', () => { - const actual = parser.attempt('in 17 months'); - const expected = { - year: 2021, - month: 8, - day: 1, - hour: 15, - minute: 16, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "in 1 week"', () => { - const actual = parser.attempt('in 1 week'); - const expected = { - year: 2020, - month: 3, - day: 8, - hour: 15, - minute: 16, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "in 2 days"', () => { - const actual = parser.attempt('in 2 days'); - const expected = { - year: 2020, - month: 3, - day: 3, - hour: 15, - minute: 16, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "in 18 hours"', () => { - const actual = parser.attempt('in 18 hours'); - const expected = { - year: 2020, - month: 3, - day: 2, - hour: 9, - minute: 16, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "in 1 minute"', () => { - const actual = parser.attempt('in 1 minute'); - const expected = { - year: 2020, - month: 3, - day: 1, - hour: 15, - minute: 17, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "in 30 seconds"', () => { - const actual = parser.attempt('in 30 seconds'); - const expected = { - year: 2020, - month: 3, - day: 1, - hour: 15, - minute: 16, - second: 30, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "in 199 milliseconds"', () => { - const actual = parser.attempt('in 199 milliseconds'); - const expected = { - year: 2020, - month: 3, - day: 1, - hour: 15, - minute: 16, - second: 0, - millisecond: 399, - }; - expect(actual).toEqual(expected); - }); + beforeAll(() => { + Format.prototype.now = () => + new Date(Date.UTC(2020, 2 /* march */, 1, 15, 16, 0, 200)); + }); + it('should handle "in 8 years"', () => { + const actual = parser.attempt('in 8 years'); + const expected = { + year: 2028, + month: 3, + day: 1, + hour: 15, + minute: 16, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "in 17 months"', () => { + const actual = parser.attempt('in 17 months'); + const expected = { + year: 2021, + month: 8, + day: 1, + hour: 15, + minute: 16, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "in 1 week"', () => { + const actual = parser.attempt('in 1 week'); + const expected = { + year: 2020, + month: 3, + day: 8, + hour: 15, + minute: 16, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "in 2 days"', () => { + const actual = parser.attempt('in 2 days'); + const expected = { + year: 2020, + month: 3, + day: 3, + hour: 15, + minute: 16, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "in 18 hours"', () => { + const actual = parser.attempt('in 18 hours'); + const expected = { + year: 2020, + month: 3, + day: 2, + hour: 9, + minute: 16, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "in 1 minute"', () => { + const actual = parser.attempt('in 1 minute'); + const expected = { + year: 2020, + month: 3, + day: 1, + hour: 15, + minute: 17, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "in 30 seconds"', () => { + const actual = parser.attempt('in 30 seconds'); + const expected = { + year: 2020, + month: 3, + day: 1, + hour: 15, + minute: 16, + second: 30, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "in 199 milliseconds"', () => { + const actual = parser.attempt('in 199 milliseconds'); + const expected = { + year: 2020, + month: 3, + day: 1, + hour: 15, + minute: 16, + second: 0, + millisecond: 399, + }; + expect(actual).toEqual(expected); + }); }); describe('+(amount) (unit)', () => { - beforeAll(() => { - Format.prototype.now = () => - new Date(Date.UTC(2020, 2 /* march */, 1, 15, 16, 0, 200)); - }); - it('should handle "+8 years"', () => { - const actual = parser.attempt('+8 years'); - const expected = { - year: 2028, - month: 3, - day: 1, - hour: 15, - minute: 16, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "+17 months"', () => { - const actual = parser.attempt('+17 months'); - const expected = { - year: 2021, - month: 8, - day: 1, - hour: 15, - minute: 16, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "+2 weeks"', () => { - const actual = parser.attempt('+2 weeks'); - const expected = { - year: 2020, - month: 3, - day: 15, - hour: 15, - minute: 16, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "+2 days"', () => { - const actual = parser.attempt('+2 days'); - const expected = { - year: 2020, - month: 3, - day: 3, - hour: 15, - minute: 16, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "+18 hours"', () => { - const actual = parser.attempt('+18 hours'); - const expected = { - year: 2020, - month: 3, - day: 2, - hour: 9, - minute: 16, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "+1 minute"', () => { - const actual = parser.attempt('+1 minute'); - const expected = { - year: 2020, - month: 3, - day: 1, - hour: 15, - minute: 17, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "+30 seconds"', () => { - const actual = parser.attempt('+30 seconds'); - const expected = { - year: 2020, - month: 3, - day: 1, - hour: 15, - minute: 16, - second: 30, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "+199 milliseconds"', () => { - const actual = parser.attempt('+199 milliseconds'); - const expected = { - year: 2020, - month: 3, - day: 1, - hour: 15, - minute: 16, - second: 0, - millisecond: 399, - }; - expect(actual).toEqual(expected); - }); + beforeAll(() => { + Format.prototype.now = () => + new Date(Date.UTC(2020, 2 /* march */, 1, 15, 16, 0, 200)); + }); + it('should handle "+8 years"', () => { + const actual = parser.attempt('+8 years'); + const expected = { + year: 2028, + month: 3, + day: 1, + hour: 15, + minute: 16, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "+17 months"', () => { + const actual = parser.attempt('+17 months'); + const expected = { + year: 2021, + month: 8, + day: 1, + hour: 15, + minute: 16, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "+2 weeks"', () => { + const actual = parser.attempt('+2 weeks'); + const expected = { + year: 2020, + month: 3, + day: 15, + hour: 15, + minute: 16, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "+2 days"', () => { + const actual = parser.attempt('+2 days'); + const expected = { + year: 2020, + month: 3, + day: 3, + hour: 15, + minute: 16, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "+18 hours"', () => { + const actual = parser.attempt('+18 hours'); + const expected = { + year: 2020, + month: 3, + day: 2, + hour: 9, + minute: 16, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "+1 minute"', () => { + const actual = parser.attempt('+1 minute'); + const expected = { + year: 2020, + month: 3, + day: 1, + hour: 15, + minute: 17, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "+30 seconds"', () => { + const actual = parser.attempt('+30 seconds'); + const expected = { + year: 2020, + month: 3, + day: 1, + hour: 15, + minute: 16, + second: 30, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "+199 milliseconds"', () => { + const actual = parser.attempt('+199 milliseconds'); + const expected = { + year: 2020, + month: 3, + day: 1, + hour: 15, + minute: 16, + second: 0, + millisecond: 399, + }; + expect(actual).toEqual(expected); + }); }); describe('+(amount)(short unit)', () => { - beforeAll(() => { - Format.prototype.now = () => - new Date(Date.UTC(2020, 2 /* march */, 1, 15, 16, 0, 200)); - }); - it('should handle "+8y"', () => { - const actual = parser.attempt('+8y'); - const expected = { - year: 2028, - month: 3, - day: 1, - hour: 15, - minute: 16, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "+17M"', () => { - const actual = parser.attempt('+17M'); - const expected = { - year: 2021, - month: 8, - day: 1, - hour: 15, - minute: 16, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "+2w"', () => { - const actual = parser.attempt('+2w'); - const expected = { - year: 2020, - month: 3, - day: 15, - hour: 15, - minute: 16, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "+2d"', () => { - const actual = parser.attempt('+2d'); - const expected = { - year: 2020, - month: 3, - day: 3, - hour: 15, - minute: 16, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "+18h"', () => { - const actual = parser.attempt('+18h'); - const expected = { - year: 2020, - month: 3, - day: 2, - hour: 9, - minute: 16, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "+1m"', () => { - const actual = parser.attempt('+1m'); - const expected = { - year: 2020, - month: 3, - day: 1, - hour: 15, - minute: 17, - second: 0, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "+30s"', () => { - const actual = parser.attempt('+30s'); - const expected = { - year: 2020, - month: 3, - day: 1, - hour: 15, - minute: 16, - second: 30, - millisecond: 200, - }; - expect(actual).toEqual(expected); - }); - it('should handle "+199ms"', () => { - const actual = parser.attempt('+199ms'); - const expected = { - year: 2020, - month: 3, - day: 1, - hour: 15, - minute: 16, - second: 0, - millisecond: 399, - }; - expect(actual).toEqual(expected); - }); + beforeAll(() => { + Format.prototype.now = () => + new Date(Date.UTC(2020, 2 /* march */, 1, 15, 16, 0, 200)); + }); + it('should handle "+8y"', () => { + const actual = parser.attempt('+8y'); + const expected = { + year: 2028, + month: 3, + day: 1, + hour: 15, + minute: 16, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "+17M"', () => { + const actual = parser.attempt('+17M'); + const expected = { + year: 2021, + month: 8, + day: 1, + hour: 15, + minute: 16, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "+2w"', () => { + const actual = parser.attempt('+2w'); + const expected = { + year: 2020, + month: 3, + day: 15, + hour: 15, + minute: 16, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "+2d"', () => { + const actual = parser.attempt('+2d'); + const expected = { + year: 2020, + month: 3, + day: 3, + hour: 15, + minute: 16, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "+18h"', () => { + const actual = parser.attempt('+18h'); + const expected = { + year: 2020, + month: 3, + day: 2, + hour: 9, + minute: 16, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "+1m"', () => { + const actual = parser.attempt('+1m'); + const expected = { + year: 2020, + month: 3, + day: 1, + hour: 15, + minute: 17, + second: 0, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "+30s"', () => { + const actual = parser.attempt('+30s'); + const expected = { + year: 2020, + month: 3, + day: 1, + hour: 15, + minute: 16, + second: 30, + millisecond: 200, + }; + expect(actual).toEqual(expected); + }); + it('should handle "+199ms"', () => { + const actual = parser.attempt('+199ms'); + const expected = { + year: 2020, + month: 3, + day: 1, + hour: 15, + minute: 16, + second: 0, + millisecond: 399, + }; + expect(actual).toEqual(expected); + }); }); diff --git a/src/formats/ago/ago.ts b/src/formats/ago/ago.ts index b63a0dd..4222203 100644 --- a/src/formats/ago/ago.ts +++ b/src/formats/ago/ago.ts @@ -1,51 +1,51 @@ -import Format from '../../Format/Format.js'; -import unitShortcuts from '../../data/unitShortcuts.js'; +import Format from '../../Format/Format'; +import unitShortcuts from '../../data/unitShortcuts'; const ago = new Format({ - /* prettier-ignore */ - // $1 $2 $3 $4 - matcher: /^(\+|-|in|) ?([\d.]+) ?(years?|months?|weeks?|days?|hours?|minutes?|seconds?|milliseconds?|ms|s|m|h|w|d|M|y)( ago)?$/i, - handler: function ([, sign, amount, unit, isAgo]) { - amount = parseFloat(amount); - if (unit.length <= 2) { - unit = unitShortcuts[unit]; - } else { - unit = unit.replace(/s$/, ''); - unit = unit.toLowerCase(); - } - if (unit === 'week') { - unit = 'day'; - amount *= 7; - } - if (sign === '-' || isAgo) { - amount *= -1; - } - const now = this.now(); - if (unit === 'millisecond') { - now.setUTCMilliseconds(now.getUTCMilliseconds() + amount); - } else if (unit === 'second') { - now.setUTCSeconds(now.getUTCSeconds() + amount); - } else if (unit === 'minute') { - now.setUTCMinutes(now.getUTCMinutes() + amount); - } else if (unit === 'hour') { - now.setUTCHours(now.getUTCHours() + amount); - } else if (unit === 'day') { - now.setUTCDate(now.getUTCDate() + amount); - } else if (unit === 'month') { - now.setUTCMonth(now.getUTCMonth() + amount); - } else if (unit === 'year') { - now.setUTCFullYear(now.getUTCFullYear() + amount); - } - return { - year: now.getUTCFullYear(), - month: now.getUTCMonth() + 1, - day: now.getUTCDate(), - hour: now.getUTCHours(), - minute: now.getUTCMinutes(), - second: now.getUTCSeconds(), - millisecond: now.getUTCMilliseconds(), - }; - }, + /* prettier-ignore */ + // $1 $2 $3 $4 + matcher: /^(\+|-|in|) ?([\d.]+) ?(years?|months?|weeks?|days?|hours?|minutes?|seconds?|milliseconds?|ms|s|m|h|w|d|M|y)( ago)?$/i, + handler: function ([, sign, amount, unit, isAgo]) { + amount = parseFloat(amount); + if (unit.length <= 2) { + unit = unitShortcuts[unit]; + } else { + unit = unit.replace(/s$/, ''); + unit = unit.toLowerCase(); + } + if (unit === 'week') { + unit = 'day'; + amount *= 7; + } + if (sign === '-' || isAgo) { + amount *= -1; + } + const now = this.now(); + if (unit === 'millisecond') { + now.setUTCMilliseconds(now.getUTCMilliseconds() + amount); + } else if (unit === 'second') { + now.setUTCSeconds(now.getUTCSeconds() + amount); + } else if (unit === 'minute') { + now.setUTCMinutes(now.getUTCMinutes() + amount); + } else if (unit === 'hour') { + now.setUTCHours(now.getUTCHours() + amount); + } else if (unit === 'day') { + now.setUTCDate(now.getUTCDate() + amount); + } else if (unit === 'month') { + now.setUTCMonth(now.getUTCMonth() + amount); + } else if (unit === 'year') { + now.setUTCFullYear(now.getUTCFullYear() + amount); + } + return { + year: now.getUTCFullYear(), + month: now.getUTCMonth() + 1, + day: now.getUTCDate(), + hour: now.getUTCHours(), + minute: now.getUTCMinutes(), + second: now.getUTCSeconds(), + millisecond: now.getUTCMilliseconds(), + }; + }, }); -module.exports = ago; +export default ago; diff --git a/src/formats/atSeconds/atSeconds.spec.ts b/src/formats/atSeconds/atSeconds.spec.ts index fe2eb57..d50f82c 100644 --- a/src/formats/atSeconds/atSeconds.spec.ts +++ b/src/formats/atSeconds/atSeconds.spec.ts @@ -1,15 +1,15 @@ -const testParser = require('../../../test-fixtures/testParser.js'); +import testParser from '../../../test-fixtures/testParser'; testParser({ - name: 'year month day', - expected: { - year: 2020, - month: 10, - day: 2, - hour: 22, - minute: 31, - second: 29, - }, - locales: ['en-US'], - dates: ['@1601677889'], + name: 'year month day', + expected: { + year: 2020, + month: 10, + day: 2, + hour: 22, + minute: 31, + second: 29, + }, + locales: ['en-US'], + dates: ['@1601677889'], }); diff --git a/src/formats/atSeconds/atSeconds.ts b/src/formats/atSeconds/atSeconds.ts index 1111a8d..e41348c 100644 --- a/src/formats/atSeconds/atSeconds.ts +++ b/src/formats/atSeconds/atSeconds.ts @@ -1,19 +1,19 @@ -const Format = require('../../Format/Format.js'); +import Format from '../../Format/Format'; const atSeconds = new Format({ - template: '^@(\\d+)$', - handler: function (matches) { - const seconds = parseInt(matches[1], 10); - const date = new Date(seconds * 1000); - return { - year: date.getUTCFullYear(), - month: date.getUTCMonth() + 1, - day: date.getUTCDate(), - hour: date.getUTCHours(), - minute: date.getUTCMinutes(), - second: date.getUTCSeconds(), - }; - }, + template: '^@(\\d+)$', + handler: function (matches) { + const seconds = parseInt(matches[1], 10); + const date = new Date(seconds * 1000); + return { + year: date.getUTCFullYear(), + month: date.getUTCMonth() + 1, + day: date.getUTCDate(), + hour: date.getUTCHours(), + minute: date.getUTCMinutes(), + second: date.getUTCSeconds(), + }; + }, }); -module.exports = atSeconds; +export default atSeconds; diff --git a/src/formats/chinese/chinese.spec.ts b/src/formats/chinese/chinese.spec.ts index 273b750..aa30eb6 100644 --- a/src/formats/chinese/chinese.spec.ts +++ b/src/formats/chinese/chinese.spec.ts @@ -1,30 +1,31 @@ -const testDates = require('../../../test-fixtures/testDates.js'); -const parser = require('../../../index.js'); +import { describe, expect, it } from 'vitest'; +import parser from '../../../index'; +import testDates from '../../../test-fixtures/testDates'; // latn digits testDates({ - name: 'chinese', - expected: { year: 2020, month: 9, day: 26 }, - locales: ['en-US'], - formats: ['yyyy年MM月dd日', 'yyyy年M月d日', 'yyyy 年 M 月 d 日'], + name: 'chinese', + expected: { year: 2020, month: 9, day: 26 }, + locales: ['en-US'], + formats: ['yyyy年MM月dd日', 'yyyy年M月d日', 'yyyy 年 M 月 d 日'], }); // fullwide digits // 0123456789 describe('chinese with full-width digits', () => { - it('should handle date', () => { - const actual = parser.attempt('2017年08月31日'); - const expected = { year: 2017, month: 8, day: 31 }; - expect(actual).toEqual(expected); - }); + it('should handle date', () => { + const actual = parser.attempt('2017年08月31日'); + const expected = { year: 2017, month: 8, day: 31 }; + expect(actual).toEqual(expected); + }); }); // hanidec digits // 〇一二三四五六七八九 describe('chinese with full-width digits', () => { - it('should handle date', () => { - const actual = parser.attempt('二〇一七年〇八月三一日'); - const expected = { year: 2017, month: 8, day: 31 }; - expect(actual).toEqual(expected); - }); + it('should handle date', () => { + const actual = parser.attempt('二〇一七年〇八月三一日'); + const expected = { year: 2017, month: 8, day: 31 }; + expect(actual).toEqual(expected); + }); }); diff --git a/src/formats/chinese/chinese.ts b/src/formats/chinese/chinese.ts index 0ad7e32..ca683b2 100644 --- a/src/formats/chinese/chinese.ts +++ b/src/formats/chinese/chinese.ts @@ -1,22 +1,22 @@ -const Format = require('../../Format/Format.js'); -const LocaleHelper = require('../../LocaleHelper/LocaleHelper.js'); -const { chineseGroup: d } = require('../../data/numberingSystems.js'); +import Format from '../../Format/Format'; +import LocaleHelper from '../../LocaleHelper/LocaleHelper'; +import { chineseGroup as d } from '../../data/numberingSystems.js'; let locHelper; const chinese = new Format({ - /* prettier-ignore */ - // $1 $2 $3 - template: `^(${d}{4}|${d}{2})\\s*年\\s*(${d}{1,2})\\s*月\\s*(${d}{1,2})\\s*日 ?(_DAYNAME_)?$`, - handler: function ([, year, month, day]) { - if (!locHelper) { - // sometimes zh has numbering system "latn" instead of fullwide or hanidec - locHelper = new LocaleHelper('zh'); - locHelper.numberingSystem = 'hanidec'; - locHelper.buildNumbers(); - } - return locHelper.castObject({ year, month, day }); - }, + /* prettier-ignore */ + // $1 $2 $3 + template: `^(${d}{4}|${d}{2})\\s*年\\s*(${d}{1,2})\\s*月\\s*(${d}{1,2})\\s*日 ?(_DAYNAME_)?$`, + handler: function ([, year, month, day]) { + if (!locHelper) { + // sometimes zh has numbering system "latn" instead of fullwide or hanidec + locHelper = new LocaleHelper('zh'); + locHelper.numberingSystem = 'hanidec'; + locHelper.buildNumbers(); + } + return locHelper.castObject({ year, month, day }); + }, }); -module.exports = chinese; +export default chinese; diff --git a/src/formats/dayMonth/dayMonth.spec.ts b/src/formats/dayMonth/dayMonth.spec.ts index 75f6dae..425fa1f 100644 --- a/src/formats/dayMonth/dayMonth.spec.ts +++ b/src/formats/dayMonth/dayMonth.spec.ts @@ -1,8 +1,8 @@ -const testDates = require('../../../test-fixtures/testDates.js'); +import testDates from '../../../test-fixtures/testDates'; testDates({ - name: 'day month', - expected: { month: 3, day: 3 }, - locales: ['en-US'], - formats: ['dd/MM', 'dd.MM', 'dd/M', 'dd.M', 'd/M', 'd.M'], + name: 'day month', + expected: { month: 3, day: 3 }, + locales: ['en-US'], + formats: ['dd/MM', 'dd.MM', 'dd/M', 'dd.M', 'd/M', 'd.M'], }); diff --git a/src/formats/dayMonth/dayMonth.ts b/src/formats/dayMonth/dayMonth.ts index bb2997b..a9b1a91 100644 --- a/src/formats/dayMonth/dayMonth.ts +++ b/src/formats/dayMonth/dayMonth.ts @@ -1,10 +1,10 @@ -const Format = require('../../Format/Format.js'); +import Format from '../../Format/Format'; const dayMonth = new Format({ - /* prettier-ignore */ - // $1 $2 - template: "^(_DAY_)[\\/. ](_MONTH_)$", - units: ['day', 'month'], + /* prettier-ignore */ + // $1 $2 + template: "^(_DAY_)[\\/. ](_MONTH_)$", + units: ['day', 'month'], }); -module.exports = dayMonth; +export default dayMonth; diff --git a/src/formats/dayMonthYear/dayMonthYear.spec.ts b/src/formats/dayMonthYear/dayMonthYear.spec.ts index 6247643..7a5c9a5 100644 --- a/src/formats/dayMonthYear/dayMonthYear.spec.ts +++ b/src/formats/dayMonthYear/dayMonthYear.spec.ts @@ -1,17 +1,17 @@ -const testDates = require('../../../test-fixtures/testDates.js'); +import testDates from '../../../test-fixtures/testDates'; testDates({ - name: 'day month year', - expected: { year: 2020, month: 3, day: 3 }, - locales: ['en-US'], - formats: [ - 'dd/MM/yyyy', - 'dd.MM.yyyy', - 'dd/M/yyyy', - 'dd.M.yyyy', - 'd/M/yyyy', - 'd.M.yyyy', - 'dd/MM/yy', - 'dd.MM.yy', - ], + name: 'day month year', + expected: { year: 2020, month: 3, day: 3 }, + locales: ['en-US'], + formats: [ + 'dd/MM/yyyy', + 'dd.MM.yyyy', + 'dd/M/yyyy', + 'dd.M.yyyy', + 'd/M/yyyy', + 'd.M.yyyy', + 'dd/MM/yy', + 'dd.MM.yy', + ], }); diff --git a/src/formats/dayMonthYear/dayMonthYear.ts b/src/formats/dayMonthYear/dayMonthYear.ts index 5bd0a29..469edc7 100644 --- a/src/formats/dayMonthYear/dayMonthYear.ts +++ b/src/formats/dayMonthYear/dayMonthYear.ts @@ -1,10 +1,10 @@ -const Format = require('../../Format/Format.js'); +import Format from '../../Format/Format'; const dayMonthYear = new Format({ - /* prettier-ignore */ - // $1 $2 $3 $4 - template: "^(_DAY_)(_GAP_)(_MONTH_)\\2(_YEAR_)$", - units: ['day', null, 'month', 'year'], + /* prettier-ignore */ + // $1 $2 $3 $4 + template: "^(_DAY_)(_GAP_)(_MONTH_)\\2(_YEAR_)$", + units: ['day', null, 'month', 'year'], }); -module.exports = dayMonthYear; +export default dayMonthYear; diff --git a/src/formats/dayMonthname/dayMonthname.spec.ts b/src/formats/dayMonthname/dayMonthname.spec.ts index 4c0a105..8e312d3 100644 --- a/src/formats/dayMonthname/dayMonthname.spec.ts +++ b/src/formats/dayMonthname/dayMonthname.spec.ts @@ -1,10 +1,10 @@ -const testDates = require('../../../test-fixtures/testDates.js'); -const localeList = require('../../../test-fixtures/localeList.js'); +import localeList from '../../../test-fixtures/localeList'; +import testDates from '../../../test-fixtures/testDates'; testDates({ - name: 'day monthname', - expected: { month: 3, day: 16 }, - // ar and zh do not have a monthname - locales: localeList.filter(l => !/^ar|zh/.test(l)), - formats: ['dd MMMM', 'd MMMM', 'dd MMM', 'd MMM'], + name: 'day monthname', + expected: { month: 3, day: 16 }, + // ar and zh do not have a monthname + locales: localeList.filter(l => !/^ar|zh/.test(l)), + formats: ['dd MMMM', 'd MMMM', 'dd MMM', 'd MMM'], }); diff --git a/src/formats/dayMonthname/dayMonthname.ts b/src/formats/dayMonthname/dayMonthname.ts index 48bde6c..6a6b07b 100644 --- a/src/formats/dayMonthname/dayMonthname.ts +++ b/src/formats/dayMonthname/dayMonthname.ts @@ -1,10 +1,10 @@ -const Format = require('../../Format/Format.js'); +import Format from '../../Format/Format'; const dayMonthname = new Format({ - /* prettier-ignore */ - // $1 $2 - template: "^(_DAY_)(?:_ORDINAL_)?[ -](_MONTHNAME_)$", - units: ['day', 'month'], + /* prettier-ignore */ + // $1 $2 + template: "^(_DAY_)(?:_ORDINAL_)?[ -](_MONTHNAME_)$", + units: ['day', 'month'], }); -module.exports = dayMonthname; +export default dayMonthname; diff --git a/src/formats/dayMonthnameYear/dayMonthnameYear.spec.ts b/src/formats/dayMonthnameYear/dayMonthnameYear.spec.ts index dd790fa..ec5de1a 100644 --- a/src/formats/dayMonthnameYear/dayMonthnameYear.spec.ts +++ b/src/formats/dayMonthnameYear/dayMonthnameYear.spec.ts @@ -1,75 +1,76 @@ -const testDates = require('../../../test-fixtures/testDates.js'); -const localeList = require('../../../test-fixtures/localeList.js'); -const parser = require('../../../index.js'); +import { describe, expect, it } from 'vitest'; +import parser from '../../../index'; +import localeList from '../../../test-fixtures/localeList'; +import testDates from '../../../test-fixtures/testDates'; testDates({ - name: 'day monthname year', - expected: { year: 2020, month: 1, day: 1 }, - // ar and zh do not have a monthname - locales: localeList.filter(l => !/^ar|zh|he/.test(l)), - formats: [ - 'cccc, dd MMMM yyyy', - 'cccc dd MMMM yyyy', - 'ccc, dd MMMM yyyy', - 'ccc dd MMMM yyyy', - 'dd MMMM yyyy', - 'dd-MMMM-yyyy', - ], + name: 'day monthname year', + expected: { year: 2020, month: 1, day: 1 }, + // ar and zh do not have a monthname + locales: localeList.filter(l => !/^ar|zh|he/.test(l)), + formats: [ + 'cccc, dd MMMM yyyy', + 'cccc dd MMMM yyyy', + 'ccc, dd MMMM yyyy', + 'ccc dd MMMM yyyy', + 'dd MMMM yyyy', + 'dd-MMMM-yyyy', + ], }); testDates({ - name: 'day short monthname year', - expected: { year: 2020, month: 1, day: 1 }, - // ar and zh do not have short monthnames - locales: localeList.filter(l => !/^ar|zh/.test(l)), - formats: ['d MMM yyyy', 'd-MMM-yyyy'], + name: 'day short monthname year', + expected: { year: 2020, month: 1, day: 1 }, + // ar and zh do not have short monthnames + locales: localeList.filter(l => !/^ar|zh/.test(l)), + formats: ['d MMM yyyy', 'd-MMM-yyyy'], }); testDates({ - name: 'day monthname two digit year', - expected: { year: 2020, month: 1, day: 1 }, - // non-latin numerals can't handle two-digit years - locales: localeList.filter(l => !/^ar|zh|bn/.test(l)), - formats: ['dd MMM yy', 'd MMM yy'], + name: 'day monthname two digit year', + expected: { year: 2020, month: 1, day: 1 }, + // non-latin numerals can't handle two-digit years + locales: localeList.filter(l => !/^ar|zh|bn/.test(l)), + formats: ['dd MMM yy', 'd MMM yy'], }); testDates({ - name: 'day ordinal monthname year', - expected: { year: 2018, month: 10, day: 28 }, - locales: ['en'], - formats: ["dd'th' MMM yy", "d'th' MMM yy", "dd'th'-MMM-yy", "d'th'-MMM-yy"], + name: 'day ordinal monthname year', + expected: { year: 2018, month: 10, day: 28 }, + locales: ['en'], + formats: ["dd'th' MMM yy", "d'th' MMM yy", "dd'th'-MMM-yy", "d'th'-MMM-yy"], }); describe('dayMonthnameYear', () => { - it('should handle GitHub issue #11', () => { - function dateAsISO(dateValue) { - if (!dateValue) { - return null; - } + it('should handle GitHub issue #11', () => { + function dateAsISO(dateValue) { + if (!dateValue) { + return null; + } - let date = null; + let date = null; - // if it's already a date, just use that. - if (dateValue instanceof Date) { - date = dateValue; - } else if (typeof dateValue === 'string') { - date = parser.fromString(dateValue); - } + // if it's already a date, just use that. + if (dateValue instanceof Date) { + date = dateValue; + } else if (typeof dateValue === 'string') { + date = parser.fromString(dateValue); + } - // when the date is invalid, it will give you a isNaN true - if (Number.isNaN(date)) { - return null; - } + // when the date is invalid, it will give you a isNaN true + if (Number.isNaN(date)) { + return null; + } - try { - return date.toISOString(); - } catch (error) { - // maybe RangeError - return null; - } - } - expect(dateAsISO('Fri, 19 Nov 2021 02:07:18 +0000')).toBe( - '2021-11-19T02:07:18.000Z' - ); - }); + try { + return date.toISOString(); + } catch (error) { + // maybe RangeError + return null; + } + } + expect(dateAsISO('Fri, 19 Nov 2021 02:07:18 +0000')).toBe( + '2021-11-19T02:07:18.000Z' + ); + }); }); diff --git a/src/formats/dayMonthnameYear/dayMonthnameYear.ts b/src/formats/dayMonthnameYear/dayMonthnameYear.ts index 4c3fdbb..a8874c7 100644 --- a/src/formats/dayMonthnameYear/dayMonthnameYear.ts +++ b/src/formats/dayMonthnameYear/dayMonthnameYear.ts @@ -1,10 +1,10 @@ -const Format = require('../../Format/Format.js'); +import Format from '../../Format/Format'; const dayMonthnameYear = new Format({ - /* prettier-ignore */ - // $1 $2 $3 - template: "^(?:(?:_DAYNAME_)(?:_GAP_))?(_DAY_)(?:_ORDINAL_)?(?:_GAP_)(_MONTHNAME_)(?:_GAP_)(_YEAR_)(?:_GAP_)?(?:_DAYNAME_)?$", - units: ['day', 'month', 'year'], + /* prettier-ignore */ + // $1 $2 $3 + template: "^(?:(?:_DAYNAME_)(?:_GAP_))?(_DAY_)(?:_ORDINAL_)?(?:_GAP_)(_MONTHNAME_)(?:_GAP_)(_YEAR_)(?:_GAP_)?(?:_DAYNAME_)?$", + units: ['day', 'month', 'year'], }); -module.exports = dayMonthnameYear; +export default dayMonthnameYear; diff --git a/src/formats/fuzzy/fuzzy.spec.ts b/src/formats/fuzzy/fuzzy.spec.ts index 5b26efc..d9f7748 100644 --- a/src/formats/fuzzy/fuzzy.spec.ts +++ b/src/formats/fuzzy/fuzzy.spec.ts @@ -1,34 +1,35 @@ -const parser = require('../../../index.js'); +import { describe, expect, it } from 'vitest'; +import parser from '../../../index'; describe('fuzzy', () => { - it('should handle ignore filler text 1', () => { - const actual = parser.attempt('On Wed 8 March in the year 2020'); - const expected = { year: 2020, month: 3, day: 8 }; - expect(actual).toEqual(expected); - }); - it('should default ignore filler text 2', () => { - const actual = parser.attempt('On Wed 8 March'); - const expected = { month: 3, day: 8 }; - expect(actual).toEqual(expected); - }); - it('should default ignore filler text 3', () => { - const actual = parser.attempt('2020 foo 31 bar 8'); - const expected = { year: 2020, month: 8, day: 31 }; - expect(actual).toEqual(expected); - }); - it('should get month alone', () => { - const actual = parser.attempt('aug'); - const expected = { month: 8 }; - expect(actual).toEqual(expected); - }); - it('should get day alone', () => { - const actual = parser.attempt('13'); - const expected = { day: 13 }; - expect(actual).toEqual(expected); - }); - it('should get day alone', () => { - const actual = parser.attempt('the 5th'); - const expected = { day: 5 }; - expect(actual).toEqual(expected); - }); + it('should handle ignore filler text 1', () => { + const actual = parser.attempt('On Wed 8 March in the year 2020'); + const expected = { year: 2020, month: 3, day: 8 }; + expect(actual).toEqual(expected); + }); + it('should default ignore filler text 2', () => { + const actual = parser.attempt('On Wed 8 March'); + const expected = { month: 3, day: 8 }; + expect(actual).toEqual(expected); + }); + it('should default ignore filler text 3', () => { + const actual = parser.attempt('2020 foo 31 bar 8'); + const expected = { year: 2020, month: 8, day: 31 }; + expect(actual).toEqual(expected); + }); + it('should get month alone', () => { + const actual = parser.attempt('aug'); + const expected = { month: 8 }; + expect(actual).toEqual(expected); + }); + it('should get day alone', () => { + const actual = parser.attempt('13'); + const expected = { day: 13 }; + expect(actual).toEqual(expected); + }); + it('should get day alone', () => { + const actual = parser.attempt('the 5th'); + const expected = { day: 5 }; + expect(actual).toEqual(expected); + }); }); diff --git a/src/formats/fuzzy/fuzzy.ts b/src/formats/fuzzy/fuzzy.ts index 49f85f6..fc0fc34 100644 --- a/src/formats/fuzzy/fuzzy.ts +++ b/src/formats/fuzzy/fuzzy.ts @@ -1,185 +1,188 @@ -const Format = require('../../Format/Format.js'); -const LocaleHelper = require('../../LocaleHelper/LocaleHelper.js'); +import Format from '../../Format/Format'; +import LocaleHelper from '../../LocaleHelper/LocaleHelper'; const extractorsByLocale = {}; function getExtractors(locale) { - if (!extractorsByLocale[locale]) { - const helper = new LocaleHelper(locale); - extractorsByLocale[locale] = [ - { - name: 'zonename', - regex: helper.compile('\\b(_ZONE_)\\b'), - handler: ([zoneName]) => { - return { offset: helper.lookups.zone[zoneName] }; - }, - }, - { - name: 'offset', - regex: helper.compile('\\b(_OFFSET_)\\b'), - handler: ([offsetString]) => { - return { offset: helper.offsetToMinutes(offsetString) }; - }, - }, - { - name: 'time24', - regex: helper.compile('\\b(_H24_):(_MIN_)(:(_SEC_))?(\\.(_MS_))?\\b'), - handler: ([, hour, min, seconds, ms]) => { - const result = { - hour: helper.toInt(hour), - minute: helper.toInt(min), - }; - if (seconds) { - result.second = helper.toInt(seconds); - } - if (ms) { - result.millisecond = helper.toInt(ms); - } - return result; - }, - }, - { - name: 'time12', - regex: helper.compile( - '\\b(_H12_)(:(_MIN_))?(:(_SEC_))?(_MERIDIEM_)\\b' - ), - handler: ([, h12, min, second, ampm]) => { - const offset = helper.lookups.meridiem[ampm.locale] || 0; - let hourInt = helper.toInt(h12); - if (hourInt < 12 && offset !== 12) { - hourInt += offset; - } - const result = { - hour: hourInt, - minute: helper.toInt(min), - }; - if (second) { - result.second = helper.toInt(second); - } - return result; - }, - }, - { - name: 'monthname', - regex: helper.compile('\\b(_MONTHNAME_)\\b'), - handler: ([monthName]) => { - const monthNumber = - helper.lookups.month[monthName.toLocaleLowerCase(locale)]; - if (monthNumber === undefined) { - return null; - } - return { month: monthNumber }; - }, - }, - { - name: 'daynumber', - // allow trailing non-number to allow ordinal suffixes - regex: helper.compile('\\b(_DAY_)(\\b|\\D)'), - handler: ([, dayNumber]) => { - return { day: helper.toInt(dayNumber) }; - }, - }, - { - name: 'monthnumber', - regex: helper.compile('\\b(_MONTH_)\\b'), - handler: ([monthNumber], alreadyFound) => { - if (alreadyFound.month === undefined) { - return { month: helper.toInt(monthNumber) }; - } - }, - }, - { - name: 'year4', - regex: helper.compile('\\b(_YEAR4_)\\b'), - handler: ([yearNumber]) => { - return { year: helper.toInt(yearNumber) }; - }, - }, - ]; - } - return extractorsByLocale[locale]; + if (!extractorsByLocale[locale]) { + const helper = new LocaleHelper(locale); + extractorsByLocale[locale] = [ + { + name: 'zonename', + regex: helper.compile('\\b(_ZONE_)\\b'), + handler: ([zoneName]) => { + return { offset: helper.lookups.zone[zoneName] }; + }, + }, + { + name: 'offset', + regex: helper.compile('\\b(_OFFSET_)\\b'), + handler: ([offsetString]) => { + return { offset: helper.offsetToMinutes(offsetString) }; + }, + }, + { + name: 'time24', + regex: helper.compile('\\b(_H24_):(_MIN_)(:(_SEC_))?(\\.(_MS_))?\\b'), + handler: ([, hour, min, seconds, ms]) => { + const result = { + hour: helper.toInt(hour), + minute: helper.toInt(min), + second: undefined, + millisecond: undefined, + }; + if (seconds) { + result.second = helper.toInt(seconds); + } + if (ms) { + result.millisecond = helper.toInt(ms); + } + return result; + }, + }, + { + name: 'time12', + regex: helper.compile( + '\\b(_H12_)(:(_MIN_))?(:(_SEC_))?(_MERIDIEM_)\\b' + ), + handler: ([, h12, min, second, ampm]) => { + const offset = helper.lookups.meridiem[ampm.locale] || 0; + let hourInt = helper.toInt(h12); + if (hourInt < 12 && offset !== 12) { + hourInt += offset; + } + const result = { + hour: hourInt, + minute: helper.toInt(min), + second: undefined, + }; + if (second) { + result.second = helper.toInt(second); + } + return result; + }, + }, + { + name: 'monthname', + regex: helper.compile('\\b(_MONTHNAME_)\\b'), + handler: ([monthName]) => { + const monthNumber = + helper.lookups.month[monthName.toLocaleLowerCase(locale)]; + if (monthNumber === undefined) { + return null; + } + return { month: monthNumber }; + }, + }, + { + name: 'daynumber', + // allow trailing non-number to allow ordinal suffixes + regex: helper.compile('\\b(_DAY_)(\\b|\\D)'), + handler: ([, dayNumber]) => { + return { day: helper.toInt(dayNumber) }; + }, + }, + { + name: 'monthnumber', + regex: helper.compile('\\b(_MONTH_)\\b'), + handler: ([monthNumber], alreadyFound) => { + if (alreadyFound.month === undefined) { + return { month: helper.toInt(monthNumber) }; + } + }, + }, + { + name: 'year4', + regex: helper.compile('\\b(_YEAR4_)\\b'), + handler: ([yearNumber]) => { + return { year: helper.toInt(yearNumber) }; + }, + }, + ]; + } + return extractorsByLocale[locale]; } const fuzzy = new Format({ - matcher: /^.+$/, - handler: function ([fullString], locale) { - let workingString = fullString; - const result = {}; - let hasMatch = false; - for (const extractor of getExtractors(locale)) { - const match = workingString.match(extractor.regex); - if (!match) { - continue; - } - const handled = extractor.handler(match, result); - if (typeof handled === 'object') { - Object.assign(result, handled); - workingString = workingString.replace(extractor.regex, ''); - hasMatch = true; - } - // console.log({ name: extractor.name, workingString, result }); - } - if (!hasMatch) { - return null; - } - // if (result.month && result.day && result.year === undefined) { - // result.year = new Date().getFullYear(); - // } - // if (result.month && result.day && result.year) { - // return result; - // } - return result; - // - // const yearExtractor = /(\d+\D+)?(\d{4})(\D+\d+)?/i; - // const yearMatch = fullString.match(yearExtractor); - // if (!yearMatch) { - // // no 4-digit year - // return null; - // } - // const [, day1String, yearString, day2String] = yearMatch; - // const year = parseInt(yearString, 10); - // const day1 = day1String ? parseInt(day1String, 10) : null; - // const day2 = day2String ? parseInt(day1String, 10) : null; - // const day = day1 || day2; - // if (!day) { - // // no day number - // return null; - // } - // if (!monthLookups[locale]) { - // const helper = new LocaleHelper(locale); - // monthLookups[locale] = { - // lookup: helper.lookups.month, - // regex: new RegExp(`(${helper.vars.MONTHNAME})`, 'i'), - // }; - // } - // const monthMatch = fullString.match(monthLookups[locale].regex); - // if (monthMatch) { - // // full month name - // const month = - // monthLookups[locale].lookup[monthMatch[1].toLocaleLowerCase(locale)]; - // if (month) { - // // hey we got a month number! - // return { - // year, - // month, - // day, - // }; - // } else { - // // unknown month name - // return null; - // } - // } - // // try looking for month number - // if (day1 >= 1 && day1 <= 12 && day2 >= 1 && day2 <= 31) { - // // day1 appears to be month and day2 appears to be day - // return { year, month: day1, day: day2 }; - // } else if (day2 >= 1 && day2 <= 12 && day1 >= 1 && day1 <= 31) { - // // day2 appears to be month and day1 appears to be day - // return { year, month: day2, day: day1 }; - // } - // // we don't seem to have valid numbers - // return null; - }, + matcher: /^.+$/, + handler: function ([fullString], locale) { + let workingString = fullString; + const result = {}; + let hasMatch = false; + for (const extractor of getExtractors(locale)) { + const match = workingString.match(extractor.regex); + if (!match) { + continue; + } + const handled = extractor.handler(match, result); + if (typeof handled === 'object') { + Object.assign(result, handled); + workingString = workingString.replace(extractor.regex, ''); + hasMatch = true; + } + // console.log({ name: extractor.name, workingString, result }); + } + if (!hasMatch) { + return null; + } + // if (result.month && result.day && result.year === undefined) { + // result.year = new Date().getFullYear(); + // } + // if (result.month && result.day && result.year) { + // return result; + // } + return result; + // + // const yearExtractor = /(\d+\D+)?(\d{4})(\D+\d+)?/i; + // const yearMatch = fullString.match(yearExtractor); + // if (!yearMatch) { + // // no 4-digit year + // return null; + // } + // const [, day1String, yearString, day2String] = yearMatch; + // const year = parseInt(yearString, 10); + // const day1 = day1String ? parseInt(day1String, 10) : null; + // const day2 = day2String ? parseInt(day1String, 10) : null; + // const day = day1 || day2; + // if (!day) { + // // no day number + // return null; + // } + // if (!monthLookups[locale]) { + // const helper = new LocaleHelper(locale); + // monthLookups[locale] = { + // lookup: helper.lookups.month, + // regex: new RegExp(`(${helper.vars.MONTHNAME})`, 'i'), + // }; + // } + // const monthMatch = fullString.match(monthLookups[locale].regex); + // if (monthMatch) { + // // full month name + // const month = + // monthLookups[locale].lookup[monthMatch[1].toLocaleLowerCase(locale)]; + // if (month) { + // // hey we got a month number! + // return { + // year, + // month, + // day, + // }; + // } else { + // // unknown month name + // return null; + // } + // } + // // try looking for month number + // if (day1 >= 1 && day1 <= 12 && day2 >= 1 && day2 <= 31) { + // // day1 appears to be month and day2 appears to be day + // return { year, month: day1, day: day2 }; + // } else if (day2 >= 1 && day2 <= 12 && day1 >= 1 && day1 <= 31) { + // // day2 appears to be month and day1 appears to be day + // return { year, month: day2, day: day1 }; + // } + // // we don't seem to have valid numbers + // return null; + }, }); -module.exports = fuzzy; +export default fuzzy; diff --git a/src/formats/korean/korean.ts b/src/formats/korean/korean.ts index bc7de7e..a23de44 100644 --- a/src/formats/korean/korean.ts +++ b/src/formats/korean/korean.ts @@ -1,10 +1,10 @@ -const Format = require('../../Format/Format.js'); +import Format from '../../Format/Format'; const korean = new Format({ - /* prettier-ignore */ - // $1 $2 $3 - template: `^(_YEAR_)년(?:_GAP_)?(_MONTH_)월(?:_GAP_)?(_DAY_)일(?:_GAP_)?(?:_DAYNAME_)?$`, - units: ['year', 'month', 'day'], + /* prettier-ignore */ + // $1 $2 $3 + template: `^(_YEAR_)년(?:_GAP_)?(_MONTH_)월(?:_GAP_)?(_DAY_)일(?:_GAP_)?(?:_DAYNAME_)?$`, + units: ['year', 'month', 'day'], }); -module.exports = korean; +export default korean; diff --git a/src/formats/microsoftJson/microsoftJson.spec.ts b/src/formats/microsoftJson/microsoftJson.spec.ts index f060a57..bfe892f 100644 --- a/src/formats/microsoftJson/microsoftJson.spec.ts +++ b/src/formats/microsoftJson/microsoftJson.spec.ts @@ -1,32 +1,32 @@ -const testParser = require('../../../test-fixtures/testParser.js'); +import testParser from '../../../test-fixtures/testParser'; testParser({ - name: 'microsoft format without offset', - expected: { - year: 2020, - month: 10, - day: 2, - hour: 22, - minute: 31, - second: 29, - millisecond: 8, - offset: 0, - }, - locales: ['en-US'], - dates: ['/Date(1601677889008)/'], + name: 'microsoft format without offset', + expected: { + year: 2020, + month: 10, + day: 2, + hour: 22, + minute: 31, + second: 29, + millisecond: 8, + offset: 0, + }, + locales: ['en-US'], + dates: ['/Date(1601677889008)/'], }); testParser({ - name: 'microsoft format with offset', - expected: { - year: 2020, - month: 10, - day: 2, - hour: 22, - minute: 31, - second: 29, - millisecond: 8, - offset: -420, - }, - locales: ['en-US'], - dates: ['/Date(1601677889008-0700)/'], + name: 'microsoft format with offset', + expected: { + year: 2020, + month: 10, + day: 2, + hour: 22, + minute: 31, + second: 29, + millisecond: 8, + offset: -420, + }, + locales: ['en-US'], + dates: ['/Date(1601677889008-0700)/'], }); diff --git a/src/formats/microsoftJson/microsoftJson.ts b/src/formats/microsoftJson/microsoftJson.ts index f3b9406..5a1007f 100644 --- a/src/formats/microsoftJson/microsoftJson.ts +++ b/src/formats/microsoftJson/microsoftJson.ts @@ -1,21 +1,21 @@ -const Format = require('../../Format/Format.js'); +import Format from '../../Format/Format'; const microsoftJson = new Format({ - matcher: /^\/Date\((\d+)([+-]\d{4})?\)\/$/, - handler: function (matches) { - const milliseconds = parseInt(matches[1], 10); - const date = new Date(milliseconds); - return { - year: date.getUTCFullYear(), - month: date.getUTCMonth() + 1, - day: date.getUTCDate(), - hour: date.getUTCHours(), - minute: date.getUTCMinutes(), - second: date.getUTCSeconds(), - millisecond: date.getUTCMilliseconds(), - offset: matches[2] || 0, - }; - }, + matcher: /^\/Date\((\d+)([+-]\d{4})?\)\/$/, + handler: function (matches) { + const milliseconds = parseInt(matches[1], 10); + const date = new Date(milliseconds); + return { + year: date.getUTCFullYear(), + month: date.getUTCMonth() + 1, + day: date.getUTCDate(), + hour: date.getUTCHours(), + minute: date.getUTCMinutes(), + second: date.getUTCSeconds(), + millisecond: date.getUTCMilliseconds(), + offset: matches[2] || 0, + }; + }, }); -module.exports = microsoftJson; +export default microsoftJson; diff --git a/src/formats/monthDay/monthDay.spec.ts b/src/formats/monthDay/monthDay.spec.ts index c87f7c0..032b61a 100644 --- a/src/formats/monthDay/monthDay.spec.ts +++ b/src/formats/monthDay/monthDay.spec.ts @@ -1,8 +1,8 @@ -const testDates = require('../../../test-fixtures/testDates.js'); +import testDates from '../../../test-fixtures/testDates'; testDates({ - name: 'month day', - expected: { month: 3, day: 14 }, - locales: ['en-US'], - formats: ['MM/dd', 'MM-dd', 'M/dd', 'M-dd'], + name: 'month day', + expected: { month: 3, day: 14 }, + locales: ['en-US'], + formats: ['MM/dd', 'MM-dd', 'M/dd', 'M-dd'], }); diff --git a/src/formats/monthDay/monthDay.ts b/src/formats/monthDay/monthDay.ts index 0f2211d..c9f430f 100644 --- a/src/formats/monthDay/monthDay.ts +++ b/src/formats/monthDay/monthDay.ts @@ -1,10 +1,10 @@ -const Format = require('../../Format/Format.js'); +import Format from '../../Format/Format'; const monthDay = new Format({ - /* prettier-ignore */ - // $1 $2 - template: "^(_MONTH_)(?:[\\/-])(_DAY_)$", - units: ['month', 'day'], + /* prettier-ignore */ + // $1 $2 + template: "^(_MONTH_)(?:[\\/-])(_DAY_)$", + units: ['month', 'day'], }); -module.exports = monthDay; +export default monthDay; diff --git a/src/formats/monthDayYear/monthDayYear.spec.ts b/src/formats/monthDayYear/monthDayYear.spec.ts index 424de11..75c4a00 100644 --- a/src/formats/monthDayYear/monthDayYear.spec.ts +++ b/src/formats/monthDayYear/monthDayYear.spec.ts @@ -1,59 +1,60 @@ -const testDates = require('../../../test-fixtures/testDates.js'); -const parser = require('../../../index.js'); +import { describe, expect, it } from 'vitest'; +import parser from '../../../index'; +import testDates from '../../../test-fixtures/testDates'; testDates({ - name: 'month day year', - expected: { year: 2020, month: 3, day: 14 }, - locales: [ - 'ee-TG', // Togo (Ewe) - 'en-AS', // American Samoa - 'en-CA', // Canada - 'en-FM', // Federated States of Micronesia - 'en-GH', // Ghana - 'en-GU', // Guam - 'en-KE', // Kenya - 'en-KY', // Cayman Islands - 'en-MH', // Marshall Islands - 'en-MP', // Northern Mariana Islands - 'en-US', // United States - 'en-VI', // US Virgin Islands - 'en-WS', // Western Samoa - 'sm-AS', // American Samoa (Samoan) - 'sm-SM', // Samoa - ], - formats: [ - 'MM/dd/yyyy', - 'MM-dd-yyyy', - 'M/dd/yyyy', - 'M-dd-yyyy', - 'MM/dd/yy', - 'MM-dd-yy', - ], + name: 'month day year', + expected: { year: 2020, month: 3, day: 14 }, + locales: [ + 'ee-TG', // Togo (Ewe) + 'en-AS', // American Samoa + 'en-CA', // Canada + 'en-FM', // Federated States of Micronesia + 'en-GH', // Ghana + 'en-GU', // Guam + 'en-KE', // Kenya + 'en-KY', // Cayman Islands + 'en-MH', // Marshall Islands + 'en-MP', // Northern Mariana Islands + 'en-US', // United States + 'en-VI', // US Virgin Islands + 'en-WS', // Western Samoa + 'sm-AS', // American Samoa (Samoan) + 'sm-SM', // Samoa + ], + formats: [ + 'MM/dd/yyyy', + 'MM-dd-yyyy', + 'M/dd/yyyy', + 'M-dd-yyyy', + 'MM/dd/yy', + 'MM-dd-yy', + ], }); describe('month day year for other locales', () => { - it('should not support month day year with slashes', () => { - const actual = parser.attempt('5/31/2021', 'FR'); - expect(actual.invalid).toMatch(/^Unable to parse/); - }); - it('should not support month day year with dashes', () => { - const actual = parser.attempt('5-31-2021', 'FR'); - expect(actual.invalid).toMatch(/^Unable to parse/); - }); - it('should recognize day month year with slashes instead', () => { - const actual = parser.attempt('5/3/2021', 'FR'); - expect(actual).toEqual({ - month: 3, - day: 5, - year: 2021, - }); - }); - it('should recognize day month year with dashes instead', () => { - const actual = parser.attempt('5-3-2021', 'FR'); - expect(actual).toEqual({ - month: 3, - day: 5, - year: 2021, - }); - }); + it('should not support month day year with slashes', () => { + const actual = parser.attempt('5/31/2021', 'FR'); + expect(actual.invalid).toMatch(/^Unable to parse/); + }); + it('should not support month day year with dashes', () => { + const actual = parser.attempt('5-31-2021', 'FR'); + expect(actual.invalid).toMatch(/^Unable to parse/); + }); + it('should recognize day month year with slashes instead', () => { + const actual = parser.attempt('5/3/2021', 'FR'); + expect(actual).toEqual({ + month: 3, + day: 5, + year: 2021, + }); + }); + it('should recognize day month year with dashes instead', () => { + const actual = parser.attempt('5-3-2021', 'FR'); + expect(actual).toEqual({ + month: 3, + day: 5, + year: 2021, + }); + }); }); diff --git a/src/formats/monthDayYear/monthDayYear.ts b/src/formats/monthDayYear/monthDayYear.ts index cbf0b73..82ab23e 100644 --- a/src/formats/monthDayYear/monthDayYear.ts +++ b/src/formats/monthDayYear/monthDayYear.ts @@ -1,31 +1,31 @@ -const Format = require('../../Format/Format.js'); +import Format from '../../Format/Format'; const monthDayYear = new Format({ - /* prettier-ignore */ - // $1 $2 $3 $4 - template: "^(_MONTH_)(_GAP_)(_DAY_)\\2(_YEAR_)$", - units: ['month', null, 'day', 'year'], - // only certain locales use this date - // see https://en.wikipedia.org/wiki/Date_format_by_country - // see https://www.localeplanet.com/icu/ - locales: [ - 'ee-TG', // Togo (Ewe) - 'en-AS', // American Samoa - 'en-CA', // Canada - 'en-FM', // Federated States of Micronesia - 'en-GH', // Ghana - 'en-GU', // Guam - 'en-KE', // Kenya - 'en-KY', // Cayman Islands - 'en-MH', // Marshall Islands - 'en-MP', // Northern Mariana Islands - 'en-US', // United States - 'en-VI', // US Virgin Islands - 'en-WS', // Western Samoa - 'jp-JP', // Japan - 'sm-AS', // American Samoa (Samoan) - 'sm-SM', // Samoa - ], + /* prettier-ignore */ + // $1 $2 $3 $4 + template: "^(_MONTH_)(_GAP_)(_DAY_)\\2(_YEAR_)$", + units: ['month', null, 'day', 'year'], + // only certain locales use this date + // see https://en.wikipedia.org/wiki/Date_format_by_country + // see https://www.localeplanet.com/icu/ + locales: [ + 'ee-TG', // Togo (Ewe) + 'en-AS', // American Samoa + 'en-CA', // Canada + 'en-FM', // Federated States of Micronesia + 'en-GH', // Ghana + 'en-GU', // Guam + 'en-KE', // Kenya + 'en-KY', // Cayman Islands + 'en-MH', // Marshall Islands + 'en-MP', // Northern Mariana Islands + 'en-US', // United States + 'en-VI', // US Virgin Islands + 'en-WS', // Western Samoa + 'jp-JP', // Japan + 'sm-AS', // American Samoa (Samoan) + 'sm-SM', // Samoa + ], }); -module.exports = monthDayYear; +export default monthDayYear; diff --git a/src/formats/monthnameDay/monthnameDay.spec.ts b/src/formats/monthnameDay/monthnameDay.spec.ts index f42c79e..f10a40f 100644 --- a/src/formats/monthnameDay/monthnameDay.spec.ts +++ b/src/formats/monthnameDay/monthnameDay.spec.ts @@ -1,23 +1,23 @@ -const testDates = require('../../../test-fixtures/testDates.js'); -const localeList = require('../../../test-fixtures/localeList.js'); +import localeList from '../../../test-fixtures/localeList'; +import testDates from '../../../test-fixtures/testDates'; testDates({ - name: 'monthname day', - expected: { month: 6, day: 28 }, - locales: localeList.filter(l => !/^ar|zh/.test(l)), - formats: [ - 'cccc, MMMM dd', - 'cccc MMMM dd', - 'ccc, MMMM dd', - 'ccc MMMM dd', - 'MMMM dd', - 'MMM dd', - ], + name: 'monthname day', + expected: { month: 6, day: 28 }, + locales: localeList.filter(l => !/^ar|zh/.test(l)), + formats: [ + 'cccc, MMMM dd', + 'cccc MMMM dd', + 'ccc, MMMM dd', + 'ccc MMMM dd', + 'MMMM dd', + 'MMM dd', + ], }); testDates({ - name: 'monthname day year', - expected: { year: 2017, month: 2, day: 28 }, - locales: ['en', 'es', 'de', 'fi', 'fr', 'pt', 'no', 'nl', 'pl'], - formats: ['MMM dd yy'], + name: 'monthname day year', + expected: { year: 2017, month: 2, day: 28 }, + locales: ['en', 'es', 'de', 'fi', 'fr', 'pt', 'no', 'nl', 'pl'], + formats: ['MMM dd yy'], }); diff --git a/src/formats/monthnameDay/monthnameDay.ts b/src/formats/monthnameDay/monthnameDay.ts index 1403296..7bbfaad 100644 --- a/src/formats/monthnameDay/monthnameDay.ts +++ b/src/formats/monthnameDay/monthnameDay.ts @@ -1,10 +1,10 @@ -const Format = require('../../Format/Format.js'); +import Format from '../../Format/Format'; const monthnameDay = new Format({ - /* prettier-ignore */ - // $1 $2 - template: '^(?:(?:_DAYNAME_),? )?(_MONTHNAME_)? (_DAY_)(?:_ORDINAL_)?$', - units: ['month', 'day'], + /* prettier-ignore */ + // $1 $2 + template: '^(?:(?:_DAYNAME_),? )?(_MONTHNAME_)? (_DAY_)(?:_ORDINAL_)?$', + units: ['month', 'day'], }); -module.exports = monthnameDay; +export default monthnameDay; diff --git a/src/formats/monthnameDayYear/monthnameDayYear.spec.ts b/src/formats/monthnameDayYear/monthnameDayYear.spec.ts index af5edbe..a013d25 100644 --- a/src/formats/monthnameDayYear/monthnameDayYear.spec.ts +++ b/src/formats/monthnameDayYear/monthnameDayYear.spec.ts @@ -1,24 +1,24 @@ -const testDates = require('../../../test-fixtures/testDates.js'); -const localeList = require('../../../test-fixtures/localeList.js'); +import localeList from '../../../test-fixtures/localeList'; +import testDates from '../../../test-fixtures/testDates'; testDates({ - name: 'monthname day year', - expected: { year: 2016, month: 3, day: 27 }, - // ar and zh do not have a monthname - locales: localeList.filter(l => !/^ar|zh/.test(l)), - formats: [ - 'cccc, MMMM dd yyyy', - 'cccc MMMM dd yyyy', - 'ccc, MMMM dd yyyy', - 'ccc MMMM dd yyyy', - 'MMMM dd yyyy', - 'MMM dd, yyyy', - 'MMM dd yyyy', - ], + name: 'monthname day year', + expected: { year: 2016, month: 3, day: 27 }, + // ar and zh do not have a monthname + locales: localeList.filter(l => !/^ar|zh/.test(l)), + formats: [ + 'cccc, MMMM dd yyyy', + 'cccc MMMM dd yyyy', + 'ccc, MMMM dd yyyy', + 'ccc MMMM dd yyyy', + 'MMMM dd yyyy', + 'MMM dd, yyyy', + 'MMM dd yyyy', + ], }); testDates({ - name: 'monthname day year', - expected: { year: 2017, month: 2, day: 28 }, - locales: ['en', 'es', 'de', 'fi', 'fr', 'pt', 'no', 'nl', 'pl'], - formats: ['MMM dd yy'], + name: 'monthname day year', + expected: { year: 2017, month: 2, day: 28 }, + locales: ['en', 'es', 'de', 'fi', 'fr', 'pt', 'no', 'nl', 'pl'], + formats: ['MMM dd yy'], }); diff --git a/src/formats/monthnameDayYear/monthnameDayYear.ts b/src/formats/monthnameDayYear/monthnameDayYear.ts index ce850a7..3e0a9e8 100644 --- a/src/formats/monthnameDayYear/monthnameDayYear.ts +++ b/src/formats/monthnameDayYear/monthnameDayYear.ts @@ -1,10 +1,10 @@ -const Format = require('../../Format/Format.js'); +import Format from '../../Format/Format'; const monthnameDayYear = new Format({ - /* prettier-ignore */ - // $1 $2 $3 - template: '^(?:(?:_DAYNAME_),? )?(_MONTHNAME_)? (_DAY_)(?:_ORDINAL_)?,? (_YEAR_)$', - units: ['month', 'day', 'year'], + /* prettier-ignore */ + // $1 $2 $3 + template: '^(?:(?:_DAYNAME_),? )?(_MONTHNAME_)? (_DAY_)(?:_ORDINAL_)?,? (_YEAR_)$', + units: ['month', 'day', 'year'], }); -module.exports = monthnameDayYear; +export default monthnameDayYear; diff --git a/src/formats/time12Hours/time12Hours.spec.ts b/src/formats/time12Hours/time12Hours.spec.ts index d86cdd2..3264757 100644 --- a/src/formats/time12Hours/time12Hours.spec.ts +++ b/src/formats/time12Hours/time12Hours.spec.ts @@ -1,83 +1,84 @@ -const parser = require('../../../index.js'); +import { describe, expect, it } from 'vitest'; +import parser from '../../../index'; describe('12 hour time', () => { - it('should handle hours: "8pm"', () => { - const actual = parser.attempt('8pm'); - const expected = { - hour: 20, - }; - expect(actual).toMatchObject(expected); - }); - it('should handle invalid date: "Foobarbaz at 8pm"', () => { - const actual = parser.attempt('Foobarbaz at 8pm'); - expect(actual.invalid).toBe('Unable to parse Foobarbaz at 8pm'); - }); - it('should handle dots in "a.m.": "4 a.m."', () => { - const actual = parser.attempt('4 a.m.'); - const expected = { - hour: 4, - }; - expect(actual).toMatchObject(expected); - }); - it('should handle minutes: "8:15pm"', () => { - const actual = parser.attempt('8:15pm'); - const expected = { - hour: 20, - minute: 15, - }; - expect(actual).toMatchObject(expected); - }); - it('should handle seconds: "8:15:14am"', () => { - const actual = parser.attempt('8:15:14am'); - const expected = { - hour: 8, - minute: 15, - second: 14, - }; - expect(actual).toMatchObject(expected); - }); - it('should handle leap seconds: "11:59:60pm"', () => { - const actual = parser.attempt('11:59:60pm'); - const expected = { - hour: 23, - minute: 59, - second: 60, - }; - expect(actual).toMatchObject(expected); - }); - it('should handle dates: "March 14, 2015 9:26pm"', () => { - const actual = parser.attempt('March 14, 2015 9:26pm'); - const expected = { - year: 2015, - month: 3, - day: 14, - hour: 21, - minute: 26, - }; - expect(actual).toEqual(expected); - }); - it('should handle dates joined with "at": "March 14, 2015 at 9:26:53 am"', () => { - const actual = parser.attempt('March 14, 2015 at 9:26:53 am'); - const expected = { - year: 2015, - month: 3, - day: 14, - hour: 9, - minute: 26, - second: 53, - }; - expect(actual).toEqual(expected); - }); - it('should handle dates with commas: "4/19/2021, 10:04:02 AM"', () => { - const actual = parser.attempt('4/19/2021, 10:04:02 AM'); - const expected = { - year: 2021, - month: 4, - day: 19, - hour: 10, - minute: 4, - second: 2, - }; - expect(actual).toEqual(expected); - }); + it('should handle hours: "8pm"', () => { + const actual = parser.attempt('8pm'); + const expected = { + hour: 20, + }; + expect(actual).toMatchObject(expected); + }); + it('should handle invalid date: "Foobarbaz at 8pm"', () => { + const actual = parser.attempt('Foobarbaz at 8pm'); + expect(actual.invalid).toBe('Unable to parse Foobarbaz at 8pm'); + }); + it('should handle dots in "a.m.": "4 a.m."', () => { + const actual = parser.attempt('4 a.m.'); + const expected = { + hour: 4, + }; + expect(actual).toMatchObject(expected); + }); + it('should handle minutes: "8:15pm"', () => { + const actual = parser.attempt('8:15pm'); + const expected = { + hour: 20, + minute: 15, + }; + expect(actual).toMatchObject(expected); + }); + it('should handle seconds: "8:15:14am"', () => { + const actual = parser.attempt('8:15:14am'); + const expected = { + hour: 8, + minute: 15, + second: 14, + }; + expect(actual).toMatchObject(expected); + }); + it('should handle leap seconds: "11:59:60pm"', () => { + const actual = parser.attempt('11:59:60pm'); + const expected = { + hour: 23, + minute: 59, + second: 60, + }; + expect(actual).toMatchObject(expected); + }); + it('should handle dates: "March 14, 2015 9:26pm"', () => { + const actual = parser.attempt('March 14, 2015 9:26pm'); + const expected = { + year: 2015, + month: 3, + day: 14, + hour: 21, + minute: 26, + }; + expect(actual).toEqual(expected); + }); + it('should handle dates joined with "at": "March 14, 2015 at 9:26:53 am"', () => { + const actual = parser.attempt('March 14, 2015 at 9:26:53 am'); + const expected = { + year: 2015, + month: 3, + day: 14, + hour: 9, + minute: 26, + second: 53, + }; + expect(actual).toEqual(expected); + }); + it('should handle dates with commas: "4/19/2021, 10:04:02 AM"', () => { + const actual = parser.attempt('4/19/2021, 10:04:02 AM'); + const expected = { + year: 2021, + month: 4, + day: 19, + hour: 10, + minute: 4, + second: 2, + }; + expect(actual).toEqual(expected); + }); }); diff --git a/src/formats/time12Hours/time12Hours.ts b/src/formats/time12Hours/time12Hours.ts index 54fe2c0..ed4295c 100644 --- a/src/formats/time12Hours/time12Hours.ts +++ b/src/formats/time12Hours/time12Hours.ts @@ -1,42 +1,47 @@ -const LocaleHelper = require('../../LocaleHelper/LocaleHelper.js'); -const Format = require('../../Format/Format.js'); +import Format from '../../Format/Format'; +import LocaleHelper from '../../LocaleHelper/LocaleHelper'; // lots of 12h time such as "11:59", "11:59pm", "11:59:33 pm", "11:59:33 p.m." const time12Hours = new Format({ - /* prettier-ignore */ - // $1 $2 $3 $4 $5 - template: '^(.*?)(?:_GAP_)?(?:at|on|T|)(?:_GAP_)?(_H12_|_H24_)(?:\\:(_MIN_)(?:\\:(_SEC_))?)?(?:_GAP_)?(_MERIDIEM_)$', - handler: function (matches, locale) { - let [, dateExpr, hour, minute, second, ampm] = matches; - let result = {}; - if (dateExpr) { - result = this.parser.attempt(dateExpr, locale); - if (result.invalid) { - // let other matchers have a chance - return null; - } - } - const tpl = LocaleHelper.factory(locale); - if (ampm) { - const offset = tpl.lookups.meridiem[ampm.toLocaleLowerCase(locale)] || 0; - hour = parseFloat(hour); - if (hour === 12) { - hour = offset; - } else if (hour > 12 && offset === 12) { - hour += 0; - } else { - hour += offset; - } - } - result.hour = hour; - if (minute) { - result.minute = minute; - } - if (second) { - result.second = second; - } - return result; - }, + /* prettier-ignore */ + // $1 $2 $3 $4 $5 + template: '^(.*?)(?:_GAP_)?(?:at|on|T|)(?:_GAP_)?(_H12_|_H24_)(?:\\:(_MIN_)(?:\\:(_SEC_))?)?(?:_GAP_)?(_MERIDIEM_)$', + handler: function (matches, locale) { + let [, dateExpr, hour, minute, second, ampm] = matches; + let result: { + invalid?: boolean; + hour?: number; + minute?: number; + second?: number; + } = {}; + if (dateExpr) { + result = this.parser.attempt(dateExpr, locale); + if (result.invalid) { + // let other matchers have a chance + return null; + } + } + const tpl = LocaleHelper.factory(locale); + if (ampm) { + const offset = tpl.lookups.meridiem[ampm.toLocaleLowerCase(locale)] || 0; + hour = parseFloat(hour); + if (hour === 12) { + hour = offset; + } else if (hour > 12 && offset === 12) { + hour += 0; + } else { + hour += offset; + } + } + result.hour = hour; + if (minute) { + result.minute = minute; + } + if (second) { + result.second = second; + } + return result; + }, }); -module.exports = time12Hours; +export default time12Hours; diff --git a/src/formats/time24Hours/time24Hours.spec.ts b/src/formats/time24Hours/time24Hours.spec.ts index a351e7b..ab3b185 100644 --- a/src/formats/time24Hours/time24Hours.spec.ts +++ b/src/formats/time24Hours/time24Hours.spec.ts @@ -1,221 +1,221 @@ -const parser = require('../../../index.js'); -const testDates = require('../../../test-fixtures/testDates.js'); +import parser from '../../../index'; +import testDates from '../../../test-fixtures/testDates'; testDates({ - name: '24 hour time', - locales: ['ar-SA', 'bn-BD', 'bn-IN', 'en-US'], - expected: { - year: 2020, - month: 10, - day: 6, - hour: 17, - minute: 41, - second: 28, - }, - formats: ['yyyy-MM-dd HH:mm:ss'], + name: '24 hour time', + locales: ['ar-SA', 'bn-BD', 'bn-IN', 'en-US'], + expected: { + year: 2020, + month: 10, + day: 6, + hour: 17, + minute: 41, + second: 28, + }, + formats: ['yyyy-MM-dd HH:mm:ss'], }); testDates({ - name: '24 hour time', - locales: ['ar-SA', 'bn-BD', 'bn-IN', 'en-US'], - expected: { - year: 2020, - month: 10, - day: 6, - hour: 17, - minute: 41, - second: 28, - }, - formats: ["yyyy-MM-dd'T'HH:mm:ss'Z'"], + name: '24 hour time', + locales: ['ar-SA', 'bn-BD', 'bn-IN', 'en-US'], + expected: { + year: 2020, + month: 10, + day: 6, + hour: 17, + minute: 41, + second: 28, + }, + formats: ["yyyy-MM-dd'T'HH:mm:ss'Z'"], }); testDates({ - name: '24 hour time with no date', - locales: ['en-US'], - expected: { - hour: 17, - minute: 41, - second: 28, - }, - formats: ['HH:mm:ss'], + name: '24 hour time with no date', + locales: ['en-US'], + expected: { + hour: 17, + minute: 41, + second: 28, + }, + formats: ['HH:mm:ss'], }); testDates({ - name: '24 hour time with milliseconds', - locales: ['en-US'], - expected: { - year: 2020, - month: 10, - day: 6, - hour: 17, - minute: 41, - second: 28, - millisecond: 999, - }, - formats: ["yyyy-MM-dd'T'HH:mm:ss.999'Z'"], + name: '24 hour time with milliseconds', + locales: ['en-US'], + expected: { + year: 2020, + month: 10, + day: 6, + hour: 17, + minute: 41, + second: 28, + millisecond: 999, + }, + formats: ["yyyy-MM-dd'T'HH:mm:ss.999'Z'"], }); testDates({ - name: '24 hour time with 2-digit milliseconds', - locales: ['en-US'], - expected: { - year: 2020, - month: 10, - day: 6, - hour: 17, - minute: 41, - second: 28, - millisecond: 99, - }, - formats: ["yyyy-MM-dd'T'HH:mm:ss.99'Z'"], + name: '24 hour time with 2-digit milliseconds', + locales: ['en-US'], + expected: { + year: 2020, + month: 10, + day: 6, + hour: 17, + minute: 41, + second: 28, + millisecond: 99, + }, + formats: ["yyyy-MM-dd'T'HH:mm:ss.99'Z'"], }); testDates({ - name: '24 hour time with 1-digit milliseconds', - locales: ['en-US'], - expected: { - year: 2020, - month: 10, - day: 6, - hour: 17, - minute: 41, - second: 28, - millisecond: 9, - }, - formats: ["yyyy-MM-dd'T'HH:mm:ss.9'Z'"], + name: '24 hour time with 1-digit milliseconds', + locales: ['en-US'], + expected: { + year: 2020, + month: 10, + day: 6, + hour: 17, + minute: 41, + second: 28, + millisecond: 9, + }, + formats: ["yyyy-MM-dd'T'HH:mm:ss.9'Z'"], }); testDates({ - name: '24 hour time with microseconds', - locales: ['en-US'], - expected: { - year: 2020, - month: 10, - day: 6, - hour: 17, - minute: 41, - second: 28, - millisecond: 999, - }, - formats: ["yyyy-MM-dd'T'HH:mm:ss.999999'Z'"], + name: '24 hour time with microseconds', + locales: ['en-US'], + expected: { + year: 2020, + month: 10, + day: 6, + hour: 17, + minute: 41, + second: 28, + millisecond: 999, + }, + formats: ["yyyy-MM-dd'T'HH:mm:ss.999999'Z'"], }); testDates({ - name: '24 hour time with nanoseconds', - locales: ['en-US'], - expected: { - year: 2020, - month: 10, - day: 6, - hour: 17, - minute: 41, - second: 28, - millisecond: 999, - }, - formats: ["yyyy-MM-dd'T'HH:mm:ss.999999999'Z'"], + name: '24 hour time with nanoseconds', + locales: ['en-US'], + expected: { + year: 2020, + month: 10, + day: 6, + hour: 17, + minute: 41, + second: 28, + millisecond: 999, + }, + formats: ["yyyy-MM-dd'T'HH:mm:ss.999999999'Z'"], }); testDates({ - name: '24 hour time with timezone name - "MST"', - locales: ['ar-SA', 'bn-BD', 'bn-IN', 'en-US'], - expected: { - year: 2020, - month: 10, - day: 6, - hour: 17, - minute: 41, - second: 28, - offset: -420, - }, - formats: ["yyyy-MM-dd'T'HH:mm:ss 'MST'"], + name: '24 hour time with timezone name - "MST"', + locales: ['ar-SA', 'bn-BD', 'bn-IN', 'en-US'], + expected: { + year: 2020, + month: 10, + day: 6, + hour: 17, + minute: 41, + second: 28, + offset: -420, + }, + formats: ["yyyy-MM-dd'T'HH:mm:ss 'MST'"], }); testDates({ - name: '24 hour time with long timezone name - "Eastern Daylight Time"', - locales: ['ar-SA', 'bn-BD', 'bn-IN', 'en-US'], - expected: { - year: 2020, - month: 10, - day: 6, - hour: 17, - minute: 41, - second: 28, - offset: -240, - }, - formats: ["yyyy-MM-dd'T'HH:mm:ss 'Eastern Daylight Time'"], + name: '24 hour time with long timezone name - "Eastern Daylight Time"', + locales: ['ar-SA', 'bn-BD', 'bn-IN', 'en-US'], + expected: { + year: 2020, + month: 10, + day: 6, + hour: 17, + minute: 41, + second: 28, + offset: -240, + }, + formats: ["yyyy-MM-dd'T'HH:mm:ss 'Eastern Daylight Time'"], }); testDates({ - name: '24 hour time with GMT hours:minutes', - locales: ['en-US'], - expected: { - year: 2020, - month: 10, - day: 6, - hour: 17, - minute: 41, - second: 28, - offset: 180, - }, - formats: ["yyyy-MM-dd'T'HH:mm:ss 'GMT+03:00'"], + name: '24 hour time with GMT hours:minutes', + locales: ['en-US'], + expected: { + year: 2020, + month: 10, + day: 6, + hour: 17, + minute: 41, + second: 28, + offset: 180, + }, + formats: ["yyyy-MM-dd'T'HH:mm:ss 'GMT+03:00'"], }); testDates({ - name: '24 hour time with GMT hours:minutes and tz name', - locales: ['en-US'], - expected: { - year: 2020, - month: 10, - day: 6, - hour: 17, - minute: 41, - second: 28, - offset: 180, - }, - formats: ["yyyy-MM-dd'T'HH:mm:ss 'GMT+03:00 (Eastern Daylight Time)'"], + name: '24 hour time with GMT hours:minutes and tz name', + locales: ['en-US'], + expected: { + year: 2020, + month: 10, + day: 6, + hour: 17, + minute: 41, + second: 28, + offset: 180, + }, + formats: ["yyyy-MM-dd'T'HH:mm:ss 'GMT+03:00 (Eastern Daylight Time)'"], }); testDates({ - name: '24 hour time with GMT hours', - locales: ['en-US'], - expected: { - year: 2020, - month: 10, - day: 6, - hour: 17, - minute: 41, - second: 28, - offset: -540, - }, - formats: ["yyyy-MM-dd'T'HH:mm:ss 'GMT-9'"], + name: '24 hour time with GMT hours', + locales: ['en-US'], + expected: { + year: 2020, + month: 10, + day: 6, + hour: 17, + minute: 41, + second: 28, + offset: -540, + }, + formats: ["yyyy-MM-dd'T'HH:mm:ss 'GMT-9'"], }); testDates({ - name: '24 hour time with zone -hours:minutes', - locales: ['en-US'], - expected: { - year: 2020, - month: 10, - day: 6, - hour: 17, - minute: 41, - second: 28, - offset: -540, - }, - formats: ["yyyy-MM-dd'T'HH:mm:ss'-09:00'"], + name: '24 hour time with zone -hours:minutes', + locales: ['en-US'], + expected: { + year: 2020, + month: 10, + day: 6, + hour: 17, + minute: 41, + second: 28, + offset: -540, + }, + formats: ["yyyy-MM-dd'T'HH:mm:ss'-09:00'"], }); testDates({ - name: '24 hour time with zone +hoursminutes', - locales: ['en-US'], - expected: { - year: 2020, - month: 10, - day: 6, - hour: 17, - minute: 41, - second: 28, - offset: 540, - }, - formats: ["yyyy-MM-dd'T'HH:mm:ss'+0900'"], + name: '24 hour time with zone +hoursminutes', + locales: ['en-US'], + expected: { + year: 2020, + month: 10, + day: 6, + hour: 17, + minute: 41, + second: 28, + offset: 540, + }, + formats: ["yyyy-MM-dd'T'HH:mm:ss'+0900'"], }); describe('24 hour time - invalid', () => { - it('should handle invalid date: "Foobarbaz at 23:59:59"', () => { - const actual = parser.attempt('Foobarbaz at 23:59:59'); - expect(actual.invalid).toBe('Unable to parse Foobarbaz at 23:59:59'); - }); + it('should handle invalid date: "Foobarbaz at 23:59:59"', () => { + const actual = parser.attempt('Foobarbaz at 23:59:59'); + expect(actual.invalid).toBe('Unable to parse Foobarbaz at 23:59:59'); + }); }); diff --git a/src/formats/time24Hours/time24Hours.ts b/src/formats/time24Hours/time24Hours.ts index 6b4aaa3..9aa2f56 100644 --- a/src/formats/time24Hours/time24Hours.ts +++ b/src/formats/time24Hours/time24Hours.ts @@ -1,39 +1,47 @@ -const LocaleHelper = require('../../LocaleHelper/LocaleHelper.js'); -const Format = require('../../Format/Format.js'); -const timezoneNames = require('../../data/timezoneNames.js'); +import Format from '../../Format/Format'; +import LocaleHelper from '../../LocaleHelper/LocaleHelper'; +import timezoneNames from '../../data/timezoneNames'; // lots of 24h time such as "23:59", "T23:59:59+0700", "23:59:59 GMT-05:00", "23:59:59 CST", "T23:59:59Z" const time24Hours = new Format({ - /* prettier-ignore */ - // $1 $2 $3 $4 $5 $6 $7 - template: '^(.*?)(?:_GAP_)?(?:at|on|T|)(?:_GAP_)?(_H24_)\\:(_MIN_)(?:\\:(_SEC_)(?:[\\.,](_MS_))?)?(?:_GAP_)?(?:GMT)?(?:_GAP_)?(_OFFSET_)?(?:_GAP_)?(_ZONE_)?$', - handler: function (matches, locale) { - let [, dateExpr, hour, minute, second, millisecond, offset, zone] = matches; - let result = {}; - if (dateExpr) { - result = this.parser.attempt(dateExpr, locale); - if (result.invalid) { - return result; - } - } - result.hour = hour; - result.minute = minute; - if (second) { - result.second = second; - } - if (millisecond && millisecond.length > 3) { - result.millisecond = millisecond.slice(0, 3); - } else if (millisecond) { - result.millisecond = millisecond; - } - if (zone && !offset && zone in timezoneNames) { - result.offset = timezoneNames[zone]; - } else if (offset) { - const locHelper = LocaleHelper.factory(locale); - result.offset = locHelper.offsetToMinutes(offset); - } - return result; - }, + /* prettier-ignore */ + // $1 $2 $3 $4 $5 $6 $7 + template: '^(.*?)(?:_GAP_)?(?:at|on|T|)(?:_GAP_)?(_H24_)\\:(_MIN_)(?:\\:(_SEC_)(?:[\\.,](_MS_))?)?(?:_GAP_)?(?:GMT)?(?:_GAP_)?(_OFFSET_)?(?:_GAP_)?(_ZONE_)?$', + handler: function (matches, locale) { + let [, dateExpr, hour, minute, second, millisecond, offset, zone] = matches; + let result: { + invalid?: boolean; + hour: number; + minute: number; + second?: number; + millisecond?: number; + offset?: number; + } = { + hour, + minute, + }; + if (dateExpr) { + result = this.parser.attempt(dateExpr, locale); + if (result.invalid) { + return result; + } + } + if (second) { + result.second = second; + } + if (millisecond && millisecond.length > 3) { + result.millisecond = millisecond.slice(0, 3); + } else if (millisecond) { + result.millisecond = millisecond; + } + if (zone && !offset && zone in timezoneNames) { + result.offset = timezoneNames[zone]; + } else if (offset) { + const locHelper = LocaleHelper.factory(locale); + result.offset = locHelper.offsetToMinutes(offset); + } + return result; + }, }); -module.exports = time24Hours; +export default time24Hours; diff --git a/src/formats/today/today.spec.ts b/src/formats/today/today.spec.ts index b521e03..e9df4de 100644 --- a/src/formats/today/today.spec.ts +++ b/src/formats/today/today.spec.ts @@ -1,49 +1,50 @@ -const Format = require('../../Format/Format.js'); -const parser = require('../../../index.js'); +import { beforeAll, describe, expect, it } from 'vitest'; +import parser from '../../../index'; +import Format from '../../Format/Format'; describe('now, today, yesterday and tomorrow', () => { - beforeAll(() => { - Format.prototype.now = () => - new Date(Date.UTC(2019, 7 /* august */, 31, 23, 59, 59, 999)); - }); - it('should handle "now"', () => { - const actual = parser.attempt('now'); - const expected = { - year: 2019, - month: 8, - day: 31, - hour: 23, - minute: 59, - second: 59, - millisecond: 999, - }; - expect(actual).toEqual(expected); - }); - it('should handle "today"', () => { - const actual = parser.attempt('today'); - const expected = { - year: 2019, - month: 8, - day: 31, - }; - expect(actual).toEqual(expected); - }); - it('should handle "tomorrow"', () => { - const actual = parser.attempt('tomorrow'); - const expected = { - year: 2019, - month: 9, - day: 1, - }; - expect(actual).toEqual(expected); - }); - it('should handle "yesterday"', () => { - const actual = parser.attempt('yesterday'); - const expected = { - year: 2019, - month: 8, - day: 30, - }; - expect(actual).toEqual(expected); - }); + beforeAll(() => { + Format.prototype.now = () => + new Date(Date.UTC(2019, 7 /* august */, 31, 23, 59, 59, 999)); + }); + it('should handle "now"', () => { + const actual = parser.attempt('now'); + const expected = { + year: 2019, + month: 8, + day: 31, + hour: 23, + minute: 59, + second: 59, + millisecond: 999, + }; + expect(actual).toEqual(expected); + }); + it('should handle "today"', () => { + const actual = parser.attempt('today'); + const expected = { + year: 2019, + month: 8, + day: 31, + }; + expect(actual).toEqual(expected); + }); + it('should handle "tomorrow"', () => { + const actual = parser.attempt('tomorrow'); + const expected = { + year: 2019, + month: 9, + day: 1, + }; + expect(actual).toEqual(expected); + }); + it('should handle "yesterday"', () => { + const actual = parser.attempt('yesterday'); + const expected = { + year: 2019, + month: 8, + day: 30, + }; + expect(actual).toEqual(expected); + }); }); diff --git a/src/formats/today/today.ts b/src/formats/today/today.ts index 3eb8ae5..c4e450e 100644 --- a/src/formats/today/today.ts +++ b/src/formats/today/today.ts @@ -1,33 +1,41 @@ -const Format = require('../../Format/Format.js'); +import Format from '../../Format/Format'; const today = new Format({ - matcher: /^(now|today|tomorrow|yesterday)/i, - handler: function (match) { - const now = this.now(); - const keyword = match[1].toLowerCase(); - switch (keyword) { - case 'tomorrow': - // JavaScript automatically handles flowing from one day to the next - // For example, 31 jan 2020 will auto convert to 1 feb 2020 - now.setUTCDate(now.getUTCDate() + 1); - break; - case 'yesterday': - now.setUTCDate(now.getUTCDate() - 1); - break; - } - const result = { - year: now.getUTCFullYear(), - month: now.getUTCMonth() + 1, - day: now.getUTCDate(), - }; - if (keyword === 'now') { - result.hour = now.getUTCHours(); - result.minute = now.getUTCMinutes(); - result.second = now.getUTCSeconds(); - result.millisecond = now.getUTCMilliseconds(); - } - return result; - }, + matcher: /^(now|today|tomorrow|yesterday)/i, + handler: function (match) { + const now = this.now(); + const keyword = match[1].toLowerCase(); + switch (keyword) { + case 'tomorrow': + // JavaScript automatically handles flowing from one day to the next + // For example, 31 jan 2020 will auto convert to 1 feb 2020 + now.setUTCDate(now.getUTCDate() + 1); + break; + case 'yesterday': + now.setUTCDate(now.getUTCDate() - 1); + break; + } + const result: { + year: number; + month: number; + day: number; + hour?: number; + minute?: number; + second?: number; + millisecond?: number; + } = { + year: now.getUTCFullYear(), + month: now.getUTCMonth() + 1, + day: now.getUTCDate(), + }; + if (keyword === 'now') { + result.hour = now.getUTCHours(); + result.minute = now.getUTCMinutes(); + result.second = now.getUTCSeconds(); + result.millisecond = now.getUTCMilliseconds(); + } + return result; + }, }); -module.exports = today; +export default today; diff --git a/src/formats/twitter/twitter.spec.ts b/src/formats/twitter/twitter.spec.ts index aa7916c..fd5c1ec 100644 --- a/src/formats/twitter/twitter.spec.ts +++ b/src/formats/twitter/twitter.spec.ts @@ -1,30 +1,30 @@ -const parser = require('../../../index.js'); +import parser from '../../../index'; describe('twitter time', () => { - it('should handle "Fri Apr 09 12:53:54 +0000 2010"', () => { - const actual = parser.attempt('Fri Apr 09 12:53:54 +0000 2010'); - const expected = { - month: 4, - day: 9, - hour: 12, - minute: 53, - second: 54, - year: 2010, - offset: 0, - }; - expect(actual).toEqual(expected); - }); - it('should handle "Fri Apr 16 22:53:54 -0130 2017"', () => { - const actual = parser.attempt('Fri Apr 16 22:53:54 -0130 2017'); - const expected = { - month: 4, - day: 16, - hour: 22, - minute: 53, - second: 54, - year: 2017, - offset: -90, - }; - expect(actual).toEqual(expected); - }); + it('should handle "Fri Apr 09 12:53:54 +0000 2010"', () => { + const actual = parser.attempt('Fri Apr 09 12:53:54 +0000 2010'); + const expected = { + month: 4, + day: 9, + hour: 12, + minute: 53, + second: 54, + year: 2010, + offset: 0, + }; + expect(actual).toEqual(expected); + }); + it('should handle "Fri Apr 16 22:53:54 -0130 2017"', () => { + const actual = parser.attempt('Fri Apr 16 22:53:54 -0130 2017'); + const expected = { + month: 4, + day: 16, + hour: 22, + minute: 53, + second: 54, + year: 2017, + offset: -90, + }; + expect(actual).toEqual(expected); + }); }); diff --git a/src/formats/twitter/twitter.ts b/src/formats/twitter/twitter.ts index 105cf1e..d8118e7 100644 --- a/src/formats/twitter/twitter.ts +++ b/src/formats/twitter/twitter.ts @@ -1,11 +1,11 @@ -const Format = require('../../Format/Format.js'); +import Format from '../../Format/Format'; // example: "Fri Apr 09 12:53:54 +0000 2010" const twitter = new Format({ - /* prettier-ignore */ - // $1 $2 $3 $4 $5 $6 $7 - template: '^(?:_DAYNAME_) (_MONTHNAME_) (_DAY_) (_H24_):(_MIN_):(_SEC_) (_OFFSET_) (_YEAR_)$', - units: ['month', 'day', 'hour', 'minute', 'second', 'offset', 'year'], + /* prettier-ignore */ + // $1 $2 $3 $4 $5 $6 $7 + template: '^(?:_DAYNAME_) (_MONTHNAME_) (_DAY_) (_H24_):(_MIN_):(_SEC_) (_OFFSET_) (_YEAR_)$', + units: ['month', 'day', 'hour', 'minute', 'second', 'offset', 'year'], }); -module.exports = twitter; +export default twitter; diff --git a/src/formats/yearMonthDay/yearMonthDay.spec.ts b/src/formats/yearMonthDay/yearMonthDay.spec.ts index a76ef57..40c3981 100644 --- a/src/formats/yearMonthDay/yearMonthDay.spec.ts +++ b/src/formats/yearMonthDay/yearMonthDay.spec.ts @@ -1,8 +1,8 @@ -const testDates = require('../../../test-fixtures/testDates.js'); +import testDates from '../../../test-fixtures/testDates'; testDates({ - name: 'year month day', - expected: { year: 2016, month: 9, day: 24 }, - locales: ['en-US'], - formats: ['yyyy-MM-dd', 'yyyy-M-dd', 'yyyy-MM-d', 'yyyy-M-d', 'yyyyMMdd'], + name: 'year month day', + expected: { year: 2016, month: 9, day: 24 }, + locales: ['en-US'], + formats: ['yyyy-MM-dd', 'yyyy-M-dd', 'yyyy-MM-d', 'yyyy-M-d', 'yyyyMMdd'], }); diff --git a/src/formats/yearMonthDay/yearMonthDay.ts b/src/formats/yearMonthDay/yearMonthDay.ts index b28809c..81806a1 100644 --- a/src/formats/yearMonthDay/yearMonthDay.ts +++ b/src/formats/yearMonthDay/yearMonthDay.ts @@ -1,10 +1,10 @@ -const Format = require('../../Format/Format.js'); +import Format from '../../Format/Format'; const yearMonthDay = new Format({ - /* prettier-ignore */ - // $1 $2 $3 $4 - template: "^(_YEAR_)(-?)(_MONTH_)\\2(_DAY_) ?(_DAYNAME_)?\\.?$", - units: ['year', null, 'month', 'day'], + /* prettier-ignore */ + // $1 $2 $3 $4 + template: "^(_YEAR_)(-?)(_MONTH_)\\2(_DAY_) ?(_DAYNAME_)?\\.?$", + units: ['year', null, 'month', 'day'], }); -module.exports = yearMonthDay; +export default yearMonthDay; diff --git a/src/formats/yearMonthDayWithDots/yearMonthDayWithDots.ts b/src/formats/yearMonthDayWithDots/yearMonthDayWithDots.ts index d6327f2..ccf11df 100644 --- a/src/formats/yearMonthDayWithDots/yearMonthDayWithDots.ts +++ b/src/formats/yearMonthDayWithDots/yearMonthDayWithDots.ts @@ -1,11 +1,11 @@ -const Format = require('../../Format/Format.js'); +import Format from '../../Format/Format'; const yearMonthDayWithDots = new Format({ - /* prettier-ignore */ - // $1 $2 $3 - // template: "^(_YEAR_)\\.\\s*(_MONTH_)\\.\\s*(_DAY_)\\.?\\s*(_DAYNAME_)?\\.?$", - template: "^(_YEAR_)\\. (_MONTH_)\\. (_DAY_)\\.", - units: ['year', 'month', 'day'], + /* prettier-ignore */ + // $1 $2 $3 + // template: "^(_YEAR_)\\.\\s*(_MONTH_)\\.\\s*(_DAY_)\\.?\\s*(_DAYNAME_)?\\.?$", + template: "^(_YEAR_)\\. (_MONTH_)\\. (_DAY_)\\.", + units: ['year', 'month', 'day'], }); -module.exports = yearMonthDayWithDots; +export default yearMonthDayWithDots; diff --git a/src/formats/yearMonthDayWithSlashes/yearMonthDayWithSlashes.ts b/src/formats/yearMonthDayWithSlashes/yearMonthDayWithSlashes.ts index 8b133e0..a49d25f 100644 --- a/src/formats/yearMonthDayWithSlashes/yearMonthDayWithSlashes.ts +++ b/src/formats/yearMonthDayWithSlashes/yearMonthDayWithSlashes.ts @@ -1,10 +1,10 @@ -const Format = require('../../Format/Format.js'); +import Format from '../../Format/Format'; const yearMonthDayWithSlashes = new Format({ - /* prettier-ignore */ - // $1 $2 $3 - template: "^(_YEAR_)\\/(_MONTH_)\\/(_DAY_) ?(_DAYNAME_)?\\.?$", - units: ['year', 'month', 'day'], + /* prettier-ignore */ + // $1 $2 $3 + template: "^(_YEAR_)\\/(_MONTH_)\\/(_DAY_) ?(_DAYNAME_)?\\.?$", + units: ['year', 'month', 'day'], }); -module.exports = yearMonthDayWithSlashes; +export default yearMonthDayWithSlashes; diff --git a/src/formats/yearMonthnameDay/yearMonthnameDay.ts b/src/formats/yearMonthnameDay/yearMonthnameDay.ts index 25826ef..30d41a6 100644 --- a/src/formats/yearMonthnameDay/yearMonthnameDay.ts +++ b/src/formats/yearMonthnameDay/yearMonthnameDay.ts @@ -1,10 +1,10 @@ -const Format = require('../../Format/Format.js'); +import Format from '../../Format/Format'; const yearMonthnameDay = new Format({ - /* prettier-ignore */ - // $1 $2 $3 $4 - template: "^(_YEAR_)(_GAP_)(_MONTHNAME_)\\2(_DAY_)(?:_GAP_)?(?:_DAYNAME_)?\\.?$", - units: ['year', null, 'month', 'day'], + /* prettier-ignore */ + // $1 $2 $3 $4 + template: "^(_YEAR_)(_GAP_)(_MONTHNAME_)\\2(_DAY_)(?:_GAP_)?(?:_DAYNAME_)?\\.?$", + units: ['year', null, 'month', 'day'], }); -module.exports = yearMonthnameDay; +export default yearMonthnameDay; diff --git a/src/fromAny/fromAny.spec.ts b/src/fromAny/fromAny.spec.ts index 3e188b6..998dbae 100644 --- a/src/fromAny/fromAny.spec.ts +++ b/src/fromAny/fromAny.spec.ts @@ -1,26 +1,27 @@ -const fromAny = require('./fromAny.js'); +import { describe, expect, it, vi } from 'vitest'; +import fromAny from './fromAny'; describe('fromAny', () => { - it('should return Date objects as is', () => { - const spy = jest.fn(); - const fromFn = fromAny(spy); - const now = new Date(); - expect(fromFn(now)).toBe(now); - expect(spy).not.toHaveBeenCalled(); - }); - it('should return milliseconds as Date object', () => { - const spy = jest.fn(); - const fromFn = fromAny(spy); - const now = new Date(); - const actual = fromFn(now.getTime()); - expect(actual).toBeInstanceOf(Date); - expect(+actual).toBe(+now); - expect(spy).not.toHaveBeenCalled(); - }); - it('should otherwise invoke fromString', () => { - const spy = jest.fn(); - const fromFn = fromAny(spy); - fromFn('my string', 'en'); - expect(spy).toHaveBeenCalledWith('my string', 'en'); - }); + it('should return Date objects as is', () => { + const spy = vi.fn(); + const fromFn = fromAny(spy); + const now = new Date(); + expect(fromFn(now)).toBe(now); + expect(spy).not.toHaveBeenCalled(); + }); + it('should return milliseconds as Date object', () => { + const spy = vi.fn(); + const fromFn = fromAny(spy); + const now = new Date(); + const actual = fromFn(now.getTime()); + expect(actual).toBeInstanceOf(Date); + expect(+actual).toBe(+now); + expect(spy).not.toHaveBeenCalled(); + }); + it('should otherwise invoke fromString', () => { + const spy = vi.fn(); + const fromFn = fromAny(spy); + fromFn('my string', 'en'); + expect(spy).toHaveBeenCalledWith('my string', 'en'); + }); }); diff --git a/src/fromAny/fromAny.ts b/src/fromAny/fromAny.ts index 1f8277d..e2604d8 100644 --- a/src/fromAny/fromAny.ts +++ b/src/fromAny/fromAny.ts @@ -1,13 +1,11 @@ -function fromAny(fromString) { - return function fromAny(any, locale) { - if (any instanceof Date) { - return any; - } - if (typeof any === 'number') { - return new Date(any); - } - return fromString(any, locale); - }; +export default function fromAny(fromString) { + return function fromAny(any, locale) { + if (any instanceof Date) { + return any; + } + if (typeof any === 'number') { + return new Date(any); + } + return fromString(any, locale); + }; } - -module.exports = fromAny; diff --git a/src/fromString/fromString.spec.ts b/src/fromString/fromString.spec.ts index 51e9279..ad12a77 100644 --- a/src/fromString/fromString.spec.ts +++ b/src/fromString/fromString.spec.ts @@ -1,109 +1,110 @@ -const fromString = require('./fromString.js'); +import { describe, expect, it, vi } from 'vitest'; +import fromString from './fromString'; const now = new Date(); describe('fromString', () => { - it('should return invalid dates', () => { - const invalid = { invalid: 'foo' }; - const parser = { attempt: jest.fn(() => invalid) }; - const fromFn = fromString(parser); - expect(fromFn()).toBe(invalid); - }); - it('should use current month and day', () => { - const result = { year: 2020 }; - const parser = { attempt: jest.fn(() => result) }; - const fromFn = fromString(parser); - const jan1 = new Date( - Date.UTC(2020, now.getUTCMonth(), now.getUTCDate(), 0, 0, 0, 0) - ); - expect(fromFn()).toEqual(jan1); - }); - it('should use current month', () => { - const result = { year: 2020, month: 5 }; - const parser = { attempt: jest.fn(() => result) }; - const fromFn = fromString(parser); - const may1 = new Date(Date.UTC(2020, 4, now.getUTCDate(), 0, 0, 0, 0)); - expect(fromFn()).toEqual(may1); - }); - it('should reset all but day', () => { - const result = { year: 2020, month: 4, day: 15 }; - const parser = { attempt: jest.fn(() => result) }; - const fromFn = fromString(parser); - const apr15 = new Date(Date.UTC(2020, 3, 15, 0, 0, 0, 0)); - expect(fromFn()).toEqual(apr15); - }); - it('should reset all but hour', () => { - const result = { year: 2020, month: 2, day: 28, hour: 23 }; - const parser = { attempt: jest.fn(() => result) }; - const fromFn = fromString(parser); - const feb28 = new Date(Date.UTC(2020, 1, 28, 23, 0, 0, 0)); - expect(fromFn()).toEqual(feb28); - }); - it('should reset all but minute', () => { - const result = { year: 2020, month: 12, day: 4, hour: 23, minute: 14 }; - const parser = { attempt: jest.fn(() => result) }; - const fromFn = fromString(parser); - const dec4 = new Date(Date.UTC(2020, 11, 4, 23, 14, 0, 0)); - expect(fromFn()).toEqual(dec4); - }); - it('should reset all but second', () => { - const result = { - year: 2020, - month: 9, - day: 8, - hour: 23, - minute: 14, - second: 59, - }; - const parser = { attempt: jest.fn(() => result) }; - const fromFn = fromString(parser); - const sep8 = new Date(Date.UTC(2020, 8, 8, 23, 14, 59, 0)); - expect(fromFn()).toEqual(sep8); - }); - it('should reset all but millisecond', () => { - const result = { - year: 2020, - month: 7, - day: 4, - hour: 23, - minute: 14, - second: 59, - millisecond: 101, - }; - const parser = { attempt: jest.fn(() => result) }; - const fromFn = fromString(parser); - const jul4 = new Date(Date.UTC(2020, 6, 4, 23, 14, 59, 101)); - expect(fromFn()).toEqual(jul4); - }); - it('should handle negative offset', () => { - const result = { - year: 2020, - month: 6, - day: 9, - hour: 19, - minute: 14, - second: 59, - millisecond: 101, - offset: -60, - }; - const parser = { attempt: jest.fn(() => result) }; - const fromFn = fromString(parser); - const jun6 = new Date(Date.UTC(2020, 5, 9, 20, 14, 59, 101)); - expect(fromFn()).toEqual(jun6); - }); - it('should handle positive offset', () => { - const result = { - year: 2020, - month: 10, - day: 31, - hour: 20, - minute: 59, - second: 59, - millisecond: 101, - offset: 45, - }; - const parser = { attempt: jest.fn(() => result) }; - const fromFn = fromString(parser); - const oct31 = new Date(Date.UTC(2020, 9, 31, 20, 14, 59, 101)); - expect(fromFn()).toEqual(oct31); - }); + it('should return invalid dates', () => { + const invalid = { invalid: 'foo' }; + const parser = { attempt: vi.fn(() => invalid) }; + const fromFn = fromString(parser); + expect(fromFn()).toBe(invalid); + }); + it('should use current month and day', () => { + const result = { year: 2020 }; + const parser = { attempt: vi.fn(() => result) }; + const fromFn = fromString(parser); + const jan1 = new Date( + Date.UTC(2020, now.getUTCMonth(), now.getUTCDate(), 0, 0, 0, 0) + ); + expect(fromFn()).toEqual(jan1); + }); + it('should use current month', () => { + const result = { year: 2020, month: 5 }; + const parser = { attempt: jest.fn(() => result) }; + const fromFn = fromString(parser); + const may1 = new Date(Date.UTC(2020, 4, now.getUTCDate(), 0, 0, 0, 0)); + expect(fromFn()).toEqual(may1); + }); + it('should reset all but day', () => { + const result = { year: 2020, month: 4, day: 15 }; + const parser = { attempt: jest.fn(() => result) }; + const fromFn = fromString(parser); + const apr15 = new Date(Date.UTC(2020, 3, 15, 0, 0, 0, 0)); + expect(fromFn()).toEqual(apr15); + }); + it('should reset all but hour', () => { + const result = { year: 2020, month: 2, day: 28, hour: 23 }; + const parser = { attempt: jest.fn(() => result) }; + const fromFn = fromString(parser); + const feb28 = new Date(Date.UTC(2020, 1, 28, 23, 0, 0, 0)); + expect(fromFn()).toEqual(feb28); + }); + it('should reset all but minute', () => { + const result = { year: 2020, month: 12, day: 4, hour: 23, minute: 14 }; + const parser = { attempt: jest.fn(() => result) }; + const fromFn = fromString(parser); + const dec4 = new Date(Date.UTC(2020, 11, 4, 23, 14, 0, 0)); + expect(fromFn()).toEqual(dec4); + }); + it('should reset all but second', () => { + const result = { + year: 2020, + month: 9, + day: 8, + hour: 23, + minute: 14, + second: 59, + }; + const parser = { attempt: jest.fn(() => result) }; + const fromFn = fromString(parser); + const sep8 = new Date(Date.UTC(2020, 8, 8, 23, 14, 59, 0)); + expect(fromFn()).toEqual(sep8); + }); + it('should reset all but millisecond', () => { + const result = { + year: 2020, + month: 7, + day: 4, + hour: 23, + minute: 14, + second: 59, + millisecond: 101, + }; + const parser = { attempt: jest.fn(() => result) }; + const fromFn = fromString(parser); + const jul4 = new Date(Date.UTC(2020, 6, 4, 23, 14, 59, 101)); + expect(fromFn()).toEqual(jul4); + }); + it('should handle negative offset', () => { + const result = { + year: 2020, + month: 6, + day: 9, + hour: 19, + minute: 14, + second: 59, + millisecond: 101, + offset: -60, + }; + const parser = { attempt: jest.fn(() => result) }; + const fromFn = fromString(parser); + const jun6 = new Date(Date.UTC(2020, 5, 9, 20, 14, 59, 101)); + expect(fromFn()).toEqual(jun6); + }); + it('should handle positive offset', () => { + const result = { + year: 2020, + month: 10, + day: 31, + hour: 20, + minute: 59, + second: 59, + millisecond: 101, + offset: 45, + }; + const parser = { attempt: jest.fn(() => result) }; + const fromFn = fromString(parser); + const oct31 = new Date(Date.UTC(2020, 9, 31, 20, 14, 59, 101)); + expect(fromFn()).toEqual(oct31); + }); }); diff --git a/src/fromString/fromString.ts b/src/fromString/fromString.ts index c0223b9..d0aa5dd 100644 --- a/src/fromString/fromString.ts +++ b/src/fromString/fromString.ts @@ -1,57 +1,52 @@ -const defaultLocale = require('../data/defaultLocale.js'); -const removeFillerWords = require('../removeFillerWords/removeFillerWords.js'); +export default function fromString(parser, defaultLocale?: string) { + return function fromStringFunction(string, locale = defaultLocale) { + // let effectiveLocale = locale; + // const bounds = { + // lower: '0001-01-01T00:00:00', + // upper: '9999-12-31T23:59:59', + // inclusive: true, + // strict: false, + // }; + // if (typeof locale === 'object') { + // effectiveLocale = locale.locale || defaultLocale; + // Object.assign(bounds, locale.bounds || {}); + // } + // string = removeFillerWords(String(string), locale).trim(); + // const matches = this.getMatches(string, locale); + // if (matches) { + // const dt = this.toDateTime(matches, locale); + // const dtDate = this.dt + // if (dtDate instanceof Date && !this.isInRange(dtDate, bounds) && bounds.strict) { + // const inclusive = bounds.inclusive ? 'inclusive' : 'not inclusive'; + // return { invalid: `Date not in range ${bounds.lower} to ${bounds.upper} ${inclusive}`, bounds }; + // } + // return dt || null; + // } + // return null; -function fromString(parser, defaultLocale) { - return function fromStringFunction(string, locale = defaultLocale) { - // let effectiveLocale = locale; - // const bounds = { - // lower: '0001-01-01T00:00:00', - // upper: '9999-12-31T23:59:59', - // inclusive: true, - // strict: false, - // }; - // if (typeof locale === 'object') { - // effectiveLocale = locale.locale || defaultLocale; - // Object.assign(bounds, locale.bounds || {}); - // } - // string = removeFillerWords(String(string), locale).trim(); - // const matches = this.getMatches(string, locale); - // if (matches) { - // const dt = this.toDateTime(matches, locale); - // const dtDate = this.dt - // if (dtDate instanceof Date && !this.isInRange(dtDate, bounds) && bounds.strict) { - // const inclusive = bounds.inclusive ? 'inclusive' : 'not inclusive'; - // return { invalid: `Date not in range ${bounds.lower} to ${bounds.upper} ${inclusive}`, bounds }; - // } - // return dt || null; - // } - // return null; - - const parsed = parser.attempt(string, locale); - if (parsed.invalid) { - return parsed; - } - const date = new Date(); - // default to current year, month and day - if (typeof parsed.year === 'number') { - date.setUTCFullYear(parsed.year); - } - if (typeof parsed.month === 'number') { - date.setUTCMonth(parsed.month - 1); - } - if (typeof parsed.day === 'number') { - date.setUTCDate(parsed.day); - } - // default to first unit for time components - date.setUTCHours(parsed.hour || 0); - date.setUTCMinutes(parsed.minute || 0); - date.setUTCSeconds(parsed.second || 0); - date.setUTCMilliseconds(parsed.millisecond || 0); - if (typeof parsed.offset === 'number') { - return new Date(date - parsed.offset * 60 * 1000); - } - return date; - }; + const parsed = parser.attempt(string, locale); + if (parsed.invalid) { + return parsed; + } + const date = new Date(); + // default to current year, month and day + if (typeof parsed.year === 'number') { + date.setUTCFullYear(parsed.year); + } + if (typeof parsed.month === 'number') { + date.setUTCMonth(parsed.month - 1); + } + if (typeof parsed.day === 'number') { + date.setUTCDate(parsed.day); + } + // default to first unit for time components + date.setUTCHours(parsed.hour || 0); + date.setUTCMinutes(parsed.minute || 0); + date.setUTCSeconds(parsed.second || 0); + date.setUTCMilliseconds(parsed.millisecond || 0); + if (typeof parsed.offset === 'number') { + return new Date(date.valueOf() - parsed.offset * 60 * 1000); + } + return date; + }; } - -module.exports = fromString; diff --git a/src/normalizeLocale/normalizeLocale.spec.ts b/src/normalizeLocale/normalizeLocale.spec.ts index 7a5e6a6..94e568a 100644 --- a/src/normalizeLocale/normalizeLocale.spec.ts +++ b/src/normalizeLocale/normalizeLocale.spec.ts @@ -1,24 +1,25 @@ -const normalizeLocale = require('./normalizeLocale.js'); +import { describe, expect, it } from 'vitest'; +import normalizeLocale from './normalizeLocale'; describe('normalizeLocale', () => { - it('should ignore .UTF-8', () => { - const normalized = normalizeLocale('en-US.UTF-8'); - expect(normalized).toEqual('en-US'); - }); - it('should ignore .utf-8', () => { - const normalized = normalizeLocale('en-US.utf-8'); - expect(normalized).toEqual('en-US'); - }); - it('should ignore :utf-8', () => { - const normalized = normalizeLocale('en-US:utf-8'); - expect(normalized).toEqual('en-US'); - }); - it('should convert underscores', () => { - const normalized = normalizeLocale('en_US'); - expect(normalized).toEqual('en-US'); - }); - it('should fall back to en-US on invalid locales', () => { - const normalized = normalizeLocale('foobar 9000'); - expect(normalized).toEqual('en-US'); - }); + it('should ignore .UTF-8', () => { + const normalized = normalizeLocale('en-US.UTF-8'); + expect(normalized).toEqual('en-US'); + }); + it('should ignore .utf-8', () => { + const normalized = normalizeLocale('en-US.utf-8'); + expect(normalized).toEqual('en-US'); + }); + it('should ignore :utf-8', () => { + const normalized = normalizeLocale('en-US:utf-8'); + expect(normalized).toEqual('en-US'); + }); + it('should convert underscores', () => { + const normalized = normalizeLocale('en_US'); + expect(normalized).toEqual('en-US'); + }); + it('should fall back to en-US on invalid locales', () => { + const normalized = normalizeLocale('foobar 9000'); + expect(normalized).toEqual('en-US'); + }); }); diff --git a/src/normalizeLocale/normalizeLocale.ts b/src/normalizeLocale/normalizeLocale.ts index 8d6e23f..96e08ec 100644 --- a/src/normalizeLocale/normalizeLocale.ts +++ b/src/normalizeLocale/normalizeLocale.ts @@ -4,16 +4,14 @@ * @returns {String} * @see https://github.com/sindresorhus/os-locale/blob/main/index.js for similar code */ -function normalizeLocale(name) { - // some systems use underscores - name = name.replace(/_/g, '-'); - // some systems append strings like .UTF-8 - name = name.replace(/[.:][\w-]*$/, ''); - try { - return new Intl.Locale(name).baseName; - } catch (e) { - return 'en-US'; - } +export default function normalizeLocale(name) { + // some systems use underscores + name = name.replace(/_/g, '-'); + // some systems append strings like .UTF-8 + name = name.replace(/[.:][\w-]*$/, ''); + try { + return new Intl.Locale(name).baseName; + } catch (e) { + return 'en-US'; + } } - -module.exports = normalizeLocale; diff --git a/src/removeFillerWords/removeFillerWords.ts b/src/removeFillerWords/removeFillerWords.ts index 134927f..ef8cf8b 100644 --- a/src/removeFillerWords/removeFillerWords.ts +++ b/src/removeFillerWords/removeFillerWords.ts @@ -1,16 +1,14 @@ -const fillerWords = require('../data/fillerWords.js'); +import fillerWords from '../data/fillerWords'; -function removeFillerWords(dateString, locale) { - dateString = dateString.replace(/\. /g, ' '); - const twoLetterLocale = locale.slice(0, 2).toLowerCase(); - const replacers = fillerWords[twoLetterLocale]; - if (!replacers) { - return dateString; - } - for (const [find, replace] of replacers) { - dateString = dateString.replace(find, replace); - } - return dateString; +export default function removeFillerWords(dateString, locale) { + dateString = dateString.replace(/\. /g, ' '); + const twoLetterLocale = locale.slice(0, 2).toLowerCase(); + const replacers = fillerWords[twoLetterLocale]; + if (!replacers) { + return dateString; + } + for (const [find, replace] of replacers) { + dateString = dateString.replace(find, replace); + } + return dateString; } - -module.exports = removeFillerWords; diff --git a/test-fixtures/dateStyles.ts b/test-fixtures/dateStyles.ts index 2b06625..17a8392 100644 --- a/test-fixtures/dateStyles.ts +++ b/test-fixtures/dateStyles.ts @@ -1,6 +1,6 @@ -const fs = require('fs'); -const localeList = require('./localeList.js'); -const parser = require('../index.js'); +import fs from 'fs'; +import parser from '../index'; +import localeList from './localeList'; const date = new Date(); const results = [date.toJSON()]; @@ -12,60 +12,60 @@ const dayPeriods = ['narrow', 'short', 'long']; let i = 0; let found = 0; for (const locale of localeList) { - if (/^ar|he/.test(locale)) { - // skip right-to-left languages - continue; - } - const fmt = new Intl.NumberFormat(locale); - const numberSystem = fmt.resolvedOptions().numberingSystem; - for (const dateStyle of dateStyles) { - // for (const timeStyle of timeStyles) { - for (const dayPeriod of dayPeriods) { - const options = { - dateStyle, - // timeStyle, - // dayPeriod, - }; - i++; - const res = new Intl.DateTimeFormat(locale, options).format(date); - const parsed = parser.attempt(res, locale); - const ok = toMDY(parsed) === today; - found += ok ? 1 : 0; - results.push( - [ - ok ? '✅' : '❌', - // `(${toMDY(parsed)} === ${today})`, - numberSystem, - locale, - dateStyle, - /*timeStyle,*/ dayPeriod, - res, - JSON.stringify(parsed), - ].join(' > ') - ); - } - // } - } + if (/^ar|he/.test(locale)) { + // skip right-to-left languages + continue; + } + const fmt = new Intl.NumberFormat(locale); + const numberSystem = fmt.resolvedOptions().numberingSystem; + for (const dateStyle of dateStyles) { + // for (const timeStyle of timeStyles) { + for (const dayPeriod of dayPeriods) { + const options = { + dateStyle, + // timeStyle, + // dayPeriod, + }; + i++; + const res = new Intl.DateTimeFormat(locale, options).format(date); + const parsed = parser.attempt(res, locale); + const ok = toMDY(parsed) === today; + found += ok ? 1 : 0; + results.push( + [ + ok ? '✅' : '❌', + // `(${toMDY(parsed)} === ${today})`, + numberSystem, + locale, + dateStyle, + /*timeStyle,*/ dayPeriod, + res, + JSON.stringify(parsed), + ].join(' > ') + ); + } + // } + } } fs.writeFileSync( - `${__dirname}/dates.json`, - JSON.stringify(results, null, 4), - 'utf-8' + `${__dirname}/dates.json`, + JSON.stringify(results, null, 4), + 'utf-8' ); function toMDY(d) { - if (d instanceof Date) { - return [d.getFullYear(), pad(d.getMonth() + 1), pad(d.getDate())].join('-'); - } - if (d.year < 100) { - d.year += 2000; - } - return [d.year, pad(d.month), pad(d.day)].join('-'); + if (d instanceof Date) { + return [d.getFullYear(), pad(d.getMonth() + 1), pad(d.getDate())].join('-'); + } + if (d.year < 100) { + d.year += 2000; + } + return [d.year, pad(d.month), pad(d.day)].join('-'); } function pad(n) { - return (n > 9 ? '' : '0') + n; + return (n > 9 ? '' : '0') + n; } console.log(`Parsed ${found}/${i} dates ok`); diff --git a/test-fixtures/localeList.ts b/test-fixtures/localeList.ts index 406f453..16f3660 100644 --- a/test-fixtures/localeList.ts +++ b/test-fixtures/localeList.ts @@ -1,59 +1,59 @@ // locale list used for unit tests // from https://www.techonthenet.com/js/language_tags.php const localeList = [ - 'ar-SA', // Arabic (Saudi Arabia) - 'bn-BD', // Bangla (Bangladesh) - 'bn-IN', // Bangla (India) - 'cs-CZ', // Czech (Czech Republic) - 'da-DK', // Danish (Denmark) - 'de-AT', // Austrian German - 'de-CH', // "Swiss" German - 'de-DE', // Standard German (as spoken in Germany) - 'el-GR', // Modern Greek - 'en-AU', // Australian English - 'en-CA', // Canadian English - 'en-GB', // British English - 'en-IE', // Irish English - 'en-IN', // Indian English - 'en-NZ', // New Zealand English - 'en-US', // US English - 'en-ZA', // English (South Africa) - 'es-AR', // Argentine Spanish - 'es-CL', // Chilean Spanish - 'es-CO', // Colombian Spanish - 'es-ES', // Castilian Spanish (as spoken in Central-Northern Spain) - 'es-MX', // Mexican Spanish - 'es-US', // American Spanish - 'fi-FI', // Finnish (Finland) - 'fr-BE', // Belgian French - 'fr-CA', // Canadian French - 'fr-CH', // "Swiss" French - 'fr-FR', // Standard French (especially in France) - 'he-IL', // Hebrew (Israel) - 'hi-IN', // Hindi (India) - 'hu-HU', // Hungarian (Hungary) - 'id-ID', // Indonesian (Indonesia) - 'it-CH', // "Swiss" Italian - 'it-IT', // Standard Italian (as spoken in Italy) - 'jp-JP', // Japanese (Japan) - 'ko-KR', // Korean (Republic of Korea) - 'nl-BE', // Belgian Dutch - 'nl-NL', // Standard Dutch (as spoken in The Netherlands) - 'no-NO', // Norwegian (Norway) - 'pl-PL', // Polish (Poland) - 'pt-BR', // Brazilian Portuguese - 'pt-PT', // European Portuguese (as written and spoken in Portugal) - 'ro-RO', // Romanian (Romania) - 'ru-RU', // Russian (Russian Federation) - 'sk-SK', // Slovak (Slovakia) - 'sv-SE', // Swedish (Sweden) - 'ta-IN', // Indian Tamil - 'ta-LK', // Sri Lankan Tamil - 'th-TH', // Thai (Thailand) - 'tr-TR', // Turkish (Turkey) - 'zh-CN', // Mainland China, simplified characters - 'zh-HK', // Hong Kong, traditional characters - 'zh-TW', // Taiwan, traditional characters + 'ar-SA', // Arabic (Saudi Arabia) + 'bn-BD', // Bangla (Bangladesh) + 'bn-IN', // Bangla (India) + 'cs-CZ', // Czech (Czech Republic) + 'da-DK', // Danish (Denmark) + 'de-AT', // Austrian German + 'de-CH', // "Swiss" German + 'de-DE', // Standard German (as spoken in Germany) + 'el-GR', // Modern Greek + 'en-AU', // Australian English + 'en-CA', // Canadian English + 'en-GB', // British English + 'en-IE', // Irish English + 'en-IN', // Indian English + 'en-NZ', // New Zealand English + 'en-US', // US English + 'en-ZA', // English (South Africa) + 'es-AR', // Argentine Spanish + 'es-CL', // Chilean Spanish + 'es-CO', // Colombian Spanish + 'es-ES', // Castilian Spanish (as spoken in Central-Northern Spain) + 'es-MX', // Mexican Spanish + 'es-US', // American Spanish + 'fi-FI', // Finnish (Finland) + 'fr-BE', // Belgian French + 'fr-CA', // Canadian French + 'fr-CH', // "Swiss" French + 'fr-FR', // Standard French (especially in France) + 'he-IL', // Hebrew (Israel) + 'hi-IN', // Hindi (India) + 'hu-HU', // Hungarian (Hungary) + 'id-ID', // Indonesian (Indonesia) + 'it-CH', // "Swiss" Italian + 'it-IT', // Standard Italian (as spoken in Italy) + 'jp-JP', // Japanese (Japan) + 'ko-KR', // Korean (Republic of Korea) + 'nl-BE', // Belgian Dutch + 'nl-NL', // Standard Dutch (as spoken in The Netherlands) + 'no-NO', // Norwegian (Norway) + 'pl-PL', // Polish (Poland) + 'pt-BR', // Brazilian Portuguese + 'pt-PT', // European Portuguese (as written and spoken in Portugal) + 'ro-RO', // Romanian (Romania) + 'ru-RU', // Russian (Russian Federation) + 'sk-SK', // Slovak (Slovakia) + 'sv-SE', // Swedish (Sweden) + 'ta-IN', // Indian Tamil + 'ta-LK', // Sri Lankan Tamil + 'th-TH', // Thai (Thailand) + 'tr-TR', // Turkish (Turkey) + 'zh-CN', // Mainland China, simplified characters + 'zh-HK', // Hong Kong, traditional characters + 'zh-TW', // Taiwan, traditional characters ]; -module.exports = localeList; +export default localeList; diff --git a/test-fixtures/testDates.ts b/test-fixtures/testDates.ts index 8518dab..8eb21b4 100644 --- a/test-fixtures/testDates.ts +++ b/test-fixtures/testDates.ts @@ -1,24 +1,24 @@ -const { DateTime, FixedOffsetZone } = require('luxon'); -const parser = require('../index.js'); +import { DateTime, FixedOffsetZone } from 'luxon'; +import parser from '../index'; -function testDates({ name, formats, expected, locales }) { - for (const locale of locales) { - describe(`${name} (${locale})`, () => { - for (const format of formats) { - const luxonObj = { ...expected }; - if (typeof luxonObj.offset === 'number') { - luxonObj.zone = FixedOffsetZone.instance(expected.offset); - luxonObj.offset = undefined; - } - const date = DateTime.fromObject(luxonObj); - const formatted = date.toFormat(format, { locale }); - it(`${formatted} (${format})`, () => { - const actual = parser.attempt(formatted, locale); - expect(actual).toMatchObject(expected); - }); - } - }); - } -} +import { describe, expect, it } from 'vitest'; -module.exports = testDates; +export default function testDates({ name, formats, expected, locales }) { + for (const locale of locales) { + describe(`${name} (${locale})`, () => { + for (const format of formats) { + const luxonObj = { ...expected }; + if (typeof luxonObj.offset === 'number') { + luxonObj.zone = FixedOffsetZone.instance(expected.offset); + luxonObj.offset = undefined; + } + const date = DateTime.fromObject(luxonObj); + const formatted = date.toFormat(format, { locale }); + it(`${formatted} (${format})`, () => { + const actual = parser.attempt(formatted, locale); + expect(actual).toMatchObject(expected); + }); + } + }); + } +} diff --git a/test-fixtures/testParser.ts b/test-fixtures/testParser.ts index ac3f404..a629e73 100644 --- a/test-fixtures/testParser.ts +++ b/test-fixtures/testParser.ts @@ -1,16 +1,15 @@ -const parser = require('../index.js'); +import { describe, expect, it } from 'vitest'; +import parser from '../index'; -function testParser({ name, expected, locales, dates }) { - describe(name, () => { - locales.forEach(locale => { - dates.forEach(date => { - it(`should handle "${date}" (${locale})`, () => { - const actual = parser.attempt(date); - expect(actual).toEqual(expected); - }); - }); - }); - }); +export default function testParser({ name, expected, locales, dates }) { + describe(name, () => { + locales.forEach(locale => { + dates.forEach(date => { + it(`should handle "${date}" (${locale})`, () => { + const actual = parser.attempt(date); + expect(actual).toEqual(expected); + }); + }); + }); + }); } - -module.exports = testParser; diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..7a227f6 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [], + test: { + coverage: { + provider: 'istanbul', + }, + }, +});