Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 30f5d13

Browse files
authoredApr 14, 2021
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 0 additions & 11 deletions
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

Lines changed: 212 additions & 12 deletions
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

Lines changed: 19 additions & 0 deletions
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

Lines changed: 5 additions & 1 deletion
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

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

‎tests/compiler/std/date.optimized.wat

Lines changed: 3900 additions & 261 deletions
Large diffs are not rendered by default.

‎tests/compiler/std/date.ts

Lines changed: 184 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,187 @@
1-
assert(Date.UTC(1970, 0, 1) == 0);
2-
assert(Date.UTC(1970, 0, 1, 0, 0, 0, 0) == 0);
1+
// Date UTC /////////////////////////////////////////////////////////////////////////////////
2+
{
3+
assert(Date.UTC(1970, 0, 1) == 0);
4+
assert(Date.UTC(1970, 0, 1, 0, 0, 0, 0) == 0);
35

4-
var creationTime = Date.UTC(2018, 10, 10, 11, 0, 0, 1);
5-
assert(creationTime == 1541847600001);
6+
let creationTime = Date.UTC(2018, 10, 10, 11, 0, 0, 1);
7+
assert(creationTime == 1541847600001);
8+
}
69

7-
assert(Date.now() > creationTime);
10+
// Date get / set Time /////////////////////////////////////////////////////////////////////////////////
811

