1- import { E_VALUEOUTOFRANGE } from "util/error" ;
1+ import { E_INVALIDDATE } from "util/error" ;
22import { now as Date_now } from "./bindings/Date" ;
33
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+
415export class Date {
16+ private year : i32 = 0 ;
17+ private month : i32 = 0 ;
18+ private day : i32 = 0 ;
19+
520 @inline static UTC (
621 year : i32 ,
722 month : i32 = 0 ,
@@ -11,155 +26,177 @@ export class Date {
1126 second : i32 = 0 ,
1227 millisecond : i32 = 0
1328 ) : 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 ;
1533 }
1634
1735 @inline static now ( ) : i64 {
1836 return < i64 > Date_now ( ) ;
1937 }
2038
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+ }
2743
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 ) {
2955 // 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 ) ;
3259 // 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+
3464 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+ }
4376 }
44- dateString = parts [ 0 ] ;
45- } else {
46- dateString = dateTimeString ;
4777 }
4878 // 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 ) ) ;
5990 }
6091
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 ) ;
6296
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 ;
65100 }
66101
67102 getTime ( ) : i64 {
68103 return this . epochMillis ;
69104 }
70105
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 ;
74115 }
75116
76117 getUTCFullYear ( ) : i32 {
77- ymdFromEpochDays ( i32 ( this . epochMillis / MILLIS_PER_DAY ) ) ;
78- return year ;
118+ return this . year ;
79119 }
80120
81121 getUTCMonth ( ) : i32 {
82- ymdFromEpochDays ( i32 ( this . epochMillis / MILLIS_PER_DAY ) ) ;
83- return month - 1 ;
122+ return this . month - 1 ;
84123 }
85124
86125 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 ) ;
89131 }
90132
91133 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 ;
93135 }
94136
95137 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 ;
97139 }
98140
99141 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 ;
101143 }
102144
103145 getUTCMilliseconds ( ) : i32 {
104- return i32 ( this . epochMillis % MILLIS_PER_SECOND ) ;
146+ return i32 ( euclidRem ( this . epochMillis , MILLIS_PER_SECOND ) ) ;
105147 }
106148
107- setUTCMilliseconds ( value : i32 ) : void {
108- this . epochMillis += value - this . getUTCMilliseconds ( ) ;
149+ setUTCMilliseconds ( millis : i32 ) : void {
150+ this . setTime ( this . epochMillis + ( millis - this . getUTCMilliseconds ( ) ) ) ;
109151 }
110152
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 ) ;
114155 }
115156
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 ) ;
119159 }
120160
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 ) ;
124163 }
125164
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 ) ;
132169 }
133170
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 ) ;
140175 }
141176
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 ) ;
147181 }
148182
149183 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" ) ;
155192 }
156193
157194 return (
158195 yearStr +
159196 "-" +
160- month . toString ( ) . padStart ( 2 , "0" ) +
197+ this . month . toString ( ) . padStart ( 2 , "0" ) +
161198 "-" +
162- day . toString ( ) . padStart ( 2 , "0" ) +
199+ this . day . toString ( ) . padStart ( 2 , "0" ) +
163200 "T" +
164201 this . getUTCHours ( ) . toString ( ) . padStart ( 2 , "0" ) +
165202 ":" +
@@ -191,48 +228,54 @@ function epochMillis(
191228 ) ;
192229}
193230
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 ;
196234}
197235
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 ;
206240}
207241
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 ) ;
212245}
213246
214- // ymdFromEpochDays returns values via globals to avoid allocations
215- let year : i32 , month : i32 , day : i32 ;
216247// see: http://howardhinnant.github.io/date_algorithms.html#civil_from_days
217- function ymdFromEpochDays ( z : i32 ) : void {
248+ function ymdFromEpochDays ( z : i32 ) : i32 {
218249 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 ;
228261}
229262
230263// http://howardhinnant.github.io/date_algorithms.html#days_from_civil
231264function 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]
237270 return era * 146097 + doe - 719468 ;
238271}
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