propertime User Manual

This is the user manual for propertime, the time library in time.js. It is one function plus a few helpers attached to it. You give it an instant and you get a time value that prints in the system's own notation and in a limited set of other manners. The manual runs the library on itself, in that the script at the foot of the page executes every example block on the page and rewrites the printed output from that run, so the printed outputs are recomputed at every load.

The facts that matter most are these:

Rules for dates older than the Roman calendar reform are collected in a footnote at the end.

Loading the library

Under Node:

const propertime = require('./time.js')

In a browser the file sets a global named propertime:

<script src="time.js"></script>

The file is also served by the jsDelivr CDN from the project repository:

<script src="https://cdn.jsdelivr.net/gh/v31null/V0309S/time.js"></script>

The file also registers itself with AMD loaders through define. In every case you get the same function, and the helpers are properties on it.

Creating a time value

propertime(input, off_set_japan, is_day_time_saving, verbose)

All four parameters are optional.

input
The instant, as a string or an object. See the next section. If omitted, the current time is taken from Japan Standard Time.
off_set_japan
A string that shifts the result by hours relative to the JST baseline. See offsets below.
is_day_time_saving
A boolean. If true, exactly one hour is added. The library never applies daylight saving on its own.
verbose
A boolean. If true, the formal deep-time formats spell out every field, including fields at value 1, which are otherwise dropped.

Input formats

A timestamp string has this layout:

[Year][Month][Day][Hour][Min][Sec][AM or PM][Optional Suffix]
propertime('20260612000101AM')
propertime('450101000101AM A.C.')

The input may also be an object. With a STRING field it is unwrapped and parsed as above. With unit fields a timestamp is built from them. The fields for a normal date are YRS, MON, DAYS, HRS, MIN, SEC, and the HRS value carries the meridiem, for example '00AM'. Deep-time dates may instead use LAP and HOL for Stonehenge, the lap and the hole of the unit table under add, or SEA and DEC for Egyptian, where SEA picks the season, 1 for AKHET, 5 for PERET, 9 for SHEMU, and 4 for the five epagomenal days, MON counts the month inside the season, and DEC is the ten-day decan inside the month. The parser also reads back the canonical deep-time display lines themselves, as the round-trip answer shows. Both calls below equal the first string form above:

propertime({ YRS: 2026, MON: 6, DAYS: 12, HRS: '00AM', MIN: 1, SEC: 1 })
propertime({ STRING: '20260612000101AM' })

Offsets and daylight saving

The off_set_japan string is hours relative to JST. A leading M means minus. A tail of the form F<numerator>TO<denominator> adds a fraction of an hour.

StringMeaning
'3'+3 hours
'M3'-3 hours
'3F1TO2'+3.5 hours
'M1F1TO4'-1.25 hours

Passing true for is_day_time_saving adds one hour on top of the offset. The library does no other daylight saving, and any further rule you script yourself with the offset string or with add.

The returned value

The function returns a time value whose fields you can read directly:

year
Astronomical year as a string, any length, negative for the years before year 1, since there is no year 0.
month
Month number, 2-digit string.
day
Day of month, 2-digit string.
hr
Hour, 2-digit string, 00 to 11.
min
Minute, 2-digit string, 01 to 60.
sec
Second as a string, 2 digits, or 3 during the leap minute.
ampm
AM or PM.
tag
The resolved era marker: O.S., N.S., A.C., I.P., or empty. The parser fills it in when the year alone settles it.
verbose
The boolean passed as the fourth parameter.

Methods

add(n, unit)

Returns a new time value moved by n units. The original value is not changed. Subtraction is the same method with a minus sign on n, there is no separate subtract. The move respects the leap minute and the calendar gaps, so one second before 00:01:01 AM is the previous day at 11:57:240 PM, or at 11:57:241 PM when that day carries a leap second.

UnitStep
SEC1 second
MIN1 minute
HRS1 hour
DAYS1 day
WEEK7 days
MON1 calendar month
YRS1 year
DEC10 years, or 10 days when the date is in the Egyptian era
CEN100 years
MIL1000 years
YWL1 year, Turkic name
AY1 calendar month, Turkic name
KUEN1 day, Turkic name
LAP20,454 days, one Stonehenge lap
HOL365.25 days, one Stonehenge hole
SEAMoves nothing in add, in the modern and the Egyptian era alike, because SEA is the season field of the Egyptian object input described under input formats, and it is not a step unit.

getMeta(is_he)

Returns { displayYear, suffix }, the year as it should be displayed and the era suffix with its leading space. With is_he set to true the year is given in the Holocene count, which adds 10,000 to the astronomical year, and the canonical suffixes are kept.

toString(is_he, force_roman)

Returns the date as one line. For dates after the Roman era boundary the line is the civil form: year, month and day, time, meridiem, suffix. For older dates it returns the canonical deep-time line of the era instead, Egyptian, Sumerian, or Stonehenge. Pass true for force_roman to get the civil form even for an ancient date. is_he works as in getMeta.

toAltFormats(is_he, force_roman)

Returns an array of exactly 20 strings, each a different rendering of the same instant, indexed as listed in the format table. The flags mean the same as on toString.

Helper functions

propertime.setEgyptianEpoch(yearBCE)

Sets the anchor of the Egyptian reckoning. Accepted values are 2782, 2776, and 2773. The parameter keeps the conventional BCE numbering rather than this system's own, so that the value can be checked directly against the sources that cite the epoch. Setting it moves the boundary between the Egyptian and Sumerian eras, and the conversions stay consistent because they take their phase from the same anchor. Returns nothing.

propertime.getclndr(input, targetEra, is_he)

Builds a calendar for the period containing the given date. Returns an array of month objects, each with title, isEpagomenal, and days, where days is the list of day numbers. targetEra is AUTO, STONEHENGE, SUMERIAN, EGYPTIAN, or NATIVE, where AUTO picks the era the date falls in and NATIVE forces the frame of the engine's own civil calendar, which is also what AUTO arrives at for any modern date.

propertime.getclndrmodern(input)

Builds a Gregorian wall calendar for the year of the given date. Only years 1800 and later are accepted, earlier years throw. Returns { calendar, wknds }. calendar is 12 month objects, each with title, weekdays, rows of day numbers split into weeks, and emptyColspan for the blank cells of the last row. wknds maps month number to the day numbers falling on weekends. The interactive chapter at the end of this manual renders one of these months as a real table.

propertime.toJDN(civilYear, month, day)

Converts a civil date to its Julian Day Number, the day count the library uses internally as common ground between the calendars.

propertime.fromJDN(jdn)

Performs the reverse conversion and returns { y, m, d }.

