UNPKG

luxon

Version:
386 lines (363 loc) 11.8 kB
import * as English from './english'; import * as Formats from './formats'; import { padStart } from './util'; function stringifyTokens(splits, tokenToString) { let s = ''; for (const token of splits) { if (token.literal) { s += token.val; } else { s += tokenToString(token.val); } } return s; } const tokenToObject = { D: Formats.DATE_SHORT, DD: Formats.DATE_MED, DDD: Formats.DATE_FULL, DDDD: Formats.DATE_HUGE, t: Formats.TIME_SIMPLE, tt: Formats.TIME_WITH_SECONDS, ttt: Formats.TIME_WITH_SHORT_OFFSET, tttt: Formats.TIME_WITH_LONG_OFFSET, T: Formats.TIME_24_SIMPLE, TT: Formats.TIME_24_WITH_SECONDS, TTT: Formats.TIME_24_WITH_SHORT_OFFSET, TTTT: Formats.TIME_24_WITH_LONG_OFFSET, f: Formats.DATETIME_SHORT, ff: Formats.DATETIME_MED, fff: Formats.DATETIME_FULL, ffff: Formats.DATETIME_HUGE, F: Formats.DATETIME_SHORT_WITH_SECONDS, FF: Formats.DATETIME_MED_WITH_SECONDS, FFF: Formats.DATETIME_FULL_WITH_SECONDS, FFFF: Formats.DATETIME_HUGE_WITH_SECONDS }; /** * @private */ export default class Formatter { static create(locale, opts = {}) { const formatOpts = Object.assign({}, { round: true }, opts); return new Formatter(locale, formatOpts); } static parseFormat(fmt) { let current = null, currentFull = '', bracketed = false; const splits = []; for (let i = 0; i < fmt.length; i++) { const c = fmt.charAt(i); if (c === "'") { if (currentFull.length > 0) { splits.push({ literal: bracketed, val: currentFull }); } current = null; currentFull = ''; bracketed = !bracketed; } else if (bracketed) { currentFull += c; } else if (c === current) { currentFull += c; } else { if (currentFull.length > 0) { splits.push({ literal: false, val: currentFull }); } currentFull = c; current = c; } } if (currentFull.length > 0) { splits.push({ literal: bracketed, val: currentFull }); } return splits; } constructor(locale, formatOpts) { this.opts = formatOpts; this.loc = locale; this.systemLoc = null; } formatWithSystemDefault(dt, opts) { if (this.systemLoc === null) { this.systemLoc = this.loc.redefaultToSystem(); } const df = this.systemLoc.dtFormatter(dt, Object.assign({}, this.opts, opts)); return df.format(); } formatDateTime(dt, opts = {}) { const df = this.loc.dtFormatter(dt, Object.assign({}, this.opts, opts)); return df.format(); } formatDateTimeParts(dt, opts = {}) { const df = this.loc.dtFormatter(dt, Object.assign({}, this.opts, opts)); return df.formatToParts(); } resolvedOptions(dt, opts = {}) { const df = this.loc.dtFormatter(dt, Object.assign({}, this.opts, opts)); return df.resolvedOptions(); } num(n, p = 0) { // we get some perf out of doing this here, annoyingly if (this.opts.forceSimple) { return padStart(n, p); } const opts = Object.assign({}, this.opts); if (p > 0) { opts.padTo = p; } return this.loc.numberFormatter(opts).format(n); } formatDateTimeFromString(dt, fmt) { const knownEnglish = this.loc.listingMode() === 'en'; const string = (opts, extract) => this.loc.extract(dt, opts, extract), formatOffset = opts => { if (dt.isOffsetFixed && dt.offset === 0 && opts.allowZ) { return 'Z'; } const hours = Math.trunc(dt.offset / 60), minutes = Math.abs(dt.offset % 60), sign = hours >= 0 ? '+' : '-', base = `${sign}${Math.abs(hours)}`; switch (opts.format) { case 'short': return `${sign}${this.num(Math.abs(hours), 2)}:${this.num(minutes, 2)}`; case 'narrow': return minutes > 0 ? `${base}:${minutes}` : base; case 'techie': return `${sign}${this.num(Math.abs(hours), 2)}${this.num(minutes, 2)}`; default: throw new RangeError(`Value format ${opts.format} is out of range for property format`); } }, meridiem = () => knownEnglish ? English.meridiemForDateTime(dt) : string({ hour: 'numeric', hour12: true }, 'dayperiod'), month = (length, standalone) => knownEnglish ? English.monthForDateTime(dt, length) : string(standalone ? { month: length } : { month: length, day: 'numeric' }, 'month'), weekday = (length, standalone) => knownEnglish ? English.weekdayForDateTime(dt, length) : string( standalone ? { weekday: length } : { weekday: length, month: 'long', day: 'numeric' }, 'weekday' ), maybeMacro = token => { const macro = tokenToObject[token]; if (macro) { return this.formatWithSystemDefault(dt, macro); } else { return token; } }, era = length => knownEnglish ? English.eraForDateTime(dt, length) : string({ era: length }, 'era'), tokenToString = token => { const outputCal = this.loc.outputCalendar; // Where possible: http://cldr.unicode.org/translation/date-time#TOC-Stand-Alone-vs.-Format-Styles switch (token) { // ms case 'S': return this.num(dt.millisecond); case 'u': // falls through case 'SSS': return this.num(dt.millisecond, 3); // seconds case 's': return this.num(dt.second); case 'ss': return this.num(dt.second, 2); // minutes case 'm': return this.num(dt.minute); case 'mm': return this.num(dt.minute, 2); // hours case 'h': return this.num(dt.hour % 12 === 0 ? 12 : dt.hour % 12); case 'hh': return this.num(dt.hour % 12 === 0 ? 12 : dt.hour % 12, 2); case 'H': return this.num(dt.hour); case 'HH': return this.num(dt.hour, 2); // offset case 'Z': // like +6 return formatOffset({ format: 'narrow', allowZ: this.opts.allowZ }); case 'ZZ': // like +06:00 return formatOffset({ format: 'short', allowZ: this.opts.allowZ }); case 'ZZZ': // like +0600 return formatOffset({ format: 'techie', allowZ: false }); case 'ZZZZ': // like EST return dt.offsetNameShort; case 'ZZZZZ': // like Eastern Standard Time return dt.offsetNameLong; // zone case 'z': // like America/New_York return dt.zoneName; // meridiems case 'a': return meridiem(); // dates case 'd': return outputCal ? string({ day: 'numeric' }, 'day') : this.num(dt.day); case 'dd': return outputCal ? string({ day: '2-digit' }, 'day') : this.num(dt.day, 2); // weekdays - standalone case 'c': // like 1 return this.num(dt.weekday); case 'ccc': // like 'Tues' return weekday('short', true); case 'cccc': // like 'Tuesday' return weekday('long', true); case 'ccccc': // like 'T' return weekday('narrow', true); // weekdays - format case 'E': // like 1 return this.num(dt.weekday); case 'EEE': // like 'Tues' return weekday('short', false); case 'EEEE': // like 'Tuesday' return weekday('long', false); case 'EEEEE': // like 'T' return weekday('narrow', false); // months - standalone case 'L': // like 1 return outputCal ? string({ month: 'numeric', day: 'numeric' }, 'month') : this.num(dt.month); case 'LL': // like 01, doesn't seem to work return outputCal ? string({ month: '2-digit', day: 'numeric' }, 'month') : this.num(dt.month, 2); case 'LLL': // like Jan return month('short', true); case 'LLLL': // like January return month('long', true); case 'LLLLL': // like J return month('narrow', true); // months - format case 'M': // like 1 return outputCal ? string({ month: 'numeric' }, 'month') : this.num(dt.month); case 'MM': // like 01 return outputCal ? string({ month: '2-digit' }, 'month') : this.num(dt.month, 2); case 'MMM': // like Jan return month('short', false); case 'MMMM': // like January return month('long', false); case 'MMMMM': // like J return month('narrow', false); // years case 'y': // like 2014 return outputCal ? string({ year: 'numeric' }, 'year') : this.num(dt.year); case 'yy': // like 14 return outputCal ? string({ year: '2-digit' }, 'year') : this.num(dt.year.toString().slice(-2), 2); case 'yyyy': // like 0012 return outputCal ? string({ year: 'numeric' }, 'year') : this.num(dt.year, 4); case 'yyyyyy': // like 000012 return outputCal ? string({ year: 'numeric' }, 'year') : this.num(dt.year, 6); // eras case 'G': // like AD return era('short'); case 'GG': // like Anno Domini return era('long'); case 'GGGGG': return era('narrow'); case 'kk': return this.num(dt.weekYear.toString().slice(-2), 2); case 'kkkk': return this.num(dt.weekYear, 4); case 'W': return this.num(dt.weekNumber); case 'WW': return this.num(dt.weekNumber, 2); case 'o': return this.num(dt.ordinal); case 'ooo': return this.num(dt.ordinal, 3); case 'q': // like 1 return this.num(dt.quarter); case 'qq': // like 01 return this.num(dt.quarter, 2); default: return maybeMacro(token); } }; return stringifyTokens(Formatter.parseFormat(fmt), tokenToString); } formatDurationFromString(dur, fmt) { const tokenToField = token => { switch (token[0]) { case 'S': return 'millisecond'; case 's': return 'second'; case 'm': return 'minute'; case 'h': return 'hour'; case 'd': return 'day'; case 'M': return 'month'; case 'y': return 'year'; default: return null; } }, tokenToString = lildur => token => { const mapped = tokenToField(token); if (mapped) { return this.num(lildur.get(mapped), token.length); } else { return token; } }, tokens = Formatter.parseFormat(fmt), realTokens = tokens.reduce( (found, { literal, val }) => (literal ? found : found.concat(val)), [] ), collapsed = dur.shiftTo(...realTokens.map(tokenToField).filter(t => t)); return stringifyTokens(tokens, tokenToString(collapsed)); } }