2024-02-01 05:13:09 +00:00
|
|
|
// This file is used to convert UTC timestamps from the server to human-readable local time in the browser.
|
|
|
|
//
|
|
|
|
// Usage: Add a time element with a `datetime` attribute and a `data-local-time` attribute to the HTML.
|
|
|
|
//
|
|
|
|
// `data-local-time` can be either 'relative' or 'date'.
|
|
|
|
// 'relative' will show the time in a human-readable format, e.g. "2 hours from now".
|
|
|
|
// 'date' will show the date in a human-readable format, e.g. "January 1, 2022".
|
|
|
|
// Any other value will be ignored and time will be shown as-is from the server as a UTC timestamp.
|
|
|
|
|
|
|
|
function pluralize(count: number, singular: string, plural: string): string {
|
|
|
|
return count === 1 ? singular : plural;
|
|
|
|
}
|
|
|
|
|
|
|
|
function formatRelativeTime(utcTime: Date): string {
|
|
|
|
const now = new Date();
|
|
|
|
const diffInSeconds = (utcTime.getTime() - now.getTime()) / 1000;
|
|
|
|
|
|
|
|
if (diffInSeconds < -86400) {
|
|
|
|
const days = Math.round(Math.abs(diffInSeconds) / 86400);
|
|
|
|
return `${days} ${pluralize(days, 'day', 'days')} ago`;
|
|
|
|
} else if (diffInSeconds < -3600) {
|
|
|
|
const hours = Math.round(Math.abs(diffInSeconds) / 3600);
|
|
|
|
return `${hours} ${pluralize(Math.abs(hours), 'hour', 'hours')} ago`;
|
|
|
|
} else if (diffInSeconds < -60) {
|
|
|
|
const minutes = Math.round(Math.abs(diffInSeconds) / 60);
|
|
|
|
return `${minutes} ${pluralize(minutes, 'minute', 'minutes')} ago`;
|
|
|
|
} else if (diffInSeconds < 0) {
|
|
|
|
const seconds = Math.abs(diffInSeconds);
|
|
|
|
return `${seconds} ${pluralize(seconds, 'second', 'seconds')} ago`;
|
|
|
|
} else if (diffInSeconds < 60) {
|
|
|
|
return `${diffInSeconds} ${pluralize(
|
|
|
|
diffInSeconds,
|
|
|
|
'second',
|
|
|
|
'seconds'
|
|
|
|
)} from now`;
|
|
|
|
} else if (diffInSeconds < 3600) {
|
|
|
|
const minutes = Math.round(diffInSeconds / 60);
|
|
|
|
return `${minutes} ${pluralize(minutes, 'minute', 'minutes')} from now`;
|
|
|
|
} else if (diffInSeconds < 86400) {
|
|
|
|
const hours = Math.round(diffInSeconds / 3600);
|
|
|
|
return `${hours} ${pluralize(hours, 'hour', 'hours')} from now`;
|
|
|
|
} else {
|
|
|
|
const days = Math.round(diffInSeconds / 86400);
|
|
|
|
return `${days} ${pluralize(days, 'day', 'days')} from now`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-01 05:05:06 +00:00
|
|
|
function convertTimeElements(): void {
|
2024-02-01 05:13:09 +00:00
|
|
|
const timeElements = document.querySelectorAll('time[data-local-time]');
|
2023-09-01 04:25:05 +00:00
|
|
|
timeElements.forEach((element) => {
|
2023-09-01 05:05:06 +00:00
|
|
|
const utcString = element.getAttribute('datetime');
|
|
|
|
if (utcString !== null) {
|
2023-09-01 04:25:05 +00:00
|
|
|
const utcTime = new Date(utcString);
|
2024-02-01 05:13:09 +00:00
|
|
|
const localTimeType = element.getAttribute('data-local-time');
|
|
|
|
if (localTimeType === 'relative') {
|
|
|
|
element.textContent = formatRelativeTime(utcTime);
|
|
|
|
} else if (localTimeType === 'date') {
|
|
|
|
element.textContent = utcTime.toLocaleDateString(
|
|
|
|
window.navigator.language,
|
|
|
|
{
|
|
|
|
year: 'numeric',
|
|
|
|
month: 'long',
|
|
|
|
day: 'numeric',
|
|
|
|
}
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
console.error(
|
|
|
|
'Unrecognized data-local-time attribute value on time[data-local-time] element. Local time cannot be displayed.',
|
|
|
|
element
|
|
|
|
);
|
|
|
|
}
|
2023-09-01 04:25:05 +00:00
|
|
|
} else {
|
2023-09-01 05:05:06 +00:00
|
|
|
console.error(
|
2024-02-01 05:13:09 +00:00
|
|
|
'Missing datetime attribute on time[data-local-time] element',
|
2023-09-01 05:05:06 +00:00
|
|
|
element
|
|
|
|
);
|
2023-09-01 04:25:05 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-09-01 05:05:06 +00:00
|
|
|
document.addEventListener('DOMContentLoaded', function () {
|
2023-09-01 04:25:05 +00:00
|
|
|
convertTimeElements();
|
|
|
|
});
|
|
|
|
|
2023-09-01 05:05:06 +00:00
|
|
|
document.body.addEventListener('htmx:afterSwap', function () {
|
2023-09-01 04:25:05 +00:00
|
|
|
convertTimeElements();
|
|
|
|
});
|