Skip to content

Default TZOFFSET to the offset libc370 already resolved - #150

Merged
mgrossmann merged 1 commit into
mainfrom
issue-145-tzoffset-default
Aug 7, 2026
Merged

Default TZOFFSET to the offset libc370 already resolved#150
mgrossmann merged 1 commit into
mainfrom
issue-145-tzoffset-default

Conversation

@mgrossmann

Copy link
Copy Markdown
Contributor

Part (a) of #145. One line plus a comment; part (b) stays open as a policy decision.

The problem

httpd carried a second, disagreeing notion of the timezone:

value on the reference system set by consumed by
httpd->tzoffset 0 nothing, unless the Parmlib has TZOFFSET Date: header, SMF timestamps, DISPLAY TIME
crt->crttzoff −5 h @@crtset.c per task from CVTTZ; refreshed by the tzset() both __start paths already call localtime() / ctime64() → modules, worker threads, dbgtime.c

Two unrelated defaults for the same question, in one address space. With SYS2.PARMLIB(HTTPPRM0) setting nothing:

0000 11.39.04 TSU   39  F HTTPD,DISPLAY TIME
FFFF 11.39.04 STC  609  HTTPD142I time 2026/08/07 16:39:04 GMT
FFFF 11.39.04 STC  609  HTTPD143I time 2026/08/07 16:39:04 Local TZOFFSET=+0

The server calls 16:39 its local time; MVS stamps the same event 11:39, and so does every module (/jes/status renders start_display five hours behind start_stamp).

The 0 was never a chosen default. The HTTPD block is static storage, set_defaults() did not touch the field, and parse_tzoffset() — its only writer — runs only for an actual TZOFFSET statement. That is also what misled the first analysis on #145: reading an untouched field as "configured for GMT".

The change

httpd->tzoffset = __tzget();

__tzget() returns crt->crttzoff as tzset() left it: the TZ environment variable when the STC allocates a SYSENV/ENVIRON DD that sets one, the system's CVTTZ otherwise. Both halves then agree with each other and with MVS.

An explicit TZOFFSET still wins — http_config() calls set_defaults() before it parses the member, and parse_tzoffset() overwrites the value.

