toilscript 0.0.1 → 0.1.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 +201 -201
- package/NOTICE +94 -94
- package/README.md +101 -114
- package/bin/asc.js +0 -0
- package/bin/asinit.js +0 -0
- package/dist/asc.generated.d.ts +10022 -0
- package/dist/asc.js +24457 -0
- package/dist/asc.js.map +7 -0
- package/dist/importmap.json +9 -0
- package/dist/toilscript.generated.d.ts +11237 -0
- package/dist/toilscript.js +337 -0
- package/dist/toilscript.js.map +7 -0
- package/dist/web.js +22 -0
- package/lib/binaryen.d.ts +2 -2
- package/lib/binaryen.js +2 -2
- package/package.json +115 -114
- package/std/README.md +6 -6
- package/std/assembly/array.ts +550 -550
- package/std/assembly/arraybuffer.ts +77 -77
- package/std/assembly/atomics.ts +127 -127
- package/std/assembly/bindings/asyncify.ts +16 -16
- package/std/assembly/bindings/dom.ts +291 -291
- package/std/assembly/bindings/node.ts +6 -6
- package/std/assembly/bitflags.ts +53 -53
- package/std/assembly/builtins.ts +2650 -2650
- package/std/assembly/byteslice.ts +177 -177
- package/std/assembly/compat.ts +2 -2
- package/std/assembly/console.ts +42 -42
- package/std/assembly/crypto.ts +9 -9
- package/std/assembly/dataview.ts +181 -181
- package/std/assembly/date.ts +375 -375
- package/std/assembly/diagnostics.ts +11 -11
- package/std/assembly/encoding.ts +151 -151
- package/std/assembly/endian.ts +45 -45
- package/std/assembly/error.ts +44 -44
- package/std/assembly/fixedarray.ts +173 -173
- package/std/assembly/fixedmap.ts +326 -326
- package/std/assembly/fixedset.ts +275 -275
- package/std/assembly/function.ts +42 -42
- package/std/assembly/index.d.ts +2891 -2891
- package/std/assembly/iterator.ts +35 -35
- package/std/assembly/map.ts +269 -269
- package/std/assembly/math.ts +3289 -3289
- package/std/assembly/memory.ts +123 -123
- package/std/assembly/number.ts +388 -388
- package/std/assembly/object.ts +36 -36
- package/std/assembly/performance.ts +9 -9
- package/std/assembly/pointer.ts +80 -80
- package/std/assembly/polyfills.ts +27 -27
- package/std/assembly/process.ts +50 -50
- package/std/assembly/reference.ts +48 -48
- package/std/assembly/regexp.ts +12 -12
- package/std/assembly/rt/README.md +83 -83
- package/std/assembly/rt/common.ts +81 -81
- package/std/assembly/rt/index-incremental.ts +2 -2
- package/std/assembly/rt/index-memory.ts +1 -1
- package/std/assembly/rt/index-minimal.ts +2 -2
- package/std/assembly/rt/index-stub.ts +1 -1
- package/std/assembly/rt/index.d.ts +37 -37
- package/std/assembly/rt/itcms.ts +419 -419
- package/std/assembly/rt/memory-runtime.ts +94 -94
- package/std/assembly/rt/rtrace.ts +15 -15
- package/std/assembly/rt/stub.ts +133 -133
- package/std/assembly/rt/tcms.ts +254 -254
- package/std/assembly/rt/tlsf.ts +592 -592
- package/std/assembly/rt.ts +90 -90
- package/std/assembly/set.ts +225 -225
- package/std/assembly/shared/feature.ts +68 -68
- package/std/assembly/shared/runtime.ts +13 -13
- package/std/assembly/shared/target.ts +11 -11
- package/std/assembly/shared/tsconfig.json +11 -11
- package/std/assembly/shared/typeinfo.ts +72 -72
- package/std/assembly/staticarray.ts +423 -423
- package/std/assembly/string.ts +850 -850
- package/std/assembly/symbol.ts +114 -114
- package/std/assembly/table.ts +16 -16
- package/std/assembly/tsconfig.json +6 -6
- package/std/assembly/typedarray.ts +1954 -1954
- package/std/assembly/uri.ts +17 -17
- package/std/assembly/util/bytes.ts +107 -107
- package/std/assembly/util/casemap.ts +497 -497
- package/std/assembly/util/error.ts +58 -58
- package/std/assembly/util/hash.ts +117 -117
- package/std/assembly/util/math.ts +1922 -1922
- package/std/assembly/util/memory.ts +290 -290
- package/std/assembly/util/number.ts +873 -873
- package/std/assembly/util/sort.ts +313 -313
- package/std/assembly/util/string.ts +1202 -1202
- package/std/assembly/util/uri.ts +275 -275
- package/std/assembly/vector.ts +4 -4
- package/std/assembly.json +16 -16
- package/std/portable/index.d.ts +461 -461
- package/std/portable/index.js +416 -416
- package/std/portable.json +11 -11
- package/std/types/assembly/index.d.ts +1 -1
- package/std/types/assembly/package.json +2 -2
- package/std/types/portable/index.d.ts +1 -1
- package/std/types/portable/package.json +2 -2
- package/tsconfig-base.json +13 -13
- package/util/README.md +23 -23
- package/util/browser/fs.js +1 -1
- package/util/browser/module.js +5 -5
- package/util/browser/path.js +520 -520
- package/util/browser/process.js +59 -59
- package/util/browser/url.js +23 -23
- package/util/cpu.d.ts +9 -9
- package/util/cpu.js +42 -42
- package/util/find.d.ts +6 -6
- package/util/find.js +20 -20
- package/util/node.d.ts +21 -21
- package/util/node.js +34 -34
- package/util/options.d.ts +70 -70
- package/util/options.js +262 -262
- package/util/terminal.d.ts +52 -52
- package/util/terminal.js +35 -35
- package/util/text.d.ts +26 -26
- package/util/text.js +114 -114
- package/util/tsconfig.json +9 -9
- package/util/web.d.ts +11 -11
- package/util/web.js +33 -33
package/std/assembly/date.ts
CHANGED
|
@@ -1,375 +1,375 @@
|
|
|
1
|
-
import { E_INVALIDDATE } from "util/error";
|
|
2
|
-
import { Date as Date_binding } from "./bindings/dom";
|
|
3
|
-
|
|
4
|
-
// @ts-ignore: decorator
|
|
5
|
-
@inline const
|
|
6
|
-
MILLIS_PER_DAY = 1000 * 60 * 60 * 24,
|
|
7
|
-
MILLIS_PER_HOUR = 1000 * 60 * 60,
|
|
8
|
-
MILLIS_PER_MINUTE = 1000 * 60,
|
|
9
|
-
MILLIS_PER_SECOND = 1000,
|
|
10
|
-
|
|
11
|
-
YEARS_PER_EPOCH = 400,
|
|
12
|
-
DAYS_PER_EPOCH = 146097,
|
|
13
|
-
EPOCH_OFFSET = 719468, // Jan 1, 1970
|
|
14
|
-
MILLIS_LIMIT = 8640000000000000;
|
|
15
|
-
|
|
16
|
-
// ymdFromEpochDays returns values via globals to avoid allocations
|
|
17
|
-
// @ts-ignore: decorator
|
|
18
|
-
@lazy let _month: i32, _day: i32;
|
|
19
|
-
|
|
20
|
-
export class Date {
|
|
21
|
-
private year: i32 = 0;
|
|
22
|
-
private month: i32 = 0;
|
|
23
|
-
private day: i32 = 0;
|
|
24
|
-
|
|
25
|
-
@inline static UTC(
|
|
26
|
-
year: i32,
|
|
27
|
-
month: i32 = 0,
|
|
28
|
-
day: i32 = 1,
|
|
29
|
-
hour: i32 = 0,
|
|
30
|
-
minute: i32 = 0,
|
|
31
|
-
second: i32 = 0,
|
|
32
|
-
millisecond: i32 = 0
|
|
33
|
-
): i64 {
|
|
34
|
-
if (year >= 0 && year <= 99) year += 1900;
|
|
35
|
-
let ms = epochMillis(year, month + 1, day, hour, minute, second, millisecond);
|
|
36
|
-
if (invalidDate(ms)) throw new RangeError(E_INVALIDDATE);
|
|
37
|
-
return ms;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
@inline static now(): i64 {
|
|
41
|
-
return <i64>Date_binding.now();
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// It can parse only ISO 8601 inputs like YYYY-MM-DDTHH:MM:SS.000Z
|
|
45
|
-
@inline static parse(dateString: string): Date {
|
|
46
|
-
return this.fromString(dateString);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
static fromString(dateTimeString: string): Date {
|
|
50
|
-
if (!dateTimeString.length) throw new RangeError(E_INVALIDDATE);
|
|
51
|
-
var
|
|
52
|
-
hour: i32 = 0,
|
|
53
|
-
min: i32 = 0,
|
|
54
|
-
sec: i32 = 0,
|
|
55
|
-
ms: i32 = 0,
|
|
56
|
-
offsetMs: i32 = 0;
|
|
57
|
-
|
|
58
|
-
let dateString = dateTimeString;
|
|
59
|
-
let posT = dateTimeString.indexOf("T");
|
|
60
|
-
if (~posT) {
|
|
61
|
-
// includes a time component
|
|
62
|
-
let timeString: string;
|
|
63
|
-
dateString = dateTimeString.substring(0, posT);
|
|
64
|
-
timeString = dateTimeString.substring(posT + 1);
|
|
65
|
-
|
|
66
|
-
// might end with an offset ("Z", "+05:30", "-08:00", etc.)
|
|
67
|
-
for (let i = timeString.length - 1; i >= 0; i--) {
|
|
68
|
-
let c = timeString.charCodeAt(i);
|
|
69
|
-
if (c == 90) { // Z
|
|
70
|
-
timeString = timeString.substring(0, i);
|
|
71
|
-
break;
|
|
72
|
-
} else if (c == 43 || c == 45) { // + or -
|
|
73
|
-
if (i == timeString.length - 1) {
|
|
74
|
-
throw new RangeError(E_INVALIDDATE);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
let posColon = timeString.indexOf(":", i + 1);
|
|
78
|
-
if (~posColon) {
|
|
79
|
-
let offsetHours = i32.parse(timeString.substring(i + 1, posColon));
|
|
80
|
-
let offsetMinutes = i32.parse(timeString.substring(posColon + 1));
|
|
81
|
-
offsetMs = (offsetHours * 60 + offsetMinutes) * MILLIS_PER_MINUTE;
|
|
82
|
-
} else {
|
|
83
|
-
let offsetHours = i32.parse(timeString.substring(i + 1));
|
|
84
|
-
offsetMs = offsetHours * MILLIS_PER_HOUR;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (c == 45) offsetMs = -offsetMs; // negative offset
|
|
88
|
-
timeString = timeString.substring(0, i);
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// parse the HH:MM:SS component
|
|
94
|
-
let timeParts = timeString.split(":");
|
|
95
|
-
let len = timeParts.length;
|
|
96
|
-
if (len <= 1) throw new RangeError(E_INVALIDDATE);
|
|
97
|
-
|
|
98
|
-
hour = i32.parse(timeParts[0]);
|
|
99
|
-
min = i32.parse(timeParts[1]);
|
|
100
|
-
if (len >= 3) {
|
|
101
|
-
let secAndFrac = timeParts[2];
|
|
102
|
-
let posDot = secAndFrac.indexOf(".");
|
|
103
|
-
if (~posDot) {
|
|
104
|
-
// includes fractional seconds (truncate to milliseconds)
|
|
105
|
-
sec = i32.parse(secAndFrac.substring(0, posDot));
|
|
106
|
-
ms = i32.parse(secAndFrac.substr(posDot + 1, 3).padEnd(3, "0"));
|
|
107
|
-
} else {
|
|
108
|
-
sec = i32.parse(secAndFrac);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// parse the YYYY-MM-DD component
|
|
114
|
-
let parts = dateString.split("-");
|
|
115
|
-
let year = i32.parse(parts[0]);
|
|
116
|
-
let month = 1, day = 1;
|
|
117
|
-
let len = parts.length;
|
|
118
|
-
if (len >= 2) {
|
|
119
|
-
month = i32.parse(parts[1]);
|
|
120
|
-
if (len >= 3) {
|
|
121
|
-
day = i32.parse(parts[2]);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return new Date(epochMillis(year, month, day, hour, min, sec, ms) - offsetMs);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
constructor(private epochMillis: i64) {
|
|
129
|
-
// this differs from JavaScript which prefer return NaN or "Invalid Date" string
|
|
130
|
-
// instead throwing exception.
|
|
131
|
-
if (invalidDate(epochMillis)) throw new RangeError(E_INVALIDDATE);
|
|
132
|
-
|
|
133
|
-
this.year = dateFromEpoch(epochMillis);
|
|
134
|
-
this.month = _month;
|
|
135
|
-
this.day = _day;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
@inline getTime(): i64 {
|
|
139
|
-
return this.epochMillis;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
setTime(time: i64): i64 {
|
|
143
|
-
if (invalidDate(time)) throw new RangeError(E_INVALIDDATE);
|
|
144
|
-
|
|
145
|
-
this.epochMillis = time;
|
|
146
|
-
this.year = dateFromEpoch(time);
|
|
147
|
-
this.month = _month;
|
|
148
|
-
this.day = _day;
|
|
149
|
-
|
|
150
|
-
return time;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
@inline getUTCFullYear(): i32 {
|
|
154
|
-
return this.year;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
@inline getUTCMonth(): i32 {
|
|
158
|
-
return this.month - 1;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
@inline getUTCDate(): i32 {
|
|
162
|
-
return this.day;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
@inline getUTCDay(): i32 {
|
|
166
|
-
return dayOfWeek(this.year, this.month, this.day);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
getUTCHours(): i32 {
|
|
170
|
-
return i32(euclidRem(this.epochMillis, MILLIS_PER_DAY)) / MILLIS_PER_HOUR;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
getUTCMinutes(): i32 {
|
|
174
|
-
return i32(euclidRem(this.epochMillis, MILLIS_PER_HOUR)) / MILLIS_PER_MINUTE;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
getUTCSeconds(): i32 {
|
|
178
|
-
return i32(euclidRem(this.epochMillis, MILLIS_PER_MINUTE)) / MILLIS_PER_SECOND;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
getUTCMilliseconds(): i32 {
|
|
182
|
-
return i32(euclidRem(this.epochMillis, MILLIS_PER_SECOND));
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
setUTCMilliseconds(millis: i32): void {
|
|
186
|
-
this.setTime(this.epochMillis + (millis - this.getUTCMilliseconds()));
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
setUTCSeconds(seconds: i32): void {
|
|
190
|
-
this.setTime(this.epochMillis + (seconds - this.getUTCSeconds()) * MILLIS_PER_SECOND);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
setUTCMinutes(minutes: i32): void {
|
|
194
|
-
this.setTime(this.epochMillis + (minutes - this.getUTCMinutes()) * MILLIS_PER_MINUTE);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
setUTCHours(hours: i32): void {
|
|
198
|
-
this.setTime(this.epochMillis + (hours - this.getUTCHours()) * MILLIS_PER_HOUR);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
setUTCDate(day: i32): void {
|
|
202
|
-
if (this.day == day) return;
|
|
203
|
-
this.setTime(join(this.year, this.month, day, this.epochMillis));
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
setUTCMonth(month: i32, day: i32 = this.day): void {
|
|
207
|
-
if (this.month == month + 1) return;
|
|
208
|
-
this.setTime(join(this.year, month + 1, day, this.epochMillis));
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
setUTCFullYear(year: i32): void {
|
|
212
|
-
if (this.year == year) return;
|
|
213
|
-
this.setTime(join(year, this.month, this.day, this.epochMillis));
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
toISOString(): string {
|
|
217
|
-
// TODO: add more low-level helper which combine toString and padStart without extra allocation
|
|
218
|
-
|
|
219
|
-
let yr = this.year;
|
|
220
|
-
let isNeg = yr < 0;
|
|
221
|
-
let year = (isNeg || yr >= 10000)
|
|
222
|
-
? (isNeg ? "-" : "+") + stringify(abs(yr), 6)
|
|
223
|
-
: stringify(yr, 4);
|
|
224
|
-
let month = stringify(this.month, 2);
|
|
225
|
-
let day = stringify(this.day);
|
|
226
|
-
let hours = stringify(this.getUTCHours());
|
|
227
|
-
let mins = stringify(this.getUTCMinutes());
|
|
228
|
-
let secs = stringify(this.getUTCSeconds());
|
|
229
|
-
let ms = stringify(this.getUTCMilliseconds(), 3);
|
|
230
|
-
|
|
231
|
-
return `${year}-${month}-${day}T${hours}:${mins}:${secs}.${ms}Z`;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
toUTCString(): string {
|
|
235
|
-
const
|
|
236
|
-
weeks: StaticArray<string> = [
|
|
237
|
-
"Sun, ", "Mon, ", "Tue, ", "Wed, ", "Thu, ", "Fri, ", "Sat, "
|
|
238
|
-
],
|
|
239
|
-
months: StaticArray<string> = [
|
|
240
|
-
" Jan ", " Feb ", " Mar ", " Apr ", " May ", " Jun ",
|
|
241
|
-
" Jul ", " Aug ", " Sep ", " Oct ", " Nov ", " Dec "
|
|
242
|
-
];
|
|
243
|
-
|
|
244
|
-
let mo = this.month;
|
|
245
|
-
let da = this.day;
|
|
246
|
-
let yr = this.year;
|
|
247
|
-
let wd = dayOfWeek(yr, mo, da);
|
|
248
|
-
let year = stringify(abs(yr), 4);
|
|
249
|
-
let month = unchecked(months[mo - 1]);
|
|
250
|
-
let week = unchecked(weeks[wd]);
|
|
251
|
-
let day = stringify(da);
|
|
252
|
-
let hours = stringify(this.getUTCHours());
|
|
253
|
-
let mins = stringify(this.getUTCMinutes());
|
|
254
|
-
let secs = stringify(this.getUTCSeconds());
|
|
255
|
-
|
|
256
|
-
return `${week}${day}${month}${yr < 0 ? "-" : ""}${year} ${hours}:${mins}:${secs} GMT`;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
toDateString(): string {
|
|
260
|
-
// TODO: use u64 static data instead 4 chars
|
|
261
|
-
// also use stream itoa variants.
|
|
262
|
-
const
|
|
263
|
-
weeks: StaticArray<string> = [
|
|
264
|
-
"Sun ", "Mon ", "Tue ", "Wed ", "Thu ", "Fri ", "Sat "
|
|
265
|
-
],
|
|
266
|
-
months: StaticArray<string> = [
|
|
267
|
-
"Jan ", "Feb ", "Mar ", "Apr ", "May ", "Jun ",
|
|
268
|
-
"Jul ", "Aug ", "Sep ", "Oct ", "Nov ", "Dec "
|
|
269
|
-
];
|
|
270
|
-
|
|
271
|
-
let mo = this.month;
|
|
272
|
-
let da = this.day;
|
|
273
|
-
let yr = this.year;
|
|
274
|
-
let wd = dayOfWeek(yr, mo, da);
|
|
275
|
-
let year = stringify(abs(yr), 4);
|
|
276
|
-
let month = unchecked(months[mo - 1]);
|
|
277
|
-
let week = unchecked(weeks[wd]);
|
|
278
|
-
let day = stringify(da);
|
|
279
|
-
|
|
280
|
-
return `${week}${month}${day}${yr < 0 ? " -" : " "}${year}`;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Note: it uses UTC time instead local time (without timezone offset)
|
|
284
|
-
toTimeString(): string {
|
|
285
|
-
let hours = stringify(this.getUTCHours());
|
|
286
|
-
let mins = stringify(this.getUTCMinutes());
|
|
287
|
-
let secs = stringify(this.getUTCSeconds());
|
|
288
|
-
// TODO: add timezone
|
|
289
|
-
return `${hours}:${mins}:${secs}`;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// Note: it uses UTC datetime instead local datetime (without timezone offset)
|
|
293
|
-
toString(): string {
|
|
294
|
-
return `${this.toDateString()} ${this.toTimeString()}`;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
function epochMillis(
|
|
299
|
-
year: i32,
|
|
300
|
-
month: i32,
|
|
301
|
-
day: i32,
|
|
302
|
-
hour: i32,
|
|
303
|
-
minute: i32,
|
|
304
|
-
second: i32,
|
|
305
|
-
milliseconds: i32
|
|
306
|
-
): i64 {
|
|
307
|
-
return (
|
|
308
|
-
daysSinceEpoch(year, month, day) * MILLIS_PER_DAY +
|
|
309
|
-
hour * MILLIS_PER_HOUR +
|
|
310
|
-
minute * MILLIS_PER_MINUTE +
|
|
311
|
-
second * MILLIS_PER_SECOND +
|
|
312
|
-
milliseconds
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// @ts-ignore: decorator
|
|
317
|
-
@inline function floorDiv<T extends number>(a: T, b: T): T {
|
|
318
|
-
return (a - (a < 0 ? b - 1 : 0)) / b as T;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// @ts-ignore: decorator
|
|
322
|
-
@inline function euclidRem<T extends number>(a: T, b: T): T {
|
|
323
|
-
let m = a % b;
|
|
324
|
-
return m + (m < 0 ? b : 0) as T;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
function invalidDate(millis: i64): bool {
|
|
328
|
-
// @ts-ignore
|
|
329
|
-
return (millis < -MILLIS_LIMIT) | (millis > MILLIS_LIMIT);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// Based on "Euclidean Affine Functions and Applications to Calendar Algorithms"
|
|
333
|
-
// Paper: https://arxiv.org/pdf/2102.06959.pdf
|
|
334
|
-
function dateFromEpoch(ms: i64): i32 {
|
|
335
|
-
let da = (<i32>floorDiv(ms, MILLIS_PER_DAY) * 4 + EPOCH_OFFSET * 4) | 3;
|
|
336
|
-
let q0 = floorDiv(da, DAYS_PER_EPOCH); // [0, 146096]
|
|
337
|
-
let r1 = <u32>da - q0 * DAYS_PER_EPOCH;
|
|
338
|
-
let u1 = u64(r1 | 3) * 2939745;
|
|
339
|
-
let dm1 = <u32>u1 / 11758980;
|
|
340
|
-
let n1 = 2141 * dm1 + 197913;
|
|
341
|
-
let year = 100 * q0 + i32(u1 >>> 32);
|
|
342
|
-
let mo = n1 >>> 16;
|
|
343
|
-
_day = (n1 & 0xFFFF) / 2141 + 1; // [1, 31]
|
|
344
|
-
if (dm1 >= 306) { mo -= 12; ++year; }
|
|
345
|
-
_month = mo; // [1, 12]
|
|
346
|
-
return year;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// http://howardhinnant.github.io/date_algorithms.html#days_from_civil
|
|
350
|
-
function daysSinceEpoch(y: i32, m: i32, d: i32): i64 {
|
|
351
|
-
y -= i32(m <= 2);
|
|
352
|
-
let era = <u32>floorDiv(y, YEARS_PER_EPOCH);
|
|
353
|
-
let yoe = <u32>y - era * YEARS_PER_EPOCH; // [0, 399]
|
|
354
|
-
let doy = <u32>(153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; // [0, 365]
|
|
355
|
-
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096]
|
|
356
|
-
return <i64><i32>(era * 146097 + doe - EPOCH_OFFSET);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// TomohikoSakamoto algorithm from https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week
|
|
360
|
-
function dayOfWeek(year: i32, month: i32, day: i32): i32 {
|
|
361
|
-
const tab = memory.data<u8>([0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4]);
|
|
362
|
-
|
|
363
|
-
year -= i32(month < 3);
|
|
364
|
-
year += floorDiv(year, 4) - floorDiv(year, 100) + floorDiv(year, YEARS_PER_EPOCH);
|
|
365
|
-
month = <i32>load<u8>(tab + month - 1);
|
|
366
|
-
return euclidRem(year + month + day, 7);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
function stringify(value: i32, padding: i32 = 2): string {
|
|
370
|
-
return value.toString().padStart(padding, "0");
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
function join(year: i32, month: i32, day: i32, ms: i64): i64 {
|
|
374
|
-
return daysSinceEpoch(year, month, day) * MILLIS_PER_DAY + euclidRem(ms, MILLIS_PER_DAY);
|
|
375
|
-
}
|
|
1
|
+
import { E_INVALIDDATE } from "util/error";
|
|
2
|
+
import { Date as Date_binding } from "./bindings/dom";
|
|
3
|
+
|
|
4
|
+
// @ts-ignore: decorator
|
|
5
|
+
@inline const
|
|
6
|
+
MILLIS_PER_DAY = 1000 * 60 * 60 * 24,
|
|
7
|
+
MILLIS_PER_HOUR = 1000 * 60 * 60,
|
|
8
|
+
MILLIS_PER_MINUTE = 1000 * 60,
|
|
9
|
+
MILLIS_PER_SECOND = 1000,
|
|
10
|
+
|
|
11
|
+
YEARS_PER_EPOCH = 400,
|
|
12
|
+
DAYS_PER_EPOCH = 146097,
|
|
13
|
+
EPOCH_OFFSET = 719468, // Jan 1, 1970
|
|
14
|
+
MILLIS_LIMIT = 8640000000000000;
|
|
15
|
+
|
|
16
|
+
// ymdFromEpochDays returns values via globals to avoid allocations
|
|
17
|
+
// @ts-ignore: decorator
|
|
18
|
+
@lazy let _month: i32, _day: i32;
|
|
19
|
+
|
|
20
|
+
export class Date {
|
|
21
|
+
private year: i32 = 0;
|
|
22
|
+
private month: i32 = 0;
|
|
23
|
+
private day: i32 = 0;
|
|
24
|
+
|
|
25
|
+
@inline static UTC(
|
|
26
|
+
year: i32,
|
|
27
|
+
month: i32 = 0,
|
|
28
|
+
day: i32 = 1,
|
|
29
|
+
hour: i32 = 0,
|
|
30
|
+
minute: i32 = 0,
|
|
31
|
+
second: i32 = 0,
|
|
32
|
+
millisecond: i32 = 0
|
|
33
|
+
): i64 {
|
|
34
|
+
if (year >= 0 && year <= 99) year += 1900;
|
|
35
|
+
let ms = epochMillis(year, month + 1, day, hour, minute, second, millisecond);
|
|
36
|
+
if (invalidDate(ms)) throw new RangeError(E_INVALIDDATE);
|
|
37
|
+
return ms;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@inline static now(): i64 {
|
|
41
|
+
return <i64>Date_binding.now();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// It can parse only ISO 8601 inputs like YYYY-MM-DDTHH:MM:SS.000Z
|
|
45
|
+
@inline static parse(dateString: string): Date {
|
|
46
|
+
return this.fromString(dateString);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static fromString(dateTimeString: string): Date {
|
|
50
|
+
if (!dateTimeString.length) throw new RangeError(E_INVALIDDATE);
|
|
51
|
+
var
|
|
52
|
+
hour: i32 = 0,
|
|
53
|
+
min: i32 = 0,
|
|
54
|
+
sec: i32 = 0,
|
|
55
|
+
ms: i32 = 0,
|
|
56
|
+
offsetMs: i32 = 0;
|
|
57
|
+
|
|
58
|
+
let dateString = dateTimeString;
|
|
59
|
+
let posT = dateTimeString.indexOf("T");
|
|
60
|
+
if (~posT) {
|
|
61
|
+
// includes a time component
|
|
62
|
+
let timeString: string;
|
|
63
|
+
dateString = dateTimeString.substring(0, posT);
|
|
64
|
+
timeString = dateTimeString.substring(posT + 1);
|
|
65
|
+
|
|
66
|
+
// might end with an offset ("Z", "+05:30", "-08:00", etc.)
|
|
67
|
+
for (let i = timeString.length - 1; i >= 0; i--) {
|
|
68
|
+
let c = timeString.charCodeAt(i);
|
|
69
|
+
if (c == 90) { // Z
|
|
70
|
+
timeString = timeString.substring(0, i);
|
|
71
|
+
break;
|
|
72
|
+
} else if (c == 43 || c == 45) { // + or -
|
|
73
|
+
if (i == timeString.length - 1) {
|
|
74
|
+
throw new RangeError(E_INVALIDDATE);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let posColon = timeString.indexOf(":", i + 1);
|
|
78
|
+
if (~posColon) {
|
|
79
|
+
let offsetHours = i32.parse(timeString.substring(i + 1, posColon));
|
|
80
|
+
let offsetMinutes = i32.parse(timeString.substring(posColon + 1));
|
|
81
|
+
offsetMs = (offsetHours * 60 + offsetMinutes) * MILLIS_PER_MINUTE;
|
|
82
|
+
} else {
|
|
83
|
+
let offsetHours = i32.parse(timeString.substring(i + 1));
|
|
84
|
+
offsetMs = offsetHours * MILLIS_PER_HOUR;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (c == 45) offsetMs = -offsetMs; // negative offset
|
|
88
|
+
timeString = timeString.substring(0, i);
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// parse the HH:MM:SS component
|
|
94
|
+
let timeParts = timeString.split(":");
|
|
95
|
+
let len = timeParts.length;
|
|
96
|
+
if (len <= 1) throw new RangeError(E_INVALIDDATE);
|
|
97
|
+
|
|
98
|
+
hour = i32.parse(timeParts[0]);
|
|
99
|
+
min = i32.parse(timeParts[1]);
|
|
100
|
+
if (len >= 3) {
|
|
101
|
+
let secAndFrac = timeParts[2];
|
|
102
|
+
let posDot = secAndFrac.indexOf(".");
|
|
103
|
+
if (~posDot) {
|
|
104
|
+
// includes fractional seconds (truncate to milliseconds)
|
|
105
|
+
sec = i32.parse(secAndFrac.substring(0, posDot));
|
|
106
|
+
ms = i32.parse(secAndFrac.substr(posDot + 1, 3).padEnd(3, "0"));
|
|
107
|
+
} else {
|
|
108
|
+
sec = i32.parse(secAndFrac);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// parse the YYYY-MM-DD component
|
|
114
|
+
let parts = dateString.split("-");
|
|
115
|
+
let year = i32.parse(parts[0]);
|
|
116
|
+
let month = 1, day = 1;
|
|
117
|
+
let len = parts.length;
|
|
118
|
+
if (len >= 2) {
|
|
119
|
+
month = i32.parse(parts[1]);
|
|
120
|
+
if (len >= 3) {
|
|
121
|
+
day = i32.parse(parts[2]);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return new Date(epochMillis(year, month, day, hour, min, sec, ms) - offsetMs);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
constructor(private epochMillis: i64) {
|
|
129
|
+
// this differs from JavaScript which prefer return NaN or "Invalid Date" string
|
|
130
|
+
// instead throwing exception.
|
|
131
|
+
if (invalidDate(epochMillis)) throw new RangeError(E_INVALIDDATE);
|
|
132
|
+
|
|
133
|
+
this.year = dateFromEpoch(epochMillis);
|
|
134
|
+
this.month = _month;
|
|
135
|
+
this.day = _day;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@inline getTime(): i64 {
|
|
139
|
+
return this.epochMillis;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
setTime(time: i64): i64 {
|
|
143
|
+
if (invalidDate(time)) throw new RangeError(E_INVALIDDATE);
|
|
144
|
+
|
|
145
|
+
this.epochMillis = time;
|
|
146
|
+
this.year = dateFromEpoch(time);
|
|
147
|
+
this.month = _month;
|
|
148
|
+
this.day = _day;
|
|
149
|
+
|
|
150
|
+
return time;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
@inline getUTCFullYear(): i32 {
|
|
154
|
+
return this.year;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
@inline getUTCMonth(): i32 {
|
|
158
|
+
return this.month - 1;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
@inline getUTCDate(): i32 {
|
|
162
|
+
return this.day;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
@inline getUTCDay(): i32 {
|
|
166
|
+
return dayOfWeek(this.year, this.month, this.day);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
getUTCHours(): i32 {
|
|
170
|
+
return i32(euclidRem(this.epochMillis, MILLIS_PER_DAY)) / MILLIS_PER_HOUR;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
getUTCMinutes(): i32 {
|
|
174
|
+
return i32(euclidRem(this.epochMillis, MILLIS_PER_HOUR)) / MILLIS_PER_MINUTE;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
getUTCSeconds(): i32 {
|
|
178
|
+
return i32(euclidRem(this.epochMillis, MILLIS_PER_MINUTE)) / MILLIS_PER_SECOND;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
getUTCMilliseconds(): i32 {
|
|
182
|
+
return i32(euclidRem(this.epochMillis, MILLIS_PER_SECOND));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
setUTCMilliseconds(millis: i32): void {
|
|
186
|
+
this.setTime(this.epochMillis + (millis - this.getUTCMilliseconds()));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
setUTCSeconds(seconds: i32): void {
|
|
190
|
+
this.setTime(this.epochMillis + (seconds - this.getUTCSeconds()) * MILLIS_PER_SECOND);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
setUTCMinutes(minutes: i32): void {
|
|
194
|
+
this.setTime(this.epochMillis + (minutes - this.getUTCMinutes()) * MILLIS_PER_MINUTE);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
setUTCHours(hours: i32): void {
|
|
198
|
+
this.setTime(this.epochMillis + (hours - this.getUTCHours()) * MILLIS_PER_HOUR);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
setUTCDate(day: i32): void {
|
|
202
|
+
if (this.day == day) return;
|
|
203
|
+
this.setTime(join(this.year, this.month, day, this.epochMillis));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
setUTCMonth(month: i32, day: i32 = this.day): void {
|
|
207
|
+
if (this.month == month + 1) return;
|
|
208
|
+
this.setTime(join(this.year, month + 1, day, this.epochMillis));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
setUTCFullYear(year: i32): void {
|
|
212
|
+
if (this.year == year) return;
|
|
213
|
+
this.setTime(join(year, this.month, this.day, this.epochMillis));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
toISOString(): string {
|
|
217
|
+
// TODO: add more low-level helper which combine toString and padStart without extra allocation
|
|
218
|
+
|
|
219
|
+
let yr = this.year;
|
|
220
|
+
let isNeg = yr < 0;
|
|
221
|
+
let year = (isNeg || yr >= 10000)
|
|
222
|
+
? (isNeg ? "-" : "+") + stringify(abs(yr), 6)
|
|
223
|
+
: stringify(yr, 4);
|
|
224
|
+
let month = stringify(this.month, 2);
|
|
225
|
+
let day = stringify(this.day);
|
|
226
|
+
let hours = stringify(this.getUTCHours());
|
|
227
|
+
let mins = stringify(this.getUTCMinutes());
|
|
228
|
+
let secs = stringify(this.getUTCSeconds());
|
|
229
|
+
let ms = stringify(this.getUTCMilliseconds(), 3);
|
|
230
|
+
|
|
231
|
+
return `${year}-${month}-${day}T${hours}:${mins}:${secs}.${ms}Z`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
toUTCString(): string {
|
|
235
|
+
const
|
|
236
|
+
weeks: StaticArray<string> = [
|
|
237
|
+
"Sun, ", "Mon, ", "Tue, ", "Wed, ", "Thu, ", "Fri, ", "Sat, "
|
|
238
|
+
],
|
|
239
|
+
months: StaticArray<string> = [
|
|
240
|
+
" Jan ", " Feb ", " Mar ", " Apr ", " May ", " Jun ",
|
|
241
|
+
" Jul ", " Aug ", " Sep ", " Oct ", " Nov ", " Dec "
|
|
242
|
+
];
|
|
243
|
+
|
|
244
|
+
let mo = this.month;
|
|
245
|
+
let da = this.day;
|
|
246
|
+
let yr = this.year;
|
|
247
|
+
let wd = dayOfWeek(yr, mo, da);
|
|
248
|
+
let year = stringify(abs(yr), 4);
|
|
249
|
+
let month = unchecked(months[mo - 1]);
|
|
250
|
+
let week = unchecked(weeks[wd]);
|
|
251
|
+
let day = stringify(da);
|
|
252
|
+
let hours = stringify(this.getUTCHours());
|
|
253
|
+
let mins = stringify(this.getUTCMinutes());
|
|
254
|
+
let secs = stringify(this.getUTCSeconds());
|
|
255
|
+
|
|
256
|
+
return `${week}${day}${month}${yr < 0 ? "-" : ""}${year} ${hours}:${mins}:${secs} GMT`;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
toDateString(): string {
|
|
260
|
+
// TODO: use u64 static data instead 4 chars
|
|
261
|
+
// also use stream itoa variants.
|
|
262
|
+
const
|
|
263
|
+
weeks: StaticArray<string> = [
|
|
264
|
+
"Sun ", "Mon ", "Tue ", "Wed ", "Thu ", "Fri ", "Sat "
|
|
265
|
+
],
|
|
266
|
+
months: StaticArray<string> = [
|
|
267
|
+
"Jan ", "Feb ", "Mar ", "Apr ", "May ", "Jun ",
|
|
268
|
+
"Jul ", "Aug ", "Sep ", "Oct ", "Nov ", "Dec "
|
|
269
|
+
];
|
|
270
|
+
|
|
271
|
+
let mo = this.month;
|
|
272
|
+
let da = this.day;
|
|
273
|
+
let yr = this.year;
|
|
274
|
+
let wd = dayOfWeek(yr, mo, da);
|
|
275
|
+
let year = stringify(abs(yr), 4);
|
|
276
|
+
let month = unchecked(months[mo - 1]);
|
|
277
|
+
let week = unchecked(weeks[wd]);
|
|
278
|
+
let day = stringify(da);
|
|
279
|
+
|
|
280
|
+
return `${week}${month}${day}${yr < 0 ? " -" : " "}${year}`;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Note: it uses UTC time instead local time (without timezone offset)
|
|
284
|
+
toTimeString(): string {
|
|
285
|
+
let hours = stringify(this.getUTCHours());
|
|
286
|
+
let mins = stringify(this.getUTCMinutes());
|
|
287
|
+
let secs = stringify(this.getUTCSeconds());
|
|
288
|
+
// TODO: add timezone
|
|
289
|
+
return `${hours}:${mins}:${secs}`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Note: it uses UTC datetime instead local datetime (without timezone offset)
|
|
293
|
+
toString(): string {
|
|
294
|
+
return `${this.toDateString()} ${this.toTimeString()}`;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function epochMillis(
|
|
299
|
+
year: i32,
|
|
300
|
+
month: i32,
|
|
301
|
+
day: i32,
|
|
302
|
+
hour: i32,
|
|
303
|
+
minute: i32,
|
|
304
|
+
second: i32,
|
|
305
|
+
milliseconds: i32
|
|
306
|
+
): i64 {
|
|
307
|
+
return (
|
|
308
|
+
daysSinceEpoch(year, month, day) * MILLIS_PER_DAY +
|
|
309
|
+
hour * MILLIS_PER_HOUR +
|
|
310
|
+
minute * MILLIS_PER_MINUTE +
|
|
311
|
+
second * MILLIS_PER_SECOND +
|
|
312
|
+
milliseconds
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// @ts-ignore: decorator
|
|
317
|
+
@inline function floorDiv<T extends number>(a: T, b: T): T {
|
|
318
|
+
return (a - (a < 0 ? b - 1 : 0)) / b as T;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// @ts-ignore: decorator
|
|
322
|
+
@inline function euclidRem<T extends number>(a: T, b: T): T {
|
|
323
|
+
let m = a % b;
|
|
324
|
+
return m + (m < 0 ? b : 0) as T;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function invalidDate(millis: i64): bool {
|
|
328
|
+
// @ts-ignore
|
|
329
|
+
return (millis < -MILLIS_LIMIT) | (millis > MILLIS_LIMIT);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Based on "Euclidean Affine Functions and Applications to Calendar Algorithms"
|
|
333
|
+
// Paper: https://arxiv.org/pdf/2102.06959.pdf
|
|
334
|
+
function dateFromEpoch(ms: i64): i32 {
|
|
335
|
+
let da = (<i32>floorDiv(ms, MILLIS_PER_DAY) * 4 + EPOCH_OFFSET * 4) | 3;
|
|
336
|
+
let q0 = floorDiv(da, DAYS_PER_EPOCH); // [0, 146096]
|
|
337
|
+
let r1 = <u32>da - q0 * DAYS_PER_EPOCH;
|
|
338
|
+
let u1 = u64(r1 | 3) * 2939745;
|
|
339
|
+
let dm1 = <u32>u1 / 11758980;
|
|
340
|
+
let n1 = 2141 * dm1 + 197913;
|
|
341
|
+
let year = 100 * q0 + i32(u1 >>> 32);
|
|
342
|
+
let mo = n1 >>> 16;
|
|
343
|
+
_day = (n1 & 0xFFFF) / 2141 + 1; // [1, 31]
|
|
344
|
+
if (dm1 >= 306) { mo -= 12; ++year; }
|
|
345
|
+
_month = mo; // [1, 12]
|
|
346
|
+
return year;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// http://howardhinnant.github.io/date_algorithms.html#days_from_civil
|
|
350
|
+
function daysSinceEpoch(y: i32, m: i32, d: i32): i64 {
|
|
351
|
+
y -= i32(m <= 2);
|
|
352
|
+
let era = <u32>floorDiv(y, YEARS_PER_EPOCH);
|
|
353
|
+
let yoe = <u32>y - era * YEARS_PER_EPOCH; // [0, 399]
|
|
354
|
+
let doy = <u32>(153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; // [0, 365]
|
|
355
|
+
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096]
|
|
356
|
+
return <i64><i32>(era * 146097 + doe - EPOCH_OFFSET);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// TomohikoSakamoto algorithm from https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week
|
|
360
|
+
function dayOfWeek(year: i32, month: i32, day: i32): i32 {
|
|
361
|
+
const tab = memory.data<u8>([0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4]);
|
|
362
|
+
|
|
363
|
+
year -= i32(month < 3);
|
|
364
|
+
year += floorDiv(year, 4) - floorDiv(year, 100) + floorDiv(year, YEARS_PER_EPOCH);
|
|
365
|
+
month = <i32>load<u8>(tab + month - 1);
|
|
366
|
+
return euclidRem(year + month + day, 7);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function stringify(value: i32, padding: i32 = 2): string {
|
|
370
|
+
return value.toString().padStart(padding, "0");
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function join(year: i32, month: i32, day: i32, ms: i64): i64 {
|
|
374
|
+
return daysSinceEpoch(year, month, day) * MILLIS_PER_DAY + euclidRem(ms, MILLIS_PER_DAY);
|
|
375
|
+
}
|