UNPKG

@rr0/time

Version:
442 lines (405 loc) 10.3 kB
import { Level0DateParser } from "./Level0DateParser.mjs" import { EDTFValidator } from "../../calendar/index.mjs" import { Level0Year } from "../year/index.mjs" import { Level0Month } from "../month/index.mjs" import { Level0Day } from "../day/Level0Day.mjs" import { Level0Hour } from "../hour/index.mjs" import { Level0Minute } from "../minute/index.mjs" import { Level0Second } from "../second/index.mjs" import { Level0Timeshift } from "../timeshift/index.mjs" import { Level0DateRenderer } from "./Level0DateRenderer.mjs" import { level0Factory, Level0Factory } from "../Level0Factory.mjs" import { Level0Duration } from "../duration/index.mjs" import { Level0Component } from "../component/index.mjs" /** @import { Level0Component } from "../component/index.mjs" */ /** @import { EDTFParser } from "../../EDTFParser.mjs" */ /** * @typedef {Object} Level0DateSpec * @property {Level0Year|number} [year] * @property {Level0Month|number} [month] * @property {Level0Day|number} [day] * @property {Level0Hour|number} [hour] * @property {Level0Minute|number} [minute] * @property {Level0Second|number} [second] * @property {Level0Timeshift|number} [timeshift] */ /** * @template {Level0Year} [Y = Level0Year] * @template {Level0Month} [MM = Level0Month] * @template {Level0Day} [D = Level0Day] * @template {Level0Hour} [H = Level0Hour] * @template {Level0Minute} [M = Level0Minute] * @template {Level0Second} [S = Level0Second] * @template {Level0Timeshift} [Z = Level0Timeshift] */ export class Level0Date { /** * @type {Level0Year|undefined} */ #year /** * @template {Level0Month} MM=Level0Month * @type {MM|undefined} */ #month /** * @template D=Level0Day * @type D */ #day /** * @type {H|undefined} */ #hour /** * @type {M|undefined} */ #minute /** * @type {S|undefined} */ #second /** * @readonly * @type {Level0Timeshift} */ #timeshift /** * @param {Level0DateSpec} [spec] */ constructor(spec = { year: new Date().getFullYear(), month: new Date().getMonth(), day: new Date().getDate(), hour: new Date().getHours(), minute: new Date().getMinutes(), second: new Date().getSeconds(), timeshift: new Date().getTimezoneOffset() }) { this.year = spec.year this.month = spec.month this.day = spec.day this.hour = spec.hour this.minute = spec.minute this.second = spec.second this.timeshift = spec.timeshift } /** * @return {Y|undefined} */ get year() { return this.#year } /** * @param {Y|number|undefined} value */ set year(value) { const isNumber = typeof value === "number" if (this.#year && isNumber) { this.#year.value = value } else { this.#year = isNumber ? this.factory.newYear(value) : !value || value.start || value instanceof Level0Component ? value : this.factory.newYear(value) } } /** * @return {MM|undefined} */ get month() { return this.#month } /** * @param {MM|number|undefined} value */ set month(value) { const isNumber = typeof value === "number" if (this.#month && isNumber) { this.#month.value = value } else { this.#month = isNumber ? this.factory.newMonth(value) : !value || value.start || value instanceof Level0Component ? value : this.factory.newMonth(value) } } /** * @return {D|undefined} */ get day() { return this.#day } /** * @param {D|number|undefined} value */ set day(value) { const isNumber = typeof value === "number" if (this.#day && isNumber) { this.#day.value = value } else { this.#day = isNumber ? this.factory.newDay(value) : !value || value.start || value instanceof Level0Component ? value : this.factory.newDay(value) } } /** * @return {H|undefined} */ get hour() { return this.#hour } /** * @param {H|number|undefined} value */ set hour(value) { const isNumber = typeof value === "number" if (this.#hour && isNumber) { this.#hour.value = value } else { this.#hour = isNumber ? this.factory.newHour(value) : !value || value.start || value instanceof Level0Component ? value : this.factory.newHour(value) } } /** * @return {M||undefined} */ get minute() { return this.#minute } /** * @param {M|number|undefined} value */ set minute(value) { const isNumber = typeof value === "number" if (this.#minute && isNumber) { this.#minute.value = value } else { this.#minute = isNumber ? this.factory.newMinute(value) : !value || value.start || value instanceof Level0Component ? value : this.factory.newMinute(value) } } /** * @return {S|undefined} */ get second() { return this.#second } /** * @param {S|number|undefined} value */ set second(value) { const isNumber = typeof value === "number" if (this.#second && isNumber) { this.#second.value = value } else { this.#second = isNumber ? this.factory.newSecond(value) : !value || value.start || value instanceof Level0Component ? value : this.factory.newSecond(value) } } /** * @return {Level0Timeshift|undefined} */ get timeshift() { return this.#timeshift } /** * @param {Level0Timeshift|number|undefined} value */ set timeshift(value) { const isNumber = typeof value === "number" if (this.#timeshift && isNumber) { this.#timeshift.value = value } else { this.#timeshift = isNumber ? new Level0Timeshift(value) : value } } /** * @type {Level0Factory} */ get factory() { return level0Factory } /** * @param {string} spec * @param {EDTFParser} parser * @return {Level0Date} */ static fromString(spec, parser = new Level0DateParser()) { const { year, month, day, hour, minute, second, timeshift } = parser.parse(spec) return new Level0Date({ year, month, day, hour, minute, second, timeshift }) } /** * @param {Date} date * @return {Level0Date} */ static fromDate(date) { return new Level0Date({ year: date.getFullYear(), month: date.getMonth() + 1, day: date.getDate(), hour: date.getHours(), minute: date.getSeconds(), second: date.getSeconds(), timeshift: date.getTimezoneOffset() }) } /** * Creates a level 0 current date. * * @return {Level0Date} */ static newInstance() { const utcNow = new Date().toISOString() const millisPos = utcNow.indexOf(".") const edtfNow = utcNow.substring(0, millisPos) + utcNow.substring(millisPos + 4) return Level0Date.fromString(edtfNow) } /** * @protected * @param other */ #checkOtherType(other) { if (this.constructor.name !== other.constructor.name) { throw new EDTFValidator(`Date "${this.toString()}" cannot be compared with "${other.toString()}"`) } } /** * Return the difference between this date and a previous one. * * @param {Level0Date} other A date before. * @return {number} The difference, in milliseconds. */ compare(other) { this.#checkOtherType(other) return this.getTime() - other.getTime() } /** * Returns the duration between this date and a previous one. * Basically, it is a Duration built from #compare() * * @param {typeof this} other A date before. * @return {Level0Duration} */ delta(other) { const delta = this.compare(other) return new Level0Duration(delta) } /** * How many milliseconds since 0000-00-00T00:00:00. * * @return {number} */ getTime() { return (this.year?.duration || 0) + (this.month?.duration || 0) + (this.day?.duration || 0) + (this.hour?.duration || 0) + (this.minute?.duration || 0) + (this.second?.duration || 0) } /** * @param {typeof this} other * @return {boolean} */ isEqual(other) { return this.compare(other) === 0 } /** * @param {typeof this} other * @return {boolean} */ isBefore(other) { return this.compare(other) < 0 } /** * @param {typeof this} other * @return {boolean} */ isAfter(other) { return this.compare(other) > 0 } clone() { return new Level0Date(this.toSpec()) } previous() { let prevDate = this.clone() try { prevDate.second = this.second.previous() } catch (e) { try { prevDate.minute = this.minute.previous() } catch (e) { try { prevDate.hour = this.hour.previous() } catch (e) { try { prevDate.day = this.day.previous() } catch (e) { try { prevDate.month = this.month.previous() } catch (e) { prevDate.year = this.year.previous() } } } } } return prevDate } next() { let nextDate = this.clone() try { nextDate.second = this.second.next() } catch (e) { try { nextDate.minute = this.minute.next() } catch (e) { try { nextDate.hour = this.hour.next() } catch (e) { try { nextDate.day = this.day.next() } catch (e) { try { nextDate.month = this.month.next() } catch (e) { nextDate.year = this.year.next() } } } } } return nextDate } toString(renderer = Level0DateRenderer.instance) { return renderer.render(this) } /** * @return {Level0DateSpec} */ toSpec() { const spec = {} const year = this.year?.toSpec() if (year) { spec.year = year } const month = this.month?.toSpec() if (month) { spec.month = month } const day = this.day?.toSpec() if (day) { spec.day = day } const hour = this.hour?.toSpec() if (hour) { spec.hour = hour } const minute = this.minute?.toSpec() if (minute) { spec.minute = minute } const second = this.second?.toSpec() if (second) { spec.second = second } const timeshift = this.timeshift?.toSpec() if (timeshift) { spec.timeshift = timeshift } return spec } toJSON() { return this.toSpec() } }