propertime.jdnDiff(cy1, m1, d1, cy2, m2, d2)

Returns the number of days from the first civil date to the second.

The twenty alternate formats

Indices 0 through 9 are the everyday written forms. Indices 10 and 11 are the Old Turkic forms, which are computed for every era. Indices 12 through 19 are era-bound, and each shows a single dash when the date does not fall in its era. The third column holds the output for propertime('20260612000101AM'), and the column is recomputed from the same call when the page is loaded. For 2026 the Egyptian, Sumerian, and Stonehenge slots are dashes, while the English regnal slots are filled. Indices 3 to 5 differ from index 2 only when the month or the day is a single digit, June 12 has two digits of each, so they look alike here.

IndexFormatFor 2026-06-12
0Year/month/day2026/06/12
1Year, then month/day2026 06/12
2Digits run together20260612
3No padding2026612
4Padded month, unpadded day20260612
5Unpadded month, padded day2026612
6Full month name, padded day2026 june 12
7Full month name, unpadded day2026 june 12
8Short month name, unpadded day2026 jun. 12
9Short month name, padded day2026 jun. 12
10Old Turkic, Runic spelling169UNČ YUNT YWL , TOERTWNČ AY , 12NČ KUEN 00:01:01 AM
11Old Turkic, English169ᵗʰ HORSE YRS , 4ᵗʰ MON , 12ᵗʰ DAY 00:01:01 AM
12Egyptian formal—
13Egyptian short—
14Sumerian formal—
15Sumerian short—
16Stonehenge formal—
17Stonehenge short—
18English regnal formalCHARLES III’s 4ᵗʰ YRS’s 10ᵗʰ MON’s 5ᵗʰ DAYS 00:01:01 AM
19English regnal shortCHARLES III‑4⁄10⁄5

You pick one rendering by index:

const turkic = propertime('20260612000101AM').toAltFormats()[11]

With verbose set, the formal deep-time renderings name every field including those at value 1. The flag does not affect indices 0 through 9.

Examples

Each block shows a call and beneath it its output. On a loaded page the script at the bottom runs every such block on the page and rewrites the output from that run, so the printed value and the computed value are the same thing. Without the script the printed values remain, each of them the library's own output for the call above it. Blocks that read the current clock change with every load.

Getting the current time

propertime().toString()

Getting the date as one run of digits

propertime('20260612000101AM').toAltFormats()[2]

Addition, and subtraction with a minus sign

propertime('20260612000101AM').add(45, 'DAYS').toString()
propertime('20260612000101AM').add(-2, 'MON').toString()
propertime('20260612000101AM').add(-1, 'SEC').toString()

The same subtraction landing on a leap-second day of the 11-day cycle ends at 241 instead:

propertime('20260604000101AM').add(-1, 'SEC').toString()

The noon alignment

propertime('20260612116060AM').add(1, 'SEC').toString()

Offsets and daylight saving

propertime('20260612100101AM', 'M3').toString()
propertime('20260612100101AM', 'M3', true).toString()

Getting a calendar

propertime.getclndr('20260612000101AM', 'NATIVE').length

Days in a month

propertime.getclndr('20260612000101AM', 'NATIVE')[5].title
propertime.getclndr('20260612000101AM', 'NATIVE')[5].days.length

Weekend days of a month

propertime.getclndrmodern('20260612000101AM').wknds[6].join(',')

Day numbers and day arithmetic

propertime.toJDN(2026, 6, 12)
JSON.stringify(propertime.fromJDN(2461204))
propertime.jdnDiff(2026, 1, 1, 2027, 1, 1)

Ancient dates and the civil override

propertime('20000101000101AM A.C.').toString()
propertime('20000101000101AM A.C.').toString(false, true)
JSON.stringify(propertime('20000101000101AM A.C.').getMeta())
JSON.stringify(propertime('20000101000101AM A.C.').getMeta(true))

The verbose flag

The formal deep-time line drops fields at value 1, and the fourth parameter restores them. Both calls below name the same instant, the first day of an Egyptian year:

propertime({ YRS: 776, SEA: 1, MON: 1, DEC: 1, DAYS: 1 }).toString()
propertime({ YRS: 776, SEA: 1, MON: 1, DEC: 1, DAYS: 1 }, null, false, true).toString()

Building on propertime, cases and solutions

The cases below are the places where building on this library in 2026 changes code compared to the standard clock.

Will a two-character column hold the seconds?

No. The seconds field reaches 240 every night, 241 on leap-second days, and 242 on the two days where an official leap second joins the cycle, so a schema built for 00 to 59 truncates real data, and the column needs room for 3 characters.

propertime('20260612000101AM').add(-1, 'SEC').sec

The stored 3-digit stamp parses back without any special handling:

propertime('202606031157241PM').toString()

Can I pass a form field through unchecked?

Not if it can contain 00 minutes or seconds. The parser does not reject a 00 second, it reads it as a step back across the boundary, so the value silently lands on the previous day.

propertime('20260612000100AM').toString()

Validate the range 01 to 60 yourself before the string reaches the parser.

Where did 12 o'clock go?

There is no hour 12 anywhere in the system. The hour after 11:60:60 AM carries hour 00 and meridiem PM. A display layer that special-cases 12 for noon and midnight, the way standard formatters do, prints a wrong value here. The hour field is rendered as it is.

const noon = propertime('20260612116060AM').add(1, 'SEC')
noon.hr + ' ' + noon.ampm

How long is the last minute of the day?

It runs to 240 seconds normally. From 11:57:01 PM, 239 more seconds still sit inside the same minute:

propertime('20260612115701PM').add(239, 'SEC').toString()

One further second crosses into the next day:

propertime('20260612115701PM').add(240, 'SEC').toString()

On a leap-second day of the 11-day cycle the same minute holds one second more, and June 3 of 2026 is such a day because its Julian Day Number, 2461195, divides by 11:

propertime('20260603115701PM').add(240, 'SEC').toString()

The two lengthenings stack. On a day that falls on the 11-day cycle and carries an official leap second at once, the minute reaches 242 seconds, and the timeline holds exactly two such days, 1 July 1994 and 1 January 2006:

propertime('20060101115701PM').add(241, 'SEC').toString()

So neither 60 nor 240 nor even 241 is a safe ceiling to hardcode for a minute.

Is plain second arithmetic safe across that stretched minute?

Yes. The engine absorbs the leap minute internally, so adding 86,400 seconds lands exactly where adding one day lands. Use whichever unit reads better, the results agree.

propertime('20260612000101AM').add(86400, 'SEC').toString()
propertime('20260612000101AM').add(1, 'DAYS').toString()

