@tubular/time
Version:
Date/time, IANA timezones, leap seconds, TAI/UTC conversions, calendar with settable Julian/Gregorian switchover
197 lines • 9.03 kB
JavaScript
import { floor } from '@tubular/math';
import { convertDigitsToAscii, isNumber, toNumber } from '@tubular/util';
export const MIN_YEAR = -271820;
export const MAX_YEAR = 275759;
export const MINUTE_MSEC = 60000;
export const HOUR_MSEC = 3600000;
export const DAY_MSEC = 86400000;
export const DAY_SEC = 86400;
export const DAY_MINUTES = 1440;
export const UNIX_TIME_ZERO_AS_JULIAN_DAY = 2440587.5;
export const JD_J2000 = 2451545.0; // Julian date for the J2000.0 epoch.
export const DELTA_TDT_SEC = 32.184;
export const DELTA_TDT_MSEC = 32184;
export const DELTA_TDT_DAYS = DELTA_TDT_SEC / DAY_SEC;
export const DELTA_MJD = 2400000.5;
export const enEras = ['BC', 'AD', 'Before Christ', 'Anno Domini'];
export const enMonths = ['January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'];
export const enMonthsShort = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
export const enWeekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
export const enWeekdaysShort = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
export const enWeekdaysMin = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
export let formatter;
export const setFormatter = (fmt) => formatter = fmt;
export let deltaTUpdater;
export const setDeltaTUpdater = (dtu) => deltaTUpdater = dtu;
const altFields = [
['y', 'year'], ['q', 'quarter'], ['m', 'month'], ['d', 'day'], ['dow', 'dayOfWeek'], ['dowmi', 'dayOfWeekMonthIndex'],
['dy', 'dayOfYear'], ['n', 'epochDay'],
['j', 'isJulian'], ['yw', 'yearByWeek'], ['w', 'week'], ['dw', 'dayByWeek'],
['ywl', 'yearByWeekLocale'], ['wl', 'weekLocale'], ['dwl', 'dayByWeekLocale'],
['hrs', 'hour'], ['min', 'minute'], ['sec', 'second']
];
const fieldOrder = [
'y', 'q', 'm', 'd', 'dow', 'dowmi', 'dy', 'n', 'j',
'year', 'quarter', 'month', 'day', 'dayOfWeek', 'dayOfWeekMonthIndex', 'dayOfYear', 'epochDay', 'isJulian',
'yw', 'w', 'dw',
'yearByWeek', 'week', 'dayByWeek',
'ywl', 'wl', 'dwl',
'yearByWeekLocale', 'weekLocale', 'dayByWeekLocale',
'hrs', 'min', 'sec',
'hour', 'minute', 'second', 'millis',
'utcOffset', 'dstOffset', 'occurrence', 'deltaTai',
'jde', 'mjde', 'jdu', 'mjdu',
'error'
];
export function syncDateAndTime(obj) {
for (const [key1, key2] of altFields) {
// eslint-disable-next-line no-prototype-builtins
if (obj.hasOwnProperty(key1))
obj[key2] = obj[key1];
// eslint-disable-next-line no-prototype-builtins
else if (obj.hasOwnProperty(key2))
obj[key1] = obj[key2];
}
return obj;
}
export function purgeAliasFields(obj, keepLongForm = false) {
for (const [short, long] of altFields)
delete obj[keepLongForm ? short : long];
return obj;
}
const minimalKeys = new Set(['y', 'year', 'm', 'month', 'd', 'day', 'hrs', 'hour', 'min', 'minute', 'sec', 'second', 'millis']);
export function minimizeFields(obj) {
Object.keys(obj).forEach(key => {
if (!minimalKeys.has(key))
delete obj[key];
});
return obj;
}
export function orderFields(obj) {
for (const key of fieldOrder) {
const value = obj[key];
delete obj[key];
if (value != null)
obj[key] = value;
}
return obj;
}
export function validateDateAndTime(obj) {
const dt = obj;
Object.keys(obj).forEach(key => {
if (key !== 'j' && key !== 'isJulian') {
const value = obj[key];
if (value != null) {
if (/^(m?(deltaTai|jde|jdu))$/.test(key)) {
if (!isNumber(value))
throw new Error(`${key} must be a numeric value (${value})`);
}
else if (!isNumber(value) || value !== floor(value))
throw new Error(`${key} must be an integer value (${value})`);
}
}
});
if (obj.y == null && obj.year == null && obj.yw == null && obj.yearByWeek == null &&
obj.ywl == null && obj.yearByWeekLocale == null && obj.n == null && obj.epochDay == null &&
dt.hrs == null && dt.hour == null && dt.jde == null && dt.mjde == null && dt.jdu == null && dt.mjdu == null)
throw new Error('A year value, an epoch day, an hour value, or a Julian date value must be specified');
}
const invalidDateTime = new Error('Invalid ISO date/time');
export function parseISODateTime(date, allowLeapSecond = false) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
date = date.trim();
let time;
let $ = /^([-+]?\d+)-(\d{1,2}(?=\D|$))(?:-(\d{1,2}))?/.exec(date);
if ($ || ($ = /^([-+]?\d{1,5}(?=[^-+:.Ww\d]|$))/.exec(date)) || ($ = /^([-+]?\d{4,})(\d\d)(\d\d)/.exec(date)))
time = { y: toNumber($[1]), m: Number((_a = $[2]) !== null && _a !== void 0 ? _a : 1), d: Number((_b = $[3]) !== null && _b !== void 0 ? _b : 1) };
else if (($ = /^([-+]?\d+)-(W)(\d+)(?:-(\d))?/i.exec(date)) || ($ = /^([-+]?\d{4,})(W)(\d\d)(\d)?/i.exec(date))) {
if ($[2] === 'W')
time = { yw: toNumber($[1]), w: Number($[3]), dw: Number((_c = $[4]) !== null && _c !== void 0 ? _c : 1) };
else
time = { ywl: toNumber($[1]), wl: Number($[3]), dwl: Number((_d = $[4]) !== null && _d !== void 0 ? _d : 1) };
}
else if (($ = /^(\d+)-(\d+)/.exec(date)) || ($ = /^(\d{4})(\d{3})/.exec(date))) {
time = { y: toNumber($[1]), dy: Number($[2]) };
}
else {
$ = ['']; // Keep trying to parse as time-only string
time = {};
}
date = date.substr($[0].length).trim().replace(/^T\s*/i, '');
if (!date)
Object.assign(time, { hrs: 0, min: 0, sec: 0 });
else if (($ = /^(\d{1,2})(?::(\d{1,2}))(?::(?:(\d{1,2})(?:[.,](\d+))?))?(?=\D|$)/.exec(date)) ||
($ = /^(\d\d)(?:(\d\d)(?:(\d\d)(?:[.,](\d+))?)?)?(?=\D|$)/.exec(date))) {
Object.assign(time, {
hrs: Number($[1]), min: Number((_e = $[2]) !== null && _e !== void 0 ? _e : 0),
sec: Number((_f = $[3]) !== null && _f !== void 0 ? _f : 0), millis: Number(((_g = $[4]) !== null && _g !== void 0 ? _g : '0').padEnd(3, '0').substr(0, 3))
});
if ($[4] == null && time.millis === 0)
delete time.millis;
date = date.substr($[0].length).trim();
}
$ = /^([-+]\d\d(\d{4}|\d\d|:\d\d(:\d\d)?)?)$/i.exec(date);
if ($)
time.utcOffset = parseTimeOffset($[1]);
else if (date)
throw invalidDateTime;
const y = (_k = (_j = (_h = time.y) !== null && _h !== void 0 ? _h : time.yw) !== null && _j !== void 0 ? _j : time.ywl) !== null && _k !== void 0 ? _k : 0;
const m = (_l = time.m) !== null && _l !== void 0 ? _l : 1;
const w = (_o = (_m = time.w) !== null && _m !== void 0 ? _m : time.wl) !== null && _o !== void 0 ? _o : 1;
const d = (_p = time.d) !== null && _p !== void 0 ? _p : 1;
if (y < MIN_YEAR || y > MAX_YEAR)
throw new Error(`Invalid year: ${y}`);
else if (m > 13)
throw new Error(`Invalid month: ${m}`);
else if (w > 53)
throw new Error(`Invalid week: ${w}`);
else if (d > 32)
throw new Error(`Invalid day of month: ${d}`);
else if (time.hrs > 23)
throw new Error(`Invalid hour: ${time.hrs}`);
else if (time.min > 59)
throw new Error(`Invalid minute: ${time.min}`);
else if (time.sec > 59 + +allowLeapSecond)
throw new Error(`Invalid second: ${time.sec}`);
else if (time.utcOffset && (time.utcOffset < -57600 || time.utcOffset > 57600))
throw new Error(`Invalid UTC offset: ${$[1]}`);
if (time.m != null)
time.q = floor((time.m - 1) / 3) + 1;
return syncDateAndTime(time);
}
export function parseTimeOffset(offset, roundToMinutes = false) {
var _a;
let sign = 1;
if (offset.startsWith('-')) {
sign = -1;
offset = offset.substr(1);
}
else if (offset.startsWith('+'))
offset = offset.substr(1);
const parts = offset.includes(':') ?
offset.split(':') :
offset.match(/../g);
let offsetSeconds = 60 * (60 * Number(parts[0]) + Number((_a = parts[1]) !== null && _a !== void 0 ? _a : 0));
if (parts[2]) {
const seconds = Number(parts[2]);
if (roundToMinutes)
offsetSeconds += (seconds < 30 ? 0 : 60);
else
offsetSeconds += seconds;
}
return sign * offsetSeconds;
}
export function getDatePart(source, dateOrPart, partName) {
const parts = (source instanceof Intl.DateTimeFormat ? source.formatToParts(dateOrPart) : source);
partName = partName !== null && partName !== void 0 ? partName : dateOrPart;
const part = parts.find(part => part.type === partName);
if (part)
return part.value;
else
return '???';
}
export function getDateValue(source, dateOrPart, partName) {
return toNumber(convertDigitsToAscii(getDatePart(source, dateOrPart, partName)));
}
//# sourceMappingURL=common.js.map