, 5 min read
Converting UNIX Timestamps to Year, Month, Day in COBOL
Original post is here eklausmeier.goip.de/blog/2024/05-03-converting-unix-timestamps-to-year-month-day-in-cobol.
1. Task at hand. COBOL programs reads UNIX timestamps as input. Output should be the values of year, month, day, hour, minutes, seconds.
In C this is just gmtime()
.
gmtime
accepts time_t
and produces struct tm
:
struct tm *gmtime(const time_t *timep);
On mainframe, however, it is sometimes a little inconvienent to call a C routine from COBOL. It is easier to just code the short algorithm in COBOL.
2. Approach. P.J. Plauger's book "The Standard C Library" contains the source code for gmtime()
and localtime()
.
This code is then translated to COBOL.
The C code is as below.
/* Convert UNIX timestamp to triple (year,month,day)
Elmar Klausmeier, 01-Apr-2024
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <limits.h>
// From P.J.Plauger: The Standard C Library, Prentice Hall, 1992
static const int daytab[2][12] = {
{ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 }, // leap year
{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }
};
int daysTo (int year, int mon) { // compute extra days to start of month
int days;
if (year > 0) days = (year - 1) / 4; // correct for leap year: 1801-2099
else if (year <= -4) days = 1 + (4 - year) / 4;
else days = 0;
return days + daytab[year&03 || (year==0)][mon];
}
struct tm *timeTotm (struct tm *t, time_t secsarg, int isdst) { // convert scalar time to time struct
int year, mon;
const int *pm;
long i, days;
time_t secs;
static struct tm ts;
secsarg += ((70 * 365LU) + 17) * 86400; // 70 years including 17 leap days since 1900
if (t == NULL) t = &ts;
t->tm_isdst = isdst;
for (secs=secsarg; ; secs=secsarg+3600) { // loop to correct for DST (not used here)
days = secs / 86400;
t->tm_wday = (days + 1) % 7;
for (year = days / 365; days < (i=daysTo(year,0)+365L*year); --year)
; // correct guess and recheck
days -= i;
t->tm_year = year;
t->tm_yday = days;
pm = daytab[year&03 || (year==0)];
for (mon=12; days<pm[--mon]; )
;
t->tm_mon = mon;
t->tm_mday = days - pm[mon] + 1;
secs %= 86400;
t->tm_hour = secs / 3600;
secs %= 3600;
t->tm_min = secs / 60;
t->tm_sec = secs % 60;
//if (t->tm_isdst >= 0 || (t->tm_isdst = IsDST(t)) <= 0) return t;
return t;
}
}
int main (int argc, char *argv[]) {
struct tm t;
long secs;
if (argc <= 1) return 0;
secs = atol(argv[1]);
timeTotm(&t, secs, 0);
printf("timeTotm(): year=%d, mon=%d, day=%d, hr=%d, min=%d, sec=%d\n",
1900+t.tm_year, 1+t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
return 0;
}
3. COBOL solution. Below is the COBOL code which was translated from above C code.
Fun fact: GNU Cobol crashed on some intermediate result, see cobc crashes on illegal COBOL source code file. This bug was fixed within a few hours by Simon Sobisch!
Below source code is compiled without problems.
000010 IDENTIFICATION DIVISION.
000020 PROGRAM-ID. Timestamp2date.
000030 AUTHOR. Elmar Klausmeier.
000040 DATE-WRITTEN. 02-May-2024.
000050
000060 DATA DIVISION.
000070 WORKING-STORAGE SECTION.
000080*
000090 01 year PIC S9(18) comp-5.
000100 01 mon PIC S9(18) comp-5.
000110 01 days PIC S9(18) comp-5.
000120*
000130* Local helper variables
000140 01 i PIC S9(18) comp-5.
000150 01 idays PIC S9(18) comp-5.
000160 01 daysTo PIC S9(18) comp-5.
000170 01 yearMod4 PIC S9(9) comp-5.
000180 01 leapIx PIC S9(9) comp-5.
000190 01 daysP1 PIC S9(18) comp-5.
000200*
000210 01 secs PIC S9(18) comp-5.
000220 01 secsarg PIC S9(18) comp-5.
000230*
000240*
000250* struct tm:
000260* int tm_sec; // Seconds [0, 60]
000270* int tm_min; // Minutes [0, 59]
000280* int tm_hour; // Hour [0, 23]
000290* int tm_mday; // Day of the month [1, 31]
000300* int tm_mon; // Month [0, 11] (January = 0)
000310* int tm_year; // Year minus 1900
000320* int tm_wday; // Day of the week [0, 6] (Sunday = 0)
000330* int tm_yday; // Day of the year [0, 365] (Jan/01 = 0)
000340* int tm_isdst; // Daylight savings flag
000350 01 tm_sec PIC S9(9).
000360 01 tm_min PIC S9(9).
000370 01 tm_hour PIC S9(9).
000380 01 tm_mday PIC S9(9).
000390* range: 1-12
000400 01 tm_mon PIC S9(9).
000410 01 tm_year PIC S9(9).
000420 01 tm_wday PIC S9(9).
000430 01 tm_yday PIC S9(9).
000440*
000450*
000460 01 daytabInit.
000470* Number of days for leap year
000480 05 daytab-1-1 pic s9(9) comp-5 value 0.
000490 05 daytab-1-2 pic s9(9) comp-5 value 31.
000500 05 daytab-1-3 pic s9(9) comp-5 value 60.
000510 05 daytab-1-4 pic s9(9) comp-5 value 91.
000520 05 daytab-1-5 pic s9(9) comp-5 value 121.
000530 05 daytab-1-6 pic s9(9) comp-5 value 152.
000540 05 daytab-1-7 pic s9(9) comp-5 value 182.
000550 05 daytab-1-8 pic s9(9) comp-5 value 213.
000560 05 daytab-1-9 pic s9(9) comp-5 value 244.
000570 05 daytab-1-10 pic s9(9) comp-5 value 274.
000580 05 daytab-1-11 pic s9(9) comp-5 value 305.
000590 05 daytab-1-12 pic s9(9) comp-5 value 335.
000600* Number of days for non-leap year
000610 05 daytab-2-1 pic s9(9) comp-5 value 0.
000620 05 daytab-2-2 pic s9(9) comp-5 value 31.
000630 05 daytab-2-3 pic s9(9) comp-5 value 59.
000640 05 daytab-2-4 pic s9(9) comp-5 value 90.
000650 05 daytab-2-5 pic s9(9) comp-5 value 120.
000660 05 daytab-2-6 pic s9(9) comp-5 value 151.
000670 05 daytab-2-7 pic s9(9) comp-5 value 181.
000680 05 daytab-2-8 pic s9(9) comp-5 value 212.
000690 05 daytab-2-9 pic s9(9) comp-5 value 243.
000700 05 daytab-2-10 pic s9(9) comp-5 value 273.
000710 05 daytab-2-11 pic s9(9) comp-5 value 304.
000720 05 daytab-2-12 pic s9(9) comp-5 value 334.
000730 01 daytabArr redefines daytabInit.
000740 05 filler occurs 2 times.
000750 10 filler occurs 12 times.
000760 15 daytab pic s9(9) comp-5.
000770*
000780*
000790
000800 PROCEDURE DIVISION.
000810******************************************************************
000820* A100-main
000830******************************************************************
000840* Function:
000850*
000860******************************************************************
000870 A100-main SECTION.
000880 A100-main-P.
000890
000900* initialize daytabArr.
000910* move daytabInit to daytabArr
000920* perform varying leapIx from 1 by 1 until leapIx > 2
000930* perform varying mon from 1 by 1 until mon > 12
000940* display 'daytab(' leapIx ',' mon ') = '
000950* daytab(leapIx, mon)
000960* end-perform
000970* end-perform.
000980
000990 ACCEPT secsarg FROM ARGUMENT-VALUE
001000 perform v910-timeToTm
001010 display ' tm_sec = ' tm_sec
001020 display ' tm_min = ' tm_min
001030 display ' tm_hour = ' tm_hour
001040 display ' tm_mday = ' tm_mday
001050 display ' tm_mon = ' tm_mon
001060 display ' tm_year = ' tm_year
001070 display ' tm_wday = ' tm_wday
001080 display ' tm_yday = ' tm_yday
001090
001100 STOP RUN.
001110
001120
001130* Convert UNIX timestamp to triple (year,month,day)
001140* Converted from C program
001150* From P.J.Plauger: The Standard C Library, Prentice Hall, 1992
001160
001170******************************************************************
001180* V900-daysTo
001190******************************************************************
001200* Function: compute daysTo given year and mon
001210* compute extra days to start of month
001220******************************************************************
001230 V900-daysTo SECTION.
001240 V900-daysTo-P.
001250
001260* correct for leap year: 1801-2099
001270 evaluate true
001280 when year > 0
001290 compute idays = (year - 1) / 4
001300 when year <= -4
001310 compute idays = 1 + (4 - year) / 4
001320 when other
001330 move zero to idays
001340 end-evaluate
001350
001360 compute yearMod4 = function mod(year,4)
001370 if yearMod4 not= zero or year = zero then
001380 move 2 to leapIx
001390 else
001400 move 1 to leapIx
001410 end-if
001420 compute daysTo = idays + daytab(leapIx, mon)
001430
001440 CONTINUE.
001450 END-V900-daysTo.
001460 EXIT.
001470
001480
001490******************************************************************
001500* V910-timeToTm
001510******************************************************************
001520* Function: compute tmT from secsarg (seconds since 01-Jan-1970)
001530* convert scalar time to time struct
001540******************************************************************
001550 V910-timeToTm SECTION.
001560 V910-timeToTm-P.
001570
001580* 70 years including 17 leap days since 1900
001590 compute secsarg = secsarg + ((70 * 365) + 17) * 86400;
001600 move secsarg to secs
001610
001620 compute days = secs / 86400
001630 add 1 to days giving daysP1
001640 compute tm_wday = function mod(daysP1, 7)
001650
001660 compute year = days / 365
001670 move 1 to mon
001680 perform until 1 = 0
001690 perform v900-daysTo
001700 compute i = daysTo + 365 * year
001710 if days >= i then
001720* exit perform
001730 go to v910-endloop
001740 end-if
001750* correct guess and recheck
001760 subtract 1 from year
001770 end-perform.
001780 v910-endloop.
001790
001800 subtract i from days
001810 move year to tm_year
001820 move days to tm_yday
001830
001840 compute yearMod4 = function mod(year,4)
001850 if yearMod4 not= zero or year = zero then
001860 move 2 to leapIx
001870 else
001880 move 1 to leapIx
001890 end-if
001900 move 12 to mon
001910 perform until days >= daytab(leapIx, mon)
001920 subtract 1 from mon
001930 end-perform
001940 move mon to tm_mon
001950 compute tm_mday = days - daytab(leapIx, mon) + 1
001960
001970 compute secs = function mod(secs,86400)
001980 compute tm_hour = secs / 3600;
001990 compute secs = function mod(secs,3600)
002000 compute tm_min = secs / 60;
002010 compute tm_sec = function mod(secs, 60)
002020
002030 CONTINUE.
002040 END-V910-timeToTm.
002050 EXIT.
002060
002070
4. Example output. Here are two examples. First example is Fri May 03 2024 14:16:01 GMT+0000. See https://www.unixtimestamp.com.
$ ./cobts2date 1714745761
tm_sec = +000000001
tm_min = +000000016
tm_hour = +000000014
tm_mday = +000000003
tm_mon = +000000005
tm_year = +000000124
tm_wday = +000000005
tm_yday = +000000123
Second example is Thu Dec 31 1964 22:59:59 GMT+0000.
$ ./cobts2date -157770001
tm_sec = +000000059
tm_min = +000000059
tm_hour = +000000022
tm_mday = +000000031
tm_mon = +000000012
tm_year = +000000064
tm_wday = +000000004
tm_yday = +000000365
For a list of leap years see Schaltjahr.