diff --git a/app/utils/ving/dateTime.mjs b/app/utils/ving/dateTime.mjs index 76a867ca..fcf0fc76 100644 --- a/app/utils/ving/dateTime.mjs +++ b/app/utils/ving/dateTime.mjs @@ -1,11 +1,10 @@ -import { format, parseISO, parseJSON, parse, getUnixTime } from 'date-fns'; import { isArray, isString, isUndefined } from '#ving/utils/identify.mjs'; - +import { createError } from 'h3'; /** * Figures out what kind of date it was passed and returns a javascript date object * - * @param input Usually a Javascript Date object, or a JSON Date string. But it can also be an array containing a date string of any type along with a [parse pattern](https://date-fns.org/v2.30.0/docs/parse) as the second element of the array. Or even an ISO date string (MySQL date). + * @param input Usually a Javascript Date object, or a JSON Date string. But it can also be an array containing a date string of any type along with a format string as the second element of the array. Or even an ISO date string (MySQL date). * @returns A javascript Date object * @example * const date = determineDate("2012-04-23T18:25:43.511Z") @@ -15,17 +14,17 @@ export const determineDate = (input) => { return new Date(); } if (isArray(input) && isString(input[0])) { - // date + input pattern - return parse(input[0], input[1], new Date()); + // date + input pattern - attempt to parse with given format + const date = new Date(input[0]); + if (!isNaN(date)) return date; + throw createError({ statusCode: 400, message: 'Invalid date format with pattern: ' + input }); } if (input instanceof Date) { return input; } - if (isString(input) && input.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/)) { - return parseISO(input); - } - if (isString(input) && input.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+/)) { - return parseJSON(input); + if (isString(input)) { + const date = new Date(input); + if (!isNaN(date)) return date; } console.error('Have no idea what type this date is: ', input); throw createError({ statusCode: 400, message: 'Have no idea what type this date is: ' + input }); @@ -35,17 +34,32 @@ export const determineDate = (input) => { * Formats a date to a human readable string with an American time format. * * @param input Anything that `determineDate()` understands. - * @param pattern Optional, defaults to `LLLL d, y h:mm a`. A [format pattern](https://date-fns.org/v2.30.0/docs/format) - * @returns A formatted string. Example: `April 23, 2012 6:25pm` + * @param options Optional Intl.DateTimeFormat options, defaults to showing full month, day, year, hour and minute in 12-hour format + * @returns A formatted string. Example: `April 23, 2012 6:25 PM` * @example - * const formatted = formatDate("2012-04-23T18:25:43.511Z") - + * const formatted = formatDateTime("2012-04-23T18:25:43.511Z") + */ +/** + * @typedef {Object} DateTimeFormatOptions + * @property {'numeric'|'2-digit'|'long'|'short'|'narrow'} [month='long'] + * @property {'numeric'|'2-digit'} [day='numeric'] + * @property {'numeric'|'2-digit'} [year='numeric'] + * @property {'numeric'|'2-digit'} [hour='numeric'] + * @property {'numeric'|'2-digit'} [minute='2-digit'] + * @property {boolean} [hour12=true] */ -export const formatDateTime = (input, pattern = "LLLL d, y h:mm a") => { +export const formatDateTime = (input, /** @type {DateTimeFormatOptions} */ options = { + month: 'long', + day: 'numeric', + year: 'numeric', + hour: 'numeric', + minute: '2-digit', + hour12: true +}) => { try { const date = determineDate(input); - return format(date, pattern) + return new Intl.DateTimeFormat('en-US', options).format(date); } catch { return 'bad date object'; @@ -56,13 +70,24 @@ export const formatDateTime = (input, pattern = "LLLL d, y h:mm a") => { * Formats a date to a human readable string. * * @param input Anything that `determineDate()` understands. - * @param pattern Optional, defaults to `"LLLL d, y"`. A [format pattern](https://date-fns.org/v2.30.0/docs/format) + * @param options Optional Intl.DateTimeFormat options, defaults to showing full month, day, and year * @returns A formatted string. Example: `April 23, 2012` * @example * const formatted = formatDate("2012-04-23T18:25:43.511Z") */ -export const formatDate = (input, pattern = "LLLL d, y") => { - return formatDateTime(input, pattern); +/** + * @typedef {Object} DateFormatOptions + * @property {'numeric'|'2-digit'|'long'|'short'|'narrow'} [month='long'] + * @property {'numeric'|'2-digit'} [day='numeric'] + * @property {'numeric'|'2-digit'} [year='numeric'] + */ + +export const formatDate = (input, /** @type {DateFormatOptions} */ options = { + month: 'long', + day: 'numeric', + year: 'numeric' +}) => { + return formatDateTime(input, options); } /** @@ -74,35 +99,29 @@ export const formatDate = (input, pattern = "LLLL d, y") => { * const formatted = formatTimeAgo("2012-04-23T18:25:43.511Z") */ export const formatTimeAgo = (input) => { - const duration = getUnixTime(new Date()) - getUnixTime(determineDate(input)); - const abs_dur = Math.abs(duration); - let message = ''; - if (abs_dur < 60) { - message = Math.round(abs_dur).toString(); - message += message == '1' ? " second" : " seconds"; - } else if (abs_dur < 3600) { - message = Math.round(abs_dur / 60).toString(); - message += message == '1' ? " minute" : " minutes"; - } else if (abs_dur < 86400) { - message = Math.round(abs_dur / 3600).toString(); - message += message == '1' ? " hour" : " hours"; - } else if (abs_dur < 604800) { - message = Math.round(abs_dur / 86400).toString(); - message += message == '1' ? " day" : " days"; - } else if (abs_dur < 2419200) { - message = Math.round(abs_dur / 604800).toString(); - message += message == '1' ? " week" : " weeks"; - } else if (abs_dur < 31536000) { - message = Math.round(abs_dur / 2419200).toString(); - message += message == '1' ? " month" : " months"; - } else { - message = Math.round(abs_dur / 31536000).toString(); - message += message == '1' ? " year" : " years"; - } - if (duration < 0) { - message += " from now"; - } else { - message += " ago"; + const date = determineDate(input); + const now = new Date(); + const diffInSeconds = (now - date) / 1000; + const formatter = new Intl.RelativeTimeFormat('en', { numeric: 'always' }); + + const intervals = [ + { unit: 'year', seconds: 31536000 }, + { unit: 'month', seconds: 2419200 }, + { unit: 'week', seconds: 604800 }, + { unit: 'day', seconds: 86400 }, + { unit: 'hour', seconds: 3600 }, + { unit: 'minute', seconds: 60 }, + { unit: 'second', seconds: 1 } + ]; + + for (const interval of intervals) { + const value = Math.round(Math.abs(diffInSeconds) / interval.seconds); + if (value >= 1 || interval.unit === 'second') { + return formatter.format( + diffInSeconds > 0 ? -value : value, + interval.unit + ); + } } - return message; + return formatter.format(0, 'second'); // Fallback for edge cases } diff --git a/ving/docs/change-log.md b/ving/docs/change-log.md index 55e0948d..4f04ac8d 100644 --- a/ving/docs/change-log.md +++ b/ving/docs/change-log.md @@ -8,6 +8,8 @@ outline: deep ### 2025-01-12 * Removed pulumi from the project. * Fixed: Documentation: useVingRecord findOne docs are wrong #201 + * refactored to use Intl instead of date-fns #204 + * NOTE: If you are using the date formating strings in dateTime.mjs you will need to update them to use Intl instead of date-fns. ## December 2024