summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Reichen <timreichen@users.noreply.github.com>2020-09-25 00:06:22 +0200
committerGitHub <noreply@github.com>2020-09-24 18:06:22 -0400
commit9c75e4876f68b36cb8a79bfe5a734d2783c527ce (patch)
tree849f775356d72b0578fce4ed09a5ac5017220e78
parent82db91372f597a0de834937dbb5edabeb68b0138 (diff)
fix(std/datetime):: 12 and 24 support (#7661)
-rw-r--r--std/datetime/README.md38
-rw-r--r--std/datetime/formatter.ts67
-rw-r--r--std/datetime/test.ts91
-rw-r--r--std/datetime/tokenizer.ts11
4 files changed, 148 insertions, 59 deletions
diff --git a/std/datetime/README.md b/std/datetime/README.md
index 62e15df2e..9678ec434 100644
--- a/std/datetime/README.md
+++ b/std/datetime/README.md
@@ -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'
-
-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"
-
-...
+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"
```
### dayOfYear
diff --git a/std/datetime/formatter.ts b/std/datetime/formatter.ts
index 14fb552cd..6f11090f0 100644
--- a/std/datetime/formatter.ts
+++ b/std/datetime/formatter.ts
@@ -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,14 +82,30 @@ 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);
}
diff --git a/std/datetime/test.ts b/std/datetime/test.ts
index 10de786b6..9d4e58121 100644
--- a/std/datetime/test.ts
+++ b/std/datetime/test.ts
@@ -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,31 +77,74 @@ 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(
+ "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(
+ "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(
+ "13:00:00.000",
+ datetime.format(new Date("2019-01-01T13:00:00.000"), "HH:mm:ss.SSS"),
);
assertEquals(
- "03:24:00",
- datetime.format(new Date("2019-01-01T03:24:00"), "hh:mm:ss"),
+ "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(
- "03:24:00.532",
- datetime.format(new Date("2019-01-01T03:24:00.532"), "hh:mm:ss.SSS"),
+ "01:00:00 AM",
+ datetime.format(new Date("2019-01-01T01:00:00"), "hh:mm:ss a"),
);
assertEquals(
- "03:24:00 AM",
- datetime.format(new Date("2019-01-01T03:24:00"), "hh:mm:ss a"),
+ "01:00:00 PM",
+ datetime.format(new Date("2019-01-01T13:00:00"), "hh:mm:ss a"),
);
assertEquals(
- "09:24:00 PM",
- datetime.format(new Date("2019-01-01T21:24:00"), "hh:mm:ss a"),
+ "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);
diff --git a/std/datetime/tokenizer.ts b/std/datetime/tokenizer.ts
index e56dc52db..d1b982b1f 100644
--- a/std/datetime/tokenizer.ts
+++ b/std/datetime/tokenizer.ts
@@ -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;