I recently created an interactive version of the French Republican Calendar, using the original equinox-based system of leap years. This was not my first encounter with calendars and time-keeping — 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 time-keeping. 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:

  1. Numbering of years in the Common Era system.
  2. Julian and Gregorian calendars.
  3. Julian Day Numbers.

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)=20662022-(-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:

  1. Proleptic Julian calendar — this is the extension of the Julian calendar to before its adoption.1
  2. 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 time-based 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 meridian3) 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 non-negative 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 ab\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

  1. 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. 

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

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