Date conversion

Teemu Kalvas <*.*@s2.org> 2015-10-11 — 2019-11-24

$$\DeclareMathOperator{\divmod}{divmod}$$

All functions here are defined only on \(\mathbb{Z}\).

Several monotonically increasing functions and their inverses are defined to convert from one representation of a date to another.

The divmod operator is defined as

$$a, b = c \divmod d \qquad \iff a = \left \lfloor{\frac{c}{d}}\right \rfloor \quad b = c - a d$$

Gregorian

Let \(y_G\) be the year, \(m_G\) the month, and \(d_G\) the day as they are normally given in Gregorian dates. For example for 11 October 2015 they are 2015, 10, and 11, respectively.

Define a continous count of months as $$m = 12y_G + (m_G - 1) - 2$$ so that the \(m\) exactly divisible by 12 is always March. This moves the problematic February to the least problematic location later on.

The inverse transformation is

$$y_G, m_G - 1 = (m + 2) \divmod 12$$

Let \(y\) be the year from March to February, numbered so that \(y = y_G\) from March to December and \(y = y_G - 1\) in January and February. Let \(m_y\) be the number of the month within that year (from March = 0 to February = 11). Then

$$m = 12y + m_y$$

with the inverse

$$y, m_y = m \divmod 12$$

Let \(d_y\) be the number of days since the beginning of the (March-based) year. \(d_m = d_G - 1\) is the zero-based day of the month.

$$d_y = \left \lfloor{\frac{153 m_y + 2}{5}}\right \rfloor + d_m$$

Then

$$m_y, 5 d_m + r = (5 d_y + 2) \divmod 153 \qquad r \in [0 .. 4]$$

Let \(d\) be the number of days since the beginning of \(y = 0\).

$$d = 365y + \left \lfloor{\frac{y}{4}}\right \rfloor - \left \lfloor{\frac{y}{100}}\right \rfloor + \left \lfloor{\frac{y}{400}}\right \rfloor + d_y$$

Then

$$y, 4 d_y + r = \left (4 \left (d + \left \lfloor{\frac{3 \left \lfloor{\frac{4 d + 146101}{146097}}\right \rfloor}{4}}\right \rfloor \right ) + 3\right ) \divmod 1461 \qquad r \in [0 .. 3]$$

Bounds checking of unreliably input \((y_G, m_G, d_G)\) can be exactly implemented by running the conversion first to and then from \(d\), and checking that the same values result.

ISO 8601 week numbering

Let \(y_w\) be the year number in ISO 8601 -specified week numbering. Let \(w\) be the continous count of weeks from the beginning of \(y_w = 0\). \(w_y\) is the zero-based week of the year.

$$w = \left \lfloor{\frac{1461 y_w + 7 - 4 \left \lfloor{\frac{y_w-1}{100}}\right \rfloor + 4 \left \lfloor{\frac{y_w}{400}}\right \rfloor}{28}}\right \rfloor + w_y$$

Then

$$y, 28w_y + r = \left (28 w + 20 - 4 \left \lfloor{\frac{3 \left (\left \lfloor{\frac{4 (5269 - w)}{20871}}\right \rfloor \right )}{4}}\right \rfloor \right ) \divmod 1461 \qquad r \in [0 .. 27]$$

Note that while the \(d\) count starts on March 1 in the year zero of the proleptic Gregorian calendar, the \(w\) count starts on Monday, January 3 of the same year, exactly 58 days earlier.

Gregorian easter

This has been optimized to death before me, so just a classical representation. First find the century

$$c = \left \lfloor{\frac{y}{100}}\right \rfloor + 1$$

Then the golden number (they used to do this one-based but zero-based makes more sense), i.e. the year modulo 19.

$$y_{19} = y - 19 \left \lfloor{\frac{y}{19}}\right \rfloor$$

The epact inverted, before the correction needed for the short month.

