1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
|
// Time utilities; get the current time (both nanos and millis), sleep, time zones.
// Instants: bare i64 UTC Unix-epoch milliseconds. Durations: i64 milliseconds.
// Monotonic readings are a separate clock domain, for deadlines/elapsed time.
val DateTime = DateTime;
val Timers = Timers;
i64 SECOND = 1000;
i64 MINUTE = 60*SECOND;
i64 HOUR = 60*MINUTE;
i64 DAY = 24*HOUR;
i64 NANOS_PER_MS = 1000*1000;
// Gregorian leap days: every fourth year, except centuries not divisible by 400.
i64 _DAYS_PER_4_YEARS = 4*365+1;
i64 _DAYS_PER_CENTURY = 100*365+100/4-1;
i64 _DAYS_PER_ERA = 4*_DAYS_PER_CENTURY+1;
// Complete years before the Unix epoch and after the last supported date.
i64 _DAYS_BEFORE_EPOCH = 1969*365+1969/4-1969/100+1969/400;
i64 _DAYS_BEFORE_YEAR_10000 = 9999*365+9999/4-9999/100+9999/400;
// The March-based calendar algorithm starts at 0000-03-01, 306 days before 0001-01-01.
i64 _MARCH_EPOCH_DAYS = _DAYS_BEFORE_EPOCH+306;
i64 MIN = -_DAYS_BEFORE_EPOCH*DAY; // 0001-01-01T00:00:00.000Z
i64 MAX = (_DAYS_BEFORE_YEAR_10000-_DAYS_BEFORE_EPOCH)*DAY-1; // 9999-12-31T23:59:59.999Z
// Access to the underlying LIBC functions
int ISO=0, CLOCK12=1, CLOCK24=2, ELAPSED=3, DATE=4, CLOCK24_SECONDS=5;
{ i64 } simple_time_now = "C";
{ i64 } simple_time_mono = "C";
{ i64 } simple_time_nano = "C";
{ i64 i32 -> i32 } simple_time_sleep = "C";
{ i64 i64 -> i32 } simple_time_local = "C";
{ i64 i64 u32 -> i32 } simple_time_zone = "C";
val now = { -> simple_time_now (); };
val monotonic = { -> simple_time_mono(); };
val nanoTime = { -> simple_time_nano(); };
val sleep = { i64 duration_ms -> simple_time_sleep(duration_ms,0); };
val sleepUntil = { i64 deadline_ms -> simple_time_sleep(deadline_ms,1); };
// Signed duration conversions. Overflow returns -1; reverse conversions truncate
// toward zero. An exact duration day is 24 hours, independent of DST.
val _scale = { i64 n, i64 unit ->
if( n>sys.integer.MAX_I64/unit || n<sys.integer.MIN_I64/unit ) return -1;
return n*unit;
};
val seconds = { i64 n -> _scale(n,SECOND); };
val minutes = { i64 n -> _scale(n,MINUTE); };
val hours = { i64 n -> _scale(n,HOUR); };
val days = { i64 n -> _scale(n,DAY); };
val toSeconds = { i64 ms -> ms/SECOND; };
val toMinutes = { i64 ms -> ms/MINUTE; };
val toHours = { i64 ms -> ms/HOUR; };
val toDays = { i64 ms -> ms/DAY; };
val fromNanos = { i64 ns -> ns/NANOS_PER_MS; };
val toNanos = { i64 ms -> _scale(ms,NANOS_PER_MS); };
// Clamp to zero once due, and saturate for widely separated i64 readings.
val remaining = { i64 deadline_ms, i64 current_ms ->
if( deadline_ms<=current_ms ) return 0;
if( current_ms<0 && deadline_ms>sys.integer.MAX_I64+current_ms ) return sys.integer.MAX_I64;
return deadline_ms-current_ms;
};
val _mod = { i64 n, i64 unit -> n-n/unit*unit; };
val leap = { int year -> _mod(year,4)==0 && (_mod(year,100)!=0 || _mod(year,400)==0); };
val monthDays = { int year, int month ->
if( month<1 || month>12 ) return 0;
return month==2 ? (leap(year) ? 29 : 28) :
month==4 || month==6 || month==9 || month==11 ? 30 : 31;
};
// Gregorian civil dates, expressed using 400-year eras; no per-year loops.
val _days = { i64 year, i64 month, i64 day ->
year -= month<=2 ? 1 : 0;
i64 era=year/400, y=year-era*400;
i64 mp=month+(month>2 ? -3 : 9);
return era*_DAYS_PER_ERA+y*365+y/4-y/100+(153*mp+2)/5+day-1-_MARCH_EPOCH_DAYS;
};
val valid = { int year, int month, int day, int hour, int minute, int second, int millisecond ->
return year>=1 && year<=9999 && month>=1 && month<=12 && day>=1 && day<=monthDays(year,month) &&
hour>=0 && hour<=23 && minute>=0 && minute<=59 && second>=0 && second<=59 &&
millisecond>=0 && millisecond<=999;
};
val makeUTC = { int year, int month, int day, int hour, int minute, int second, int millisecond ->
if( !valid(year,month,day,hour,minute,second,millisecond) ) return -1;
return _days(year,month,day)*DAY+hour*HOUR+minute*MINUTE+second*SECOND+millisecond;
};
val utc = { i64 instant ->
val d=DateTime.empty();
if( instant<MIN || instant>MAX ) return d;
i64 day=instant/DAY, rest=_mod(instant,DAY);
if( rest<0 ) { rest+=DAY; day--; }
i64 z=day+_MARCH_EPOCH_DAYS, era=z/_DAYS_PER_ERA, doe=z-era*_DAYS_PER_ERA;
i64 y=(doe-doe/(_DAYS_PER_4_YEARS-1)+doe/_DAYS_PER_CENTURY-doe/(_DAYS_PER_ERA-1))/365;
i64 doy=doe-(365*y+y/4-y/100), mp=(5*doy+2)/153;
d.day=doy-(153*mp+2)/5+1;
d.month=mp+(mp<10 ? 3 : -9);
d.year=y+era*400+(d.month<=2 ? 1 : 0);
d.hour=rest/HOUR; d.minute=_mod(rest/MINUTE,60);
d.second=_mod(rest/SECOND,60); d.millisecond=_mod(rest,SECOND);
d.weekday=_mod(day+4,7);
if( d.weekday<0 ) d.weekday+=7;
return d;
};
val local = { i64 instant ->
val d=DateTime.empty();
val f=new i64[10];
i64 ptr=f;
if( simple_time_local(instant,ptr)!=0 ) return d;
d.year=f[0]; d.month=f[1]; d.day=f[2];
d.hour=f[3]; d.minute=f[4]; d.second=f[5]; d.millisecond=f[6];
d.weekday=f[7]; d.offsetSeconds=f[8]; d.daylight=f[9]!=0;
return d;
};
val zoneName = { i64 instant ->
val bytes=new u8[128];
i64 ptr=bytes;
int n=simple_time_zone(instant,ptr,bytes#);
if( n<0 ) return "";
val b=new aryu8();
for( int i=0; i<n; i++ ) b.add(bytes[i]);
return text.end(b);
};
val _digits = { !aryu8 b, i64 n, int width ->
text.integer(b,n,'d'|4096,width,-1);
};
val _format = { i64 instant, int style, bool localZone ->
val d = localZone ? local(instant) : utc(instant);
if( d.year==0 || style<0 || style>5 || style==3 ) return "<invalid time>";
val b = new aryu8();
if( style==4 ) {
_digits(b,d.day,2).add('/'); _digits(b,d.month,2).add('/'); _digits(b,_mod(d.year,100),2);
} else {
if( style==0 ) {
_digits(b,d.year,4).add('-'); _digits(b,d.month,2).add('-'); _digits(b,d.day,2).add('T');
}
int hour=d.hour;
if( style==1 ) { hour=_mod(hour,12); if( hour==0 ) hour=12; }
_digits(b,hour,style==1 ? 1 : 2).add(':'); _digits(b,d.minute,2);
if( style==0 || style==5 ) { b.add(':'); _digits(b,d.second,2); }
if( style==1 ) b.addAry(d.hour<12 ? "am" : "pm");
if( style==0 ) {
b.add('.'); _digits(b,d.millisecond,3);
if( d.offsetSeconds==0 ) b.add('Z');
else {
i64 offset=d.offsetSeconds;
b.add(offset<0 ? '-' : '+');
if( offset<0 ) offset=-offset;
_digits(b,offset/3600,2).add(':'); _digits(b,_mod(offset/60,60),2);
if( _mod(offset,60)!=0 ) { b.add(':'); _digits(b,_mod(offset,60),2); }
}
}
}
return text.end(b);
};
val elapsed = { i64 ms ->
val b=new aryu8();
// Nonpositive arithmetic handles the minimum i64 without negating it.
if( ms<0 ) b.add('-'); else ms=-ms;
_digits(b,-(ms/MINUTE),2).add(':');
_digits(b,-_mod(ms/SECOND,60),2).add('.'); _digits(b,-_mod(ms,SECOND),3);
return text.end(b);
};
val format = { i64 instant, int style ->
if( style==ELAPSED ) return elapsed(instant);
return _format(instant,style,false);
};
val formatLocal = { i64 instant, int style ->
if( style==ELAPSED ) return elapsed(instant);
return _format(instant,style,true);
};
val _number = { u8[~] s, i64 at, int count ->
if( at<0 || count<1 || at+count>s# ) return -1;
int n=0;
for( int i=0; i<count; i++ ) {
int c=s[at+i];
if( c<'0' || c>'9' ) return -1;
n=n*10+c-'0';
}
return n;
};
// Keep suffix validation separate from calendar construction.
val _parseZone = { u8[~] s, int at, i64 instant ->
if( at<0 || at>=s# ) return -1;
i64 offset=0;
if( s[at]=='Z' ) { if( at+1!=s# ) return -1; }
else {
int sign=s[at]=='+' ? 1 : s[at]=='-' ? -1 : 0;
if( !sign || (s#!=at+6 && s#!=at+9) || s[at+3]!=':' ) return -1;
int oh=_number(s,at+1,2), om=_number(s,at+4,2), os=0;
if( s#==at+9 ) { if( s[at+6]!=':' ) return -1; os=_number(s,at+7,2); }
if( oh<0 || oh>23 || om<0 || om>59 || os<0 || os>59 ) return -1;
offset=sign*(oh*HOUR+om*MINUTE+os*SECOND);
}
instant-=offset;
return instant<MIN || instant>MAX ? -1 : instant;
};
// Strict ISO absolute instants only. -1 is reserved for failure (and therefore
// cannot distinguish the valid instant one millisecond before Unix epoch).
val parse = { u8[~] s ->
if( s#<20 || s[4]!='-' || s[7]!='-' || s[10]!='T' || s[13]!=':' || s[16]!=':' ) return -1;
int y=_number(s, 0,4), m =_number(s, 5,2), d =_number(s, 8,2);
int h=_number(s,11,2), min=_number(s,14,2), sec=_number(s,17,2);
bool fraction=s[19]=='.';
int ms=fraction ? _number(s,20,3) : 0;
if( !valid(y,m,d,h,min,sec,ms) ) return -1;
return _parseZone(s,fraction ? 23 : 19,makeUTC(y,m,d,h,min,sec,ms));
};
|