How do I send a value over JSON or store it in a row?

Concatenate the fields back into the wire format the parser reads. The fields are already zero-padded, so the round trip is exact. Append the tag when it is not empty, the overlap years need it.

const t = propertime('20260612093101AM')
const wire = t.year + t.month + t.day + t.hr + t.min + t.sec + t.ampm
propertime(wire).toString() === t.toString()

Do not store the output of toString instead, it is a display form, the parser does not read it back, and the round-trip answer later in this manual shows the mangling.

How do I index dates in a database?

For modern data the wire stamp itself sorts correctly, because the format runs big to small, year then month then day, so alphabetical order is chronological order while the years keep the same width:

['20261105', '20260612', '20250101'].sort().join(' ')

The caveat is the width of the year, which is not padded, so once the data crosses into years of another length, three digits, five digits, or the A.C. side, string order breaks. For data that leaves the modern window, store the Julian Day Number as an integer key, it is what the library itself uses as common ground between the calendars:

propertime.toJDN(2026, 6, 12)

How do I write a five minutes ago label?

Step the current time back 300 seconds. The block reads the current clock, so its output differs from load to load.

propertime().add(-300, 'SEC').toString()

My users are not in Japan, what do I show them?

The baseline is JST and GMT is 9 hours behind it, so a London clock is the current time at offset M9. These two blocks also read the current clock.

propertime(null, 'M9').toString()

British Summer Time remains your decision, because the library never applies it by itself. Pass true and the hour is added:

propertime(null, 'M9', true).toString()

How many days until my deadline?

propertime.jdnDiff(2026, 6, 12, 2027, 1, 1)

What happens to historical data crossing September 1752?

Day arithmetic steps over the eleven wiped days on its own, and the style tag flips with it. One day after September 2 is September 14, and the result comes back tagged N.S. automatically.

propertime('17520902000101AM O.S.').add(1, 'DAYS').toString()

Can it do milliseconds, microseconds, atomic precision?

No. The second is the smallest unit anywhere in the system, in the fields, in the parser, and in add. This is a human clock, sub-second timing belongs to a different craft, and I can not claim the qualification to code it. Feeding a fraction in does not extend the precision, it only breaks the field, as the example below shows, so keep your quantities whole.

propertime('20260612000101AM').add(0.5, 'SEC').toString()

What does garbage input do?

It throws. The parser fails fast on impossible dates instead of normalizing them the way the standard Date object does, so wrap the parsing of user input in a try block and treat the message as the validation result.

let msg = 'parsed fine'
try { propertime('20260230000101AM') } catch (e) { msg = e.message }
msg

Databasing, storing and querying stamps

The SQL in this chapter is fixed text, since a browser does not run SQL, and each statement is therefore paired with its JavaScript equivalent. The equivalence holds because a stamp is plain ASCII, and the byte order an engine uses to compare TEXT agrees with the code-unit order JavaScript uses to compare strings.

How do I store it, and would anything break?

Store the raw wire stamp in a plain text column and nothing breaks, because to the engine the stamp is an opaque string, stored, compared, and returned byte for byte, while all parsing and arithmetic stay in your code. The library is one file and runs in any JavaScript host, so a backend in another language, for example PHP, can hand stamps to it through a Node process or an embedded JavaScript engine, and the rows themselves never need the library at all. Validation happens in code before the INSERT, as the cases chapter does for the 00 second, because a CHECK constraint cannot carry the notation's rules, the 01 floors and the 240 ceiling that rises to 241 on the cycle days.

CREATE TABLE events (
  id      INTEGER PRIMARY KEY,
  title   TEXT NOT NULL,
  stamp   VARCHAR(20) NOT NULL,
  jdn     INTEGER NOT NULL,
  timekey TEXT NOT NULL
)

The jdn and timekey columns are derived keys explained under the ordering question below, written once at INSERT from the same value the stamp came from. The six rows below serve the examples of this chapter:

INSERT INTO events (title, stamp, jdn, timekey) VALUES
  ('spring deadline',   '20260530101010AM',      2461191, 'AM1010010'),
  ('leap minute end',   '202606031157241PM',     2461195, 'PM1157241'),
  ('morning meeting',   '20260612093101AM',      2461204, 'AM0931001'),
  ('afternoon meeting', '20260612003101PM',      2461204, 'PM0031001'),
  ('july start',        '20260701000101AM',      2461223, 'AM0001001'),
  ('styled history',    '17520902000101AM O.S.', 2361221, 'AM0001001')

Though it is apt to say that stamp is enough by itself for modern times.

Is VARCHAR(20) wide enough?

For the modern window yes. A modern stamp runs 16 characters, the leap minute adds one for its 3-digit second, and a five-digit year would add one more, so 20 holds everything the present era writes. The mandatory style tags of the overlap years push a stamp to 21 and 22 characters, and A.C. years grow with their digits without a bound, so historical data either sizes the column for its era or leans on the JDN key from the cases chapter. Engines also differ on enforcement. SQLite records a declared width and never enforces it, so a 21-character stamp enters a VARCHAR(20) column unchanged, while stricter engines truncate or refuse, and the declared width therefore matters on every engine except SQLite. A modern stamp, a leap-minute stamp, and the tagged stamp of 1752 have these lengths:

const wire = t => t.year + t.month + t.day + t.hr + t.min + t.sec + t.ampm + (t.tag ? ' ' + t.tag : '')
const a = wire(propertime('20260612093101AM'))
const b = wire(propertime('20260612000101AM').add(-1, 'SEC'))
const c = wire(propertime('17520902000101AM O.S.'))
a.length + ', ' + b.length + ', ' + c.length

Does it convert to ISO 8601?

No. None of the twenty formats is ISO 8601, and none contains even an ISO date shape:

propertime('20260612000101AM').toAltFormats().filter(f => /\d{4}-\d{2}-\d{2}/.test(f)).length

Nor can a faithful field-for-field conversion exist, because ISO 8601 rests on an 86,400-second day with hours 00 to 23 and minutes and seconds 00 to 59, while this notation has no hour 12, no 00 minute or second, and a last minute of 240 seconds and more. Where an external system demands ISO 8601, the translation belongs to your own layer, and it will not survive the leap minute intact.

Does ORDER BY put stamps in time order?

The order holds to the day and fails within the day. The date digits lead the string, so stamps of one year width sort chronologically by day, as the cases chapter showed. Inside one day the meridiem sits at the tail where it cannot influence the comparison, so an afternoon stamp whose hour digits are small sorts ahead of a morning stamp whose hour digits are large:

['20260612003101PM', '20260612093101AM'].sort().join(' ')

