Skip to content

Commit 30f5d13

Browse files
feat: Implemented getters / setters and various string fns on Date (#1768)
1 parent 70c3bf9 commit 30f5d13

File tree

9 files changed

+10096
-559
lines changed

9 files changed

+10096
-559
lines changed

NOTICE

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ under the licensing terms detailed in LICENSE:
3737
* ookangzheng <git-ed@runbox.no>
3838
* yjhmelody <yjh465402634@gmail.com>
3939
* bnbarak <bn.barak@gmail.com>
40+
* Colin Eberhardt <colin.eberhardt@gmail.com>
4041

4142
Portions of this software are derived from third-party works licensed under
4243
the following terms:

std/assembly/bindings/Date.ts

-11
Original file line numberDiff line numberDiff line change
@@ -1,12 +1 @@
1-
export declare function UTC(
2-
// NOTE: Using i32 below saves us a f64.convert_s instruction and moves the responsibility for
3-
// converting the value to the WASM/JS boundary.
4-
year: i32,
5-
month: i32,
6-
day: i32,
7-
hour: i32,
8-
minute: i32,
9-
second: i32,
10-
millisecond: f64
11-
): f64;
121
export declare function now(): f64;

std/assembly/date.ts

+212-12
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,238 @@
1-
import {
2-
UTC as Date_UTC,
3-
now as Date_now
4-
} from "./bindings/Date";
1+
import { E_VALUEOUTOFRANGE } from "util/error";
2+
import { now as Date_now } from "./bindings/Date";
53

64
export class Date {
7-
85
@inline static UTC(
96
year: i32,
107
month: i32 = 0,
118
day: i32 = 1,
129
hour: i32 = 0,
1310
minute: i32 = 0,
1411
second: i32 = 0,
15-
millisecond: i64 = 0
12+
millisecond: i32 = 0
1613
): i64 {
17-
return <i64>Date_UTC(year, month, day, hour, minute, second, <f64>millisecond);
14+
return epochMillis(year, month + 1, day, hour, minute, second, millisecond);
1815
}
1916

2017
@inline static now(): i64 {
2118
return <i64>Date_now();
2219
}
2320

24-
private value: i64;
21+
static fromString(dateTimeString: string): Date {
22+
let hour: i32 = 0,
23+
minute: i32 = 0,
24+
second: i32 = 0,
25+
millisecond: i32 = 0;
26+
let dateString: string;
27+
28+
if (dateTimeString.includes("T")) {
29+
// includes a time component
30+
const parts = dateTimeString.split("T");
31+
const timeString = parts[1];
32+
// parse the HH-MM-SS component
33+
const timeParts = timeString.split(":");
34+
hour = I32.parseInt(timeParts[0]);
35+
minute = I32.parseInt(timeParts[1]);
36+
if (timeParts[2].includes(".")) {
37+
// includes milliseconds
38+
const secondParts = timeParts[2].split(".");
39+
second = I32.parseInt(secondParts[0]);
40+
millisecond = I32.parseInt(secondParts[1]);
41+
} else {
42+
second = I32.parseInt(timeParts[2]);
43+
}
44+
dateString = parts[0];
45+
} else {
46+
dateString = dateTimeString;
47+
}
48+
// parse the YYYY-MM-DD component
49+
const parts = dateString.split("-");
50+
const year = I32.parseInt(
51+
parts[0].length == 2 ? "19" + parts[0] : parts[0]
52+
);
53+
const month = I32.parseInt(parts[1]);
54+
const day = I32.parseInt(parts[2]);
2555

26-
constructor(value: i64) {
27-
this.value = value;
56+
return new Date(
57+
epochMillis(year, month, day, hour, minute, second, millisecond)
58+
);
59+
}
60+
61+
private epochMillis: i64;
62+
63+
constructor(epochMillis: i64) {
64+
this.epochMillis = epochMillis;
2865
}
2966

3067
getTime(): i64 {
31-
return this.value;
68+
return this.epochMillis;
3269
}
3370

3471
setTime(value: i64): i64 {
35-
this.value = value;
72+
this.epochMillis = value;
3673
return value;
3774
}
75+
76+
getUTCFullYear(): i32 {
77+
ymdFromEpochDays(i32(this.epochMillis / MILLIS_PER_DAY));
78+
return year;
79+
}
80+
81+
getUTCMonth(): i32 {
82+
ymdFromEpochDays(i32(this.epochMillis / MILLIS_PER_DAY));
83+
return month - 1;
84+
}
85+
86+
getUTCDate(): i32 {
87+
ymdFromEpochDays(i32(this.epochMillis / MILLIS_PER_DAY));
88+
return day;
89+
}
90+
91+
getUTCHours(): i32 {
92+
return i32(this.epochMillis % MILLIS_PER_DAY) / MILLIS_PER_HOUR;
93+
}
94+
95+
getUTCMinutes(): i32 {
96+
return i32(this.epochMillis % MILLIS_PER_HOUR) / MILLIS_PER_MINUTE;
97+
}
98+
99+
getUTCSeconds(): i32 {
100+
return i32(this.epochMillis % MILLIS_PER_MINUTE) / MILLIS_PER_SECOND;
101+
}
102+
103+
getUTCMilliseconds(): i32 {
104+
return i32(this.epochMillis % MILLIS_PER_SECOND);
105+
}
106+
107+
setUTCMilliseconds(value: i32): void {
108+
this.epochMillis += value - this.getUTCMilliseconds();
109+
}
110+
111+
setUTCSeconds(value: i32): void {
112+
throwIfNotInRange(value, 0, 59);
113+
this.epochMillis += (value - this.getUTCSeconds()) * MILLIS_PER_SECOND;
114+
}
115+
116+
setUTCMinutes(value: i32): void {
117+
throwIfNotInRange(value, 0, 59);
118+
this.epochMillis += (value - this.getUTCMinutes()) * MILLIS_PER_MINUTE;
119+
}
120+
121+
setUTCHours(value: i32): void {
122+
throwIfNotInRange(value, 0, 23);
123+
this.epochMillis += (value - this.getUTCHours()) * MILLIS_PER_HOUR;
124+
}
125+
126+
setUTCDate(value: i32): void {
127+
ymdFromEpochDays(i32(this.epochMillis / MILLIS_PER_DAY));
128+
throwIfNotInRange(value, 1, daysInMonth(year, month));
129+
const mills = this.epochMillis % MILLIS_PER_DAY;
130+
this.epochMillis =
131+
i64(daysSinceEpoch(year, month, value)) * MILLIS_PER_DAY + mills;
132+
}
133+
134+
setUTCMonth(value: i32): void {
135+
throwIfNotInRange(value, 1, 12);
136+
ymdFromEpochDays(i32(this.epochMillis / MILLIS_PER_DAY));
137+
const mills = this.epochMillis % MILLIS_PER_DAY;
138+
this.epochMillis =
139+
i64(daysSinceEpoch(year, value + 1, day)) * MILLIS_PER_DAY + mills;
140+
}
141+
142+
setUTCFullYear(value: i32): void {
143+
ymdFromEpochDays(i32(this.epochMillis / MILLIS_PER_DAY));
144+
const mills = this.epochMillis % MILLIS_PER_DAY;
145+
this.epochMillis =
146+
i64(daysSinceEpoch(value, month, day)) * MILLIS_PER_DAY + mills;
147+
}
148+
149+
toISOString(): string {
150+
ymdFromEpochDays(i32(this.epochMillis / MILLIS_PER_DAY));
151+
152+
let yearStr = year.toString();
153+
if (yearStr.length > 4) {
154+
yearStr = "+" + yearStr.padStart(6, "0");
155+
}
156+
157+
return (
158+
yearStr +
159+
"-" +
160+
month.toString().padStart(2, "0") +
161+
"-" +
162+
day.toString().padStart(2, "0") +
163+
"T" +
164+
this.getUTCHours().toString().padStart(2, "0") +
165+
":" +
166+
this.getUTCMinutes().toString().padStart(2, "0") +
167+
":" +
168+
this.getUTCSeconds().toString().padStart(2, "0") +
169+
"." +
170+
this.getUTCMilliseconds().toString().padStart(3, "0") +
171+
"Z"
172+
);
173+
}
174+
}
175+
176+
function epochMillis(
177+
year: i32,
178+
month: i32,
179+
day: i32,
180+
hour: i32,
181+
minute: i32,
182+
second: i32,
183+
milliseconds: i32
184+
): i64 {
185+
return (
186+
i64(daysSinceEpoch(year, month, day)) * MILLIS_PER_DAY +
187+
hour * MILLIS_PER_HOUR +
188+
minute * MILLIS_PER_MINUTE +
189+
second * MILLIS_PER_SECOND +
190+
milliseconds
191+
);
192+
}
193+
194+
function throwIfNotInRange(value: i32, lower: i32, upper: i32): void {
195+
if (value < lower || value > upper) throw new RangeError(E_VALUEOUTOFRANGE);
196+
}
197+
198+
const MILLIS_PER_DAY = 1_000 * 60 * 60 * 24;
199+
const MILLIS_PER_HOUR = 1_000 * 60 * 60;
200+
const MILLIS_PER_MINUTE = 1_000 * 60;
201+
const MILLIS_PER_SECOND = 1_000;
202+
203+
// http://howardhinnant.github.io/date_algorithms.html#is_leap
204+
function isLeap(y: i32): bool {
205+
return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0);
206+
}
207+
208+
function daysInMonth(year: i32, month: i32): i32 {
209+
return month == 2
210+
? 28 + i32(isLeap(year))
211+
: 30 + ((month + i32(month >= 8)) & 1);
212+
}
213+
214+
// ymdFromEpochDays returns values via globals to avoid allocations
215+
let year: i32, month: i32, day: i32;
216+
// see: http://howardhinnant.github.io/date_algorithms.html#civil_from_days
217+
function ymdFromEpochDays(z: i32): void {
218+
z += 719468;
219+
const era = (z >= 0 ? z : z - 146096) / 146097;
220+
const doe = z - era * 146097; // [0, 146096]
221+
const yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; // [0, 399]
222+
year = yoe + era * 400;
223+
const doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365]
224+
const mp = (5 * doy + 2) / 153; // [0, 11]
225+
day = doy - (153 * mp + 2) / 5 + 1; // [1, 31]
226+
month = mp + (mp < 10 ? 3 : -9); // [1, 12]
227+
year += (month <= 2 ? 1 : 0);
228+
}
229+
230+
// http://howardhinnant.github.io/date_algorithms.html#days_from_civil
231+
function daysSinceEpoch(y: i32, m: i32, d: i32): i32 {
232+
y -= m <= 2 ? 1 : 0;
233+
const era = (y >= 0 ? y : y - 399) / 400;
234+
const yoe = y - era * 400; // [0, 399]
235+
const doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; // [0, 365]
236+
const doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096]
237+
return era * 146097 + doe - 719468;
38238
}