$$x' = 15 - 11 y_{19} + \left \lfloor{\frac{3c}{4}}\right \rfloor - \left \lfloor{\frac{5 + 8c}{25}}\right \rfloor$$ $$x = x' - 30 \left \lfloor{\frac{x'}{30}}\right \rfloor$$

Apply the correction and get the number of days to add to March 21 to get the Paschal full moon.

$$d_{21} = x - \left \lfloor{\frac{x + \left \lfloor{\frac{y_{19}}{11}}\right \rfloor}{29}}\right \rfloor$$

Convert to the same day numbering as above

$$d_{pfm} = 365y + \left \lfloor{\frac{y}{4}}\right \rfloor - \left \lfloor{\frac{y}{100}}\right \rfloor + \left \lfloor{\frac{y}{400}}\right \rfloor + 20 + d_{21}$$

Now it is easy to find the Sunday after Paschal full moon (\(d\) is Wednesday-based)

$$d_{easter} = d_{pfm} + 1 + (3 - d_{pfm}) - 7 \left \lfloor{\frac{3 - d_{pfm}}{7}}\right \rfloor = 4 - 7 \left \lfloor{\frac{3 - d_{pfm}}{7}}\right \rfloor = 4 + 7 \left \lfloor{\frac{3 + d_{pfm}}{7}}\right \rfloor$$

Notes for programmers

Python implementation of these equations, with exhaustive tests and some usage examples here.

Many programming languages implement an integer division where rounding is towards zero, not towards negative infinity as in these equations. Ignoring this can limit the domain where the implementation of these equations is correct.

A slightly more complicated version of \(y(w)\) which uses divisions of positive numbers for positive \(w\) is

$$y, 28w_y + r = \left (28 w + 8 + 4 \left \lfloor{\frac{3 \left (\left \lfloor{\frac{4 (w + 10384)}{20871}}\right \rfloor + 3\right )}{4}}\right \rfloor \right ) \divmod 1461 \qquad r \in [0 .. 27]$$

Alternative for \(x'(y_{19})\) ensuring positive dividends in \(x(x')\)

$$x' = 225 - 11 y_{19} + \left \lfloor{\frac{3c}{4}}\right \rfloor - \left \lfloor{\frac{5 + 8c}{25}}\right \rfloor$$

Changelog

2018-05-19: simplified \(y_w(w)\)

Old:

$$y_w = \left \lfloor{\frac{w - \left \lfloor{\frac{\left \lfloor{\frac{2000 (w+1)}{20871}}\right \rfloor + 7 - 4 \left \lfloor{\frac{w + \left \lfloor{\frac{w}{20871}}\right \rfloor}{5218}}\right \rfloor + 4 \left \lfloor{\frac{w}{20871}}\right \rfloor}{28}}\right \rfloor}{52}}\right \rfloor$$

New:

$$y_w = \left \lfloor{\frac{w - \left \lfloor{\frac{\left \lfloor{\frac{2000 w}{20871}}\right \rfloor + 8 - 4 \left \lfloor{\frac{w + \left \lfloor{\frac{w}{20871}}\right \rfloor}{5218}}\right \rfloor + 4 \left \lfloor{\frac{w}{20871}}\right \rfloor}{28}}\right \rfloor}{52}}\right \rfloor$$

2019-11-14: simplified \(d_y(m_y)\)

Old:

$$d_y = 1 + 30 m_y + \left \lfloor{\frac{3 (m_y - 1)}{5}}\right \rfloor$$

New:

$$d_y = \left \lfloor{\frac{153 m_y + 2}{5}}\right \rfloor$$

simplified \(m_y(d_y)\)

Old:

$$m_y = \left \lfloor{\frac{d_y - \left \lfloor{\frac{d_y + 20}{50}}\right \rfloor}{30}}\right \rfloor$$

New:

$$m_y, 5 d_m + r = (5 d_y + 2) \divmod 153 \qquad r \in [0, 5)$$

simplified \(y(d)\)

Old:

$$y = \left \lfloor{\frac{d - \left \lfloor{\frac{d + 2 + \left \lfloor{\frac{3d}{146097}}\right \rfloor}{1461}}\right \rfloor + \left \lfloor{\frac{d - \left \lfloor{\frac{d}{146097}}\right \rfloor}{36524}}\right \rfloor - \left \lfloor{\frac{d + 1}{146097}}\right \rfloor}{365}}\right \rfloor$$

New:

$$y, 4 d_y + r = \left (4 \left (d + \left \lfloor{\frac{3 \left \lfloor{\frac{4 d + 146101}{146097}}\right \rfloor}{4}}\right \rfloor \right ) + 3\right ) \divmod 1461 \qquad r \in [0 .. 3]$$

Simplified \(w(y_w)\)

Old:

$$w = 52y_w + \left \lfloor{\frac{5y_w + 7 - 4 \left \lfloor{\frac{y_w-1}{100}}\right \rfloor + 4 \left \lfloor{\frac{y_w}{400}}\right \rfloor}{28}}\right \rfloor$$

New:

$$w = \left \lfloor{\frac{1461 y_w + 7 - 4 \left \lfloor{\frac{y_w-1}{100}}\right \rfloor + 4 \left \lfloor{\frac{y_w}{400}}\right \rfloor}{28}}\right \rfloor$$

2019-11-24: simplified \(y_w(w)\)

Old:

$$y_w = \left \lfloor{\frac{w - \left \lfloor{\frac{\left \lfloor{\frac{2000 w}{20871}}\right \rfloor + 8 - 4 \left \lfloor{\frac{w + \left \lfloor{\frac{w}{20871}}\right \rfloor}{5218}}\right \rfloor + 4 \left \lfloor{\frac{w}{20871}}\right \rfloor}{28}}\right \rfloor}{52}}\right \rfloor$$

New:

$$y, 28w_y + r = \left (28 w + 20 - 4 \left \lfloor{\frac{3 \left (\left \lfloor{\frac{4 (5269 - w)}{20871}}\right \rfloor \right )}{4}}\right \rfloor \right ) \divmod 1461 \qquad r \in [0 .. 27]$$