UNPKG

date-vir

Version:

Easy and explicit dates and times.

224 lines (223 loc) 6.72 kB
import { check } from '@augment-vir/assert'; import { stringify } from '@augment-vir/common'; import { DateUnit, oneIndexedDateUnits } from '@date-vir/duration'; import { defineShape, exact, isValidShape, or } from 'object-shape-tester'; import { createFullDate } from '../full-date/create-full-date.js'; import { toLuxonDateTime } from '../full-date/luxon-date-time-conversion.js'; import { calculateRelativeDate } from './calculate-relative-date.js'; import { diffDates } from './diff-dates.js'; /** * Get the {@link DateUnit} value of a {@link FullDate}. * * @category Calculation * @example * * ```ts * import {getDateUnit, DateUnit, utcTimezone} from 'date-vir'; * * getDateUnit( * { * year: 2024, * month: 11, * day: 7, * * hour: 12, * minute: 12, * second: 12, * millisecond: 12, * * timezone: utcTimezone, * }, * DateUnit.Week, * ); // outputs `45` * ``` */ export function getDateUnit(date, unit) { const luxonInstance = toLuxonDateTime(date); if (unit === DateUnit.Week) { return luxonInstance.weekNumber; } else { return luxonInstance[unit]; } } /** * Get the start date of a specific {@link DateUnit} based on a given {@link FullDate}. * * @category Calculation * @example * * ```ts * import {getStartDate, DateUnit, utcTimezone} from 'date-vir'; * * getStartDate( * { * year: 2024, * month: 11, * day: 7, * * hour: 12, * minute: 12, * second: 12, * millisecond: 12, * * timezone: utcTimezone, * }, * DateUnit.Month, * ); // outputs {year: 2024, month: 11, day: 1, hour: 0, minute: 0, second: 0, millisecond: 0, timezone: utcTimezone} * ``` */ export function getStartDate(date, unit) { /** * This package treats Sunday as the first day of the week, but Luxon treats Monday as the first * day of the week. */ if (unit === DateUnit.Week) { return calculateRelativeDate(createFullDate(toLuxonDateTime(calculateRelativeDate(date, { days: 1 })).startOf(unit), date.timezone), { days: -1, }); } else { return createFullDate(toLuxonDateTime(date).startOf(unit), date.timezone); } } /** * Get the end date of a specific {@link DateUnit} based on a given {@link FullDate}. * * @category Calculation * @example * * ```ts * import {getEndDate, DateUnit, utcTimezone} from 'date-vir'; * * getEndDate( * { * year: 2024, * month: 11, * day: 7, * * hour: 12, * minute: 12, * second: 12, * millisecond: 12, * * timezone: utcTimezone, * }, * DateUnit.Month, * ); // outputs {year: 2024, month: 11, day: 30, hour: 23, minute: 59, second: 59, millisecond: 999, timezone: utcTimezone} * ``` */ export function getEndDate(date, unit) { /** * This package treats Sunday as the first day of the week, but Luxon treats Monday as the first * day of the week. */ if (unit === DateUnit.Week) { return calculateRelativeDate(createFullDate(toLuxonDateTime(calculateRelativeDate(date, { days: 1 })).endOf(unit), date.timezone), { days: -1, }); } else { return createFullDate(toLuxonDateTime(date).endOf(unit), date.timezone); } } /** * Shape definition for all valid date position calculations. Used for {@link calculateDatePosition}. * * @category Internal */ export const datePositionCalculationShape = defineShape(or({ get: exact(DateUnit.Month), in: or(exact(DateUnit.Year), exact(DateUnit.Quarter)), }, { get: exact(DateUnit.Week), in: or(exact(DateUnit.Year), exact(DateUnit.Quarter), exact(DateUnit.Month)), }, { get: exact(DateUnit.Day), in: or(exact(DateUnit.Year), exact(DateUnit.Quarter), exact(DateUnit.Month), exact(DateUnit.Week)), }, { get: exact(DateUnit.Hour), in: or(exact(DateUnit.Year), exact(DateUnit.Quarter), exact(DateUnit.Month), exact(DateUnit.Week), exact(DateUnit.Day)), }, { get: exact(DateUnit.Minute), in: or(exact(DateUnit.Year), exact(DateUnit.Quarter), exact(DateUnit.Month), exact(DateUnit.Week), exact(DateUnit.Day), exact(DateUnit.Hour)), }, { get: exact(DateUnit.Second), in: or(exact(DateUnit.Year), exact(DateUnit.Quarter), exact(DateUnit.Month), exact(DateUnit.Week), exact(DateUnit.Day), exact(DateUnit.Hour), exact(DateUnit.Minute)), }, { get: exact(DateUnit.Millisecond), in: or(exact(DateUnit.Year), exact(DateUnit.Quarter), exact(DateUnit.Month), exact(DateUnit.Week), exact(DateUnit.Day), exact(DateUnit.Hour), exact(DateUnit.Minute), exact(DateUnit.Second)), })); /** * Calculate the position of the given date's "get" unit within the given "in" unit. * * @category Calculation * @example * * ```ts * import {calculateDatePosition, DateUnit} from 'date-vir'; * * calculateDatePosition( * { * year: 2020, * month: 4, * day: 5, * * hour: 0, * minute: 0, * second: 0, * millisecond: 0, * * timezone: utcTimezone, * }, * { * get: DateUnit.Week, * in: DateUnit.Year, * }, * ); * // result is ~14.14 (always round up for week in the year numbers, thus yielding week 15) * * calculateDatePosition( * { * year: 2020, * month: 4, * day: 5, * * hour: 0, * minute: 0, * second: 0, * millisecond: 0, * timezone: utcTimezone, * }, * { * get: DateUnit.Day, * in: DateUnit.Week, * }, * ); * // result is 0 (Sunday) * ``` */ export function calculateDatePosition(date, calculation) { if (!isValidShape(calculation, datePositionCalculationShape)) { throw new Error(`Invalid date position calculation for '${stringify(date)}': cannot get ${calculation.get} in ${calculation.in}.`); } const start = getStartDate(date, calculation.in); const startWithOffset = calculation.get === DateUnit.Week ? calculateRelativeDate(start, { days: calculateDatePosition(start, { get: DateUnit.Day, in: DateUnit.Week, }), }) : start; const diffUnit = `${calculation.get}s`; const diff = diffDates({ start: startWithOffset, end: date }, { [diffUnit]: true }); const value = diff[diffUnit]; const isDayOfWeek = calculation.get === DateUnit.Day && calculation.in === DateUnit.Week; if (!isDayOfWeek && check.isIn(calculation.get, oneIndexedDateUnits)) { return value + 1; } else { return value; } }