RoadieRoadie
  • Home
  • Foundations
  • Tokens
  • Components
  • Charts
  • Widgets
  • Appearance
HomeFoundationsComponents
Appearance

Foundations

  • Visual

    • Colors
    • Elevation
    • Iconography
    • Shape
    • Theming
  • Content

    • Date and time
    • Layout
    • Typography
  • Behaviour

    • Accessibility
    • Interactions
    • Motion
    • View transitions
  • Building apps

    • Linking
    • Navigation
    • Performance

Date and time

ReferenceComponent utilities tokens

Someone can meet the same event half a dozen times. An event page, a checkout summary, a listing card, a report. They should see the date and time spelled the same way every time.

Choosing

In React, always a component. The styles below are how you configure one, not things you call yourself.

What you are showingComponentConfigured withReadsWithout React
A moment<DateTime>dateStyle, timeStyleFri 27 Nov 2026, 7:30pmformatLong
A moment, as elapsed time<DateTime relative>cutoffMs3 minutes agoformatRelative
A span between two moments<DateTime to>showDuration, sameNightFri 27 to Sun 29 Nov 2026formatDateRange
A length of time<Duration>durationStyle2 hours 30 minutesformatDuration
Time remaining, ticking<Countdown>seconds, display4:32formatDuration
A calendar tile<CalendarTile>weekday, dateTimeNOV 27formatGlyph
A chart axis tick<DateTime render>dateStyle 'short', timeStyle 'numeric'27 Nov 19:30formatDateTime

A component renders a time element and sets its machine-readable value for you. That attribute is easy to get wrong, and wrong silently.

Every component is a thin call into a formatter, and the formatters are exported. The test: if it renders, use the component; if it is a string going into an attribute, a file or another system, use the formatter. Each component page lists its own under Without React.

Two cases look like exceptions and are not. A chart tooltip is ordinary HTML, so DateTime works there; only the axis is SVG, and render covers it. A range has no element of its own, which argues against a single time, not against a component: <DateTime to> renders one per end.

The scale

One scale, from spelled out to machine readable. Each step drops exactly one thing. Picking a style is a question about the reader. Do they need the weekday? Do they need the year?

StyleRendersReach for it when
fullFriday, 27 November 2026A single date with room around it. The date is the point of the page.
longFri 27 Nov 2026The everyday default. Lists, cards, table rows, summaries.
medium27 Nov 2026The day of week does not help the reader. Order dates, settlement dates.
short27 NovContext already supplies the year. Chart ticks, group headings.
iso2026-11-27Exports, filenames, anything sorted or parsed. Never shown to a customer.

The names are Intl.DateTimeFormat’s. The output departs from it twice, on purpose. long keeps a weekday where Intl drops it. iso replaces Intl’s numeric short, which renders 27/11/26 and is ambiguous outside Australia.

Time is a separate axis

Time is a separate option. It is not a property of the date style. Any date style can carry a time. A date style on its own never does. The step names are the same words, so medium means the same kind of thing on both axes: the everyday form, one step down from the fullest.

StyleRendersReach for it when
long7:30pm AEDTThe reader may be elsewhere and has to act on it. On-sale times, national tours, anything someone sets an alarm for.
medium7:30pmThe default. The reader is in the venue’s zone, or the zone is already established nearby.
short7:30pm · 7pmA dense row where the time is one fact among several. A ticket tag, a chip, a chart tooltip.
numeric19:30Charts and dense tables. Fixed width, sorts as it reads. A buyer should never meet it.
(omitted)not shownThe date alone is the answer. A festival’s date range, a settlement date.

A date and a time are joined with a comma, which is the platform’s own separator. The meridiem closes up against the digits at every style, which is where the house style departs from the platform. Under iso the time switches to 24 hour, joins with a space and carries no zone name, because a spreadsheet cell is read as local wall-clock. It carries no offset either, which is why formatMachine exists for markup.

The choice is about the reader, not about space. long and medium differ by whether the reader can be trusted to know the zone. short is the one place minutes are dropped, so reach for it only where the time is incidental.

long means different things on the two axes, exactly as it does in Intl. dateStyle: 'long' is Fri 27 Nov 2026; timeStyle: 'long' adds the zone. They are independent, so { dateStyle: 'medium', timeStyle: 'long' } is an ordinary pairing.

Name the zone when someone has to act across one

