Skip to content

A page has a date

Level: 201 · for anyone about to trust a manual

One line: man 5 utf8 on a 2026 Mac documents six-byte UTF-8 running to 0x7FFFFFFF and cites an RFC that was superseded in 2003 — and it is not wrong about the machine, because that is still the UTF-8 the machine's iconv accepts.

The date line is the first thing to read

Every man page prints its date in the bottom margin. Almost nobody looks, because a manual feels like a statement about the present tense. It is not; it is a document with a version, exactly like the Unicode table, and the version is printed on it.

Measured 2026-09-07 — macOS 26.6. The bottom margin of man 5 utf8.
macOS 26.6                    April 7, 2004                    macOS 26.6

April 2004. The page's own STANDARDS section says what it is a specification of:

The utf8 encoding is compatible with RFC 2279 and Unicode 3.2.

RFC 2279 ↗ is from January 1998 and was obsoleted by RFC 3629 ↗ in November 2003 — five months before the page was last touched. Unicode 3.2 is from 2002; the current version is in the twenties. So the page describes a standard that had already been replaced when it was written, and has been on every Mac since.

What the two tables actually differ about

The page prints six rows. The standard in force has four, and the fourth is shorter than the one printed. Rather than take that on trust, the example rebuilds both tables from the byte templates — no lookup, no table file, just the arithmetic that 8 - n - 1 free bits in the lead byte and six per continuation byte give you:

Verified output of a_page_has_a_date_py.py — regenerated by tools/run_examples.py, never hand-typed.

1. THE TABLE utf8(5) PRINTS -- RFC 2279, 1 TO 6 BYTES
   n  bits  highest       template
   1     7  0x0000007F    0bbbbbbb
   2    11  0x000007FF    110bbbbb, 10bbbbbb
   3    16  0x0000FFFF    1110bbbb, 10bbbbbb, 10bbbbbb
   4    21  0x001FFFFF    11110bbb, 10bbbbbb, 10bbbbbb, 10bbbbbb
   5    26  0x03FFFFFF    111110bb, 10bbbbbb, 10bbbbbb, 10bbbbbb, 10bbbbbb
   6    31  0x7FFFFFFF    1111110b, 10bbbbbb, 10bbbbbb, 10bbbbbb, 10bbbbbb, 10bbbbbb
   The last row reaches 0x7FFFFFFF: 31 bits, six bytes.

2. THE TABLE IN FORCE -- RFC 3629, 2003, 1 TO 4 BYTES
   n  bits  highest       still reachable
   1     7  0x0000007F    0x0000007F
   2    11  0x000007FF    0x000007FF
   3    16  0x0000FFFF    0x0000FFFF
   4    21  0x001FFFFF    0x0010FFFF  (row truncated)
   5    26  0x03FFFFFF    -             (row deleted)
   6    31  0x7FFFFFFF    -             (row deleted)

3. WHAT THE 2003 CAP ACTUALLY REMOVED
   highest code point, 1998 rule   0x7FFFFFFF  = 2,147,483,647
   highest code point, 2003 rule   0x0010FFFF  =  1,114,111
   numbers no longer encodable                 2,146,369,536
   that is 99.9% of the old space, and the
   whole of the 5- and 6-byte forms.  A decoder written from the older
   table accepts every one of them, which is why 'valid UTF-8' is not
   one question -- see 03_Encodings/validation_is_a_boundary.

4. THE ARITHMETIC, CHECKED AGAINST PYTHON'S OWN CODEC
   U+000041  41           == codec
   U+0000E9  c3 a9        == codec
   U+00017C  c5 bc        == codec
   U+0020AC  e2 82 ac     == codec
   U+01F600  f0 9f 98 80  == codec
   U+10FFFF  f4 8f bf bf  == codec
   U+110000  U+110000 is not encodable under RFC 3629
   U+200000  U+200000 is not encodable under RFC 3629

   Six agreements and two refusals.  The refusals are the rows the
   1998 table has and the 2004 man page still prints.

Section 1 is utf8(5)'s table, regenerated. Compare it against the page on your own machine and it matches row for row, which is the point: the page is precise. It is simply precise about 1998.

99.9% of the old code-point space is gone. RFC 3629 capped UTF-8 at U+10FFFF so that everything expressible in UTF-8 is also expressible in UTF-16, which deleted the five- and six-byte forms outright and truncated the four-byte one from 0x1FFFFF to 0x10FFFF.

The page is right about this machine

Here is the part that makes this more than a documentation bug. Ask iconv — which ships with the same libc the man page documents — whether a five-byte sequence is valid UTF-8, and it says yes.

That is not this page's finding; it is recorded, measured on both platforms, on Validation is a boundary, which is where the whole story of F4 90 80 80 belongs. What belongs here is the correspondence: the stale man page is an accurate description of the validator that is still installed. The documentation and the implementation are pinned to the same 1998 specification, and everything written since — Python, Rust, the browser — moved to the 2003 one without them.

