From c8510a9625fae5b29932adc26cd0a65d2d2867c9 Mon Sep 17 00:00:00 2001 From: Tomas Roun Date: Sat, 7 Nov 2026 23:45:16 +0100 Subject: [PATCH] Add the Julian day ('g') date pattern --- babel/dates.py | 29 +++++++++++++++++++++++++++++ docs/dates.rst | 2 +- tests/test_dates.py | 17 +++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/babel/dates.py b/babel/dates.py index e0e0e5940..54d9d9ded 100644 --- a/babel/dates.py +++ b/babel/dates.py @@ -1584,6 +1584,8 @@ def __getitem__(self, name: str) -> str: return self.format_day_of_year(num) elif char == 'F': return self.format_day_of_week_in_month() + elif char == 'g': + return self.format_julian_day(num) elif char in ('E', 'e', 'c'): return self.format_weekday(char, num) elif char in ('a', 'b', 'B'): @@ -1721,6 +1723,33 @@ def format_day_of_year(self, num: int) -> str: def format_day_of_week_in_month(self) -> str: return str((self.value.day - 1) // 7 + 1) + def format_julian_day(self, num: int) -> str: + """ + Return the modified Julian day. + + This is different from the conventional Julian day number: + - It demarcates days at local zone midnight, rather than noon GMT. + - It depends on the local time zone. + + >>> from datetime import date + >>> format = DateTimeFormat(date(2016, 2, 28), 'cs_CZ') + >>> format.format_julian_day(1) + '2457447' + + :param num: count of format character + + """ + # Python defines `dt.toordinal()` which gives us the Gregorian day for + # a given date. To compute the Julian day for a given date, we just + # need to know the offset between the Julian and Gregorian calendars. + # This offset can be computed by taking the `JAN_1_1_JULIAN_DAY` + # constant defined in ICU and subtracting `1` from it. This is because + # `date(1, 1, 1).toordinal() == 1` since January 1, 1 is the start of + # the Gregorian calendar. + # See: https://github.com/unicode-org/icu/blob/6d18ecab418ac4cf5a3f51ec3b69b77ba713d719/icu4j/main/core/src/main/java/com/ibm/icu/util/Calendar.java#L1136-L1141 + JULIAN_DAY_OFFSET = 1721426 - 1 + return self.format(self.value.toordinal() + JULIAN_DAY_OFFSET, num) + def format_period(self, char: str, num: int) -> str: """ Return period from parsed datetime according to format pattern. diff --git a/docs/dates.rst b/docs/dates.rst index d1a735523..33db59b75 100644 --- a/docs/dates.rst +++ b/docs/dates.rst @@ -173,7 +173,7 @@ Date Fields | +--------+--------------------------------------------------------+ | | ``F`` | Day of week in month. | | +--------+--------------------------------------------------------+ - | | ``g`` | ?? | + | | ``g`` | Modified Julian day (numeric). | +----------+--------+--------------------------------------------------------+ | Week day | ``E`` | Day of week. Use one through three letters for the | | | | short day, or four for the full name, or five for the | diff --git a/tests/test_dates.py b/tests/test_dates.py index f181a3636..394466fa6 100644 --- a/tests/test_dates.py +++ b/tests/test_dates.py @@ -38,6 +38,23 @@ def test_format_date_with_day_of_year_in_pattern_and_datetime_param(): assert dates.format_date(d, 'w', locale='en_US') == '14' +def test_format_julian_day(timezone_getter): + assert dates.format_date(date(476, 9, 4), 'g', locale='en') == '1895163' + assert dates.format_date(date(1970, 1, 1), 'g', locale='en') == '2440588' + # The time part of the datetime is ignored + assert dates.format_date(datetime(1970, 1, 1, 12, 30), 'g', locale='en') == '2440588' + # Zero padding + assert dates.format_date(date(2026, 11, 7), 'ggggggggg', locale='en') == '002461352' + # Local time is used for the calculation + # 23:30 UTC on March 31 is already April 1 in Zurich so the + # Julian day in the Zurich timezone should be 1 more than UTC + dt = datetime(2007, 3, 31, 23, 30, tzinfo=UTC) + jd_utc = dates.format_datetime(dt, 'g', locale='en') + jd_zurich = dates.format_datetime(dt, 'g', tzinfo=timezone_getter('Europe/Zurich'), locale='en') + assert jd_utc == '2454191' + assert jd_zurich == f'{int(jd_utc)+1}' + + def test_format_datetime_with_float(timezone_getter): UTC = timezone_getter('UTC') d = datetime(2012, 4, 1, 15, 30, 29, tzinfo=UTC)