format

The formatters the components here already use — durations, dates, timestamps, initials.

Quick Preview

CallResultNote
formatDuration(95)1:35Under an hour
formatDuration(3723)1:02:03Over an hour
formatDuration(NaN)0:00Metadata not loaded
formatDate(moment)08 August 2026Day first, month spelled
formatTimestamp(moment)14:0524-hour
formatTimestamp('Yesterday')YesterdayPassed through
toDateTimeAttribute(moment)2026-08-08T14:05:00.000ZFor <time datetime>
getInitials('Daniel Ikoyo')DIFirst two words

Usage

These were living inside the components that needed them. They are published because the same values get written outside a component as often as inside one — a duration in a table cell, initials on a server-rendered avatar, a date in an export — and a second implementation of "how we write a duration" is how a product ends up with two.

import { formatDuration, getInitials } from "@giddaa-housing/ui/utils/format";

formatDuration(3723);        // "1:02:03"
getInitials("Daniel Ikoyo"); // "DI"

Each is a pure function with no React and no styling, so a server-only module can import them. MediaPlayer, Message, EditorialCard and DatePicker now read from here rather than keeping their own copies.

Examples

What each one writes

CallResultNote
formatDuration(95)1:35Under an hour
formatDuration(3723)1:02:03Over an hour
formatDuration(NaN)0:00Metadata not loaded
formatDate(moment)08 August 2026Day first, month spelled
formatTimestamp(moment)14:0524-hour
formatTimestamp('Yesterday')YesterdayPassed through
toDateTimeAttribute(moment)2026-08-08T14:05:00.000ZFor <time datetime>
getInitials('Daniel Ikoyo')DIFirst two words

Durations

The hours part is dropped below an hour, so a short clip does not read as if it might be long. Anything that is not a finite number is "0:00" — a media element reports NaN for its duration until metadata loads, and the alternative is NaN:NaN on screen for the first few frames.

formatDuration(95);     // "1:35"
formatDuration(3723);   // "1:02:03"
formatDuration(NaN);    // "0:00"
formatDuration(-10);    // "0:00"

Timestamps

formatTimestamp passes a string through untouched, so a caller that already has the wording it wants can hand it over without this second-guessing it. Pair it with toDateTimeAttribute for a <time> element.

<time dateTime={toDateTimeAttribute(sentAt)}>{formatTimestamp(sentAt)}</time>
formatTimestamp(new Date(2026, 7, 8, 14, 5)); // "14:05"
formatTimestamp("Yesterday");                 // "Yesterday"

Dates

Day before month, spelled out — "08 August 2026" — because "8/8/2026" is ambiguous between two conventions where a spelled month is not. Pass a locale for anything else.

formatDate(new Date(2026, 7, 8));          // "08 August 2026"
formatDate(new Date(2026, 7, 8), "en-US"); // "August 08, 2026"

Props

Prop/APITypeDefaultDescription
formatDuration(seconds?: number) => string-Seconds as a clock. "0:00" for anything not finite.
formatDate(value: Date, locale?: string) => string"en-GB"A date written out in full.
formatTimestamp(value: Date | string, locale?: string) => string-A 24-hour time of day. A string passes through.
toDateTimeAttribute(value: Date | string) => string-The machine-readable form for a <time datetime>.
getInitials(name: string) => string-The first letter of the first two words.

TimePicker's own parser is deliberately not here: it falls back to the current time when a value will not parse, which is right for seeding a picker's wheels and wrong for a general-purpose formatter.

Accessibility

  • Pair formatTimestamp with toDateTimeAttribute on a <time> element so assistive tech and search engines get an unambiguous value alongside the readable one.
  • getInitials produces a decorative monogram. Give the avatar an accessible name of the full name — initials alone are read out as two letters and identify nobody.
  • A duration read as "1:02:03" is announced as digits by most screen readers. Where the exact length matters, put the spelled form in an aria-label.

On this page