daet
Version:
Minimal immutable date class that supports relative time, calendar time, and plus/minus of different units.
287 lines (286 loc) • 11.3 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Week = exports.Day = exports.Hour = exports.Minute = exports.Second = exports.Millisecond = void 0;
const memo_1 = __importDefault(require("@bevry/memo"));
const start_of_week_1 = __importDefault(require("start-of-week"));
const startOfWeek = (0, start_of_week_1.default)();
const intl = __importStar(require("./intl.js"));
exports.Millisecond = 1;
exports.Second = exports.Millisecond * 1000;
exports.Minute = exports.Second * 60;
exports.Hour = exports.Minute * 60;
exports.Day = exports.Hour * 24;
exports.Week = exports.Day * 7;
/** A minimal immutable date class that supports relative time, calendar time, and plus/minus of different units. */
class Daet {
/** The tiers used for human relative date display. */
static get tiers() {
return [
{
// right now
limit: exports.Second,
refresh: exports.Second,
message: intl.rightNow,
},
{
// x seconds
limit: exports.Minute,
refresh: exports.Second,
message: intl.relativeDelta,
},
{
// x minutes
limit: exports.Hour,
refresh: exports.Minute,
message: intl.relativeDelta,
},
{
// x hours x minutes
limit: 12 * exports.Hour,
refresh: exports.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) {
/** Get the epoch time of this daet instance. */
this.getTime = (0, memo_1.default)(() => this.raw.getTime());
/** Return a new daet instance that is the start of the week proceeding that of this daet instance. */
this.startOfNextWeek = (0, memo_1.default)(() => {
// 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. */
this.endOfLastWeek = (0, memo_1.default)(() => {
// 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');
});
/** Return the ISO string for this daet instance. */
this.toISOString = (0, memo_1.default)(() => this.raw.toISOString());
/** Return the JSON string for this daet instance. */
this.toJSON = (0, memo_1.default)(() => this.raw.toJSON());
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 * exports.Second, 'millisecond');
}
case 'minute': {
return this.plus(value * exports.Minute, 'millisecond');
}
case 'hour': {
return this.plus(value * exports.Hour, 'millisecond');
}
case 'day': {
return this.plus(value * exports.Day, 'millisecond');
}
case 'week': {
return this.plus(value * exports.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 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);
}
/** 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;
}
}
exports.default = Daet;