1
- import { E_VALUEOUTOFRANGE } from "util/error" ;
1
+ import { E_INVALIDDATE } from "util/error" ;
2
2
import { now as Date_now } from "./bindings/Date" ;
3
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
+ // ymdFromEpochDays returns values via globals to avoid allocations
12
+ // @ts -ignore: decorator
13
+ @lazy let _month : i32 , _day : i32 ;
14
+
4
15
export class Date {
16
+ private year : i32 = 0 ;
17
+ private month : i32 = 0 ;
18
+ private day : i32 = 0 ;
19
+
5
20
@inline static UTC (
6
21
year : i32 ,
7
22
month : i32 = 0 ,
@@ -11,155 +26,177 @@ export class Date {
11
26
second : i32 = 0 ,
12
27
millisecond : i32 = 0
13
28
) : i64 {
14
- return epochMillis ( year , month + 1 , day , hour , minute , second , millisecond ) ;
29
+ if ( year >= 0 && year <= 99 ) year += 1900 ;
30
+ var ms = epochMillis ( year , month + 1 , day , hour , minute , second , millisecond ) ;
31
+ if ( invalidDate ( ms ) ) throw new RangeError ( E_INVALIDDATE ) ;
32
+ return ms ;
15
33
}
16
34
17
35
@inline static now ( ) : i64 {
18
36
return < i64 > Date_now ( ) ;
19
37
}
20
38
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 ;
39
+ // It can parse only ISO 8601 inputs like YYYY-MM-DDTHH:MM:SS.000Z
40
+ @inline static parse ( dateString : string ) : Date {
41
+ return this . fromString ( dateString ) ;
42
+ }
27
43
28
- if ( dateTimeString . includes ( "T" ) ) {
44
+ static fromString ( dateTimeString : string ) : Date {
45
+ if ( ! dateTimeString . length ) throw new RangeError ( E_INVALIDDATE ) ;
46
+ var
47
+ hour : i32 = 0 ,
48
+ min : i32 = 0 ,
49
+ sec : i32 = 0 ,
50
+ ms : i32 = 0 ;
51
+
52
+ var dateString = dateTimeString ;
53
+ var posT = dateTimeString . indexOf ( "T" ) ;
54
+ if ( ~ posT ) {
29
55
// includes a time component
30
- const parts = dateTimeString . split ( "T" ) ;
31
- const timeString = parts [ 1 ] ;
56
+ let timeString : string ;
57
+ dateString = dateTimeString . substring ( 0 , posT ) ;
58
+ timeString = dateTimeString . substring ( posT + 1 ) ;
32
59
// parse the HH-MM-SS component
33
- const timeParts = timeString . split ( ":" ) ;
60
+ let timeParts = timeString . split ( ":" ) ;
61
+ let len = timeParts . length ;
62
+ if ( len <= 1 ) throw new RangeError ( E_INVALIDDATE ) ;
63
+
34
64
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 ] ) ;
65
+ min = I32 . parseInt ( timeParts [ 1 ] ) ;
66
+ if ( len >= 3 ) {
67
+ let secAndMs = timeParts [ 2 ] ;
68
+ let posDot = secAndMs . indexOf ( "." ) ;
69
+ if ( ~ posDot ) {
70
+ // includes milliseconds
71
+ sec = I32 . parseInt ( secAndMs . substring ( 0 , posDot ) ) ;
72
+ ms = I32 . parseInt ( secAndMs . substring ( posDot + 1 ) ) ;
73
+ } else {
74
+ sec = I32 . parseInt ( secAndMs ) ;
75
+ }
43
76
}
44
- dateString = parts [ 0 ] ;
45
- } else {
46
- dateString = dateTimeString ;
47
77
}
48
78
// 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 ] ) ;
55
-
56
- return new Date (
57
- epochMillis ( year , month , day , hour , minute , second , millisecond )
58
- ) ;
79
+ var parts = dateString . split ( "-" ) ;
80
+ var year = I32 . parseInt ( parts [ 0 ] ) ;
81
+ var month = 1 , day = 1 ;
82
+ var len = parts . length ;
83
+ if ( len >= 2 ) {
84
+ month = I32 . parseInt ( parts [ 1 ] ) ;
85
+ if ( len >= 3 ) {
86
+ day = I32 . parseInt ( parts [ 2 ] ) ;
87
+ }
88
+ }
89
+ return new Date ( epochMillis ( year , month , day , hour , min , sec , ms ) ) ;
59
90
}
60
91
61
- private epochMillis : i64 ;
92
+ constructor ( private epochMillis : i64 ) {
93
+ // this differs from JavaScript which prefer return NaN or "Invalid Date" string
94
+ // instead throwing exception.
95
+ if ( invalidDate ( epochMillis ) ) throw new RangeError ( E_INVALIDDATE ) ;
62
96
63
- constructor ( epochMillis : i64 ) {
64
- this . epochMillis = epochMillis ;
97
+ this . year = ymdFromEpochDays ( i32 ( floorDiv ( epochMillis , MILLIS_PER_DAY ) ) ) ;
98
+ this . month = _month ;
99
+ this . day = _day ;
65
100
}
66
101
67
102
getTime ( ) : i64 {
68
103
return this . epochMillis ;
69
104
}
70
105
71
- setTime ( value : i64 ) : i64 {
72
- this . epochMillis = value ;
73
- return value ;
106
+ setTime ( time : i64 ) : i64 {
107
+ if ( invalidDate ( time ) ) throw new RangeError ( E_INVALIDDATE ) ;
108
+
109
+ this . epochMillis = time ;
110
+ this . year = ymdFromEpochDays ( i32 ( floorDiv ( time , MILLIS_PER_DAY ) ) ) ;
111
+ this . month = _month ;
112
+ this . day = _day ;
113
+
114
+ return time ;
74
115
}
75
116
76
117
getUTCFullYear ( ) : i32 {
77
- ymdFromEpochDays ( i32 ( this . epochMillis / MILLIS_PER_DAY ) ) ;
78
- return year ;
118
+ return this . year ;
79
119
}
80
120
81
121
getUTCMonth ( ) : i32 {
82
- ymdFromEpochDays ( i32 ( this . epochMillis / MILLIS_PER_DAY ) ) ;
83
- return month - 1 ;
122
+ return this . month - 1 ;
84
123
}
85
124
86
125
getUTCDate ( ) : i32 {
87
- ymdFromEpochDays ( i32 ( this . epochMillis / MILLIS_PER_DAY ) ) ;
88
- return day ;
126
+ return this . day ;
127
+ }
128
+
129
+ getUTCDay ( ) : i32 {
130
+ return dayOfWeek ( this . year , this . month , this . day ) ;
89
131
}
90
132
91
133
getUTCHours ( ) : i32 {
92
- return i32 ( this . epochMillis % MILLIS_PER_DAY ) / MILLIS_PER_HOUR ;
134
+ return i32 ( euclidRem ( this . epochMillis , MILLIS_PER_DAY ) ) / MILLIS_PER_HOUR ;
93
135
}
94
136
95
137
getUTCMinutes ( ) : i32 {
96
- return i32 ( this . epochMillis % MILLIS_PER_HOUR ) / MILLIS_PER_MINUTE ;
138
+ return i32 ( euclidRem ( this . epochMillis , MILLIS_PER_HOUR ) ) / MILLIS_PER_MINUTE ;
97
139
}
98
140
99
141
getUTCSeconds ( ) : i32 {
100
- return i32 ( this . epochMillis % MILLIS_PER_MINUTE ) / MILLIS_PER_SECOND ;
142
+ return i32 ( euclidRem ( this . epochMillis , MILLIS_PER_MINUTE ) ) / MILLIS_PER_SECOND ;
101
143
}
102
144
103
145
getUTCMilliseconds ( ) : i32 {
104
- return i32 ( this . epochMillis % MILLIS_PER_SECOND ) ;
146
+ return i32 ( euclidRem ( this . epochMillis , MILLIS_PER_SECOND ) ) ;
105
147
}
106
148
107
- setUTCMilliseconds ( value : i32 ) : void {
108
- this . epochMillis += value - this . getUTCMilliseconds ( ) ;
149
+ setUTCMilliseconds ( millis : i32 ) : void {
150
+ this . setTime ( this . epochMillis + ( millis - this . getUTCMilliseconds ( ) ) ) ;
109
151
}
110
152
111
- setUTCSeconds ( value : i32 ) : void {
112
- throwIfNotInRange ( value , 0 , 59 ) ;
113
- this . epochMillis += ( value - this . getUTCSeconds ( ) ) * MILLIS_PER_SECOND ;
153
+ setUTCSeconds ( seconds : i32 ) : void {
154
+ this . setTime ( this . epochMillis + ( seconds - this . getUTCSeconds ( ) ) * MILLIS_PER_SECOND ) ;
114
155
}
115
156
116
- setUTCMinutes ( value : i32 ) : void {
117
- throwIfNotInRange ( value , 0 , 59 ) ;
118
- this . epochMillis += ( value - this . getUTCMinutes ( ) ) * MILLIS_PER_MINUTE ;
157
+ setUTCMinutes ( minutes : i32 ) : void {
158
+ this . setTime ( this . epochMillis + ( minutes - this . getUTCMinutes ( ) ) * MILLIS_PER_MINUTE ) ;
119
159
}
120
160
121
- setUTCHours ( value : i32 ) : void {
122
- throwIfNotInRange ( value , 0 , 23 ) ;
123
- this . epochMillis += ( value - this . getUTCHours ( ) ) * MILLIS_PER_HOUR ;
161
+ setUTCHours ( hours : i32 ) : void {
162
+ this . setTime ( this . epochMillis + ( hours - this . getUTCHours ( ) ) * MILLIS_PER_HOUR ) ;
124
163
}
125
164
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 ;
165
+ setUTCDate ( day : i32 ) : void {
166
+ if ( this . day == day ) return ;
167
+ var ms = euclidRem ( this . epochMillis , MILLIS_PER_DAY ) ;
168
+ this . setTime ( i64 ( daysSinceEpoch ( this . year , this . month , day ) ) * MILLIS_PER_DAY + ms ) ;
132
169
}
133
170
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 ;
171
+ setUTCMonth ( month : i32 ) : void {
172
+ if ( this . month == month ) return ;
173
+ var ms = euclidRem ( this . epochMillis , MILLIS_PER_DAY ) ;
174
+ this . setTime ( i64 ( daysSinceEpoch ( this . year , month + 1 , this . day ) ) * MILLIS_PER_DAY + ms ) ;
140
175
}
141
176
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 ;
177
+ setUTCFullYear ( year : i32 ) : void {
178
+ if ( this . year == year ) return ;
179
+ var ms = euclidRem ( this . epochMillis , MILLIS_PER_DAY ) ;
180
+ this . setTime ( i64 ( daysSinceEpoch ( year , this . month , this . day ) ) * MILLIS_PER_DAY + ms ) ;
147
181
}
148
182
149
183
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" ) ;
184
+ // TODO: add more low-level helper which combine toString and padStart without extra allocation
185
+ var yearStr : string ;
186
+ var year = this . year ;
187
+ var isNeg = year < 0 ;
188
+ if ( isNeg || year >= 10000 ) {
189
+ yearStr = ( isNeg ? "-" : "+" ) + abs ( year ) . toString ( ) . padStart ( 6 , "0" ) ;
190
+ } else {
191
+ yearStr = year . toString ( ) . padStart ( 4 , "0" ) ;
155
192
}
156
193
157
194
return (
158
195
yearStr +
159
196
"-" +
160
- month . toString ( ) . padStart ( 2 , "0" ) +
197
+ this . month . toString ( ) . padStart ( 2 , "0" ) +
161
198
"-" +
162
- day . toString ( ) . padStart ( 2 , "0" ) +
199
+ this . day . toString ( ) . padStart ( 2 , "0" ) +
163
200
"T" +
164
201
this . getUTCHours ( ) . toString ( ) . padStart ( 2 , "0" ) +
165
202
":" +
@@ -191,48 +228,54 @@ function epochMillis(
191
228
) ;
192
229
}
193
230
194
- function throwIfNotInRange ( value : i32 , lower : i32 , upper : i32 ) : void {
195
- if ( value < lower || value > upper ) throw new RangeError ( E_VALUEOUTOFRANGE ) ;
231
+ // @ts -ignore: decorator
232
+ @inline function floorDiv < T extends number > ( a : T , b : T ) : T {
233
+ return ( a >= 0 ? a : a - b + 1 ) / b as T ;
196
234
}
197
235
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 ) ;
236
+ // @ts -ignore: decorator
237
+ @inline function euclidRem < T extends number > ( a : T , b : T ) : T {
238
+ var m = a % b ;
239
+ return m + ( m < 0 ? b : 0 ) as T ;
206
240
}
207
241
208
- function daysInMonth ( year : i32 , month : i32 ) : i32 {
209
- return month == 2
210
- ? 28 + i32 ( isLeap ( year ) )
211
- : 30 + ( ( month + i32 ( month >= 8 ) ) & 1 ) ;
242
+ function invalidDate ( millis : i64 ) : bool {
243
+ // @ts -ignore
244
+ return ( millis < - 8640000000000000 ) | ( millis > 8640000000000000 ) ;
212
245
}
213
246
214
- // ymdFromEpochDays returns values via globals to avoid allocations
215
- let year : i32 , month : i32 , day : i32 ;
216
247
// see: http://howardhinnant.github.io/date_algorithms.html#civil_from_days
217
- function ymdFromEpochDays ( z : i32 ) : void {
248
+ function ymdFromEpochDays ( z : i32 ) : i32 {
218
249
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 ) ;
250
+ var era = < u32 > floorDiv ( z , 146097 ) ;
251
+ var doe = < u32 > z - era * 146097 ; // [0, 146096]
252
+ var yoe = ( doe - doe / 1460 + doe / 36524 - doe / 146096 ) / 365 ; // [0, 399]
253
+ var year = yoe + era * 400 ;
254
+ var doy = doe - ( 365 * yoe + yoe / 4 - yoe / 100 ) ; // [0, 365]
255
+ var mo = ( 5 * doy + 2 ) / 153 ; // [0, 11]
256
+ _day = doy - ( 153 * mo + 2 ) / 5 + 1 ; // [1, 31]
257
+ mo += mo < 10 ? 3 : - 9 ; // [1, 12]
258
+ _month = mo ;
259
+ year += u32 ( mo <= 2 ) ;
260
+ return year ;
228
261
}
229
262
230
263
// http://howardhinnant.github.io/date_algorithms.html#days_from_civil
231
264
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]
265
+ y -= i32 ( m <= 2 ) ;
266
+ var era = < u32 > floorDiv ( y , 400 ) ;
267
+ var yoe = < u32 > y - era * 400 ; // [0, 399]
268
+ var doy = < u32 > ( 153 * ( m + ( m > 2 ? - 3 : 9 ) ) + 2 ) / 5 + d - 1 ; // [0, 365]
269
+ var doe = yoe * 365 + yoe / 4 - yoe / 100 + doy ; // [0, 146096]
237
270
return era * 146097 + doe - 719468 ;
238
271
}
272
+
273
+ // TomohikoSakamoto algorithm from https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week
274
+ function dayOfWeek ( year : i32 , month : i32 , day : i32 ) : i32 {
275
+ const tab = memory . data < u8 > ( [ 0 , 3 , 2 , 5 , 0 , 3 , 5 , 1 , 4 , 6 , 2 , 4 ] ) ;
276
+
277
+ year -= i32 ( month < 3 ) ;
278
+ year += year / 4 - year / 100 + year / 400 ;
279
+ month = < i32 > load < u8 > ( tab + month - 1 ) ;
280
+ return euclidRem ( year + month + day , 7 ) ;
281
+ }
0 commit comments