valor-software/ng2-bootstrap

View on GitHub
src/chronos/locale/locale.class.ts

Summary

Maintainability
F
1 wk
Test Coverage
// tslint:disable:max-file-line-count max-line-length cyclomatic-complexity

import { weekOfYear } from '../units/week-calendar-utils';
import { hasOwnProp, isArray, isFunction } from '../utils/type-checks';
import { getDay, getMonth } from '../utils/date-getters';
import { matchWord, regexEscape } from '../parse/regex';
import { setDayOfWeek } from '../units/day-of-week';

export interface LocaleOptionsFormat {
  format: string[];
  standalone: string[];
  isFormat?: RegExp;
}

export type LocaleOptions = string[] | LocaleOptionsFormat;

const MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/;
export const defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split(
  '_'
);
export const defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split(
  '_'
);
export const defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split(
  '_'
);
export const defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split(
  '_'
);
export const defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_');
export const defaultLongDateFormat: { [index: string]: string } = {
  LTS: 'h:mm:ss A',
  LT: 'h:mm A',
  L: 'MM/DD/YYYY',
  LL: 'MMMM D, YYYY',
  LLL: 'MMMM D, YYYY h:mm A',
  LLLL: 'dddd, MMMM D, YYYY h:mm A'
};

export const defaultOrdinal = '%d';
export const defaultDayOfMonthOrdinalParse = /\d{1,2}/;

const defaultMonthsShortRegex = matchWord;
const defaultMonthsRegex = matchWord;

export type OrdinalDateFn = (num: number, token?: string) => string;
export type PluralizeDateFn = (num: number, withoutSuffix: boolean,
                               key?: string, isFuture?: boolean) => string;

export interface LocaleData {
  abbr?: string;
  parentLocale?: string;

  months?: LocaleOptions | ((date: Date, format: string, isUTC?: boolean) => string | string[]);
  monthsShort?: LocaleOptions | ((date: Date, format: string, isUTC?: boolean) => string | string[]);
  monthsParseExact?: boolean;

  weekdays?: LocaleOptions | ((date: Date, format: string, isUTC?: boolean) => string | string[]);
  weekdaysShort?: string[] | ((date: Date, format: string, isUTC?: boolean) => string | string[]);
  weekdaysMin?: string[] | ((date: Date, format: string, isUTC?: boolean) => string | string[]);
  weekdaysParseExact?: boolean;

  longDateFormat?: { [index: string]: string };
  calendar?: {
    [key: string]: (string
      | ((date: Date, now?: Date) => string)
      // tslint:disable-next-line
      | ((dayOfWeek: number, isNextWeek: boolean) => string))
  };
  relativeTime?: { [key: string]: string | PluralizeDateFn };
  dayOfMonthOrdinalParse?: RegExp;
  ordinal?: string | OrdinalDateFn;

  week?: { dow?: number; doy?: number };

  invalidDate?: string;

  monthsRegex?: RegExp;
  monthsParse?: RegExp[];
  monthsShortRegex?: RegExp;
  monthsStrictRegex?: RegExp;
  monthsShortStrictRegex?: RegExp;
  longMonthsParse?: RegExp[];
  shortMonthsParse?: RegExp[];

  meridiemParse?: RegExp;

  meridiemHour?(hour: number, meridiem: string): number;

  preparse?(str: string): string;

  postformat?(str: string | number): string;

  meridiem?(hour: number, minute?: number, isLower?: boolean): string;

  isPM?(input: string): boolean;
}

export class Locale {
  parentLocale?: Locale;
  _abbr: string;
  _config: LocaleData;
  meridiemHour: (hour: number, meridiem: string) => number;

  _invalidDate: string;
  _week: { dow: number; doy: number };
  _dayOfMonthOrdinalParse: RegExp;
  _ordinalParse: RegExp;
  _meridiemParse: RegExp;

