---
title: "Numbers, dates and time"
description: "The prose style guide for dates, times, durations and ranges. It also covers the moment when \"2 hours ago\" becomes dangerous."
url: "https://opsinjs.pensievelabs.org/content/numbers-dates-and-time"
source: "https://opsinjs.pensievelabs.org/content/numbers-dates-and-time.md"
section: "Content & language"
kind: "content"
reviewed: "2026-09-20"
reviewer: "content"
aliases: ["date format", "time format", "relative time", "ordinals"]
---

> Elements written as `<PascalCase … />` below are opsinjs documentation
> components. Their attributes are the content: the values they render are
> generated from `tokens/*.json` and `registry/catalogue.ts` and are
> published separately at https://opsinjs.pensievelabs.org/r/index.json and under the Reference
> section.
> Nothing is missing from this page. The data simply does not live in
> the prose.

<PageTemplate kind="content" />

## The rule [#the-rule]

**Relative time is a convenience; absolute time is the record. Never let the
convenience replace the record for anything a clinician might read.**

That is the one rule on this page that has consequences beyond tidiness.
Everything else here is style. This one is a correctness rule, and it is why
`RelativeTime` is a component with a specification rather than a formatting
helper.

### The boundary with the clinical rules [#the-boundary-with-the-clinical-rules]

This page owns the **prose style** for dates, times, durations and how a range is
worded. It does not own numeric precision.

How many significant figures a measurement may carry, when a rounded figure
becomes misleading, and how unit conversions are handled are clinical questions
owned by
[Numbers, units and precision](../health/numbers-units-precision.mdx) and
[Unit systems](../health/unit-systems.mdx). The typographic mechanics are
spacing, dashes, leading zeros and digit grouping, and they are on
[Grammar and mechanics](./grammar-and-mechanics.mdx). This page is the third
piece: how a moment in time is worded.

### Dates [#dates]

* **Day, month, year, with the month as a word**: 14 March 2026. Never
  14/03/2026 and never 03/14/2026. An all-numeric date is ambiguous between
  conventions, and a health record is exactly where that ambiguity is expensive.
* **Drop the year for a date inside the current year**, in an interface where the
  reader can see the context. Keep it in anything exported, printed, shared or
  older than a few months.
* **No ordinal suffixes.** "1 March", not "1st March".
* **A weekday helps for a near date and hurts for a far one.** "Friday 14 March"
  is useful this week; a month out, the weekday is noise.

### Times [#times]

* **24-hour clock with a colon**: 08:12, 17:45. It is unambiguous, sorts, and
  matches how appointment letters and device logs are written.
* **A time with no date is only allowed when today is unmistakable.** "Taken at
  08:12" on a screen that also says "today" is fine; the same string in a
  notification is not.
* **Say the time zone only when it can differ from the reader's.** For a
  measurement taken by the reader's own device, it cannot.

### Relative time [#relative-time]

Relative phrasing is easier to read and loses information. The rules:

* **Use relative for recency, within a day.** "2 hours ago", "just now",
  "yesterday".
* **Switch to absolute beyond about a day.** "on Tuesday", then "on 14 March".
  A relative phrase is unusable once it exceeds the reader's working memory for
  counting backwards, which happens quickly.
* **Always pair relative with absolute where the value matters.** "2 hours ago
  (08:12)". This costs a few characters and removes the whole class of error.
* **Never use relative alone in anything durable.** An export, a printout, a
  share to a clinician and a notification that may be read hours later are all
  durable. A printed page saying "2 hours ago" is a page with no time on it at
  all. See [Print and export](../accessibility/print-and-export.mdx).
* **Relative time must update or be honest that it does not.** A string rendered
  server-side hours before the reader sees it is a lie with a timestamp's
  authority.

### Durations and frequencies [#durations-and-frequencies]

* **Words for short durations, numerals with units for longer ones**: "a few
  minutes", "20 minutes", "3 days", "6 months".
* **Say the window, not the count, for a trend**: "over the last 7 days", not
  "in the last week". The reader needs to know what was included.
* **Frequency as "n times a day", not "n/day"** in prose.

### Ranges [#ranges]

