Skip to content

Return JES2 job timestamps as ISO 8601 UTC - #151

Merged
mgrossmann merged 3 commits into
mainfrom
issue-146-iso8601-jes-times
Aug 7, 2026
Merged

Return JES2 job timestamps as ISO 8601 UTC#151
mgrossmann merged 3 commits into
mainfrom
issue-146-iso8601-jes-times

Conversation

@mgrossmann

Copy link
Copy Markdown
Contributor

Follows #150. The JES API carried two timestamps per job from different sources, and they disagreed.

What was wrong

start_stamp is the epoch httpjes2.c computes explicitly, and the intent was already right:

tzadjust   = httpd->tzoffset * -1;   /* change sign as we want to convert local time to GMT */
start_time = j->start_time + tzadjust;

Correct now that tzoffset defaults to the system offset (#150). start_display then ran that same instant through ctime64(), which converts through crt->crttzoff — the task's timezone, taken from CVTTZ — shifting it a second time by an unrelated offset.

For a job that started at 17:25:18 UTC on the reference system:

reported
start_stamp 17:25:18 UTC correct after #150
start_display Fri Aug 7 05:25:18 2026 UTC − 5 h

Neither UTC, nor the system's local time (12:25:18), nor the caller's (19:25:18 for CET summer). Two timezone sources in adjacent lines of the same JSON object.

Why UTC and not local

A local time is the wrong thing for an API to return at all. The server cannot know the caller's timezone, so any local rendering is unlabelled — the client cannot tell which zone it received. Return an unambiguous instant and let the client localize; that is what zowe and every browser already do.

It is also the convention mvsMF settled on elsewhere. mvsmf/src/ussapi.c formats mtime with mgmtime64() and a literal Z, and both docs/endpoints/uss/list.md and docs/endpoints/console/hardcopy-log.md specify ISO 8601 UTC (2026-06-30T02:00:00Z). Those timestamps never had this class of bug because they never touch localtime — using gmtime64 removes the dependency on per-task timezone state instead of trying to keep two copies of it in step.

The change

New jestime() (src/jestime.c, include/jestime.h) renders a time64_t through gmtime64_r() as 2026-08-07T17:25:18Z. Both display fields in httpjes2.c and both in jesst.c now call it, replacing four open-coded ctime64() calls that had already drifted into two spellings.

A zero timestamp (a job that has not started or ended) still yields "...", exactly as before, so clients that special-case it are unaffected.

start_stamp is untouched — it is the machine-readable epoch and it is correct.

Verification

make clean under -Wall -Werror, 6 modules link, make test-host 63 assertions pass. Deployed to IBMUSER.HTTPD.V4R0M0D.LINKLIB.

Not yet verified live — the STC still runs the previous build. After it is re-bestückt, /jes/status should show start_display equal to start_stamp interpreted as UTC:

curl -s -u IBMUSER:sys1 "http://mvsdev.lan:8080/jes/status?jobname=HTTPD" \
  | grep -E 'start_stamp|start_display'

For that to be the true time as well, TZOFFSET must be out of the Parmlib (or set to the system's actual offset) — with TZOFFSET +02:00 on a UTC−5 system the epoch itself is still 7 h early, which is #145 part (b), not this PR.

Noticed, not fixed

/jes/status is served by httpjes2.c, not jesst.c — the node and jesinfo fields in the response only exist in the former. Both define main(), so only httpjes2.o can be in the HTTPJES2 module and jesst.o is never autocalled: jesst.c looks like dead code. It is changed here anyway so the two cannot drift again, but it is a candidate for deletion along with the rest of the deprecated JES CGI.

The ctime64() calls in httpcons.c and httpdsrv.c are left alone: those are operator- and dump-facing displays of internal times, where local time is arguably what is wanted. Only the API contract is changed here.

The JSON carried two timestamps per job that came from different sources and
disagreed. start_stamp is the epoch httpjes2.c computes explicitly:

    tzadjust = httpd->tzoffset * -1;  /* convert local time to GMT */
    start_time = j->start_time + tzadjust;

which is correct now that tzoffset defaults to the system offset (#150).
start_display then ran that same instant through ctime64(), which converts
through crt->crttzoff -- the *task's* timezone, taken from CVTTZ -- shifting
it a second time by an unrelated offset. On the reference system a job that
started at 17:25:18 UTC was reported as "Fri Aug  7 05:25:18 2026": neither
UTC, nor the system's local time, nor the caller's.

Beyond the double shift, a local time is the wrong thing for an API to
return. The server cannot know the caller's timezone, so any local rendering
is unlabelled and the client cannot tell which zone it got. Returning an
unambiguous instant and letting the client localize is what zowe and every
browser already do.

That is also the convention mvsMF settled on elsewhere: ussapi.c formats
mtime with mgmtime64() and a literal "Z", and docs/endpoints/uss/list.md and
console/hardcopy-log.md both specify ISO 8601 UTC. Those timestamps never had
this class of bug because they never touch localtime.

So both display fields now render through gmtime64_r() as
"2026-08-07T17:25:18Z", via one shared jestime() rather than two open-coded
ctime64() calls that had already drifted apart. A zero timestamp still yields
"...", as before.

start_stamp is unchanged -- it is the machine-readable epoch and is correct.

Note for the reader: /jes/status is served by httpjes2.c, not jesst.c. Both
define main(), so only httpjes2.o can be in the HTTPJES2 module and jesst.o is
never autocalled; jesst.c looks like dead code. It is changed here anyway so
the two do not drift again if it is ever wired up, but it is a candidate for
deletion alongside the rest of the deprecated JES CGI.

Refs #145
Real z/OSMF emits the exec-* fields as

    "exec-started":"2018-11-03T09:05:18.010Z"

so jestime() now renders ".000Z" rather than "Z". JES2 gives second
resolution here (start_time64 is a time64_t), so the fraction is always zero
-- which is what z/OSMF itself reports for exec-submitted anyway.

httpjes2 is the reference mvsMF's own implementation gets compared against,
so the two formats should be identical rather than merely similar.
The "Z" on these fields is a promise, and TZOFFSET could break it. The epoch
was computed as

    tzadjust = httpd->tzoffset * -1;

so an operator who set TZOFFSET to their own zone rather than the machine's
skewed every timestamp the API reports. On the reference system, TZOFFSET
+02:00 against a CVTTZ of -5h put a job that ran at 17:25:18 UTC at
10:25:18 UTC -- seven hours out, and labelled Z.

Converting JES2's stored local timestamps to UTC needs the offset the machine
actually runs at, which is a fact rather than a setting. __tzget() returns
crt->crttzoff for the calling task, which cgistart's tzset() filled in from TZ
or the system's CVTTZ. That is immune to what the Parmlib says, which is the
point: the field is UTC by contract.

httpd->tzoffset keeps its job -- httpd's own local-time rendering, DISPLAY
TIME and the SMF records. The Date: header was never affected; it goes
through gmtime64() on a time64() value, and measuring it against a UTC clock
gives the same second.

Leaves jesst.c alone. It applies no offset at all, so it is a third behaviour
again -- but it defines its own main(), is therefore never autocalled into
HTTPJES2, and looks like dead code. Deleting it is the right fix, not
adjusting it.

Refs #145
@mgrossmann
mgrossmann merged commit 60e2053 into main Aug 7, 2026
1 check passed
@mgrossmann
mgrossmann deleted the issue-146-iso8601-jes-times branch August 7, 2026 18:55
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