  private _calendar: { [key: string]: string };
  private _relativeTime: { future: string; past: string };
  private _months: LocaleOptions;
  private _monthsShort: LocaleOptions;
  private _monthsRegex: RegExp;
  private _monthsShortRegex: RegExp;
  private _monthsStrictRegex: RegExp;
  private _monthsShortStrictRegex: RegExp;
  private _monthsParse: RegExp[];
  private _longMonthsParse: string[] | RegExp[];
  private _shortMonthsParse: string[] | RegExp[];
  private _monthsParseExact: RegExp;
  private _weekdaysParseExact: boolean;
  private _weekdaysRegex: RegExp;
  private _weekdaysShortRegex: RegExp;
  private _weekdaysMinRegex: RegExp;

  private _weekdaysStrictRegex: RegExp;
  private _weekdaysShortStrictRegex: RegExp;
  private _weekdaysMinStrictRegex: RegExp;

  private _weekdays: LocaleOptions;
  private _weekdaysShort: string[];
  private _weekdaysMin: string[];
  private _weekdaysParse: string[] | RegExp[];
  private _minWeekdaysParse: string[] | RegExp[];
  private _shortWeekdaysParse: string[] | RegExp[];
  private _fullWeekdaysParse: RegExp[];
  private _longDateFormat: { [key: string]: string };

  private _ordinal: string;

  constructor(config: LocaleData) {
    if (!!config) {
      this.set(config);
    }
  }

  set(config: LocaleData): void {
    let confKey;
    for (confKey in config) {
      if (!config.hasOwnProperty(confKey)) {
        continue;
      }
      const prop = config[confKey as keyof LocaleData];
      const key = (isFunction(prop) ? confKey : `_${confKey}`) as keyof Locale;

      this[key] = prop as any;
    }

    this._config = config;
  }

  calendar(key: string, date: Date, now: Date): string {
    const output = this._calendar[key] || this._calendar.sameElse;

    return isFunction(output) ? output.call(null, date, now) : output;
  }

