mirror of
https://github.com/denoland/deno.git
synced 2025-01-11 08:33:43 -05:00
fix(std/datetime):: 12 and 24 support (#7661)
This commit is contained in:
parent
82db91372f
commit
9c75e4876f
4 changed files with 147 additions and 58 deletions
|
@ -4,7 +4,9 @@ Simple helper to help parse date strings into `Date`, with additional functions.
|
|||
|
||||
## 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
|
||||
- `yy` - 2-digit year
|
||||
|
@ -13,8 +15,10 @@ The following symbols are supported:
|
|||
- `d` - numeric day
|
||||
- `dd` - 2-digit day
|
||||
|
||||
- `h` - numeric hour
|
||||
- `hh` - 2-digit hour
|
||||
- `H` - numeric hour (0-23 hours)
|
||||
- `HH` - 2-digit hour (00-23 hours)
|
||||
- `h` - numeric hour (1-12 hours)
|
||||
- `hh` - 2-digit hour (01-12 hours)
|
||||
- `m` - numeric minute
|
||||
- `mm` - 2-digit minute
|
||||
- `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("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("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("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("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)
|
||||
...
|
||||
```
|
||||
|
||||
|
@ -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`.
|
||||
|
||||
```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), "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, 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), "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), "'today:' yyyy-MM-dd") // output : "today: 2019-01-20"
|
||||
|
||||
...
|
||||
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), "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 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, 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"
|
||||
```
|
||||
|
||||
### dayOfYear
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
TestFunction,
|
||||
TestResult,
|
||||
Tokenizer,
|
||||
ReceiverResult,
|
||||
} from "./tokenizer.ts";
|
||||
|
||||
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 = [
|
||||
{
|
||||
test: createLiteralTestFunction("yyyy"),
|
||||
|
@ -81,13 +82,29 @@ const defaultRules = [
|
|||
},
|
||||
|
||||
{
|
||||
test: createLiteralTestFunction("hh"),
|
||||
test: createLiteralTestFunction("HH"),
|
||||
fn: (): CallbackResult => ({ type: "hour", value: "2-digit" }),
|
||||
},
|
||||
{
|
||||
test: createLiteralTestFunction("h"),
|
||||
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" }),
|
||||
|
@ -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[];
|
||||
|
||||
export class DateTimeFormatter {
|
||||
|
@ -151,19 +172,23 @@ export class DateTimeFormatter {
|
|||
|
||||
constructor(formatString: string, rules: Rule[] = defaultRules) {
|
||||
const tokenizer = new Tokenizer(rules);
|
||||
this.#format = tokenizer.tokenize(formatString, ({ type, value }) => ({
|
||||
type,
|
||||
value,
|
||||
})) as Format;
|
||||
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";
|
||||
const hour12 = this.#format.find(
|
||||
(token: FormatPart) => token.type === "dayPeriod",
|
||||
);
|
||||
|
||||
for (const token of this.#format) {
|
||||
const type = token.type;
|
||||
|
@ -225,7 +250,7 @@ export class DateTimeFormatter {
|
|||
}
|
||||
case "hour": {
|
||||
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) {
|
||||
case "numeric": {
|
||||
string += value;
|
||||
|
@ -290,7 +315,7 @@ export class DateTimeFormatter {
|
|||
// break
|
||||
}
|
||||
case "dayPeriod": {
|
||||
string += hour12 ? (date.getHours() >= 12 ? "PM" : "AM") : "";
|
||||
string += token.value ? (date.getHours() >= 12 ? "PM" : "AM") : "";
|
||||
break;
|
||||
}
|
||||
case "literal": {
|
||||
|
@ -377,10 +402,20 @@ export class DateTimeFormatter {
|
|||
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:
|
||||
|
@ -425,9 +460,8 @@ export class DateTimeFormatter {
|
|||
break;
|
||||
}
|
||||
case "fractionalSecond": {
|
||||
value = new RegExp(`^\\d{${token.value}}`).exec(
|
||||
string,
|
||||
)?.[0] as string;
|
||||
value = new RegExp(`^\\d{${token.value}}`).exec(string)
|
||||
?.[0] as string;
|
||||
break;
|
||||
}
|
||||
case "timeZoneName": {
|
||||
|
@ -463,6 +497,7 @@ export class DateTimeFormatter {
|
|||
);
|
||||
}
|
||||
parts.push({ type, value });
|
||||
|
||||
string = string.slice(value.length);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,39 +6,43 @@ Deno.test({
|
|||
name: "[std/datetime] parse",
|
||||
fn: () => {
|
||||
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),
|
||||
);
|
||||
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),
|
||||
);
|
||||
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),
|
||||
);
|
||||
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),
|
||||
);
|
||||
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),
|
||||
);
|
||||
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),
|
||||
);
|
||||
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),
|
||||
);
|
||||
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),
|
||||
);
|
||||
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),
|
||||
);
|
||||
assertEquals(
|
||||
|
@ -73,30 +77,73 @@ Deno.test({
|
|||
Deno.test({
|
||||
name: "[std/datetime] format",
|
||||
fn: () => {
|
||||
// Date
|
||||
assertEquals(
|
||||
"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(
|
||||
"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(
|
||||
"03:24:00",
|
||||
datetime.format(new Date("2019-01-01T03:24:00"), "hh:mm:ss"),
|
||||
"13:00:00",
|
||||
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(
|
||||
"03:24:00.532",
|
||||
datetime.format(new Date("2019-01-01T03:24:00.532"), "hh:mm:ss.SSS"),
|
||||
"01:00:00",
|
||||
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(
|
||||
"03:24:00 AM",
|
||||
datetime.format(new Date("2019-01-01T03:24:00"), "hh:mm:ss a"),
|
||||
"13:00:00.000",
|
||||
datetime.format(new Date("2019-01-01T13:00:00.000"), "HH:mm:ss.SSS"),
|
||||
);
|
||||
assertEquals(
|
||||
"09:24:00 PM",
|
||||
datetime.format(new Date("2019-01-01T21:24:00"), "hh:mm:ss a"),
|
||||
"13:00:00.123",
|
||||
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(
|
||||
datetime.format(new Date(2019, 0, 20), "'today:' yyyy-MM-dd"),
|
||||
"today: 2019-01-20",
|
||||
|
@ -181,9 +228,9 @@ Deno.test({
|
|||
Deno.test({
|
||||
name: "[std/datetime] weekOfYear",
|
||||
fn: () => {
|
||||
assertEquals(datetime.weekOfYear(new Date("2020-01-05T03:24:00")), 1);
|
||||
assertEquals(datetime.weekOfYear(new Date("2020-12-28T03:24:00")), 53); // 53 weeks in 2020
|
||||
assertEquals(datetime.weekOfYear(new Date("2020-06-28T03:24:00")), 26);
|
||||
assertEquals(datetime.weekOfYear(new Date("2020-01-05T03:00:00")), 1);
|
||||
assertEquals(datetime.weekOfYear(new Date("2020-12-28T03:00:00")), 53); // 53 weeks in 2020
|
||||
assertEquals(datetime.weekOfYear(new Date("2020-06-28T03:00:00")), 26);
|
||||
|
||||
// iso weeks year starting sunday
|
||||
assertEquals(datetime.weekOfYear(new Date(2012, 0, 1)), 52);
|
||||
|
|
|
@ -4,12 +4,17 @@ export type Token = {
|
|||
type: string;
|
||||
value: string | number;
|
||||
index: number;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
interface ReceiverResult {
|
||||
[name: string]: string | number;
|
||||
export interface ReceiverResult {
|
||||
[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;
|
||||
|
||||
export type TestResult = { value: unknown; length: number } | undefined;
|
||||
|
|
Loading…
Reference in a new issue