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.

DateFormat is not a way out. The parameter only affects DATE fields, which travel as text. DATETIME always comes back as an epoch number regardless of it, even with %H:%M:%S or %z in the format string.

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:

  • DATETIME index fields, reading and writing

  • the <Created> and <Modified> base parameters (obj.system.base_params)

  • obj.system.deleted_at (OBJECT_DELETED)

  • the concurrency_timestamp used for optimistic locking, a round trip of OBJECT_MODIFYTIME

  • workflow variables of type DATETIME

  • JobParameterTypes.DATE_TIME on 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_to_server_time(seconds)

Epoch value → naive installation wall clock

server_time_to_epoch(value)

datetime → epoch value; the inverse of the above, hence exact across a round trip

to_server_time(value)

datetime → naive installation wall clock, for the text form in DMS XML