1from __future__ import annotations
2
3from datetime import datetime
4from decimal import ROUND_HALF_UP, Decimal
5from typing import TYPE_CHECKING
6
7from isoduration.operations.util import max_day_in_month, mod2, mod3, quot2, quot3
8
9if TYPE_CHECKING: # pragma: no cover
10 from isoduration.types import Duration
11
12
13def add(start: datetime, duration: Duration) -> datetime:
14 """
15 https://www.w3.org/TR/xmlschema-2/#adding-durations-to-dateTimes
16 """
17
18 # Months.
19 temp = Decimal(start.month) + duration.date.months
20 end_month = mod3(temp, Decimal(1), Decimal(13))
21 carry = quot3(temp, Decimal(1), Decimal(13))
22
23 # Years.
24 end_year = Decimal(start.year) + duration.date.years + carry
25
26 # Zone.
27 end_tzinfo = start.tzinfo
28
29 # Seconds.
30 temp = Decimal(start.second) + duration.time.seconds
31 end_second = mod2(temp, Decimal("60"))
32 carry = quot2(temp, Decimal("60"))
33
34 # Minutes.
35 temp = Decimal(start.minute) + duration.time.minutes + carry
36 end_minute = mod2(temp, Decimal("60"))
37 carry = quot2(temp, Decimal("60"))
38
39 # Hours.
40 temp = Decimal(start.hour) + duration.time.hours + carry
41 end_hour = mod2(temp, Decimal("24"))
42 carry = quot2(temp, Decimal("24"))
43
44 # Days.
45 end_max_day_in_month = max_day_in_month(end_year, end_month)
46
47 if start.day > end_max_day_in_month:
48 temp = end_max_day_in_month
49 else:
50 temp = Decimal(start.day)
51
52 end_day = temp + duration.date.days + (7 * duration.date.weeks) + carry
53
54 while True:
55 if end_day < 1:
56 end_day += max_day_in_month(end_year, end_month - 1)
57 carry = Decimal(-1)
58 elif end_day > max_day_in_month(end_year, end_month):
59 end_day -= max_day_in_month(end_year, end_month)
60 carry = Decimal(1)
61 else:
62 break
63
64 temp = end_month + carry
65 end_month = mod3(temp, Decimal(1), Decimal(13))
66 end_year = end_year + quot3(temp, Decimal(1), Decimal(13))
67
68 return datetime(
69 year=int(end_year.to_integral_value(ROUND_HALF_UP)),
70 month=int(end_month.to_integral_value(ROUND_HALF_UP)),
71 day=int(end_day.to_integral_value(ROUND_HALF_UP)),
72 hour=int(end_hour.to_integral_value(ROUND_HALF_UP)),
73 minute=int(end_minute.to_integral_value(ROUND_HALF_UP)),
74 second=int(end_second.to_integral_value(ROUND_HALF_UP)),
75 tzinfo=end_tzinfo,
76 )