A sale opening is the clearest case. Somebody in Perth needs to know whether to set an alarm for 9am or 6am.

On sale Fri 27 Nov, 9:00am AEDT
Doors 7:30pm
Sold 7:30pm · Sec A Row 12

Do

The zone where it changes a decision. Left off where the reader is standing at the venue. Dropped entirely where the time is one fact in a row.
On sale Fri 27 Nov, 9:00am
Doors 7:30pm AEDT AEDT
Doors 7pm

Don’t

A national on-sale with no zone. A zone repeated because the surrounding copy already said it. And a door time missing its minutes, which only short may do.

The rules

1. The reader picks the style

Pick by what the reader needs, not by which screen you are on. What is not allowed is two spellings of the same thing in different places.

2. Weekday abbreviation follows the month

Abbreviated with abbreviated, full with full. A date never mixes registers with itself. The scale guarantees this, including the months the locale spells with four letters, such as Sept. You only break it by hand rolling.

Fri 27 Nov
Friday, 27 November

Do

Both halves agree, because the style decides both.
Fri, 27 November
Friday, 27 Nov

Don’t

A short weekday against a long month. This shipped inside Roadie itself for months. It is the reason this page exists.

3. The year is a disambiguator

Show it unless something already on screen establishes it. Measured in the venue’s timezone. Two people in different states then agree about a New Year’s Eve event.

WhereYear
full, long, medium. Standing aloneDropped when it is the current year
full, long, medium. In a list or tableAlways shown. Siblings may span years
shortNever. That is what the style means
isoAlways
Range, both ends same yearOn the later end only
Range straddling a yearBoth ends

context defaults to 'list'. A forgotten context yields a slightly verbose date. Never an ambiguous one.

4. One clock, set once

Lowercase, minutes always, and no space before the meridiem. That last part is a deliberate departure: the platform’s own short time spaces it. Call sites never need a defensive toLowerCase().

7:30pm
7:30pm AEDT
7pm (short only)

Do

Minutes are always present, so a column of times aligns. short is the single documented exception, for rows where the time is incidental.
7:30 pm
7:30 PM
7.30pm
7:30:00pm

Don’t

Four spellings of one time. The spaced meridiem is the near miss to watch for. Seconds are never display information, and 24 hour belongs only to numeric and iso.

5. One separator per meaning

A comma joins a date to its time. The word to joins the two ends of a range. A middot · separates a date from an adjacent fact. Never a dash of any kind.

Fri 27 Nov 2026, 7:30pm
Fri 27 to Sun 29 Nov 2026
Fri 27 Nov 2026 · 3 days

Do

The separator tells you what the next token is. And it survives being read aloud.
12:00pm - 10:00pm · Sun 29 Nov
Fri 27 – Sun 29 Nov 2026

Don’t

A hyphen joining a range, with a middot introducing an end date so it reads as part of it. And an en dash, which a screen reader may skip entirely.

6. Show a time only when it changes something

A time earns its place when the reader can act on it, or when it changes a decision. Otherwise the date is the unit of meaning and the time is noise.

Doors 7:30pm
On sale Fri 27 Nov, 9:00am AEDT
Edited 27 Nov 2026, 2:14pm

Do

Someone turns up, sets an alarm, or checks which edit was latest.
Fri 27 to Sun 29 Nov 2026, 12:00am
Settled 27 Nov 2026, 12:00am
Report period: 1 Nov 2026, 12:00am

Don’t

A midnight that means nothing. The date alone was the answer.

7. Format in the zone the moment belongs to

An event time belongs to its venue. A timestamp belongs to whoever is reading it. timeZone is required so this is always a decision. There is no fallback to the browser.

formatLong(startsAt, {
timeZone: venue.timeZone
})
formatMedium(placedAt, {
timeZone: viewerTimeZone(),
timeStyle: 'medium'
})

Do

An 11pm show reads Fri 27 Nov from every state. An order reads in the hours the reader actually worked.
new Date(startsAt).toLocaleDateString()
format(startsAt, 'dd MMM yyyy')

Don’t

Both render in the browser’s zone by accident. A late evening event then shows the wrong day for half the country.

Which clock

A moment belongs to a place, and which place depends on what kind of moment it is. Rule 7 turns on the distinction, so it is worth being explicit.

