Skip to content

Commit 8e67d10

Browse files
committed
Improve coverage
1 parent 15a1cee commit 8e67d10

File tree

2 files changed

+563
-233
lines changed

2 files changed

+563
-233
lines changed

datetime/_date_time_formatter.ts

+119-63
Original file line numberDiff line numberDiff line change
@@ -132,53 +132,55 @@ function symbolToFormatPart(symbol: string): FormatPart {
132132
}
133133
}
134134

135-
function formatToFormatParts(format: string) {
136-
const formatParts: FormatPart[] = [];
137-
let index = 0;
138-
while (index < format.length) {
139-
const substring = format.slice(index);
140-
141-
const symbolMatch = SYMBOL_REGEXP.exec(substring);
142-
if (symbolMatch) {
143-
const symbol = symbolMatch.groups!.symbol!;
144-
formatParts.push(symbolToFormatPart(symbol));
145-
index += symbol.length;
146-
continue;
147-
}
148-
149-
const quotedLiteralMatch = QUOTED_LITERAL_REGEXP.exec(substring);
150-
if (quotedLiteralMatch) {
151-
const value = quotedLiteralMatch.groups!.value!;
152-
formatParts.push({ type: "literal", value });
153-
index += quotedLiteralMatch[0].length;
154-
continue;
155-
}
135+
export class DateTimeFormatter {
136+
#formatParts: FormatPart[];
156137

157-
const literalMatch = LITERAL_REGEXP.exec(substring)!;
158-
const value = literalMatch.groups!.value!;
159-
formatParts.push({ type: "literal", value });
160-
index += value.length;
138+
constructor(formatString: string) {
139+
this.#formatParts = DateTimeFormatter.formatToFormatParts(formatString);
161140
}
162141

163-
return formatParts;
164-
}
142+
static formatToFormatParts(format: string) {
143+
const formatParts: FormatPart[] = [];
144+
let index = 0;
145+
while (index < format.length) {
146+
const substring = format.slice(index);
147+
148+
const symbolMatch = SYMBOL_REGEXP.exec(substring);
149+
if (symbolMatch) {
150+
const symbol = symbolMatch.groups!.symbol!;
151+
formatParts.push(symbolToFormatPart(symbol));
152+
index += symbol.length;
153+
continue;
154+
}
165155

166-
export class DateTimeFormatter {
167-
#formatParts: FormatPart[];
156+
const quotedLiteralMatch = QUOTED_LITERAL_REGEXP.exec(substring);
157+
if (quotedLiteralMatch) {
158+
const value = quotedLiteralMatch.groups!.value!;
159+
formatParts.push({ type: "literal", value });
160+
index += quotedLiteralMatch[0].length;
161+
continue;
162+
}
168163

169-
constructor(formatString: string) {
170-
this.#formatParts = formatToFormatParts(formatString);
164+
const literalMatch = LITERAL_REGEXP.exec(substring)!;
165+
const value = literalMatch.groups!.value!;
166+
formatParts.push({ type: "literal", value });
167+
index += value.length;
168+
}
169+
170+
return formatParts;
171171
}
172172

173-
format(date: Date, options: Options = {}): string {
173+
static formatPartsToString(
174+
date: Date,
175+
parts: FormatPart[],
176+
options: Options = {},
177+
): string {
174178
let string = "";
175179

176180
const utc = options.timeZone === "UTC";
177181

178-
for (const part of this.#formatParts) {
179-
const type = part.type;
180-
181-
switch (type) {
182+
for (const part of parts) {
183+
switch (part.type) {
182184
case "year": {
183185
const value = utc ? date.getUTCFullYear() : date.getFullYear();
184186
switch (part.value) {
@@ -292,6 +294,14 @@ export class DateTimeFormatter {
292294
break;
293295
}
294296
case "fractionalSecond": {
297+
if (
298+
typeof part.value === "string" || part.value < 1 || part.value > 3
299+
) {
300+
throw new Error(
301+
`FormatterError: DateTimeFormatPartType "fractionalSecond" does not support value ${part.value}`,
302+
);
303+
}
304+
295305
const value = utc
296306
? date.getUTCMilliseconds()
297307
: date.getMilliseconds();
@@ -305,7 +315,9 @@ export class DateTimeFormatter {
305315
}
306316

307317
// TODO(WWRS): add support for time zones
308-
throw new Error(`FormatterError: Time zone is not supported`);
318+
throw new Error(
319+
`FormatterError: DateTimeFormatPartType "timeZoneName" does not support value ${part.value}`,
320+
);
309321
}
310322
case "dayPeriod": {
311323
switch (part.value) {
@@ -336,10 +348,13 @@ export class DateTimeFormatter {
336348
return string;
337349
}
338350

339-
formatToParts(string: string): DateTimeFormatPart[] {
340-
const parts: DateTimeFormatPart[] = [];
351+
static formatPartsToDateTimeParts(
352+
string: string,
353+
formatParts: FormatPart[],
354+
): DateTimeFormatPart[] {
355+
const dateTimeParts: DateTimeFormatPart[] = [];
341356

342-
for (const part of this.#formatParts) {
357+
for (const part of formatParts) {
343358
let value: string | undefined;
344359
// The number of chars consumed in the parse, defaults to `value.length`
345360
let parsedLength: number | undefined;
@@ -434,7 +449,7 @@ export class DateTimeFormatter {
434449
}
435450
default:
436451
throw new Error(
437-
`ParserError: DateTimeFormatPartType "hour" does not support value "${part.value}"`,
452+
`ParserError: DateTimeFormatPartType "hour" does not support value ${part.value}`,
438453
);
439454
}
440455
break;
@@ -474,6 +489,14 @@ export class DateTimeFormatter {
474489
break;
475490
}
476491
case "fractionalSecond": {
492+
if (
493+
typeof part.value === "string" || part.value < 1 || part.value > 3
494+
) {
495+
throw new Error(
496+
`ParserError: DateTimeFormatPartType "fractionalSecond" does not support value ${part.value}`,
497+
);
498+
}
499+
477500
const re = new RegExp(`^\\d{${part.value}}`);
478501
value = re.exec(string)?.[0];
479502
break;
@@ -484,22 +507,49 @@ export class DateTimeFormatter {
484507
break;
485508
}
486509
case "dayPeriod": {
487-
value = /^[AP](?:\.M\.|M\.?)/i.exec(string)?.[0];
488-
parsedLength = value?.length;
489-
switch (value?.toUpperCase()) {
490-
case "AM":
491-
case "AM.":
492-
case "A.M.":
493-
value = "AM";
510+
switch (part.value) {
511+
case "short":
512+
case "long": {
513+
value = /^[AP](?:\.M\.|M\.?)/i.exec(string)?.[0];
514+
parsedLength = value?.length;
515+
switch (value?.toUpperCase()) {
516+
case "AM":
517+
case "AM.":
518+
case "A.M.":
519+
value = "AM";
520+
break;
521+
case "PM":
522+
case "PM.":
523+
case "P.M.":
524+
value = "PM";
525+
break;
526+
default:
527+
throw new Error(
528+
`ParserError: Could not parse dayPeriod from "${string}"`,
529+
);
530+
}
494531
break;
495-
case "PM":
496-
case "PM.":
497-
case "P.M.":
498-
value = "PM";
532+
}
533+
case "narrow": {
534+
value = /^[AP]/i.exec(string)?.[0];
535+
parsedLength = 1;
536+
switch (value?.toUpperCase()) {
537+
case "A":
538+
value = "AM";
539+
break;
540+
case "P":
541+
value = "PM";
542+
break;
543+
default:
544+
throw new Error(
545+
`ParserError: Could not parse dayPeriod from "${string}"`,
546+
);
547+
}
499548
break;
549+
}
500550
default:
501551
throw new Error(
502-
`ParserError: Could not parse dayPeriod from "${value}"`,
552+
`ParserError: DateTimeFormatPartType "dayPeriod" does not support value ${part.value}`,
503553
);
504554
}
505555
break;
@@ -536,7 +586,7 @@ export class DateTimeFormatter {
536586
}`,
537587
);
538588
}
539-
parts.push({ type: part.type, value });
589+
dateTimeParts.push({ type: part.type, value });
540590

541591
string = string.slice(parsedLength ?? value.length);
542592
}
@@ -549,10 +599,10 @@ export class DateTimeFormatter {
549599
);
550600
}
551601

552-
return parts;
602+
return dateTimeParts;
553603
}
554604

555-
partsToDate(parts: DateTimeFormatPart[]): Date {
605+
static dateTimePartsToDate(parts: DateTimeFormatPart[]): Date {
556606
let year;
557607
let month;
558608
let day;
@@ -612,11 +662,6 @@ export class DateTimeFormatter {
612662
}
613663

614664
if (dayPeriod !== undefined) {
615-
if (hour === undefined) {
616-
throw new Error(
617-
`ParserError: Cannot use dayPeriod without hour`,
618-
);
619-
}
620665
if (hour > 12) {
621666
throw new Error(
622667
`ParserError: Cannot use dayPeriod with hour greater than 12, hour is "${hour}"`,
@@ -647,8 +692,19 @@ export class DateTimeFormatter {
647692
);
648693
}
649694

695+
format(date: Date, options: Options = {}): string {
696+
return DateTimeFormatter.formatPartsToString(
697+
date,
698+
this.#formatParts,
699+
options,
700+
);
701+
}
702+
650703
parse(string: string): Date {
651-
const parts = this.formatToParts(string);
652-
return this.partsToDate(parts);
704+
const parts = DateTimeFormatter.formatPartsToDateTimeParts(
705+
string,
706+
this.#formatParts,
707+
);
708+
return DateTimeFormatter.dateTimePartsToDate(parts);
653709
}
654710
}

0 commit comments

Comments
 (0)