Skip to content

Commit

Permalink
refactored to use Intl instead of date-fns #204
Browse files Browse the repository at this point in the history
  • Loading branch information
rizen committed Jan 12, 2025
1 parent ef0a7e7 commit a4289d7
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 49 deletions.
117 changes: 68 additions & 49 deletions app/utils/ving/dateTime.mjs
Original file line number Diff line number Diff line change
@@ -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")
Expand All @@ -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 });
Expand All @@ -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';
Expand All @@ -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);
}

/**
Expand All @@ -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
}
2 changes: 2 additions & 0 deletions ving/docs/change-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit a4289d7

Please sign in to comment.