KindMeansTimezoneLooks like
Event timeWhen something happens at a place. Doors, set times, the show.The venue’s. Never converted.Fri 27 Nov 2026, 7:30pm
Access timeWhen the reader must do something from wherever they are. On sale, presale, registration opening.The venue’s, always labelled.Fri 27 Nov, 9:00am AEDT
TimestampWhen something was recorded. An order, an edit, a note.The reader’s.27 Nov 2026, 2:14pm

A timestamp usually wants medium. The weekday of an audit entry tells the reader nothing. An event time usually wants long or full, because the weekday is often the whole reason someone is looking.

Why an event time is never converted

The usual advice is to convert everything into the reader’s local zone. That is right for a meeting, where each attendee joins from wherever they are sitting. It is wrong here.

A buyer will be standing at the venue when the doors open. Converting an 8pm Perth show into 11pm for a reader in Sydney does not save them arithmetic. It tells them the wrong time. The venue’s clock is not a formatting preference. It is the answer.

Convert what the reader acts on, not what they attend

The test is where the reader will be when the moment arrives.

Doors 8:00pm
On sale Fri 27 Nov, 9:00am AEDT
Order placed 2:14pm

Do

The show in venue time, because that is where they will be. The on sale labelled, because they act from home. The order in their own time, because that is when it happened to them.
Doors 11:00pm (a Perth show, read from Sydney)
On sale 9:00am
Order placed 2:14pm AWST

Don’t

A show time nobody can use. An on sale that is three hours out for half the country. And a timestamp dressed in a zone the reader has never been in.

An access time may need both

A national on sale is the one case where a second clock earns its place. The reader is at their own computer, competing for tickets. Lead with the venue’s zone, because that is what the promoter announced, and offer theirs after it.

On sale Fri 27 Nov, 9:00am AEDT
6:00am your time

Say when the day moves

The most common silent failure in any timezone code is a conversion that lands on a different calendar day. Whenever a second clock is shown and it falls on another date, say so. Never show a bare time that is quietly a day out.

Show the date when a conversion crosses midnight

A reader who sees only a time assumes it is the same day. Half the time it is not.

10:00pm AEDT
7:00pm your time
12:30am AEDT
9:30pm your time, Thu 26 Nov

Do

The second reads without a date only while the day is unchanged. When it moves, the date comes with it.
12:30am AEDT
9:30pm your time

Don’t

The reader sets an alarm for the wrong night.

All day means every zone

A date with no time does not belong to a zone and must never be shifted by one. A public holiday, a birthday, an on-sale date with no announced hour: the same date everywhere. Converting it is how a date silently becomes the day before.

Why we do not print the offset

A common recommendation is to pair the abbreviation with its offset, 9:00am AEST (UTC+10), because abbreviations collide internationally: CST is Central US, China and Cuba.

Australian abbreviations do not collide with each other, and the audience reads them fluently. People do not think in offsets, so an offset is noise for almost every reader. Add one only where the audience is genuinely international, and never in place of the abbreviation.

The abbreviation needs a place beside it

AEST and AEDT differ by one character and one hour. For half the year Queensland and New South Wales sit on opposite sides of it. A reader in Brisbane has to catch a single letter to know the time is not their own.

Usually the place is already on the page. An event page names its venue; a listing names a city. Where a time appears without one beside it, name the place too.

Let the layout carry the place, or say it

The abbreviation is a confirmation, not an introduction.

The Lyrebird, Richmond VIC
Doors 8:00pm AEDT
On sale 9:00am AEDT, Sydney
8:00am your time

Do

The venue answers where, so the abbreviation only has to confirm it. The notification has no venue on it, so the place comes with the time.
On sale 9:00am AEDT
On sale 9:00am Australia/Sydney
On sale 9:00am AET

Don’t

A bare abbreviation with no place. A database identifier in copy. And a generic form that hides daylight saving, which is the one thing the reader needed to know.

Do not reach for Intl’s generic zone names to solve this. shortGeneric renders Sydney as AET and Brisbane as AEST, so it removes the daylight saving distinction from one and not the other, making the two states harder to tell apart rather than easier.

Tools where the venue cannot be assumed

A buyer is usually local to the event they are looking at. Someone running events often is too, and just as often is not: the same desk handles venues in several states. They also set the on-sale times thousands of buyers act on, so a zone read wrong here is an incident rather than an inconvenience.

Two rules bind harder here. The zone is never optional, because you cannot tell from the screen whether the reader is in it. And a second clock stops being a nicety: where the reader’s zone differs from the venue’s, show both, venue first.

