UNPKG

edtf

Version:

Extended Date Time Format (EDTF) / ISO 8601-2 Parser and Library

268 lines (200 loc) 5.45 kB
import assert from './assert.js' import { Bitmask } from './bitmask.js' import { ExtDateTime } from './interface.js' import { mixin } from './mixin.js' import { format } from './format.js' const { abs } = Math const { isArray } = Array const P = new WeakMap() const U = new WeakMap() const A = new WeakMap() const X = new WeakMap() const PM = [Bitmask.YMD, Bitmask.Y, Bitmask.YM, Bitmask.YMD] export class Date extends globalThis.Date { constructor(...args) { // eslint-disable-line complexity let precision = 0 let uncertain, approximate, unspecified switch (args.length) { case 0: break case 1: switch (typeof args[0]) { case 'number': break case 'string': args = [Date.parse(args[0])] // eslint-disable-next-line no-fallthrough case 'object': if (isArray(args[0])) args[0] = { values: args[0] } { let obj = args[0] assert(obj != null) if (obj.type) assert.equal('Date', obj.type) if (obj.values && obj.values.length) { precision = obj.values.length args = obj.values.slice() // ECMA Date constructor needs at least two date parts! if (args.length < 2) args.push(0) if (obj.offset) { if (args.length < 3) args.push(1) while (args.length < 5) args.push(0) // ECMA Date constructor handles overflows so we // simply add the offset! args[4] = args[4] + obj.offset } args = [ExtDateTime.UTC(...args)] } ({ uncertain, approximate, unspecified } = obj) } break default: throw new RangeError('Invalid time value') } break default: precision = args.length } super(...args) this.precision = precision this.uncertain = uncertain this.approximate = approximate this.unspecified = unspecified } set precision(value) { P.set(this, (value > 3) ? 0 : Number(value)) } get precision() { return P.get(this) } set uncertain(value) { U.set(this, this.bits(value)) } get uncertain() { return U.get(this) } set approximate(value) { A.set(this, this.bits(value)) } get approximate() { return A.get(this) } set unspecified(value) { X.set(this, new Bitmask(value)) } get unspecified() { return X.get(this) } get atomic() { return !( this.precision || this.unspecified.value ) } get min() { // TODO uncertain and approximate if (this.unspecified.value && this.year < 0) { let values = this.unspecified.max(this.values.map(Date.pad)) values[0] = -values[0] return (new Date({ values })).getTime() } return this.getTime() } get max() { // TODO uncertain and approximate return (this.atomic) ? this.getTime() : this.next().getTime() - 1 } get year() { return this.getUTCFullYear() } get month() { return this.getUTCMonth() } get date() { return this.getUTCDate() } get hours() { return this.getUTCHours() } get minutes() { return this.getUTCMinutes() } get seconds() { return this.getUTCSeconds() } get values() { switch (this.precision) { case 1: return [this.year] case 2: return [this.year, this.month] case 3: return [this.year, this.month, this.date] default: return [ this.year, this.month, this.date, this.hours, this.minutes, this.seconds ] } } /** * Returns the next second, day, month, or year, depending on * the current date's precision. Uncertain, approximate and * unspecified masks are copied. */ next(k = 1) { let { values, unspecified, uncertain, approximate } = this if (unspecified.value) { let bc = values[0] < 0 values = (k < 0) ^ bc ? unspecified.min(values.map(Date.pad)) : unspecified.max(values.map(Date.pad)) if (bc) values[0] = -values[0] } values.push(values.pop() + k) return new Date({ values, unspecified, uncertain, approximate }) } prev(k = 1) { return this.next(-k) } *[Symbol.iterator]() { let cur = this while (cur <= this.max) { yield cur cur = cur.next() } } toEDTF() { if (!this.precision) return this.toISOString() let sign = (this.year < 0) ? '-' : '' let values = this.values.map(Date.pad) if (this.unspecified.value) return sign + this.unspecified.masks(values).join('-') if (this.uncertain.value) values = this.uncertain.marks(values, '?') if (this.approximate.value) { values = this.approximate.marks(values, '~') .map(value => value.replace(/(~\?)|(\?~)/, '%')) } return sign + values.join('-') } format(...args) { return format(this, ...args) } static pad(number, idx = 0) { if (!idx) { // idx 0 = year, 1 = month, ... let k = abs(number) if (k < 10) return `000${k}` if (k < 100) return `00${k}` if (k < 1000) return `0${k}` return `${k}` } if (idx === 1) number = number + 1 return (number < 10) ? `0${number}` : `${number}` } bits(value) { if (value === true) value = PM[this.precision] return new Bitmask(value) } } mixin(Date, ExtDateTime) export const pad = Date.pad