Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions include/jestime.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* JESTIME.H
** Render a JES2 job timestamp for a JSON response.
**
** Shared by jesst.c (/jes/status, /jes/ddlist) and httpjes2.c, which emitted
** the same field two ways and both wrong -- see src/jestime.c for why.
*/
#ifndef JESTIME_H
#define JESTIME_H

#include <time64.h>

/* Longest output is "2026-08-07T17:25:18.000Z" plus NUL. */
#define JESTIME_LEN 32

/* Format *t as an ISO 8601 instant in UTC ("2026-08-07T17:25:18.000Z") into
** out -- the shape real z/OSMF uses for exec-submitted / exec-started /
** exec-ended, so this API and mvsMF's stay directly comparable.
** A zero timestamp (a job that has not started or ended) yields "...", which
** is what the JSON carried before and what clients already tolerate.
** out must hold at least JESTIME_LEN bytes. */
void jestime(const time64_t *t, char *out, unsigned outlen);

#endif /* JESTIME_H */
36 changes: 23 additions & 13 deletions src/httpjes2.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
/* HTTPJES2.C - CGI Program, REST style CGI program for access of JES2 resources */
#include "httpd.h"
#include "clibjes2.h" /* JES prototypes */
#include "jestime.h" /* ISO 8601 UTC job timestamps */
#include "clibcp.h" /* JES checkpoint struct */

/* libc370 ships __tzget() (src/clib/@@tzget.c) but declares it in no header
-- see libc370 #70. Returns crt->crttzoff for the calling task. */
extern int __tzget(void);

#define httpx (httpd->httpx)

