summaryrefslogtreecommitdiff
path: root/std/datetime/formatter.ts
diff options
context:
space:
mode:
authorCasper Beyer <caspervonb@pm.me>2021-02-02 19:05:46 +0800
committerGitHub <noreply@github.com>2021-02-02 12:05:46 +0100
commit6abf126c2a7a451cded8c6b5e6ddf1b69c84055d (patch)
treefd94c013a19fcb38954844085821ec1601c20e18 /std/datetime/formatter.ts
parenta2b5d44f1aa9d64f448a2a3cc2001272e2f60b98 (diff)
chore: remove std directory (#9361)
This removes the std folder from the tree. Various parts of the tests are pretty tightly dependent on std (47 direct imports and 75 indirect imports, not counting the cli tests that use them as fixtures) so I've added std as a submodule for now.
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);
- }
-}