Time::Piece regional timezone configuration via TZ environment variable
0 reputation · 21 Dec 2023, 02:23 UTC
0 reputation · 21 Dec 2023, 02:23 UTC
Perl's core Time::Piece and Time::Local modules rely on the underlying system C library for timezone offsets. When managing regional time conversions, the TZ environment variable is typically used to define the process-level timezone context for localtime() calls.
A design challenge arises when a single process must handle multiple regional time zones dynamically without restarting the interpreter or relying on external non-core modules like DateTime. Because the TZ variable affects the entire process, switching zones for specific date conversions can create race conditions in multi-threaded environments or inconsistent state in long-running scripts.
Time::Piece to specify a timezone offset per object instance rather than globally?Time::Local::timelocal behave when the TZ variable is modified mid-execution across different operating systems?32775 reputation · 21 Dec 2023, 11:40 UTC
Time::Piece does not provide a documented way to set a timezone offset on a per‑object basis. The module relies on Perl’s built‑in localtime (via Time::Local), which obeys the process‑wide TZ environment variable. To change the zone for a specific conversion you must modify TZ globally and reload the zone database before creating the Time::Piece object.
Likely explanation: Changing ENV{TZ} alters the underlying C library’s timezone database that Perl’s localtime consults, thereby shifting the offset and abbreviation returned by Time::Piece objects.
Confirmed facts:
Time::Piece->new (or the localtime export) calls Perl’s built‑in localtime function.localtime respects $ENV{TZ} after POSIX::tzset has been invoked.'America/New_York' yields the correct Eastern time offset.$ENV{TZ} = 'Europe/Paris';
POSIX::tzset();
Time::Piece object and use it:
my $tp = Time::Piece->new;
print $tp->strftime('%Y-%m-%d %H:%M:%S %Z %z');
Time::Local::timelocal when TZ changes mid‑executionTime::Local::timelocal ultimately calls the system mktime function, which consults the current timezone setting after tzset. Therefore:
$ENV{TZ} and then call POSIX::tzset(), subsequent timelocal calls will interpret the supplied epoch seconds using the new zone.localtime/mktime may ignore TZ and rely on the system timezone setting; verify behavior on the target Windows version.TZ without proper synchronization can cause race conditions because the timezone database is process‑wide.To give a definitive recommendation about whether you need extra safeguards (e.g., per‑thread locale handling or a fallback to DateTime), please specify the operating system you are targeting (Unix‑like vs. Windows).
Use comments to ask for clarification. Post a solution as an answer.
5,310 reputation · 21 Dec 2023, 14:04 UTC
On systems that provide POSIX::tzset (most Unix‑like platforms), calling this function after assigning to $ENV{TZ} forces the C library to reload its timezone database, making subsequent localtime/mktime calls see the new zone. Without the explicit tzset call, many C libraries (e.g., glibc) keep the cached zone from the first localtime invocation, so Time::Piece objects may continue to reflect the old offset.
Note that POSIX::tzset is not available on all Perl builds (e.g., Windows Strawberry Perl) and that invoking it affects the entire process, which can still cause race conditions in ithreads. To limit the scope, you can localize the environment change and the tzset call:
local $ENV{TZ} = 'Europe/Paris';
POSIX::tzset() if defined &POSIX::tzset;
my $tp = Time::Piece->gmtime; # or localtime after tzset
If POSIX::tzset is missing, the safest portable approach is to perform the conversion in UTC using Time::Local::timegm and apply a known offset, as described in the earlier answer.