Expand Down Expand Up @@ -130,11 +135,24 @@ do_status(HTTPD *httpd, HTTPC *httpc, const char *jobname, const char *jobid, in
char jesinfo[20] = "unknown";
time64_t start_time;
time64_t end_time;
char tbuf[JESTIME_LEN];
const char *smfid;

httpsecs(&start);

tzadjust = httpd->tzoffset * -1; /* change sign as we want to convert local time to GMT */
/* JES2 stores its checkpoint timestamps in system local time, so they need
** the system's offset to become UTC -- and specifically the SYSTEM's, not
** httpd->tzoffset. That field is a configured preference for httpd's own
** local-time rendering (Date: is unaffected, it goes through gmtime64), and
** an operator who sets TZOFFSET to their own zone rather than the machine's
** would otherwise skew every timestamp this API reports. These fields are
** labelled "Z", so they have to be UTC whatever the Parmlib says.
**
** __tzget() returns crt->crttzoff for this task, which cgistart's tzset()
** filled in from TZ or the system's CVTTZ -- a fact about the machine, not a
** setting. Sign: crttzoff is seconds east of UTC (negative west), and
** UTC = local - offset, hence the * -1 to give an addend. */
tzadjust = __tzget() * -1;

/* select filtering criteria */
if (jobname && !jobid) {
Expand Down Expand Up @@ -317,21 +335,13 @@ do_status(HTTPD *httpd, HTTPC *httpc, const char *jobname, const char *jobid, in
#else
rc = http_printf(httpc, " \"start_stamp\": \"%llu\",\n", start_time);
if (rc < 0) goto quit;
if (__64_cmp_u32(&start_time, 0) != __64_EQUAL) {
rc = http_printf(httpc, " \"start_display\": \"%-24.24s\",\n", ctime64(&start_time) );
}
else {
rc = http_printf(httpc, " \"start_display\": \"...\",\n");
}
jestime(&start_time, tbuf, sizeof(tbuf));
rc = http_printf(httpc, " \"start_display\": \"%s\",\n", tbuf);
if (rc < 0) goto quit;
rc = http_printf(httpc, " \"end_stamp\": \"%llu\",\n", end_time);
if (rc < 0) goto quit;
if (__64_cmp_u32(&end_time, 0) != __64_EQUAL) {
rc = http_printf(httpc, " \"end_display\": \"%-24.24s\",\n", ctime64(&end_time) );
}
else {
rc = http_printf(httpc, " \"end_display\": \"...\",\n" );
}
jestime(&end_time, tbuf, sizeof(tbuf));
rc = http_printf(httpc, " \"end_display\": \"%s\",\n", tbuf);
if (rc < 0) goto quit;
#endif
rc = do_status_ddlist(httpd, httpc, j, " ");
Expand Down
18 changes: 6 additions & 12 deletions src/jesst.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* JESST.C - CGI Program, display JOE from JES checkpoint */
#include "httpd.h"
#include "clibjes2.h" /* JES prototypes */
#include "jestime.h" /* ISO 8601 UTC job timestamps */

#define httpx (httpd->httpx)

Expand All @@ -21,6 +22,7 @@ int main(int argc, char **argv)
double start = 0.0;
double end = 0.0;
unsigned n;
char tbuf[JESTIME_LEN];
#if 0
struct {
unsigned short len;
Expand Down Expand Up @@ -123,19 +125,11 @@ int main(int argc, char **argv)
}
#else
printf(" \"start_stamp\": \"%llu\",\n", j->start_time64.u64 );
if (__64_cmp_u32(&j->start_time64, 0) != __64_EQUAL) {
printf(" \"start_display\": \"%-24.24s\",\n", ctime64(&j->start_time64) );
}
else {
printf(" \"start_display\": \"...\",\n");
}
jestime(&j->start_time64, tbuf, sizeof(tbuf));
printf(" \"start_display\": \"%s\",\n", tbuf);
printf(" \"end_stamp\": \"%llu\",\n", j->end_time64.u64);
if (__64_cmp_u32(&j->end_time64, 0) != __64_EQUAL) {
printf(" \"end_display\": \"%-24.24s\",\n", ctime64(&j->end_time64) );
}
else {
printf(" \"end_display\": \"...\",\n" );
}
jestime(&j->end_time64, tbuf, sizeof(tbuf));
printf(" \"end_display\": \"%s\",\n", tbuf);
#endif
print_dd(httpd, j, " ");
printf(" }%s\n", (n+1) < count ? ",":"");
Expand Down
64 changes: 64 additions & 0 deletions src/jestime.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/* JESTIME.C
** Render a JES2 job timestamp for a JSON response, as an ISO 8601 instant in
** UTC.
**
** The field used to be produced by ctime64(), which converts through
** crt->crttzoff -- the *task's* timezone, filled in from the system's CVTTZ
** when nothing else set it. Two things were wrong with that.
**
** It applied a second, unrelated offset. The epoch value beside it comes from
** the JES2 conversion, which uses httpd->tzoffset; running the same instant
** through ctime64() then shifted it again by the CRT's offset. On the
** reference system (CVTTZ = -5h) 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. See issue #145 for how the two offsets came to
** disagree.
**
** And 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 and
** unusable -- the caller cannot tell which zone it is in. Returning an
** unambiguous instant and letting the client localize is what zowe and every
** browser already do, and it is the convention mvsMF settled on elsewhere:
** ussapi.c formats mtime with mgmtime64() and a literal "Z" (see
** mvsmf docs/endpoints/uss/list.md). Those timestamps never had this class of
** bug precisely because they never touch localtime.
**
** So: gmtime64_r(), never ctime64() or localtime64(). That removes the
** dependency on per-task timezone state rather than trying to keep two copies
** of it in step.
*/
#include "jestime.h"
#include "clib64.h"
#include <stdio.h>

void
jestime(const time64_t *t, char *out, unsigned outlen)
{
struct tm tm;

if (!out || outlen == 0) return;
out[0] = 0;
if (!t) return;

/* a job that has not started (or has not ended) carries a zero time */
if (__64_cmp_u32((time64_t *)t, 0) == __64_EQUAL) {
snprintf(out, outlen, "...");
return;
}

if (!gmtime64_r(t, &tm)) {
snprintf(out, outlen, "...");
return;
}

/* ".000" rather than no fraction: this is the shape real z/OSMF emits for
** exec-submitted / exec-started / exec-ended,
** "exec-started":"2018-11-03T09:05:18.010Z"
** and httpjes2 is the reference mvsMF's own implementation is compared
** against, so the two should be directly comparable. JES2 gives us 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. */
snprintf(out, outlen, "%04d-%02d-%02dT%02d:%02d:%02d.000Z",
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec);
}