std/assembly/index.d.ts

+19
Original file line numberDiff line numberDiff line change
@@ -1716,12 +1716,31 @@ declare class Date {
17161716
): i64;
17171717
/** Returns the current UTC timestamp in milliseconds. */
17181718
static now(): i64;
1719+
static fromString(dateStr: string): Date;
17191720
/** Constructs a new date object from an UTC timestamp in milliseconds. */
17201721
constructor(value: i64);
17211722
/** Returns the UTC timestamp of this date in milliseconds. */
17221723
getTime(): i64;
17231724
/** Sets the UTC timestamp of this date in milliseconds. */
17241725
setTime(value: i64): i64;
1726+
1727+
getUTCFullYear(): i32;
1728+
getUTCMonth(): i32;
1729+
getUTCDate(): i32;
1730+
getUTCHours(): i32;
1731+
getUTCMinutes(): i32;
1732+
getUTCSeconds(): i32;
1733+
getUTCMilliseconds(): i32;
1734+
1735+
setUTCFullYear(value: i32): void;
1736+
setUTCMonth(value: i32): void;
1737+
setUTCDate(value: i32): void;
1738+
setUTCHours(value: i32): void;
1739+
setUTCMinutes(value: i32): void;
1740+
setUTCSeconds(value: i32): void;
1741+
setUTCMilliseconds(value: i32): void;
1742+
1743+
toISOString(): string;
17251744
}
17261745

17271746
/** Class for representing a runtime error. Base class of all errors. */

std/assembly/util/error.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
// Common error messages for use accross the standard library. Keeping error messages compact
1+
// Common error messages for use across the standard library. Keeping error messages compact
22
// and reusing them where possible ensures minimal static data in binaries.
33

44
// @ts-ignore: decorator
55
@lazy @inline
66
export const E_INDEXOUTOFRANGE: string = "Index out of range";
77

8+
// @ts-ignore: decorator
9+
@lazy @inline
10+
export const E_VALUEOUTOFRANGE: string = "Value out of range";
11+
812
// @ts-ignore: decorator
913
@lazy @inline
1014
export const E_INVALIDLENGTH: string = "Invalid length";

tests/compiler/std/date.json

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
22
"asc_flags": [
3+
"--explicitStart"
34
]
45
}

0 commit comments

Comments
 (0)