@@ -97,15 +97,26 @@ def test_naturaldelta_nomonths(test_input: dt.timedelta, expected: str) -> None:
9797 (23.5 , "23 seconds" ),
9898 (30 , "30 seconds" ),
9999 (dt .timedelta (microseconds = 13 ), "a moment" ),
100- (dt .timedelta (minutes = 1 , seconds = 30 ), "a minute" ),
100+ (dt .timedelta (minutes = 1 , seconds = 29 ), "a minute" ),
101+ (dt .timedelta (minutes = 1 , seconds = 30 ), "2 minutes" ),
102+ (dt .timedelta (minutes = 1 , seconds = 59 ), "2 minutes" ),
101103 (dt .timedelta (minutes = 2 ), "2 minutes" ),
102- (dt .timedelta (hours = 1 , minutes = 30 , seconds = 30 ), "an hour" ),
103- (dt .timedelta (hours = 23 , minutes = 50 , seconds = 50 ), "23 hours" ),
104+ (dt .timedelta (minutes = 59 ), "59 minutes" ),
105+ (dt .timedelta (minutes = 59 , seconds = 30 ), "an hour" ),
106+ (dt .timedelta (hours = 1 , minutes = 29 ), "an hour" ),
107+ # Round to nearest, ties away from zero.
108+ # See https://en.wikipedia.org/wiki/IEEE_754#Rounding_rules
109+ (dt .timedelta (hours = 1 , minutes = 30 ), "2 hours" ),
110+ (dt .timedelta (hours = 2 , minutes = 30 ), "2 hours" ),
111+ (dt .timedelta (hours = 3 , minutes = 30 ), "4 hours" ),
112+ (dt .timedelta (hours = 23 , minutes = 50 , seconds = 50 ), "a day" ),
104113 (dt .timedelta (days = 1 ), "a day" ),
105114 (dt .timedelta (days = 500 ), "1 year, 4 months" ),
106115 (dt .timedelta (days = 365 * 2 + 35 ), "2 years" ),
107116 (dt .timedelta (seconds = 1 ), "a second" ),
108117 (dt .timedelta (seconds = 30 ), "30 seconds" ),
118+ (dt .timedelta (days = 364 ), "a year" ),
119+ (dt .timedelta (days = 365 + 364 ), "2 years" ),
109120 # regression tests for bugs in post-release humanize
110121 (dt .timedelta (days = 10000 ), "27 years" ),
111122 (dt .timedelta (days = 365 + 35 ), "1 year, 1 month" ),
@@ -134,19 +145,25 @@ def test_naturaldelta(test_input: float | dt.timedelta, expected: str) -> None:
134145 (NOW , "now" ),
135146 (NOW - dt .timedelta (seconds = 1 ), "a second ago" ),
136147 (NOW - dt .timedelta (seconds = 30 ), "30 seconds ago" ),
137- (NOW - dt .timedelta (minutes = 1 , seconds = 30 ), "a minute ago" ),
148+ (NOW - dt .timedelta (minutes = 1 , seconds = 29 ), "a minute ago" ),
149+ (NOW - dt .timedelta (minutes = 1 , seconds = 30 ), "2 minutes ago" ),
138150 (NOW - dt .timedelta (minutes = 2 ), "2 minutes ago" ),
139- (NOW - dt .timedelta (hours = 1 , minutes = 30 , seconds = 30 ), "an hour ago" ),
140- (NOW - dt .timedelta (hours = 23 , minutes = 50 , seconds = 50 ), "23 hours ago" ),
151+ (NOW - dt .timedelta (hours = 1 , minutes = 29 , seconds = 30 ), "an hour ago" ),
152+ (NOW - dt .timedelta (hours = 1 , minutes = 30 , seconds = 30 ), "2 hours ago" ),
153+ (NOW - dt .timedelta (hours = 23 , minutes = 29 , seconds = 50 ), "23 hours ago" ),
154+ (NOW - dt .timedelta (hours = 23 , minutes = 50 , seconds = 50 ), "a day ago" ),
141155 (NOW - dt .timedelta (days = 1 ), "a day ago" ),
142156 (NOW - dt .timedelta (days = 500 ), "1 year, 4 months ago" ),
143157 (NOW - dt .timedelta (days = 365 * 2 + 35 ), "2 years ago" ),
144158 (NOW + dt .timedelta (seconds = 1 ), "a second from now" ),
145159 (NOW + dt .timedelta (seconds = 30 ), "30 seconds from now" ),
146- (NOW + dt .timedelta (minutes = 1 , seconds = 30 ), "a minute from now" ),
160+ (NOW + dt .timedelta (minutes = 1 , seconds = 29 ), "a minute from now" ),
161+ (NOW + dt .timedelta (minutes = 1 , seconds = 30 ), "2 minutes from now" ),
147162 (NOW + dt .timedelta (minutes = 2 ), "2 minutes from now" ),
148- (NOW + dt .timedelta (hours = 1 , minutes = 30 , seconds = 30 ), "an hour from now" ),
149- (NOW + dt .timedelta (hours = 23 , minutes = 50 , seconds = 50 ), "23 hours from now" ),
163+ (NOW + dt .timedelta (hours = 1 , minutes = 29 , seconds = 30 ), "an hour from now" ),
164+ (NOW + dt .timedelta (hours = 1 , minutes = 30 , seconds = 30 ), "2 hours from now" ),
165+ (NOW + dt .timedelta (hours = 23 , minutes = 29 , seconds = 50 ), "23 hours from now" ),
166+ (NOW + dt .timedelta (hours = 23 , minutes = 50 , seconds = 50 ), "a day from now" ),
150167 (NOW + dt .timedelta (days = 1 ), "a day from now" ),
151168 (NOW + dt .timedelta (days = 500 ), "1 year, 4 months from now" ),
152169 (NOW + dt .timedelta (days = 365 * 2 + 35 ), "2 years from now" ),
@@ -156,6 +173,7 @@ def test_naturaldelta(test_input: float | dt.timedelta, expected: str) -> None:
156173 (dt .timedelta (days = - 10000 ), "27 years from now" ),
157174 (dt .timedelta (days = 365 + 35 ), "1 year, 1 month ago" ),
158175 (23.5 , "23 seconds ago" ),
176+ # (23.9, "24 seconds ago"),
159177 (30 , "30 seconds ago" ),
160178 (NOW - dt .timedelta (days = 365 * 2 + 65 ), "2 years ago" ),
161179 (NOW - dt .timedelta (days = 365 + 4 ), "1 year, 4 days ago" ),
@@ -175,21 +193,26 @@ def test_naturaltime(
175193 (NOW , "now" ),
176194 (NOW - dt .timedelta (seconds = 1 ), "a second ago" ),
177195 (NOW - dt .timedelta (seconds = 30 ), "30 seconds ago" ),
178- (NOW - dt .timedelta (minutes = 1 , seconds = 30 ), "a minute ago" ),
196+ (NOW - dt .timedelta (minutes = 1 , seconds = 29 ), "a minute ago" ),
197+ (NOW - dt .timedelta (minutes = 1 , seconds = 30 ), "2 minutes ago" ),
179198 (NOW - dt .timedelta (minutes = 2 ), "2 minutes ago" ),
180- (NOW - dt .timedelta (hours = 1 , minutes = 30 , seconds = 30 ), "an hour ago" ),
181- (NOW - dt .timedelta (hours = 23 , minutes = 50 , seconds = 50 ), "23 hours ago" ),
199+ (NOW - dt .timedelta (hours = 1 , minutes = 29 , seconds = 30 ), "an hour ago" ),
200+ (NOW - dt .timedelta (hours = 1 , minutes = 30 , seconds = 30 ), "2 hours ago" ),
201+ (NOW - dt .timedelta (hours = 23 , minutes = 50 , seconds = 50 ), "a day ago" ),
182202 (NOW - dt .timedelta (days = 1 ), "a day ago" ),
183203 (NOW - dt .timedelta (days = 17 ), "17 days ago" ),
184204 (NOW - dt .timedelta (days = 47 ), "47 days ago" ),
185205 (NOW - dt .timedelta (days = 500 ), "1 year, 135 days ago" ),
186206 (NOW - dt .timedelta (days = 365 * 2 + 35 ), "2 years ago" ),
187207 (NOW + dt .timedelta (seconds = 1 ), "a second from now" ),
188208 (NOW + dt .timedelta (seconds = 30 ), "30 seconds from now" ),
189- (NOW + dt .timedelta (minutes = 1 , seconds = 30 ), "a minute from now" ),
209+ (NOW + dt .timedelta (minutes = 1 , seconds = 29 ), "a minute from now" ),
210+ (NOW + dt .timedelta (minutes = 1 , seconds = 30 ), "2 minutes from now" ),
190211 (NOW + dt .timedelta (minutes = 2 ), "2 minutes from now" ),
191- (NOW + dt .timedelta (hours = 1 , minutes = 30 , seconds = 30 ), "an hour from now" ),
192- (NOW + dt .timedelta (hours = 23 , minutes = 50 , seconds = 50 ), "23 hours from now" ),
212+ (NOW + dt .timedelta (hours = 1 , minutes = 29 , seconds = 30 ), "an hour from now" ),
213+ (NOW + dt .timedelta (hours = 1 , minutes = 30 , seconds = 30 ), "2 hours from now" ),
214+ (NOW + dt .timedelta (hours = 23 , minutes = 29 , seconds = 50 ), "23 hours from now" ),
215+ (NOW + dt .timedelta (hours = 23 , minutes = 50 , seconds = 50 ), "a day from now" ),
193216 (NOW + dt .timedelta (days = 1 ), "a day from now" ),
194217 (NOW + dt .timedelta (days = 500 ), "1 year, 135 days from now" ),
195218 (NOW + dt .timedelta (days = 365 * 2 + 35 ), "2 years from now" ),
@@ -419,19 +442,25 @@ def test_naturaltime_minimum_unit_explicit(
419442 (NOW_UTC , "now" ),
420443 (NOW_UTC - dt .timedelta (seconds = 1 ), "a second ago" ),
421444 (NOW_UTC - dt .timedelta (seconds = 30 ), "30 seconds ago" ),
422- (NOW_UTC - dt .timedelta (minutes = 1 , seconds = 30 ), "a minute ago" ),
445+ (NOW_UTC - dt .timedelta (minutes = 1 , seconds = 29 ), "a minute ago" ),
446+ (NOW_UTC - dt .timedelta (minutes = 1 , seconds = 30 ), "2 minutes ago" ),
423447 (NOW_UTC - dt .timedelta (minutes = 2 ), "2 minutes ago" ),
424- (NOW_UTC - dt .timedelta (hours = 1 , minutes = 30 , seconds = 30 ), "an hour ago" ),
425- (NOW_UTC - dt .timedelta (hours = 23 , minutes = 50 , seconds = 50 ), "23 hours ago" ),
448+ (NOW_UTC - dt .timedelta (hours = 1 , minutes = 29 , seconds = 30 ), "an hour ago" ),
449+ (NOW_UTC - dt .timedelta (hours = 1 , minutes = 30 , seconds = 30 ), "2 hours ago" ),
450+ (NOW_UTC - dt .timedelta (hours = 23 , minutes = 29 , seconds = 50 ), "23 hours ago" ),
451+ (NOW_UTC - dt .timedelta (hours = 23 , minutes = 50 , seconds = 50 ), "a day ago" ),
426452 (NOW_UTC - dt .timedelta (days = 1 ), "a day ago" ),
427453 (NOW_UTC - dt .timedelta (days = 500 ), "1 year, 4 months ago" ),
428454 (NOW_UTC - dt .timedelta (days = 365 * 2 + 35 ), "2 years ago" ),
429455 (NOW_UTC + dt .timedelta (seconds = 1 ), "a second from now" ),
430456 (NOW_UTC + dt .timedelta (seconds = 30 ), "30 seconds from now" ),
431- (NOW_UTC + dt .timedelta (minutes = 1 , seconds = 30 ), "a minute from now" ),
457+ (NOW_UTC + dt .timedelta (minutes = 1 , seconds = 29 ), "a minute from now" ),
458+ (NOW_UTC + dt .timedelta (minutes = 1 , seconds = 30 ), "2 minutes from now" ),
432459 (NOW_UTC + dt .timedelta (minutes = 2 ), "2 minutes from now" ),
433- (NOW_UTC + dt .timedelta (hours = 1 , minutes = 30 , seconds = 30 ), "an hour from now" ),
434- (NOW_UTC + dt .timedelta (hours = 23 , minutes = 50 , seconds = 50 ), "23 hours from now" ),
460+ (NOW_UTC + dt .timedelta (hours = 1 , minutes = 29 , seconds = 30 ), "an hour from now" ),
461+ (NOW_UTC + dt .timedelta (hours = 1 , minutes = 30 , seconds = 30 ), "2 hours from now" ),
462+ (NOW_UTC + dt .timedelta (hours = 23 , minutes = 29 , seconds = 50 ), "23 hours from now" ),
463+ (NOW_UTC + dt .timedelta (hours = 23 , minutes = 50 , seconds = 50 ), "a day from now" ),
435464 (NOW_UTC + dt .timedelta (days = 1 ), "a day from now" ),
436465 (NOW_UTC + dt .timedelta (days = 500 ), "1 year, 4 months from now" ),
437466 (NOW_UTC + dt .timedelta (days = 365 * 2 + 35 ), "2 years from now" ),
@@ -453,19 +482,25 @@ def test_naturaltime_timezone(test_input: dt.datetime, expected: str) -> None:
453482 (NOW_UTC , "now" ),
454483 (NOW_UTC - dt .timedelta (seconds = 1 ), "a second ago" ),
455484 (NOW_UTC - dt .timedelta (seconds = 30 ), "30 seconds ago" ),
456- (NOW_UTC - dt .timedelta (minutes = 1 , seconds = 30 ), "a minute ago" ),
485+ (NOW_UTC - dt .timedelta (minutes = 1 , seconds = 29 ), "a minute ago" ),
486+ (NOW_UTC - dt .timedelta (minutes = 1 , seconds = 30 ), "2 minutes ago" ),
457487 (NOW_UTC - dt .timedelta (minutes = 2 ), "2 minutes ago" ),
458- (NOW_UTC - dt .timedelta (hours = 1 , minutes = 30 , seconds = 30 ), "an hour ago" ),
459- (NOW_UTC - dt .timedelta (hours = 23 , minutes = 50 , seconds = 50 ), "23 hours ago" ),
488+ (NOW_UTC - dt .timedelta (hours = 1 , minutes = 29 , seconds = 30 ), "an hour ago" ),
489+ (NOW_UTC - dt .timedelta (hours = 1 , minutes = 30 , seconds = 30 ), "2 hours ago" ),
490+ (NOW_UTC - dt .timedelta (hours = 23 , minutes = 29 , seconds = 50 ), "23 hours ago" ),
491+ (NOW_UTC - dt .timedelta (hours = 23 , minutes = 50 , seconds = 50 ), "a day ago" ),
460492 (NOW_UTC - dt .timedelta (days = 1 ), "a day ago" ),
461493 (NOW_UTC - dt .timedelta (days = 500 ), "1 year, 4 months ago" ),
462494 (NOW_UTC - dt .timedelta (days = 365 * 2 + 35 ), "2 years ago" ),
463495 (NOW_UTC + dt .timedelta (seconds = 1 ), "a second from now" ),
464496 (NOW_UTC + dt .timedelta (seconds = 30 ), "30 seconds from now" ),
465- (NOW_UTC + dt .timedelta (minutes = 1 , seconds = 30 ), "a minute from now" ),
497+ (NOW_UTC + dt .timedelta (minutes = 1 , seconds = 29 ), "a minute from now" ),
498+ (NOW_UTC + dt .timedelta (minutes = 1 , seconds = 30 ), "2 minutes from now" ),
466499 (NOW_UTC + dt .timedelta (minutes = 2 ), "2 minutes from now" ),
467- (NOW_UTC + dt .timedelta (hours = 1 , minutes = 30 , seconds = 30 ), "an hour from now" ),
468- (NOW_UTC + dt .timedelta (hours = 23 , minutes = 50 , seconds = 50 ), "23 hours from now" ),
500+ (NOW_UTC + dt .timedelta (hours = 1 , minutes = 29 , seconds = 30 ), "an hour from now" ),
501+ (NOW_UTC + dt .timedelta (hours = 1 , minutes = 30 , seconds = 30 ), "2 hours from now" ),
502+ (NOW_UTC + dt .timedelta (hours = 23 , minutes = 29 , seconds = 50 ), "23 hours from now" ),
503+ (NOW_UTC + dt .timedelta (hours = 23 , minutes = 50 , seconds = 50 ), "a day from now" ),
469504 (NOW_UTC + dt .timedelta (days = 1 ), "a day from now" ),
470505 (NOW_UTC + dt .timedelta (days = 500 ), "1 year, 4 months from now" ),
471506 (NOW_UTC + dt .timedelta (days = 365 * 2 + 35 ), "2 years from now" ),
0 commit comments