  longDateFormat(key: string) {
    const format = this._longDateFormat[key];
    const formatUpper = this._longDateFormat[key.toUpperCase()];

    if (format || !formatUpper) {
      return format;
    }

    this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val: string) {
      return val.slice(1);
    });

    return this._longDateFormat[key];
  }

  get invalidDate(): string {
    return this._invalidDate;
  }

  set invalidDate(val: string) {
    this._invalidDate = val;
  }

  ordinal(num: number, token?: string): string {
    return this._ordinal.replace('%d', num.toString(10));
  }

  preparse(str: string) {
    return str;
  }

  postformat(str: string) {
    return str;
  }

  relativeTime(num: number, withoutSuffix: boolean, str: 'future' | 'past', isFuture: boolean): string {
    const output = this._relativeTime[str];

    return (isFunction(output)) ?
      output(num, withoutSuffix, str, isFuture) :
      output.replace(/%d/i, num.toString(10));
  }

  pastFuture(diff: number, output: string): string {
    const format = this._relativeTime[diff > 0 ? 'future' : 'past'];

    return isFunction(format) ? format(output) : format.replace(/%s/i, output);
  }

  /** Months */
  months(): string[];
  months(date: Date, format?: string, isUTC?: boolean): string;
  months(date?: Date, format?: string, isUTC = false): string | string[] {
    if (!date) {
      return isArray<string>(this._months)
        ? this._months
        : this._months.standalone;
    }

    if (isArray<string>(this._months)) {
      return this._months[getMonth(date, isUTC)];
    }

    const key = (this._months.isFormat || MONTHS_IN_FORMAT).test(format)
      ? 'format'
      : 'standalone';

    return this._months[key][getMonth(date, isUTC)];
  }

  monthsShort(): string[];
  monthsShort(date?: Date, format?: string, isUTC?: boolean): string;
  monthsShort(date?: Date, format?: string, isUTC = false): string | string[] {
    if (!date) {
      return isArray<string>(this._monthsShort)
        ? this._monthsShort
        : this._monthsShort.standalone;
    }

    if (isArray<string>(this._monthsShort)) {
      return this._monthsShort[getMonth(date, isUTC)];
    }
    const key = MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone';

    return this._monthsShort[key][getMonth(date, isUTC)];
  }

  monthsParse(monthName: string, format?: string, strict?: boolean): number {
    let date;
    let regex;

    if (this._monthsParseExact) {
      return this.handleMonthStrictParse(monthName, format, strict);
    }

    if (!this._monthsParse) {
      this._monthsParse = [];
      this._longMonthsParse = [];
      this._shortMonthsParse = [];
    }

    // TODO: add sorting
    // Sorting makes sure if one month (or abbr) is a prefix of another
    // see sorting in computeMonthsParse
    let i;
    for (i = 0; i < 12; i++) {
      // make the regex if we don't have it already
      date = new Date(Date.UTC(2000, i));
      if (strict && !this._longMonthsParse[i]) {
        const _months = this.months(date, '').replace('.', '');
        const _shortMonths = this.monthsShort(date, '').replace('.', '');
        this._longMonthsParse[i] = new RegExp(`^${_months}$`, 'i');
        this._shortMonthsParse[i] = new RegExp(`^${_shortMonths}$`, 'i');
      }
      if (!strict && !this._monthsParse[i]) {
        regex = `^${this.months(date, '')}|^${this.monthsShort(date, '')}`;
        this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
      }
      // test the regex
      if (strict && format === 'MMMM' && (this._longMonthsParse[i] as RegExp).test(monthName)) {
        return i;
      }

      if (strict && format === 'MMM' && (this._shortMonthsParse[i] as RegExp).test(monthName)) {
        return i;
      }

      if (!strict && this._monthsParse[i].test(monthName)) {
        return i;
      }
    }
  }

  monthsRegex(isStrict: boolean): RegExp {
    if (this._monthsParseExact) {
      if (!hasOwnProp(this, '_monthsRegex')) {
        this.computeMonthsParse();
      }
      if (isStrict) {
        return this._monthsStrictRegex;
      }

      return this._monthsRegex;
    }

    if (!hasOwnProp(this, '_monthsRegex')) {
      this._monthsRegex = defaultMonthsRegex;
    }

    return this._monthsStrictRegex && isStrict ?
      this._monthsStrictRegex : this._monthsRegex;
  }

  monthsShortRegex(isStrict: boolean): RegExp {
    if (this._monthsParseExact) {
      if (!hasOwnProp(this, '_monthsRegex')) {
        this.computeMonthsParse();
      }
      if (isStrict) {
        return this._monthsShortStrictRegex;
      }

      return this._monthsShortRegex;
    }
    if (!hasOwnProp(this, '_monthsShortRegex')) {
      this._monthsShortRegex = defaultMonthsShortRegex;
    }

    return this._monthsShortStrictRegex && isStrict ?
      this._monthsShortStrictRegex : this._monthsShortRegex;
  }

  /** Week */
  week(date: Date): number {
    return weekOfYear(date, this._week.dow, this._week.doy).week;
  }

  firstDayOfWeek(): number {
    return this._week.dow;
  }

  firstDayOfYear(): number {
    return this._week.doy;
  }

  /** Day of Week */
  weekdays(): string[];
  weekdays(date: Date, format?: string, isUTC?: boolean): string;
  weekdays(date?: Date, format?: string, isUTC?: boolean): string | string[] {
    if (!date) {
      return isArray<string>(this._weekdays)
        ? this._weekdays
        : this._weekdays.standalone;
    }

    if (isArray<string>(this._weekdays)) {
      return this._weekdays[getDay(date, isUTC)];
    }

    const _key = this._weekdays.isFormat.test(format)
      ? 'format'
      : 'standalone';

    return this._weekdays[_key][getDay(date, isUTC)];
  }

  weekdaysMin(): string[];
  weekdaysMin(date: Date, format?: string, isUTC?: boolean): string;
  weekdaysMin(date?: Date, format?: string, isUTC?: boolean): string | string[] {
    return date ? this._weekdaysMin[getDay(date, isUTC)] : this._weekdaysMin;
  }

  weekdaysShort(): string[];
  weekdaysShort(date: Date, format?: string, isUTC?: boolean): string;
  weekdaysShort(date?: Date, format?: string, isUTC?: boolean): string | string[] {
    return date ? this._weekdaysShort[getDay(date, isUTC)] : this._weekdaysShort;
  }


  // proto.weekdaysParse  =        localeWeekdaysParse;
  weekdaysParse(weekdayName?: string, format?: string, strict?: boolean): number {
    let i;
    let regex;

    if (this._weekdaysParseExact) {
      return this.handleWeekStrictParse(weekdayName, format, strict);
    }

    if (!this._weekdaysParse) {
      this._weekdaysParse = [];
      this._minWeekdaysParse = [];
      this._shortWeekdaysParse = [];
      this._fullWeekdaysParse = [];
    }

    for (i = 0; i < 7; i++) {
      // make the regex if we don't have it already
      // fix: here is the issue
      const date = setDayOfWeek(new Date(Date.UTC(2000, 1)), i, null, true);
      if (strict && !this._fullWeekdaysParse[i]) {
        this._fullWeekdaysParse[i] = new RegExp(`^${this.weekdays(date, '').replace('.', '\.?')}$`, 'i');
        this._shortWeekdaysParse[i] = new RegExp(`^${this.weekdaysShort(date).replace('.', '\.?')}$`, 'i');
        this._minWeekdaysParse[i] = new RegExp(`^${this.weekdaysMin(date).replace('.', '\.?')}$`, 'i');
      }
      if (!this._weekdaysParse[i]) {
        regex = `^${this.weekdays(date, '')}|^${this.weekdaysShort(date)}|^${this.weekdaysMin(date)}`;
        this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
      }

      if (!isArray<RegExp>(this._fullWeekdaysParse)
        || !isArray<RegExp>(this._shortWeekdaysParse)
        || !isArray<RegExp>(this._minWeekdaysParse)
        || !isArray<RegExp>(this._weekdaysParse)) {
        return;
      }

      // test the regex
      if (strict && format === 'dddd' && this._fullWeekdaysParse[i].test(weekdayName)) {
        return i;
      } else if (strict && format === 'ddd' && this._shortWeekdaysParse[i].test(weekdayName)) {
        return i;
      } else if (strict && format === 'dd' && this._minWeekdaysParse[i].test(weekdayName)) {
        return i;
      } else if (!strict && this._weekdaysParse[i].test(weekdayName)) {
        return i;
      }
    }
  }

  // proto.weekdaysRegex       =        weekdaysRegex;
  weekdaysRegex(isStrict: boolean) {
    if (this._weekdaysParseExact) {
      if (!hasOwnProp(this, '_weekdaysRegex')) {
        this.computeWeekdaysParse();
      }

      if (isStrict) {
        return this._weekdaysStrictRegex;
      } else {
        return this._weekdaysRegex;
      }
    } else {
      if (!hasOwnProp(this, '_weekdaysRegex')) {
        this._weekdaysRegex = matchWord;
      }

      return this._weekdaysStrictRegex && isStrict ?
        this._weekdaysStrictRegex : this._weekdaysRegex;
    }
  }

  // proto.weekdaysShortRegex  =        weekdaysShortRegex;
  // proto.weekdaysMinRegex    =        weekdaysMinRegex;


  weekdaysShortRegex(isStrict?: boolean): RegExp {
    if (this._weekdaysParseExact) {
      if (!hasOwnProp(this, '_weekdaysRegex')) {
        this.computeWeekdaysParse();
      }
      if (isStrict) {
        return this._weekdaysShortStrictRegex;
      } else {
        return this._weekdaysShortRegex;
      }
    } else {
      if (!hasOwnProp(this, '_weekdaysShortRegex')) {
        this._weekdaysShortRegex = matchWord;
      }

      return this._weekdaysShortStrictRegex && isStrict ?
        this._weekdaysShortStrictRegex : this._weekdaysShortRegex;
    }
  }

  weekdaysMinRegex(isStrict?: boolean): RegExp {
    if (this._weekdaysParseExact) {
      if (!hasOwnProp(this, '_weekdaysRegex')) {
        this.computeWeekdaysParse();
      }
      if (isStrict) {
        return this._weekdaysMinStrictRegex;
      } else {
        return this._weekdaysMinRegex;
      }
    } else {
      if (!hasOwnProp(this, '_weekdaysMinRegex')) {
        this._weekdaysMinRegex = matchWord;
      }

      return this._weekdaysMinStrictRegex && isStrict ?
        this._weekdaysMinStrictRegex : this._weekdaysMinRegex;
    }
  }

  isPM(input: string): boolean {
    // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
    // Using charAt should be more compatible.
    return input.toLowerCase().charAt(0) === 'p';
  }

  meridiem(hours: number, minutes: number, isLower: boolean): string {
    if (hours > 11) {
      return isLower ? 'pm' : 'PM';
    }

    return isLower ? 'am' : 'AM';
  }

  formatLongDate(key: string) {
    this._longDateFormat = this._longDateFormat ? this._longDateFormat : defaultLongDateFormat;
    const format = this._longDateFormat[key];
    const formatUpper = this._longDateFormat[key.toUpperCase()];

    if (format || !formatUpper) {
      return format;
    }

    this._longDateFormat[
      key
      ] = formatUpper.replace(/MMMM|MM|DD|dddd/g, (val: string) => {
      return val.slice(1);
    });

    return this._longDateFormat[key];
  }

  private handleMonthStrictParse(monthName: string, format: string, strict?: boolean) {
    const llc = monthName.toLocaleLowerCase();
    let i;
    let ii;
    let mom;
    if (!this._monthsParse) {
      // this is not used
      this._monthsParse = [];
      this._longMonthsParse = [];
      this._shortMonthsParse = [];
      for (i = 0; i < 12; ++i) {
        mom = new Date(2000, i);
        this._shortMonthsParse[i] = this.monthsShort(mom, '').toLocaleLowerCase();
        this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase();
      }
    }

    if (strict) {
      if (format === 'MMM') {
        ii = (this._shortMonthsParse as string[]).indexOf(llc);

        return ii !== -1 ? ii : null;
      }
      ii = (this._longMonthsParse as string[]).indexOf(llc);

      return ii !== -1 ? ii : null;
    }

    if (format === 'MMM') {
      ii = (this._shortMonthsParse as string[]).indexOf(llc);
      if (ii !== -1) {
        return ii;
      }

      ii = (this._longMonthsParse as string[]).indexOf(llc);

      return ii !== -1 ? ii : null;
    }

    ii = (this._longMonthsParse as string[]).indexOf(llc);
    if (ii !== -1) {
      return ii;
    }
    ii = (this._shortMonthsParse as string[]).indexOf(llc);

    return ii !== -1 ? ii : null;
  }

  private handleWeekStrictParse(weekdayName: string, format: string, strict: boolean): number {
    let ii;
    const llc = weekdayName.toLocaleLowerCase();
    if (!this._weekdaysParse) {
      this._weekdaysParse = [];
      this._shortWeekdaysParse = [];
      this._minWeekdaysParse = [];

      let i;
      for (i = 0; i < 7; ++i) {
        const date = setDayOfWeek(new Date(Date.UTC(2000, 1)), i, null, true);
        this._minWeekdaysParse[i] = this.weekdaysMin(date).toLocaleLowerCase();
        this._shortWeekdaysParse[i] = this.weekdaysShort(date).toLocaleLowerCase();
        this._weekdaysParse[i] = this.weekdays(date, '').toLocaleLowerCase();
      }
    }

    if (!isArray<string>(this._weekdaysParse)
      || !isArray<string>(this._shortWeekdaysParse)
      || !isArray<string>(this._minWeekdaysParse)) {
      return;
    }

    if (strict) {
      if (format === 'dddd') {
        ii = this._weekdaysParse.indexOf(llc);

        return ii !== -1 ? ii : null;
      } else if (format === 'ddd') {
        ii = this._shortWeekdaysParse.indexOf(llc);

        return ii !== -1 ? ii : null;
      } else {
        ii = this._minWeekdaysParse.indexOf(llc);

        return ii !== -1 ? ii : null;
      }
    } else {
      if (format === 'dddd') {
        ii = this._weekdaysParse.indexOf(llc);
        if (ii !== -1) {
          return ii;
        }
        ii = this._shortWeekdaysParse.indexOf(llc);
        if (ii !== -1) {
          return ii;
        }
        ii = this._minWeekdaysParse.indexOf(llc);

        return ii !== -1 ? ii : null;
      } else if (format === 'ddd') {
        ii = this._shortWeekdaysParse.indexOf(llc);
        if (ii !== -1) {
          return ii;
        }
        ii = this._weekdaysParse.indexOf(llc);
        if (ii !== -1) {
          return ii;
        }
        ii = this._minWeekdaysParse.indexOf(llc);

        return ii !== -1 ? ii : null;
      } else {
        ii = this._minWeekdaysParse.indexOf(llc);
        if (ii !== -1) {
          return ii;
        }
        ii = this._weekdaysParse.indexOf(llc);
        if (ii !== -1) {
          return ii;
        }
        ii = this._shortWeekdaysParse.indexOf(llc);

        return ii !== -1 ? ii : null;
      }
    }
  }

  private computeMonthsParse() {
    const shortPieces: string[] = [];
    const longPieces: string[] = [];
    const mixedPieces: string[] = [];
    let date;

    let i;
    for (i = 0; i < 12; i++) {
      // make the regex if we don't have it already
      date = new Date(2000, i);
      shortPieces.push(this.monthsShort(date, ''));
      longPieces.push(this.months(date, ''));
      mixedPieces.push(this.months(date, ''));
      mixedPieces.push(this.monthsShort(date, ''));
    }
    // Sorting makes sure if one month (or abbr) is a prefix of another it
    // will match the longer piece.
    shortPieces.sort(cmpLenRev);
    longPieces.sort(cmpLenRev);
    mixedPieces.sort(cmpLenRev);
    for (i = 0; i < 12; i++) {
      shortPieces[i] = regexEscape(shortPieces[i]);
      longPieces[i] = regexEscape(longPieces[i]);
    }
    for (i = 0; i < 24; i++) {
      mixedPieces[i] = regexEscape(mixedPieces[i]);
    }

    this._monthsRegex = new RegExp(`^(${mixedPieces.join('|')})`, 'i');
    this._monthsShortRegex = this._monthsRegex;
    this._monthsStrictRegex = new RegExp(`^(${longPieces.join('|')})`, 'i');
    this._monthsShortStrictRegex = new RegExp(`^(${shortPieces.join('|')})`, 'i');
  }

  private computeWeekdaysParse() {
    const minPieces = [];
    const shortPieces = [];
    const longPieces = [];
    const mixedPieces = [];

    let i;
    for (i = 0; i < 7; i++) {
      // make the regex if we don't have it already
      // let mom = createUTC([2000, 1]).day(i);
      const date = setDayOfWeek(new Date(Date.UTC(2000, 1)), i, null, true);
      const minp = this.weekdaysMin(date);
      const shortp = this.weekdaysShort(date);
      const longp = this.weekdays(date);
      minPieces.push(minp);
      shortPieces.push(shortp);
      longPieces.push(longp);
      mixedPieces.push(minp);
      mixedPieces.push(shortp);
      mixedPieces.push(longp);
    }
    // Sorting makes sure if one weekday (or abbr) is a prefix of another it
    // will match the longer piece.
    minPieces.sort(cmpLenRev);
    shortPieces.sort(cmpLenRev);
    longPieces.sort(cmpLenRev);
    mixedPieces.sort(cmpLenRev);
    for (i = 0; i < 7; i++) {
      shortPieces[i] = regexEscape(shortPieces[i]);
      longPieces[i] = regexEscape(longPieces[i]);
      mixedPieces[i] = regexEscape(mixedPieces[i]);
    }

    this._weekdaysRegex = new RegExp(`^(${mixedPieces.join('|')})`, 'i');
    this._weekdaysShortRegex = this._weekdaysRegex;
    this._weekdaysMinRegex = this._weekdaysRegex;

    this._weekdaysStrictRegex = new RegExp(`^(${longPieces.join('|')})`, 'i');
    this._weekdaysShortStrictRegex = new RegExp(`^(${shortPieces.join('|')})`, 'i');
    this._weekdaysMinStrictRegex = new RegExp(`^(${minPieces.join('|')})`, 'i');
  }
}

function cmpLenRev(a: string, b: string): number {
  return b.length - a.length;
}