Files
gochat/frontend/app/javascript/shared/helpers/timeHelper.js
T
2026-08-24 10:44:23 +08:00

178 lines
6.2 KiB
JavaScript

import {
format,
isSameYear,
fromUnixTime,
formatDistanceToNow,
differenceInDays,
} from 'date-fns';
// Numeric realtime timestamps below 1e10 are Unix seconds (through 2286-11-20);
// larger values are milliseconds, bounded by the ECMAScript Date range.
const UNIX_SECONDS_LIMIT = 1e10;
const DATE_MILLISECONDS_LIMIT = 8.64e15;
const normalizeTimestamp = value => {
const numericValue =
typeof value === 'string' && value.trim() ? Number(value) : value;
if (typeof numericValue === 'number' && Number.isFinite(numericValue)) {
const timestamp =
Math.abs(numericValue) < UNIX_SECONDS_LIMIT
? numericValue * 1000
: numericValue;
return Math.abs(timestamp) <= DATE_MILLISECONDS_LIMIT ? timestamp : null;
}
if (typeof value !== 'string') return null;
const timestamp = Date.parse(value);
return Number.isNaN(timestamp) ? null : timestamp;
};
export const isNewerTimestamp = (currentTimestamp, nextTimestamp) => {
if (currentTimestamp == null || nextTimestamp == null) return true;
const next = normalizeTimestamp(nextTimestamp);
if (next === null) return false;
const current = normalizeTimestamp(currentTimestamp);
return current === null || next > current;
};
/**
* Formats a Unix timestamp into a human-readable time format.
* @param {number} time - Unix timestamp.
* @param {string} [dateFormat='h:mm a'] - Desired format of the time.
* @returns {string} Formatted time string.
*/
export const messageStamp = (time, dateFormat = 'h:mm a') => {
const unixTime = fromUnixTime(time);
return format(unixTime, dateFormat);
};
/**
* Provides a formatted timestamp, adjusting the format based on the current year.
* @param {number} time - Unix timestamp.
* @param {string} [dateFormat='MMM d, yyyy'] - Desired date format.
* @param {object} [options] - date-fns options and a different-year fallback format.
* @returns {string} Formatted date string.
*/
export const messageTimestamp = (
time,
dateFormat = 'MMM d, yyyy',
options = {}
) => {
const messageTime = fromUnixTime(time);
const now = new Date();
const { fallbackFormat = 'LLL d y, h:mm a', ...formatOptions } = options;
const messageDate = format(messageTime, dateFormat, formatOptions);
if (!isSameYear(messageTime, now)) {
return format(messageTime, fallbackFormat, formatOptions);
}
return messageDate;
};
/**
* Converts a Unix timestamp to a relative time string (e.g., 3 hours ago).
* @param {number} time - Unix timestamp.
* @returns {string} Relative time string.
*/
export const dynamicTime = (time, options = {}) => {
const unixTime = fromUnixTime(time);
return formatDistanceToNow(unixTime, { addSuffix: true, ...options });
};
/**
* Formats a Unix timestamp into a specified date format.
* @param {number} time - Unix timestamp.
* @param {string} [dateFormat='MMM d, yyyy'] - Desired date format.
* @returns {string} Formatted date string.
*/
export const dateFormat = (time, df = 'MMM d, yyyy', options = {}) => {
const unixTime = fromUnixTime(time);
return format(unixTime, df, options);
};
/**
* Converts a detailed time description into a shorter format, optionally appending 'ago'.
* @param {string} time - Detailed time description (e.g., 'a minute ago').
* @param {boolean} [withAgo=false] - Whether to append 'ago' to the result.
* @returns {string} Shortened time description.
*/
export const shortTimestamp = (time, withAgo = false) => {
// This function takes a time string and converts it to a short time string
// with the following format: 1m, 1h, 1d, 1mo, 1y
// The function also takes an optional boolean parameter withAgo
// which will add the word "ago" to the end of the time string
const suffix = withAgo ? ' ago' : '';
const timeMappings = {
'less than a minute ago': 'now',
'in less than a minute': 'now',
'a minute ago': `1m${suffix}`,
'an hour ago': `1h${suffix}`,
'a day ago': `1d${suffix}`,
'a month ago': `1mo${suffix}`,
'a year ago': `1y${suffix}`,
};
// Check if the time string is one of the specific cases
if (timeMappings[time]) {
return timeMappings[time];
}
const convertToShortTime = time
.replace(/about|over|almost|/g, '')
.replace(' minute ago', `m${suffix}`)
.replace(' minutes ago', `m${suffix}`)
.replace(' hour ago', `h${suffix}`)
.replace(' hours ago', `h${suffix}`)
.replace(' day ago', `d${suffix}`)
.replace(' days ago', `d${suffix}`)
.replace(' month ago', `mo${suffix}`)
.replace(' months ago', `mo${suffix}`)
.replace(' year ago', `y${suffix}`)
.replace(' years ago', `y${suffix}`);
return convertToShortTime;
};
/**
* Formats a duration in seconds into mm:ss or hh:mm:ss.
* @param {number|string} durationInSeconds - Duration in seconds.
* @returns {string} Formatted duration string. Empty string for invalid input.
*/
export const formatDuration = durationInSeconds => {
if (durationInSeconds === null || durationInSeconds === undefined) return '';
const totalSeconds = Number(durationInSeconds);
if (Number.isNaN(totalSeconds) || totalSeconds < 0) return '';
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
const mm = minutes.toString().padStart(2, '0');
const ss = seconds.toString().padStart(2, '0');
if (hours > 0) {
return `${hours.toString().padStart(2, '0')}:${mm}:${ss}`;
}
return `${mm}:${ss}`;
};
/**
* Calculates the difference in days between now and a given timestamp.
* @param {Date} now - Current date/time.
* @param {number} timestampInSeconds - Unix timestamp in seconds.
* @returns {number} Number of days difference.
*/
export const getDayDifferenceFromNow = (now, timestampInSeconds) => {
const date = new Date(timestampInSeconds * 1000);
return differenceInDays(now, date);
};
/**
* Checks if more than 24 hours have passed since a given timestamp.
* Useful for determining if retry/refresh actions should be disabled.
* @param {number} timestamp - Unix timestamp.
* @returns {boolean} True if more than 24 hours have passed.
*/
export const hasOneDayPassed = timestamp => {
if (!timestamp) return true; // Defensive check
return getDayDifferenceFromNow(new Date(), timestamp) >= 1;
};