Skip to content

Commit fd056fb

Browse files
committed
save
1 parent 107a4ab commit fd056fb

File tree

2 files changed

+111
-27
lines changed

2 files changed

+111
-27
lines changed

src/tests/test_datetime.py

Lines changed: 105 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,16 @@
5858
TimedeltaToMillisecondsError,
5959
YieldDaysError,
6060
YieldWeekdaysError,
61+
_DateDurationToIntFloatError,
62+
_DateDurationToIntTimeDeltaError,
63+
_DateDurationToTimeDeltaFloatError,
64+
_DateDurationToTimeDeltaTimeDeltaError,
6165
add_duration,
6266
add_weekdays,
6367
check_date_not_datetime,
6468
check_zoned_datetime,
69+
date_duration_to_int,
70+
date_duration_to_timedelta,
6571
date_to_datetime,
6672
date_to_month,
6773
datetime_duration_to_float,
@@ -114,6 +120,7 @@
114120
text_clean,
115121
zoned_datetimes,
116122
)
123+
from utilities.math import is_integral
117124
from utilities.zoneinfo import UTC, HongKong, Tokyo
118125

119126
if TYPE_CHECKING:
@@ -208,6 +215,86 @@ def test_datetime(self, *, datetime: dt.datetime) -> None:
208215
check_zoned_datetime(datetime)
209216

210217

218+
@mark.only
219+
class TestDateDurationToInt:
220+
@given(n=integers())
221+
def test_int(self, *, n: int) -> None:
222+
result = date_duration_to_int(n)
223+
assert result == n
224+
225+
@given(n=integers().map(float))
226+
def test_float_integral(self, *, n: float) -> None:
227+
result = date_duration_to_int(n)
228+
assert result == round(n)
229+
230+
@given(n=integers())
231+
def test_timedelta(self, *, n: int) -> None:
232+
with assume_does_not_raise(OverflowError):
233+
timedelta = dt.timedelta(days=n)
234+
result = date_duration_to_int(timedelta)
235+
assert result == n
236+
237+
@given(n=floats(allow_nan=False, allow_infinity=False))
238+
def test_error_float(self, *, n: float) -> None:
239+
_ = assume(not is_integral(n))
240+
with raises(_DateDurationToIntFloatError):
241+
_ = date_duration_to_int(n)
242+
243+
@given(
244+
n=integers(),
245+
frac=timedeltas(
246+
min_value=-(DAY - MICROSECOND), max_value=DAY - MICROSECOND
247+
).filter(lambda x: x != ZERO_TIME),
248+
)
249+
def test_error_timedelta(self, *, n: int, frac: dt.timedelta) -> None:
250+
with assume_does_not_raise(OverflowError):
251+
timedelta = dt.timedelta(days=n) + frac
252+
with raises(_DateDurationToIntTimeDeltaError):
253+
_ = date_duration_to_int(timedelta)
254+
255+
256+
@mark.only
257+
class TestDateDurationToTimeDelta:
258+
@given(n=integers())
259+
def test_int(self, *, n: int) -> None:
260+
with assume_does_not_raise(OverflowError):
261+
result = date_duration_to_timedelta(n)
262+
expected = dt.timedelta(days=n)
263+
assert result == expected
264+
265+
@given(n=integers().map(float))
266+
def test_float_integral(self, *, n: float) -> None:
267+
with assume_does_not_raise(OverflowError):
268+
result = date_duration_to_timedelta(n)
269+
expected = dt.timedelta(days=round(n))
270+
assert result == expected
271+
272+
@given(n=integers())
273+
def test_timedelta(self, *, n: int) -> None:
274+
with assume_does_not_raise(OverflowError):
275+
timedelta = dt.timedelta(days=n)
276+
result = date_duration_to_timedelta(timedelta)
277+
assert result == timedelta
278+
279+
@given(n=floats(allow_nan=False, allow_infinity=False))
280+
def test_error_float(self, *, n: float) -> None:
281+
_ = assume(not is_integral(n))
282+
with raises(_DateDurationToTimeDeltaFloatError):
283+
_ = date_duration_to_timedelta(n)
284+
285+
@given(
286+
n=integers(),
287+
frac=timedeltas(
288+
min_value=-(DAY - MICROSECOND), max_value=DAY - MICROSECOND
289+
).filter(lambda x: x != ZERO_TIME),
290+
)
291+
def test_error_timedelta(self, *, n: int, frac: dt.timedelta) -> None:
292+
with assume_does_not_raise(OverflowError):
293+
timedelta = dt.timedelta(days=n) + frac
294+
with raises(_DateDurationToTimeDeltaTimeDeltaError):
295+
_ = date_duration_to_timedelta(timedelta)
296+
297+
211298
class TestDateToDatetime:
212299
@given(date=dates())
213300
def test_main(self, *, date: dt.date) -> None:
@@ -234,6 +321,24 @@ def test_timedelta(self, *, duration: dt.timedelta) -> None:
234321
assert result == duration.total_seconds()
235322

236323

324+
class TestDateTimeDurationToTimedelta:
325+
@given(duration=integers(0, 10))
326+
def test_int(self, *, duration: int) -> None:
327+
result = datetime_duration_to_timedelta(duration)
328+
assert result.total_seconds() == duration
329+
330+
@given(duration=floats(0.0, 10.0))
331+
def test_float(self, *, duration: float) -> None:
332+
duration = round(10 * duration) / 10
333+
result = datetime_duration_to_timedelta(duration)
334+
assert isclose(result.total_seconds(), duration)
335+
336+
@given(duration=timedeltas())
337+
def test_timedelta(self, *, duration: dt.timedelta) -> None:
338+
result = datetime_duration_to_timedelta(duration)
339+
assert result == duration
340+
341+
237342
class TestDaysSinceEpoch:
238343
@given(date=dates())
239344
def test_datetime_to_microseconds(self, *, date: dt.date) -> None:
@@ -257,24 +362,6 @@ def test_main(self, *, datetime: dt.datetime) -> None:
257362
assert result.microsecond == 0
258363

259364

260-
class TestDurationToTimedelta:
261-
@given(duration=integers(0, 10))
262-
def test_int(self, *, duration: int) -> None:
263-
result = datetime_duration_to_timedelta(duration)
264-
assert result.total_seconds() == duration
265-
266-
@given(duration=floats(0.0, 10.0))
267-
def test_float(self, *, duration: float) -> None:
268-
duration = round(10 * duration) / 10
269-
result = datetime_duration_to_timedelta(duration)
270-
assert isclose(result.total_seconds(), duration)
271-
272-
@given(duration=timedeltas())
273-
def test_timedelta(self, *, duration: dt.timedelta) -> None:
274-
result = datetime_duration_to_timedelta(duration)
275-
assert result == duration
276-
277-
278365
class TestEpoch:
279366
def test_date(self) -> None:
280367
assert isinstance(EPOCH_DATE, dt.date)

src/utilities/datetime.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def date_duration_to_int(duration: Duration, /) -> int:
173173
except SafeRoundError:
174174
raise _DateDurationToIntFloatError(duration=duration) from None
175175
case dt.timedelta():
176-
if (duration.seconds == 0) and (duration.microseconds == 0):
176+
if is_integral_timedelta(duration):
177177
return duration.days
178178
raise _DateDurationToIntTimeDeltaError(duration=duration) from None
179179
case _ as never: # pyright: ignore[reportUnnecessaryComparison]
@@ -196,7 +196,7 @@ def __str__(self) -> str:
196196
class _DateDurationToIntTimeDeltaError(DateDurationToIntError):
197197
@override
198198
def __str__(self) -> str:
199-
return f"Timedelta duration must be day-only; got {self.duration}"
199+
return f"Timedelta duration must be integral; got {self.duration}"
200200

201201

202202
def date_duration_to_timedelta(duration: Duration, /) -> dt.timedelta:
@@ -211,14 +211,11 @@ def date_duration_to_timedelta(duration: Duration, /) -> dt.timedelta:
211211
raise _DateDurationToTimeDeltaFloatError(duration=duration) from None
212212
return dt.timedelta(days=as_int)
213213
case dt.timedelta():
214-
if (duration.seconds == 0) and (duration.microseconds == 0):
215-
return duration.days
216-
raise _DateDurationToDateTimeTimeDeltaError(duration=duration) from None
214+
if is_integral_timedelta(duration):
215+
return duration
216+
raise _DateDurationToTimeDeltaTimeDeltaError(duration=duration) from None
217217
case _ as never: # pyright: ignore[reportUnnecessaryComparison]
218218
assert_never(never)
219-
if isinstance(duration, int | float):
220-
return dt.timedelta(seconds=duration)
221-
return duration
222219

223220

224221
@dataclass(kw_only=True, slots=True)
@@ -237,7 +234,7 @@ def __str__(self) -> str:
237234
class _DateDurationToTimeDeltaTimeDeltaError(DateDurationToTimeDeltaError):
238235
@override
239236
def __str__(self) -> str:
240-
return f"Timedelta duration must be day-only; got {self.duration}"
237+
return f"Timedelta duration must be integral; got {self.duration}"
241238

242239

243240
##

0 commit comments

Comments
 (0)