UNPKG

@variantjs/core

Version:

VariantJS common functions and utilities

238 lines (204 loc) 7.14 kB
import dateIsValid from './dateIsValid'; import clone from '../helpers/clone'; import { DateValue, DateLocale, TokenRegex, TokenParsingFunctions, TokenParsingFunction, DateToken, } from '../types/Dates'; import { English } from './l10n/default'; const boolToInt = (bool: boolean) : 1 | 0 => (bool === true ? 1 : 0); const doNothing = (): undefined => undefined; const tokenParsingFunctions: TokenParsingFunctions = { D: doNothing, F(dateObj: Date, monthName: string, locale: DateLocale) { dateObj.setMonth(locale.months.longhand.indexOf(monthName)); }, G: (dateObj: Date, hour: string) => { dateObj.setHours(parseFloat(hour)); }, H: (dateObj: Date, hour: string) => { dateObj.setHours(parseFloat(hour)); }, J: (dateObj: Date, day: string) => { dateObj.setDate(parseFloat(day)); }, K: (dateObj: Date, amPM: string, locale: DateLocale) => { dateObj.setHours( (dateObj.getHours() % 12) + 12 * boolToInt(new RegExp(locale.amPM[1], 'i').test(amPM)), ); }, M(dateObj: Date, shortMonth: string, locale: DateLocale) { dateObj.setMonth(locale.months.shorthand.indexOf(shortMonth)); }, S: (dateObj: Date, seconds: string) => { dateObj.setSeconds(parseFloat(seconds)); }, U: (_: Date, unixSeconds: string) => new Date(parseFloat(unixSeconds) * 1000), W(dateObj: Date, weekNum: string, locale: DateLocale) { const weekNumber = parseInt(weekNum, 10); const date = new Date( dateObj.getFullYear(), 0, 2 + (weekNumber - 1) * 7, 0, 0, 0, 0, ); date.setDate(date.getDate() - date.getDay() + locale.firstDayOfWeek); return date; }, Y: (dateObj: Date, year: string) => { dateObj.setFullYear(parseFloat(year)); }, Z: (_: Date, ISODate: string) => new Date(ISODate), d: (dateObj: Date, day: string) => { dateObj.setDate(parseFloat(day)); }, h: (dateObj: Date, hour: string) => { dateObj.setHours(parseFloat(hour)); }, i: (dateObj: Date, minutes: string) => { dateObj.setMinutes(parseFloat(minutes)); }, j: (dateObj: Date, day: string) => { dateObj.setDate(parseFloat(day)); }, l: doNothing, m: (dateObj: Date, month: string) => { dateObj.setMonth(parseFloat(month) - 1); }, n: (dateObj: Date, month: string) => { dateObj.setMonth(parseFloat(month) - 1); }, s: (dateObj: Date, seconds: string) => { dateObj.setSeconds(parseFloat(seconds)); }, w: doNothing, y: (dateObj: Date, year: string) => { dateObj.setFullYear(2000 + parseFloat(year)); }, }; const tokenRegex: TokenRegex = { // A textual representation of a day (regex matches any word) D: '(\\w+)', // A full textual representation of a month (regex matches any word) F: '(\\w+)', // Hours, 2 digits with leading zeros (regex matches a number with 1 or 2 digits) G: '(\\d\\d|\\d)', // Hours (24 hours) regex matches a number with 1 or 2 digits H: '(\\d\\d|\\d)', // Day of the month without leading zeros and ordinal suffix (regex matches a number with 1 or 2 digits and an optional ordinal suffix) J: '(\\d\\d|\\d)\\w+', // locale-dependent, setup on runtime (am/pm) K: '', // A short textual representation of a month (regex matches any word) M: '(\\w+)', // Seconds, 2 digits (regex matches a number with 1 or 2 digits) S: '(\\d\\d|\\d)', // The number of seconds since the Unix Epoch (Regex accepts any value) U: '(.+)', W: '(\\d\\d|\\d)', Y: '(\\d{4})', Z: '(.+)', d: '(\\d\\d|\\d)', h: '(\\d\\d|\\d)', i: '(\\d\\d|\\d)', j: '(\\d\\d|\\d)', l: '(\\w+)', m: '(\\d\\d|\\d)', n: '(\\d\\d|\\d)', s: '(\\d\\d|\\d)', w: '(\\d\\d|\\d)', y: '(\\d{2})', }; const isTimestamp = (date: string | number): boolean => typeof date === 'number'; const isGMTString = (date: string): boolean => date.toLowerCase().endsWith('gmt'); const isIsoString = (date: string): boolean => date.toLowerCase().endsWith('z'); const getIsBackSlash = (char: string | undefined): boolean => char === '\\'; const getTokenParsingOperationsFromFormat = (date: string, format: string, locale: DateLocale): { fn: TokenParsingFunction; match: string }[] => { // The regex used for the `K` token is different for English and other languages const localeTokenRegex = { ...tokenRegex }; // Generates something like `(AM|PM|am|pm)` localeTokenRegex.K = `(${locale.amPM[0]}|${ locale.amPM[1] }|${locale.amPM[0].toLowerCase()}|${locale.amPM[1].toLowerCase()})`; const operations: { fn: TokenParsingFunction; match: string }[] = []; let regexString = ''; let matchIndex = 0; format.split('').forEach((token: string | DateToken, tokenIndex: number) => { const isBackSlash = getIsBackSlash(token); const isEscaped = getIsBackSlash(format[tokenIndex - 1]) || isBackSlash; const regex: string | undefined = localeTokenRegex[token as DateToken]; if (!isEscaped && regex) { regexString += regex; const match = new RegExp(regexString).exec(date); if (match !== null) { matchIndex += 1; if (token === 'Y') { // Should run the operation for years first operations.unshift({ fn: tokenParsingFunctions[token], match: match[matchIndex], }); } else { operations.push({ fn: tokenParsingFunctions[token], match: match[matchIndex], }); } } } else if (!isBackSlash) { // Meaning any character regexString += '.'; } }); return operations; }; const getToday = (): Date => { const today = new Date(); today.setHours(0, 0, 0, 0); return today; }; const parseDate = (date: DateValue | undefined | null, fromFormat = 'Y-m-d H:i:S', timeless?: boolean, customLocale?: DateLocale): Date | undefined => { if (date !== 0 && !date) { return undefined; } if (date === 'today') { return getToday(); } const locale = customLocale || English; let parsedDate: Date | undefined; const originalDate = date; if (date instanceof Date) { parsedDate = clone(date); } else if (isTimestamp(date)) { // New date from timestamp parsedDate = new Date(date); } else if (typeof date === 'string') { if (isGMTString(date) || isIsoString(date)) { parsedDate = new Date(date); } else { const operations = getTokenParsingOperationsFromFormat(date, fromFormat, locale); if (operations.length === 0) { parsedDate = undefined; } else { parsedDate = new Date(new Date().getFullYear(), 0, 1, 0, 0, 0, 0); operations.forEach((operation) => { const { fn, match } = operation; parsedDate = fn(parsedDate as Date, String(match), locale) || parsedDate; }); } } } else { throw new Error(`Invalid date provided: ${originalDate}`); } // eslint-disable-next-line no-restricted-globals if (!dateIsValid(parsedDate) || parsedDate === undefined) { throw new Error(`Invalid date provided: ${originalDate}`); } if (timeless === true) { parsedDate.setHours(0, 0, 0, 0); } return parsedDate; }; export default parseDate;