* **A range always uses the word "to"**: "90 to 120 mmHg" in prose, "90 to 120"
  in a compact display. The compact form loses the unit, never the relation.
* **Name whose range it is.** "Your usual range", "the range your GP set", "the
  range on your results letter". An unattributed range implies a universal
  standard, which is exactly the implication
  [Reference ranges](../health/reference-ranges.mdx) exists to prevent.
* **Never call a range "normal".**

## Approved / Rejected [#approved--rejected]

<DoDont>
  <DoDont.Do>
    Taken 2 hours ago (today at 08:12)
  </DoDont.Do>

  <DoDont.Dont>
    Taken 2 hours ago
  </DoDont.Dont>
</DoDont>

<DoDont>
  <DoDont.Do>
    14 March 2026
  </DoDont.Do>

  <DoDont.Dont>
    14/03/2026
  </DoDont.Dont>
</DoDont>

<DoDont>
  <DoDont.Do>
    Your last three readings, on 12, 13 and 14 March
  </DoDont.Do>

  <DoDont.Dont>
    Your last three readings (2d ago, 1d ago, 4h ago)
  </DoDont.Dont>
</DoDont>

<DoDont>
  <DoDont.Do>
    Your usual range is 90 to 120 mmHg.
  </DoDont.Do>

  <DoDont.Dont>
    Normal range: 90-120.
  </DoDont.Dont>
</DoDont>

<DoDont>
  <DoDont.Do>
    Over the last 7 days you logged 5 readings.
  </DoDont.Do>

  <DoDont.Dont>
    Weekly adherence: 71%.
  </DoDont.Dont>
</DoDont>

## Patterns [#patterns]

**A measurement's timestamp, on screen**

> `relative phrase` (`absolute time`)

**A measurement's timestamp, exported or printed**

> `weekday`, `day` `month` `year` at `HH:MM`

**A range with an owner**

> `Owner's` range is `low` to `high` `unit`.

**A trend window**

> Over the last `n` `days / weeks`, `what happened`.

**Not enough data for a window**

> We need `n` more readings before we can show a trend for `window`.

**A scheduled event**

> `Weekday` `day` `month` at `HH:MM`

**A gap in the record**

> No reading between `date` and `date`.

Say the gap. A chart that joins two points across a two-week hole is asserting
something about the fortnight in between. See
[Uncertainty, staleness and missing data](../health/uncertainty-and-staleness.mdx).

## Banned words [#banned-words]

| Banned                            | Why                                                                             | Use instead                             |
| --------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------- |
| an all-numeric date (14/03/2026)  | Ambiguous between conventions in exactly the document where ambiguity costs.    | 14 March 2026                           |
| 1st, 2nd, 3rd in a date           | Noise, and a screen reader reads the suffix.                                    | 1 March                                 |
| am / pm                           | Two more characters of ambiguity than a 24-hour clock.                          | 08:12, 17:45                            |
| 2d ago, 4h ago, 3w                | Telegraphic shorthand in a health record.                                       | 2 days ago, or the date                 |
| recently, a while ago, lately     | Unquantified time in a record of measurements.                                  | the window, or the date                 |
| a bare relative time in an export | A printed page with no time on it.                                              | absolute, or both                       |
| normal range                      | A verdict, and unowned. See [Reference ranges](../health/reference-ranges.mdx). | your usual range, the range your GP set |
| week (for a 7-day window)         | Implies calendar weeks, which is usually not what was computed.                 | the last 7 days                         |

## Related components [#related-components]

* **`RelativeTime`** takes every rule in the Relative time section above as a
  requirement, including the hardest one: knowing when to refuse to be relative.
* **`Value`** covers the number and its unit; the typography is on
  [Grammar and mechanics](./grammar-and-mechanics.mdx).
* **`TrendSparkline`** takes the window wording and the gap wording as its
  content contract, and the minimum number of readings before a trend may be
  drawn is a clinical rule from
  [Trends and change](../health/trends-and-change.mdx).
* **`LogSheet`*&#x2A; and &#x2A;*`ReadingInput`** capture a moment; the date and time
  format used at capture is the one that ends up in every export.
* **`RangeBar`** displays a range, and must name whose range it is.
