OCaml Unix.localtime and ocaml-tz: thread‑safe zone conversion interoperability
0 reputation · 17 Jan 2021, 21:28 UTC
0 reputation · 17 Jan 2021, 21:28 UTC
Goal: enable conversion of a POSIX timestamp to a broken‑down time in an arbitrary time zone without mutating the process TZ variable, while remaining safe for concurrent calls.
Constraint: Unix.localtime and Unix.gmtime rely on the C library’s tzdata and modify a shared static struct, making them unsafe in multi‑threaded code; the standard library offers no zone‑aware alternative, forcing developers to use external libraries such as ocaml‑tz or to manipulate TZ, which introduces race conditions.
Uncertainty: whether the OCaml standard library should expose a thread‑safe, zone‑aware API akin to Java’s java.time, and if so, what its signature and module location should be.
Should the standard library provide a thread‑safe localtime/gmtime analogue that accepts an explicit time‑zone identifier? If added, where should it reside (e.g., Unix.Zone or a new Zonetime module) and what type should it return? How would this new API coexist with the existing TZ‑dependent functions without breaking existing code?
34775 reputation · 18 Jan 2021, 06:34 UTC
To convert a POSIX timestamp to a broken-down time in an arbitrary time zone without mutating the process-level TZ variable, you must move away from Unix.localtime and Unix.gmtime. Because these functions wrap C library calls that rely on a shared static buffer and global environment state, they are inherently unsafe for concurrent use in multi-threaded OCaml applications.
The practical solution is to use a library like ocaml-tz. This library implements the IANA time zone database in pure OCaml, allowing you to pass a specific zone object as an argument to the conversion function, thereby bypassing the global C runtime state entirely.
To implement this safely, follow these steps:
putenv/setenv: Do not attempt to change the TZ variable at runtime to switch zones; this creates race conditions across all threads.Tz.zone "America/New_York" (or the desired identifier) to create a zone handle.tm struct.Regarding the potential for a standard library API, the following architectural assumptions apply:
Unix.Zone or Zonetime, would be preferable to Unix. Adding parameters to existing Unix.localtime signatures would break backward compatibility.tm but without the C-binding constraints) to ensure thread safety.TZ environment variable entirely, relying instead on an explicit zone parameter. This separates "system local time" (legacy) from "explicit zone time" (modern).When interoperating between Unix.localtime and ocaml-tz, be aware that discrepancies may arise if the host system's tzdata version differs from the version bundled with the OCaml library. You can verify consistency by comparing the library output against the system's zdump utility for a specific timestamp.
Missing Diagnostic: To refine this recommendation, please specify if your application must strictly adhere to the host system's tzdata updates without requiring a library re-compile.
Use comments to ask for clarification. Post a solution as an answer.
34,775 reputation · 18 Jan 2021, 03:27 UTC
While Unix.localtime and Unix.gmtime call the C library’s localtime_r and gmtime_r under the hood, they still depend on a shared tm buffer that can be clobbered by concurrent threads. ocaml‑tz sidesteps this by keeping the entire IANA time‑zone database in OCaml and exposing a function
ZonedTime.of_unix_time : Unix.time -> TZ.t -> ZonedTime.t
which returns a record with fields like year, month, hour, etc. Because the conversion is performed in pure OCaml, no TZ environment variable is touched and the call is safe for multi‑threaded programs.
If the standard library were to add a thread‑safe API, a natural home would be a new Unix.Zone or Time.Zone module, mirroring the signature above. It could coexist with the existing functions by keeping the old ones non‑deprecated and documenting the new module as the recommended path for zone‑aware code.