UNPKG

shelving

Version:

Toolkit for using data in JavaScript.

225 lines (224 loc) 9.89 kB
import { RequiredError } from "../error/RequiredError.js"; /** * Is a value a valid date? * - Note: `Date` instances can be invalid (e.g. `new Date("blah blah").getTime()` returns `NaN`). These are detected and will always return `false` */ export function isDate(value) { return value instanceof Date && Number.isFinite(value.getTime()); } /** Assert that a value is a `Date` instance. */ export function assertDate(value, caller = assertDate) { if (!isDate(value)) throw new RequiredError("Must be valid date", { received: value, caller }); } /** * Convert an unknown value to a valid `Date` instance, or return `undefined` if it couldn't be converted. * - Note: `Date` instances can be invalid (e.g. `new Date("blah blah").getTime()` returns `NaN`). These are detected and will always return `null` * * @param value Any value that we want to parse as a valid date (defaults to `undefined`). * - `Date` instance returns unchanged (BUT if the date isn't valid, `undefined` is returned). * - `null` or `undefined` or `""` empty string returns `undefined` * - The string `"now"` returns the current date (e.g. `new Date()`). * - The string `"today"` returns the current date at midnight (e.g. `getMidnight()`). * - The string `"tomorrow"` returns tomorrow's date at midnight (e.g. `addDays(getMidnight(), 1)`). * - The string `"yesterday"` returns yesterday's date at midnight (e.g. `addDays(getMidnight(), 1)`). * - Date strings (e.g. `"2003-09-12"` or `"2003 feb 20:09"`) return the corresponding date (using the user's current locale). * - Time strings (e.g. `"18:32"` or `"23:59:59.999"`) return today's date at that time (using the user's current locale). * - Numbers are return the corresponding date (using `new Date(number)`, i.e. milliseconds since 01/01/1970). * - Anything else returns `undefined` * * @returns `Date` instance if the value could be converted to a valid date, and `null` if not. */ export function getDate(value) { if (value === "now") return getNow(); if (value === "yesterday") return getYesterday(); if (value === "today") return getToday(); if (value === "tomorrow") return getTomorrow(); if (isDate(value)) return value; if (typeof value === "string" || typeof value === "number") { const date = new Date(value); if (Number.isFinite(date.getTime())) return date; const time = new Date(`${requireDateString()}T${value}`); if (Number.isFinite(time.getTime())) return time; } } /** Get a date representing this exact moment. */ export function getNow() { return new Date(); } /** Get a date representing midnight of the previous day. */ export function getYesterday() { const date = new Date(); date.setHours(0, 0, 0, 0); date.setDate(date.getDate() - 1); return date; } /** Get a date representing midnight of the current day. */ export function getToday() { const date = new Date(); date.setHours(0, 0, 0, 0); return date; } /** Get a date representing midnight of the next day. */ export function getTomorrow() { const date = new Date(); date.setHours(0, 0, 0, 0); date.setDate(date.getDate() + 1); return date; } /** Get a Date representing exactly midnight of the specified date. */ export function getMidnight(target, caller = getMidnight) { const date = new Date(requireDate(target, caller)); date.setHours(0, 0, 0, 0); return date; } /** Get a Date representing midnight on Monday of the specified week. */ export function getMonday(target, caller = getMonday) { const date = getMidnight(target, caller); const day = date.getDay(); if (day === 0) date.setDate(date.getDate() - 6); else if (day !== 1) date.setDate(date.getDate() - (day - 1)); return date; } /** Get a Date representing the first day of the specified month. */ export function getMonthStart(target, caller = getMonthStart) { const date = getMidnight(target, caller); date.setDate(1); return date; } /** * Convert a possible date to a `Date` instance, or throw `RequiredError` if it couldn't be converted. * @param value Any value that we want to parse as a valid date (defaults to `"now"`). */ export function requireDate(value = "now", caller = requireDate) { const date = getDate(value); assertDate(date, caller); return date; } /** Convert an unknown value to a timestamp (milliseconds past Unix epoch), or `undefined` if it couldn't be converted. */ export function getTimestamp(value) { return getDate(value)?.getTime(); } /** Convert a possible date to a timestamp (milliseconds past Unix epoch), or throw `RequiredError` if it couldn't be converted. */ export function requireTimestamp(value) { return requireDate(value, requireTimestamp).getTime(); } // Helpers. function _pad(num, length = 2) { return num.toString().padStart(length, "0"); } function _date(date) { return `${_pad(date.getFullYear(), 4)}-${_pad(date.getMonth() + 1)}-${_pad(date.getDate())}`; } function _time(date) { return `${_pad(date.getHours())}:${_pad(date.getMinutes())}:${_pad(date.getSeconds())}`; } function _datetime(date) { return `${_date(date)}T${_time(date)}`; } /** Convert an unknown value to a local date string like "2015-09-12T18:30:00", or `undefined` if it couldn't be converted. */ export function getDateTimeString(value) { const date = getDate(value); if (date) return _datetime(date); } /** Convert a possible `Date` instance to a local YMD string like "2015-09-12T18:30:00", or throw `RequiredError` if it couldn't be converted. */ export function requireDateTimeString(value, caller = requireDateTimeString) { return _datetime(requireDate(value, caller)); } /** Convert an unknown value to a local date string like "2015-09-12", or `undefined` if it couldn't be converted. */ export function getDateString(value) { const date = getDate(value); if (date) return _date(date); } /** Convert a possible `Date` instance to a local date string like "2015-09-12", or throw `RequiredError` if it couldn't be converted. */ export function requireDateString(value, caller = requireDateString) { return _date(requireDate(value, caller)); } /** Convert an unknown value to a local time string like "18:32:00", or `undefined` if it couldn't be converted. */ export function getTimeString(value) { const date = getDate(value); if (date) return _time(date); } /** Convert a possible `Date` instance to local time string like "18:32:00", or throw `RequiredError` if it couldn't be converted. */ export function requireTimeString(value, caller = requireTimeString) { return _time(requireDate(value, caller)); } /** List of day-of-week strings. */ export const DAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; /** Convert a `Date` instance to a day-of-week string like "Monday" */ export function getDay(target) { return DAYS[requireDate(target, getDay).getDay()]; } /** * Return a new date that increase or decreases the month based on an input date. * - February 29th is a special cased and is _rounded down_ to February 28th on non-leap years. */ export function addYears(change, target, caller = addYears) { const input = requireDate(target, caller); const output = new Date(input); output.setFullYear(output.getFullYear() + change); if (input.getMonth() !== output.getMonth()) output.setDate(0); // Handle February 29th case. return output; } /** * Return a new date that increase or decreases the month based on an input date. * - Note that with Javascript "rollover" semantics, adding a month when we're on e.g. 31st of August would normally roll _past_ September and return 1st October. * - To avoid this we clamp the date to the end of the month if rollover happens. */ export function addMonths(change, target, caller = addMonths) { const input = requireDate(target, caller); const output = new Date(input); output.setMonth(output.getMonth() + change); if (input.getMonth() !== output.getMonth() + change) output.setDate(0); // Handle 31st rollover case. return output; } /** Return a new date that increase or decreases the week based on an input date. */ export function addWeeks(change, target, caller = addWeeks) { const date = new Date(requireDate(target, caller)); date.setDate(date.getDate() + change * 7); return date; } /** Return a new date that increase or decreases the day based on an input date. */ export function addDays(change, target, caller = addDays) { const date = new Date(requireDate(target, caller)); date.setDate(date.getDate() + change); return date; } /** Return a new date that increase or decreases the hour based on an input date. */ export function addHours(change, target, caller = addHours) { const date = new Date(requireDate(target, caller)); date.setHours(date.getHours() + change); return date; } /** Return a new date that increase or decreases the minute based on an input date. */ export function addMinutes(change, target, caller = addMinutes) { const date = new Date(requireDate(target, caller)); date.setMinutes(date.getMinutes() + change); return date; } /** Return a new date that increase or decreases the minute based on an input date. */ export function addSeconds(change, target, caller = addSeconds) { const date = new Date(requireDate(target, caller)); date.setSeconds(date.getSeconds() + change); return date; } /** Return a new date that increase or decreases the minute based on an input date. */ export function addMilliseconds(change, target, caller = addMilliseconds) { const date = new Date(requireDate(target, caller)); date.setMilliseconds(date.getMilliseconds() + change); return date; }