__tzget() is declared locally, like sleep() and __tzset() above it: libc370 ships it (src/clib/@@tzget.c) but declares it in no header (mvslovers/libc370#70).

Documentation

docs/configuration.md gave the default as +00:00, which documented the bug rather than an intention. It now says the offset comes from the system unless the Parmlib overrides it, and names where the system value comes from.

Verification

make clean under -Wall -Werror, 6 modules link, make test-host 63 assertions pass.

No test covers this. The value comes from the calling task's CRT, so there is nothing a host test can exercise and nothing an MVS test can assert without a known CVTTZ. It has to be checked on the live system:

  • F HTTPD,DISPLAY TIMEHTTPD142I (GMT) and HTTPD143I (Local) should now differ by the system offset instead of being equal, and TZOFFSET= should report it.
  • /jes/status?jobname=…start_display should match start_stamp interpreted at that same offset.
  • Setting TZOFFSET in the Parmlib should still override both.

I would rather that ran before this merges, since the whole change is about a value only the live system produces.

What stays open

Part (b) of #145: an explicit TZOFFSET still reaches only the task that parsed the Parmlib, because parse_tzoffset()__tzset() writes that task's CRT. Worker threads and modules keep theirs. The libc370-sanctioned way to reach all of them already exists — TZ in the SYSENV/ENVIRON DD, honoured by tzset() in both httpstrt.c and cgistart.c — which raises the question whether the Parmlib keyword should do anything beyond feeding httpd's own formatting. That is a decision, not a defect, and it is not in this PR.

httpd carried a second, disagreeing notion of the timezone. httpd->tzoffset
fed the Date: header, the SMF timestamps and DISPLAY TIME, while everything
that went through localtime()/ctime64() -- modules, worker threads,
dbgtime.c -- used crt->crttzoff. The two had unrelated defaults: 0 for the
former, the system's CVTTZ for the latter, filled in per task by @@crtset.c
and refreshed by the tzset() that both __start paths already call.

On the reference system CVTTZ is -5h, so with an empty Parmlib the server
reported its own local time as equal to GMT while the same event was stamped
five hours earlier by MVS and by every module. Nothing was configured; the 0
was not a chosen default but an untouched field in static storage, which is
also why it read as "GMT" in the HTTPD block and misled the first analysis of
issue #145.

set_defaults() now takes __tzget(), i.e. crt->crttzoff as tzset() left it --
the TZ environment variable when a SYSENV/ENVIRON DD sets one, the system's
CVTTZ otherwise. Both halves then agree with each other and with MVS. A
TZOFFSET statement still wins, because http_config() runs set_defaults()
before it parses the member.

Documentation said the default was +00:00, which described the bug rather
than an intent; it now says the offset comes from the system unless the
Parmlib overrides it.

Not covered by a test: the value comes from the task's CRT, so there is
nothing a host test can exercise. Verify with F HTTPD,DISPLAY TIME -- GMT and
Local should differ by the system offset instead of being equal -- and by
comparing start_stamp against start_display in /jes/status.

This is part (a) of #145. Part (b) -- that an explicit TZOFFSET still reaches
only the task that parsed the Parmlib, while TZ in the SYSENV DD reaches all
of them -- is a policy decision and stays open.

Refs #145
@mgrossmann

Copy link
Copy Markdown
Contributor Author

Verified on MVS — and this PR does more than its description claims

F HTTPD,DISPLAY TIME, three states:

GMT Local TZOFFSET= system stamp (MTT)
before 16:39:04 16:39:04 +0 11.39.04 ✗
after, empty Parmlib 17:05:35 12:05:35 −300 12.05.35 ✓
after, TZOFFSET +02:00 17:09:14 19:09:14 +120 ✓ override intact

The default now matches the system, and an explicit statement still wins.

The part I had not predicted: this also fixes the JES2 epoch timestamps

Reading /jes/status turned up something the PR description gets wrong by omission. start_stamp is not an absolute value — it is computed at read time as

epoch = stored-local-time − httpd->tzoffset

JES2 stores system local time, so the epoch is only correct when tzoffset equals the system's real offset. Two independent observations:

  1. STC00609's start_stamp changed by exactly 7200 s between two reads of a job that had long finished — the only thing that changed in between was TZOFFSET +02:00 reaching the Parmlib. The conversion is retroactive.
  2. The arithmetic predicts the reading exactly. STC00614 started 12:08:57 system-local (DISPLAY TIME, MTT). Control block +24 reads 0x00001C20 = 7200. /jes/status reports start_stamp = 10:08:57 UTC. 12:08:57 − 7200 = 10:08:57 ✓.

So, for a job started at 12:08:57 local on a system running at UTC−5 (true UTC 17:08:57):

tzoffset start_stamp reports
0 — before this PR 12:08:57 UTC 5 h early
−18000 — after this PR, empty Parmlib 17:08:57 UTC correct
+7200TZOFFSET +02:00 10:08:57 UTC 7 h early

Before this change the JES2 epoch timestamps were systematically wrong by the system offset, on every system whose CVTTZ is not zero. That is a correctness fix, not just an alignment of httpd's own formatting, and the PR title and body both undersell it.

Every input above is measured. The one step not read directly is /jes/status while tzoffset is −18000 — it is arithmetic from verified values rather than a fresh observation, and the model predicted the +7200 reading to the second.

It also reshapes part (b) of #145

I asked there whether TZOFFSET should be "formatting only". The answer is no. It is the declaration of what the TOD clock means, and setting it to anything other than the system's real offset corrupts epoch values, not just display strings — +02:00 moved every JES2 timestamp by seven hours. Detail on #145.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant