EMS

Technical Reference: dates, times and timezones

Every date/time EMS shows, reads or decides with is in the company’s timezone (the company partner’s tz, Europe/Madrid for Institut Puig Castellar, set in data/custom/res.partner.csv). Never the acting user’s own tz, never the browser’s, never the server’s clock read naively. The whole centre works from a single place, so there is exactly one timezone; a centre elsewhere only has to set its own company’s timezone.

Read this before touching anything that stores, shows, compares or converts a date or a time.

Why: three sources of “time”, none of them trustworthy on its own

Source Who uses it What goes wrong
The browser’s clock and timezone The whole web client (luxon’s “default” zone), and any JS that calls new Date()/DateTime.now() A computer with a wrong timezone or clock shows and saves shifted times, and decides “today”/”now” wrongly: a teacher got the wrong roll-call slot preselected because of it (issue #518)
Each user’s own tz (res.partner.tz) Native Odoo server-side formatting: portal QWeb datetime widgets, reports, format_datetime() (e.g. the attendance kiosk’s “hasn’t checked out since…” error, formatted with the public user’s tz) Odoo fills it in from the browser (the tz cookie) on a user’s first login, so it ends up being whatever that computer said (users in America/Lima, Atlantic/Canary… were found)
The server’s clock, read naively (datetime.now(), date.today(), fields.Date.today()) Python code The Odoo process always runs in UTC (odoo/_monkeypatches/__init__.py forces TZ=UTC, whatever the OS is set to), so those are UTC: 2h behind in summer, and “today” is yesterday between 00:00 and 02:00

The server itself must stay in UTC (Odoo’s standard, and changing the OS timezone has no effect on Odoo anyway): datetimes are stored as naive UTC, and converted only when shown or read from a user.

The mechanism

flowchart LR
    C["Company partner tz\n(Europe/Madrid)"] --> U["ems.datetime_utils\ncurrent_tz() / get_local_datetime()\n/ get_local_today()"]
    C --> S["ir.http session_info\n+ frontend session info\n('ems_tz')"]
    S --> J["company_timezone_service.js\nluxon default zone + 'tz' cookie"]
    K["Server clock\nget_server_epoch_ms()"] --> N["server_clock.js\nserverNow()"]
    J --> N

Rules for new code

Every record’s own tz follows the company’s

Native Odoo formats plenty with a record’s own tz (portal widgets, reports, emails, the kiosk’s error, formatted with the public user’s), so those are kept equal to the company’s:

The one exception: LimeSurvey

Our LimeSurvey server runs in UTC with no time adjustment, and reads the survey start/expiry dates EMS sends it as its own time, so those are sent in UTC (LimesurveyApi._limesurvey_now()). If its “time difference” setting is ever changed, change that method too.