The remedy is the pair of derived key columns from the schema above. jdn carries propertime.toJDN of the date and orders the days across any year width, and timekey carries the meridiem first and then the time fields, with the second padded to 3 digits because the leap minute writes a 240 where ordinary minutes write at most a 60, and the strings only compare correctly at one width:

const key = t => t.ampm + t.hr + t.min + t.sec.padStart(3, '0')
const morning = key(propertime('20260612093101AM'))
const afternoon = key(propertime('20260612003101PM'))
morning + ' ' + afternoon + ' ' + (morning < afternoon)
SELECT stamp FROM events ORDER BY jdn, timekey

How do I query a date range?

A range of dates is selected with the date digits as string bounds. Every stamp of June 2026 begins with 202606, every stamp of an earlier month compares below that, and every stamp of a later month compares at or above 202607, whatever time of day the stamp carries. Against the rows above, a query would return the three stamps of June 2026, the leap-minute stamp of June 3 among them:

SELECT stamp FROM events
WHERE stamp >= '202606' AND stamp < '202607'
ORDER BY jdn, timekey

The comparison behaves identically on plain strings:

['20260530101010AM', '20260612093101AM', '20260701000101AM'].filter(s => s >= '202606' && s < '202607').join(' ')

The bound trick carries the same caveat as the sorting, it holds while the years keep one width, and data of mixed widths queries on the jdn column instead, WHERE jdn BETWEEN the two day numbers.

How many days lie between two stored dates?

The jdn columns subtract as plain integers, so the number of days between two rows is a single subtraction in the query, and the same number comes from the library directly:

SELECT b.jdn - a.jdn AS days FROM events a, events b
WHERE a.title = 'morning meeting' AND b.title = 'july start'
propertime.toJDN(2026, 7, 1) - propertime.toJDN(2026, 6, 12)

Two users share one calendar and write at the same moment, whose write wins?

That question lies outside the stamp. The stamp names the instant to the second and no finer, two writes inside one second carry equal stamps, and an equal stamp gives the engine no tiebreaker. Which write lands is decided by the database's own locking, transactions, and conflict rules, so the answer is configured in the database, and the notation neither helps nor hinders that configuration. Where the order inside one second must also be kept, the PAQ describes the numerus tertius counter, a row number the engine assigns under those same rules.

Try it in the browser

This chapter computes nothing ahead of time. Type a timestamp, pick a step, and the script at the foot of the page runs the library on what you typed, so the outputs and the month table below are recomputed at every keystroke. The default step of -300 SEC answers what the moment was five minutes earlier. If the input does not parse, the first line shows the parser's message. This chapter is the one part of the manual that needs the script, everything else reads without it.

toString
After the step
Wire stamp
Old Turkic
English regnal
Month

The wall calendar of the typed month, built from getclndrmodern, which serves years 1800 and later:

Live tests

The list below is drawn from the Moment.js project guides, the nine-year history of the Temporal proposal, and the history of the Date object.

The table fills when the page is loaded beside time.js.

« Can I »s

The questions below are shortcuts that developers commonly ask for, and the answer to each of them is no, given each time with the reason and with the way that works instead.

Can I keep the database in whatever the user typed?

No. A stored local time leaves open the question of whose locality it was written in, and every later reader of the row inherits that open question. Keep one canonical wire stamp in the row, the proper time itself, and localize at the edge where the user meets it. In the example below the stored row stays the same while the reading of it changes with the offset.

const stored = propertime('20260612093101AM')
const wire = stored.year + stored.month + stored.day + stored.hr + stored.min + stored.sec + stored.ampm
wire + ' stays in the row, a London reader sees ' + propertime(wire, 'M9').toString()

Can I feed the string it gave me back in, or is it one-way?

For the modern civil line it is one way, and the failure is silent, the parser does not throw. The display line carries separators the compact parser never expects, the fields shear apart, and the result is a wrong value with a six-digit year and NaN in every time field. The example below feeds a display line straight back in.

propertime(propertime('20260612093101AM').toString()).toString()

So never store or resend a display line, the two-way form is the wire stamp built from the fields, shown in the first answer of this chapter. There is one exception in the other direction. The canonical deep-time lines belong to the parser's own grammar, so for an ancient date the display line does round-trip exactly:

const a = propertime('20000101000101AM A.C.')
propertime(a.toString()).toString() === a.toString()

One caveat remains even there. The formal line drops fields at value 1 unless the verbose flag is set, as the examples chapter shows, and a line with dropped fields does not parse back:

let msg = ''
try { propertime('776ᵗʰ YRS’s AKHET 00:01:01 AM') } catch (e) { msg = e.message }
msg

So an ancient display line meant for storage should be the full line, the one that names every field.

Can I set M9 as a global tag, since all my users are in one country?

No. Although it simplifies the logging, it creates a language barrier for other propertime users, your stamps stop meaning what theirs mean. As the creator I would suggest to use, well, the proper time, then give the users a simple form. Something like the selector below, alarm-clock style, a direction, hours, and minutes, which writes the offset string for you and shows that clock running. Let users do their stuff and do not touch it unless necessary. You may add pre-ready checks in your own layer, if their daylight saving is not strictly one hour you may fix it there. This is a suggestion though, if it works it works. The selector carries the only CSS in this manual, as a demo of what the form can be in your own page, while the document around it stays bare.

Offset string
That clock right now

Shall I apply daylight saving for the user myself?

No. The engine never moves the clock for daylight saving and your layer should not guess either, the flag is the user's to set. When it is set, the hour added is exactly one, the two examples below differ by nothing else.

propertime('20260612100101AM', 'M9').toString()
propertime('20260612100101AM', 'M9', true).toString()

Can I zero-pad the year so the columns line up?

No, although the parser itself forgives the padding and reads the same instant from a padded year:

propertime('020260612000101AM').toString() === propertime('20260612000101AM').toString()

Yet the padded stamp sorts ahead of everything unpadded, here a November lands before a June, so an index built on the lined-up column returns the wrong order. Keep years unpadded, and for data of mixed year widths sort on the Julian Day Number as shown in the cases chapter.

['020261105000101AM', '20260612000101AM'].sort().join(' ')

PAQ, possible askable questions

The overview states a day is 86,164 seconds, yes, but stretching the last minute to 240 seconds adds up to exactly 86,400. Is the day 86,164 or 86,400 seconds?

