/*
* Author: Vlad Seryakov vseryakov@gmail.com
* backendjs 2018
*/
const lib = require(__dirname + '/../lib');
const logger = require(__dirname + '/../logger');
const { performance } = require("node:perf_hooks");
lib.strftimeFormat = "%Y-%m-%d %H:%M:%S %Z";
lib.strftimeMap = {
weekDays: {
"": [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ]
},
weekDaysFull: {
"": [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ]
},
months: {
"": [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]
},
monthsFull: {
"": [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ]
},
};
lib.tzMap = [
// name, GMT offset, daylight, linux support
["EDT", "GMT-0400", true],
["EST", "GMT-0500", false],
["PDT", "GMT-0700", true],
["PST", "GMT-0800", false],
["CDT", "GMT-0500", true],
["CST", "GMT-0600", false],
["MDT", "GMT-0600", true],
["MST", "GMT-0700", false],
["HADT", "GMT-0900", true, false],
["HAST", "GMT-1000", false, false],
["AKDT", "GMT-0800", true, false],
["AKST", "GMT-0900", false, false],
["ADT", "GMT-0300", true, false],
["AST", "GMT-0400", false, false],
];
lib._epoch = Date.UTC(2026, 0, 1);
lib._epoch_usec = lib._epoch * 1000.0;
lib._epoch_sec = lib._epoch/1000;
/**
* Returns current time in seconds (s), microseconds (m), time struct (tm) or milliseconds since the local `lib._epoch` (2026-01-01 UTC)
* @param {string} [type]
* @return {int}
* @memberof module:lib
* @method localEpoch
*/
lib.localEpoch = function(type)
{
switch (type) {
case "s": return Math.round(Date.now()/1000) - this._epoch_sec;
case "m": return lib.clock() - this._epoch_usec;
default: return Date.now() - this._epoch;
}
}
/**
* Returns current time in microseconds since January 1, 1970, UTC
* @return {int}
* @memberof module:lib
* @method clock
*/
lib.clock = function()
{
return Math.trunc((performance.timeOrigin + performance.now())*1000);
}
/**
* Return number of seconds for current time
* @return {int}
* @memberof module:lib
* @method now
*/
lib.now = function()
{
return Math.round(Date.now()/1000);
}
/**
* Return the number of days in the given month of the specified year.
* @param {int} year
* @param {int} month
* @return {int}
* @memberof module:lib
* @method daysInMonth
*/
lib.daysInMonth = function(year, month)
{
return new Date(year, month, 0).getDate();
}
/**
* Return an ISO week number for given date, from https://www.epochconverter.com/weeknumbers
* @param {string|Date} date
* @param {boolean} [utc]
* @return {int}
* @memberof module:lib
* @method weekOfYear
*/
lib.weekOfYear = function(date, utc)
{
date = this.toDate(date, null);
if (!date) return 0;
utc = utc ? "UTC": "";
var target = new Date(date.valueOf());
target[`set${utc}Date`](target[`get${utc}Date`]() - ((date[`get${utc}Day`]() + 6) % 7) + 3);
var firstThursday = target.valueOf();
target[`set${utc}Month`](0, 1);
var day = target[`get${utc}Day`]();
if (day != 4) target[`set${utc}Month`](0, 1 + ((4 - day) + 7) % 7);
return 1 + Math.ceil((firstThursday - target) / 604800000);
}
/**
* Return first day of the week by ISO week number
* @param {int} year
* @param {int} week
* @return {Date}
* @memberof module:lib
* @method weekDate
*/
lib.weekDate = function(year, week)
{
week = lib.toNumber(week) - 1;
var d = new Date(year, 0, 1);
if (lib.weekOfYear(d) != 1) week--;
var day = d.getDay();
d.setDate(d.getDate() - (day == 1 ? 0: (day || 7) - 1));
return new Date(d.getTime() + 86400000 * 7 * Math.max(0, week));
}
/**
* Returns true if the given date is in DST timezone
* @param {Date} date
* @return {boolean}
* @memberof module:lib
* @method isDST
*/
lib.isDST = function(date)
{
var jan = new Date(date.getFullYear(), 0, 1).getTimezoneOffset();
var jul = new Date(date.getFullYear(), 6, 1).getTimezoneOffset();
return Math.max(jan, jul) != date.getTimezoneOffset();
}
/**
* Return a timezone human name if matched (EST, PDT...), tz must be in GMT-NNNN format
* @param {string} tz
* @param {string} dst
* @return {string}
* @memberof module:lib
* @method tzName
*/
lib.tzName = function(tz, dst)
{
if (!tz || typeof tz != "string") return "";
var t = tz.indexOf(":") > 0 ? tz.replace(":", "") : tz;
for (const m of this.tzMap) {
if (t == m[1] && dst === m[2]) return m[0];
}
return tz;
}
/**
* Parses a string with time and return an array [hour, min], accepts 12 and 24hrs formats,
* a single hour is accepted as well, returns undefined if cannot parse
* @param {string} time
* @return {int[]}
* @memberof module:lib
* @method parseTime
*/
lib.parseTime = function(time)
{
if (typeof time != "string") time = String(time);
const d = time.match(/^(([0-9]+)|([0-9]+):([0-9]+)) *(am|pm)?$/i);
if (!d) return;
let h = lib.toNumber(d[2] || d[3]);
const m = lib.toNumber(d[4]);
switch (d[5]) {
case "am":
case "AM":
if (h >= 12) h -= 12;
break;
case "pm":
case "PM":
if (h < 12) h += 12;
break;
}
if (h < 0 || h > 23 || m < 0 || m > 59) return;
return [h, m]
}
/**
* Returns 0 if the current time is not within specified valid time range or it is invalid. Only continious time range is supported, it
* does not handle over the midninght ranges, i.e. time1 is always must be greater than time2.
* @param {string} time1
* @param {string} time2
* @param {object} [options]
* @param {int} [options.tz] to specify timezone, no timezone means current timezone.
* @param {int|Date|string} [options.date] if given must be a list of dates in the format: YYY-MM-DD,...
* @return {boolean}
* @memberof module:lib
* @method isTimeRange
*/
lib.isTimeRange = function(time1, time2, options)
{
if (!time1 && !time2) return 0;
var now = new Date(), tz = options?.tz;
if (tz === "GMT" || tz === "UTC") {
tz = 0;
} else {
tz = typeof tz == "string" && tz.match(/GMT(-|\+)?([0-9]{2}):?([0-9]{2})/);
if (tz) tz = (parseInt(tz[2], 10) * 3600000 + parseInt(tz[3], 10) * 60000) * (tz[1] == "+" ? 1 : -1);
if (!tz) tz = now.getTimezoneOffset() * -60000;
}
now = new Date(now.getTime() + tz);
if (options?.date) {
if (lib.strftime(now, "%Y-%m-%d") != lib.strftime(lib.toDate(options.date), "%Y-%m-%d")) return 0;
}
var h0 = now.getUTCHours();
var m0 = now.getUTCMinutes();
if (time1) {
const t = this.parseTime(time1);
if (!t) return 0;
logger.debug("isTimeRange:", "start:", h0, m0, " - ", t, time1, "tz:", tz, "now:", now);
if (h0*100+m0 < t[0]*100+t[1]) return 0;
}
if (time2) {
const t = this.parseTime(time2);
if (!t) return 0;
logger.debug("isTimeRange:", "end:", h0, m0, " - ", t, time2, "tz:", tz, "now:", now);
if (h0*100+m0 < t[0]*100+t[1]) return 0;
}
return 1;
}
function zeropad(n) { return n > 9 ? n : '0' + n }
function spacepad(n) { return n > 9 ? n : ' ' + n }
const _strftime = {
a: function(t, utc, lang, tz) {
if (lang && !lib.strftimeMap.weekDays[lang]) {
lib.strftimeMap.weekDays[lang] = lib.strftimeMap.weekDays[""].map((x) => (lib.__({ phrase: x, locale: lang })));
}
return lib.strftimeMap.weekDays[lang || ""][utc ? t.getUTCDay() : t.getDay()]
},
A: function(t, utc, lang, tz) {
if (lang && !lib.strftimeMap.weekDaysFull[lang]) {
lib.strftimeMap.weekDaysFull[lang] = lib.strftimeMap.weekDaysFull[""].map((x) => (lib.__({ phrase: x, locale: lang })));
}
return lib.strftimeMap.weekDaysFull[lang || ""][utc ? t.getUTCDay() : t.getDay()]
},
b: function(t, utc, lang, tz) {
if (lang && !lib.strftimeMap.months[lang]) {
lib.strftimeMap.months[lang] = lib.strftimeMap.months[""].map((x) => (lib.__({ phrase: x, locale: lang })));
}
return lib.strftimeMap.months[lang || ""][utc ? t.getUTCMonth() : t.getMonth()]
},
B: function(t, utc, lang, tz) {
if (lang && !lib.strftimeMap.monthsFull[lang]) {
lib.strftimeMap.monthsFull[lang] = lib.strftimeMap.monthsFull[""].map((x) => (lib.__({ phrase: x, locale: lang })));
}
return lib.strftimeMap.monthsFull[lang || ""][utc ? t.getUTCMonth() : t.getMonth()]
},
c: function(t, utc, lang, tz) {
return utc ? t.toUTCString() : t.toString()
},
d: function(t, utc, lang, tz) {
return zeropad(utc ? t.getUTCDate() : t.getDate())
},
e: function(t, utc, lang, tz) {
return spacepad(utc ? t.getUTCDate() : t.getDate())
},
H: function(t, utc, lang, tz) {
return zeropad(utc ? t.getUTCHours() : t.getHours())
},
I: function(t, utc, lang, tz) {
return zeropad((((utc ? t.getUTCHours() : t.getHours()) + 12) % 12) || 12)
},
k: function(t, utc, lang, tz) {
return spacepad(utc ? t.getUTCHours() : t.getHours())
},
l: function(t, utc, lang, tz) {
return spacepad((((utc ? t.getUTCHours() : t.getHours()) + 12) % 12) || 12)
},
L: function(t, utc, lang, tz) {
return zeropad(utc ? t.getUTCMilliseconds() : t.getMilliseconds())
},
m: function(t, utc, lang, tz) {
return zeropad((utc ? t.getUTCMonth() : t.getMonth()) + 1)
}, // month-1
M: function(t, utc, lang, tz) {
return zeropad(utc ? t.getUTCMinutes() : t.getMinutes())
},
p: function(t, utc, lang, tz) {
return (utc ? t.getUTCHours() : t.getHours()) < 12 ? 'am' : 'pm';
},
S: function(t, utc, lang, tz) {
return zeropad(utc ? t.getUTCSeconds() : t.getSeconds())
},
w: function(t, utc, lang, tz) {
return utc ? t.getUTCDay() : t.getDay()
}, // 0..6 == sun..sat
W: function(t, utc, lang, tz) {
return zeropad(lib.weekOfYear(t, utc))
},
y: function(t, utc, lang, tz) {
return zeropad(t.getYear() % 100);
},
Y: function(t, utc, lang, tz) {
return utc ? t.getUTCFullYear() : t.getFullYear()
},
t: function(t, utc, lang, tz) {
return t.getTime()
},
u: function(t, utc, lang, tz) {
return Math.floor(t.getTime()/1000)
},
Z: function(t, utc, lang, tz) {
tz = tz ? tz/60000 : t.getTimezoneOffset();
return "GMT" + (tz < 0 ? "+" : "-") + zeropad(Math.abs(-tz/60)) + "00";
},
zz: function(t, utc, lang, tz) {
return _strftime.z(t, utc, lang, tz, 1);
},
z: function(t, utc, lang, tz, zz) {
tz = tz ? tz/60000 : t.getTimezoneOffset();
tz = "GMT" + (tz < 0 ? "+" : "-") + zeropad(Math.abs(-tz/60)) + "00";
var dst = lib.isDST(t);
for (const i in lib.tzMap) {
if (tz == lib.tzMap[i][1] && (dst === lib.tzMap[i][2])) {
return zz ? tz + " " + lib.tzMap[i][0] : lib.tzMap[i][0];
}
}
return tz;
},
Q: function(t, utc, lang, tz) {
var h = utc ? t.getUTCHours() : t.getHours();
return h < 12 ? lib.__({ phrase: "Morning", locale: lang }) :
h < 17 ? lib.__({ phrase: "Afternoon", locale: lang }) :
lib.__({ phrase: "Evening", locale: lang }) },
'%': function() { return '%' },
};
/**
* Format date object according to Unix stftime function
* @param {string|number|Date} date
* @param {string} fmt
* @param {object} [options]
* @return {toString}
* @memberof module:lib
* @method strftimeMap
*/
lib.strftime = function(date, fmt, options)
{
date = this.toDate(date, null);
if (!date) return "";
var utc = options && options.utc;
var lang = options && options.lang;
var tz = options && typeof options.tz == "number" ? options.tz : 0;
if (tz) date = new Date(date.getTime() - tz);
fmt = fmt || this.strftimeFormat;
for (const p in _strftime) {
fmt = fmt.replace('%' + p, _strftime[p](date, utc, lang, tz));
}
return fmt;
}