// The viewer's zone is a client fact, so resolve it after mount
// rather than during render, where the server would disagree.
const viewer = useViewerTimeZone()

<DateTime at={startsAt} timeZone={venue.timeZone} timeStyle='long' />
{viewer && viewer !== venue.timeZone && (
  <span className='text-subtle'>
    <DateTime at={startsAt} timeZone={viewer} dateStyle='medium' timeStyle='medium' />
    {' your time'}
  </span>
)}

Name the zone, never the record

An operator tool that prints an identifier or a raw offset is printing a database value, not copy.

Fri 27 Nov 2026, 7:30pm AWST
27 Nov 2026, 9:30pm your time

Do

The venue’s clock first, because that is when the doors open. The reader’s underneath, so nobody does the arithmetic.
27 Nov 2026 07:30 PM Australia/Perth
27 Nov 2026, 07:30 PM (GMT+8)
27 Nov 2026 07:30 PM

Don’t

A database identifier. A raw offset. And a time with no zone at all, which is how an operator in one state sets an on sale for another and does not notice.

One thing must never happen here: an event time rendered in the reader’s zone as the primary value. That is the wrong answer wearing the right label, and the most common way a scheduling mistake reaches a buyer.

The Australian zones

Australia has five zones and only some states observe daylight saving. For half the year a show in Brisbane and a show in Sydney are an hour apart. For the other half they are not. That is why timeZone is required, and why it takes an IANA identifier rather than an abbreviation.

AbbreviationWhereDaylight saving
AEST / AEDTNSW, VIC, TAS, ACTObserves DST
AESTQLDNo DST
ACST / ACDTSAObserves DST
ACSTNTNo DST
AWSTWANo DST
LHST / LHDTLord Howe IslandHalf hour DST shift

Show the zone when the reader might not be in it. An event page for a national tour, an on-sale time, anything someone in another state acts on. Leave it off when the reader is obviously local, because it is noise.

Name the zone, do not offset it

An abbreviation is what people recognise. A UTC offset is not.

On sale Fri 27 Nov, 9:00am AEDT

Do

Someone in Perth knows to set an alarm for 6am.
On sale Fri 27 Nov, 9:00am GMT+11
On sale Fri 27 Nov, 9:00am Australia/Sydney

Don’t

An offset nobody converts in their head. Or a raw IANA identifier, which is a database key, not copy.

Midnight, and the small hours

A late show is the normal case in this business, not an edge case. Two rules follow from that.

First, a set that ends at or before 6am belongs to the night before. A show running 10pm to 3am is one night out, not two. formatDurationDays applies this, so a festival does not gain a day on one screen and not another.

Second, 12:00am is ambiguous to a lot of readers. In prose, prefer the word. In a table or a row of times, keep the digits so the column aligns.

Say midnight when it is prose

The formatter emits digits, which is right for a table. A sentence is different.

Doors 8:00pm, close midnight
Sat 28 Nov, 12:00am

Do

The word in a sentence. The digits in a row, where alignment is worth more.
Doors 8:00pm, close 12:00am
Closes 12 pm

Don’t

An ambiguous midnight in prose. And a 12 pm that half of readers will take for midnight.

Which register

formatRelative renders a timestamp as elapsed time. It falls back to the absolute date once the moment is further away than the cutoff, which defaults to seven days.

formatRelative(note.addedAt, { timeZone: viewerTimeZone() })
// → 'just now' · '3 minutes ago' · '5 hours ago'
// → 'yesterday' · '3 days ago'
// → 'Fri 27 Nov 2026' once it is past the cutoff

Always keep the absolute reachable

A relative time cannot be checked against a calendar or quoted to anyone. Put the absolute in a title or tooltip.

<DateTime at={note.addedAt} timeZone={viewerTimeZone()} relative />

Do

Scannable at a glance, precise on hover. The component puts the absolute in title, sets the machine value, and swaps the text in after mount so it neither goes stale nor mismatches the server.
<span>{formatRelative(at, o)}</span>
<time dateTime={iso}>{formatRelative(at, o)}</time>

Don’t

The exact moment is unreachable, and hand-rolled relative text is frozen at whatever the server rendered.

It reads the future too, so in 20 minutes works for a door opening. Not for the event itself: “in 3 days” is a worse answer than Fri 27 Nov for something a person has to turn up to.