The mathematical sum of the labels does reach ≈86,400 to match the civil solar day, but the day genuinely contains only ≈86,164 true seconds. The Earth's rotation, the sidereal day, completes in exactly ≈86,164 seconds, which lands exactly at 11:57:04 PM. The 57ᵗʰ pars minuta prima officially ends at its 4ᵗʰ, 5ᵗʰ, or 6ᵗʰ pars minuta secunda, meaning; any count outside of that bound is, by definition, not a true pars minuta secunda. The values from 5 to ≈240 are a deliberate fiction introduced to pad the timeline, an artificial stretch that holds the clock back to keep the sun at its zenith at noon, the leap minute simply acknowledges that the true seconds run out at ≈86,164. The remaining ≈236 counts are filler labels to bridge the gap to the next solar morning.

Because the modern computing stack is permanently hardwired, from the silicon up to the language specification, to expect a civil day of exactly 86,400 seconds, despite being wrong. At the hardware level, time is driven by 32.768 kHz quartz oscillators feeding the motherboard’s RTC circuitry as-alike HPET, which possess no mechanical concept of sidereal rotation. Above the silicon, the operating systems are governed by the POSIX standard (IEEE 1003.1), which strictly defines a day as 86,400 seconds. Finally, JavaScript engines themselves are bound by the ECMA-262 specification, which hardcodes the internal calendar integer as msPerDay = 86,400,000.

Since it is impossible to alter the world's motherboards or rewrite the V8 engine's core C++ logic, the library code acts as a translation layer. It accepts the rigid 86,400-second mathematical delusion forced by the hardware and-then normalizes it into the real notation, parsing the flat civil integer into the true sidereal sequence and its padding.

I originally tried to omit that strange padding entirely and end the day exactly at 86,164. However, severing those final counts causes insurmountable incompatibility problems, as external systems, databases, and network protocols categorically expect that span of time to exist. Building a fundamentally new hardware clock to enforce this is above my limits as a person. If I possessed the means, the solar zenith problem would be handled simply by skipping the excess. The day would end abruptly the moment the sidereal rotation completes, much in the same way we do not care to enforce a base-80 logic on the pars minuta prima, thus; what you see implemented here is therefore a compromise with a flawed world.

I repeat: those extra counts stretching the last pars minuta prima to 240 do not, in reality, exist, they are an artificial bridge constructed purely for compatibility.

What do the extra leap seconds (241 or 242) in the final minute actually mean? Do we truly need them in this system?

Unless you are measuring timelines across, milennia, millions, or milliards of years: no.

The Earth’s physical rotation slows by roughly 1.7 milliseconds per century. It would take geological epochs for the true duration of the planet's rotation to shift by a full pars minuta secunda. Modern scientifically added leap seconds have nothing to do with the real day as they are added entirely because of the sun day, which carries to propertime logic because of the way computers process time, as since; atomic clocks run at a rigid speed, they eventually drift out of phase with the sun’s zenith, meaning: the official leap seconds are inserted merely to push the civil clock back into alignment with the sun, therefore; since the final counts of our stretched minute are already an artificial fiction meant to bridge the gap to the next solar morning, an official leap second is simply an adjustment made to the padding, not an adjustment to time itself.

Why do minutes and seconds run from 01 to 60, and why does the hour begin at 00?

The fields count different things. Minutes and seconds carry ordinal labels, the minute, pars minuta prima , in progress is the first and the second, pars minuta secunda, in progress is the first, so the labels run 1 to 60 and the last label equals the count, as romans intended. The everyday statement that a minute holds 60 seconds, and an hour 60 minutes, is read directly off the notation, where the standard 00-to-59 labeling tops out at 59 and the number 60 never appears in a label, except for scientifically added leap‑seconds. Counting from 1 follows the natural numbers, shown as ℕ* or ℤ⁺, as axiomatized by Giuseppe Peano in Arithmetices principia, nova methodo exposita, 1889, whose first axiom places 1 among the numbers, « Axiomata. \  \ 1. 1∈N. ». The hour field counts completed units instead, a cardinal, and during the first hour zero hours are complete, so the day opens at 00:01:01 AM, zero hours done, first minute, first second. The distinction between the ordinal and the cardinal number is the set-theoretic one drawn by Georg Cantor in Beiträge zur Begründung der transfiniten Mengenlehre, 1895, Article I (1895), §§ 1 „ Mächtigkeit oder Kardinalzahl „ & 7 „ Die Ordnungstypen einfach geordneter Mengen „ as well as Article II (1897), § 14 „ Die Ordnungszahlen wohlgeordneter Mengen „, reachable at Göttinger Digitalisierungszentrum starting from p. 500 or archive.org. The largest labels exist and parse:

propertime('20260612116060AM').toString()
Will it break a standard web app, a database, or an API?

No. Outside the library the stamp is a plain ASCII string, and a string breaks nothing, it rides JSON, query parameters, headers, and text columns unchanged:

const s = '202606121157240PM'
JSON.parse(JSON.stringify({ stamp: s })).stamp === s

Three worries recur. The first is that the stamp cannot be cast to a standard Date object or an ISO 8601 string. That is true, and it is the design rather than a defect, no converter is shipped in either direction, and the database chapter sets out why a faithful field-for-field conversion cannot exist.

The second is that a relational database would reject a timestamp containing 240 seconds. No engine ever sees the 240 as seconds. The stamp lives in a text column, where it is stored, compared, and returned byte for byte, and even a column declared TIMESTAMP takes it whole under SQLite, whose column types are affinities rather than rules. A stricter engine refuses the stamp in a native datetime column, but it refuses the whole notation equally, with or without a 240, because the shape is not the engine's date literal at all, which is why the schema of the database chapter stores text and derived keys and never a native datetime.

The third is that alphabetical sorting breaks across an era boundary or on an A.C. year. That is true and is the standing rule of the cases and database chapters, string order holds only while the years keep one width. Every year from 1000 to 9999 is four digits, so modern rows sort clean into the year 9999, and the data that does leave that window, the A.C. side included, sorts on the jdn integer column derived for that purpose.

How far back does it run, and do all the formats run that far?

The day count itself has no practical floor, it runs past the Sumerian kings into Stonehenge deep time, where years do not exist and the canonical line is given in laps and holes instead. Each named format has its own era window, and outside it the format table shows a dash. The Old Turkic overlay alone is computed across the whole line.

propertime('3000000101000101AM A.C.').toString()

The engine's BigInt translation layer is mathematically unbounded, it will successfully return complete, accurate calendar arrays for years spanning dozens of digits, both forward into the modern era and backward into Stonehenge deep time.

propertime('23568312759253262346436346436125072358392052 0517 110199AM A.C.').toString()
propertime('23568312759253262346436346436125072358392052 0517 1157239PM').toString()
propertime('23568312759253262346436346436125072358392052 0517 1157239PM').add(10, 'SEC')
propertime('235683127591532623464363464361250723583920510101000101AM').add(10000, 'YRS').year
propertime('23568312759153262346436346436125072358392051 0101 000101AM A.C.').add(1, 'YRS').add(2, 'MON').add(16, 'DAYS')
propertime('23568312759153262346436346436125072358392050 0317 000101AM A.C.').add(-1, 'YRS').add(-2, 'MON').add(-16, 'DAYS')
JSON.stringify(propertime.getclndrmodern('235683127591532623464323526743773473265123521634643612507235839205 0101 000101AM'))
propertime.getclndrmodern('235683127591532623464323526743773473265123521634643612507235839205 0101 000101AM').calendar[0].weekdays.join(',')
propertime.getclndrmodern('235683127591532623464323526743773473265123521634643612507235839206 0101 000101AM').calendar[0].weekdays.join(',')

However; while the arrays return perfectly, the calendar is proleptic: it blindly overwrites physical reality with mathematical extrapolation, the reasons for this is outside of this project’s control.

  1. Human political structures, civil timekeeping, and planetary mechanics will inevitably change as‑so the engine simply projects the modern 12-month calendar and the reign of CHARLES III endlessly forward.
  2. The 400-year leap cycle is a civil approximation. Over tens of thousands of years, it loses tracking with the true solar year, extrapolating it by decillions of years guarantees it is entirely out of phase with the seasons, similarly; assuming the Stonehenge lunar standstill mechanics remain perfectly stable across infinity is simply our best mathematical bet to keep the deep-time scale running.
  3. The universe did not exist before ≈13.8 milliard years ago.
The deep-time path uses BigInt, so; will that slow down a web application or a database insert?

The entry into the BigInt path is a single string-length comparison, ayStr.length > 14, performed once on the year string before any arithmetic begins, and a string's length property in V8 is a cached integer stored in the string header, read without traversing any characters, so the gate itself costs nothing measurable. What follows the gate is where the cost lies: JavaScript's BigInt type executes on the CPU's arbitrary-precision integer path rather than on the float-64 fast path, and those two paths are not comparable in throughput.

Measured at 10,000 iterations with warmup, the BigInt overhead relative to a normal-year call runs 1.50 times slower at parse, 1.16 times slower across toAltFormats, 3 to 4 times slower on add with day and year steps, and up to 7 times slower on isolated helpers such as getMeta, where the BigInt branch converts between representations more often. Those ratios sound consequential, but the absolute figures resolve the question: the heaviest single operation, a complete toString that derives the Stonehenge lap-and-hole position from a forty-two-digit year, completes in roughly 4.6 microseconds on a modern consumer machine, and a plain toAltFormats on the same date takes about 3.2 microseconds. A million such calls would finish in under five seconds. A server handling ten thousand deep-time inserts per second spends fewer than 46 milliseconds per second in the library, and almost none of that time is in BigInt arithmetic specifically.

In a database schema, BigInt does not appear at all. The derived jdn column is a plain integer, computed once at insert time and written to the schema as a regular numeric literal, meaning; even a Stonehenge date's day count, which the engine holds internally as a BigInt during the library call, arrives at the prepared statement as an ordinary string representation of a whole number, accepted without modification by SQLite, PostgreSQL, or any other engine. The forty-two-digit year itself lives in a text column and is never passed through a native datetime or numeric column type, so no engine ever sees a BigInt in the schema at all.

How far forward does the regnal format run?

It runs to the reigning monarch, whose reign has no end written in the table, and so the current reign extrapolates forward without a bound.

propertime('21000101000101AM').toAltFormats()[18]
I have dates of people in O.S., N.S., or unlabeled, is the regnal result the real result, or is there a mismatch?

The regnal result is the real one when the label matches how the date was recorded, because every input funnels through the one day count and the regnal table reads nothing else. Unlabeled dates resolve to the English civil reckoning of their era, Old Style before 1752 with the Lady Day year, New Style after, which is also how English lives were written down, so an English-sourced date passes through as recorded. Queen Anne died on 1 August 1714, and on that exact day the table still returns her reign:

propertime('17140801000101AM').toAltFormats()[18]

The demise day itself prints the departing sovereign, and the successor's count begins on that same day, so the morrow of the accession already reads as the second day:

propertime('17140802000101AM').toAltFormats()[18]

The mismatch risk comes from the year label rather than from the table. Contemporaries wrote the execution of Charles I as 30 January 1648, the year by Lady Day reckoning, and typed as written it lands rightly in his final days:

propertime('16480130000101AM').toAltFormats()[18]

Modernize that year yourself to 1649 and the parser takes the year as written and shifts it by Lady Day again, so you land a whole year onward, in the interregnum:

propertime('16490130000101AM').toAltFormats()[18]

So feed old dates exactly as the records wrote them and let the parser do the shifting. A continental New Style date inside the Old Style window is refused outright rather than mangled, convert such dates to the English reckoning in your own layer before parsing:

let msg = ''
try { propertime('17140801000101AM N.S.') } catch (e) { msg = e.message }
msg

The second half of the question is whether the count itself is correct, whether Anne had truly reigned that long, whether Charles III is really that deep into his reign, and the counts hold to the historical record one reign at a time. Anne acceded on 8 March 1702, written 8 March 1701 the Old Style way, and the engine opens her count on that very day, so the morrow already reads the second day:

propertime('17010309000101AM').toAltFormats()[18]

From an accession on 8 March 1702, the first of August 1714 falls in the thirteenth year, so the line printed on her last day is the correct one. The precision holds deep into the middle ages, William the Conqueror was crowned on Christmas Day 1066 and his count opens on the coronation:

propertime('10661226000101AM').toAltFormats()[18]

For Charles III both the year and the day are true. He acceded on 8 September 2022, the accession day itself prints Elizabeth II deep in her seventy-first year, by the demise-day rule above, and the morrow already reads his second day:

propertime('20220908000101AM').toAltFormats()[18]
propertime('20220909000101AM').toAltFormats()[18]

His fourth year opens on the true anniversary, 8 September 2025, and runs on through 2026:

propertime('20250908000101AM').toAltFormats()[18]

Victoria holds the same way, her accession of 20 June 1837 prints William IV's last day and the morrow her second:

propertime('18370621000101AM').toAltFormats()[18]

So the years agree with the record and the days agree with it too, on both sides of the change of style. One reservation attaches to the whole answer. The anchors grow coarser as the table runs further back, because the chronicle that supplies them grows coarse itself, and where the record gives a year and no day, the reign is anchored on the first of January of that year, which is exactly where the earliest Wessex counts open:

propertime('5190102000101AM').toAltFormats()[18]
propertime('8020102000101AM').toAltFormats()[18]

