UNPKG

@tubular/time

Version:

Date/time, IANA timezones, leap seconds, TAI/UTC conversions, calendar with settable Julian/Gregorian switchover

197 lines 9.03 kB
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