The Art of TimeKeeping, Part 1: Years and Dates
I recently created an interactive version of the French Republican Calendar, using the original equinoxbased system of leap years. This was not my first encounter with calendars and timekeeping — back in 2021, I had created a programming contest about calendars for World Emoji Day (July 17th, the date most often shown on the calendar emoji 📅), and most of what I learned then was applied to my French Republican Calendar app.
While creating these, I was forced to confront the inherent difficulties of timekeeping. At first glance, it seems deceptively simple — one simply has to count the number of seconds, and once enough seconds have passed, we say that a day has passed, and when enough days have passed, we say that it is a new year. This seems simple enough. At this point, you might remember that there isn’t an integer number of days in a year, which is why leap years are needed.
This is, however, a simplified explanation of something far more complicated. As we shall see, the rotation of the Earth itself is not consistent. This is the reason behind the dreaded leap seconds, but I am getting ahead of myself. Other factors at play include time dilation caused by relativity. I thought I’d share some of the knowledge, so that we may all gain a greater appreciation of the work needed to make clocks tick correctly.
This subject is beyond what a single blog post can cover, so instead, I shall turn this into a series. First, we shall cover the background knowledge needed to understand the rest of the series, starting with years and dates:
Numbering of Years
Using the Common Era (CE) system for year numbering (also known as the BC/AD system), the current year is 2022, and every year, that number increases by one. We also know, for example, that the assassination of Julius Cæsar happened on the Ides (15th) of March, 44 BCE. How many years ago was that?
The natural thought would be to convert 44 BCE into 44, then subtract that from 2022 to obtain $2022(44)=2066$, but that would be wrong. When Dionysius Exiguus originally created the BC/AD system to label years since what he believed to be the birth of Jesus Christ, the concept of zero was unfamiliar to most. As a result, the years went from 1 BC to 1 AD. To keep things consistent, in the Common Era system, years also went from 1 BCE to 1 CE. This means that it has only been 2065 years since the assassination of Julius Cæsar.
In general, the lack of year 0 has made arithmetic on years awfully complicated. Fortunately, there is a common convention among astronomers to use a modified numbering system: year 0 would be 1 BCE, year 1 would be 2 BCE, and so on. In this series, unless otherwise specified, this is the system we will be using.
Julian and Gregorian calendars
The calendar system in common use these days is the Gregorian calendar. This is a modified version of the Julian calendar, first introduced by Julius Cæsar in 46 BCE.
Most readers are probably familiar with how this calendar works broadly: there are 12 months, with either 30 or 31 days each — except for the second month, February, which has 28 days in normal years and 29 in leap years.
In the Julian calendar, a leap year occurs every four years, with no exception, i.e. all years divisible by 4 are leap years. This meant that every year was 365.25 days, which is longer than the real value of 365.2422 days. Eventually, the days drifted out of sync with respect to the seasons.
To counter this, Pope Gregory XIII introduced the Gregorian calendar in 1582, which changed the rule regarding leap years — if a year is divisible by 100 but not by 400, it is not a leap year. This made every year 365.2425 days long, which is much more accurate (though it will still drift). Since the date of Easter was the main motivation behind this change, it was decided to skip 10 days to realign the seasons to their original dates when the date of Easter was decided. Different countries adopted this change at different times, resulting in confusion for over 300 years.
For the sake of clarity, we will define the following terms:
 Proleptic Julian calendar — this is the extension of the Julian calendar to before its adoption.^{1}
 Proleptic Gregorian calendar — this is the extension of the Gregorian calendar to before its adoption.