9-
var date = new Date(creationTime);
10-
assert(date.getTime() == creationTime);
11-
date.setTime(creationTime + 1);
12-
assert(date.getTime() == creationTime + 1);
12+
{
13+
let creationTime = 1541847600001;
14+
let date = new Date(creationTime);
15+
assert(date.getTime() == creationTime);
16+
date.setTime(creationTime + 1);
17+
assert(date.getTime() == creationTime + 1);
18+
}
19+
20+
// Date getters /////////////////////////////////////////////////////////////////////////////////
21+
22+
{
23+
// from +189512-12-14T22:09:43.706Z"
24+
let date = new Date(5918283958183706);
25+
assert(date.getUTCFullYear() == 189512);
26+
assert(date.getUTCMonth() == 11);
27+
assert(date.getUTCDate() == 14);
28+
assert(date.getUTCHours() == 22);
29+
assert(date.getUTCMinutes() == 9);
30+
assert(date.getUTCSeconds() == 43);
31+
assert(date.getUTCMilliseconds() == 706);
32+
}
33+
34+
{
35+
// from 1973-12-04T01:03:11.274Z"
36+
let date = new Date(123814991274);
37+
assert(date.getUTCFullYear() == 1973);
38+
assert(date.getUTCMonth() == 11);
39+
assert(date.getUTCDate() == 4);
40+
assert(date.getUTCHours() == 1);
41+
assert(date.getUTCMinutes() == 3);
42+
assert(date.getUTCSeconds() == 11);
43+
assert(date.getUTCMilliseconds() == 274);
44+
}
45+
46+
// Date#setUTCMilliseconds /////////////////////////////////////////////////////////////////////////////////
47+
{
48+
let date = new Date(399464523963984);
49+
assert(date.getUTCMilliseconds() == 984);
50+
date.setUTCMilliseconds(12);
51+
assert(date.getUTCMilliseconds() == 12);
52+
date.setUTCMilliseconds(568);
53+
assert(date.getUTCMilliseconds() == 568);
54+
// test boundaries
55+
date.setUTCMilliseconds(0);
56+
date.setUTCMilliseconds(999);
57+
}
58+
59+
// Date#setUTCSeconds /////////////////////////////////////////////////////////////////////////////////
60+
{
61+
let date = new Date(372027318331986);
62+
assert(date.getUTCSeconds() == 31);
63+
date.setUTCSeconds(12);
64+
assert(date.getUTCSeconds() == 12);
65+
date.setUTCSeconds(50);
66+
assert(date.getUTCSeconds() == 50);
67+
// test boundaries
68+
date.setUTCSeconds(0);
69+
date.setUTCSeconds(59);
70+
}
71+
72+
// Date#setUTCMinutes /////////////////////////////////////////////////////////////////////////////////
73+
{
74+
let date = new Date(372027318331986);
75+
assert(date.getUTCMinutes() == 45);
76+
date.setUTCMinutes(12);
77+
assert(date.getUTCMinutes() == 12);
78+
date.setUTCMinutes(50);
79+
assert(date.getUTCMinutes() == 50);
80+
// test boundaries
81+
date.setUTCMinutes(0);
82+
date.setUTCMinutes(59);
83+
}
84+
85+
// Date#setUTCHours /////////////////////////////////////////////////////////////////////////////////
86+
{
87+
let date = new Date(372027318331986);
88+
assert(date.getUTCHours() == 17);
89+
date.setUTCHours(12);
90+
assert(date.getUTCHours() == 12);
91+
date.setUTCHours(2);
92+
assert(date.getUTCHours() == 2);
93+
// test boundaries
94+
date.setUTCHours(0);
95+
date.setUTCHours(23);
96+
}
97+
98+
// Date#setUTCDate /////////////////////////////////////////////////////////////////////////////////
99+
{
100+
let date = new Date(123814991274);
101+
assert(date.getUTCFullYear() == 1973);
102+
assert(date.getUTCMonth() == 11);
103+
104+
// test a few values
105+
date.setUTCDate(12);
106+
assert(date.getUTCDate() == 12);
107+
date.setUTCDate(2);
108+
assert(date.getUTCDate() == 2);
109+
110+
// test boundaries
111+
// nov has 30 days
112+
date.setUTCDate(1);
113+
date.setUTCDate(30);
114+
115+
// jan has 31 days
116+
date.setUTCMonth(1);
117+
date.setUTCDate(1);
118+
date.setUTCDate(31);
119+
120+
// feb on leap year
121+
date.setUTCFullYear(2024);
122+
date.setUTCMonth(2);
123+
date.setUTCDate(1);
124+
date.setUTCDate(29);
125+
}
126+
127+
// Date#setUTCMonth /////////////////////////////////////////////////////////////////////////////////
128+
{
129+
let date = new Date(7899943856218720);
130+
assert(date.getUTCMonth() == 3);
131+
date.setUTCMonth(10);
132+
assert(date.getUTCMonth() == 10);
133+
date.setUTCMonth(2);
134+
assert(date.getUTCMonth() == 2);
135+
// test boundaries
136+
date.setUTCMonth(1);
137+
date.setUTCMonth(12);
138+
}
139+
140+
// Date#setUTCFullYear /////////////////////////////////////////////////////////////////////////////////
141+
{
142+
let date = new Date(7941202527925698);
143+
assert(date.getUTCFullYear() == 253616);
144+
date.setUTCFullYear(1976);
145+
assert(date.getUTCFullYear() == 1976);
146+
date.setUTCFullYear(20212);
147+
assert(date.getUTCFullYear() == 20212);
148+
}
149+
150+
// Date#toString /////////////////////////////////////////////////////////////////////////////////
151+
{
152+
let date = new Date(1231231231020);
153+
assert(date.toISOString() == "2009-01-06T08:40:31.020Z");
154+
date = new Date(1231231231456);
155+
assert(date.toISOString() == "2009-01-06T08:40:31.456Z");
156+
date = new Date(322331231231020);
157+
assert(date.toISOString() == "+012184-04-08T13:07:11.020Z");
158+
}
159+
160+
// Date#fromString /////////////////////////////////////////////////////////////////////////////////
161+
{
162+
// supports year / month / day
163+
let date = Date.fromString("1976-02-02");
164+
assert(date.getTime() == 192067200000);
165+
date = Date.fromString("1976-2-2");
166+
assert(date.getTime() == 192067200000);
167+
date = Date.fromString("2345-11-04");
168+
assert(date.getTime() == 11860387200000);
169+
170+
// supports two digit years
171+
assert(
172+
Date.fromString("1976-04-02").getTime() ==
173+
Date.fromString("76-04-02").getTime()
174+
);
175+
176+
// supports year / month / day / hour / minute / second
177+
date = Date.fromString("1976-02-02T12:34:56");
178+
assert(date.getTime() == 192112496000);
179+
180+
// supports milliseconds
181+
date = Date.fromString("1976-02-02T12:34:56.456");
182+
assert(date.getTime() == 192112496456);
183+
184+
// supports 'Z' suffix
185+
date = Date.fromString("1976-02-02T12:34:56.456Z");
186+
assert(date.getTime() == 192112496456);
187+
}

‎tests/compiler/std/date.untouched.wat

Lines changed: 5774 additions & 265 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.