UNPKG

daet

Version:

Minimal immutable date class that supports relative time, calendar time, and plus/minus of different units.

259 lines (258 loc) 9.61 kB
import memo from '@bevry/memo'; import getSW from 'start-of-week'; const startOfWeek = getSW(); import * as intl from './intl.js'; export const Millisecond = 1; export const Second = Millisecond * 1000; export const Minute = Second * 60; export const Hour = Minute * 60; export const Day = Hour * 24; export const Week = Day * 7; /** A minimal immutable date class that supports relative time, calendar time, and plus/minus of different units. */ export default class Daet { /** The raw date object behind this daet instance. */ raw; /** The tiers used for human relative date display. */ static get tiers() { return [ { // right now limit: Second, refresh: Second, message: intl.rightNow, }, { // x seconds limit: Minute, refresh: Second, message: intl.relativeDelta, }, { // x minutes limit: Hour, refresh: Minute, message: intl.relativeDelta, }, { // x hours x minutes limit: 12 * Hour, refresh: Minute, message: intl.relativeDelta, }, { // later/earlier today when: ({ past }) => past ? new Daet().reset('hour').getTime() : new Daet().plus(1, 'day').reset('hour').getTime(), message: intl.earlierOrLaterToday, }, { // yesterday or tomorrow when: ({ past }) => past ? new Daet().minus(1, 'day').reset('hour').getTime() : new Daet().plus(2, 'day').reset('hour').getTime(), message: intl.yesterdayOrTommorow, }, { // this week when: ({ past }) => past ? new Daet().endOfLastWeek().getTime() : new Daet().startOfNextWeek().getTime(), message: intl.relativeThisWeek, }, { // next or last week when: ({ past }) => past ? new Daet().endOfLastWeek().endOfLastWeek().getTime() : new Daet().startOfNextWeek().startOfNextWeek().getTime(), message: intl.relativeSecondWeek, }, { // earlier or later limit: Infinity, message: intl.earlierOrLater, }, ]; } /** Create a new daet instance based on the input */ static create(input) { return new this(input); } /** Create a new daet instance based on the input */ constructor(input) { this.raw = input instanceof Daet ? new Date(input.raw) : input ? new Date(input) : new Date(); this.raw.setMilliseconds(0); } /** Return a new daet instance that is in the past by the value amount. */ minus(value, unit) { return this.plus(value * -1, unit); } /** Return a new daet instance that is in the future by the value amount. */ plus(value, unit) { switch (unit) { case 'millisecond': { const next = new Date(this.raw.getTime() + value); return new Daet(next); } case 'second': { return this.plus(value * Second, 'millisecond'); } case 'minute': { return this.plus(value * Minute, 'millisecond'); } case 'hour': { return this.plus(value * Hour, 'millisecond'); } case 'day': { return this.plus(value * Day, 'millisecond'); } case 'week': { return this.plus(value * Week, 'millisecond'); } default: // https://basarat.gitbooks.io/typescript/docs/types/discriminated-unions.html const neverCheck = unit; throw new Error('unknown unit'); } } /** Clone this daet instance based on its raw value. */ rawClone() { return new Date(this.raw); } /** Clone this daet instance. */ clone() { return new Daet(this); } /** Return a new daet instance that has the unit set to the desired value. */ set(value, unit) { switch (unit) { case 'millisecond': { const next = this.rawClone().setMilliseconds(value); return new Daet(next); } case 'second': { const next = this.rawClone().setSeconds(value); return new Daet(next); } case 'minute': { const next = this.rawClone().setMinutes(value); return new Daet(next); } case 'hour': { const next = this.rawClone().setHours(value); return new Daet(next); } default: // https://basarat.gitbooks.io/typescript/docs/types/discriminated-unions.html const neverCheck = unit; throw new Error('unknown unit'); } } /** Return a new daet instance that has the desired unit reset to 0. */ reset(unit) { switch (unit) { case 'millisecond': { const next = this.rawClone().setMilliseconds(0); return new Daet(next); } case 'second': { const next = this.rawClone().setSeconds(0, 0); return new Daet(next); } case 'minute': { const next = this.rawClone().setMinutes(0, 0, 0); return new Daet(next); } case 'hour': { const next = this.rawClone().setHours(0, 0, 0, 0); return new Daet(next); } default: // https://basarat.gitbooks.io/typescript/docs/types/discriminated-unions.html const neverCheck = unit; throw new Error('unknown unit'); } } /** Get the epoch time of this daet instance. */ getTime = memo(() => this.raw.getTime()); /** Get the milliseconds from the passed daet instance to this daet instance. */ getMillisecondsFrom(from) { const now = from.getTime(); const time = this.getTime(); return time - now; } /** Get the milliseconds from now to this daet instance */ getMillisecondsFromNow() { return this.getMillisecondsFrom(new Daet()); } /** Use https://devdocs.io/javascript/global_objects/datetimeformat to format our daet instance. */ format(locale, options) { return new Intl.DateTimeFormat(locale, options).format(this.raw); } /** Return a new daet instance that is the start of the week proceeding that of this daet instance. */ startOfNextWeek = memo(() => { // continue until we become the start of next week let latest = this.plus(1, 'day'); while (latest.raw.getDay() !== startOfWeek) { latest = latest.plus(1, 'day'); } // reset the time, so we become the start time of that week return latest.reset('hour'); }); /** Return a new daet instance that is the end of the week preceeding that of this daet instance. */ endOfLastWeek = memo(() => { // continue until we become the start of this week let latest = this; while (latest.raw.getDay() !== startOfWeek) { latest = latest.minus(1, 'day'); } // reset the time, then minus a second, so we become the end of the prior week return latest.reset('hour').minus(1, 'second'); }); /** Get the human absolute date display for this daet instance. */ calendar() { return this.format('en', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric', }); } /** Return the human relative display from now, as well as the millisecond delta before a refresh is needed. */ fromNowDetails() { const now = new Daet(); const nowTime = now.getTime(); const eventTime = this.getTime(); const past = nowTime > eventTime; const delta = Math.abs(eventTime - nowTime); const tiers = Daet.tiers; let lastTierDelta = 0; for (const tier of tiers) { const limit = tier.limit; const when = tier.when ? tier.when({ past }) : 0; const tierDelta = limit || Math.abs(when - nowTime); if (delta < tierDelta) { const message = tier.message({ past, delta, when: this }); const refresh = tier.refresh || delta - lastTierDelta; return { message, refresh }; } lastTierDelta = tierDelta; } throw new Error('no tier matched the input delta'); } /** Return the human relative display from now. */ fromNow() { return this.fromNowDetails().message; } /** Return the ISO string for this daet instance. */ toISOString = memo(() => this.raw.toISOString()); /** Return the JSON string for this daet instance. */ toJSON = memo(() => this.raw.toJSON()); }