summaryrefslogtreecommitdiff
path: root/node_modules/luxon/src/impl/formatter.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/luxon/src/impl/formatter.js')
-rw-r--r--node_modules/luxon/src/impl/formatter.js434
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));
+ }
+}