That imprecision comes from the record and not from the library, for the chronicle is the only source there is, and where it gives no day the first of January stands as a declared placeholder rather than as a claim that the day is known.

Where is the Old Turkic cycle anchored?

The cycle is anchored at year 1, which falls 4 years after the birth of jeshua, so it keys to the year count and not to the birth itself. The Turkic year opens in spring, and its first month ARAM falls in March of year 1.

propertime('10301000101AM').toAltFormats()[11]

January of year 1 still belongs to the closing months of the cycle before it:

propertime('10101000101AM').toAltFormats()[11]
Does the Holocene flag change the birth logic?

The birth keeps its role under the flag, for a date before the birth keeps its A.C. beside the Holocene year. The I.P. tag shifts its duty there, in Holocene display it marks only dates before the Holocene zero, and the boundary years lose it because their Holocene year is already unambiguous.

JSON.stringify(propertime('100101000101AM A.C.').getMeta(true))
JSON.stringify(propertime('20101000101AM I.P.').getMeta(true))
JSON.stringify(propertime('100500101000101AM A.C.').getMeta(true))
Is an I.P. year still a minus year?

Yes. The year field stays negative, the tag only says the span is not Ante Christum, since the birth came earlier than year 1.

propertime('20101000101AM I.P.').year
Does it forgive saying A.C. in place of I.P.?

Yes, in both directions. The parser reads either tag as the minus sign and nothing more, then the display layer assigns the proper tag from the date itself. Type A.C. on the post-birth year 2 and it comes back corrected:

JSON.stringify(propertime('20101000101AM A.C.').getMeta().suffix)

Both spellings parse to the very same value:

propertime('20101000101AM I.P.').toString() === propertime('20101000101AM A.C.').toString()

And the reverse forgiveness holds, an I.P. on a year deep before the birth comes back as A.C.:

JSON.stringify(propertime('1000101000101AM I.P.').getMeta().suffix)
Which leap year rule applies?

The English rule of the era applies. Before September 1752 the engine keeps the Julian rule, every fourth year, so the year 1700 has its February 29. After the change the Gregorian rule holds, 2024 is leap, 1900 and 2100 are not. The labeling needs care, because under Old Style the civil year for January through March 24 runs one behind, so the historical 29 February 1700 is reached by typing year 1699.

propertime('16990229000101AM').toString()
propertime('20240229000101AM').toString()
let msg = ''
try { propertime('21000229000101AM') } catch (e) { msg = e.message }
msg
What is the internal epoch?

The Julian Day Number is the common ground every conversion passes through, and its day zero falls deep in the fifth millennium before year 1, read here in the engine's own civil reckoning, where ancient months follow the Roman order.

JSON.stringify(propertime.fromJDN(0))
Does it ship a time zone database?

No. Offsets are explicit strings against the JST baseline, the library resolves no zone names and carries no table of them. That mapping belongs to your layer, as the selector in the chapter above suggests.

Is it immutable, and what does it depend on?

It is immutable, as the live tests chapter shows, and it depends on nothing beyond itself, being one file that serves Node, AMD, and the bare browser.

Does it speak locales?

There is no locale system. The outputs are fixed, English for the civil and regnal lines, the era tongues for the rest, Turkic runic spelling, Egyptian seasons, Sumerian kings. Where you need translation, that work belongs to your layer.

Can it do sub-second precision?

No. The second is the smallest unit in the system, and the cases chapter answers the same question in full.

I work in sub-seconds, how do I do that here?

By counting rather than dividing. The historical continuation of the notation would be pars minuta tertia and pars minuta quarta, the third and fourth small parts that the old reckoning set below the secunda, and as the developer I thought of adding them. The times have changed though, and what sub-second work needs in practice is not a finer wall reading but the order of events inside one second, so the third position is a counter and not a new division, and it is named numerus tertius rather than pars minuta tertia, because a counter is a number of a record and not a part of a minute.

The counter declares itself. Records landing in the same secunda take tertius 1, 2, 3 in the order the store commits them, and that order is assigned by the engine's own machinery, file locks, row locks, transactions, the key sequence, the same rules the database chapter's last answer points to. The clock names the secunda, the store names the order inside it, and the count runs in ℕ*, from 1, by the counting logic of the first entry of this chapter. First record of the secunda 10:56:60 would be called as, in verbose terms: 10 hours’ 56ᵗʰ prima 60ᵗʰ secunda 1ˢᵗ tertius

The next record of the same secunda is 2ⁿᵈ tertius, id est tertius 2, and the number comes from the row order, not from any clock, so forth. You may add it as a counter, for PHP, an example:

<?php
$db = new PDO('sqlite:events.sqlite');
$db->exec('CREATE TABLE IF NOT EXISTS events (
  stamp   TEXT NOT NULL,
  tertius INTEGER NOT NULL,
  UNIQUE (stamp, tertius)
)');

