The Machines · Core numerics

julian

The Calendar as Arithmetic — machines/julian.shoddy

the julian machine's icon

Summary

julian is the one place the civil calendar is spelled: the Gregorian leap rules, a date record, checked and total builders, day arithmetic, weekdays, and the two day-number epochs. (An epoch is the chosen zero instant a count is measured from.) The first epoch is the serial count from 1970-01-01 that finance and computing use. The second is the astronomical Julian day number the sky uses. Before this machine, those rules shipped twice. fin carried a serial-day arithmetic for its bond words (its own header called it a stand-in), and geo carried a Julian day for its sun words. Both now define their words over this machine, so there is one set of leap rules and not two.

It is pure, it includes nothing, and it never reads a clock. A date arrives as an argument or it does not arrive. clock's ClockDate() is the one bridge from “now” into this arithmetic, at the caller's edge. That is exactly what keeps fin, geo and ephemeris out of the capability system while still computing with dates.

A Brief History of the Day Count

The scholar Joseph Scaliger published, in 1583, a scheme for placing every recorded event on a single unbroken count of days — no months, no leap rules, no dependence on whose calendar the chronicler kept. An event simply fell on day number so-many of a 7,980-year period he named the Julian period. He built it to reconcile ancient chronologies. Astronomers stole it, because a subtraction is the one date calculation that cannot go wrong. John Herschel's Outlines of Astronomy pressed the Julian day into routine astronomical use in 1849, with the convention that gives this machine its .5: the astronomical day begins at noon, so that a whole night's observing falls under one day number. Computing rediscovered the same idea from scratch when Unix began counting days and seconds from 1970. The two counts in this machine — serial and Julian day — are Scaliger's insight and Unix's, one constant apart. The calendar words around them exist mostly so a program can get onto a day count as early as possible and stay there.

Why It's Useful

Every program that touches a date needs the same small set of facts: which years leap, how long February is, how many days lie between two dates, what weekday a date falls on. And every hand-rolled copy of those facts gets the century rule wrong eventually. 1900 was not a leap year. 2000 was. The difference is three terms of integer arithmetic, and a program that carries its own copy carries its own chance of losing one of them.

Two epochs, one set of rules, one constant. JulSerial counts whole days from 1970-01-01 — serial 0 — the epoch spreadsheets, databases and Unix all already count from. JulDayNumber answers the astronomical Julian day at 00:00 UT, the representation astronomy uses. It is defined over JulSerial: the whole relationship between the two epochs is one line, JulSerial(d) + 2440587.5, and that constant appears in exactly one place in the tree.

A Julian day number always ends in .5 at civil midnight, because Julian days begin at noon. That fraction is the commonest way to confuse the two epochs. JulFromSerial refuses a fractional serial for exactly that reason: handed 2451544.5, it aborts naming the mistake, rather than answering a plausible date five thousand years wide of the mark.

Unlike geo's points and eng's complex numbers, = on two dates is honest. A JulDate is whole-number arithmetic end to end — no trigonometry, no rounding — so a computed date lands exactly on its literal or the arithmetic is wrong. There is no JulNear, and none is missing.

User's Guide

Include "julian.shoddy"

Def Main()
    Let d = JulOf(2026, 8, 12)            ' checked: Feb 30 would abort
    Print(JulWeekday(d))                  ' 3 - a Wednesday; Monday is 1
    Print(JulSerial(d))                   ' 20677 days since 1970-01-01
    Print(JulDayNumber(d))                ' 2461264.5 - for the sun and the sky
    Print(JulDay(JulAddMonths(JulOf(2026, 1, 31), 1)))   ' 28 - clamped, not 3 March
    Print(JulDiff(d, JulOf(2027, 8, 12))) ' 365 - signed, b minus a

A date from outside the program goes through the total twin instead — "total" meaning it always answers a value rather than aborting, so the refusal comes back as a value:

Select Case JulTryOf(y, m, d)
    Case Ok(dt)
        JulSerial(dt)
    Case Err(why, at)
        ...               ' "JULTRYOF: DAY '30' DOES NOT FALL IN THAT MONTH"

Things worth remembering:

Word Reference

The type, the rules, the two epochs, the arithmetic

The type and its builders

WordDescription
Type JulDateA calendar date: JulYear, JulMonth, JulDay, all Number, in the order a date is written. The raw constructor checks nothing, exactly as GeoPt checks nothing; the two builders below are the checked ways in.
JulOf(y, m, d)The checked builder. Aborts, naming JULOF, on a month outside 1..12, a day that month does not have, a non-whole part, or a year outside −400000..400000. Those bounds also make a NaN or an infinity fail a positive range test for free.
JulTryOf(y, m, d)The total twin, answering Result — for a date that came from outside the program. JulOf is defined over it, so there is one set of rules and not two.

The rules

WordDescription
JulIsLeap(y)Whether a year leaps, century rules included: divisible by 4, except centuries, except every fourth century.
JulDaysInMonth(y, m)How many days that month has in that year — 28, 29, 30 or 31.

The two epochs

WordDescription
JulSerial(d)The date as a whole count of days from 1970-01-01, which is serial 0. Negative before it. The form to subtract two dates with, and the arithmetic that fin's bond words proved for years before it moved here.
JulFromSerial(n)The serial back into a date. Refuses a fraction, naming itself: a number ending in .5 is usually a Julian day number handed to the wrong epoch.
JulDayNumber(d)The astronomical Julian day number at 00:00 UT — noon-based, so a civil midnight always ends in .5. Defined over JulSerial; the offset 2440587.5 lives in this one line. geo's GeoJulian is this word by another name, and the sky words in ephemeris take one of these.

Arithmetic

WordDescription
JulDiff(a, b)Days from a to b, signed: negative when b falls before a.
JulAddDays(d, days)The date that many days later; a negative count walks back.
JulAddMonths(d, months)That many months later, clamped to month end: one month after 31 January is 28 February, not the 3rd of March. The coupon-schedule word.
JulWeekday(d)The day of the week, 1..7 with Monday 1 — the ISO convention.

Who Uses It

UserHow
clockClockDate() lifts one wall-clock reading into a JulDate — the single bridge between the clock capability and this pure arithmetic.
finThe bond words take JulDates and the day-count bases stand on JulSerial and JulDiff. fin's own date record was the stand-in this machine retired.
geoGeoJulian is one line over JulDayNumber, so the sun words and the calendar share one set of leap rules.

The Machines It Uses

None — a tier-one machine like math, it includes nothing, so any program can pull the calendar in alone.