So "the man page is out of date" is the wrong reading. The right one is that your machine contains more than one answer to "what is valid UTF-8?", and the man page tells you which answer that particular tool will give.

The three lines to read first

Before believing any page, in this order:

  1. The date in the bottom margin. man 5 utf8 says 2004. man 1 iconv and man 3 multibyte are the same vintage. man 1 git-config is from this year. The tree is not uniformly maintained and does not pretend to be.
  2. The STANDARDS section, if there is one. It names the specification, and specifications are dated and obsoleted in public. Look the number up: rfc-editor.org prints "Obsoleted by" at the top of every superseded RFC.
  3. The SEE ALSO section, which is where a page shows its age structurally rather than in prose. multibyte(3) refers you to mklocale(1), mkcsmapper and mkesdb. None of the three exists on macOS. A cross-reference to a page that was removed is a reliable sign the surrounding text was not revisited when it went.

If you are coming from Python or ABAP

Python stamps the version on the documentation URL — docs.python.org/3.13/ — and that is the single most useful habit to carry into any other manual: which version am I reading? The stdlib's own answer to the question this page asks is unicodedata.unidata_version, and the library's The table has a version works through why a value read out of that table can never become an answer key.

ABAP has the same shape with sharper edges: SAP Help Portal serves a version-selected page, and a search engine will hand you the 7.31 one for a system running 7.58. The Notes are the dated artifacts — a Note has a version number and a date, and reading an old version of a Note is the exact failure this page is about. Check the release dropdown before quoting a behaviour, and never quote a code-page number without verifying it against the system. (Not machine-checked — CI cannot run ABAP.)

Try it

  1. man 5 utf8 | tail -3 on your own machine. What year?
  2. Open RFC 2279 ↗ and read the header block. It says Obsoleted by: 3629 before it says anything else.
  3. man 3 multibyte, then try man 1 mklocale. Three cross-references, zero pages.
  4. Pick any tool you rely on and find its date line. man 1 tr, man 1 od, man 1 iconv. Then ask what has happened to text since.

Practice

Out of date, or wrong? man 5 utf8 on a 2026 Mac describes six-byte UTF-8 running to 0x7FFFFFFF and cites RFC 2279. State what is true of Unicode today, when it changed, and what the largest code point and its UTF-8 length actually are.

Then answer the question the page is built on: is that man page wrong? Justify your answer, then name the three independent dates any page carries and say which one is evidence about what your command will do.

Answers

Verified output of a_page_has_a_date_kata_py.py — regenerated by tools/run_examples.py, never hand-typed.

WHAT THE PAGE SAYS
   man 5 utf8 on a 2026 Mac describes UTF-8 as encoding up to SIX bytes,
   covering 0x00000000 to 0x7FFFFFFF, and cites RFC 2279.

WHAT IS TRUE OF UNICODE TODAY
   RFC 3629 superseded RFC 2279 in NOVEMBER 2003. UTF-8 is at most FOUR
   bytes and stops at U+10FFFF -- the ceiling UTF-16's surrogate pairs
   can express. Five- and six-byte forms are ill-formed, and every
   modern decoder rejects them.
   the largest code point:   U+10FFFF = 1114111
   its UTF-8 length:         4 bytes

SO IS THE PAGE WRONG?
   No -- and this is the part worth sitting with. That page documents
   the machine's own iconv, which still ACCEPTS the six-byte forms it
   describes. The page is an accurate description of a 1998 standard
   that a 2026 binary still implements. Unicode moved; that copy of the
   software did not; the page describes the software.

   'Out of date' and 'wrong' are different findings, and only one of
   them tells you to stop reading.

THE THREE DATES ON ANY PAGE, AND WHAT EACH IS EVIDENCE OF
   the date at the foot     when somebody last EDITED the page
   the standard it cites    which version of the world it describes
   the binary's --version   what will actually run
   They are three independent facts and they are routinely years apart.
   Only the third one is evidence about what your command will do.

HOW TO READ A PAGE THAT MIGHT BE OLD
   1. Find the citation. An RFC number, an ISO number, a standard's
      year -- that is the page telling you which era it is from.
   2. Check whether that standard was superseded. RFC 2279 -> 3629 is
      the example here; there are many.
   3. Then ask the binary anyway, because a superseded standard that is
      still implemented is exactly the situation that produces a bug
      report nobody can reproduce.

AND THE REASON THIS MATTERS BEYOND MAN PAGES
   The six-byte forms are not a curiosity. A decoder that accepts them
   accepts a SECOND spelling for code points that already have one --
   which is the overlong-sequence problem, and the reason RFC 3629
   tightened the definition rather than merely narrowing the range.

See also