TTiimmee aass FFrraaccttiioonnaall DDaayyss??
Published November 13, 2021
Is it reasonable to encode time as the fractional number of days since a
suitable epoch? _E_x_c_e_l[1] and _S_Q_L_i_t_e[2] do it, but I thought the precision
would be too low.
If your epoch starts recently and you use a _b_i_n_a_r_y_6_4[3] for storage, your
precision is 0.6 µs during this century, 80 µs when the
_g_l_a_c_i_e_r_s _r_e_t_r_e_a_t_e_d[4], 10 ms when _H_o_m_o _h_a_b_i_l_i_s _e_m_e_r_g_e_d[5], and 1.4 minutes
at the _s_t_a_r_t _o_f _t_h_e _U_n_i_v_e_r_s_e[6]. Considering that _s_i_g_n_i_f_i_c_a_n_t _e_v_e_n_t_s[7] in
recent history are discussed with precision of one second, these precisions
seem generous.
Programming languages often represent time as seconds since January 1, 1970
plus nanoseconds thereafter. For example, _c_l_o_c_k___g_e_t_t_i_m_e_(_2_)[8] or
_G_oʼ_s _t_i_m_e_._T_i_m_e[9]. This representation uses 16 bytes of memory to provide
nanosecond precision everywhen. The algorithms require caution at the
boundary between seconds and nanoseconds. Floating point days would reduce
storage by half and might simplify the algorithms a little.
If fractional days work, what about fractional weeks or fractions of a 400-year
Gregorian cycle? (We canʼt do months or years since their lengths vary).
It turns out that the units donʼt matter much. A binary64 only has so much
precision, so choose a representation with a convenient implementation.
NNootteess:
_T_h_i_s _c_o_d_e[10] calculates the precisions. The only non-obvious part is the
way it converts a ffllooaatt6644 to a uuiinntt6644, increments it, then converts it back.
Since an IEEE-754 float stores its significand in the least significant
bits, this trick increments a ffllooaatt6644 by the smallest amount possible.
This article is only relevant when working with absolute times. If youʼre
measuring the duration between two events, use your systemʼs
_m_o_n_o_t_o_n_i_c _c_l_o_c_k[11]. It does the right thing when _N_T_P[12] moves time
backwards.
January 1, 2001 is a convenient epoch since it begins a four century
Gregorian cycle. Some date algorithms are cleaner if the leap years are at
the end of a cycle instead of the beginning. Using a recent cycle, instead
of 1601 or 1201, keeps the precision high for recent dates.
UUppddaattee February 2, 2022: rewording, mention fractional weeks and fractional
Gregorian cycles. The previous text is available at _a_r_c_h_i_v_e_._t_o_d_a_y[13] or
_a_r_c_h_i_v_e_._o_r_g[14].
1: https://stackoverflow.com/a/981865/174463
2: https://www.sqlite.org/datatype3.html#date_and_time_datatype
3: https://en.wikipedia.org/wiki/Double-precision_floating-point_format
4: https://en.wikipedia.org/wiki/Holocene
5: https://en.wikipedia.org/wiki/Homo
6: https://en.wikipedia.org/wiki/Age_of_the_universe
7: https://en.wikipedia.org/wiki/Mars_Pathfinder#Entry,_descent_and_landing
8: https://man.openbsd.org/OpenBSD-7.0/clock_gettime
9: https://cs.opensource.google/go/go/+/refs/tags/go1.17.3:src/time/time.go;l=127
10: https://go.dev/play/p/ptaAXO3ZAn1
11: https://man.openbsd.org/clock_gettime#EXAMPLES
12: https://en.wikipedia.org/wiki/Network_Time_Protocol
13: https://archive.today/Rdjlk
14: https://web.archive.org/web/20220202125122/https://m.ndrix.org/a/2021/time-as-fractional-days/