|
2 | 2 | * _Times are parsed as UTC if no offset is specified_
|
3 | 3 | */
|
4 | 4 | export class IsoDateParts {
|
5 |
| - static DEFAULT_TIMEZONE_OFFSET = { |
6 |
| - hours: 0, |
7 |
| - minutes: 0, |
8 |
| - }; |
9 |
| - |
10 | 5 | static FULL_DATE_REGEX = /^([+-]\d{6}|\d{4})-?([01]\d)-?([0-3]\d)$/;
|
11 | 6 | static DATETIME_REGEX = /^([+-]\d{6}|\d{4})-?([01]\d)-?([0-3]\d)[Tt ]([0-2]\d(?:[\.\,]\d+)?)(?::?([0-5]\d(?:[\.\,]\d+)?)(?::?([0-5]\d))?(?:[\.\,](\d{1,9}))?)?(Z|[+-][0-2]\d(?::?[0-5]\d)?)?$/;
|
12 | 7 | static TIMEZONE_REGEX = /^([+-]\d{2})(?::?(\d{2}))?$/;
|
13 | 8 | static IS_FRACTIONAL_REGEX = /^\d+[\.\,]\d+$/;
|
14 | 9 |
|
15 |
| - static getTimezoneOffset(offset = "") { |
16 |
| - if(offset === "Z") { |
17 |
| - return this.DEFAULT_TIMEZONE_OFFSET; |
18 |
| - } |
19 |
| - let match = offset.match(this.TIMEZONE_REGEX); |
20 |
| - if(!match) { |
21 |
| - return this.DEFAULT_TIMEZONE_OFFSET; |
22 |
| - } |
| 10 | + static getTimezoneOffset(offset = "Z") { |
| 11 | + let [, hours = "0", minutes = "0"] = offset.match(this.TIMEZONE_REGEX) ?? []; |
23 | 12 |
|
24 |
| - let [hours, minutes] = [match[1], match[2]]; |
25 |
| - let sign = hours[0] === '-' ? -1 : 1; |
| 13 | + let sign = hours[0] === '-' ? -1 : 1; |
26 | 14 | return {
|
27 |
| - hours: parseInt(hours, 10) || 0, |
28 |
| - minutes: (parseInt(minutes, 10) || 0) * sign |
| 15 | + hours: parseInt(hours, 10), |
| 16 | + minutes: parseInt(minutes, 10) * sign |
29 | 17 | };
|
30 | 18 | }
|
31 | 19 |
|
32 | 20 | /** @param {RegExpMatchArray} match */
|
33 |
| - static getByDateTime(match) { |
34 |
| - let offset = this.getTimezoneOffset(match[8]); |
| 21 | + static getByDateTime( |
| 22 | + _, // full match |
| 23 | + year = "", |
| 24 | + month = "0", // 0-indexed default |
| 25 | + day = "1", // 1-indexed default |
| 26 | + hours = "0", |
| 27 | + minutes = "0", |
| 28 | + seconds = "0", |
| 29 | + milliseconds = "0", |
| 30 | + timezone = "Z", |
| 31 | + ) { |
| 32 | + let offset = this.getTimezoneOffset(timezone); |
35 | 33 |
|
36 | 34 | return {
|
37 |
| - year: parseInt(match[1], 10), |
38 |
| - month: match[2] ? parseInt(match[2], 10) - 1 : 0, |
39 |
| - day: match[3] ? parseInt(match[3], 10) : 1, // 1-indexed default |
40 |
| - hours: (match[4] ? parseInt(match[4], 10) : 0) - offset.hours, |
41 |
| - minutes: (match[5] ? parseInt(match[5], 10) : 0) - offset.minutes, |
42 |
| - seconds: match[6] ? parseInt(match[6], 10) : 0, |
| 35 | + year: parseInt(year, 10), |
| 36 | + month: parseInt(month, 10) - 1, |
| 37 | + day: parseInt(day, 10), |
| 38 | + hours: parseInt(hours, 10) - offset.hours, |
| 39 | + minutes: parseInt(minutes, 10) - offset.minutes, |
| 40 | + seconds: parseInt(seconds, 10), |
43 | 41 | // may include extra precision but we only count the first 3 digits for milliseconds
|
44 |
| - milliseconds: match[7] ? parseInt(match[7].slice(0, 3), 10) : 0, |
| 42 | + milliseconds: parseInt(milliseconds.slice(0, 3), 10), |
45 | 43 | };
|
46 | 44 | }
|
47 | 45 |
|
48 |
| - /** @param {string} str An [RFC 9557](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDateTime#rfc_9557_format)-compatible string */ |
49 |
| - static getParts(str) { |
50 |
| - let dateMatch = str.match(this.FULL_DATE_REGEX); |
51 |
| - if(dateMatch) { |
52 |
| - return this.getByDateTime(dateMatch); |
| 46 | + /** @param {string} str An [RFC 9557](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDateTime#rfc_9557_format)-compatible string */ |
| 47 | + static getParts(str = "") { |
| 48 | + let dateTimeMatch = str.match(this.FULL_DATE_REGEX) ?? str.match(this.DATETIME_REGEX); |
| 49 | + if(!dateTimeMatch) { |
| 50 | + throw new Error(`Unsupported date format: ${str}`); |
53 | 51 | }
|
54 |
| - |
55 |
| - let dateTimeMatch = str.match(this.DATETIME_REGEX); |
56 |
| - if(dateTimeMatch) { |
57 |
| - if(dateTimeMatch[4]?.match(this.IS_FRACTIONAL_REGEX) || dateTimeMatch[5]?.match(this.IS_FRACTIONAL_REGEX)) { |
58 |
| - throw new Error(`Unsupported date format (fractional hours or minutes): ${str}`); |
59 |
| - } |
60 |
| - |
61 |
| - return this.getByDateTime(dateTimeMatch); |
| 52 | + if(dateTimeMatch.slice(4,6).some(part => !!part?.match(this.IS_FRACTIONAL_REGEX))) { |
| 53 | + throw new Error(`Unsupported date format (fractional hours or minutes): ${str}`); |
62 | 54 | }
|
63 | 55 |
|
64 |
| - throw new Error(`Unsupported date format: ${str}`); |
| 56 | + return this.getByDateTime(...dateTimeMatch); |
65 | 57 | }
|
66 | 58 | }
|
67 | 59 |
|
|
0 commit comments