Formatting and locale / Dates and times
A timestamp with no offset is a timestamp with no meaning
A time printed in a document without saying which zone it is in, which is unresolvable by the reader and frequently wrong by a day at the boundaries.
The defect
A bare time is a number without a unit. Printed on a document that travels, it is read in the reader's own zone, which can be most of a day away from the one it was recorded in. Near midnight this moves the date as well, so a document generated late in the evening in one zone shows the next day's date to a reader in another, and an audit trail ordered by those timestamps is not in the order the events happened. The failure is silent because a wrong time still looks exactly like a right one, and it is systematic rather than random, so it affects every document rather than an unlucky few.
What it does to the document
A timestamp with a zone is substantially longer than one without, and it is usually placed in a header, a footer or a narrow metadata column where there is no spare width. Decide early whether the zone goes on the same line or beneath, because retrofitting it into a laid-out header is how it ends up abbreviated and ambiguous again.
The decisions
Reasons rather than a description of the code. Formatting rules are data that changes, so most of these are about taking the rule from a maintained source rather than encoding today's answer.
- Print the zone or the offset with any timestamp a person will act on. A time without one is not a fact.
- Store in a single absolute reference and convert at the moment of formatting, rather than storing local times and hoping the zone travelled with them.
- Choose deliberately whose zone the document displays: the recipient's, the issuer's, or a fixed one, and label it. All three are reasonable and only one can be the answer.
- Use a zone identifier rather than an abbreviation. Abbreviations are ambiguous between regions, and several of them mean different offsets in different parts of the world.
- Remember an offset changes with the season in many zones, so an offset stored once and reused is wrong for half the year. The zone identifier is stable; the offset is not.
- Where only the date matters, print only the date. Attaching a meaningless time to a date invites a reader to attribute meaning to it.
In code
The formatting happens before the markup exists, so this is mostly code rather than CSS. The wrong version is kept in a comment where the wrong version is the thing people write.
const at = new Date("2026-03-14T23:40:00Z");
// A zone identifier and a visible zone name: a fact rather than a number.
new Intl.DateTimeFormat("en-GB", {
dateStyle: "long",
timeStyle: "long",
timeZone: "Europe/London",
}).format(at);
// The same instant, displayed in the recipient's zone. Note the date can
// differ from the line above: that is the point.
new Intl.DateTimeFormat("en-GB", {
dateStyle: "long",
timeStyle: "long",
timeZone: "Asia/Kolkata",
}).format(at);
// Where only the date matters, print only the date. An arbitrary time
// beside it invites the reader to believe it means something.
// Not this: an abbreviation, which is ambiguous between regions.
// "14 Mar 2026 23:40 IST"What people do instead
Printing the server's local time and assuming the reader shares it. It is correct on the machine that generated the document and wrong for everybody else, and near midnight it is wrong by a whole day, which makes an audit trail that appears to record events out of order.
How to find out rather than assume
Generate a document at an instant close to midnight in your own zone and render it for a reader several hours away. The date should be the one you intended, whichever that is, and the zone should be stated on the page. If you cannot tell from the document which zone was meant, neither can the reader.
Where the API sits in this
Nowhere, and that is worth saying plainly. This API renders HTML to PDF. By the time markup reaches it, every value in it is already a string that somebody's code produced, so there is no locale option on the render because there would be nothing for one to do. What the render controls is the page: the size, the margins, and whether the formatted value still fits the column it landed in. That is why each of these pages has a paragraph about the layout consequence, because that is the half a document adds to the general problem.
Frequently asked
Should the document use the sender's conventions or the reader's?
That is a decision rather than a fact, and the only wrong answer is not making it. The issuer's conventions are defensible for a document that is a record of what the issuer did. The recipient's are defensible for a document meant to be acted on. What fails is a document that mixes them, because a reader who sees two conventions on one page cannot tell which one any given figure is in.
Is it enough to set the document language?
No. A language attribute helps with hyphenation, with the assistive technology reading order and with anything that needs to know what language the text is in, and it changes none of the values. A date formatted as 03/04/2026 is exactly as ambiguous in a document declared as English as in one declared as anything else. The values are formatted upstream.
Why does this belong in a document reference rather than a general one?
Because a document cannot be re-rendered by its reader. On a screen a wrong format is annoying and a user can often change a setting. On paper the format is final, the layout was tuned to the string lengths of one language, and a value that grew by a few characters can push a table onto another page. The formatting problem and the pagination problem are the same problem here.
Related formatting topics
The defects that travel together, then the rest of the same group.
Why 03/04/2026 is a defect in a document that crosses a border
An all-numeric date whose day and month can be swapped without producing anything that looks wrong, which makes it unreadable rather than merely unclear.
Alphabetical order that is not alphabetical to the reader
The order a list is sorted into, which is defined by the language rather than by the byte values of the characters.
Name order and the assumption that breaks a salutation
The assumption that a person has a given name followed by a family name, in that order, which is wrong often enough that a document built on it insults people regularly.
Document control block, as a layout problem
The block this value lands in, and what it takes to make it fit.
Every formatting and locale topic
The full list, grouped by money, time, identity and generated language.
Writing systems in a PDF
The other half: whether the glyphs of a script come out shaped and joined correctly.
Values are formatted by your code before they reach the markup, so every fix on this page is upstream of the render. What the render decides is whether the result still fits.