The ladder runs both ways

Past and future are not mirror images. For something that already happened, closer means relative is better. For something that has not happened yet, closer means relative is worse.

“3 minutes ago” beats a timestamp on an audit row. But “in 4 hours” is a bad answer for a sale opening today, because you cannot set an alarm from it. As a future moment gets nearer, hand back the actual time.

Past: relative near, absolute far

This is what formatRelative does. The cutoff defaults to seven days.

DistanceReads
Under a minutejust now
Under an hour3 minutes ago
Under a day5 hours ago
One dayyesterday
Under a week3 days ago
OlderTue 17 Nov 2026

Future: absolute near, relative far

The inverse, and the formatter does not do it for you. Build it from the distance, the way an on-sale label does.

DistanceReadsWhy
Over a weekMon 7 Dec 2026A date. Nobody counts down from nine days.
Under a weekin 5 daysClose enough to feel soon.
One daytomorrowThe word beats the number.
Today9:00am AEDTAbsolute. You cannot set an alarm from “in 4 hours”.

Picking a cutoff

The default suits a note or an audit row. Shorten it where staleness matters, and do not reach for it at all where someone is making plans.

SurfaceCutoffWhy
Notification, activity feed1 hourAnything older wants a real time.
Audit row, note, comment7 daysThe default.
“Last updated” on a report7 daysRecency is the whole signal.
Anything a person turns up toDo not use itA countdown is not a plan.

Match the register to the decision

Ask what the reader does next. If the answer involves a calendar or an alarm, give them a date and a time.

Edited 3 minutes ago
On sale in 5 days
On sale tomorrow
On sale at 9:00am AEDT

Do

The first is a recency check. The rest walk down to the exact time as the moment gets close enough to act on.
Edited 27 Nov 2026, 2:14:03pm
On sale in 4 hours
Starts in 3 days
On sale in 63 days

Don’t

A timestamp where recency was the question. A countdown where a time was needed. A countdown to the event itself. And a number nobody converts back into a date.

Dates in data

Charts and dense tables are scanned and compared, not read. In prose a date is a fact in a sentence. In a column it is a value to line up against the ones above and below it.

So the rules bend in one specific direction: fixed width, no decoration, and nothing that repeats what the surrounding structure already says.

WhereUseReadsWhy
Axis tickdateStyle 'short' + timeStyle 'numeric'27 Nov 19:30Dozens on screen at once. No weekday, no year, no meridiem.
TooltipdateStyle 'long' + timeStyle 'medium'Fri 27 Nov 2026, 7:30pmRestores everything the tick dropped. That is what makes the tick safe.
Table column, date identifies the rowdateStyle 'medium'27 Nov 2026An order or settlement date. The weekday is not what the reader is comparing.
Table column, weekday is a variabledateStyle 'long'Fri 27 Nov 2026An events list. The weekday is a dimension the reader analyses down the column.
Table column, with time+ timeStyle 'numeric'27 Nov 2026 19:30Two fixed-width fields. The column aligns and sorts.
Export celldateStyle 'iso' + timeStyle 'numeric'2026-11-27 19:30Sorts lexicographically. Unambiguous in any locale.
Sort or group keyNot a display formatnot shownKey on the instant. A formatted string is not a key.

When the weekday is a dimension

A date in a column is usually a label: it says which row this is, and the reader is comparing the numbers beside it. There, the weekday is repetition and medium is right.

Sometimes the weekday is the thing being compared. A promoter reading their events list wants to know whether Wednesdays sell worse than Fridays, and the only way to see that is to scan the weekday down the column against the figures. There it is an axis of the analysis. Dropping it removes the pattern.

Ask whether the reader is comparing the weekday

The test is not how wide the column is. It is whether the answer to their question is in that token.

Events list Fri 27 Nov 2026
Orders table 27 Nov 2026
Settlements 27 Nov 2026

Do

The events list keeps the weekday because day of week drives attendance. The others do not, because nobody asks which weekday an order was placed.
Events list 27 Nov 2026
Orders table Fri 27 Nov 2026

Don’t

The pattern removed from the one place it matters, and repetition added to a column where it says nothing.

When it is a dimension, keep it leading and abbreviated. Three characters in a fixed position is what lets the eye run down the column and catch the repetition. A weekday buried mid-string, or spelled out at varying widths, cannot be scanned that way.

The axis and its tooltip are one design

