Skip to content

Commit 055979f

Browse files
committed
Fix %I/%-I strftime directives to use the 12-hour clock
The %I and %-I directives mapped straight to the hour attribute (0-23), making them identical to %H. Per the strftime specification (and the '12' fallback already present) they are the 12-hour clock, so e.g. 1pm rendered as '13' instead of '01' and midnight as '00' instead of '12'. Add an hour12 property (hour % 12, with 0 mapped to 12) and point both directives at it. A plain date (no time) still falls back to '12'.
1 parent 9eda62a commit 055979f

3 files changed

Lines changed: 39 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## [Unreleased]
4+
5+
### Fixed
6+
7+
* `%I` and `%-I` now render the 12-hour clock instead of duplicating `%H`
8+
39
## [5.3.0] - 2026-05-19
410

511
## Add

jdatetime/__init__.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@
4444
'%f': ('_strftime_get_attr_value', {'attr': 'microsecond', 'fmt': '%06.d', 'fb': '000000'}),
4545
'%H': ('_strftime_get_attr_value', {'attr': 'hour', 'fmt': '%02.d', 'fb': '00'}),
4646
'%-H': ('_strftime_get_attr_value', {'attr': 'hour', 'fmt': '%d', 'fb': '0'}),
47-
'%I': ('_strftime_get_attr_value', {'attr': 'hour', 'fmt': '%02.d', 'fb': '12'}),
48-
'%-I': ('_strftime_get_attr_value', {'attr': 'hour', 'fmt': '%d', 'fb': '12'}),
47+
'%I': ('_strftime_get_attr_value', {'attr': 'hour12', 'fmt': '%02.d', 'fb': '12'}),
48+
'%-I': ('_strftime_get_attr_value', {'attr': 'hour12', 'fmt': '%d', 'fb': '12'}),
4949
'%M': ('_strftime_get_attr_value', {'attr': 'minute', 'fmt': '%02.d', 'fb': '00'}),
5050
'%-M': ('_strftime_get_attr_value', {'attr': 'minute', 'fmt': '%d', 'fb': '0'}),
5151
'%S': ('_strftime_get_attr_value', {'attr': 'second', 'fmt': '%02.d', 'fb': '00'}),
@@ -875,6 +875,12 @@ def fromordinal(ordinal: int) -> datetime:
875875
def hour(self) -> int:
876876
return self.__time.hour
877877

878+
@property
879+
def hour12(self) -> int:
880+
"""Return the hour on a 12-hour clock (1..12), used by the %I directive."""
881+
hour = self.hour % 12
882+
return 12 if hour == 0 else hour
883+
878884
@property
879885
def minute(self) -> int:
880886
return self.__time.minute

tests/test_jdatetime.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,31 @@ def dst(self, dt):
211211
dt = jdatetime.datetime(1389, 2, 17, 19, 10, 2, tzinfo=teh)
212212
self.assertEqual(dt.strftime('%Z %z'), 'IRDT +0330')
213213

214+
def test_strftime_I_directive_uses_twelve_hour_clock(self):
215+
import datetime as _std
216+
217+
# %I and %-I must render the 12-hour clock (1..12), matching stdlib.
218+
for hour in range(24):
219+
with self.subTest(hour=hour):
220+
jd = jdatetime.datetime(1400, 1, 1, hour, 5, 0)
221+
std = _std.datetime(2020, 1, 1, hour, 5, 0)
222+
self.assertEqual(jd.strftime('%I'), std.strftime('%I'))
223+
self.assertEqual(jd.strftime('%-I'), std.strftime('%-I'))
224+
self.assertEqual(jd.hour12, int(std.strftime('%I')))
225+
# %I must be consistent with the (correct) %p directive:
226+
# reconstructing the 24-hour value from (%p, %I) round-trips.
227+
twelve = int(jd.strftime('%I'))
228+
base = 0 if twelve == 12 else twelve
229+
reconstructed = base + (12 if jd.strftime('%p') == 'PM' else 0)
230+
self.assertEqual(reconstructed, hour)
231+
# Spot-check the values that the previous 24-hour behaviour got wrong.
232+
self.assertEqual(jdatetime.datetime(1400, 1, 1, 0, 0).strftime('%I'), '12')
233+
self.assertEqual(jdatetime.datetime(1400, 1, 1, 13, 0).strftime('%I'), '01')
234+
self.assertEqual(jdatetime.datetime(1400, 1, 1, 23, 0).strftime('%I'), '11')
235+
self.assertEqual(jdatetime.datetime(1400, 1, 1, 13, 0).strftime('%-I'), '1')
236+
# A plain date (no time component) is midnight -> 12.
237+
self.assertEqual(jdatetime.date(1400, 1, 1).strftime('%I'), '12')
238+
214239
def test_strftime_fa_locale_uses_short_month_names_for_b_directive(self):
215240
tests = [
216241
(1, 'فرو', 'فروردین'),

0 commit comments

Comments
 (0)