This would mean, for example, that March 15, 44 BCE would actually be March 13, 44 BCE in the proleptic Gregorian calendar. The proleptic calendars are very useful for having one set of consistent rules to apply to the past dates.
Julian Day Numbers
If you are a programmer, you might be familiar with the concept of Unix time. This is a system for describing time as the number of seconds since midnight UTC on January 1, 1970 (excluding leap seconds). It proves awfully convenient for timebased arithmetic, as there is no need to worry about the varying length of months and years.
For the purposes of date reckoning, there is actually a similar system called Julian day which is used extensively in astronomy. Day 0 is January 1, 4713 BCE in the proleptic Julian calendar, or November 24, 4714 BCE in the proleptic Gregorian calendar. With each passing day, the number increases by one unconditionally. When referring to a date, this number is called a Julian day number (JDN)^{2}. For example, the date January 1, 2000 maps to JDN 2,451,545. There is no need to worry about leap years or anything similar.
In astronomy, the system is extended to refer to any moment in time through fractional days. The resulting value is called the Julian date. Somewhat confusingly, integer Julian Dates refer to noon universal time (essentially the time at the prime meridian^{3}) and not midnight as you expect. This means that 06:00:00 UT on January 1, 2000 would be JD 2,451,544.75 and 18:00:00 UT on the same day would be JD 2,451,545.25. Since astronomy usually happens at night, and when defined this way, the integer part of the Julian date will not change overnight.
In this series, JDNs are incredibly useful for converting between calendar systems: instead of trying to wrangle the complexity of converting between each pair of calendar systems, we can simply convert them to and from JDNs.
Other uses for Julian Day Numbers
Have you ever wondered how to calculate the day of the week from a date? Well, you can simply use divide the JDN by 7. The remainder is 0 if it’s a Monday, 1 if it’s a Tuesday, …, and 6 if it’s a Sunday.
Converting to and from Julian Day Numbers
Unfortunately, most of the formulæ given for converting to and from JDN found
online only works for nonnegative JDN values. This is a shame, as we might want
to deal with dates before 4713 BCE. I was able to find algorithms in the Python
library skyfield
that works for negative JDN values, and I have confirmed this
to my satisfaction.
The function skyfield.timelib.julian_day
converts from a Julian/Gregorian
date to JDN, and the function skyfield.timelib.compute_calendar_date
performs the inverse operation.
For your convenience, I have reproduced the algorithms here, in case the links ever break:
def julian_day(year, month=1, day=1, julian_before=None):
"""Given a calendar date, return a Julian day integer.
Uses the proleptic Gregorian calendar unless ``julian_before`` is
set to a specific Julian day, in which case the Julian calendar is
used for dates older than that.
"""
# Support months <1 and >12 by overflowing cleanly into adjacent years.
y, month = divmod(month  1, 12)
year = year + y
month += 1
# See the Explanatory Supplement to the Astronomical Almanac 15.11.
janfeb = month <= 2
g = year + 4716  janfeb
f = (month + 9) % 12
e = 1461 * g // 4 + day  1402
J = e + (153 * f + 2) // 5
mask = 1 if (julian_before is None) else (J >= julian_before)
J += (38  (g + 184) // 100 * 3 // 4) * mask
return J
def compute_calendar_date(jd_integer, julian_before=None):
"""Convert Julian day ``jd_integer`` into a calendar (year, month, day).
Uses the proleptic Gregorian calendar unless ``julian_before`` is
set to a specific Julian day, in which case the Julian calendar is
used for dates older than that.
"""
use_gregorian = (julian_before is None) or (jd_integer >= julian_before)
# See the Explanatory Supplement to the Astronomical Almanac 15.11.
f = jd_integer + 1401
f += use_gregorian * ((4 * jd_integer + 274277) // 146097 * 3 // 4  38)
e = 4 * f + 3
g = e % 1461 // 4
h = 5 * g + 2
day = h % 153 // 5 + 1
month = (h // 153 + 2) % 12 + 1
year = e // 1461  4716 + (12 + 2  month) // 12
return year, month, day
Please be aware that the //
operator in Python performs floor division, i.e.
a // b
is equivalent to $\left\lfloor \dfrac{a}{b} \right\rfloor$.
This post is already a bit too long, so that will be all for today. Next time, we shall look at the different ways of measuring time.
Notes

Interestingly, the Romans mistakenly observed a leap year every 3 years instead of 4 as Julius Cæsar had wanted. Augustus noticed this error and skipped the leap day for 12 years, realigning the years. So it was only starting in 4 CE that the calendar was observed correctly, and thus the term proleptic Julian calendar means applying the current rules to years before 4 CE. ↩

Technically, Julian Day Numbers refers to days starting at noon, but for simplicity, we will use it to refer to the whole calendar day. ↩

This will be expanded upon in the future. It’s a lot more complicated than that. ↩