@elaraai/east 0.0.1-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +682 -0
- package/README.md +276 -0
- package/dist/src/analyze.d.ts +95 -0
- package/dist/src/analyze.d.ts.map +1 -0
- package/dist/src/analyze.js +1110 -0
- package/dist/src/analyze.js.map +1 -0
- package/dist/src/ast.d.ts +263 -0
- package/dist/src/ast.d.ts.map +1 -0
- package/dist/src/ast.js +151 -0
- package/dist/src/ast.js.map +1 -0
- package/dist/src/ast_to_ir.d.ts +24 -0
- package/dist/src/ast_to_ir.d.ts.map +1 -0
- package/dist/src/ast_to_ir.js +834 -0
- package/dist/src/ast_to_ir.js.map +1 -0
- package/dist/src/builtins.d.ts +18 -0
- package/dist/src/builtins.d.ts.map +1 -0
- package/dist/src/builtins.js +1105 -0
- package/dist/src/builtins.js.map +1 -0
- package/dist/src/comparison.d.ts +28 -0
- package/dist/src/comparison.d.ts.map +1 -0
- package/dist/src/comparison.js +1017 -0
- package/dist/src/comparison.js.map +1 -0
- package/dist/src/compile.d.ts +22 -0
- package/dist/src/compile.d.ts.map +1 -0
- package/dist/src/compile.js +3260 -0
- package/dist/src/compile.js.map +1 -0
- package/dist/src/containers/ref.d.ts +106 -0
- package/dist/src/containers/ref.d.ts.map +1 -0
- package/dist/src/containers/ref.js +100 -0
- package/dist/src/containers/ref.js.map +1 -0
- package/dist/src/containers/sortedmap.d.ts +165 -0
- package/dist/src/containers/sortedmap.d.ts.map +1 -0
- package/dist/src/containers/sortedmap.js +237 -0
- package/dist/src/containers/sortedmap.js.map +1 -0
- package/dist/src/containers/sortedset.d.ts +185 -0
- package/dist/src/containers/sortedset.d.ts.map +1 -0
- package/dist/src/containers/sortedset.js +312 -0
- package/dist/src/containers/sortedset.js.map +1 -0
- package/dist/src/containers/variant.d.ts +131 -0
- package/dist/src/containers/variant.d.ts.map +1 -0
- package/dist/src/containers/variant.js +68 -0
- package/dist/src/containers/variant.js.map +1 -0
- package/dist/src/datetime_format/parse.d.ts +50 -0
- package/dist/src/datetime_format/parse.d.ts.map +1 -0
- package/dist/src/datetime_format/parse.js +908 -0
- package/dist/src/datetime_format/parse.js.map +1 -0
- package/dist/src/datetime_format/print.d.ts +35 -0
- package/dist/src/datetime_format/print.d.ts.map +1 -0
- package/dist/src/datetime_format/print.js +157 -0
- package/dist/src/datetime_format/print.js.map +1 -0
- package/dist/src/datetime_format/tokenize.d.ts +76 -0
- package/dist/src/datetime_format/tokenize.d.ts.map +1 -0
- package/dist/src/datetime_format/tokenize.js +271 -0
- package/dist/src/datetime_format/tokenize.js.map +1 -0
- package/dist/src/datetime_format/types.d.ts +99 -0
- package/dist/src/datetime_format/types.d.ts.map +1 -0
- package/dist/src/datetime_format/types.js +103 -0
- package/dist/src/datetime_format/types.js.map +1 -0
- package/dist/src/datetime_format/validate.d.ts +51 -0
- package/dist/src/datetime_format/validate.d.ts.map +1 -0
- package/dist/src/datetime_format/validate.js +208 -0
- package/dist/src/datetime_format/validate.js.map +1 -0
- package/dist/src/default.d.ts +21 -0
- package/dist/src/default.d.ts.map +1 -0
- package/dist/src/default.js +82 -0
- package/dist/src/default.js.map +1 -0
- package/dist/src/eastir.d.ts +33 -0
- package/dist/src/eastir.d.ts.map +1 -0
- package/dist/src/eastir.js +92 -0
- package/dist/src/eastir.js.map +1 -0
- package/dist/src/error.d.ts +13 -0
- package/dist/src/error.d.ts.map +1 -0
- package/dist/src/error.js +8 -0
- package/dist/src/error.js.map +1 -0
- package/dist/src/expr/array.d.ts +1711 -0
- package/dist/src/expr/array.d.ts.map +1 -0
- package/dist/src/expr/array.js +1805 -0
- package/dist/src/expr/array.js.map +1 -0
- package/dist/src/expr/ast.d.ts +17 -0
- package/dist/src/expr/ast.d.ts.map +1 -0
- package/dist/src/expr/ast.js +302 -0
- package/dist/src/expr/ast.js.map +1 -0
- package/dist/src/expr/blob.d.ts +141 -0
- package/dist/src/expr/blob.d.ts.map +1 -0
- package/dist/src/expr/blob.js +198 -0
- package/dist/src/expr/blob.js.map +1 -0
- package/dist/src/expr/block.d.ts +201 -0
- package/dist/src/expr/block.d.ts.map +1 -0
- package/dist/src/expr/block.js +1505 -0
- package/dist/src/expr/block.js.map +1 -0
- package/dist/src/expr/boolean.d.ts +207 -0
- package/dist/src/expr/boolean.d.ts.map +1 -0
- package/dist/src/expr/boolean.js +261 -0
- package/dist/src/expr/boolean.js.map +1 -0
- package/dist/src/expr/datetime.d.ts +544 -0
- package/dist/src/expr/datetime.d.ts.map +1 -0
- package/dist/src/expr/datetime.js +980 -0
- package/dist/src/expr/datetime.js.map +1 -0
- package/dist/src/expr/dict.d.ts +1242 -0
- package/dist/src/expr/dict.d.ts.map +1 -0
- package/dist/src/expr/dict.js +1492 -0
- package/dist/src/expr/dict.js.map +1 -0
- package/dist/src/expr/expr.d.ts +95 -0
- package/dist/src/expr/expr.d.ts.map +1 -0
- package/dist/src/expr/expr.js +171 -0
- package/dist/src/expr/expr.js.map +1 -0
- package/dist/src/expr/float.d.ts +357 -0
- package/dist/src/expr/float.d.ts.map +1 -0
- package/dist/src/expr/float.js +637 -0
- package/dist/src/expr/float.js.map +1 -0
- package/dist/src/expr/function.d.ts +46 -0
- package/dist/src/expr/function.d.ts.map +1 -0
- package/dist/src/expr/function.js +58 -0
- package/dist/src/expr/function.js.map +1 -0
- package/dist/src/expr/index.d.ts +450 -0
- package/dist/src/expr/index.d.ts.map +1 -0
- package/dist/src/expr/index.js +423 -0
- package/dist/src/expr/index.js.map +1 -0
- package/dist/src/expr/integer.d.ts +256 -0
- package/dist/src/expr/integer.d.ts.map +1 -0
- package/dist/src/expr/integer.js +311 -0
- package/dist/src/expr/integer.js.map +1 -0
- package/dist/src/expr/libs/array.d.ts +106 -0
- package/dist/src/expr/libs/array.d.ts.map +1 -0
- package/dist/src/expr/libs/array.js +140 -0
- package/dist/src/expr/libs/array.js.map +1 -0
- package/dist/src/expr/libs/blob.d.ts +42 -0
- package/dist/src/expr/libs/blob.d.ts.map +1 -0
- package/dist/src/expr/libs/blob.js +70 -0
- package/dist/src/expr/libs/blob.js.map +1 -0
- package/dist/src/expr/libs/datetime.d.ts +479 -0
- package/dist/src/expr/libs/datetime.d.ts.map +1 -0
- package/dist/src/expr/libs/datetime.js +624 -0
- package/dist/src/expr/libs/datetime.js.map +1 -0
- package/dist/src/expr/libs/dict.d.ts +66 -0
- package/dist/src/expr/libs/dict.d.ts.map +1 -0
- package/dist/src/expr/libs/dict.js +77 -0
- package/dist/src/expr/libs/dict.js.map +1 -0
- package/dist/src/expr/libs/float.d.ts +299 -0
- package/dist/src/expr/libs/float.d.ts.map +1 -0
- package/dist/src/expr/libs/float.js +564 -0
- package/dist/src/expr/libs/float.js.map +1 -0
- package/dist/src/expr/libs/integer.d.ts +228 -0
- package/dist/src/expr/libs/integer.d.ts.map +1 -0
- package/dist/src/expr/libs/integer.js +398 -0
- package/dist/src/expr/libs/integer.js.map +1 -0
- package/dist/src/expr/libs/set.d.ts +59 -0
- package/dist/src/expr/libs/set.d.ts.map +1 -0
- package/dist/src/expr/libs/set.js +69 -0
- package/dist/src/expr/libs/set.js.map +1 -0
- package/dist/src/expr/libs/string.d.ts +71 -0
- package/dist/src/expr/libs/string.d.ts.map +1 -0
- package/dist/src/expr/libs/string.js +75 -0
- package/dist/src/expr/libs/string.js.map +1 -0
- package/dist/src/expr/never.d.ts +15 -0
- package/dist/src/expr/never.d.ts.map +1 -0
- package/dist/src/expr/never.js +12 -0
- package/dist/src/expr/never.js.map +1 -0
- package/dist/src/expr/null.d.ts +15 -0
- package/dist/src/expr/null.d.ts.map +1 -0
- package/dist/src/expr/null.js +12 -0
- package/dist/src/expr/null.js.map +1 -0
- package/dist/src/expr/ref.d.ts +103 -0
- package/dist/src/expr/ref.d.ts.map +1 -0
- package/dist/src/expr/ref.js +131 -0
- package/dist/src/expr/ref.js.map +1 -0
- package/dist/src/expr/regex_validation.d.ts +25 -0
- package/dist/src/expr/regex_validation.d.ts.map +1 -0
- package/dist/src/expr/regex_validation.js +130 -0
- package/dist/src/expr/regex_validation.js.map +1 -0
- package/dist/src/expr/set.d.ts +1071 -0
- package/dist/src/expr/set.d.ts.map +1 -0
- package/dist/src/expr/set.js +1137 -0
- package/dist/src/expr/set.js.map +1 -0
- package/dist/src/expr/string.d.ts +414 -0
- package/dist/src/expr/string.d.ts.map +1 -0
- package/dist/src/expr/string.js +683 -0
- package/dist/src/expr/string.js.map +1 -0
- package/dist/src/expr/struct.d.ts +48 -0
- package/dist/src/expr/struct.d.ts.map +1 -0
- package/dist/src/expr/struct.js +65 -0
- package/dist/src/expr/struct.js.map +1 -0
- package/dist/src/expr/types.d.ts +68 -0
- package/dist/src/expr/types.d.ts.map +1 -0
- package/dist/src/expr/types.js +6 -0
- package/dist/src/expr/types.js.map +1 -0
- package/dist/src/expr/variant.d.ts +137 -0
- package/dist/src/expr/variant.d.ts.map +1 -0
- package/dist/src/expr/variant.js +105 -0
- package/dist/src/expr/variant.js.map +1 -0
- package/dist/src/fuzz.d.ts +80 -0
- package/dist/src/fuzz.d.ts.map +1 -0
- package/dist/src/fuzz.js +300 -0
- package/dist/src/fuzz.js.map +1 -0
- package/dist/src/index.d.ts +21 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +21 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/internal.d.ts +36 -0
- package/dist/src/internal.d.ts.map +1 -0
- package/dist/src/internal.js +11 -0
- package/dist/src/internal.js.map +1 -0
- package/dist/src/ir.d.ts +1571 -0
- package/dist/src/ir.d.ts.map +1 -0
- package/dist/src/ir.js +56 -0
- package/dist/src/ir.js.map +1 -0
- package/dist/src/location.d.ts +48 -0
- package/dist/src/location.d.ts.map +1 -0
- package/dist/src/location.js +62 -0
- package/dist/src/location.js.map +1 -0
- package/dist/src/platform.d.ts +21 -0
- package/dist/src/platform.d.ts.map +1 -0
- package/dist/src/platform.js +8 -0
- package/dist/src/platform.js.map +1 -0
- package/dist/src/serialization/beast.d.ts +39 -0
- package/dist/src/serialization/beast.d.ts.map +1 -0
- package/dist/src/serialization/beast.js +555 -0
- package/dist/src/serialization/beast.js.map +1 -0
- package/dist/src/serialization/beast2-stream.d.ts +38 -0
- package/dist/src/serialization/beast2-stream.d.ts.map +1 -0
- package/dist/src/serialization/beast2-stream.js +665 -0
- package/dist/src/serialization/beast2-stream.js.map +1 -0
- package/dist/src/serialization/beast2.d.ts +41 -0
- package/dist/src/serialization/beast2.d.ts.map +1 -0
- package/dist/src/serialization/beast2.js +489 -0
- package/dist/src/serialization/beast2.js.map +1 -0
- package/dist/src/serialization/binary-utils.d.ts +151 -0
- package/dist/src/serialization/binary-utils.d.ts.map +1 -0
- package/dist/src/serialization/binary-utils.js +929 -0
- package/dist/src/serialization/binary-utils.js.map +1 -0
- package/dist/src/serialization/east.d.ts +84 -0
- package/dist/src/serialization/east.d.ts.map +1 -0
- package/dist/src/serialization/east.js +1802 -0
- package/dist/src/serialization/east.js.map +1 -0
- package/dist/src/serialization/index.d.ts +11 -0
- package/dist/src/serialization/index.d.ts.map +1 -0
- package/dist/src/serialization/index.js +12 -0
- package/dist/src/serialization/index.js.map +1 -0
- package/dist/src/serialization/json.d.ts +36 -0
- package/dist/src/serialization/json.d.ts.map +1 -0
- package/dist/src/serialization/json.js +849 -0
- package/dist/src/serialization/json.js.map +1 -0
- package/dist/src/type_of_type.d.ts +115 -0
- package/dist/src/type_of_type.d.ts.map +1 -0
- package/dist/src/type_of_type.js +362 -0
- package/dist/src/type_of_type.js.map +1 -0
- package/dist/src/types.d.ts +648 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +1631 -0
- package/dist/src/types.js.map +1 -0
- package/package.json +87 -0
|
@@ -0,0 +1,908 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Elara AI Pty Ltd
|
|
3
|
+
* Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Month names in English for parsing.
|
|
7
|
+
*/
|
|
8
|
+
const MONTH_NAMES_FULL = [
|
|
9
|
+
"january", "february", "march", "april", "may", "june",
|
|
10
|
+
"july", "august", "september", "october", "november", "december"
|
|
11
|
+
];
|
|
12
|
+
const MONTH_NAMES_SHORT = [
|
|
13
|
+
"jan", "feb", "mar", "apr", "may", "jun",
|
|
14
|
+
"jul", "aug", "sep", "oct", "nov", "dec"
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* Weekday names in English for parsing (currently ignored during parsing).
|
|
18
|
+
*/
|
|
19
|
+
const WEEKDAY_NAMES_FULL = [
|
|
20
|
+
"sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"
|
|
21
|
+
];
|
|
22
|
+
const WEEKDAY_NAMES_SHORT = [
|
|
23
|
+
"sun", "mon", "tue", "wed", "thu", "fri", "sat"
|
|
24
|
+
];
|
|
25
|
+
const WEEKDAY_NAMES_MIN = [
|
|
26
|
+
"su", "mo", "tu", "we", "th", "fr", "sa"
|
|
27
|
+
];
|
|
28
|
+
/**
|
|
29
|
+
* Parses a datetime string according to format tokens.
|
|
30
|
+
*
|
|
31
|
+
* @param input - The string to parse
|
|
32
|
+
* @param tokens - Array of format tokens specifying the expected format
|
|
33
|
+
* @returns Parse result containing either the Date or an error with position
|
|
34
|
+
*
|
|
35
|
+
* @remarks
|
|
36
|
+
* All dates are treated as UTC (naive datetimes with no timezone information).
|
|
37
|
+
* The parsed components are used to construct a Date using Date.UTC().
|
|
38
|
+
*
|
|
39
|
+
* Weekday tokens (dd, ddd, dddd) are currently ignored during parsing - they
|
|
40
|
+
* are consumed from the input but not validated against the actual weekday.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* const tokens = tokenizeDateTimeFormat("YYYY-MM-DD");
|
|
45
|
+
* const result = parseDateTimeFormatted("2025-01-15", tokens);
|
|
46
|
+
* if (result.success) {
|
|
47
|
+
* console.log(result.value); // Date object for 2025-01-15T00:00:00.000Z
|
|
48
|
+
* } else {
|
|
49
|
+
* console.log(`Parse error at position ${result.position}: ${result.error}`);
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export function parseDateTimeFormatted(input, tokens) {
|
|
54
|
+
let position = 0;
|
|
55
|
+
// Datetime components - will validate redundancy and check for gaps
|
|
56
|
+
let year = null;
|
|
57
|
+
let month = null;
|
|
58
|
+
let day = null;
|
|
59
|
+
let hour = null;
|
|
60
|
+
let minute = null;
|
|
61
|
+
let second = null;
|
|
62
|
+
let millisecond = null;
|
|
63
|
+
let isPM = null;
|
|
64
|
+
let hour12 = null; // Track 12-hour format separately
|
|
65
|
+
let parsedWeekday = null; // Track parsed weekday for validation
|
|
66
|
+
for (const token of tokens) {
|
|
67
|
+
if (position > input.length) {
|
|
68
|
+
return {
|
|
69
|
+
success: false,
|
|
70
|
+
error: `Unexpected end of input`,
|
|
71
|
+
position
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
switch (token.type) {
|
|
75
|
+
case "year4": {
|
|
76
|
+
const match = input.slice(position, position + 4);
|
|
77
|
+
if (match.length < 4 || !/^\d{4}$/.test(match)) {
|
|
78
|
+
return {
|
|
79
|
+
success: false,
|
|
80
|
+
error: `Expected 4-digit year`,
|
|
81
|
+
position
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const parsedYear = parseInt(match, 10);
|
|
85
|
+
if (year !== null && year !== parsedYear) {
|
|
86
|
+
return {
|
|
87
|
+
success: false,
|
|
88
|
+
error: `Year specified multiple times with different values: ${year} and ${parsedYear}`,
|
|
89
|
+
position
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
year = parsedYear;
|
|
93
|
+
position += 4;
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
case "year2": {
|
|
97
|
+
const match = input.slice(position, position + 2);
|
|
98
|
+
if (match.length < 2 || !/^\d{2}$/.test(match)) {
|
|
99
|
+
return {
|
|
100
|
+
success: false,
|
|
101
|
+
error: `Expected 2-digit year`,
|
|
102
|
+
position
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const yy = parseInt(match, 10);
|
|
106
|
+
// 2-digit year: 00-99 -> 2000-2099 (simple heuristic)
|
|
107
|
+
const parsedYear = 2000 + yy;
|
|
108
|
+
if (year !== null && year !== parsedYear) {
|
|
109
|
+
return {
|
|
110
|
+
success: false,
|
|
111
|
+
error: `Year specified multiple times with different values: ${year} and ${parsedYear}`,
|
|
112
|
+
position
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
year = parsedYear;
|
|
116
|
+
position += 2;
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
case "month2": {
|
|
120
|
+
const match = input.slice(position, position + 2);
|
|
121
|
+
if (match.length < 2 || !/^\d{2}$/.test(match)) {
|
|
122
|
+
return {
|
|
123
|
+
success: false,
|
|
124
|
+
error: `Expected 2-digit month (01-12)`,
|
|
125
|
+
position
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const parsedMonth = parseInt(match, 10);
|
|
129
|
+
if (parsedMonth < 1 || parsedMonth > 12) {
|
|
130
|
+
return {
|
|
131
|
+
success: false,
|
|
132
|
+
error: `Month out of range (got ${parsedMonth}, expected 01-12)`,
|
|
133
|
+
position
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (month !== null && month !== parsedMonth) {
|
|
137
|
+
return {
|
|
138
|
+
success: false,
|
|
139
|
+
error: `Month specified multiple times with different values: ${month} and ${parsedMonth}`,
|
|
140
|
+
position
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
month = parsedMonth;
|
|
144
|
+
position += 2;
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
case "month1": {
|
|
148
|
+
// Try 2 digits first, then 1
|
|
149
|
+
let match = input.slice(position, position + 2);
|
|
150
|
+
let parsedMonth;
|
|
151
|
+
if (/^\d{2}$/.test(match)) {
|
|
152
|
+
parsedMonth = parseInt(match, 10);
|
|
153
|
+
position += 2;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
match = input.slice(position, position + 1);
|
|
157
|
+
if (!/^\d$/.test(match)) {
|
|
158
|
+
return {
|
|
159
|
+
success: false,
|
|
160
|
+
error: `Expected 1 or 2-digit month`,
|
|
161
|
+
position
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
parsedMonth = parseInt(match, 10);
|
|
165
|
+
position += 1;
|
|
166
|
+
}
|
|
167
|
+
if (parsedMonth < 1 || parsedMonth > 12) {
|
|
168
|
+
return {
|
|
169
|
+
success: false,
|
|
170
|
+
error: `Month out of range (got ${parsedMonth}, expected 1-12)`,
|
|
171
|
+
position: position - match.length
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
if (month !== null && month !== parsedMonth) {
|
|
175
|
+
return {
|
|
176
|
+
success: false,
|
|
177
|
+
error: `Month specified multiple times with different values: ${month} and ${parsedMonth}`,
|
|
178
|
+
position: position - match.length
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
month = parsedMonth;
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
case "monthNameFull": {
|
|
185
|
+
let matched = false;
|
|
186
|
+
let parsedMonth = null;
|
|
187
|
+
for (let i = 0; i < MONTH_NAMES_FULL.length; i++) {
|
|
188
|
+
const name = MONTH_NAMES_FULL[i];
|
|
189
|
+
const slice = input.slice(position, position + name.length).toLowerCase();
|
|
190
|
+
if (slice === name) {
|
|
191
|
+
parsedMonth = i + 1;
|
|
192
|
+
position += name.length;
|
|
193
|
+
matched = true;
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (!matched) {
|
|
198
|
+
return {
|
|
199
|
+
success: false,
|
|
200
|
+
error: `Expected full month name (e.g., "January")`,
|
|
201
|
+
position
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
if (month !== null && month !== parsedMonth) {
|
|
205
|
+
return {
|
|
206
|
+
success: false,
|
|
207
|
+
error: `Month specified multiple times with different values: ${month} and ${parsedMonth}`,
|
|
208
|
+
position
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
month = parsedMonth;
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
case "monthNameShort": {
|
|
215
|
+
let matched = false;
|
|
216
|
+
let parsedMonth = null;
|
|
217
|
+
for (let i = 0; i < MONTH_NAMES_SHORT.length; i++) {
|
|
218
|
+
const name = MONTH_NAMES_SHORT[i];
|
|
219
|
+
const slice = input.slice(position, position + name.length).toLowerCase();
|
|
220
|
+
if (slice === name) {
|
|
221
|
+
parsedMonth = i + 1;
|
|
222
|
+
position += name.length;
|
|
223
|
+
matched = true;
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (!matched) {
|
|
228
|
+
return {
|
|
229
|
+
success: false,
|
|
230
|
+
error: `Expected short month name (e.g., "Jan")`,
|
|
231
|
+
position
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
if (month !== null && month !== parsedMonth) {
|
|
235
|
+
return {
|
|
236
|
+
success: false,
|
|
237
|
+
error: `Month specified multiple times with different values: ${month} and ${parsedMonth}`,
|
|
238
|
+
position
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
month = parsedMonth;
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
case "day2": {
|
|
245
|
+
const match = input.slice(position, position + 2);
|
|
246
|
+
if (match.length < 2 || !/^\d{2}$/.test(match)) {
|
|
247
|
+
return {
|
|
248
|
+
success: false,
|
|
249
|
+
error: `Expected 2-digit day (01-31)`,
|
|
250
|
+
position
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
const parsedDay = parseInt(match, 10);
|
|
254
|
+
if (parsedDay < 1 || parsedDay > 31) {
|
|
255
|
+
return {
|
|
256
|
+
success: false,
|
|
257
|
+
error: `Day out of range (got ${parsedDay}, expected 01-31)`,
|
|
258
|
+
position
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
if (day !== null && day !== parsedDay) {
|
|
262
|
+
return {
|
|
263
|
+
success: false,
|
|
264
|
+
error: `Day specified multiple times with different values: ${day} and ${parsedDay}`,
|
|
265
|
+
position
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
day = parsedDay;
|
|
269
|
+
position += 2;
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
case "day1": {
|
|
273
|
+
// Try 2 digits first, then 1
|
|
274
|
+
let match = input.slice(position, position + 2);
|
|
275
|
+
let parsedDay;
|
|
276
|
+
if (/^\d{2}$/.test(match)) {
|
|
277
|
+
parsedDay = parseInt(match, 10);
|
|
278
|
+
position += 2;
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
match = input.slice(position, position + 1);
|
|
282
|
+
if (!/^\d$/.test(match)) {
|
|
283
|
+
return {
|
|
284
|
+
success: false,
|
|
285
|
+
error: `Expected 1 or 2-digit day`,
|
|
286
|
+
position
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
parsedDay = parseInt(match, 10);
|
|
290
|
+
position += 1;
|
|
291
|
+
}
|
|
292
|
+
if (parsedDay < 1 || parsedDay > 31) {
|
|
293
|
+
return {
|
|
294
|
+
success: false,
|
|
295
|
+
error: `Day out of range (got ${parsedDay}, expected 1-31)`,
|
|
296
|
+
position: position - match.length
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
if (day !== null && day !== parsedDay) {
|
|
300
|
+
return {
|
|
301
|
+
success: false,
|
|
302
|
+
error: `Day specified multiple times with different values: ${day} and ${parsedDay}`,
|
|
303
|
+
position: position - match.length
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
day = parsedDay;
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
// Weekday parsing - store for validation after Date construction
|
|
310
|
+
case "weekdayNameFull": {
|
|
311
|
+
let matched = false;
|
|
312
|
+
for (let i = 0; i < WEEKDAY_NAMES_FULL.length; i++) {
|
|
313
|
+
const name = WEEKDAY_NAMES_FULL[i];
|
|
314
|
+
const slice = input.slice(position, position + name.length).toLowerCase();
|
|
315
|
+
if (slice === name) {
|
|
316
|
+
// Map to JavaScript getDay() values: 0=Sunday, 1=Monday, ..., 6=Saturday
|
|
317
|
+
const weekdayValue = i;
|
|
318
|
+
if (parsedWeekday !== null && parsedWeekday !== weekdayValue) {
|
|
319
|
+
return {
|
|
320
|
+
success: false,
|
|
321
|
+
error: `Weekday specified multiple times with different values`,
|
|
322
|
+
position
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
parsedWeekday = weekdayValue;
|
|
326
|
+
position += name.length;
|
|
327
|
+
matched = true;
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (!matched) {
|
|
332
|
+
return {
|
|
333
|
+
success: false,
|
|
334
|
+
error: `Expected full weekday name (e.g., "Monday")`,
|
|
335
|
+
position
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
case "weekdayNameShort": {
|
|
341
|
+
let matched = false;
|
|
342
|
+
for (let i = 0; i < WEEKDAY_NAMES_SHORT.length; i++) {
|
|
343
|
+
const name = WEEKDAY_NAMES_SHORT[i];
|
|
344
|
+
const slice = input.slice(position, position + name.length).toLowerCase();
|
|
345
|
+
if (slice === name) {
|
|
346
|
+
// Map to JavaScript getDay() values: 0=Sunday, 1=Monday, ..., 6=Saturday
|
|
347
|
+
const weekdayValue = i;
|
|
348
|
+
if (parsedWeekday !== null && parsedWeekday !== weekdayValue) {
|
|
349
|
+
return {
|
|
350
|
+
success: false,
|
|
351
|
+
error: `Weekday specified multiple times with different values`,
|
|
352
|
+
position
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
parsedWeekday = weekdayValue;
|
|
356
|
+
position += name.length;
|
|
357
|
+
matched = true;
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (!matched) {
|
|
362
|
+
return {
|
|
363
|
+
success: false,
|
|
364
|
+
error: `Expected short weekday name (e.g., "Mon")`,
|
|
365
|
+
position
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
case "weekdayNameMin": {
|
|
371
|
+
let matched = false;
|
|
372
|
+
for (let i = 0; i < WEEKDAY_NAMES_MIN.length; i++) {
|
|
373
|
+
const name = WEEKDAY_NAMES_MIN[i];
|
|
374
|
+
const slice = input.slice(position, position + name.length).toLowerCase();
|
|
375
|
+
if (slice === name) {
|
|
376
|
+
// Map to JavaScript getDay() values: 0=Sunday, 1=Monday, ..., 6=Saturday
|
|
377
|
+
const weekdayValue = i;
|
|
378
|
+
if (parsedWeekday !== null && parsedWeekday !== weekdayValue) {
|
|
379
|
+
return {
|
|
380
|
+
success: false,
|
|
381
|
+
error: `Weekday specified multiple times with different values`,
|
|
382
|
+
position
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
parsedWeekday = weekdayValue;
|
|
386
|
+
position += name.length;
|
|
387
|
+
matched = true;
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (!matched) {
|
|
392
|
+
return {
|
|
393
|
+
success: false,
|
|
394
|
+
error: `Expected minimal weekday name (e.g., "Mo")`,
|
|
395
|
+
position
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
case "hour24_2": {
|
|
401
|
+
const match = input.slice(position, position + 2);
|
|
402
|
+
if (match.length < 2 || !/^\d{2}$/.test(match)) {
|
|
403
|
+
return {
|
|
404
|
+
success: false,
|
|
405
|
+
error: `Expected 2-digit hour (00-23)`,
|
|
406
|
+
position
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
const parsedHour = parseInt(match, 10);
|
|
410
|
+
if (parsedHour > 23) {
|
|
411
|
+
return {
|
|
412
|
+
success: false,
|
|
413
|
+
error: `Hour out of range (got ${parsedHour}, expected 00-23)`,
|
|
414
|
+
position
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
if (hour !== null && hour !== parsedHour) {
|
|
418
|
+
return {
|
|
419
|
+
success: false,
|
|
420
|
+
error: `Hour (24-hour) specified multiple times with different values: ${hour} and ${parsedHour}`,
|
|
421
|
+
position
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
hour = parsedHour;
|
|
425
|
+
position += 2;
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
case "hour24_1": {
|
|
429
|
+
// Try 2 digits first, then 1
|
|
430
|
+
let match = input.slice(position, position + 2);
|
|
431
|
+
let parsedHour;
|
|
432
|
+
if (/^\d{2}$/.test(match)) {
|
|
433
|
+
parsedHour = parseInt(match, 10);
|
|
434
|
+
position += 2;
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
match = input.slice(position, position + 1);
|
|
438
|
+
if (!/^\d$/.test(match)) {
|
|
439
|
+
return {
|
|
440
|
+
success: false,
|
|
441
|
+
error: `Expected 1 or 2-digit hour`,
|
|
442
|
+
position
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
parsedHour = parseInt(match, 10);
|
|
446
|
+
position += 1;
|
|
447
|
+
}
|
|
448
|
+
if (parsedHour > 23) {
|
|
449
|
+
return {
|
|
450
|
+
success: false,
|
|
451
|
+
error: `Hour out of range (got ${parsedHour}, expected 0-23)`,
|
|
452
|
+
position: position - match.length
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
if (hour !== null && hour !== parsedHour) {
|
|
456
|
+
return {
|
|
457
|
+
success: false,
|
|
458
|
+
error: `Hour (24-hour) specified multiple times with different values: ${hour} and ${parsedHour}`,
|
|
459
|
+
position: position - match.length
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
hour = parsedHour;
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
465
|
+
case "hour12_2": {
|
|
466
|
+
const match = input.slice(position, position + 2);
|
|
467
|
+
if (match.length < 2 || !/^\d{2}$/.test(match)) {
|
|
468
|
+
return {
|
|
469
|
+
success: false,
|
|
470
|
+
error: `Expected 2-digit hour (01-12)`,
|
|
471
|
+
position
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
const parsedHour12 = parseInt(match, 10);
|
|
475
|
+
if (parsedHour12 < 1 || parsedHour12 > 12) {
|
|
476
|
+
return {
|
|
477
|
+
success: false,
|
|
478
|
+
error: `Hour out of range (got ${parsedHour12}, expected 01-12)`,
|
|
479
|
+
position
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
if (hour12 !== null && hour12 !== parsedHour12) {
|
|
483
|
+
return {
|
|
484
|
+
success: false,
|
|
485
|
+
error: `Hour (12-hour) specified multiple times with different values: ${hour12} and ${parsedHour12}`,
|
|
486
|
+
position
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
hour12 = parsedHour12;
|
|
490
|
+
position += 2;
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
493
|
+
case "hour12_1": {
|
|
494
|
+
// Try 2 digits first, then 1
|
|
495
|
+
let match = input.slice(position, position + 2);
|
|
496
|
+
let parsedHour12;
|
|
497
|
+
if (/^\d{2}$/.test(match)) {
|
|
498
|
+
parsedHour12 = parseInt(match, 10);
|
|
499
|
+
position += 2;
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
match = input.slice(position, position + 1);
|
|
503
|
+
if (!/^\d$/.test(match)) {
|
|
504
|
+
return {
|
|
505
|
+
success: false,
|
|
506
|
+
error: `Expected 1 or 2-digit hour`,
|
|
507
|
+
position
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
parsedHour12 = parseInt(match, 10);
|
|
511
|
+
position += 1;
|
|
512
|
+
}
|
|
513
|
+
if (parsedHour12 < 1 || parsedHour12 > 12) {
|
|
514
|
+
return {
|
|
515
|
+
success: false,
|
|
516
|
+
error: `Hour out of range (got ${parsedHour12}, expected 1-12)`,
|
|
517
|
+
position: position - match.length
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
if (hour12 !== null && hour12 !== parsedHour12) {
|
|
521
|
+
return {
|
|
522
|
+
success: false,
|
|
523
|
+
error: `Hour (12-hour) specified multiple times with different values: ${hour12} and ${parsedHour12}`,
|
|
524
|
+
position: position - match.length
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
hour12 = parsedHour12;
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
case "minute2": {
|
|
531
|
+
const match = input.slice(position, position + 2);
|
|
532
|
+
if (match.length < 2 || !/^\d{2}$/.test(match)) {
|
|
533
|
+
return {
|
|
534
|
+
success: false,
|
|
535
|
+
error: `Expected 2-digit minute (00-59)`,
|
|
536
|
+
position
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
const parsedMinute = parseInt(match, 10);
|
|
540
|
+
if (parsedMinute > 59) {
|
|
541
|
+
return {
|
|
542
|
+
success: false,
|
|
543
|
+
error: `Minute out of range (got ${parsedMinute}, expected 00-59)`,
|
|
544
|
+
position
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
if (minute !== null && minute !== parsedMinute) {
|
|
548
|
+
return {
|
|
549
|
+
success: false,
|
|
550
|
+
error: `Minute specified multiple times with different values: ${minute} and ${parsedMinute}`,
|
|
551
|
+
position
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
minute = parsedMinute;
|
|
555
|
+
position += 2;
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
case "minute1": {
|
|
559
|
+
// Try 2 digits first, then 1
|
|
560
|
+
let match = input.slice(position, position + 2);
|
|
561
|
+
let parsedMinute;
|
|
562
|
+
if (/^\d{2}$/.test(match)) {
|
|
563
|
+
parsedMinute = parseInt(match, 10);
|
|
564
|
+
position += 2;
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
match = input.slice(position, position + 1);
|
|
568
|
+
if (!/^\d$/.test(match)) {
|
|
569
|
+
return {
|
|
570
|
+
success: false,
|
|
571
|
+
error: `Expected 1 or 2-digit minute`,
|
|
572
|
+
position
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
parsedMinute = parseInt(match, 10);
|
|
576
|
+
position += 1;
|
|
577
|
+
}
|
|
578
|
+
if (parsedMinute > 59) {
|
|
579
|
+
return {
|
|
580
|
+
success: false,
|
|
581
|
+
error: `Minute out of range (got ${parsedMinute}, expected 0-59)`,
|
|
582
|
+
position: position - match.length
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
if (minute !== null && minute !== parsedMinute) {
|
|
586
|
+
return {
|
|
587
|
+
success: false,
|
|
588
|
+
error: `Minute specified multiple times with different values: ${minute} and ${parsedMinute}`,
|
|
589
|
+
position: position - match.length
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
minute = parsedMinute;
|
|
593
|
+
break;
|
|
594
|
+
}
|
|
595
|
+
case "second2": {
|
|
596
|
+
const match = input.slice(position, position + 2);
|
|
597
|
+
if (match.length < 2 || !/^\d{2}$/.test(match)) {
|
|
598
|
+
return {
|
|
599
|
+
success: false,
|
|
600
|
+
error: `Expected 2-digit second (00-59)`,
|
|
601
|
+
position
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
const parsedSecond = parseInt(match, 10);
|
|
605
|
+
if (parsedSecond > 59) {
|
|
606
|
+
return {
|
|
607
|
+
success: false,
|
|
608
|
+
error: `Second out of range (got ${parsedSecond}, expected 00-59)`,
|
|
609
|
+
position
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
if (second !== null && second !== parsedSecond) {
|
|
613
|
+
return {
|
|
614
|
+
success: false,
|
|
615
|
+
error: `Second specified multiple times with different values: ${second} and ${parsedSecond}`,
|
|
616
|
+
position
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
second = parsedSecond;
|
|
620
|
+
position += 2;
|
|
621
|
+
break;
|
|
622
|
+
}
|
|
623
|
+
case "second1": {
|
|
624
|
+
// Try 2 digits first, then 1
|
|
625
|
+
let match = input.slice(position, position + 2);
|
|
626
|
+
let parsedSecond;
|
|
627
|
+
if (/^\d{2}$/.test(match)) {
|
|
628
|
+
parsedSecond = parseInt(match, 10);
|
|
629
|
+
position += 2;
|
|
630
|
+
}
|
|
631
|
+
else {
|
|
632
|
+
match = input.slice(position, position + 1);
|
|
633
|
+
if (!/^\d$/.test(match)) {
|
|
634
|
+
return {
|
|
635
|
+
success: false,
|
|
636
|
+
error: `Expected 1 or 2-digit second`,
|
|
637
|
+
position
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
parsedSecond = parseInt(match, 10);
|
|
641
|
+
position += 1;
|
|
642
|
+
}
|
|
643
|
+
if (parsedSecond > 59) {
|
|
644
|
+
return {
|
|
645
|
+
success: false,
|
|
646
|
+
error: `Second out of range (got ${parsedSecond}, expected 0-59)`,
|
|
647
|
+
position: position - match.length
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
if (second !== null && second !== parsedSecond) {
|
|
651
|
+
return {
|
|
652
|
+
success: false,
|
|
653
|
+
error: `Second specified multiple times with different values: ${second} and ${parsedSecond}`,
|
|
654
|
+
position: position - match.length
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
second = parsedSecond;
|
|
658
|
+
break;
|
|
659
|
+
}
|
|
660
|
+
case "millisecond3": {
|
|
661
|
+
const match = input.slice(position, position + 3);
|
|
662
|
+
if (match.length < 3 || !/^\d{3}$/.test(match)) {
|
|
663
|
+
return {
|
|
664
|
+
success: false,
|
|
665
|
+
error: `Expected 3-digit millisecond (000-999)`,
|
|
666
|
+
position
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
const parsedMillisecond = parseInt(match, 10);
|
|
670
|
+
if (millisecond !== null && millisecond !== parsedMillisecond) {
|
|
671
|
+
return {
|
|
672
|
+
success: false,
|
|
673
|
+
error: `Millisecond specified multiple times with different values: ${millisecond} and ${parsedMillisecond}`,
|
|
674
|
+
position
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
millisecond = parsedMillisecond;
|
|
678
|
+
position += 3;
|
|
679
|
+
break;
|
|
680
|
+
}
|
|
681
|
+
case "ampmUpper": {
|
|
682
|
+
const match = input.slice(position, position + 2).toUpperCase();
|
|
683
|
+
if (match === "AM") {
|
|
684
|
+
isPM = false;
|
|
685
|
+
position += 2;
|
|
686
|
+
}
|
|
687
|
+
else if (match === "PM") {
|
|
688
|
+
isPM = true;
|
|
689
|
+
position += 2;
|
|
690
|
+
}
|
|
691
|
+
else {
|
|
692
|
+
return {
|
|
693
|
+
success: false,
|
|
694
|
+
error: `Expected "AM" or "PM"`,
|
|
695
|
+
position
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
break;
|
|
699
|
+
}
|
|
700
|
+
case "ampmLower": {
|
|
701
|
+
const match = input.slice(position, position + 2).toLowerCase();
|
|
702
|
+
if (match === "am") {
|
|
703
|
+
isPM = false;
|
|
704
|
+
position += 2;
|
|
705
|
+
}
|
|
706
|
+
else if (match === "pm") {
|
|
707
|
+
isPM = true;
|
|
708
|
+
position += 2;
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
return {
|
|
712
|
+
success: false,
|
|
713
|
+
error: `Expected "am" or "pm"`,
|
|
714
|
+
position
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
break;
|
|
718
|
+
}
|
|
719
|
+
case "literal": {
|
|
720
|
+
const expected = token.value;
|
|
721
|
+
const actual = input.slice(position, position + expected.length);
|
|
722
|
+
if (actual !== expected) {
|
|
723
|
+
return {
|
|
724
|
+
success: false,
|
|
725
|
+
error: `Expected literal "${expected}", got "${actual}"`,
|
|
726
|
+
position
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
position += expected.length;
|
|
730
|
+
break;
|
|
731
|
+
}
|
|
732
|
+
default: {
|
|
733
|
+
// TypeScript exhaustiveness check
|
|
734
|
+
const _exhaustive = token;
|
|
735
|
+
return {
|
|
736
|
+
success: false,
|
|
737
|
+
error: `Unknown token type: ${_exhaustive.type}`,
|
|
738
|
+
position
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
// Check for unconsumed input
|
|
744
|
+
if (position < input.length) {
|
|
745
|
+
return {
|
|
746
|
+
success: false,
|
|
747
|
+
error: `Unexpected trailing characters: "${input.slice(position)}"`,
|
|
748
|
+
position
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
// Prefix validation: Fill in defaults while checking for gaps in the component hierarchy
|
|
752
|
+
// The hierarchy is: Year → Month → Day → Hour → Minute → Second → Millisecond
|
|
753
|
+
// We check for gaps and fill in defaults from most significant to least significant
|
|
754
|
+
// Check if we have ANY date components to determine if this is a time-only format
|
|
755
|
+
const hasAnyDateComponent = year !== null || month !== null || day !== null;
|
|
756
|
+
let foundGap = false;
|
|
757
|
+
// Date components
|
|
758
|
+
if (year === null) {
|
|
759
|
+
if (hasAnyDateComponent) {
|
|
760
|
+
foundGap = true;
|
|
761
|
+
}
|
|
762
|
+
year = 1970; // Default epoch year for time-only formats
|
|
763
|
+
}
|
|
764
|
+
if (month === null) {
|
|
765
|
+
if (hasAnyDateComponent) {
|
|
766
|
+
foundGap = true;
|
|
767
|
+
}
|
|
768
|
+
month = 1;
|
|
769
|
+
}
|
|
770
|
+
else if (foundGap) {
|
|
771
|
+
// Can't have month if we already found a gap (e.g., no year)
|
|
772
|
+
return {
|
|
773
|
+
success: false,
|
|
774
|
+
error: `Invalid format: cannot have month without year`,
|
|
775
|
+
position: 0
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
if (day === null) {
|
|
779
|
+
if (hasAnyDateComponent) {
|
|
780
|
+
foundGap = true;
|
|
781
|
+
}
|
|
782
|
+
day = 1;
|
|
783
|
+
}
|
|
784
|
+
else if (foundGap) {
|
|
785
|
+
// Can't have day if we already found a gap
|
|
786
|
+
return {
|
|
787
|
+
success: false,
|
|
788
|
+
error: `Invalid format: cannot have day without year and month`,
|
|
789
|
+
position: 0
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
// Hour validation and conversion: handle hour24 vs hour12+AM/PM redundancy
|
|
793
|
+
if (hour !== null && hour12 !== null) {
|
|
794
|
+
// Both hour24 and hour12 specified - validate they match
|
|
795
|
+
let expectedHour24;
|
|
796
|
+
if (isPM === null) {
|
|
797
|
+
// hour12 without AM/PM is ambiguous - can't validate
|
|
798
|
+
return {
|
|
799
|
+
success: false,
|
|
800
|
+
error: `12-hour format specified without AM/PM indicator`,
|
|
801
|
+
position: 0
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
// Convert hour12 + isPM to hour24
|
|
805
|
+
if (isPM) {
|
|
806
|
+
expectedHour24 = hour12 === 12 ? 12 : hour12 + 12; // 12 PM -> 12, 1 PM -> 13
|
|
807
|
+
}
|
|
808
|
+
else {
|
|
809
|
+
expectedHour24 = hour12 === 12 ? 0 : hour12; // 12 AM -> 0, 1 AM -> 1
|
|
810
|
+
}
|
|
811
|
+
if (hour !== expectedHour24) {
|
|
812
|
+
return {
|
|
813
|
+
success: false,
|
|
814
|
+
error: `Hour mismatch: 24-hour format specifies ${hour}, but 12-hour format with ${isPM ? 'PM' : 'AM'} implies ${expectedHour24}`,
|
|
815
|
+
position: 0
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
else if (hour === null && hour12 !== null) {
|
|
820
|
+
// Only hour12 specified - convert to hour24
|
|
821
|
+
if (isPM === null) {
|
|
822
|
+
return {
|
|
823
|
+
success: false,
|
|
824
|
+
error: `12-hour format specified without AM/PM indicator`,
|
|
825
|
+
position: 0
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
if (isPM) {
|
|
829
|
+
hour = hour12 === 12 ? 12 : hour12 + 12; // 12 PM -> 12, 1 PM -> 13
|
|
830
|
+
}
|
|
831
|
+
else {
|
|
832
|
+
hour = hour12 === 12 ? 0 : hour12; // 12 AM -> 0, 1 AM -> 1
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
// Continue with time components
|
|
836
|
+
if (hour === null) {
|
|
837
|
+
foundGap = true;
|
|
838
|
+
hour = 0;
|
|
839
|
+
}
|
|
840
|
+
else if (foundGap) {
|
|
841
|
+
return {
|
|
842
|
+
success: false,
|
|
843
|
+
error: `Invalid format: cannot have hour without year, month, and day (or use time-only format)`,
|
|
844
|
+
position: 0
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
if (minute === null) {
|
|
848
|
+
foundGap = true;
|
|
849
|
+
minute = 0;
|
|
850
|
+
}
|
|
851
|
+
else if (foundGap) {
|
|
852
|
+
return {
|
|
853
|
+
success: false,
|
|
854
|
+
error: `Invalid format: cannot have minute without hour`,
|
|
855
|
+
position: 0
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
if (second === null) {
|
|
859
|
+
foundGap = true;
|
|
860
|
+
second = 0;
|
|
861
|
+
}
|
|
862
|
+
else if (foundGap) {
|
|
863
|
+
return {
|
|
864
|
+
success: false,
|
|
865
|
+
error: `Invalid format: cannot have second without minute`,
|
|
866
|
+
position: 0
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
if (millisecond === null) {
|
|
870
|
+
millisecond = 0;
|
|
871
|
+
}
|
|
872
|
+
else if (foundGap) {
|
|
873
|
+
return {
|
|
874
|
+
success: false,
|
|
875
|
+
error: `Invalid format: cannot have millisecond without second`,
|
|
876
|
+
position: 0
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
// Construct Date using UTC
|
|
880
|
+
const date = new Date(Date.UTC(year, month - 1, day, hour, minute, second, millisecond));
|
|
881
|
+
// Validate the date is actually valid (e.g., not Feb 31)
|
|
882
|
+
if (date.getUTCFullYear() !== year ||
|
|
883
|
+
date.getUTCMonth() !== month - 1 ||
|
|
884
|
+
date.getUTCDate() !== day) {
|
|
885
|
+
return {
|
|
886
|
+
success: false,
|
|
887
|
+
error: `Invalid date: ${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`,
|
|
888
|
+
position: 0
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
// Validate weekday if it was parsed
|
|
892
|
+
if (parsedWeekday !== null) {
|
|
893
|
+
const actualWeekday = date.getUTCDay();
|
|
894
|
+
if (actualWeekday !== parsedWeekday) {
|
|
895
|
+
const weekdayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
896
|
+
return {
|
|
897
|
+
success: false,
|
|
898
|
+
error: `Weekday mismatch: parsed "${weekdayNames[parsedWeekday]}" but date is actually "${weekdayNames[actualWeekday]}"`,
|
|
899
|
+
position: 0
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
return {
|
|
904
|
+
success: true,
|
|
905
|
+
value: date
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
//# sourceMappingURL=parse.js.map
|