summaryrefslogtreecommitdiff
path: root/std/datetime/formatter.ts
diff options
context:
space:
mode:
Diffstat (limited to 'std/datetime/formatter.ts')
-rw-r--r--std/datetime/formatter.ts594
1 files changed, 0 insertions, 594 deletions
diff --git a/std/datetime/formatter.ts b/std/datetime/formatter.ts
deleted file mode 100644
index 788de6d00..000000000
--- a/std/datetime/formatter.ts
+++ /dev/null
@@ -1,594 +0,0 @@
-// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-import {
- CallbackResult,
- ReceiverResult,
- Rule,
- TestFunction,
- TestResult,
- Tokenizer,
-} from "./tokenizer.ts";
-
-function digits(value: string | number, count = 2): string {
- return String(value).padStart(count, "0");
-}
-
-// as declared as in namespace Intl
-type DateTimeFormatPartTypes =
- | "day"
- | "dayPeriod"
- // | "era"
- | "hour"
- | "literal"
- | "minute"
- | "month"
- | "second"
- | "timeZoneName"
- // | "weekday"
- | "year"
- | "fractionalSecond";
-
-interface DateTimeFormatPart {
- type: DateTimeFormatPartTypes;
- value: string;
-}
-
-type TimeZone = "UTC";
-
-interface Options {
- timeZone?: TimeZone;
-}
-
-function createLiteralTestFunction(value: string): TestFunction {
- return (string: string): TestResult => {
- return string.startsWith(value)
- ? { value, length: value.length }
- : undefined;
- };
-}
-
-function createMatchTestFunction(match: RegExp): TestFunction {
- return (string: string): TestResult => {
- const result = match.exec(string);
- if (result) return { value: result, length: result[0].length };
- };
-}
-
-// according to unicode symbols (http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table)
-const defaultRules = [
- {
- test: createLiteralTestFunction("yyyy"),
- fn: (): CallbackResult => ({ type: "year", value: "numeric" }),
- },
- {
- test: createLiteralTestFunction("yy"),
- fn: (): CallbackResult => ({ type: "year", value: "2-digit" }),
- },
-
- {
- test: createLiteralTestFunction("MM"),
- fn: (): CallbackResult => ({ type: "month", value: "2-digit" }),
- },
- {
- test: createLiteralTestFunction("M"),
- fn: (): CallbackResult => ({ type: "month", value: "numeric" }),
- },
- {
- test: createLiteralTestFunction("dd"),
- fn: (): CallbackResult => ({ type: "day", value: "2-digit" }),
- },
- {
- test: createLiteralTestFunction("d"),
- fn: (): CallbackResult => ({ type: "day", value: "numeric" }),
- },
-
- {
- test: createLiteralTestFunction("HH"),
- fn: (): CallbackResult => ({ type: "hour", value: "2-digit" }),
- },
- {
- test: createLiteralTestFunction("H"),
- fn: (): CallbackResult => ({ type: "hour", value: "numeric" }),
- },
- {
- test: createLiteralTestFunction("hh"),
- fn: (): CallbackResult => ({
- type: "hour",
- value: "2-digit",
- hour12: true,
- }),
- },
- {
- test: createLiteralTestFunction("h"),
- fn: (): CallbackResult => ({
- type: "hour",
- value: "numeric",
- hour12: true,
- }),
- },
- {
- test: createLiteralTestFunction("mm"),
- fn: (): CallbackResult => ({ type: "minute", value: "2-digit" }),
- },
- {
- test: createLiteralTestFunction("m"),
- fn: (): CallbackResult => ({ type: "minute", value: "numeric" }),
- },
- {
- test: createLiteralTestFunction("ss"),
- fn: (): CallbackResult => ({ type: "second", value: "2-digit" }),
- },
- {
- test: createLiteralTestFunction("s"),
- fn: (): CallbackResult => ({ type: "second", value: "numeric" }),
- },
- {
- test: createLiteralTestFunction("SSS"),
- fn: (): CallbackResult => ({ type: "fractionalSecond", value: 3 }),
- },
- {
- test: createLiteralTestFunction("SS"),
- fn: (): CallbackResult => ({ type: "fractionalSecond", value: 2 }),
- },
- {
- test: createLiteralTestFunction("S"),
- fn: (): CallbackResult => ({ type: "fractionalSecond", value: 1 }),
- },
-
- {
- test: createLiteralTestFunction("a"),
- fn: (value: unknown): CallbackResult => ({
- type: "dayPeriod",
- value: value as string,
- }),
- },
-
- // quoted literal
- {
- test: createMatchTestFunction(/^(')(?<value>\\.|[^\']*)\1/),
- fn: (match: unknown): CallbackResult => ({
- type: "literal",
- value: (match as RegExpExecArray).groups!.value as string,
- }),
- },
- // literal
- {
- test: createMatchTestFunction(/^.+?\s*/),
- fn: (match: unknown): CallbackResult => ({
- type: "literal",
- value: (match as RegExpExecArray)[0],
- }),
- },
-];
-
-type FormatPart = {
- type: DateTimeFormatPartTypes;
- value: string | number;
- hour12?: boolean;
-};
-type Format = FormatPart[];
-
-export class DateTimeFormatter {
- #format: Format;
-
- constructor(formatString: string, rules: Rule[] = defaultRules) {
- const tokenizer = new Tokenizer(rules);
- this.#format = tokenizer.tokenize(
- formatString,
- ({ type, value, hour12 }) => {
- const result = {
- type,
- value,
- } as unknown as ReceiverResult;
- if (hour12) result.hour12 = hour12 as boolean;
- return result;
- },
- ) as Format;
- }
-
- format(date: Date, options: Options = {}): string {
- let string = "";
-
- const utc = options.timeZone === "UTC";
-
- for (const token of this.#format) {
- const type = token.type;
-
- switch (type) {
- case "year": {
- const value = utc ? date.getUTCFullYear() : date.getFullYear();
- switch (token.value) {
- case "numeric": {
- string += value;
- break;
- }
- case "2-digit": {
- string += digits(value, 2).slice(-2);
- break;
- }
- default:
- throw Error(
- `FormatterError: value "${token.value}" is not supported`,
- );
- }
- break;
- }
- case "month": {
- const value = (utc ? date.getUTCMonth() : date.getMonth()) + 1;
- switch (token.value) {
- case "numeric": {
- string += value;
- break;
- }
- case "2-digit": {
- string += digits(value, 2);
- break;
- }
- default:
- throw Error(
- `FormatterError: value "${token.value}" is not supported`,
- );
- }
- break;
- }
- case "day": {
- const value = utc ? date.getUTCDate() : date.getDate();
- switch (token.value) {
- case "numeric": {
- string += value;
- break;
- }
- case "2-digit": {
- string += digits(value, 2);
- break;
- }
- default:
- throw Error(
- `FormatterError: value "${token.value}" is not supported`,
- );
- }
- break;
- }
- case "hour": {
- let value = utc ? date.getUTCHours() : date.getHours();
- value -= token.hour12 && date.getHours() > 12 ? 12 : 0;
- switch (token.value) {
- case "numeric": {
- string += value;
- break;
- }
- case "2-digit": {
- string += digits(value, 2);
- break;
- }
- default:
- throw Error(
- `FormatterError: value "${token.value}" is not supported`,
- );
- }
- break;
- }
- case "minute": {
- const value = utc ? date.getUTCMinutes() : date.getMinutes();
- switch (token.value) {
- case "numeric": {
- string += value;
- break;
- }
- case "2-digit": {
- string += digits(value, 2);
- break;
- }
- default:
- throw Error(
- `FormatterError: value "${token.value}" is not supported`,
- );
- }
- break;
- }
- case "second": {
- const value = utc ? date.getUTCSeconds() : date.getSeconds();
- switch (token.value) {
- case "numeric": {
- string += value;
- break;
- }
- case "2-digit": {
- string += digits(value, 2);
- break;
- }
- default:
- throw Error(
- `FormatterError: value "${token.value}" is not supported`,
- );
- }
- break;
- }
- case "fractionalSecond": {
- const value = utc
- ? date.getUTCMilliseconds()
- : date.getMilliseconds();
- string += digits(value, Number(token.value));
- break;
- }
- // FIXME(bartlomieju)
- case "timeZoneName": {
- // string += utc ? "Z" : token.value
- break;
- }
- case "dayPeriod": {
- string += token.value ? (date.getHours() >= 12 ? "PM" : "AM") : "";
- break;
- }
- case "literal": {
- string += token.value;
- break;
- }
-
- default:
- throw Error(`FormatterError: { ${token.type} ${token.value} }`);
- }
- }
-
- return string;
- }
-
- parseToParts(string: string): DateTimeFormatPart[] {
- const parts: DateTimeFormatPart[] = [];
-
- for (const token of this.#format) {
- const type = token.type;
-
- let value = "";
- switch (token.type) {
- case "year": {
- switch (token.value) {
- case "numeric": {
- value = /^\d{1,4}/.exec(string)?.[0] as string;
- break;
- }
- case "2-digit": {
- value = /^\d{1,2}/.exec(string)?.[0] as string;
- break;
- }
- }
- break;
- }
- case "month": {
- switch (token.value) {
- case "numeric": {
- value = /^\d{1,2}/.exec(string)?.[0] as string;
- break;
- }
- case "2-digit": {
- value = /^\d{2}/.exec(string)?.[0] as string;
- break;
- }
- case "narrow": {
- value = /^[a-zA-Z]+/.exec(string)?.[0] as string;
- break;
- }
- case "short": {
- value = /^[a-zA-Z]+/.exec(string)?.[0] as string;
- break;
- }
- case "long": {
- value = /^[a-zA-Z]+/.exec(string)?.[0] as string;
- break;
- }
- default:
- throw Error(
- `ParserError: value "${token.value}" is not supported`,
- );
- }
- break;
- }
- case "day": {
- switch (token.value) {
- case "numeric": {
- value = /^\d{1,2}/.exec(string)?.[0] as string;
- break;
- }
- case "2-digit": {
- value = /^\d{2}/.exec(string)?.[0] as string;
- break;
- }
- default:
- throw Error(
- `ParserError: value "${token.value}" is not supported`,
- );
- }
- break;
- }
- case "hour": {
- switch (token.value) {
- case "numeric": {
- value = /^\d{1,2}/.exec(string)?.[0] as string;
- if (token.hour12 && parseInt(value) > 12) {
- console.error(
- `Trying to parse hour greater than 12. Use 'H' instead of 'h'.`,
- );
- }
- break;
- }
- case "2-digit": {
- value = /^\d{2}/.exec(string)?.[0] as string;
- if (token.hour12 && parseInt(value) > 12) {
- console.error(
- `Trying to parse hour greater than 12. Use 'HH' instead of 'hh'.`,
- );
- }
- break;
- }
- default:
- throw Error(
- `ParserError: value "${token.value}" is not supported`,
- );
- }
- break;
- }
- case "minute": {
- switch (token.value) {
- case "numeric": {
- value = /^\d{1,2}/.exec(string)?.[0] as string;
- break;
- }
- case "2-digit": {
- value = /^\d{2}/.exec(string)?.[0] as string;
- break;
- }
- default:
- throw Error(
- `ParserError: value "${token.value}" is not supported`,
- );
- }
- break;
- }
- case "second": {
- switch (token.value) {
- case "numeric": {
- value = /^\d{1,2}/.exec(string)?.[0] as string;
- break;
- }
- case "2-digit": {
- value = /^\d{2}/.exec(string)?.[0] as string;
- break;
- }
- default:
- throw Error(
- `ParserError: value "${token.value}" is not supported`,
- );
- }
- break;
- }
- case "fractionalSecond": {
- value = new RegExp(`^\\d{${token.value}}`).exec(string)
- ?.[0] as string;
- break;
- }
- case "timeZoneName": {
- value = token.value as string;
- break;
- }
- case "dayPeriod": {
- value = /^(A|P)M/.exec(string)?.[0] as string;
- break;
- }
- case "literal": {
- if (!string.startsWith(token.value as string)) {
- throw Error(
- `Literal "${token.value}" not found "${string.slice(0, 25)}"`,
- );
- }
- value = token.value as string;
- break;
- }
-
- default:
- throw Error(`${token.type} ${token.value}`);
- }
-
- if (!value) {
- throw Error(
- `value not valid for token { ${type} ${value} } ${
- string.slice(
- 0,
- 25,
- )
- }`,
- );
- }
- parts.push({ type, value });
-
- string = string.slice(value.length);
- }
-
- if (string.length) {
- throw Error(
- `datetime string was not fully parsed! ${string.slice(0, 25)}`,
- );
- }
-
- return parts;
- }
-
- /** sort & filter dateTimeFormatPart */
- sortDateTimeFormatPart(parts: DateTimeFormatPart[]): DateTimeFormatPart[] {
- let result: DateTimeFormatPart[] = [];
- const typeArray = [
- "year",
- "month",
- "day",
- "hour",
- "minute",
- "second",
- "fractionalSecond",
- ];
- for (const type of typeArray) {
- const current = parts.findIndex((el) => el.type === type);
- if (current !== -1) {
- result = result.concat(parts.splice(current, 1));
- }
- }
- result = result.concat(parts);
- return result;
- }
-
- partsToDate(parts: DateTimeFormatPart[]): Date {
- const date = new Date();
- const utc = parts.find(
- (part) => part.type === "timeZoneName" && part.value === "UTC",
- );
-
- utc ? date.setUTCHours(0, 0, 0, 0) : date.setHours(0, 0, 0, 0);
- for (const part of parts) {
- switch (part.type) {
- case "year": {
- const value = Number(part.value.padStart(4, "20"));
- utc ? date.setUTCFullYear(value) : date.setFullYear(value);
- break;
- }
- case "month": {
- const value = Number(part.value) - 1;
- utc ? date.setUTCMonth(value) : date.setMonth(value);
- break;
- }
- case "day": {
- const value = Number(part.value);
- utc ? date.setUTCDate(value) : date.setDate(value);
- break;
- }
- case "hour": {
- let value = Number(part.value);
- const dayPeriod = parts.find(
- (part: DateTimeFormatPart) => part.type === "dayPeriod",
- );
- if (dayPeriod?.value === "PM") value += 12;
- utc ? date.setUTCHours(value) : date.setHours(value);
- break;
- }
- case "minute": {
- const value = Number(part.value);
- utc ? date.setUTCMinutes(value) : date.setMinutes(value);
- break;
- }
- case "second": {
- const value = Number(part.value);
- utc ? date.setUTCSeconds(value) : date.setSeconds(value);
- break;
- }
- case "fractionalSecond": {
- const value = Number(part.value);
- utc ? date.setUTCMilliseconds(value) : date.setMilliseconds(value);
- break;
- }
- }
- }
- return date;
- }
-
- parse(string: string): Date {
- const parts = this.parseToParts(string);
- const sortParts = this.sortDateTimeFormatPart(parts);
- return this.partsToDate(sortParts);
- }
-}