104 lines
3.2 KiB
TypeScript
104 lines
3.2 KiB
TypeScript
/** Converts time units to milliseconds. */
|
|
export const ms = {
|
|
seconds: (n: number) => n * 1000,
|
|
minutes: (n: number) => n * 60 * 1000,
|
|
hours: (n: number) => n * 60 * 60 * 1000,
|
|
days: (n: number) => n * 24 * 60 * 60 * 1000,
|
|
weeks: (n: number) => n * 7 * 24 * 60 * 60 * 1000,
|
|
months: (n: number) => n * 30 * 24 * 60 * 60 * 1000,
|
|
};
|
|
|
|
/** Converts time units to seconds. */
|
|
export const seconds = {
|
|
minutes: (n: number) => n * 60,
|
|
hours: (n: number) => n * 60 * 60,
|
|
days: (n: number) => n * 24 * 60 * 60,
|
|
weeks: (n: number) => n * 7 * 24 * 60 * 60,
|
|
months: (n: number) => n * 30 * 24 * 60 * 60,
|
|
};
|
|
|
|
/**
|
|
* Validates that a timestamp is in milliseconds (not seconds).
|
|
* Returns true if valid, false otherwise.
|
|
*/
|
|
export const isValidMsTimestamp = (unixTimestamp: number): boolean => {
|
|
// Millisecond timestamps from ~2001 onwards are > 10^12
|
|
// Second timestamps won't reach 10^12 until year ~33658
|
|
const MIN_MS_TIMESTAMP = 1_000_000_000_000; // ~Sept 2001 in ms
|
|
const MAX_MS_TIMESTAMP = 10_000_000_000_000; // ~Nov 2286 in ms
|
|
|
|
if (unixTimestamp < MIN_MS_TIMESTAMP) {
|
|
return false; // Likely in seconds, not milliseconds
|
|
}
|
|
|
|
if (unixTimestamp > MAX_MS_TIMESTAMP) {
|
|
return false; // Too large to be valid
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Validates that a timestamp is in seconds, then converts to milliseconds.
|
|
* Returns undefined if input is undefined or not a valid seconds timestamp.
|
|
*/
|
|
export function secondsToMs(seconds: number): number;
|
|
export function secondsToMs(seconds: undefined): undefined;
|
|
export function secondsToMs(seconds: number | undefined): number | undefined;
|
|
export function secondsToMs(seconds: number | undefined): number | undefined {
|
|
if (seconds === undefined) {
|
|
return undefined;
|
|
}
|
|
|
|
// Seconds timestamps are currently ~10 digits (1.7 billion)
|
|
// They won't reach 10^12 until year ~33658
|
|
const MIN_SEC_TIMESTAMP = 0;
|
|
const MAX_SEC_TIMESTAMP = 10_000_000_000; // ~Nov 2286 in seconds
|
|
|
|
if (seconds < MIN_SEC_TIMESTAMP || seconds > MAX_SEC_TIMESTAMP) {
|
|
return undefined; // Not a valid seconds timestamp
|
|
}
|
|
|
|
return seconds * 1000;
|
|
}
|
|
|
|
export const msToSeconds = (ms: number): number => {
|
|
return Math.floor(ms / 1000);
|
|
};
|
|
|
|
/** Truncates ms to second-level precision (removes sub-second component). */
|
|
export const truncateMsToSecondPrecision = (ms: number): number => {
|
|
return Math.floor(ms / 1000) * 1000;
|
|
};
|
|
|
|
/**
|
|
* Returns true if two timestamps are within `toleranceMs` of each other.
|
|
* Defaults to 1 second to absorb Stripe's second-precision rounding drift.
|
|
*/
|
|
export const timestampsMatch = (
|
|
a: number,
|
|
b: number,
|
|
toleranceMs = ms.seconds(1),
|
|
): boolean => Math.abs(a - b) <= toleranceMs;
|
|
|
|
const START_DATE_TOLERANCE_MS = ms.minutes(1);
|
|
|
|
export const isFutureStartDate = (
|
|
startDate: number | undefined,
|
|
currentEpochMs: number,
|
|
toleranceMs = START_DATE_TOLERANCE_MS,
|
|
): boolean =>
|
|
startDate !== undefined && startDate > currentEpochMs + toleranceMs;
|
|
|
|
export const isPastStartDate = (
|
|
startDate: number,
|
|
currentEpochMs: number,
|
|
): boolean => startDate < currentEpochMs - START_DATE_TOLERANCE_MS;
|
|
|
|
export const stripePhaseStartsInFuture = (
|
|
startDate: number | "now" | undefined,
|
|
currentEpochMs: number,
|
|
): boolean =>
|
|
typeof startDate === "number" &&
|
|
isFutureStartDate(secondsToMs(startDate), currentEpochMs, 0);
|