Files
cell/scripts/time.js

225 lines
7.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* time.js exports: {record, number, text} */
var time = this;
/* -------- 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;
/* 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; };
/* 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; };
/* timecode utility */
time.timecode = function(t, fps = 24)
{
var s = Math.trunc(t);
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];
/* -------- 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;
/* 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;
return rec;
}
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 */
}
c = second;
c += minute * time.minute;
c += hour * time.hour;
c += yday * time.day;
c -= (zone + dst) * time.hour;
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");
}
/* 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);
fmt = fmt.replaceAll("dd", rec.day.toString().padStart(2, "0"));
fmt = fmt.replaceAll("d", rec.day);
fmt = fmt.replaceAll("hh", rec.hour.toString().padStart(2, "0"));
fmt = fmt.replaceAll("h", rec.hour);
fmt = fmt.replaceAll("nn", rec.minute.toString().padStart(2, "0"));
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("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", 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[cell.DOC] = {
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)."
};
/* -------- public exports ------------------------------------------------ */
return { record, number, text };