Skip to content
Browse files

[FIX] hr_payroll: handle timezones coherently in hr.benefit

This commit simplifies the way the timezones are handled in hr_benefit.
Creating a benefit now uses naive datetimes obtained by converting from
the employee's timezone into UTC and then stripping timezone

closes #31968

Signed-off-by: Yannick Tivisse (yti) <>
  • Loading branch information...
RomainLibert committed Mar 19, 2019
1 parent 20ae38a commit 6878c9f3424f27df9b07a665f9c0f5ef6e254996
@@ -139,7 +139,9 @@ def _safe_duplicate_create(self, vals_list, date_start, date_stop):
r['benefit_type_id'][0] if r['benefit_type_id'] else False,
) for r in month_recs}
new_vals = [v for v in vals_list if (v['date_start'].replace(tzinfo=None), v['date_stop'].replace(tzinfo=None), v['employee_id'], v['benefit_type_id']) not in existing_entries]
assert all(v['date_start'].tzinfo is None for v in vals_list)
assert all(v['date_stop'].tzinfo is None for v in vals_list)
new_vals = [v for v in vals_list if (v['date_start'], v['date_stop'], v['employee_id'], v['benefit_type_id']) not in existing_entries]
# Remove duplicates from vals_list, shouldn't be necessary from saas-12.2
unique_new_vals = set()
for values in new_vals:
@@ -184,18 +186,16 @@ def _split_range_by_day(start, end):
if ==
new_benefits |= benefit
tz = pytz.timezone(
benefit_start, benefit_stop = tz.localize(benefit.date_start), tz.localize(benefit.date_stop)
values = {
benefit_state = benefit.state
benefits_to_unlink |= benefit
for start, stop in _split_range_by_day(benefit_start, benefit_stop):
values['date_start'] = start.astimezone(pytz.utc)
values['date_stop'] = stop.astimezone(pytz.utc)
for start, stop in _split_range_by_day(benefit.date_start, benefit.date_stop):
values['date_start'] = start
values['date_stop'] = stop
new_benefit = self.create(values)
# Write the state after the creation due to the ir.rule on benefit state
new_benefit.state = benefit_state
@@ -244,8 +244,8 @@ def _duplicate_to_calendar_leave(self):
def _duplicate_to_calendar_attendance(self):
mapped_data = {
benefit: [
pytz.utc.localize(benefit.date_start).astimezone(pytz.timezone(, # Start date
pytz.utc.localize(benefit.date_stop).astimezone(pytz.timezone( # End date
] for benefit in self

@@ -1,11 +1,11 @@
# -*- coding:utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo import api, fields, models, _
from odoo.addons.resource.models.resource import Intervals
from datetime import datetime, timedelta
from datetime import datetime
import pytz

from odoo import api, fields, models

class HrEmployee(models.Model):
_inherit = 'hr.employee'
@@ -22,41 +22,39 @@ def _compute_payslip_count(self):

def generate_benefit(self, date_start, date_stop):
date_start = date_start.replace(tzinfo=pytz.utc)
date_stop = date_stop.replace(tzinfo=pytz.utc)
date_start = fields.Datetime.to_datetime(date_start)
date_stop = fields.Datetime.to_datetime(date_stop)
attendance_type = self.env.ref('hr_payroll.benefit_type_attendance')
vals_list = []
leaves = self.env['hr.leave']

for employee in self:

# Approved leaves
emp_leaves = employee.resource_calendar_id.leave_ids.filtered(
lambda r:
r.resource_id == employee.resource_id and
r.date_from.replace(tzinfo=pytz.utc) <= date_stop and
r.date_to.replace(tzinfo=pytz.utc) >= date_start
r.date_from <= date_stop and
r.date_to >= date_start
global_leaves = employee.resource_calendar_id.global_leave_ids

employee_leaves = (emp_leaves | global_leaves).mapped('holiday_id')

for contract in employee._get_contracts(date_start, date_stop):

date_start_benefits = max(date_start, datetime.combine(contract.date_start, datetime.min.time()).replace(tzinfo=pytz.utc))
date_stop_benefits = min(date_stop, datetime.combine(contract.date_end or, datetime.max.time()).replace(tzinfo=pytz.utc))
date_start_benefits = max(date_start, fields.Datetime.to_datetime(contract.date_start)).replace(tzinfo=pytz.utc)
date_stop_benefits = min(date_stop, datetime.combine(contract.date_end or, datetime.max.time())).replace(tzinfo=pytz.utc)

calendar = contract.resource_calendar_id
resource = employee.resource_id
attendances = calendar._work_intervals(date_start_benefits, date_stop_benefits, resource=resource)
# Attendances
for interval in attendances:
benefit_type_id = interval[2].mapped('benefit_type_id')[:1] or attendance_type
# All benefits generated here are using datetimes converted from the employee's timezone
vals_list += [{
'name': "%s: %s" % (,,
'date_start': interval[0].astimezone(pytz.utc),
'date_stop': interval[1].astimezone(pytz.utc),
'date_start': interval[0].astimezone(pytz.utc).replace(tzinfo=None),
'date_stop': interval[1].astimezone(pytz.utc).replace(tzinfo=None),
'state': 'confirmed',
Oops, something went wrong.

0 comments on commit 6878c9f

Please sign in to comment.
You can’t perform that action at this time.