Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 23 additions & 13 deletions hr_holidays_leave_repeated/models/hr_leave.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,25 +29,38 @@ class HrLeave(models.Model):

@api.model
def _update_repeated_workday_dates(self, resource_calendar, from_dt, to_dt, days):
user = self.env.user
from_dt = fields.Datetime.context_timestamp(user, from_dt)
to_dt = fields.Datetime.context_timestamp(user, to_dt)
client_tz = timezone(self._context.get("tz") or self.env.user.tz or "UTC")
from_dt = from_dt.astimezone(client_tz)
to_dt = to_dt.astimezone(client_tz)
work_hours = resource_calendar.get_work_hours_count(
from_dt, to_dt, compute_leaves=False
)
while work_hours:
previous_from_dt = from_dt
previous_to_dt = to_dt
from_dt = from_dt + relativedelta(days=days)
to_dt = to_dt + relativedelta(days=days)

# Handle case where Daylight Saving Time changes between 2 dates
# We want to keep the same hours in localized time
from_dt = from_dt.astimezone(client_tz)
to_dt = to_dt.astimezone(client_tz)

from_dt_dst_diff = previous_from_dt.dst() - from_dt.dst()
to_dt_dst_diff = previous_to_dt.dst() - to_dt.dst()

from_dt = from_dt + from_dt_dst_diff
from_dt = from_dt.astimezone(client_tz)
to_dt = to_dt + to_dt_dst_diff
to_dt = to_dt.astimezone(client_tz)

new_work_hours = resource_calendar.get_work_hours_count(
from_dt, to_dt, compute_leaves=True
)
if new_work_hours and work_hours <= new_work_hours:
break

return from_dt.astimezone(utc).replace(tzinfo=None), to_dt.astimezone(
utc
).replace(tzinfo=None)
return from_dt, to_dt

@api.model
def _get_repeated_vals_dict(self):
Expand Down Expand Up @@ -95,16 +108,13 @@ def _update_repeated_leave_vals(self, leave, resource_calendar):
from_dt, to_dt = self._update_repeated_workday_dates(
resource_calendar, from_dt, to_dt, param_dict["days"]
)
client_tz = timezone(self._context.get("tz") or self.env.user.tz or "UTC")
request_date_from = utc.localize(from_dt).astimezone(client_tz)
request_date_to = utc.localize(to_dt).astimezone(client_tz)

return {
"employee_id": leave.employee_id.id,
"date_from": from_dt,
"date_to": to_dt,
"request_date_from": request_date_from,
"request_date_to": request_date_to,
"date_from": from_dt.astimezone(utc).replace(tzinfo=None),
"date_to": to_dt.astimezone(utc).replace(tzinfo=None),
"request_date_from": from_dt,
"request_date_to": to_dt,
}

@api.model
Expand Down
104 changes: 104 additions & 0 deletions hr_holidays_leave_repeated/tests/test_holidays_leave_repeated.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,3 +320,107 @@ def test_09_check_repeat_end_date(self):
}
)
self.assertEqual(len(leaves), 5)

def test_10_check_dst_time(self):
self.calendar.tz = "Europe/Paris"
self.employee_1.tz = "Europe/Paris"
allocation = self.env["hr.leave.allocation"].create(
{
"name": "Initial Allocation",
"holiday_status_id": self.status_1.id,
"number_of_days": 20,
"employee_id": self.employee_1.id,
"date_from": date(2025, 1, 1),
}
)
allocation.action_validate()
leaves_1 = self.env["hr.leave"].create(
{
"holiday_status_id": self.status_1.id,
"repeat_every": "week",
"repeat_mode": "times",
"repeat_limit": 5,
"request_date_from": datetime(2025, 3, 20, 8),
"request_date_to": datetime(2025, 3, 20, 18),
"employee_id": self.employee_1.id,
}
)
leaves_2 = self.env["hr.leave"].create(
{
"holiday_status_id": self.status_1.id,
"repeat_every": "week",
"repeat_mode": "times",
"repeat_limit": 5,
"request_date_from": datetime(2025, 10, 16, 8),
"request_date_to": datetime(2025, 10, 16, 18),
"employee_id": self.employee_1.id,
}
)
self.assertEqual(
leaves_1.mapped("date_from"),
[
datetime(2025, 3, 20, 7),
datetime(2025, 3, 27, 7),
datetime(2025, 4, 3, 6),
datetime(2025, 4, 10, 6),
datetime(2025, 4, 17, 6),
],
)
self.assertEqual(
leaves_2.mapped("date_from"),
[
datetime(2025, 10, 16, 6),
datetime(2025, 10, 23, 6),
datetime(2025, 10, 30, 7),
datetime(2025, 11, 6, 7),
datetime(2025, 11, 13, 7),
],
)
self.assertEqual(
list(
set(
leaves_1.mapped(
lambda leave: leave.date_from.astimezone(
timezone("Europe/Paris")
).hour
)
)
),
[8],
)
self.assertEqual(
list(
set(
leaves_1.mapped(
lambda leave: leave.date_to.astimezone(
timezone("Europe/Paris")
).hour
)
)
),
[18],
)
self.assertEqual(
list(
set(
leaves_2.mapped(
lambda leave: leave.date_from.astimezone(
timezone("Europe/Paris")
).hour
)
)
),
[8],
)
self.assertEqual(
list(
set(
leaves_2.mapped(
lambda leave: leave.date_to.astimezone(
timezone("Europe/Paris")
).hour
)
)
),
[18],
)