mirror of
https://github.com/swc-project/swc.git
synced 2024-12-29 16:42:28 +03:00
390 lines
12 KiB
TypeScript
390 lines
12 KiB
TypeScript
// Loaded from https://unpkg.com/luxon@1.25.0/src/impl/formatter.js
|
|
|
|
|
|
import * as English from "./english.js";
|
|
import * as Formats from "./formats.js";
|
|
import { hasFormatToParts, 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) {
|
|
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;
|
|
}
|
|
|
|
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, 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",
|
|
useDateTimeFormatter =
|
|
this.loc.outputCalendar && this.loc.outputCalendar !== "gregory" && hasFormatToParts(),
|
|
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", 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 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: 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: 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 "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 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));
|
|
}
|
|
}
|