diff options
| author | Shipwreckt <me@shipwreckt.co.uk> | 2025-10-31 20:02:14 +0000 |
|---|---|---|
| committer | Shipwreckt <me@shipwreckt.co.uk> | 2025-10-31 20:02:14 +0000 |
| commit | 7a52ddeba2a68388b544f529d2d92104420f77b0 (patch) | |
| tree | 15ddd47457a2cb4a96060747437d36474e4f6b4e /node_modules/luxon/src/impl/formatter.js | |
| parent | 53d6ae2b5568437afa5e4995580a3fb679b7b91b (diff) | |
Changed from static to 11ty!
Diffstat (limited to 'node_modules/luxon/src/impl/formatter.js')
| -rw-r--r-- | node_modules/luxon/src/impl/formatter.js | 434 |
1 files changed, 434 insertions, 0 deletions
diff --git a/node_modules/luxon/src/impl/formatter.js b/node_modules/luxon/src/impl/formatter.js new file mode 100644 index 0000000..f7e2d04 --- /dev/null +++ b/node_modules/luxon/src/impl/formatter.js @@ -0,0 +1,434 @@ +import * as English from "./english.js"; +import * as Formats from "./formats.js"; +import { padStart } from "./util.js"; + +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 macroTokenToFormatOpts = { + 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 = {}) { + return new Formatter(locale, opts); + } + + static parseFormat(fmt) { + // white-space is always considered a literal in user-provided formats + // the " " token has a special meaning (see unitForToken) + + let current = null, + currentFull = "", + bracketed = false; + const splits = []; + for (let i = 0; i < fmt.length; i++) { + const c = fmt.charAt(i); + if (c === "'") { + // turn '' into a literal signal quote instead of just skipping the empty literal + if (currentFull.length > 0 || bracketed) { + splits.push({ + literal: bracketed || /^\s+$/.test(currentFull), + val: currentFull === "" ? "'" : 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: /^\s+$/.test(currentFull), val: currentFull }); + } + currentFull = c; + current = c; + } + } + + if (currentFull.length > 0) { + splits.push({ literal: bracketed || /^\s+$/.test(currentFull), val: currentFull }); + } + + return splits; + } + + static macroTokenToFormatOpts(token) { + return macroTokenToFormatOpts[token]; + } + + 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, { ...this.opts, ...opts }); + return df.format(); + } + + dtFormatter(dt, opts = {}) { + return this.loc.dtFormatter(dt, { ...this.opts, ...opts }); + } + + formatDateTime(dt, opts) { + return this.dtFormatter(dt, opts).format(); + } + + formatDateTimeParts(dt, opts) { + return this.dtFormatter(dt, opts).formatToParts(); + } + + formatInterval(interval, opts) { + const df = this.dtFormatter(interval.start, opts); + return df.dtf.formatRange(interval.start.toJSDate(), interval.end.toJSDate()); + } + + resolvedOptions(dt, opts) { + return this.dtFormatter(dt, opts).resolvedOptions(); + } + + num(n, p = 0, signDisplay = undefined) { + // we get some perf out of doing this here, annoyingly + if (this.opts.forceSimple) { + return padStart(n, p); + } + + const opts = { ...this.opts }; + + if (p > 0) { + opts.padTo = p; + } + if (signDisplay) { + opts.signDisplay = signDisplay; + } + + return this.loc.numberFormatter(opts).format(n); + } + + formatDateTimeFromString(dt, fmt) { + const knownEnglish = this.loc.listingMode() === "en", + useDateTimeFormatter = this.loc.outputCalendar && this.loc.outputCalendar !== "gregory", + string = (opts, extract) => this.loc.extract(dt, opts, extract), + formatOffset = (opts) => { + if (dt.isOffsetFixed && dt.offset === 0 && opts.allowZ) { + return "Z"; + } + + return dt.isValid ? dt.zone.formatOffset(dt.ts, opts.format) : ""; + }, + meridiem = () => + knownEnglish + ? English.meridiemForDateTime(dt) + : string({ hour: "numeric", hourCycle: "h12" }, "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 formatOpts = Formatter.macroTokenToFormatOpts(token); + if (formatOpts) { + return this.formatWithSystemDefault(dt, formatOpts); + } else { + return token; + } + }, + era = (length) => + knownEnglish ? English.eraForDateTime(dt, length) : string({ era: length }, "era"), + tokenToString = (token) => { + // Where possible: https://cldr.unicode.org/translation/date-time/date-time-symbols + 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); + // fractional seconds + case "uu": + return this.num(Math.floor(dt.millisecond / 10), 2); + case "uuu": + return this.num(Math.floor(dt.millisecond / 100)); + // 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: this.opts.allowZ }); + case "ZZZZ": + // like EST + return dt.zone.offsetName(dt.ts, { format: "short", locale: this.loc.locale }); + case "ZZZZZ": + // like Eastern Standard Time + return dt.zone.offsetName(dt.ts, { format: "long", locale: this.loc.locale }); + // zone + case "z": + // like America/New_York + return dt.zoneName; + // meridiems + case "a": + return meridiem(); + // dates + case "d": + return useDateTimeFormatter ? string({ day: "numeric" }, "day") : this.num(dt.day); + case "dd": + return useDateTimeFormatter ? 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 useDateTimeFormatter + ? string({ month: "numeric", day: "numeric" }, "month") + : this.num(dt.month); + case "LL": + // like 01, doesn't seem to work + return useDateTimeFormatter + ? 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 useDateTimeFormatter + ? string({ month: "numeric" }, "month") + : this.num(dt.month); + case "MM": + // like 01 + return useDateTimeFormatter + ? 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 useDateTimeFormatter ? string({ year: "numeric" }, "year") : this.num(dt.year); + case "yy": + // like 14 + return useDateTimeFormatter + ? string({ year: "2-digit" }, "year") + : this.num(dt.year.toString().slice(-2), 2); + case "yyyy": + // like 0012 + return useDateTimeFormatter + ? string({ year: "numeric" }, "year") + : this.num(dt.year, 4); + case "yyyyyy": + // like 000012 + return useDateTimeFormatter + ? 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 "n": + return this.num(dt.localWeekNumber); + case "nn": + return this.num(dt.localWeekNumber, 2); + case "ii": + return this.num(dt.localWeekYear.toString().slice(-2), 2); + case "iiii": + return this.num(dt.localWeekYear, 4); + 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); + case "X": + return this.num(Math.floor(dt.ts / 1000)); + case "x": + return this.num(dt.ts); + default: + return maybeMacro(token); + } + }; + + return stringifyTokens(Formatter.parseFormat(fmt), tokenToString); + } + + formatDurationFromString(dur, fmt) { + const invertLargest = this.opts.signMode === "negativeLargestOnly" ? -1 : 1; + const tokenToField = (token) => { + switch (token[0]) { + case "S": + return "milliseconds"; + case "s": + return "seconds"; + case "m": + return "minutes"; + case "h": + return "hours"; + case "d": + return "days"; + case "w": + return "weeks"; + case "M": + return "months"; + case "y": + return "years"; + default: + return null; + } + }, + tokenToString = (lildur, info) => (token) => { + const mapped = tokenToField(token); + if (mapped) { + const inversionFactor = + info.isNegativeDuration && mapped !== info.largestUnit ? invertLargest : 1; + let signDisplay; + if (this.opts.signMode === "negativeLargestOnly" && mapped !== info.largestUnit) { + signDisplay = "never"; + } else if (this.opts.signMode === "all") { + signDisplay = "always"; + } else { + // "auto" and "negative" are the same, but "auto" has better support + signDisplay = "auto"; + } + return this.num(lildur.get(mapped) * inversionFactor, token.length, signDisplay); + } 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)), + durationInfo = { + isNegativeDuration: collapsed < 0, + // this relies on "collapsed" being based on "shiftTo", which builds up the object + // in order + largestUnit: Object.keys(collapsed.values)[0], + }; + return stringifyTokens(tokens, tokenToString(collapsed, durationInfo)); + } +} |
