shared/dates.ts (166 lines of code) (raw):

import { format, parse } from 'date-fns'; import type { DiscountPeriodType } from '@/client/utilities/discountPreview'; import { appendCorrectPluralisation } from './generalTypes'; import { number2words } from './numberUtils'; export const DATE_FNS_INPUT_FORMAT = 'yyyy-MM-dd'; // example: 1969-07-16 export const DATE_FNS_LONG_OUTPUT_FORMAT = 'd MMMM yyyy'; // example: 1 July 2021 export const DATE_FNS_SHORT_OUTPUT_FORMAT = 'd MMM yyyy'; // example: 5 Jan 2019 export const cancellationFormatDate = ( cancellationEffectiveDate?: string, outputFormat: string = DATE_FNS_SHORT_OUTPUT_FORMAT, ) => cancellationEffectiveDate === undefined ? 'today' : parseDate(cancellationEffectiveDate).dateStr(outputFormat); export interface ParsedDate { date: Date; dateStr: (outputFormat?: string) => string; isBefore: (comparisonDate: Date) => boolean; isSameOrAfter: (comparisonDate: Date) => boolean; isSame: (comparisonDate: Date) => boolean; isLeapYear: boolean; } export const parseDate = ( inputDateStr?: string, dateInputFormat: string = DATE_FNS_INPUT_FORMAT, ): ParsedDate => { const dateObject = inputDateStr ? parse(inputDateStr, dateInputFormat, new Date()) : new Date(); return { date: dateObject, dateStr: (outputFormat = DATE_FNS_SHORT_OUTPUT_FORMAT) => dateString(dateObject, outputFormat), isBefore: (comparisonDate) => dateIsBefore(dateObject, comparisonDate), isSameOrAfter: (comparisonDate) => dateIsSameOrAfter(dateObject, comparisonDate), isSame: (comparisonDate) => dateIsSame(dateObject, comparisonDate), isLeapYear: dateIsLeapYear(dateObject), }; }; export const dateString = ( inputDate: Date, outputFormat: string = DATE_FNS_SHORT_OUTPUT_FORMAT, ) => format(inputDate, outputFormat); export const dateIsBefore = (inputDate: Date, comparisonDate: Date) => inputDate.valueOf() < comparisonDate.valueOf(); export const dateIsSameOrBefore = (inputDate: Date, comparisonDate: Date) => inputDate.valueOf() <= comparisonDate.valueOf(); export const dateIsAfter = (inputDate: Date, comparisonDate: Date) => inputDate.valueOf() > comparisonDate.valueOf(); export const dateIsSameOrAfter = (inputDate: Date, comparisonDate: Date) => inputDate.valueOf() >= comparisonDate.valueOf(); export const dateIsSame = (inputDate: Date, comparisonDate: Date) => inputDate.valueOf() === comparisonDate.valueOf(); export const dateClone = (inputDate: Date) => new Date(inputDate.valueOf()); export const dateIsLeapYear = (inputDate: Date) => { const year = inputDate.getFullYear(); return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; }; export const dateAddDays = (inputDate: Date, daysModifier: number) => { const modifiedDate = new Date(inputDate.valueOf()); modifiedDate.setDate(modifiedDate.getDate() + daysModifier); return modifiedDate; }; export const dateAddMonths = (inputDate: Date, monthsModifier: number) => { const modifiedDate = new Date(inputDate.valueOf()); modifiedDate.setMonth(modifiedDate.getMonth() + monthsModifier); return modifiedDate; }; export const dateAddYears = (inputDate: Date, yearsModifier: number) => { const modifiedDate = new Date(inputDate.valueOf()); modifiedDate.setFullYear(modifiedDate.getFullYear() + yearsModifier); return modifiedDate; }; export const numberOfDaysInMonth = (inputDate: Date) => new Date(inputDate.getFullYear(), inputDate.getMonth() + 1, 0).getDate(); export const getWeekDay = (inputDate: Date) => new Intl.DateTimeFormat('en-US', { weekday: 'long' }).format(inputDate); export interface DateRange { start: Date; end: Date; } export const dateRange = ( startDate: string | Date, endDate: string | Date, dateInputFormat: string = DATE_FNS_INPUT_FORMAT, ): DateRange => { const start = startDate instanceof Date ? startDate : parse(startDate, dateInputFormat, new Date()); const end = endDate instanceof Date ? endDate : parse(endDate, dateInputFormat, new Date()); return { start, end, }; }; export interface DateStates { state: string; range: DateRange; } export const isDateBetweenRange = ( date: Date, rangeStart: Date, rangeEnd: Date, ) => date.valueOf() >= rangeStart.valueOf() && date.valueOf() <= rangeEnd.valueOf(); export const getOldestDate = (dates: Date[]) => dates.reduce((dateA: Date, dateB: Date) => dateA.valueOf() <= dateB.valueOf() ? dateA : dateB, ); export function convertTimestampToDate(timestamp: number): string { return dateString(new Date(timestamp), DATE_FNS_LONG_OUTPUT_FORMAT); } interface OrderdTimePeriod { peroidName: 'day' | 'week' | 'month' | 'quarter' | 'year'; higherPeriod?: string; unitsToSingularHigherPeriod?: number; } export const getAppropriateReadableTimePeriod = ( unit: number, periodType: DiscountPeriodType, ) => { const orderdTimePeriods: OrderdTimePeriod[] = [ { peroidName: 'year' }, { peroidName: 'quarter', higherPeriod: 'year', unitsToSingularHigherPeriod: 4, }, { peroidName: 'month', higherPeriod: 'year', unitsToSingularHigherPeriod: 12, }, { peroidName: 'week', higherPeriod: 'month', unitsToSingularHigherPeriod: 4, }, { peroidName: 'day', higherPeriod: 'week', unitsToSingularHigherPeriod: 7, }, ]; const periodTypeSingularLowerCase = periodTypeToSingular(periodType).toLowerCase(); const periodTypeInComparisonTimePeriods = orderdTimePeriods.find( (element) => element.peroidName === periodTypeSingularLowerCase, ); if (!periodTypeInComparisonTimePeriods) { return `${number2words(unit)} ${periodType}`; } // is there a higher applicable time period eg week instead of day // and is the unit a multiple of the number of units it takes to make // a singular unit of the higher time period if ( periodTypeInComparisonTimePeriods.higherPeriod && periodTypeInComparisonTimePeriods.unitsToSingularHigherPeriod && unit % periodTypeInComparisonTimePeriods.unitsToSingularHigherPeriod === 0 ) { const numberOfHigherPeriods = unit / periodTypeInComparisonTimePeriods.unitsToSingularHigherPeriod; return `${number2words( numberOfHigherPeriods, )} ${appendCorrectPluralisation( periodTypeInComparisonTimePeriods.higherPeriod, numberOfHigherPeriods, )}`; } else { return `${number2words(unit)} ${periodType}`; } }; export const periodTypeToSingular = (periodType: string) => { return periodType.endsWith('s') ? periodType.substring(0, periodType.length - 1) : periodType; };