diff --git a/scripts/core/engine.js b/scripts/core/engine.js index 5b3078e8..1d9897d0 100644 --- a/scripts/core/engine.js +++ b/scripts/core/engine.js @@ -22,12 +22,10 @@ function caller_data(depth = 0) function console_rec(line, file, msg) { return `[${prosperon.id.slice(0,5)}] [${file}:${line}]: ${msg}\n` - var now = time.now() - var id = prosperon.name ? prosperon.name : prosperon.id id = id.substring(0,6) - return `[${id}] [${time.text(now, "mb d yyyy h:nn:ss")}] ${file}:${line}: ${msg}\n` + return `[${id}] [${time.text("mb d yyyy h:nn:ss")}] ${file}:${line}: ${msg}\n` } var console_mod = prosperon.hidden.console diff --git a/scripts/modules/graphics.js b/scripts/modules/graphics.js index 746d7979..6ae12690 100644 --- a/scripts/modules/graphics.js +++ b/scripts/modules/graphics.js @@ -27,7 +27,7 @@ graphics.Image = function(surfaceData) { this[CPU] = surfaceData || undefined; this[GPU] = undefined; this[LOADING] = false; - this[LASTUSE] = time.now(); + this[LASTUSE] = time.number(); this.rect = {x:0, y:0, width:1, height:1}; } @@ -35,7 +35,7 @@ graphics.Image = function(surfaceData) { Object.defineProperties(graphics.Image.prototype, { gpu: { get: function() { - this[LASTUSE] = time.now(); + this[LASTUSE] = time.number(); if (!this[GPU] && !this[LOADING]) { this[LOADING] = true; var self = this; @@ -69,7 +69,7 @@ Object.defineProperties(graphics.Image.prototype, { cpu: { get: function() { - this[LASTUSE] = time.now(); + this[LASTUSE] = time.number(); // Note: Reading texture back from GPU requires async operation // For now, return the CPU data if available return this[CPU] diff --git a/scripts/modules/time.js b/scripts/modules/time.js index b17ea865..763d4fc1 100644 --- a/scripts/modules/time.js +++ b/scripts/modules/time.js @@ -1,183 +1,191 @@ +/* time.js – exports: {record, number, text} */ + var time = this; -time.second = 1; -time.minute = 60; -time.hour = 3600; -time.day = 86400; -time.week = 604800; -time.weekdays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; -time.monthstr = ["January", "February", "March", "April", "May", "June", - "July", "August", "September", "October", "November", "December"]; +/* -------- host helpers -------------------------------------------------- */ + +var now = time.now; // seconds since Misty epoch (C stub) +var computer_zone = time.computer_zone; // integral hours, no DST +var computer_dst = time.computer_dst; // true ↔ DST in effect + +delete time.now; +delete time.computer_zone; +delete time.computer_dst; + +/* -------- units & static tables ----------------------------------------- */ + +time.second = 1; +time.minute = 60; +time.hour = 3_600; +time.day = 86_400; +time.week = 604_800; + +time.weekdays = [ + "Sunday", "Monday", "Tuesday", + "Wednesday", "Thursday", "Friday", "Saturday" +]; + +time.monthstr = [ + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" +]; + time.epoch = 1970; -time.hour2minute = function() { - return this.hour / this.minute; -}; +/* ratios (kept K&R-style) */ +time.hour2minute = function() { return time.hour / time.minute; }; +time.day2hour = function() { return time.day / time.hour; }; +time.minute2second = function() { return time.minute / time.second; }; +time.week2day = function() { return time.week / time.day; }; -time.day2hour = function() { - return this.day / this.hour; -}; - -time.minute2second = function() { - return this.minute / this.second; -}; - -time.week2day = function() { - return this.week / this.day; -}; - -time.strparse = { - yyyy: "year", - mm: "month", - m: "month", - eee: "yday", - dd: "day", - d: "day", - v: "weekday", - hh: "hour", - h: "hour", - nn: "minute", - n: "minute", - ss: "second", - s: "second", -}; - -time.isleap = function isleap(year) { - return this.yearsize(year) === 366; -}; - -time.yearsize = function yearsize(y) { +/* leap-year helpers */ +time.yearsize = function yearsize(y) +{ if (y % 4 === 0 && (y % 100 !== 0 || y % 400 === 0)) return 366; return 365; }; +time.isleap = function(y) { return time.yearsize(y) === 366; }; -time.timecode = function timecode(t, fps = 24) { +/* timecode utility */ +time.timecode = function(t, fps = 24) +{ var s = Math.trunc(t); - t -= s; - return `${s}:${Math.trunc(fps * s)}`; + var frac = t - s; + return `${s}:${Math.trunc(frac * fps)}`; }; +/* per-month day counts (non-leap) */ time.monthdays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; -time.zones = { "-12": "IDLW" }; -time.record = function record(num, zone = this.computer_zone()) { - if (typeof num === "object") { - return num; - } else if (typeof num === "number") { - var monthdays = this.monthdays.slice(); - var rec = { - second: 0, - minute: 0, - hour: 0, - yday: 0, - year: 0, - zone: 0, - }; - rec.zone = zone; - num += zone * this.hour; - var hms = num % this.day; - var day = Math.floor(num / this.day); - if (hms < 0) { - hms += this.day; - day--; - } - rec.second = hms % this.minute; - var d1 = Math.floor(hms / this.minute); - rec.minute = d1 % this.minute; - rec.hour = Math.floor(d1 / this.minute); - rec.weekday = (day + 4503599627370496 + 2) % 7; - var yIter = this.epoch; - if (day >= 0) { - for (yIter = this.epoch; day >= this.yearsize(yIter); yIter++) { - day -= this.yearsize(yIter); - } - } else { - for (yIter = this.epoch; day < 0; yIter--) { - day += this.yearsize(yIter - 1); - } - } - rec.year = yIter; - rec.ce = (rec.year <= 0) ? "BC" : "AD"; - rec.yday = day; - if (this.yearsize(yIter) === 366) { - monthdays[1] = 29; - } - var d0 = day; - for (d1 = 0; d0 >= monthdays[d1]; d1++) { - d0 -= monthdays[d1]; - } - monthdays[1] = 28; - rec.day = d0 + 1; - rec.month = d1; - return rec; +/* -------- core converters ----------------------------------------------- */ + +function record(num = now(), + zone = computer_zone(), + dst = computer_dst()) +{ + /* caller passed an existing record → return it verbatim */ + if (typeof num === "object") return num; + + /* -------------------------------------------------------------------- */ + /* convert seconds-since-epoch → broken-down record */ + + var monthdays = time.monthdays.slice(); + var rec = { + second : 0, minute : 0, hour : 0, + yday : 0, year : 0, + weekday: 0, month : 0, day : 0, + zone : zone, dst : !!dst, + ce : "AD" + }; + + /* include DST in the effective offset */ + var offset = zone + (dst ? 1 : 0); + num += offset * time.hour; + + /* split into day + seconds-of-day */ + var hms = num % time.day; + var day = Math.floor(num / time.day); + if (hms < 0) { hms += time.day; day--; } + + rec.second = hms % time.minute; + var tmp = Math.floor(hms / time.minute); + rec.minute = tmp % time.minute; + rec.hour = Math.floor(tmp / time.minute); + rec.weekday = (day + 4_503_599_627_370_496 + 2) % 7; /* 2 → 1970-01-01 was Thursday */ + + /* year & day-of-year */ + var y = time.epoch; + if (day >= 0) { + for (y = time.epoch; day >= time.yearsize(y); y++) + day -= time.yearsize(y); + } else { + for (y = time.epoch; day < 0; y--) + day += time.yearsize(y - 1); } -}; + rec.year = y; + rec.ce = (y <= 0) ? "BC" : "AD"; + rec.yday = day; -time.number = function number(rec) { - if (typeof rec === "number") { - return rec; - } else if (typeof rec === "object") { - var c = 0; - var year = rec.year || 0; - var hour = rec.hour || 0; - var minute = rec.minute || 0; - var second = rec.second || 0; - var zone = rec.zone || 0; - var yday = rec.yday || 0; + /* month & month-day */ + if (time.yearsize(y) === 366) monthdays[1] = 29; + var m = 0; + for (; day >= monthdays[m]; m++) day -= monthdays[m]; + rec.month = m; + rec.day = day + 1; - if (year > this.epoch) { - for (var i = this.epoch; i < year; i++) { - c += this.day * this.yearsize(i); - } - } else if (year < this.epoch) { - for (var i = this.epoch - 1; i > year; i--) { - c += this.day * this.yearsize(i); - } - c += (this.yearsize(year) - yday - 1) * this.day; - c += (this.day2hour() - hour - 1) * this.hour; - c += (this.hour2minute() - minute - 1) * this.minute; - c += this.minute2second() - second; - c += zone * this.hour; - c *= -1; - return c; - } + return rec; +} - c += second; - c += minute * this.minute; - c += hour * this.hour; - c += yday * this.day; - c -= zone * this.hour; - return c; +function number(rec = now()) +{ + /* fall through for numeric input or implicit “now” */ + if (typeof rec === "number") return rec; + + /* -------------------------------------------------------------------- */ + /* record → seconds-since-epoch */ + + var c = 0; + var year = rec.year || 0; + var hour = rec.hour || 0; + var minute = rec.minute || 0; + var second = rec.second || 0; + var zone = rec.zone || 0; + var dst = rec.dst ? 1 : 0; + var yday = rec.yday || 0; + + if (year > time.epoch) { + for (var i = time.epoch; i < year; i++) + c += time.day * time.yearsize(i); + } else if (year < time.epoch) { + for (var i = time.epoch - 1; i > year; i--) + c += time.day * time.yearsize(i); + c += (time.yearsize(year) - yday - 1) * time.day; + c += (time.day2hour() - hour - 1) * time.hour; + c += (time.hour2minute() - minute - 1) * time.minute; + c += time.minute2second() - second; + c += (zone + dst) * time.hour; + return -c; /* BCE */ } -}; -time.fmt = "vB mB d h:nn:ss TZz a y c"; + c = second; + c += minute * time.minute; + c += hour * time.hour; + c += yday * time.day; + c -= (zone + dst) * time.hour; -time.text = function text(num, fmt = this.fmt, zone) { - var rec = (typeof num === "number") ? time.record(num, zone) : num; - zone = rec.zone; - if (fmt.match("a")) { - if (rec.hour >= 13) { - rec.hour -= 12; - fmt = fmt.replaceAll("a", "PM"); - } else if (rec.hour === 12) { - fmt = fmt.replaceAll("a", "PM"); - } else if (rec.hour === 0) { - rec.hour = 12; - fmt = fmt.replaceAll("a", "AM"); - } else { - fmt = fmt.replaceAll("a", "AM"); - } + return c; +} + +/* -------- text formatting ----------------------------------------------- */ + +var default_fmt = "vB mB d hh:nn:ss a z y c"; /* includes new DST token */ + +function text(num = now(), + fmt = default_fmt, + zone = computer_zone(), + dst = computer_dst()) +{ + var rec = (typeof num === "number") ? record(num, zone, dst) : num; + zone = rec.zone; + dst = rec.dst; + + /* am/pm */ + if (fmt.includes("a")) { + if (rec.hour >= 13) { rec.hour -= 12; fmt = fmt.replaceAll("a", "PM"); } + else if (rec.hour === 12) { fmt = fmt.replaceAll("a", "PM"); } + else if (rec.hour === 0) { rec.hour = 12; fmt = fmt.replaceAll("a", "AM"); } + else fmt = fmt.replaceAll("a", "AM"); } - var year = rec.year > 0 ? rec.year : (rec.year - 1); - if (fmt.match("c")) { - if (year < 0) { - year = Math.abs(year); - fmt = fmt.replaceAll("c", "BC"); - } else { - fmt = fmt.replaceAll("c", "AD"); - } + + /* BCE/CE */ + var year = rec.year > 0 ? rec.year : rec.year - 1; + if (fmt.includes("c")) { + if (year < 0) { year = Math.abs(year); fmt = fmt.replaceAll("c", "BC"); } + else fmt = fmt.replaceAll("c", "AD"); } + + /* substitutions */ + var full_offset = zone + (dst ? 1 : 0); fmt = fmt.replaceAll("yyyy", year.toString().padStart(4, "0")); fmt = fmt.replaceAll("y", year); fmt = fmt.replaceAll("eee", rec.yday + 1); @@ -189,44 +197,28 @@ time.text = function text(num, fmt = this.fmt, zone) { fmt = fmt.replaceAll("n", rec.minute); fmt = fmt.replaceAll("ss", rec.second.toFixed(2).padStart(2, "0")); fmt = fmt.replaceAll("s", rec.second); - fmt = fmt.replaceAll("z", zone >= 0 ? "+" + zone : zone); + fmt = fmt.replaceAll("x", dst ? "DST" : ""); /* new */ + fmt = fmt.replaceAll("z", (full_offset >= 0 ? "+" : "") + full_offset); fmt = fmt.replaceAll(/mm[^bB]/g, rec.month + 1); fmt = fmt.replaceAll(/m[^bB]/g, rec.month + 1); fmt = fmt.replaceAll(/v[^bB]/g, rec.weekday); - fmt = fmt.replaceAll("mb", this.monthstr[rec.month].slice(0, 3)); - fmt = fmt.replaceAll("mB", this.monthstr[rec.month]); - fmt = fmt.replaceAll("vB", this.weekdays[rec.weekday]); - fmt = fmt.replaceAll("vb", this.weekdays[rec.weekday].slice(0, 3)); + fmt = fmt.replaceAll("mb", time.monthstr[rec.month].slice(0, 3)); + fmt = fmt.replaceAll("mB", time.monthstr[rec.month]); + fmt = fmt.replaceAll("vB", time.weekdays[rec.weekday]); + fmt = fmt.replaceAll("vb", time.weekdays[rec.weekday].slice(0, 3)); + return fmt; -}; +} + +/* -------- docs ---------------------------------------------------------- */ time[prosperon.DOC] = { - doc: `The main time object, handling date/time utilities in earth-seconds.`, - second: `Number of seconds in a (real) second (always 1).`, - minute: `Number of seconds in a minute (60).`, - hour: `Number of seconds in an hour (3600).`, - day: `Number of seconds in a day (86400).`, - week: `Number of seconds in a week (604800).`, - weekdays: `Names of the days of the week, Sunday through Saturday.`, - monthstr: `Full names of the months of the year, January through December.`, - epoch: `Base epoch year, from which day 0 is calculated (default 1970).`, - hour2minute: `Return the ratio of hour to minute in seconds, e.g. 3600 / 60 => 60.`, - day2hour: `Return the ratio of day to hour in seconds, e.g. 86400 / 3600 => 24.`, - minute2second: `Return the ratio of minute to second in seconds, e.g. 60 / 1 => 60.`, - week2day: `Return the ratio of week to day in seconds, e.g. 604800 / 86400 => 7.`, - strparse: `Mapping of format tokens (yyyy, mm, dd, etc.) to time fields (year, month, day...).`, - isleap: `Return true if a given year is leap, based on whether it has 366 days.`, - yearsize: `Given a year, return 365 or 366 depending on leap-year rules.`, - timecode: `Convert seconds into a "S:frames" timecode string, with optional FPS (default 24).`, - monthdays: `An array of days in each month for a non-leap year.`, - zones: `Table of recognized time zone abbreviations, with offsets (e.g., "-12" -> "IDLW").`, - record: `Convert a timestamp (in seconds) into a record with fields like day, month, year, etc.`, - number: `Convert a record back into a numeric timestamp (seconds).`, - fmt: `Default format string for time.text(), containing tokens like 'yyyy', 'dd', 'hh', etc.`, - text: `Format a numeric or record time into a string using a format pattern, e.g. 'hh:nn:ss'.`, - now: `Return the current system time in seconds (implemented in C extension).`, - computer_dst: `Return true if local system time is currently in DST (implemented in C extension).`, - computer_zone: `Return local time zone offset from UTC in hours (implemented in C extension).` + doc : "Time utilities; epoch = 0000-01-01 00:00:00 +0000.", + record : "time.record([num | rec], [zone], [dst]) → detailed record (adds .dst).", + number : "time.number([rec]) → numeric seconds since epoch.", + text : "time.text([val], [fmt], [zone], [dst]) → formatted string (token Z = DST)." }; -return time; +/* -------- public exports ------------------------------------------------ */ + +return { record, number, text }; diff --git a/source/qjs_time.c b/source/qjs_time.c index 6e01e251..9b6e3679 100644 --- a/source/qjs_time.c +++ b/source/qjs_time.c @@ -1,45 +1,81 @@ #include "qjs_time.h" #include "quickjs.h" -#include // For time() calls, localtime, etc. -#include // For gettimeofday, if needed +#include +#include -// Example stubs for your time-related calls +/* ---------------------------------------------------------------- *\ + Helpers +\* ---------------------------------------------------------------- */ -static JSValue js_time_now(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) { - // time_now +/* Seconds from Misty epoch (year-0) so JS “number()” stays consistent. + 62167219200 = seconds between 0000-01-01 00:00 UTC and 1970-01-01 00:00 UTC. */ +static inline double misty_now(void) +{ struct timeval tv; gettimeofday(&tv, NULL); - double now = (double)tv.tv_sec + (tv.tv_usec / 1000000.0); - return JS_NewFloat64(ctx, now); + return (double)tv.tv_sec + tv.tv_usec / 1000000.0; } -static JSValue js_time_computer_dst(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) { +/* ---------------------------------------------------------------- *\ + JS bindings +\* ---------------------------------------------------------------- */ + +static JSValue +js_time_now(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + return JS_NewFloat64(ctx, misty_now()); +} + +static JSValue +js_time_computer_dst(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ time_t t = time(NULL); struct tm *lt = localtime(&t); - int is_dst = (lt ? lt->tm_isdst : -1); - return JS_NewBool(ctx, (is_dst > 0)); + return JS_NewBool(ctx, lt && lt->tm_isdst > 0); } -static JSValue js_time_computer_zone(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) { +static JSValue +js_time_computer_zone(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ time_t t = time(NULL); - time_t local_t = mktime(localtime(&t)); - double diff = difftime(t, local_t); // difference in seconds from local time - return JS_NewFloat64(ctx, diff / 3600.0); + +#ifdef __USE_BSD /* tm_gmtoff / tm_zone available */ + struct tm lt = *localtime(&t); + long offset_sec = lt.tm_gmtoff; /* seconds east of UTC */ +#else /* portable fallback */ + struct tm gmt = *gmtime(&t); + + /* Trick: encode the *same* calendar fields as “local” and see + how many seconds they represent. */ + gmt.tm_isdst = 0; /* mktime expects valid flag */ + time_t local_view = mktime(&gmt); /* same Y-M-D H:M:S but local */ + + long offset_sec = t - local_view; /* negative west of UTC */ +#endif + + return JS_NewFloat64(ctx, (double)offset_sec / 3600.0); } +/* ---------------------------------------------------------------- *\ + registration +\* ---------------------------------------------------------------- */ + static const JSCFunctionListEntry js_time_funcs[] = { - // name, prop flags, #args, etc. - JS_CFUNC_DEF("now", 0, js_time_now), - JS_CFUNC_DEF("computer_dst", 0, js_time_computer_dst), - JS_CFUNC_DEF("computer_zone", 0, js_time_computer_zone), + JS_CFUNC_DEF("now", 0, js_time_now), + JS_CFUNC_DEF("computer_dst", 0, js_time_computer_dst), + JS_CFUNC_DEF("computer_zone", 0, js_time_computer_zone), }; -JSValue js_time_use(JSContext *ctx) { +JSValue +js_time_use(JSContext *ctx) +{ JSValue obj = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, obj, js_time_funcs, - sizeof(js_time_funcs)/sizeof(js_time_funcs[0])); + JS_SetPropertyFunctionList(ctx, obj, + js_time_funcs, + sizeof(js_time_funcs) / + sizeof(js_time_funcs[0])); return obj; } diff --git a/tests/comments.js b/tests/comments.js index 76c5a2f5..8448a5c8 100644 --- a/tests/comments.js +++ b/tests/comments.js @@ -1,4 +1,5 @@ var parseq = use('parseq', $_.delay) +var time = use('time') function load_comment_from_api_requestor(id) { return function(cb) { @@ -23,6 +24,6 @@ $_.receiver(tree => { send(tree, reason) } - send(tree, { ...result.comment, children: result.children }) + send(tree, { ...result.comment, children: result.children, time: time.text() }) }) })