Skip to content
VersionSize

timeDiff

The timeDiff utility calculates the time difference between two dates and returns it in a human-readable format (e.g., "5 days ago" or "in 2 hours"). It's perfect for displaying relative timestamps in user interfaces.

Implementation

View Source Code
ts
// #region TimeDiffTypes
export type TimeUnit = 'YEAR' | 'MONTH' | 'WEEK' | 'DAY' | 'HOUR' | 'MINUTE' | 'SECOND' | 'INVALID_DATE';
export type TimeDirection = 'FUTURE' | 'PAST';
export type TimeResult = { unit: TimeUnit; value: number };
// #endregion TimeDiffTypes

let defaultUnits: TimeUnit[] = ['YEAR', 'MONTH', 'WEEK', 'DAY', 'HOUR', 'MINUTE', 'SECOND'];

const MS_PER_SECOND = 1000;
const MS_PER_MINUTE = 60 * MS_PER_SECOND;
const MS_PER_HOUR = 60 * MS_PER_MINUTE;
const MS_PER_DAY = 24 * MS_PER_HOUR;
const MS_PER_WEEK = 7 * MS_PER_DAY;
const MS_PER_MONTH = 30 * MS_PER_DAY;
const MS_PER_YEAR = 365 * MS_PER_DAY;

const TIME_UNITS = [
  { ms: MS_PER_YEAR, unit: 'YEAR' as const },
  { ms: MS_PER_MONTH, unit: 'MONTH' as const },
  { ms: MS_PER_WEEK, unit: 'WEEK' as const },
  { ms: MS_PER_DAY, unit: 'DAY' as const },
  { ms: MS_PER_HOUR, unit: 'HOUR' as const },
  { ms: MS_PER_MINUTE, unit: 'MINUTE' as const },
  { ms: MS_PER_SECOND, unit: 'SECOND' as const },
];

/**
 * Calculates the remaining time until a target date.
 *
 * @example
 * ```ts
 * timeDiff(new Date(Date.now() + 1000 * 60 * 60 * 24 * 5)); // { value: 5, unit: 'DAY' }
 * timeDiff(new Date(Date.now() - 1000 * 60 * 60 * 24 * 3), 'past'); // { value: 3, unit: 'DAY' }
 * timeDiff(new Date(Date.now() + 1000 * 60 * 60 * 24 * 31)); // { value: 1, unit: 'MONTH' }
 * timeDiff(new Date(Date.now() + 1000 * 60 * 60 * 24 * 365)); // { value: 1, unit: 'YEAR' }
 * ```
 *
 * @param a - The target date (Date object or ISO string).
 * @param b - The target date (Date object or ISO string).
 * @param allowedUnits - (optional) array of units to filter the result. If provided, only these units will be considered.
 *
 * @returns An object containing the remaining time and its unit ('DAY', 'HOUR', or 'MINUTE').
 */
export function timeDiff(
  a: Date | string,
  b: Date | string = new Date(),
  allowedUnits: TimeUnit[] = defaultUnits,
): TimeResult {
  const aDate = typeof a === 'string' ? Date.parse(a) : a.getTime();
  const bDate = typeof b === 'string' ? Date.parse(b) : b.getTime();

  if (Number.isNaN(aDate) || Number.isNaN(bDate)) {
    return { unit: 'INVALID_DATE', value: 0 };
  }

  const units = TIME_UNITS.filter((u) => allowedUnits.includes(u.unit));
  const diff = Math.abs(aDate - bDate);
  const smallestUnit = units[units.length - 1]?.unit ?? 'SECOND';

  if (diff <= 0) {
    return { unit: smallestUnit, value: 0 };
  }

  // Find the largest unit that fits
  const bestUnit = units.find((u) => diff >= u.ms);

  if (bestUnit) {
    return { unit: bestUnit.unit, value: Math.floor(diff / bestUnit.ms) };
  }

  return { unit: smallestUnit, value: 0 };
}

timeDiff.defaultUnits = (units: TimeUnit[]) => {
  defaultUnits = units;
};

Features

  • Isomorphic: Works in both Browser and Node.js.
  • Smart Scaling: Automatically selects the largest possible unit for the difference.
  • Configurable Units: Restrict the output to specific units (e.g., only "DAY" or "HOUR").
  • Bi-directional: Supports both future and past date comparisons.

API

Type Definitions
ts
export type TimeUnit = 'YEAR' | 'MONTH' | 'WEEK' | 'DAY' | 'HOUR' | 'MINUTE' | 'SECOND' | 'INVALID_DATE';
export type TimeDirection = 'FUTURE' | 'PAST';
export type TimeResult = { unit: TimeUnit; value: number };
ts
function timeDiff(a: Date | string, b?: Date | string, allowedUnits?: TimeUnit[]): TimeResult;

Parameters

  • date: The target date to compare with the current time.
  • direction: Optional. Specify if the target is in the 'FUTURE' or 'PAST' (defaults to 'FUTURE').
  • allowedUnits: Optional. An array of units to consider for the output (defaults to all units).

Returns

  • An object containing the calculated numeric value and the chosen unit.
  • Returns { value: 0, unit: 'INVALID_DATE' } if the input date is malformed.

Examples

Future Countdown

ts
import { timeDiff } from '@vielzeug/toolkit';

// Target is 5 days from now
timeDiff(new Date(Date.now() + 5 * 24 * 60 * 60 * 1000));
// { value: 5, unit: 'DAY' }

Past Comparison

ts
import { timeDiff } from '@vielzeug/toolkit';

// Event happened 3 hours ago
timeDiff(new Date(Date.now() - 3 * 60 * 60 * 1000), 'PAST');
// { value: 3, unit: 'HOUR' }

Restricted Units

ts
import { timeDiff } from '@vielzeug/toolkit';

// Show difference only in hours, even if it's days away
timeDiff(futureDate, 'FUTURE', ['HOUR']);

Implementation Notes

  • Performance-optimized for real-time updates.
  • Uses Math.floor for all calculated values.
  • Throws nothing; handles invalid dates gracefully.

See Also

  • expires: Categorize a date's expiration status.
  • interval: Generate a sequence of dates.