From 360aae04727620ab611e90d2ddf6b5e8c39df93c Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 1 Jun 2026 10:33:41 -0600 Subject: [PATCH 01/18] Remove dangerous eval function. --- centrallix-os/sys/js/ht_render.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/centrallix-os/sys/js/ht_render.js b/centrallix-os/sys/js/ht_render.js index 12c5b7faf..410dd13f6 100644 --- a/centrallix-os/sys/js/ht_render.js +++ b/centrallix-os/sys/js/ht_render.js @@ -310,14 +310,6 @@ function cxjs_right(s,l) if (s == null || l == null) return null; return s.substr(s.length-l); } -function cxjs_eval(x) - { - var _this = null; - var _context = null; - if (x == null) return null; - if (typeof x == 'object') x = x.toString(); - return eval(x); - } function cxjs_isnull(v,d) { if (v == null) From efb7cc7b289ed98ce1f0f2c83fc406aa3ee362ec Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 1 Jun 2026 10:33:49 -0600 Subject: [PATCH 02/18] Update copyright notice. --- centrallix-os/sys/js/ht_render.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix-os/sys/js/ht_render.js b/centrallix-os/sys/js/ht_render.js index 410dd13f6..b51a3490e 100644 --- a/centrallix-os/sys/js/ht_render.js +++ b/centrallix-os/sys/js/ht_render.js @@ -1,4 +1,4 @@ -// Copyright (C) 1998-2015 LightSys Technology Services, Inc. +// Copyright (C) 1998-2026 LightSys Technology Services, Inc. // // You may use these files and this library under the terms of the // GNU Lesser General Public License, Version 2.1, contained in the From 40bc78f98482102ca69a0435f8622365d07b8946 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 1 Jun 2026 12:25:27 -0600 Subject: [PATCH 03/18] Add Claude Code's initial implementation of the following client-side functions: wordify, abs, replicate, datepart, datediff, dateformat, round, truncate, constrain, rand, sqrt, square, degrees, radians, power, moneyformat. --- centrallix-os/sys/js/ht_render.js | 351 ++++++++++++++++++++++++++++++ 1 file changed, 351 insertions(+) diff --git a/centrallix-os/sys/js/ht_render.js b/centrallix-os/sys/js/ht_render.js index b51a3490e..d373bc46f 100644 --- a/centrallix-os/sys/js/ht_render.js +++ b/centrallix-os/sys/js/ht_render.js @@ -509,6 +509,357 @@ function cxjs_replace(str, srch, rep) rep); } +// Convert integer n to English words; result has a trailing space. +function cxjs_wordify(n) + { + if (n == null) return null; + + // Word tables + var digits = ["Zero","One","Two","Three","Four","Five","Six","Seven","Eight","Nine","Ten"]; + var teens = ["Eleven","Twelve","Thirteen","Fourteen","Fifteen","Sixteen","Seventeen","Eighteen","Nineteen"]; + var tens_w = ["Ten","Twenty","Thirty","Forty","Fifty","Sixty","Seventy","Eighty","Ninety"]; + var mults = ["","Thousand","Million","Billion","Trillion","Quadrillion"]; + + // Normalize; handle sign + n = Math.round(n); + if (isNaN(n)) return null; + var abs_n = Math.abs(n); + var result = n < 0 ? "Negative " : ""; + + // Walk thousands-groups high to low + if (abs_n === 0) + { + result += "Zero "; + } + else + { + for (var mult = 5; mult >= 0; mult--) + { + var chunk = Math.floor(abs_n / Math.pow(1000, mult)) % 1000; + if (chunk === 0) continue; + if (chunk >= 100) + result += digits[Math.floor(chunk/100)] + " Hundred "; + var rem = chunk % 100; + if (rem > 19) + { + result += tens_w[Math.floor(rem/10)-1]; + if (rem%10 !== 0) result += "-" + digits[rem%10] + " "; + else result += " "; + } + else if (rem > 10) result += teens[rem-11] + " "; + else if (rem > 0) result += digits[rem] + " "; + if (chunk !== 0) result += mults[mult]; + if (chunk !== 0 && chunk > 10 && (chunk%10 !== 0 || chunk > 100) && mult !== 0) + result += ", "; + else if (mult !== 0 && chunk !== 0) + result += " "; + } + } + return result; + } + +// Repeat string s n times (capped at 255); returns null if n < 0. +function cxjs_replicate(s, n) + { + if (s == null || n == null) return null; + s = String(s); + n = Math.floor(n); + if (n < 0) return null; + if (n > 255) n = 255; + var result = ""; + for (var i = 0; i < n; i++) result += s; + return result; + } + +// Internal: parse M/D/YYYY H:MM:SS date string into a Date; returns null on failure. +function cxjs__parsedate(s) + { + if (s == null) return null; + var m = String(s).match(/^(\d+)\/(\d+)\/(\d+)(?:\s+(\d+):(\d+)(?::(\d+))?)?/); + if (!m) return null; + return new Date(parseInt(m[3],10), parseInt(m[1],10)-1, parseInt(m[2],10), + m[4]?parseInt(m[4],10):0, m[5]?parseInt(m[5],10):0, m[6]?parseInt(m[6],10):0, 0); + } + +// Extract year/month/day/hour/minute/second/weekday from a date string; returns int or null. +function cxjs_datepart(part, datestr) + { + if (part == null || datestr == null) return null; + var d = cxjs__parsedate(datestr); + if (!d) return null; + switch(String(part).toLowerCase()) + { + case "year": return d.getFullYear(); + case "month": return d.getMonth() + 1; + case "day": return d.getDate(); + case "hour": return d.getHours(); + case "minute": return d.getMinutes(); + case "second": return d.getSeconds(); + case "weekday": return d.getDay() + 1; + } + return null; + } +function cxjs_datediff(part, d1, d2) + { + if (part == null || d1 == null || d2 == null) return null; + var dt1 = cxjs__parsedate(d1); + var dt2 = cxjs__parsedate(d2); + if (!dt1 || !dt2) return null; + var sign = 1; + if (dt2 < dt1) { sign = -1; var tmp = dt2; dt2 = dt1; dt1 = tmp; } + part = String(part).toLowerCase(); + if (part == "year") + return sign * (dt2.getFullYear() - dt1.getFullYear()); + if (part == "month") + return sign * ((dt2.getFullYear()-dt1.getFullYear())*12 + dt2.getMonth()-dt1.getMonth()); + var m1 = new Date(dt1.getFullYear(), dt1.getMonth(), dt1.getDate()); + var m2 = new Date(dt2.getFullYear(), dt2.getMonth(), dt2.getDate()); + var days = Math.round((m2 - m1) / 86400000); + if (part == "day") return sign * days; + var hours = days*24 + dt2.getHours() - dt1.getHours(); + if (part == "hour") return sign * hours; + var minutes = hours*60 + dt2.getMinutes() - dt1.getMinutes(); + if (part == "minute") return sign * minutes; + var seconds = minutes*60 + dt2.getSeconds() - dt1.getSeconds(); + if (part == "second") return sign * seconds; + return null; + } +function cxjs_dateformat(datestr, fmt) + { + if (datestr == null || fmt == null) return null; + var d = cxjs__parsedate(datestr); + if (!d) return null; + var sm = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]; + var lm = ["January","February","March","April","May","June","July","August","September","October","November","December"]; + var result = ""; + var append_ampm = 0; + var c, day, mo, hr, sfx, end; + var i = 0; + while (i < fmt.length) + { + c = fmt[i]; + if (c == 'D') + { i++; } + else if (c == 'd') + { + day = d.getDate(); + if (fmt[i+1]=='d' && fmt[i+2]=='d') + { + sfx = (day==1||day==21||day==31)?"st":(day==2||day==22)?"nd":(day==3||day==23)?"rd":"th"; + result += day + sfx; + i += 3; + } + else if (fmt[i+1]=='d') + { result += (day<10?"0":"")+day; i += 2; } + else + { result += day; i++; } + } + else if (c == 'M') + { + mo = d.getMonth(); + if (fmt[i+1]=='M' && fmt[i+2]=='M' && fmt[i+3]=='M') + { result += lm[mo]; i += 4; } + else if (fmt[i+1]=='M' && fmt[i+2]=='M') + { result += sm[mo]; i += 3; } + else if (fmt[i+1]=='M') + { result += (mo+1<10?"0":"")+(mo+1); i += 2; } + else + { result += mo+1; i++; } + } + else if (c == 'y') + { + if (fmt[i+1]=='y' && fmt[i+2]=='y' && fmt[i+3]=='y') + { result += ("000"+d.getFullYear()).slice(-4); i += 4; } + else if (fmt[i+1]=='y') + { result += ("0"+(d.getFullYear()%100)).slice(-2); i += 2; } + else { i++; } + } + else if (c == 'H') + { + if (fmt[i+1]=='H') + { result += (d.getHours()<10?"0":"")+d.getHours(); i++; } + i++; + append_ampm = 0; + } + else if (c == 'h') + { + if (fmt[i+1]=='h') + { hr = d.getHours()%12||12; result += (hr<10?"0":"")+hr; i++; } + i++; + append_ampm = 1; + if (i >= fmt.length || fmt[i]==' ' || fmt[i]==',') + { result += d.getHours()>=12?"PM":"AM"; append_ampm = 0; } + } + else if (c == 'm') + { + if (fmt[i+1]=='m') + { result += (d.getMinutes()<10?"0":"")+d.getMinutes(); i++; } + i++; + if (append_ampm && (i >= fmt.length || fmt[i]==' ' || fmt[i]==',')) + { result += d.getHours()>=12?"PM":"AM"; append_ampm = 0; } + } + else if (c == 's') + { + if (fmt[i+1]=='s') + { result += (d.getSeconds()<10?"0":"")+d.getSeconds(); i++; } + i++; + if (append_ampm && (i >= fmt.length || fmt[i]==' ' || fmt[i]==',')) + { result += d.getHours()>=12?"PM":"AM"; append_ampm = 0; } + } + else if (c == 'I') + { if (fmt[i+1]=='I') i++; i++; } + else if (c=='L' && i+2= 0) ? end+1 : i+1; + } + else + { result += c; i++; } + } + return result; + } +function cxjs_abs(v) + { + if (v == null) return null; + return Math.abs(v); + } +function cxjs_round(v, dec) + { + if (v == null) return null; + if (dec == null) dec = 0; + dec = Math.round(dec); + var factor = Math.pow(10, dec); + var scaled = v * factor; + return (v > 0 ? Math.floor(scaled+0.5) : Math.ceil(scaled-0.5)) / factor; + } +function cxjs_truncate(v, dec) + { + if (v == null) return null; + if (dec == null) dec = 0; + dec = Math.round(dec); + var factor = Math.pow(10, dec); + var scaled = v * factor; + return (v > 0 ? Math.floor(scaled+0.000001) : Math.ceil(scaled-0.000001)) / factor; + } +function cxjs_constrain(v, mn, mx) + { + if (v == null) return null; + if (mn != null && v < mn) return mn; + if (mx != null && v > mx) return mx; + return v; + } +function cxjs_rand(seed) + { + return Math.random(); + } +function cxjs_sqrt(v) + { + if (v == null) return null; + var r = Math.sqrt(v); + return isNaN(r) ? null : r; + } +function cxjs_square(v) + { + if (v == null) return null; + return v * v; + } +function cxjs_power(n, p) + { + if (n == null || p == null) return null; + return Math.pow(n, p); + } +function cxjs_degrees(v) + { + if (v == null) return null; + return v * 180.0 / Math.PI; + } +function cxjs_radians(v) + { + if (v == null) return null; + return v * Math.PI / 180.0; + } +function cxjs_moneyformat(v, fmt) + { + if (v == null || fmt == null) return null; + var num = parseFloat(v); + if (isNaN(num)) return null; + var is_neg = num < 0; + var abs_v = Math.abs(num); + var pw = Math.floor(abs_v); + var pf = Math.round((abs_v - pw) * 10000); + if (pf >= 10000) { pw++; pf = 0; } + var decimal_char = '.'; + var comma_char = ','; + var zero_type = 0; + var zero_strings = [null, '-0-', '0', '']; + var automatic_sign = 1; + var tm = 1; + var pc, nc, pc2, c; + for (var pi = 0; pi < fmt.length && fmt[pi] != '.'; pi++) + { + pc = fmt[pi]; + if (pc=='0'||pc=='#'||pc=='^'||pc==' '||pc=='*') tm *= 10; + else if (pc=='I') { decimal_char = ','; comma_char = '.'; } + else if (pc=='Z') zero_type = 1; + else if (pc=='z') zero_type = 2; + else if (pc=='B') zero_type = 3; + } + if (tm > 1) tm /= 10; + if (/[+\-\(\)\[\]]/.test(fmt)) automatic_sign = 0; + if (pw === 0 && pf === 0 && zero_type !== 0) return zero_strings[zero_type]; + var result = ''; + var suppressing_zeros = 1; + var in_decimal_part = 0; + var d; + var i = 0; + while (i < fmt.length) + { + c = fmt[i]; + if (automatic_sign) { automatic_sign = 0; if (is_neg) result += '-'; } + switch(c) + { + case '$': result += '$'; break; + case ' ': case '*': case '0': case '^': case '#': + if (!tm) break; + if (in_decimal_part) + { d = Math.floor(pf/tm)%10; tm = Math.floor(tm/10); } + else + { d = Math.floor(pw/tm); pw -= d*tm; tm = Math.floor(tm/10); } + if (d !== 0 || c=='0' || c=='^') suppressing_zeros = 0; + if (suppressing_zeros) { if (c==' '||c=='*') result += ' '; } + else result += d; + break; + case ',': + if (!suppressing_zeros) result += comma_char; + else if (!((i>0 && fmt[i-1]=='#') || (i+1 Date: Mon, 1 Jun 2026 12:50:49 -0600 Subject: [PATCH 04/18] Review wordify, replicate, and datepart. --- centrallix-os/sys/js/ht_render.js | 110 ++++++++++++++++++------------ 1 file changed, 66 insertions(+), 44 deletions(-) diff --git a/centrallix-os/sys/js/ht_render.js b/centrallix-os/sys/js/ht_render.js index d373bc46f..5ceb06fb9 100644 --- a/centrallix-os/sys/js/ht_render.js +++ b/centrallix-os/sys/js/ht_render.js @@ -509,65 +509,74 @@ function cxjs_replace(str, srch, rep) rep); } -// Convert integer n to English words; result has a trailing space. + +// Convert integer number (n) to English words, with a trailing space. function cxjs_wordify(n) { if (n == null) return null; - - // Word tables - var digits = ["Zero","One","Two","Three","Four","Five","Six","Seven","Eight","Nine","Ten"]; - var teens = ["Eleven","Twelve","Thirteen","Fourteen","Fifteen","Sixteen","Seventeen","Eighteen","Nineteen"]; - var tens_w = ["Ten","Twenty","Thirty","Forty","Fifty","Sixty","Seventy","Eighty","Ninety"]; - var mults = ["","Thousand","Million","Billion","Trillion","Quadrillion"]; - - // Normalize; handle sign + + // Declare word tables. + const digits = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"]; + const teens = ["Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"]; + const tens = ["Ten", "Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety"]; + const multiples = ["", "Thousand", "Million", "Billion", "Trillion", "Quadrillion"]; + + // Handle negative values and normalize n. n = Math.round(n); if (isNaN(n)) return null; - var abs_n = Math.abs(n); - var result = n < 0 ? "Negative " : ""; - - // Walk thousands-groups high to low + const abs_n = Math.abs(n); + let result = (n < 0) ? "Negative " : ""; + + // Handle zero. if (abs_n === 0) { result += "Zero "; + return result; } - else + + // Traverse thousands-chunks from highest to lowest. + for (let multiple = 5; multiple >= 0; multiple--) { - for (var mult = 5; mult >= 0; mult--) + const chunk = Math.floor(abs_n / Math.pow(1000, multiple)) % 1000; + + if (chunk === 0) continue; + if (chunk >= 100) + result += digits[Math.floor(chunk/100)] + " Hundred "; + + const rem = chunk % 100; + if (rem > 19) { - var chunk = Math.floor(abs_n / Math.pow(1000, mult)) % 1000; - if (chunk === 0) continue; - if (chunk >= 100) - result += digits[Math.floor(chunk/100)] + " Hundred "; - var rem = chunk % 100; - if (rem > 19) - { - result += tens_w[Math.floor(rem/10)-1]; - if (rem%10 !== 0) result += "-" + digits[rem%10] + " "; - else result += " "; - } - else if (rem > 10) result += teens[rem-11] + " "; - else if (rem > 0) result += digits[rem] + " "; - if (chunk !== 0) result += mults[mult]; - if (chunk !== 0 && chunk > 10 && (chunk%10 !== 0 || chunk > 100) && mult !== 0) - result += ", "; - else if (mult !== 0 && chunk !== 0) - result += " "; + result += tens[Math.floor(rem/10)-1]; + if (rem%10 !== 0) result += "-" + digits[rem%10] + " "; + else result += " "; } + else if (rem > 10) result += teens[rem-11] + " "; + else if (rem > 0) result += digits[rem] + " "; + + result += multiples[multiple]; + if (chunk > 10 && (chunk%10 !== 0 || chunk > 100) && multiple !== 0) + result += ", "; + else if (multiple !== 0 && chunk !== 0) + result += " "; } return result; } -// Repeat string s n times (capped at 255); returns null if n < 0. +// Repeat string s n times (up to 255); returns null if n < 0. function cxjs_replicate(s, n) { if (s == null || n == null) return null; - s = String(s); - n = Math.floor(n); - if (n < 0) return null; - if (n > 255) n = 255; - var result = ""; - for (var i = 0; i < n; i++) result += s; + + // Process inputs. + const str = String(s); + let num = Math.floor(n); + if (num < 0) return null; + if (num > 255) num = 255; + + // Replicate string. + let result = ""; + for (let i = 0; i < num; i++) result += str; + return result; } @@ -575,19 +584,30 @@ function cxjs_replicate(s, n) function cxjs__parsedate(s) { if (s == null) return null; - var m = String(s).match(/^(\d+)\/(\d+)\/(\d+)(?:\s+(\d+):(\d+)(?::(\d+))?)?/); + + const m = String(s).match(/^(\d+)\/(\d+)\/(\d+)(?:\s+(\d+):(\d+)(?::(\d+))?)?/); if (!m) return null; - return new Date(parseInt(m[3],10), parseInt(m[1],10)-1, parseInt(m[2],10), - m[4]?parseInt(m[4],10):0, m[5]?parseInt(m[5],10):0, m[6]?parseInt(m[6],10):0, 0); + + return new Date( + parseInt(m[3], 10), + parseInt(m[1], 10) - 1, + parseInt(m[2], 10), + (m[4]) ? parseInt(m[4], 10) : 0, + (m[5]) ? parseInt(m[5], 10) : 0, + (m[6]) ? parseInt(m[6], 10) : 0, + 0 + ); } // Extract year/month/day/hour/minute/second/weekday from a date string; returns int or null. function cxjs_datepart(part, datestr) { if (part == null || datestr == null) return null; + var d = cxjs__parsedate(datestr); if (!d) return null; - switch(String(part).toLowerCase()) + + switch (String(part).toLowerCase()) { case "year": return d.getFullYear(); case "month": return d.getMonth() + 1; @@ -597,8 +617,10 @@ function cxjs_datepart(part, datestr) case "second": return d.getSeconds(); case "weekday": return d.getDay() + 1; } + return null; } + function cxjs_datediff(part, d1, d2) { if (part == null || d1 == null || d2 == null) return null; From 00f34c87ea571dbeb57353e116d8acd239371555 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 1 Jun 2026 12:58:39 -0600 Subject: [PATCH 05/18] Add more docs from Claude that it forgot to add before for some reason. --- centrallix-os/sys/js/ht_render.js | 48 +++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/centrallix-os/sys/js/ht_render.js b/centrallix-os/sys/js/ht_render.js index 5ceb06fb9..0c226adbe 100644 --- a/centrallix-os/sys/js/ht_render.js +++ b/centrallix-os/sys/js/ht_render.js @@ -621,19 +621,27 @@ function cxjs_datepart(part, datestr) return null; } +// Signed difference (d2-d1) in part units; part: "year","month","day","hour","minute","second"; returns int or null. function cxjs_datediff(part, d1, d2) { + // Validate and parse. if (part == null || d1 == null || d2 == null) return null; var dt1 = cxjs__parsedate(d1); var dt2 = cxjs__parsedate(d2); if (!dt1 || !dt2) return null; + + // Normalize order; get part name. var sign = 1; if (dt2 < dt1) { sign = -1; var tmp = dt2; dt2 = dt1; dt1 = tmp; } part = String(part).toLowerCase(); + + // Year and month parts. if (part == "year") return sign * (dt2.getFullYear() - dt1.getFullYear()); if (part == "month") return sign * ((dt2.getFullYear()-dt1.getFullYear())*12 + dt2.getMonth()-dt1.getMonth()); + + // Day, hour, minute, second parts. var m1 = new Date(dt1.getFullYear(), dt1.getMonth(), dt1.getDate()); var m2 = new Date(dt2.getFullYear(), dt2.getMonth(), dt2.getDate()); var days = Math.round((m2 - m1) / 86400000); @@ -646,17 +654,24 @@ function cxjs_datediff(part, d1, d2) if (part == "second") return sign * seconds; return null; } + +// Format date string datestr using fmt (see centrallix-sysdoc format chars); returns null on invalid input. function cxjs_dateformat(datestr, fmt) { + // Validate and parse. if (datestr == null || fmt == null) return null; var d = cxjs__parsedate(datestr); if (!d) return null; + + // Month name tables and state vars. var sm = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]; var lm = ["January","February","March","April","May","June","July","August","September","October","November","December"]; var result = ""; var append_ampm = 0; var c, day, mo, hr, sfx, end; var i = 0; + + // Scan format string. while (i < fmt.length) { c = fmt[i]; @@ -741,29 +756,43 @@ function cxjs_dateformat(datestr, fmt) } return result; } + +// Absolute value of v; returns null if v is null. function cxjs_abs(v) { if (v == null) return null; return Math.abs(v); } + +// Round v to dec decimal places (default 0), toward nearest; returns null if v is null. function cxjs_round(v, dec) { + // Validate; normalize dec. if (v == null) return null; if (dec == null) dec = 0; dec = Math.round(dec); + + // Scale, round, unscale. var factor = Math.pow(10, dec); var scaled = v * factor; return (v > 0 ? Math.floor(scaled+0.5) : Math.ceil(scaled-0.5)) / factor; } + +// Truncate v toward zero to dec decimal places (default 0); returns null if v is null. function cxjs_truncate(v, dec) { + // Validate; normalize dec. if (v == null) return null; if (dec == null) dec = 0; dec = Math.round(dec); + + // Scale, truncate, unscale. var factor = Math.pow(10, dec); var scaled = v * factor; return (v > 0 ? Math.floor(scaled+0.000001) : Math.ceil(scaled-0.000001)) / factor; } + +// Clamp v to [mn, mx]; either bound may be null (unbounded); returns null if v is null. function cxjs_constrain(v, mn, mx) { if (v == null) return null; @@ -771,38 +800,53 @@ function cxjs_constrain(v, mn, mx) if (mx != null && v > mx) return mx; return v; } + +// Return a random float in [0,1); seed is accepted but ignored. function cxjs_rand(seed) { return Math.random(); } + +// Square root of v; returns null if v is null or negative. function cxjs_sqrt(v) { if (v == null) return null; var r = Math.sqrt(v); return isNaN(r) ? null : r; } + +// v squared; returns null if v is null. function cxjs_square(v) { if (v == null) return null; return v * v; } + +// n raised to power p; returns null if either is null. function cxjs_power(n, p) { if (n == null || p == null) return null; return Math.pow(n, p); } + +// Convert radians to degrees; returns null if v is null. function cxjs_degrees(v) { if (v == null) return null; return v * 180.0 / Math.PI; } + +// Convert degrees to radians; returns null if v is null. function cxjs_radians(v) { if (v == null) return null; return v * Math.PI / 180.0; } + +// Format number v as money using fmt format string (see centrallix-sysdoc format chars); returns null on invalid input. function cxjs_moneyformat(v, fmt) { + // Validate and parse number. if (v == null || fmt == null) return null; var num = parseFloat(v); if (isNaN(num)) return null; @@ -811,6 +855,8 @@ function cxjs_moneyformat(v, fmt) var pw = Math.floor(abs_v); var pf = Math.round((abs_v - pw) * 10000); if (pf >= 10000) { pw++; pf = 0; } + + // Init state; scan pre-decimal format for width and sign options. var decimal_char = '.'; var comma_char = ','; var zero_type = 0; @@ -830,6 +876,8 @@ function cxjs_moneyformat(v, fmt) if (tm > 1) tm /= 10; if (/[+\-\(\)\[\]]/.test(fmt)) automatic_sign = 0; if (pw === 0 && pf === 0 && zero_type !== 0) return zero_strings[zero_type]; + + // Format result string. var result = ''; var suppressing_zeros = 1; var in_decimal_part = 0; From b7a9cf4c99bf36f8020bfdbd088cfaf0f2a4bda4 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 1 Jun 2026 13:54:31 -0600 Subject: [PATCH 06/18] Review datediff and dateformat. --- centrallix-os/sys/js/ht_render.js | 267 ++++++++++++++++++------------ 1 file changed, 165 insertions(+), 102 deletions(-) diff --git a/centrallix-os/sys/js/ht_render.js b/centrallix-os/sys/js/ht_render.js index 0c226adbe..adbbb63df 100644 --- a/centrallix-os/sys/js/ht_render.js +++ b/centrallix-os/sys/js/ht_render.js @@ -604,7 +604,7 @@ function cxjs_datepart(part, datestr) { if (part == null || datestr == null) return null; - var d = cxjs__parsedate(datestr); + const d = cxjs__parsedate(datestr); if (!d) return null; switch (String(part).toLowerCase()) @@ -621,37 +621,46 @@ function cxjs_datepart(part, datestr) return null; } -// Signed difference (d2-d1) in part units; part: "year","month","day","hour","minute","second"; returns int or null. +// Calculate the signed difference (d2-d1) between dates and return the +// requested datetime part (e.g. "year", "month", "day", "hour", "minute", +// "second"), or null on failure. function cxjs_datediff(part, d1, d2) { - // Validate and parse. + // Validate and parse dates. if (part == null || d1 == null || d2 == null) return null; - var dt1 = cxjs__parsedate(d1); - var dt2 = cxjs__parsedate(d2); + let dt1 = cxjs__parsedate(d1); + let dt2 = cxjs__parsedate(d2); if (!dt1 || !dt2) return null; // Normalize order; get part name. - var sign = 1; - if (dt2 < dt1) { sign = -1; var tmp = dt2; dt2 = dt1; dt1 = tmp; } + let sign = 1; + if (dt2 < dt1) + { + sign = -1; + const tmp = dt2; + dt2 = dt1; + dt1 = tmp; + } part = String(part).toLowerCase(); // Year and month parts. - if (part == "year") + if (part === "year") return sign * (dt2.getFullYear() - dt1.getFullYear()); - if (part == "month") + if (part === "month") return sign * ((dt2.getFullYear()-dt1.getFullYear())*12 + dt2.getMonth()-dt1.getMonth()); // Day, hour, minute, second parts. - var m1 = new Date(dt1.getFullYear(), dt1.getMonth(), dt1.getDate()); - var m2 = new Date(dt2.getFullYear(), dt2.getMonth(), dt2.getDate()); - var days = Math.round((m2 - m1) / 86400000); - if (part == "day") return sign * days; - var hours = days*24 + dt2.getHours() - dt1.getHours(); - if (part == "hour") return sign * hours; - var minutes = hours*60 + dt2.getMinutes() - dt1.getMinutes(); - if (part == "minute") return sign * minutes; - var seconds = minutes*60 + dt2.getSeconds() - dt1.getSeconds(); - if (part == "second") return sign * seconds; + const m1 = new Date(dt1.getFullYear(), dt1.getMonth(), dt1.getDate()); + const m2 = new Date(dt2.getFullYear(), dt2.getMonth(), dt2.getDate()); + const days = Math.round((m2 - m1) / 86400000); + if (part === "day") return sign * days; + const hours = days*24 + dt2.getHours() - dt1.getHours(); + if (part === "hour") return sign * hours; + const minutes = hours*60 + dt2.getMinutes() - dt1.getMinutes(); + if (part === "minute") return sign * minutes; + const seconds = minutes*60 + dt2.getSeconds() - dt1.getSeconds(); + if (part === "second") return sign * seconds; + return null; } @@ -660,100 +669,154 @@ function cxjs_dateformat(datestr, fmt) { // Validate and parse. if (datestr == null || fmt == null) return null; - var d = cxjs__parsedate(datestr); + const d = cxjs__parsedate(datestr); if (!d) return null; - // Month name tables and state vars. - var sm = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]; - var lm = ["January","February","March","April","May","June","July","August","September","October","November","December"]; - var result = ""; - var append_ampm = 0; - var c, day, mo, hr, sfx, end; - var i = 0; + // Month name tables. + const month_abbrevs = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + const month_names = ["", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; + // Declare state variables. + let result = ""; + let i = 0; + const append = (str, n) => + { + result += str; + i += n; + } + + // Handle AM & PM. + let append_am_pm = 0; + const check_append_am_pm = () => + { + if (!append_am_pm) return; + if (i >= fmt.length || fmt[i]===' ' || fmt[i]===',') + { + result += (d.getHours() >= 12) ? "PM" : "AM"; + append_am_pm = 0; + } + } + // Scan format string. while (i < fmt.length) { - c = fmt[i]; - if (c == 'D') - { i++; } - else if (c == 'd') + const c = fmt[i]; + switch (c) { - day = d.getDate(); - if (fmt[i+1]=='d' && fmt[i+2]=='d') + case 'D': i++; break; + case 'd': { - sfx = (day==1||day==21||day==31)?"st":(day==2||day==22)?"nd":(day==3||day==23)?"rd":"th"; - result += day + sfx; - i += 3; + const day = d.getDate(); + if (fmt[i+1] === 'd' && fmt[i+2] === 'd') + { + const suffix = + (day === 1 || day === 21 || day === 31) ? "st" : + (day === 2 || day === 22) ? "nd" : + (day === 3 || day === 23) ? "rd" : + "th"; + append(day + suffix, 3); + } + else if (fmt[i+1] === 'd') + { + append(((day < 10) ? "0" : "") + day, 2); + } + else + { + append(day, 1); + } + break; + } + case 'M': + { + const month = d.getMonth() + 1; + + // MMMM + if (fmt[i+1] === 'M' && fmt[i+2] === 'M' && fmt[i+3] === 'M') + append(month_names[month], 4); + // MMM + else if (fmt[i+1] === 'M' && fmt[i+2] === 'M') + append(month_abbrevs[month], 3); + // MM + else if (fmt[i+1] === 'M') + append(((month < 10) ? "0" : "") + (month), 2); + // M + else + append(month, 1); + + break; + } + case 'y': + { + if (fmt[i+1] === 'y' && fmt[i+2] === 'y' && fmt[i+3] === 'y') + append(("000" + d.getFullYear()).slice(-4), 4); + else if (fmt[i+1] === 'y') + append(("0" + (d.getFullYear()%100)).slice(-2), 2); + else i++; + break; + } + case 'H': + { + if (fmt[i+1] === 'H') + append(((d.getHours() < 10) ? "0" : "") + d.getHours(), 1); + i++; + append_am_pm = 0; + break; + } + case 'h': + { + if (fmt[i+1] === 'h') + { + const hr = d.getHours() % 12 || 12; + append(((hr < 10) ? "0" : "") + hr, 1); + } + i++; + append_am_pm = 1; + check_append_am_pm(); + break; + } + case 'm': + { + if (fmt[i+1] === 'm') + append(((d.getMinutes() < 10) ? "0" : "") + d.getMinutes(), 1); + i++; + check_append_am_pm(); + break; + } + case 's': + { + if (fmt[i+1] === 's') + append(((d.getSeconds() < 10) ? "0" : "") + d.getSeconds(), 1); + i++; + check_append_am_pm(); + break; + } + case 'I': + { + if (fmt[i+1] === 'I') + i++; + i++; + break; + } + case 'L': if ( + i + 2 < fmt.length && ( + fmt[i+1] === 'm' || + fmt[i+1] === 'M' || + fmt[i+1] === 'w' || + fmt[i+1] === 'W' + ) && fmt[i+2] === '[') + { + const end = fmt.indexOf(']', i); + i = (end >= 0) ? end+1 : i+1; + break; + } // Fallthrough + default: + { + append(c, 1); + break; } - else if (fmt[i+1]=='d') - { result += (day<10?"0":"")+day; i += 2; } - else - { result += day; i++; } - } - else if (c == 'M') - { - mo = d.getMonth(); - if (fmt[i+1]=='M' && fmt[i+2]=='M' && fmt[i+3]=='M') - { result += lm[mo]; i += 4; } - else if (fmt[i+1]=='M' && fmt[i+2]=='M') - { result += sm[mo]; i += 3; } - else if (fmt[i+1]=='M') - { result += (mo+1<10?"0":"")+(mo+1); i += 2; } - else - { result += mo+1; i++; } - } - else if (c == 'y') - { - if (fmt[i+1]=='y' && fmt[i+2]=='y' && fmt[i+3]=='y') - { result += ("000"+d.getFullYear()).slice(-4); i += 4; } - else if (fmt[i+1]=='y') - { result += ("0"+(d.getFullYear()%100)).slice(-2); i += 2; } - else { i++; } - } - else if (c == 'H') - { - if (fmt[i+1]=='H') - { result += (d.getHours()<10?"0":"")+d.getHours(); i++; } - i++; - append_ampm = 0; - } - else if (c == 'h') - { - if (fmt[i+1]=='h') - { hr = d.getHours()%12||12; result += (hr<10?"0":"")+hr; i++; } - i++; - append_ampm = 1; - if (i >= fmt.length || fmt[i]==' ' || fmt[i]==',') - { result += d.getHours()>=12?"PM":"AM"; append_ampm = 0; } - } - else if (c == 'm') - { - if (fmt[i+1]=='m') - { result += (d.getMinutes()<10?"0":"")+d.getMinutes(); i++; } - i++; - if (append_ampm && (i >= fmt.length || fmt[i]==' ' || fmt[i]==',')) - { result += d.getHours()>=12?"PM":"AM"; append_ampm = 0; } - } - else if (c == 's') - { - if (fmt[i+1]=='s') - { result += (d.getSeconds()<10?"0":"")+d.getSeconds(); i++; } - i++; - if (append_ampm && (i >= fmt.length || fmt[i]==' ' || fmt[i]==',')) - { result += d.getHours()>=12?"PM":"AM"; append_ampm = 0; } - } - else if (c == 'I') - { if (fmt[i+1]=='I') i++; i++; } - else if (c=='L' && i+2= 0) ? end+1 : i+1; } - else - { result += c; i++; } } + return result; } From cbe849bae70b17ee69eda6f1b91a732311459696 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 1 Jun 2026 13:57:05 -0600 Subject: [PATCH 07/18] Improve some table formatting. --- centrallix-os/sys/js/ht_render.js | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/centrallix-os/sys/js/ht_render.js b/centrallix-os/sys/js/ht_render.js index adbbb63df..6e764862a 100644 --- a/centrallix-os/sys/js/ht_render.js +++ b/centrallix-os/sys/js/ht_render.js @@ -516,10 +516,21 @@ function cxjs_wordify(n) if (n == null) return null; // Declare word tables. - const digits = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"]; - const teens = ["Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"]; - const tens = ["Ten", "Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety"]; - const multiples = ["", "Thousand", "Million", "Billion", "Trillion", "Quadrillion"]; + const digits = [ + "Zero", "One", "Two", "Three", "Four", "Five", + "Six", "Seven", "Eight", "Nine", "Ten" + ]; + const teens = [ + "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", + "Sixteen", "Seventeen", "Eighteen", "Nineteen" + ]; + const tens = [ + "Ten", "Twenty", "Thirty", "Forty", "Fifty", + "Sixty", "Seventy", "Eighty", "Ninety" + ]; + const multiples = ["", + "Thousand", "Million", "Billion", "Trillion", "Quadrillion" + ]; // Handle negative values and normalize n. n = Math.round(n); @@ -673,8 +684,16 @@ function cxjs_dateformat(datestr, fmt) if (!d) return null; // Month name tables. - const month_abbrevs = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; - const month_names = ["", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; + const month_abbrevs = ["", + "Jan", "Feb", "Mar", "Apr", + "May", "Jun", "Jul", "Aug", + "Sep", "Oct", "Nov", "Dec" + ]; + const month_names = ["", + "January", "February", "March", "April", + "May", "June", "July", "August", + "September", "October", "November", "December" + ]; // Declare state variables. let result = ""; From 878f1b2a0de68c64ac1727e565e28dee0fd9ed8c Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 1 Jun 2026 15:18:15 -0600 Subject: [PATCH 08/18] Fix bad variable naming. --- centrallix-os/sys/js/ht_render.js | 126 +++++++++++++++--------------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/centrallix-os/sys/js/ht_render.js b/centrallix-os/sys/js/ht_render.js index 6e764862a..f5ccaea55 100644 --- a/centrallix-os/sys/js/ht_render.js +++ b/centrallix-os/sys/js/ht_render.js @@ -592,55 +592,55 @@ function cxjs_replicate(s, n) } // Internal: parse M/D/YYYY H:MM:SS date string into a Date; returns null on failure. -function cxjs__parsedate(s) +function cxjs__parsedate(date_str) { - if (s == null) return null; + if (date_str == null) return null; - const m = String(s).match(/^(\d+)\/(\d+)\/(\d+)(?:\s+(\d+):(\d+)(?::(\d+))?)?/); - if (!m) return null; + const match = String(date_str).match(/^(\d+)\/(\d+)\/(\d+)(?:\s+(\d+):(\d+)(?::(\d+))?)?/); + if (!match) return null; return new Date( - parseInt(m[3], 10), - parseInt(m[1], 10) - 1, - parseInt(m[2], 10), - (m[4]) ? parseInt(m[4], 10) : 0, - (m[5]) ? parseInt(m[5], 10) : 0, - (m[6]) ? parseInt(m[6], 10) : 0, + parseInt(match[3], 10), + parseInt(match[1], 10) - 1, + parseInt(match[2], 10), + (match[4]) ? parseInt(match[4], 10) : 0, + (match[5]) ? parseInt(match[5], 10) : 0, + (match[6]) ? parseInt(match[6], 10) : 0, 0 ); } -// Extract year/month/day/hour/minute/second/weekday from a date string; returns int or null. -function cxjs_datepart(part, datestr) +// Extract year/month/day/hour/minute/second/weekday from date_str; returns int or null. +function cxjs_datepart(part, date_str) { - if (part == null || datestr == null) return null; + if (part == null || date_str == null) return null; - const d = cxjs__parsedate(datestr); - if (!d) return null; + const datetime = cxjs__parsedate(date_str); + if (!datetime) return null; switch (String(part).toLowerCase()) { - case "year": return d.getFullYear(); - case "month": return d.getMonth() + 1; - case "day": return d.getDate(); - case "hour": return d.getHours(); - case "minute": return d.getMinutes(); - case "second": return d.getSeconds(); - case "weekday": return d.getDay() + 1; + case "year": return datetime.getFullYear(); + case "month": return datetime.getMonth() + 1; + case "day": return datetime.getDate(); + case "hour": return datetime.getHours(); + case "minute": return datetime.getMinutes(); + case "second": return datetime.getSeconds(); + case "weekday": return datetime.getDay() + 1; } return null; } -// Calculate the signed difference (d2-d1) between dates and return the +// Calculate the signed difference (date2-date1) between dates and return the // requested datetime part (e.g. "year", "month", "day", "hour", "minute", // "second"), or null on failure. -function cxjs_datediff(part, d1, d2) +function cxjs_datediff(part, date1, date2) { // Validate and parse dates. - if (part == null || d1 == null || d2 == null) return null; - let dt1 = cxjs__parsedate(d1); - let dt2 = cxjs__parsedate(d2); + if (part == null || date1 == null || date2 == null) return null; + let dt1 = cxjs__parsedate(date1); + let dt2 = cxjs__parsedate(date2); if (!dt1 || !dt2) return null; // Normalize order; get part name. @@ -658,7 +658,7 @@ function cxjs_datediff(part, d1, d2) if (part === "year") return sign * (dt2.getFullYear() - dt1.getFullYear()); if (part === "month") - return sign * ((dt2.getFullYear()-dt1.getFullYear())*12 + dt2.getMonth()-dt1.getMonth()); + return sign * ((dt2.getFullYear() - dt1.getFullYear())*12 + dt2.getMonth()-dt1.getMonth()); // Day, hour, minute, second parts. const m1 = new Date(dt1.getFullYear(), dt1.getMonth(), dt1.getDate()); @@ -675,12 +675,12 @@ function cxjs_datediff(part, d1, d2) return null; } -// Format date string datestr using fmt (see centrallix-sysdoc format chars); returns null on invalid input. -function cxjs_dateformat(datestr, fmt) +// Format date string date_str using format (see centrallix-sysdoc format chars); returns null on invalid input. +function cxjs_dateformat(date_str, format) { // Validate and parse. - if (datestr == null || fmt == null) return null; - const d = cxjs__parsedate(datestr); + if (date_str == null || format == null) return null; + const d = cxjs__parsedate(date_str); if (!d) return null; // Month name tables. @@ -709,7 +709,7 @@ function cxjs_dateformat(datestr, fmt) const check_append_am_pm = () => { if (!append_am_pm) return; - if (i >= fmt.length || fmt[i]===' ' || fmt[i]===',') + if (i >= format.length || format[i]===' ' || format[i]===',') { result += (d.getHours() >= 12) ? "PM" : "AM"; append_am_pm = 0; @@ -717,16 +717,16 @@ function cxjs_dateformat(datestr, fmt) } // Scan format string. - while (i < fmt.length) + while (i < format.length) { - const c = fmt[i]; + const c = format[i]; switch (c) { case 'D': i++; break; case 'd': { const day = d.getDate(); - if (fmt[i+1] === 'd' && fmt[i+2] === 'd') + if (format[i+1] === 'd' && format[i+2] === 'd') { const suffix = (day === 1 || day === 21 || day === 31) ? "st" : @@ -735,7 +735,7 @@ function cxjs_dateformat(datestr, fmt) "th"; append(day + suffix, 3); } - else if (fmt[i+1] === 'd') + else if (format[i+1] === 'd') { append(((day < 10) ? "0" : "") + day, 2); } @@ -750,13 +750,13 @@ function cxjs_dateformat(datestr, fmt) const month = d.getMonth() + 1; // MMMM - if (fmt[i+1] === 'M' && fmt[i+2] === 'M' && fmt[i+3] === 'M') + if (format[i+1] === 'M' && format[i+2] === 'M' && format[i+3] === 'M') append(month_names[month], 4); // MMM - else if (fmt[i+1] === 'M' && fmt[i+2] === 'M') + else if (format[i+1] === 'M' && format[i+2] === 'M') append(month_abbrevs[month], 3); // MM - else if (fmt[i+1] === 'M') + else if (format[i+1] === 'M') append(((month < 10) ? "0" : "") + (month), 2); // M else @@ -766,16 +766,16 @@ function cxjs_dateformat(datestr, fmt) } case 'y': { - if (fmt[i+1] === 'y' && fmt[i+2] === 'y' && fmt[i+3] === 'y') + if (format[i+1] === 'y' && format[i+2] === 'y' && format[i+3] === 'y') append(("000" + d.getFullYear()).slice(-4), 4); - else if (fmt[i+1] === 'y') + else if (format[i+1] === 'y') append(("0" + (d.getFullYear()%100)).slice(-2), 2); else i++; break; } case 'H': { - if (fmt[i+1] === 'H') + if (format[i+1] === 'H') append(((d.getHours() < 10) ? "0" : "") + d.getHours(), 1); i++; append_am_pm = 0; @@ -783,7 +783,7 @@ function cxjs_dateformat(datestr, fmt) } case 'h': { - if (fmt[i+1] === 'h') + if (format[i+1] === 'h') { const hr = d.getHours() % 12 || 12; append(((hr < 10) ? "0" : "") + hr, 1); @@ -795,7 +795,7 @@ function cxjs_dateformat(datestr, fmt) } case 'm': { - if (fmt[i+1] === 'm') + if (format[i+1] === 'm') append(((d.getMinutes() < 10) ? "0" : "") + d.getMinutes(), 1); i++; check_append_am_pm(); @@ -803,7 +803,7 @@ function cxjs_dateformat(datestr, fmt) } case 's': { - if (fmt[i+1] === 's') + if (format[i+1] === 's') append(((d.getSeconds() < 10) ? "0" : "") + d.getSeconds(), 1); i++; check_append_am_pm(); @@ -811,20 +811,20 @@ function cxjs_dateformat(datestr, fmt) } case 'I': { - if (fmt[i+1] === 'I') + if (format[i+1] === 'I') i++; i++; break; } case 'L': if ( - i + 2 < fmt.length && ( - fmt[i+1] === 'm' || - fmt[i+1] === 'M' || - fmt[i+1] === 'w' || - fmt[i+1] === 'W' - ) && fmt[i+2] === '[') + i + 2 < format.length && ( + format[i+1] === 'm' || + format[i+1] === 'M' || + format[i+1] === 'w' || + format[i+1] === 'W' + ) && format[i+2] === '[') { - const end = fmt.indexOf(']', i); + const end = format.indexOf(']', i); i = (end >= 0) ? end+1 : i+1; break; } // Fallthrough @@ -839,25 +839,25 @@ function cxjs_dateformat(datestr, fmt) return result; } -// Absolute value of v; returns null if v is null. -function cxjs_abs(v) +// Absolute value of n; returns null if n is null. +function cxjs_abs(n) { - if (v == null) return null; - return Math.abs(v); + if (n == null) return null; + return Math.abs(n); } -// Round v to dec decimal places (default 0), toward nearest; returns null if v is null. -function cxjs_round(v, dec) +// Round n to dec decimal places (default 0), toward nearest; returns null if n is null. +function cxjs_round(n, dec) { // Validate; normalize dec. - if (v == null) return null; + if (n == null) return null; if (dec == null) dec = 0; dec = Math.round(dec); // Scale, round, unscale. var factor = Math.pow(10, dec); - var scaled = v * factor; - return (v > 0 ? Math.floor(scaled+0.5) : Math.ceil(scaled-0.5)) / factor; + var scaled = n * factor; + return (n > 0 ? Math.floor(scaled+0.5) : Math.ceil(scaled-0.5)) / factor; } // Truncate v toward zero to dec decimal places (default 0); returns null if v is null. From 46842df466a9f345983ece71e6c75aa02dc61493 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 1 Jun 2026 15:43:34 -0600 Subject: [PATCH 09/18] Review abs, round, truncate, constrain, rand, sqrt, square, power, degrees, and radians. --- centrallix-os/sys/js/ht_render.js | 95 +++++++++++++++++-------------- 1 file changed, 51 insertions(+), 44 deletions(-) diff --git a/centrallix-os/sys/js/ht_render.js b/centrallix-os/sys/js/ht_render.js index f5ccaea55..1e86284a6 100644 --- a/centrallix-os/sys/js/ht_render.js +++ b/centrallix-os/sys/js/ht_render.js @@ -578,7 +578,7 @@ function cxjs_replicate(s, n) { if (s == null || n == null) return null; - // Process inputs. + // Process parameters. const str = String(s); let num = Math.floor(n); if (num < 0) return null; @@ -637,7 +637,7 @@ function cxjs_datepart(part, date_str) // "second"), or null on failure. function cxjs_datediff(part, date1, date2) { - // Validate and parse dates. + // Validate and parse parameters. if (part == null || date1 == null || date2 == null) return null; let dt1 = cxjs__parsedate(date1); let dt2 = cxjs__parsedate(date2); @@ -678,7 +678,7 @@ function cxjs_datediff(part, date1, date2) // Format date string date_str using format (see centrallix-sysdoc format chars); returns null on invalid input. function cxjs_dateformat(date_str, format) { - // Validate and parse. + // Validate and parse parameters. if (date_str == null || format == null) return null; const d = cxjs__parsedate(date_str); if (!d) return null; @@ -839,90 +839,97 @@ function cxjs_dateformat(date_str, format) return result; } -// Absolute value of n; returns null if n is null. +// Absolute value of n. Returns null if n is null. function cxjs_abs(n) { if (n == null) return null; return Math.abs(n); } -// Round n to dec decimal places (default 0), toward nearest; returns null if n is null. +// Round n to dec decimal places (default 0), toward nearest. +// Returns null if n is null. function cxjs_round(n, dec) { - // Validate; normalize dec. + // Validate parameters. if (n == null) return null; if (dec == null) dec = 0; - dec = Math.round(dec); - + dec = Math.round(dec); // Normalize. + // Scale, round, unscale. - var factor = Math.pow(10, dec); - var scaled = n * factor; - return (n > 0 ? Math.floor(scaled+0.5) : Math.ceil(scaled-0.5)) / factor; + const factor = Math.pow(10, dec); + const scaled = n * factor; + const rounded = ((n > 0) ? Math.floor(scaled+0.5) : Math.ceil(scaled-0.5)); + return rounded / factor; } -// Truncate v toward zero to dec decimal places (default 0); returns null if v is null. -function cxjs_truncate(v, dec) +// Truncate n toward zero to dec decimal places (default 0). +// Returns null if n is null. +function cxjs_truncate(n, dec) { - // Validate; normalize dec. - if (v == null) return null; + // Validate parameters. + if (n == null) return null; if (dec == null) dec = 0; - dec = Math.round(dec); + dec = Math.round(dec); // Normalize. // Scale, truncate, unscale. - var factor = Math.pow(10, dec); - var scaled = v * factor; - return (v > 0 ? Math.floor(scaled+0.000001) : Math.ceil(scaled-0.000001)) / factor; + const factor = Math.pow(10, dec); + const scaled = n * factor; + const truncated = (n > 0) ? Math.floor(scaled+0.000001) : Math.ceil(scaled-0.000001); + return truncated / factor; } -// Clamp v to [mn, mx]; either bound may be null (unbounded); returns null if v is null. -function cxjs_constrain(v, mn, mx) +// Clamp n to range [min, max]. Either bound may be null to unbound results. +// Returns null if n is null. +function cxjs_constrain(n, min, max) { - if (v == null) return null; - if (mn != null && v < mn) return mn; - if (mx != null && v > mx) return mx; - return v; + if (n === null) return null; + if (min !== null && n < min) return min; + if (max !== null && n > max) return max; + return n; } -// Return a random float in [0,1); seed is accepted but ignored. +// Returns a random float in range [0,1). Seed is accepted, but it is +// ignored because ecma262* does not provide a way to specify a seed. +// *see: https://tc39.es/ecma262/#sec-math.random. function cxjs_rand(seed) { return Math.random(); } -// Square root of v; returns null if v is null or negative. -function cxjs_sqrt(v) +// Returns the square root of n, or null if n is null or negative. +function cxjs_sqrt(n) { - if (v == null) return null; - var r = Math.sqrt(v); - return isNaN(r) ? null : r; + if (n == null) return null; + const result = Math.sqrt(n); + return isNaN(result) ? null : result; } -// v squared; returns null if v is null. -function cxjs_square(v) +// Returns the square of n, or null if n is null. +function cxjs_square(n) { - if (v == null) return null; - return v * v; + if (n == null) return null; + return n * n; } -// n raised to power p; returns null if either is null. +// Returns n raised to power p, or null if either parameter is null. function cxjs_power(n, p) { if (n == null || p == null) return null; return Math.pow(n, p); } -// Convert radians to degrees; returns null if v is null. -function cxjs_degrees(v) +// Converts radians to degrees, or returns null if radians is null. +function cxjs_degrees(radians) { - if (v == null) return null; - return v * 180.0 / Math.PI; + if (radians == null) return null; + return (radians * 180.0) / Math.PI; } -// Convert degrees to radians; returns null if v is null. -function cxjs_radians(v) +// Converts degrees to radians, or returns null if degrees is null. +function cxjs_radians(degrees) { - if (v == null) return null; - return v * Math.PI / 180.0; + if (degrees == null) return null; + return (degrees * Math.PI) / 180.0; } // Format number v as money using fmt format string (see centrallix-sysdoc format chars); returns null on invalid input. From a6ce55924dfcc0d5a74a2bdf6590eb6d277b9547 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 1 Jun 2026 15:43:59 -0600 Subject: [PATCH 10/18] Remove cxjs_moneyformat because it seemed like too much work to review. --- centrallix-os/sys/js/ht_render.js | 87 ------------------------------- 1 file changed, 87 deletions(-) diff --git a/centrallix-os/sys/js/ht_render.js b/centrallix-os/sys/js/ht_render.js index 1e86284a6..9eec9af44 100644 --- a/centrallix-os/sys/js/ht_render.js +++ b/centrallix-os/sys/js/ht_render.js @@ -932,93 +932,6 @@ function cxjs_radians(degrees) return (degrees * Math.PI) / 180.0; } -// Format number v as money using fmt format string (see centrallix-sysdoc format chars); returns null on invalid input. -function cxjs_moneyformat(v, fmt) - { - // Validate and parse number. - if (v == null || fmt == null) return null; - var num = parseFloat(v); - if (isNaN(num)) return null; - var is_neg = num < 0; - var abs_v = Math.abs(num); - var pw = Math.floor(abs_v); - var pf = Math.round((abs_v - pw) * 10000); - if (pf >= 10000) { pw++; pf = 0; } - - // Init state; scan pre-decimal format for width and sign options. - var decimal_char = '.'; - var comma_char = ','; - var zero_type = 0; - var zero_strings = [null, '-0-', '0', '']; - var automatic_sign = 1; - var tm = 1; - var pc, nc, pc2, c; - for (var pi = 0; pi < fmt.length && fmt[pi] != '.'; pi++) - { - pc = fmt[pi]; - if (pc=='0'||pc=='#'||pc=='^'||pc==' '||pc=='*') tm *= 10; - else if (pc=='I') { decimal_char = ','; comma_char = '.'; } - else if (pc=='Z') zero_type = 1; - else if (pc=='z') zero_type = 2; - else if (pc=='B') zero_type = 3; - } - if (tm > 1) tm /= 10; - if (/[+\-\(\)\[\]]/.test(fmt)) automatic_sign = 0; - if (pw === 0 && pf === 0 && zero_type !== 0) return zero_strings[zero_type]; - - // Format result string. - var result = ''; - var suppressing_zeros = 1; - var in_decimal_part = 0; - var d; - var i = 0; - while (i < fmt.length) - { - c = fmt[i]; - if (automatic_sign) { automatic_sign = 0; if (is_neg) result += '-'; } - switch(c) - { - case '$': result += '$'; break; - case ' ': case '*': case '0': case '^': case '#': - if (!tm) break; - if (in_decimal_part) - { d = Math.floor(pf/tm)%10; tm = Math.floor(tm/10); } - else - { d = Math.floor(pw/tm); pw -= d*tm; tm = Math.floor(tm/10); } - if (d !== 0 || c=='0' || c=='^') suppressing_zeros = 0; - if (suppressing_zeros) { if (c==' '||c=='*') result += ' '; } - else result += d; - break; - case ',': - if (!suppressing_zeros) result += comma_char; - else if (!((i>0 && fmt[i-1]=='#') || (i+1 Date: Mon, 1 Jun 2026 15:44:51 -0600 Subject: [PATCH 11/18] Fix bad equality checks. --- centrallix-os/sys/js/ht_render.js | 32 +++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/centrallix-os/sys/js/ht_render.js b/centrallix-os/sys/js/ht_render.js index 9eec9af44..2be14b58a 100644 --- a/centrallix-os/sys/js/ht_render.js +++ b/centrallix-os/sys/js/ht_render.js @@ -513,7 +513,7 @@ function cxjs_replace(str, srch, rep) // Convert integer number (n) to English words, with a trailing space. function cxjs_wordify(n) { - if (n == null) return null; + if (n === null) return null; // Declare word tables. const digits = [ @@ -576,7 +576,7 @@ function cxjs_wordify(n) // Repeat string s n times (up to 255); returns null if n < 0. function cxjs_replicate(s, n) { - if (s == null || n == null) return null; + if (s === null || n === null) return null; // Process parameters. const str = String(s); @@ -594,7 +594,7 @@ function cxjs_replicate(s, n) // Internal: parse M/D/YYYY H:MM:SS date string into a Date; returns null on failure. function cxjs__parsedate(date_str) { - if (date_str == null) return null; + if (date_str === null) return null; const match = String(date_str).match(/^(\d+)\/(\d+)\/(\d+)(?:\s+(\d+):(\d+)(?::(\d+))?)?/); if (!match) return null; @@ -613,7 +613,7 @@ function cxjs__parsedate(date_str) // Extract year/month/day/hour/minute/second/weekday from date_str; returns int or null. function cxjs_datepart(part, date_str) { - if (part == null || date_str == null) return null; + if (part === null || date_str === null) return null; const datetime = cxjs__parsedate(date_str); if (!datetime) return null; @@ -638,7 +638,7 @@ function cxjs_datepart(part, date_str) function cxjs_datediff(part, date1, date2) { // Validate and parse parameters. - if (part == null || date1 == null || date2 == null) return null; + if (part === null || date1 === null || date2 === null) return null; let dt1 = cxjs__parsedate(date1); let dt2 = cxjs__parsedate(date2); if (!dt1 || !dt2) return null; @@ -679,7 +679,7 @@ function cxjs_datediff(part, date1, date2) function cxjs_dateformat(date_str, format) { // Validate and parse parameters. - if (date_str == null || format == null) return null; + if (date_str === null || format === null) return null; const d = cxjs__parsedate(date_str); if (!d) return null; @@ -842,7 +842,7 @@ function cxjs_dateformat(date_str, format) // Absolute value of n. Returns null if n is null. function cxjs_abs(n) { - if (n == null) return null; + if (n === null) return null; return Math.abs(n); } @@ -851,8 +851,8 @@ function cxjs_abs(n) function cxjs_round(n, dec) { // Validate parameters. - if (n == null) return null; - if (dec == null) dec = 0; + if (n === null) return null; + if (dec === null) dec = 0; dec = Math.round(dec); // Normalize. // Scale, round, unscale. @@ -867,8 +867,8 @@ function cxjs_round(n, dec) function cxjs_truncate(n, dec) { // Validate parameters. - if (n == null) return null; - if (dec == null) dec = 0; + if (n === null) return null; + if (dec === null) dec = 0; dec = Math.round(dec); // Normalize. // Scale, truncate, unscale. @@ -899,7 +899,7 @@ function cxjs_rand(seed) // Returns the square root of n, or null if n is null or negative. function cxjs_sqrt(n) { - if (n == null) return null; + if (n === null) return null; const result = Math.sqrt(n); return isNaN(result) ? null : result; } @@ -907,28 +907,28 @@ function cxjs_sqrt(n) // Returns the square of n, or null if n is null. function cxjs_square(n) { - if (n == null) return null; + if (n === null) return null; return n * n; } // Returns n raised to power p, or null if either parameter is null. function cxjs_power(n, p) { - if (n == null || p == null) return null; + if (n === null || p === null) return null; return Math.pow(n, p); } // Converts radians to degrees, or returns null if radians is null. function cxjs_degrees(radians) { - if (radians == null) return null; + if (radians === null) return null; return (radians * 180.0) / Math.PI; } // Converts degrees to radians, or returns null if degrees is null. function cxjs_radians(degrees) { - if (degrees == null) return null; + if (degrees === null) return null; return (degrees * Math.PI) / 180.0; } From 832dd59c932f8f44dffca02b0d87ad16036dcd90 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 1 Jun 2026 15:46:12 -0600 Subject: [PATCH 12/18] Improve a comment. --- centrallix-os/sys/js/ht_render.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/centrallix-os/sys/js/ht_render.js b/centrallix-os/sys/js/ht_render.js index 2be14b58a..bf444caa6 100644 --- a/centrallix-os/sys/js/ht_render.js +++ b/centrallix-os/sys/js/ht_render.js @@ -675,7 +675,8 @@ function cxjs_datediff(part, date1, date2) return null; } -// Format date string date_str using format (see centrallix-sysdoc format chars); returns null on invalid input. +// Format date_str using format (see centrallix-sysdoc format chars). +// Returns the formatted date string, or null if the input is invalid. function cxjs_dateformat(date_str, format) { // Validate and parse parameters. From 3182852610edf7e5caa4e903c39ac4145eee7ff0 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 1 Jun 2026 16:11:24 -0600 Subject: [PATCH 13/18] Fix bugs caused by switching to strict equality. --- centrallix-os/sys/js/ht_render.js | 46 ++++++++++++++++++------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/centrallix-os/sys/js/ht_render.js b/centrallix-os/sys/js/ht_render.js index bf444caa6..fff86d547 100644 --- a/centrallix-os/sys/js/ht_render.js +++ b/centrallix-os/sys/js/ht_render.js @@ -513,7 +513,7 @@ function cxjs_replace(str, srch, rep) // Convert integer number (n) to English words, with a trailing space. function cxjs_wordify(n) { - if (n === null) return null; + if (n === null || n === undefined) return null; // Declare word tables. const digits = [ @@ -576,7 +576,9 @@ function cxjs_wordify(n) // Repeat string s n times (up to 255); returns null if n < 0. function cxjs_replicate(s, n) { - if (s === null || n === null) return null; + if (s === null || n === null || + s === undefined || n === undefined + ) return null; // Process parameters. const str = String(s); @@ -594,7 +596,7 @@ function cxjs_replicate(s, n) // Internal: parse M/D/YYYY H:MM:SS date string into a Date; returns null on failure. function cxjs__parsedate(date_str) { - if (date_str === null) return null; + if (date_str === null || date_str === undefined) return null; const match = String(date_str).match(/^(\d+)\/(\d+)\/(\d+)(?:\s+(\d+):(\d+)(?::(\d+))?)?/); if (!match) return null; @@ -613,7 +615,9 @@ function cxjs__parsedate(date_str) // Extract year/month/day/hour/minute/second/weekday from date_str; returns int or null. function cxjs_datepart(part, date_str) { - if (part === null || date_str === null) return null; + if (part === null || date_str === null || + part === undefined || date_str === undefined + ) return null; const datetime = cxjs__parsedate(date_str); if (!datetime) return null; @@ -638,7 +642,9 @@ function cxjs_datepart(part, date_str) function cxjs_datediff(part, date1, date2) { // Validate and parse parameters. - if (part === null || date1 === null || date2 === null) return null; + if (part === null || date1 === null || date2 === null || + part === undefined || date1 === undefined || date2 === undefined + ) return null; let dt1 = cxjs__parsedate(date1); let dt2 = cxjs__parsedate(date2); if (!dt1 || !dt2) return null; @@ -680,7 +686,9 @@ function cxjs_datediff(part, date1, date2) function cxjs_dateformat(date_str, format) { // Validate and parse parameters. - if (date_str === null || format === null) return null; + if (date_str === null || format === null || + date_str === undefined || format === undefined + ) return null; const d = cxjs__parsedate(date_str); if (!d) return null; @@ -843,7 +851,7 @@ function cxjs_dateformat(date_str, format) // Absolute value of n. Returns null if n is null. function cxjs_abs(n) { - if (n === null) return null; + if (n === null || n === undefined) return null; return Math.abs(n); } @@ -852,8 +860,8 @@ function cxjs_abs(n) function cxjs_round(n, dec) { // Validate parameters. - if (n === null) return null; - if (dec === null) dec = 0; + if (n === null || n === undefined) return null; + if (dec === null || dec === undefined) dec = 0; dec = Math.round(dec); // Normalize. // Scale, round, unscale. @@ -868,8 +876,8 @@ function cxjs_round(n, dec) function cxjs_truncate(n, dec) { // Validate parameters. - if (n === null) return null; - if (dec === null) dec = 0; + if (n === null || n === undefined) return null; + if (dec === null || dec === undefined) dec = 0; dec = Math.round(dec); // Normalize. // Scale, truncate, unscale. @@ -883,9 +891,9 @@ function cxjs_truncate(n, dec) // Returns null if n is null. function cxjs_constrain(n, min, max) { - if (n === null) return null; - if (min !== null && n < min) return min; - if (max !== null && n > max) return max; + if (n === null || n === undefined) return null; + if (min !== null && min !== undefined && n < min) return min; + if (max !== null && max !== undefined && n > max) return max; return n; } @@ -900,7 +908,7 @@ function cxjs_rand(seed) // Returns the square root of n, or null if n is null or negative. function cxjs_sqrt(n) { - if (n === null) return null; + if (n === null || n === undefined) return null; const result = Math.sqrt(n); return isNaN(result) ? null : result; } @@ -908,28 +916,28 @@ function cxjs_sqrt(n) // Returns the square of n, or null if n is null. function cxjs_square(n) { - if (n === null) return null; + if (n === null || n === undefined) return null; return n * n; } // Returns n raised to power p, or null if either parameter is null. function cxjs_power(n, p) { - if (n === null || p === null) return null; + if (n === null || p === null || n === undefined || p === undefined) return null; return Math.pow(n, p); } // Converts radians to degrees, or returns null if radians is null. function cxjs_degrees(radians) { - if (radians === null) return null; + if (radians === null || radians === undefined) return null; return (radians * 180.0) / Math.PI; } // Converts degrees to radians, or returns null if degrees is null. function cxjs_radians(degrees) { - if (degrees === null) return null; + if (degrees === null || degrees === undefined) return null; return (degrees * Math.PI) / 180.0; } From 92da8fc407eb2f50080983cb595cdde6b4437462 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 1 Jun 2026 16:21:39 -0600 Subject: [PATCH 14/18] Fix trailing comma bug (caught by Claude). --- centrallix-os/sys/js/ht_render.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/centrallix-os/sys/js/ht_render.js b/centrallix-os/sys/js/ht_render.js index fff86d547..6874ea663 100644 --- a/centrallix-os/sys/js/ht_render.js +++ b/centrallix-os/sys/js/ht_render.js @@ -570,6 +570,10 @@ function cxjs_wordify(n) else if (multiple !== 0 && chunk !== 0) result += " "; } + + // Strip trailing comma left when the highest group needs no separator. + if (result.endsWith(", ")) result = result.slice(0, -2) + " "; + return result; } @@ -657,7 +661,7 @@ function cxjs_datediff(part, date1, date2) const tmp = dt2; dt2 = dt1; dt1 = tmp; - } + } part = String(part).toLowerCase(); // Year and month parts. From bb381f32f49a8eab80967ead2ef66b737f202f35 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 1 Jun 2026 16:26:29 -0600 Subject: [PATCH 15/18] Remove complex functions (moved to another PR). Remove wordify. Remove datepart. Remove datediff. Remove dateformat. Clean up. --- centrallix-os/sys/js/ht_render.js | 323 ------------------------------ 1 file changed, 323 deletions(-) diff --git a/centrallix-os/sys/js/ht_render.js b/centrallix-os/sys/js/ht_render.js index 6874ea663..8f23e1668 100644 --- a/centrallix-os/sys/js/ht_render.js +++ b/centrallix-os/sys/js/ht_render.js @@ -509,74 +509,6 @@ function cxjs_replace(str, srch, rep) rep); } - -// Convert integer number (n) to English words, with a trailing space. -function cxjs_wordify(n) - { - if (n === null || n === undefined) return null; - - // Declare word tables. - const digits = [ - "Zero", "One", "Two", "Three", "Four", "Five", - "Six", "Seven", "Eight", "Nine", "Ten" - ]; - const teens = [ - "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", - "Sixteen", "Seventeen", "Eighteen", "Nineteen" - ]; - const tens = [ - "Ten", "Twenty", "Thirty", "Forty", "Fifty", - "Sixty", "Seventy", "Eighty", "Ninety" - ]; - const multiples = ["", - "Thousand", "Million", "Billion", "Trillion", "Quadrillion" - ]; - - // Handle negative values and normalize n. - n = Math.round(n); - if (isNaN(n)) return null; - const abs_n = Math.abs(n); - let result = (n < 0) ? "Negative " : ""; - - // Handle zero. - if (abs_n === 0) - { - result += "Zero "; - return result; - } - - // Traverse thousands-chunks from highest to lowest. - for (let multiple = 5; multiple >= 0; multiple--) - { - const chunk = Math.floor(abs_n / Math.pow(1000, multiple)) % 1000; - - if (chunk === 0) continue; - if (chunk >= 100) - result += digits[Math.floor(chunk/100)] + " Hundred "; - - const rem = chunk % 100; - if (rem > 19) - { - result += tens[Math.floor(rem/10)-1]; - if (rem%10 !== 0) result += "-" + digits[rem%10] + " "; - else result += " "; - } - else if (rem > 10) result += teens[rem-11] + " "; - else if (rem > 0) result += digits[rem] + " "; - - result += multiples[multiple]; - if (chunk > 10 && (chunk%10 !== 0 || chunk > 100) && multiple !== 0) - result += ", "; - else if (multiple !== 0 && chunk !== 0) - result += " "; - } - - // Strip trailing comma left when the highest group needs no separator. - if (result.endsWith(", ")) result = result.slice(0, -2) + " "; - - return result; - } - // Repeat string s n times (up to 255); returns null if n < 0. function cxjs_replicate(s, n) { @@ -597,261 +529,6 @@ function cxjs_replicate(s, n) return result; } -// Internal: parse M/D/YYYY H:MM:SS date string into a Date; returns null on failure. -function cxjs__parsedate(date_str) - { - if (date_str === null || date_str === undefined) return null; - - const match = String(date_str).match(/^(\d+)\/(\d+)\/(\d+)(?:\s+(\d+):(\d+)(?::(\d+))?)?/); - if (!match) return null; - - return new Date( - parseInt(match[3], 10), - parseInt(match[1], 10) - 1, - parseInt(match[2], 10), - (match[4]) ? parseInt(match[4], 10) : 0, - (match[5]) ? parseInt(match[5], 10) : 0, - (match[6]) ? parseInt(match[6], 10) : 0, - 0 - ); - } - -// Extract year/month/day/hour/minute/second/weekday from date_str; returns int or null. -function cxjs_datepart(part, date_str) - { - if (part === null || date_str === null || - part === undefined || date_str === undefined - ) return null; - - const datetime = cxjs__parsedate(date_str); - if (!datetime) return null; - - switch (String(part).toLowerCase()) - { - case "year": return datetime.getFullYear(); - case "month": return datetime.getMonth() + 1; - case "day": return datetime.getDate(); - case "hour": return datetime.getHours(); - case "minute": return datetime.getMinutes(); - case "second": return datetime.getSeconds(); - case "weekday": return datetime.getDay() + 1; - } - - return null; - } - -// Calculate the signed difference (date2-date1) between dates and return the -// requested datetime part (e.g. "year", "month", "day", "hour", "minute", -// "second"), or null on failure. -function cxjs_datediff(part, date1, date2) - { - // Validate and parse parameters. - if (part === null || date1 === null || date2 === null || - part === undefined || date1 === undefined || date2 === undefined - ) return null; - let dt1 = cxjs__parsedate(date1); - let dt2 = cxjs__parsedate(date2); - if (!dt1 || !dt2) return null; - - // Normalize order; get part name. - let sign = 1; - if (dt2 < dt1) - { - sign = -1; - const tmp = dt2; - dt2 = dt1; - dt1 = tmp; - } - part = String(part).toLowerCase(); - - // Year and month parts. - if (part === "year") - return sign * (dt2.getFullYear() - dt1.getFullYear()); - if (part === "month") - return sign * ((dt2.getFullYear() - dt1.getFullYear())*12 + dt2.getMonth()-dt1.getMonth()); - - // Day, hour, minute, second parts. - const m1 = new Date(dt1.getFullYear(), dt1.getMonth(), dt1.getDate()); - const m2 = new Date(dt2.getFullYear(), dt2.getMonth(), dt2.getDate()); - const days = Math.round((m2 - m1) / 86400000); - if (part === "day") return sign * days; - const hours = days*24 + dt2.getHours() - dt1.getHours(); - if (part === "hour") return sign * hours; - const minutes = hours*60 + dt2.getMinutes() - dt1.getMinutes(); - if (part === "minute") return sign * minutes; - const seconds = minutes*60 + dt2.getSeconds() - dt1.getSeconds(); - if (part === "second") return sign * seconds; - - return null; - } - -// Format date_str using format (see centrallix-sysdoc format chars). -// Returns the formatted date string, or null if the input is invalid. -function cxjs_dateformat(date_str, format) - { - // Validate and parse parameters. - if (date_str === null || format === null || - date_str === undefined || format === undefined - ) return null; - const d = cxjs__parsedate(date_str); - if (!d) return null; - - // Month name tables. - const month_abbrevs = ["", - "Jan", "Feb", "Mar", "Apr", - "May", "Jun", "Jul", "Aug", - "Sep", "Oct", "Nov", "Dec" - ]; - const month_names = ["", - "January", "February", "March", "April", - "May", "June", "July", "August", - "September", "October", "November", "December" - ]; - - // Declare state variables. - let result = ""; - let i = 0; - const append = (str, n) => - { - result += str; - i += n; - } - - // Handle AM & PM. - let append_am_pm = 0; - const check_append_am_pm = () => - { - if (!append_am_pm) return; - if (i >= format.length || format[i]===' ' || format[i]===',') - { - result += (d.getHours() >= 12) ? "PM" : "AM"; - append_am_pm = 0; - } - } - - // Scan format string. - while (i < format.length) - { - const c = format[i]; - switch (c) - { - case 'D': i++; break; - case 'd': - { - const day = d.getDate(); - if (format[i+1] === 'd' && format[i+2] === 'd') - { - const suffix = - (day === 1 || day === 21 || day === 31) ? "st" : - (day === 2 || day === 22) ? "nd" : - (day === 3 || day === 23) ? "rd" : - "th"; - append(day + suffix, 3); - } - else if (format[i+1] === 'd') - { - append(((day < 10) ? "0" : "") + day, 2); - } - else - { - append(day, 1); - } - break; - } - case 'M': - { - const month = d.getMonth() + 1; - - // MMMM - if (format[i+1] === 'M' && format[i+2] === 'M' && format[i+3] === 'M') - append(month_names[month], 4); - // MMM - else if (format[i+1] === 'M' && format[i+2] === 'M') - append(month_abbrevs[month], 3); - // MM - else if (format[i+1] === 'M') - append(((month < 10) ? "0" : "") + (month), 2); - // M - else - append(month, 1); - - break; - } - case 'y': - { - if (format[i+1] === 'y' && format[i+2] === 'y' && format[i+3] === 'y') - append(("000" + d.getFullYear()).slice(-4), 4); - else if (format[i+1] === 'y') - append(("0" + (d.getFullYear()%100)).slice(-2), 2); - else i++; - break; - } - case 'H': - { - if (format[i+1] === 'H') - append(((d.getHours() < 10) ? "0" : "") + d.getHours(), 1); - i++; - append_am_pm = 0; - break; - } - case 'h': - { - if (format[i+1] === 'h') - { - const hr = d.getHours() % 12 || 12; - append(((hr < 10) ? "0" : "") + hr, 1); - } - i++; - append_am_pm = 1; - check_append_am_pm(); - break; - } - case 'm': - { - if (format[i+1] === 'm') - append(((d.getMinutes() < 10) ? "0" : "") + d.getMinutes(), 1); - i++; - check_append_am_pm(); - break; - } - case 's': - { - if (format[i+1] === 's') - append(((d.getSeconds() < 10) ? "0" : "") + d.getSeconds(), 1); - i++; - check_append_am_pm(); - break; - } - case 'I': - { - if (format[i+1] === 'I') - i++; - i++; - break; - } - case 'L': if ( - i + 2 < format.length && ( - format[i+1] === 'm' || - format[i+1] === 'M' || - format[i+1] === 'w' || - format[i+1] === 'W' - ) && format[i+2] === '[') - { - const end = format.indexOf(']', i); - i = (end >= 0) ? end+1 : i+1; - break; - } // Fallthrough - default: - { - append(c, 1); - break; - } - } - } - - return result; - } - // Absolute value of n. Returns null if n is null. function cxjs_abs(n) { From 9d2d0aa58cc5ffdffadebb3bef92656d35fecc94 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 2 Jun 2026 10:48:35 -0600 Subject: [PATCH 16/18] Fix bugs and simplify cxjs_truncate() by using a solution from ChatGPT. --- centrallix-os/sys/js/ht_render.js | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/centrallix-os/sys/js/ht_render.js b/centrallix-os/sys/js/ht_render.js index 8f23e1668..0e1e6fd06 100644 --- a/centrallix-os/sys/js/ht_render.js +++ b/centrallix-os/sys/js/ht_render.js @@ -554,18 +554,12 @@ function cxjs_round(n, dec) // Truncate n toward zero to dec decimal places (default 0). // Returns null if n is null. -function cxjs_truncate(n, dec) +function cxjs_truncate(n, dec = 0) { - // Validate parameters. if (n === null || n === undefined) return null; - if (dec === null || dec === undefined) dec = 0; - dec = Math.round(dec); // Normalize. - - // Scale, truncate, unscale. - const factor = Math.pow(10, dec); - const scaled = n * factor; - const truncated = (n > 0) ? Math.floor(scaled+0.000001) : Math.ceil(scaled-0.000001); - return truncated / factor; + + const factor = 10 ** Math.round(dec); + return Math.trunc(n * factor) / factor; } // Clamp n to range [min, max]. Either bound may be null to unbound results. From 4f5a6833fe2093b044c91084c432a2540874cb94 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 2 Jun 2026 10:54:58 -0600 Subject: [PATCH 17/18] Update from Math.pow() to **. --- centrallix-os/sys/js/ht_render.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/centrallix-os/sys/js/ht_render.js b/centrallix-os/sys/js/ht_render.js index 0e1e6fd06..444efe790 100644 --- a/centrallix-os/sys/js/ht_render.js +++ b/centrallix-os/sys/js/ht_render.js @@ -538,15 +538,14 @@ function cxjs_abs(n) // Round n to dec decimal places (default 0), toward nearest. // Returns null if n is null. -function cxjs_round(n, dec) +function cxjs_round(n, dec = 0) { // Validate parameters. if (n === null || n === undefined) return null; - if (dec === null || dec === undefined) dec = 0; dec = Math.round(dec); // Normalize. // Scale, round, unscale. - const factor = Math.pow(10, dec); + const factor = 10 ** dec; const scaled = n * factor; const rounded = ((n > 0) ? Math.floor(scaled+0.5) : Math.ceil(scaled-0.5)); return rounded / factor; @@ -599,7 +598,7 @@ function cxjs_square(n) function cxjs_power(n, p) { if (n === null || p === null || n === undefined || p === undefined) return null; - return Math.pow(n, p); + return n ** p; } // Converts radians to degrees, or returns null if radians is null. From 39456f919302baef49591ee55546a85f31048456 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 2 Jun 2026 10:57:52 -0600 Subject: [PATCH 18/18] Add warning when seed is ignored. --- centrallix-os/sys/js/ht_render.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/centrallix-os/sys/js/ht_render.js b/centrallix-os/sys/js/ht_render.js index 444efe790..547cf9343 100644 --- a/centrallix-os/sys/js/ht_render.js +++ b/centrallix-os/sys/js/ht_render.js @@ -576,6 +576,8 @@ function cxjs_constrain(n, min, max) // *see: https://tc39.es/ecma262/#sec-math.random. function cxjs_rand(seed) { + if (seed !== null && seed !== undefined) + console.warn(`Ignoring seed "${seed}" because ecma262 does not define how to specify a seed.`); return Math.random(); }