An axis tick drops the weekday, the year and the meridiem because the axis range establishes all three and because dozens of ticks share the width of one chart. That is only safe because hovering restores them. Design the pair together or neither works.

Let the tooltip carry what the tick cannot

A reader aims at a date, never at a two-pixel line. The tick orients; the tooltip answers.

tick: 27 Nov 19:30
tooltip: Fri 27 Nov 2026, 7:30pm

Do

The tick stays legible at density. The tooltip is the only place the full date needs to exist.
tick: Fri 27 Nov 2026, 7:30pm
tooltip: 27 Nov 19:30

Don’t

A tick nobody can fit and a tooltip that answers less than the axis.

24 hour, and only here

numeric is where rule 4 gives way for a reader, the way iso does for a file. A meridiem costs three characters on every tick, and worse, it changes width across midday, so 11:45am and 12:00pm crowd differently and a chart library starts dropping ticks unevenly.

It also matches the source. Operators read a scan chart against a run sheet, and run sheets are 24 hour. A buyer should never meet this style.

Never key on a formatted date

Grouping, sorting, bucketing and caching key on the instant, never on the string that gets displayed. A formatted date is an output. The moment one is used as a key, changing how a date looks changes which rows group together, and that failure is silent.

Writing dates here

The scale is day first, never month first. 27 Nov 2026, not Nov 27, 2026. This is not a preference. A month first date is read wrongly by most people here.

Write dates the way people here read them

These follow from the scale. You only break them by hand rolling a date.

Fri 27 Nov 2026
7:30pm
Anzac Day, Boxing Day

Do

Day first. No leading zero on the hour. Named days and public holidays are proper nouns, so they take capitals.
Nov 27, 2026
Fri 27th Nov
07:30pm

Don’t

Month first. Ordinals, which add nothing and are read aloud awkwardly. A padded hour, which belongs only to the two 24 hour styles, numeric and iso.

Recurring events

Residencies, weekly nights and seasons all need plain words. Several common ones mean two different things to two different readers.

Say the interval, not a word that hides it

If a word has two readings, a promoter and a buyer will pick different ones.

Every 2 weeks
Twice a month
Thursdays, 5 Nov to 17 Dec

Do

One reading only. The last one also bounds the run, which a recurring label on its own never does.
Fortnightly
Bimonthly
Biannual

Don’t

Each of these has two accepted meanings. None of them survive a refund conversation.

Accessibility

Ranges need nothing extra. They are joined with the word to, so they read correctly on screen and out loud. A dash would not. Screen readers announce an en dash inconsistently and often skip it, which turns a range into one run on date.

Seat runs are the exception and keep a hyphen. A1-4 is a compressed list, not a range someone reads as a sentence, and A1 to 4 would suggest four separate seats. The brand guide already spells number ranges this way, and it is the easier character to type.

Outside React

The rules are portable. The implementation is not. There are two cases and they take different answers.

JavaScript, but no React

Use the formatters. Each component page lists the ones behind it under Without React, with the call sites they are for: DateTime, Duration, Countdown and CalendarTile. formatMachine gives you the attribute value, so there is no reason to build one by hand.

Another language on the server

The application owns its own formatter and follows the same rules, with the same style names. Name its methods after the style, never the format string they produce, so a call site cannot pick the wrong one.

This is the only case that writes the time element by hand. The attribute is the machine value. Three shapes cover it.

ShowingdatetimeWhy
A date2026-11-27A calendar date has no zone. Do not invent one.
A date and time2026-11-27T19:30:00+10:00The offset is what makes it an instant.
A time only19:30Rare. Only where the date is already established.

Carry the offset whenever you show a time

A local time with no offset is a floating value. It parses to a different instant in every timezone that reads it.

datetime="2026-11-27T19:30:00+10:00"
datetime="2026-11-27"

Do

The first identifies one instant anywhere on earth. The second is a calendar date, which genuinely has no zone.
datetime="2026-11-27 19:30"
datetime="Fri 27 Nov 2026, 7:30pm"

Don’t

A floating local time, read ten hours out in London. And a human string, which is not a machine value at all.

Do not reuse the iso style here. It has no offset and uses a space rather than a T, which is right for a spreadsheet cell and wrong for this attribute. See DateTime.

Skip relative time on the server. It is stale the moment it is sent, and there is nothing to re-render it.

Previous page← ThemingNext pageLayout →