Clojure's #inst reader parses offset-less ISO-8601 strings as UTC instants by default. There is no global switch to treat these as belonging to a specific ZoneId. To achieve per-thread configuration without altering global *data-readers*, you must use clojure.tools.reader and lexical scope rebinding.
Per-Thread Configuration via Binding
The safest way to apply a default ZoneId to a specific thread is to rebind the :inst reader within a binding block. This ensures that only code within that scope uses the regional zone, while libraries and other parts of the application continue to see standard UTC instants.
(require '[clojure.tools.reader as reader]
[java.time :as dt])
(defn with-local-zone [zone-id f]
(binding [*data-readers* (assoc *data-readers* :inst (fn [s]
(let [i (reader #inst s)]
(dt/ZonedDateTime/ofInstant i zone-id)))]
(f)))
Mechanism for Zone-Specific Conversion
To allow application code to request zone-specific conversion while keeping libraries safe, use a helper function that explicitly converts the Instant returned by standard #inst. This prevents the "side-effect" of a custom reader from leaking into business logic.
(defn to-zone [instant zone-id]
(dt/ZonedDateTime/ofInstant instant (dt/ZoneId/of zone-id))))
Safe Rebinding in Concurrent Environments
The established pattern for safely rebinding *data-readers* is the binding macro itself. Because *data-readers* is a thread-local map in the Clojure runtime, rebinding it is thread-safe. To avoid breaking dependent code:
- Avoid using
swap! on global atoms to store reader-specific logic at runtime.
- Use
clojure.tools.reader for the custom logic to avoid conflicting with the core language #inst behavior.
- Ensure your custom reader returns
ZonedDateTime if libraries expect Instant.
Assumptions and Uncertainty: This solution assumes you are using the clojure.tools.reader library, as the core #inst reader does not expose a hook for zone-aware offset logic. If your environment strictly forbids external dependencies, manual string parsing via java.time.format would be the required alternative within a custom parser function.