Timezone of the enaio installation
enaio stores every point in time as a Unix epoch number, and it computes that number in the timezone of the server. The protocol does not carry that timezone, so the client cannot derive it — it has to be told.
1. The problem
A DATETIME index field is written as wall-clock text, without a timezone:
<Field internal_name="DateTimeField">15.06.2024 10:30:00</Field>
It comes back as an epoch number, which the server computed from that wall clock in its own timezone:
<Field value="1718440200" internal_name="DateTimeField" datatype="DATETIME" dbname="zahl1" />
1718440200 is 08:30 UTC, which is 10:30 in Europe/Zurich. Resolving that number in any
other timezone yields a different time of day than the one the enaio client displays.
Historically the library handled this implicitly: every conversion went through
datetime.fromtimestamp() and datetime.timestamp(), that is, through the timezone of the
client process. That works as long as the process runs in the installation’s timezone. When it
does not, every timestamp is shifted by the offset, and nothing anywhere reports an error.
|
|
2. Configuration
Set it once at start-up, before the first job runs:
from ecmind_blue_client import set_server_timezone
set_server_timezone("Europe/Zurich")
Or through an environment variable, if application code should stay untouched:
ECMIND_BLUE_SERVER_TIMEZONE=Europe/Zurich
The setting is process-wide on purpose: the timezone is a property of the installation, not of a connection, and every pool in a process talks to one installation.
A name that cannot be resolved raises ValueError instead of silently falling back to the host
timezone. The cause is a missing IANA time zone database. Windows has none, which is why the
library depends on tzdata there; in a slim Linux container the package has to be installed.
3. Scope
Every epoch conversion is affected:
-
DATETIMEindex fields, reading and writing -
the
<Created>and<Modified>base parameters (obj.system.base_params) -
obj.system.deleted_at(OBJECT_DELETED) -
the
concurrency_timestampused for optimistic locking, a round trip ofOBJECT_MODIFYTIME -
workflow variables of type
DATETIME -
JobParameterTypes.DATE_TIMEon the wire, in both directions
Not affected are values that travel as text (DATE index fields, workflow date and time
variables) and timestamps the library has always read as explicit UTC (administration info,
process list, the last_modified of OS events). Those denote instants, not installation wall
clock.
4. Behaviour when unset
Left unset, the previous behaviour applies: the host process timezone governs. Nothing changes
for an installation that is already operated with a matching TZ. What is new is being able to
state it explicitly instead of imposing it on the host.
5. Naive and aware values
Return values are naive datetime objects in the installation’s wall clock. Naive so that a
value read from the server can be written straight back and compared with other field values
without mixing naive and aware datetimes.
When writing, a naive value is read as installation wall clock. An aware value is already unambiguous and is converted exactly, so the instant the caller expressed is the instant that gets stored.
from datetime import datetime, timezone
set_server_timezone("Europe/Zurich")
doc.DateTimeField = datetime(2024, 6, 15, 10, 30) # 10:30 in the DMS
doc.DateTimeField = datetime(2024, 6, 15, 8, 30, tzinfo=timezone.utc) # also 10:30
6. Conversion functions
The conversions are exported for your own processing:
| Function | Meaning |
|---|---|
|
Epoch value → naive installation wall clock |
|
|
|
|