summaryrefslogtreecommitdiff
path: root/src/main/smp/sys/time.smp
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-08-10 11:11:18 -0700
committerCliff Click <cliffc@acm.org>2026-11-13 16:17:13 -0800
commit236a9b50ffe5c13f33027dbf5fef38f9fc58a8f9 (patch)
tree9fe5d79a2d0c7b12e1317b737857065fff5093a5 /src/main/smp/sys/time.smp
parent2cb35c65bde63f90d7afc3d7a8a985b0c6ea15d7 (diff)
downloadSimple-236a9b50ffe5c13f33027dbf5fef38f9fc58a8f9.tar.gz
Simple-236a9b50ffe5c13f33027dbf5fef38f9fc58a8f9.zip
Chapter 31: Software Drawing, Time, and Graphical Gameslinear
Co-authored-by: Hsiang-Ying Fu <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: fuhsnn <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: Symious <14933944+symious@users.noreply.github.com> Co-authored-by: Jun Kawasaki <jun784@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'src/main/smp/sys/time.smp')
-rw-r--r--src/main/smp/sys/time.smp219
1 files changed, 219 insertions, 0 deletions
diff --git a/src/main/smp/sys/time.smp b/src/main/smp/sys/time.smp
new file mode 100644
index 00000000..1706990e
--- /dev/null
+++ b/src/main/smp/sys/time.smp
@@ -0,0 +1,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));
+};