1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-11 16:42:21 -05:00

fix(std/datetime):: 12 and 24 support (#7661)

This commit is contained in:
Tim Reichen 2020-09-25 00:06:22 +02:00 committed by GitHub
parent 82db91372f
commit 9c75e4876f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 147 additions and 58 deletions

View file

@ -4,7 +4,9 @@ Simple helper to help parse date strings into `Date`, with additional functions.
## Usage ## Usage
The following symbols are supported: The following symbols from
[unicode LDML](http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table)
are supported:
- `yyyy` - numeric year - `yyyy` - numeric year
- `yy` - 2-digit year - `yy` - 2-digit year
@ -13,8 +15,10 @@ The following symbols are supported:
- `d` - numeric day - `d` - numeric day
- `dd` - 2-digit day - `dd` - 2-digit day
- `h` - numeric hour - `H` - numeric hour (0-23 hours)
- `hh` - 2-digit hour - `HH` - 2-digit hour (00-23 hours)
- `h` - numeric hour (1-12 hours)
- `hh` - 2-digit hour (01-12 hours)
- `m` - numeric minute - `m` - numeric minute
- `mm` - 2-digit minute - `mm` - 2-digit minute
- `s` - numeric second - `s` - numeric second
@ -38,10 +42,10 @@ import { parse } from 'https://deno.land/std/datetime/mod.ts'
parse("20-01-2019", "dd-MM-yyyy") // output : new Date(2019, 0, 20) parse("20-01-2019", "dd-MM-yyyy") // output : new Date(2019, 0, 20)
parse("2019-01-20", "yyyy-MM-dd") // output : new Date(2019, 0, 20) parse("2019-01-20", "yyyy-MM-dd") // output : new Date(2019, 0, 20)
parse("2019-01-20", "dd.MM.yyyy") // output : new Date(2019, 0, 20) parse("2019-01-20", "dd.MM.yyyy") // output : new Date(2019, 0, 20)
parse("01-20-2019 16:34", "MM-dd-yyyy hh:mm") // output : new Date(2019, 0, 20, 16, 34) parse("01-20-2019 16:34", "MM-dd-yyyy HH:mm") // output : new Date(2019, 0, 20, 16, 34)
parse("01-20-2019 04:34 PM", "MM-dd-yyyy hh:mm a") // output : new Date(2019, 0, 20, 16, 34) parse("01-20-2019 04:34 PM", "MM-dd-yyyy hh:mm a") // output : new Date(2019, 0, 20, 16, 34)
parse("16:34 01-20-2019", "hh:mm MM-dd-yyyy") // output : new Date(2019, 0, 20, 16, 34) parse("16:34 01-20-2019", "HH:mm MM-dd-yyyy") // output : new Date(2019, 0, 20, 16, 34)
parse("01-20-2019 16:34:23.123", "MM-dd-yyyy hh:mm:ss.SSS") // output : new Date(2019, 0, 20, 16, 34, 23, 123) parse("01-20-2019 16:34:23.123", "MM-dd-yyyy HH:mm:ss.SSS") // output : new Date(2019, 0, 20, 16, 34, 23, 123)
... ...
``` ```
@ -50,18 +54,16 @@ parse("01-20-2019 16:34:23.123", "MM-dd-yyyy hh:mm:ss.SSS") // output : new Date
Takes an input `date` and a `formatString` to format to a `string`. Takes an input `date` and a `formatString` to format to a `string`.
```ts ```ts
import { format } from 'https://deno.land/std/datetime/mod.ts' import { format } from "https://deno.land/std/datetime/mod.ts";
format(new Date(2019, 0, 20), "dd-MM-yyyy") // output : "20-01-2019" format(new Date(2019, 0, 20), "dd-MM-yyyy"); // output : "20-01-2019"
format(new Date(2019, 0, 20), "yyyy-MM-dd") // output : "2019-01-20" format(new Date(2019, 0, 20), "yyyy-MM-dd"); // output : "2019-01-20"
format(new Date(2019, 0, 20), "dd.MM.yyyy") // output : "2019-01-20" format(new Date(2019, 0, 20), "dd.MM.yyyy"); // output : "2019-01-20"
format(new Date(2019, 0, 20, 16, 34), "MM-dd-yyyy hh:mm") // output : "01-20-2019 16:34" format(new Date(2019, 0, 20, 16, 34), "MM-dd-yyyy HH:mm"); // output : "01-20-2019 16:34"
format(new Date(2019, 0, 20, 16, 34), "MM-dd-yyyy hh:mm a") // output : "01-20-2019 04:34 PM" format(new Date(2019, 0, 20, 16, 34), "MM-dd-yyyy hh:mm a"); // output : "01-20-2019 04:34 PM"
format(new Date(2019, 0, 20, 16, 34), "hh:mm MM-dd-yyyy") // output : "16:34 01-20-2019" format(new Date(2019, 0, 20, 16, 34), "HH:mm MM-dd-yyyy"); // output : "16:34 01-20-2019"
format(new Date(2019, 0, 20, 16, 34, 23, 123), "MM-dd-yyyy hh:mm:ss.SSS") // output : "01-20-2019 16:34:23.123" format(new Date(2019, 0, 20, 16, 34, 23, 123), "MM-dd-yyyy HH:mm:ss.SSS"); // output : "01-20-2019 16:34:23.123"
format(new Date(2019, 0, 20), "'today:' yyyy-MM-dd") // output : "today: 2019-01-20" format(new Date(2019, 0, 20), "'today:' yyyy-MM-dd"); // output : "today: 2019-01-20"
...
``` ```
### dayOfYear ### dayOfYear

View file

@ -5,6 +5,7 @@ import {
TestFunction, TestFunction,
TestResult, TestResult,
Tokenizer, Tokenizer,
ReceiverResult,
} from "./tokenizer.ts"; } from "./tokenizer.ts";
function digits(value: string | number, count = 2): string { function digits(value: string | number, count = 2): string {
@ -52,7 +53,7 @@ function createMatchTestFunction(match: RegExp): TestFunction {
}; };
} }
// according to unicode symbols (http://userguide.icu-project.org/formatparse/datetime) // according to unicode symbols (http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table)
const defaultRules = [ const defaultRules = [
{ {
test: createLiteralTestFunction("yyyy"), test: createLiteralTestFunction("yyyy"),
@ -81,13 +82,29 @@ const defaultRules = [
}, },
{ {
test: createLiteralTestFunction("hh"), test: createLiteralTestFunction("HH"),
fn: (): CallbackResult => ({ type: "hour", value: "2-digit" }), fn: (): CallbackResult => ({ type: "hour", value: "2-digit" }),
}, },
{ {
test: createLiteralTestFunction("h"), test: createLiteralTestFunction("H"),
fn: (): CallbackResult => ({ type: "hour", value: "numeric" }), 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"), test: createLiteralTestFunction("mm"),
fn: (): CallbackResult => ({ type: "minute", value: "2-digit" }), fn: (): CallbackResult => ({ type: "minute", value: "2-digit" }),
@ -143,7 +160,11 @@ const defaultRules = [
}, },
]; ];
type FormatPart = { type: DateTimeFormatPartTypes; value: string | number }; type FormatPart = {
type: DateTimeFormatPartTypes;
value: string | number;
hour12?: boolean;
};
type Format = FormatPart[]; type Format = FormatPart[];
export class DateTimeFormatter { export class DateTimeFormatter {
@ -151,19 +172,23 @@ export class DateTimeFormatter {
constructor(formatString: string, rules: Rule[] = defaultRules) { constructor(formatString: string, rules: Rule[] = defaultRules) {
const tokenizer = new Tokenizer(rules); const tokenizer = new Tokenizer(rules);
this.#format = tokenizer.tokenize(formatString, ({ type, value }) => ({ this.#format = tokenizer.tokenize(
type, formatString,
value, ({ type, value, hour12 }) => {
})) as Format; 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 { format(date: Date, options: Options = {}): string {
let string = ""; let string = "";
const utc = options.timeZone === "UTC"; const utc = options.timeZone === "UTC";
const hour12 = this.#format.find(
(token: FormatPart) => token.type === "dayPeriod",
);
for (const token of this.#format) { for (const token of this.#format) {
const type = token.type; const type = token.type;
@ -225,7 +250,7 @@ export class DateTimeFormatter {
} }
case "hour": { case "hour": {
let value = utc ? date.getUTCHours() : date.getHours(); let value = utc ? date.getUTCHours() : date.getHours();
value -= hour12 && date.getHours() > 12 ? 12 : 0; value -= token.hour12 && date.getHours() > 12 ? 12 : 0;
switch (token.value) { switch (token.value) {
case "numeric": { case "numeric": {
string += value; string += value;
@ -290,7 +315,7 @@ export class DateTimeFormatter {
// break // break
} }
case "dayPeriod": { case "dayPeriod": {
string += hour12 ? (date.getHours() >= 12 ? "PM" : "AM") : ""; string += token.value ? (date.getHours() >= 12 ? "PM" : "AM") : "";
break; break;
} }
case "literal": { case "literal": {
@ -377,10 +402,20 @@ export class DateTimeFormatter {
switch (token.value) { switch (token.value) {
case "numeric": { case "numeric": {
value = /^\d{1,2}/.exec(string)?.[0] as string; 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; break;
} }
case "2-digit": { case "2-digit": {
value = /^\d{2}/.exec(string)?.[0] as string; 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; break;
} }
default: default:
@ -425,9 +460,8 @@ export class DateTimeFormatter {
break; break;
} }
case "fractionalSecond": { case "fractionalSecond": {
value = new RegExp(`^\\d{${token.value}}`).exec( value = new RegExp(`^\\d{${token.value}}`).exec(string)
string, ?.[0] as string;
)?.[0] as string;
break; break;
} }
case "timeZoneName": { case "timeZoneName": {
@ -463,6 +497,7 @@ export class DateTimeFormatter {
); );
} }
parts.push({ type, value }); parts.push({ type, value });
string = string.slice(value.length); string = string.slice(value.length);
} }

View file

@ -6,39 +6,43 @@ Deno.test({
name: "[std/datetime] parse", name: "[std/datetime] parse",
fn: () => { fn: () => {
assertEquals( assertEquals(
datetime.parse("01-03-2019 16:30", "MM-dd-yyyy hh:mm"), datetime.parse("01-03-2019 16:30", "MM-dd-yyyy HH:mm"),
new Date(2019, 0, 3, 16, 30), new Date(2019, 0, 3, 16, 30),
); );
assertEquals( assertEquals(
datetime.parse("01.03.2019 16:30", "MM.dd.yyyy hh:mm"), datetime.parse("01.03.2019 16:30", "MM.dd.yyyy HH:mm"),
new Date(2019, 0, 3, 16, 30), new Date(2019, 0, 3, 16, 30),
); );
assertEquals( assertEquals(
datetime.parse("03-01-2019 16:31", "dd-MM-yyyy hh:mm"), datetime.parse("01.03.2019 16:30", "MM.dd.yyyy HH:mm"),
new Date(2019, 0, 3, 16, 30),
);
assertEquals(
datetime.parse("03-01-2019 16:31", "dd-MM-yyyy HH:mm"),
new Date(2019, 0, 3, 16, 31), new Date(2019, 0, 3, 16, 31),
); );
assertEquals( assertEquals(
datetime.parse("2019-01-03 16:32", "yyyy-MM-dd hh:mm"), datetime.parse("2019-01-03 16:32", "yyyy-MM-dd HH:mm"),
new Date(2019, 0, 3, 16, 32), new Date(2019, 0, 3, 16, 32),
); );
assertEquals( assertEquals(
datetime.parse("16:33 01-03-2019", "hh:mm MM-dd-yyyy"), datetime.parse("16:33 01-03-2019", "HH:mm MM-dd-yyyy"),
new Date(2019, 0, 3, 16, 33), new Date(2019, 0, 3, 16, 33),
); );
assertEquals( assertEquals(
datetime.parse("01-03-2019 16:33:23.123", "MM-dd-yyyy hh:mm:ss.SSS"), datetime.parse("01-03-2019 16:33:23.123", "MM-dd-yyyy HH:mm:ss.SSS"),
new Date(2019, 0, 3, 16, 33, 23, 123), new Date(2019, 0, 3, 16, 33, 23, 123),
); );
assertEquals( assertEquals(
datetime.parse("01-03-2019 09:33 PM", "MM-dd-yyyy hh:mm a"), datetime.parse("01-03-2019 09:33 PM", "MM-dd-yyyy HH:mm a"),
new Date(2019, 0, 3, 21, 33), new Date(2019, 0, 3, 21, 33),
); );
assertEquals( assertEquals(
datetime.parse("16:34 03-01-2019", "hh:mm dd-MM-yyyy"), datetime.parse("16:34 03-01-2019", "HH:mm dd-MM-yyyy"),
new Date(2019, 0, 3, 16, 34), new Date(2019, 0, 3, 16, 34),
); );
assertEquals( assertEquals(
datetime.parse("16:35 2019-01-03", "hh:mm yyyy-MM-dd"), datetime.parse("16:35 2019-01-03", "HH:mm yyyy-MM-dd"),
new Date(2019, 0, 3, 16, 35), new Date(2019, 0, 3, 16, 35),
); );
assertEquals( assertEquals(
@ -73,30 +77,73 @@ Deno.test({
Deno.test({ Deno.test({
name: "[std/datetime] format", name: "[std/datetime] format",
fn: () => { fn: () => {
// Date
assertEquals( assertEquals(
"2019-01-01", "2019-01-01",
datetime.format(new Date("2019-01-01T03:24:00"), "yyyy-MM-dd"), datetime.format(new Date("2019-01-01"), "yyyy-MM-dd"),
); );
assertEquals( assertEquals(
"01.01.2019", "01.01.2019",
datetime.format(new Date("2019-01-01T03:24:00"), "dd.MM.yyyy"), datetime.format(new Date("2019-01-01"), "dd.MM.yyyy"),
);
// 00 hours
assertEquals(
"01:00:00",
datetime.format(new Date("2019-01-01T01:00:00"), "HH:mm:ss"),
); );
assertEquals( assertEquals(
"03:24:00", "13:00:00",
datetime.format(new Date("2019-01-01T03:24:00"), "hh:mm:ss"), datetime.format(new Date("2019-01-01T13:00:00"), "HH:mm:ss"),
);
// 12 hours
assertEquals(
"01:00:00",
datetime.format(new Date("2019-01-01T01:00:00"), "hh:mm:ss"),
); );
assertEquals( assertEquals(
"03:24:00.532", "01:00:00",
datetime.format(new Date("2019-01-01T03:24:00.532"), "hh:mm:ss.SSS"), datetime.format(new Date("2019-01-01T13:00:00"), "hh:mm:ss"),
);
// milliseconds
assertEquals(
"13:00:00.000",
datetime.format(new Date("2019-01-01T13:00:00"), "HH:mm:ss.SSS"),
); );
assertEquals( assertEquals(
"03:24:00 AM", "13:00:00.000",
datetime.format(new Date("2019-01-01T03:24:00"), "hh:mm:ss a"), datetime.format(new Date("2019-01-01T13:00:00.000"), "HH:mm:ss.SSS"),
); );
assertEquals( assertEquals(
"09:24:00 PM", "13:00:00.123",
datetime.format(new Date("2019-01-01T21:24:00"), "hh:mm:ss a"), datetime.format(new Date("2019-01-01T13:00:00.123"), "HH:mm:ss.SSS"),
); );
// day period
assertEquals(
"01:00:00 AM",
datetime.format(new Date("2019-01-01T01:00:00"), "HH:mm:ss a"),
);
assertEquals(
"01:00:00 AM",
datetime.format(new Date("2019-01-01T01:00:00"), "hh:mm:ss a"),
);
assertEquals(
"01:00:00 PM",
datetime.format(new Date("2019-01-01T13:00:00"), "hh:mm:ss a"),
);
assertEquals(
"21:00:00 PM",
datetime.format(new Date("2019-01-01T21:00:00"), "HH:mm:ss a"),
);
assertEquals(
"09:00:00 PM",
datetime.format(new Date("2019-01-01T21:00:00"), "hh:mm:ss a"),
);
// quoted literal
assertEquals( assertEquals(
datetime.format(new Date(2019, 0, 20), "'today:' yyyy-MM-dd"), datetime.format(new Date(2019, 0, 20), "'today:' yyyy-MM-dd"),
"today: 2019-01-20", "today: 2019-01-20",
@ -181,9 +228,9 @@ Deno.test({
Deno.test({ Deno.test({
name: "[std/datetime] weekOfYear", name: "[std/datetime] weekOfYear",
fn: () => { fn: () => {
assertEquals(datetime.weekOfYear(new Date("2020-01-05T03:24:00")), 1); assertEquals(datetime.weekOfYear(new Date("2020-01-05T03:00:00")), 1);
assertEquals(datetime.weekOfYear(new Date("2020-12-28T03:24:00")), 53); // 53 weeks in 2020 assertEquals(datetime.weekOfYear(new Date("2020-12-28T03:00:00")), 53); // 53 weeks in 2020
assertEquals(datetime.weekOfYear(new Date("2020-06-28T03:24:00")), 26); assertEquals(datetime.weekOfYear(new Date("2020-06-28T03:00:00")), 26);
// iso weeks year starting sunday // iso weeks year starting sunday
assertEquals(datetime.weekOfYear(new Date(2012, 0, 1)), 52); assertEquals(datetime.weekOfYear(new Date(2012, 0, 1)), 52);

View file

@ -4,12 +4,17 @@ export type Token = {
type: string; type: string;
value: string | number; value: string | number;
index: number; index: number;
[key: string]: unknown;
}; };
interface ReceiverResult { export interface ReceiverResult {
[name: string]: string | number; [name: string]: string | number | unknown;
} }
export type CallbackResult = { type: string; value: string | number }; export type CallbackResult = {
type: string;
value: string | number;
[key: string]: unknown;
};
type CallbackFunction = (value: unknown) => CallbackResult; type CallbackFunction = (value: unknown) => CallbackResult;
export type TestResult = { value: unknown; length: number } | undefined; export type TestResult = { value: unknown; length: number } | undefined;