UNPKG

tell-me-when

Version:
310 lines (282 loc) 7.9 kB
import { GrammarNode } from './util/GrammarNode' import * as EnglishGrammar from './en-US' import { ParseNode } from './util/ParseNode' import type { DateFn } from './util/DateFn' import { ParseRootNode } from './util/ParseRootNode' import * as base from './util/parse' const { group, named, oneOf, longestOf } = GrammarNode const { space, AmPmValue } = EnglishGrammar const kanjiNumberMap = new Map([ ['〇', '0'], ['一', '1'], ['二', '2'], ['三', '3'], ['四', '4'], ['五', '5'], ['六', '6'], ['七', '7'], ['八', '8'], ['九', '9'], ]) class YearNode extends EnglishGrammar.FullYearNode { constructor(public wrapped: ParseNode) { super(wrapped) } year(input: string) { const yearText = input .substring(this.from, this.to - 1) .split('') .map((char) => kanjiNumberMap.get(char) ?? char) .join('') return parseInt(yearText) } } const JapaneseYear = named( 'JapaneseFullYear', /([0-9〇一二三四五六七八九]{1,4})年/ ).parseAs(YearNode) class MonthNameNode extends EnglishGrammar.MonthNameNode { static monthMap = new Map([ ['一月', 0], ['二月', 1], ['三月', 2], ['四月', 3], ['五月', 4], ['六月', 5], ['七月', 6], ['八月', 7], ['九月', 8], ['十月', 9], ['十一月', 10], ['十二月', 11], ['睦月', 0], ['如月', 1], ['弥生', 2], ['卯月', 3], ['皐月', 4], ['水無月', 5], ['文月', 6], ['葉月', 7], ['長月', 8], ['神無月', 9], ['神在月', 9], ['霜月', 10], ['師走', 11], ]) month(input: string) { const monthText = this.substringOf(input).replace('〇', '') return MonthNameNode.monthMap.get(monthText)! } } const Month = named('JapaneseMonth', /(1[0-2]|0?[1-9])月/).parseAs( EnglishGrammar.MonthNumNode ) const MonthName = named( 'JapaneseMonthName', /((?:〇?[一二三四五六七八九]|十[一二]?)月|睦月|如月|弥生|卯月|皐月|水無月|文月|葉月|長月|神[無在]月|霜月|師走)/ ).parseAs(MonthNameNode) class DayOfMonthNode extends EnglishGrammar.DayOfMonthNumNode { constructor(public wrapped: ParseNode) { super(wrapped) } dayOfMonth(input: string) { const dayText = input.substring(this.from, this.to - 1) switch (dayText) { case '十': return 10 default: return parseInt( dayText .replace(/^十/, '一') .replace(/十$/, '〇') .replace('十', '') .split('') .map((char) => kanjiNumberMap.get(char) ?? char) .join('') ) } } } const DayOfMonth = named( 'JapaneseDayOfMonth', /([12][0-9]|3[01]|0?[1-9]|[一二][〇一二三四五六七八九]|三[〇一]|[二三]?十[一二三四五六七八九]?|〇?[一二三四五六七八九])日/ ).parseAs(DayOfMonthNode) class DateNode extends EnglishGrammar.DateNode { yearFns(input: string): DateFn[] | undefined { return this.find(YearNode)?.dateFns(input) } monthFns(input: string): DateFn[] | undefined { const month = ( this.find(MonthNameNode) || this.find(EnglishGrammar.MonthNumNode) )?.month(input) return month != null ? [['setMonth', month]] : undefined } day(input: string) { return this.find(DayOfMonthNode)?.dayOfMonth(input) } } const Date = named( 'JapaneseDate', longestOf( group( JapaneseYear, group(oneOf(Month, MonthName), DayOfMonth.maybe()).maybe() ), group(oneOf(Month, MonthName), DayOfMonth.maybe()), DayOfMonth ) ).parseAs(DateNode) class HoursNode extends EnglishGrammar.HoursNode { hours(input: string): number { const hoursText = input.substring(this.from, this.to - 1) switch (hoursText) { case '零': return 0 case '十': return 10 default: return parseInt( hoursText .replace(/^十/, '一') .replace(/十$/, '〇') .replace('十', '') .split('') .map((char) => kanjiNumberMap.get(char) ?? char) .join('') ) } } } const Hours = named( 'JapaneseHours', /(2[0-3]|[01]?[0-9]|二?十[一二三]?|十?[一二三四五六七八九]|零)時/ ).parseAs(HoursNode) class MinutesNode extends EnglishGrammar.MinutesNode { minutes(input: string) { const minutesText = input.substring(this.from, this.to - 1) switch (minutesText) { case '零': return 0 case '十': return 10 default: return parseInt( minutesText .replace(/^十/, '一') .replace(/十$/, '〇') .replace('十', '') .split('') .map((char) => kanjiNumberMap.get(char) ?? char) .join('') ) } } } const Minutes = named( 'JapaneseMinutes', /([0-5]?[0-9]|[二三四五]?十[一二三四五六七八九]?|[一二三四五六七八九]|零)分/ ).parseAs(MinutesNode) class SecondsNode extends EnglishGrammar.SecondsNode { seconds(input: string): number { const secondsText = input.substring(this.from, this.to - 1) switch (secondsText) { case '零': return 0 case '十': return 10 default: return parseInt( secondsText .replace(/^十/, '一') .replace(/十$/, '〇') .replace('十', '') .split('') .map((char) => kanjiNumberMap.get(char) ?? char) .join('') ) } } } const Seconds = named( 'JapaneseSeconds', /([0-5]?[0-9]|[二三四五]?十[一二三四五六七八九]?|[一二三四五六七八九]|零)秒/ ).parseAs(SecondsNode) class TimeNode extends EnglishGrammar.TimeNode { hours(input: string) { return this.find(HoursNode)?.hours(input) } minutes(input: string) { return this.find(MinutesNode)?.minutes(input) } seconds(input: string) { return this.find(SecondsNode)?.seconds(input) } amPm(input: string) { return this.find(AmPmNode)?.amPm(input) } } class AmPmNode extends EnglishGrammar.AmPmNode { amPm(input: string) { switch (this.substringOf(input)) { case '午前': return AmPmValue.AM case '午後': return AmPmValue.PM default: throw new Error(`unexpected`) } } } const AmPm = named('JapaneseAmPm', /午[前後]/).parseAs(AmPmNode) const Time = named( 'Time', longestOf( group(AmPm.maybe(), Hours, group(Minutes, Seconds.maybe())), group(AmPm.maybe(), Hours, group(Minutes, Seconds.maybe()).maybe()) ) ).parseAs(TimeNode) export class DateTimeNode extends EnglishGrammar.DateTimeNode { date(input: string): DateFn[] | undefined { return this.find(DateNode)?.dateFns(input) } time(input: string): DateFn[] | undefined { return this.find(TimeNode)?.dateFns(input) } } export const DateTime = named( 'JapaneseDateTime', longestOf(Date, Time, group(Date, group(space.maybe(), Time))) ).parseAs(DateTimeNode) export const Range = named( 'Range', group( named('RangeStart', DateTime), group( space.maybe(), oneOf('から', '~', '-', 'ー', '~', '-'), space.maybe() ), named('RangeEnd', DateTime), group(space.maybe(), 'まで').maybe() ) ).parseAs(EnglishGrammar.RangeNode) export class RootNode extends ParseRootNode { dateFns(input: string): DateFn[] { return ( (this.find(EnglishGrammar.RangeNode) || this.find(DateTimeNode))?.dateFns( input ) || [] ) } } export const Root = group( space.maybe(), oneOf(Range, DateTime), space.maybe() ).parseAs(RootNode) export function parse(input: string) { return base.parse(input, { grammar: Root }) } export function tellMeWhen(when: string, options?: { now?: Date }) { return base.tellMeWhen(when, { ...options, grammar: Root }) }