$stamp = '20260612105660AM';
$db->exec('BEGIN IMMEDIATE');
$db->prepare('INSERT INTO events (stamp, tertius)
  SELECT ?, COALESCE(MAX(tertius), 0) + 1 FROM events WHERE stamp = ?')
   ->execute([$stamp, $stamp]);
$db->exec('COMMIT');

Each run inside the same secunda takes the next number, the first writes tertius 1, the second tertius 2, and a fresh secunda starts the count at 1 again. Two processes landing at once serialize on the immediate transaction, the engine's own write lock, and the unique pair of stamp and tertius stands behind that as the table's own rule, so a database prepared this way for actions below the pars minuta secunda never clashes.

What is the drag flag, and how does it reckon a day?

The constructor takes a fifth positional argument after the input string, the Japan offset, and the daylight and verbose booleans, a boolean named drag, and when it is true the engine stops treating the day as the padded 86,400 seconds of civil convention and gives it instead the real length of the Earth's sidereal rotation for the epoch the date falls in, so the day closes at the moment the planet has actually turned once. Ordinary mode keeps the long arrangement where the true rotation finishes at 11:57:04 in the evening and the counts that follow, up to 240, are an admitted padding that holds noon against the sun, and drag drops that padding, ending the day at the instant the rotation completes even when that instant falls inside a labelled minute, which is the behaviour the library's overview has always named as the thing it would do were the surrounding machinery of the world willing.

A day under drag is no longer a fixed 24 hours, so the clock face cannot be set by halving at 12, and the meridiem, the turn from ante to post that marks the sun at its zenith, sits at the true midpoint of whatever the day's real length is, while the hours run as real hours of 3,600 seconds each, counted from zero within each half, so that a short ancient day of 15 hours still keeps a real morning and a real afternoon and never borrows the arithmetic of a 24 hour day. A present-day date opened in drag and carried to the last whole second of its rotation reads late in the evening before it rolls:

propertime('20260614000101AM', '', '', false, true).add(86163, 'SEC').toString()

and the next second rolls the date and opens the following day at its first second:

propertime('20260614000101AM', '', '', false, true).add(86164, 'SEC').toString()

The length of a day on its own, read without stepping the clock, comes from the lengthOfDay helper on the constructor, which takes the same input forms and returns the rotation in seconds, the same span in hours, and the date's distance from the present in years:

propertime.lengthOfDay('20260614000101AM').seconds

The same reading taken at a deep date returns the shorter rotation that held then, a date some hundred and twenty thousand years back still sitting near the present length while a date in the billions falls away toward the young Earth:

propertime.lengthOfDay('1220260614000101AMA.C.').seconds
propertime.lengthOfDay('42344420260614000101AMA.C.').seconds
How do I read a drag time against the civil one, and what does DPK convert?

Drag is the clock the day would keep if it followed the true spin of the Earth rather than the padded civil count, the reading the time could be were the day measured by the rotation itself, so a drag reading and a civil reading of the same written fields are two different moments, and the question that arises in use is what a civil stamp corresponds to on the real-rotation clock, or what a drag stamp corresponds to back on the civil one. The DPK method answers both, and its alias DTP is the same call. It reads the object's own drag flag for the direction, a reading made in drag returns its civil twin and a reading made in the civil mode returns its drag twin, so the flag the timestamp was opened with is the flag that decides which way the conversion runs.

Two readings of the same written time look the same and are not the same moment, which is the first thing to hold. Taking a deep date as a plain string and rendering it once in the civil mode and once in drag gives the very same label, because nothing has been converted, only displayed:

propertime('344420260 0101 110101PMA.C.', '', '', false, false).toString()
propertime('344420260 0101 110101PMA.C.', '', '', false, true).toString()

The conversion is where the difference shows. Reading the civil stamp and asking for its drag twin moves both the clock and the date, because the two reckonings are set equal only at the present and drift apart with every day that separates a date from it, the real day being shorter than the civil 86,400 even now and shorter still in deep time, so the shortfall gathers day on day and a date hundreds of millions of years back lands its drag twin millions of years further out:

propertime('344420260 0101 110101PMA.C.', '', '', false, false).DPK(1).toString()

and feeding that drag twin back, read in drag this time, returns the civil stamp it came from, the conversion holding exact in both directions:

propertime('358698289 0317 101017PMA.C.', '', '', false, true).DPK(1).toString()

A deep date prints as the canonical line of laps and holes, which is hard to read across a conversion, so the second argument to toString forces the civil layout instead and the twin reads as a plain year, month, day, and clock:

propertime('344420260 0101 110101PMA.C.', '', '', false, false).DPK(1).toString(false,true)
propertime('358698289 0317 101017PMA.C.', '', '', false, true).DPK(1).toString(false,true)

The drift is not a deep-time curiosity, it is already a day or so within living memory, because the present rotation runs 236 seconds short of the civil day and that shortfall accumulates from the moment the two clocks were set level. A current civil stamp asked for its drag twin lands on the following day, and the same stamp read in drag and asked for its civil twin lands on the day before:

propertime('20260614100101PM', '', '', false, false).DPK(1).toString()
propertime('20260614100101PM', '', '', false, true).DPK(1).toString()

The use of the method is exactly this crossing, holding a civil timestamp and learning where it falls on the clock the Earth actually turns by, or holding a real-rotation reading and bringing it back to the civil date a record would carry, with the present as the one moment the two agree and every other date parted from it by the gathered difference of all the days between.

Which length-of-day anchors does drag stand on, and where does it stop?

The rotation rate is not constant across deep time, the Moon's tidal pull has lengthened the day for as long as there has been a Moon, so drag carries a short table of anchor points, each a distance in years from the present paired with the day length held to obtain there, and it reads a date by finding the two anchors it falls between and drawing a straight line of day length across the interval, a continuous staircase that bends at each anchor. The present anchor is the measured sidereal day of 86,164.0905 seconds, and the deep-past anchors descend through the Devonian, where coral banding and tidal rhythmites put the day near 22 hours, into the long Proterozoic plateau where the day held close to 19.5 hours for roughly a milliard years because the sun's atmospheric thermal tide pressed the rotation forward by about as much as the Moon's ocean tide held it back, a balance reported by Mitchell and Kirscher in Nature Geoscience, 2023 and known as the stall of the boring billion, and down once more through the Archean toward the gathering of the Earth, where the young planet turned in something near 6 hours. The future side of the table runs the opposite way, the day lengthening past the present as the Moon keeps receding, and the table is by intent a model of the real spin rather than a record of any calendar, the divergence from the civil date is expected and is the reason the flag bears the name drag.

The stall reads back as a flat 19.5 hours anywhere within its span:

propertime.lengthOfDay('10000000000101000101AM A.C.').hours

a date close to the formation returns the short rotation of the young Earth:

propertime.lengthOfDay('44990000000101000101AM A.C.').seconds

and a date a milliard years ahead returns a day longer than the present one:

propertime.lengthOfDay('10000020260101000101AM').hours

Two walls bound the model and the engine throws rather than reckon past either. Earlier than the formation of the Earth, about 4.5 milliard years back, there was no planet to turn, and such a date is refused:

propertime.lengthOfDay('50000000000101000101AM A.C.')

and at the far end the swelling sun reaches and engulfs the Earth near 7.6 milliard years ahead, after which there is no Earth to turn, and a date beyond that is refused in the same manner:

propertime.lengthOfDay('80000000000101000101AM')

The same refusal holds inside addition, a step that would carry the clock across either wall throws on the day of the crossing rather than return a date the model cannot stand behind.

Errors

The library throws instead of guessing, and these are the cases:

One case that looks like an error but is not: 00 in the seconds place is read as a step back across the previous boundary, so an input ending in 0100AM for the start of a day returns the previous day's leap minute rather than throwing.

Footnote, ancient dates

None of this touches modern dates, it applies only when you work before the Roman calendar reform. Before 45 A.C. Month 01 is Martius, not January, and the months follow the old Roman order. Before 713 A.C. January and February do not exist at all, and the winter is an uncounted gap held in Month 90. Months 91, 92, and 93 are the Roman intercalary months, Mercedonius, Intercalaris Prior, and Intercalaris Posterior, and such months stretch well past 31 days. For dates this old, toString already returns the canonical deep-time line of the era, and getclndr draws the calendar in the matching frame.