Dates and Times

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against ISO/IEC 1989:2023 (edition 3, January 2023; an edition 4 is upcoming, in DIS ballot), GnuCOBOL 3.2 (28 July 2023, the latest release) and IBM Enterprise COBOL for z/OS 6.5 (PTFs through September 2026), and was checked on 2026-10-20. Every portable example below was compiled with cobc -x -Wall (GnuCOBOL 3.2.0, fixed reference format) without a single warning (except the two examples that are deliberately wrong and say so) and run; the output and the diagnostics shown are real. The ISO standard is paywalled and was not read: the rules come from the Enterprise COBOL manuals and the GnuCOBOL manuals, and where the two differ the page says so.

A date in a COBOL program is not a type: it is a number or a string that the program interprets. The language gives three families of tools for working with that number, and the page follows them in turn:

  • the ACCEPT ... FROM DATE|DAY|DAY-OF-WEEK|TIME statement, which is among the oldest features of the language and reads the system clock into a data item;

  • FUNCTION CURRENT-DATE and the other intrinsic functions, which return four-digit years and the offset from UTC and can convert a calendar date into a number of days, add to it and convert it back;

  • the ISO 8601 functions (FORMATTED-DATE, FORMATTED-DATETIME, INTEGER-OF-FORMATTED-DATE and their relatives), which read and write the text forms that web services and JSON use.

The examples use the layouts of the Circulation library back office, in particular LOAN-DATE in LOANREC, and the page ends with the rule that computes when a book is due and how many days late it is. The general syntax of intrinsic functions, and the table of every function, are in Intrinsic Functions; the numeric items that hold the dates are described in PICTURE Clause and USAGE and Numeric Representation.

A word about reproducibility. Anything that reads the clock produces a different result every time it is run, so the examples below are of two kinds. Those that use fixed dates (a literal, a VALUE clause or a record from a file) print the same output on any day and any machine. The two that read the clock (accept-demo and live-clock, and the last lines of current-date) are labelled "varies": the output shown is what the run on 2026-10-20 printed, with a container whose time zone is UTC.

ACCEPT FROM DATE, DAY, DAY-OF-WEEK and TIME

Format 2 of the ACCEPT statement (Enterprise COBOL Language Reference, ACCEPT statement; GnuCOBOL Programmer’s Guide, ACCEPT FROM DATE/TIME) moves the date or the time kept by the system into a receiving item. Nothing is read from a device: the source is one of six conceptual data items that cannot be declared in the program and that behave as USAGE DISPLAY numbers, moved to the receiver by the rules of MOVE (without CORRESPONDING).

Phrase Implicit PICTURE Layout Example for 27 April 2003

DATE

9(6)

yymmdd

030427

DATE YYYYMMDD

9(8)

yyyymmdd

20030427

DAY

9(5)

yyddd (day of the year)

03117

DAY YYYYDDD

9(7)

yyyyddd

2003117

DAY-OF-WEEK

9

1 (Monday) to 7 (Sunday)

a Sunday is 7

TIME

9(8)

hhmmsscc (hundredths of a second)

2:41 PM is 14410000

The YYYYMMDD and YYYYDDD forms came with COBOL 2002; Enterprise COBOL accepts them and so does GnuCOBOL. The receiver does not have to be a numeric item: an alphanumeric item receives the digits as text, and an alphanumeric group item is allowed as the receiver. The TIME and DATE receivers are therefore often declared as groups whose subordinate items pick the fields apart, as the first example does by redefining the time.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. ACCEPT-DEMO.
      * ACCEPT ... FROM the clock: the values change on every run.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-DATE-6            PIC 9(6).
       01  WS-DATE-8            PIC 9(8).
       01  WS-DAY-5             PIC 9(5).
       01  WS-DAY-7             PIC 9(7).
       01  WS-WEEKDAY           PIC 9.
       01  WS-TIME              PIC 9(8).
       01  WS-TIME-PARTS REDEFINES WS-TIME.
           05  WS-HOUR          PIC 99.
           05  WS-MINUTE        PIC 99.
           05  WS-SECOND        PIC 99.
           05  WS-HUNDREDTH     PIC 99.
       PROCEDURE DIVISION.
       MAIN-PARAGRAPH.
           ACCEPT WS-DATE-6 FROM DATE
           ACCEPT WS-DATE-8 FROM DATE YYYYMMDD
           ACCEPT WS-DAY-5 FROM DAY
           ACCEPT WS-DAY-7 FROM DAY YYYYDDD
           ACCEPT WS-WEEKDAY FROM DAY-OF-WEEK
           ACCEPT WS-TIME FROM TIME
           DISPLAY "DATE          " WS-DATE-6
           DISPLAY "DATE YYYYMMDD " WS-DATE-8
           DISPLAY "DAY           " WS-DAY-5
           DISPLAY "DAY YYYYDDD   " WS-DAY-7
           DISPLAY "DAY-OF-WEEK   " WS-WEEKDAY
           DISPLAY "TIME          " WS-TIME
           DISPLAY "  hour " WS-HOUR " minute " WS-MINUTE
                   " second " WS-SECOND " hundredths " WS-HUNDREDTH
           STOP RUN.

The program prints the clock in every format it supports. This output varies with every run: it is the run of 2026-10-20 at 13:04:21 UTC, a Tuesday (DAY-OF-WEEK 2) and the 293rd day of 2026.

DATE          261020
DATE YYYYMMDD 20261020
DAY           26293
DAY YYYYDDD   2026293
DAY-OF-WEEK   2
TIME          13095666
  hour 13 minute 09 second 56 hundredths 66

The same program also compiled and ran under -std=ibm and printed the same kinds of values. Three habits make the statement safer:

  • Take the date once. ACCEPT FROM DATE followed by ACCEPT FROM TIME are two reads of the clock. A job that runs at 23:59:59 can get yesterday’s date and tomorrow’s time. Reading one FUNCTION CURRENT-DATE (next section) returns a consistent date and time.

  • Prefer the four-digit forms. ACCEPT FROM DATE and ACCEPT FROM DAY return two-digit years, so 990101 and 000101 do not sort in time order. The YYYYMMDD and YYYYDDD phrases, or CURRENT-DATE, return the century.

  • Remember that this is local time. DATE and TIME are the system’s local time, which with GnuCOBOL follows the TZ environment variable of the process and on z/OS follows the system’s time settings. A server in another time zone reports another day for the same instant.

FUNCTION CURRENT-DATE

FUNCTION CURRENT-DATE takes no arguments and returns a 21-character alphanumeric value, so a single MOVE fills a group that names the pieces. The layout is defined by the standard and is the same in Enterprise COBOL and GnuCOBOL:

Positions Length Contents

1-4

4

year in the Gregorian calendar

5-6

2

month, 01 to 12

7-8

2

day of the month, 01 to 31

9-10

2

hours past midnight, 00 to 23

11-12

2

minutes, 00 to 59

13-14

2

seconds, 00 to 59

15-16

2

hundredths of a second (00 where the system has no fractions)

17

1

- if local time is behind Greenwich mean time, + if the same or ahead, 0 if the offset is not available

18-19

2

hours of the offset (00 to 12 behind, 00 to 13 ahead)

20-21

2

additional minutes of the offset (00 to 59)

Enterprise COBOL’s manual works the example of 15 February 1995, 05:14:27.81 in Eastern Standard Time, where the value is 1995021505142781-0500. The following program redefines the 21 characters in a group, parses that literal (so that its first part prints the same every day) and then reads the live clock. The last lines vary.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. CURRENT-DATE-DEMO.
      * Layout of FUNCTION CURRENT-DATE (21 characters).
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-STAMP.
           05  WS-DATE.
               10  WS-YEAR      PIC 9(4).
               10  WS-MONTH     PIC 9(2).
               10  WS-DAY       PIC 9(2).
           05  WS-TIME.
               10  WS-HOUR      PIC 9(2).
               10  WS-MINUTE    PIC 9(2).
               10  WS-SECOND    PIC 9(2).
               10  WS-HUNDREDTH PIC 9(2).
           05  WS-OFFSET.
               10  WS-OFFSET-SIGN    PIC X.
               10  WS-OFFSET-HOURS   PIC 9(2).
               10  WS-OFFSET-MINUTES PIC 9(2).
       01  WS-RUN-DATE          PIC 9(8).
       01  WS-LENGTH            PIC 99.
       PROCEDURE DIVISION.
       MAIN-PARAGRAPH.
      *    A fixed 21-character value: 15 Feb 1995, 05:14:27.81, UTC-5.
           MOVE "1995021505142781-0500" TO WS-STAMP
           DISPLAY "year " WS-YEAR " month " WS-MONTH " day " WS-DAY
           DISPLAY "time " WS-HOUR ":" WS-MINUTE ":" WS-SECOND "."
                   WS-HUNDREDTH
           DISPLAY "offset " WS-OFFSET-SIGN WS-OFFSET-HOURS ":"
                   WS-OFFSET-MINUTES
      *    The live clock: this part changes on every run.
           MOVE FUNCTION CURRENT-DATE TO WS-STAMP
           DISPLAY "now  " WS-STAMP
           MOVE FUNCTION LENGTH(FUNCTION CURRENT-DATE) TO WS-LENGTH
           DISPLAY "length " WS-LENGTH
           MOVE FUNCTION CURRENT-DATE(1:8) TO WS-RUN-DATE
           DISPLAY "run date " WS-RUN-DATE
           STOP RUN.
year 1995 month 02 day 15
time 05:14:27.81
offset -05:00
now  2026102013095712+0000
length 21
run date 20261020

Two details are visible in the output. The live line ends in +0000 because the container runs in UTC; running the same binary with TZ=America/New_York printed -0400 at that moment (daylight saving time), so the offset really follows the time zone of the process. And the reference-modified form FUNCTION CURRENT-DATE(1:8) gives the yyyymmdd run date directly, which is how the nightly Circulation run obtains "today". The offset in positions 17 to 21 is the difference between local time and Greenwich mean time, which is why a value stored with it can be reduced to UTC later; the formatted functions below return the same information in ISO 8601 form.

Integer dates: arithmetic on calendar dates

Subtracting two yyyymmdd numbers does not give a number of days, because the digits are a calendar and not a count. The standard solves this with an integer date: the number of days since a fixed day zero, which can be added to and subtracted from, and converted back. Day 1 is Monday 1 January 1601 (in the Gregorian calendar, reckoned backwards), and the range is integer 1 (16010101) to 3,067,671 (99991231). The functions are:

Function Argument Result

INTEGER-OF-DATE

standard date yyyymmdd

integer date

DATE-OF-INTEGER

integer date

standard date yyyymmdd

INTEGER-OF-DAY

Julian date yyyyddd

integer date

DAY-OF-INTEGER

integer date

Julian date yyyyddd

TEST-DATE-YYYYMMDD

an integer

0 if valid, 1 bad year, 2 bad month, 3 bad day

TEST-DAY-YYYYDDD

an integer

0 if valid, 1 bad year, 2 bad day of the year

The next program does the usual things: converts a date to an integer, adds 14 days (the loan period used below), converts back, gets the Julian form, finds the weekday and counts days between two dates. It prints the same output on any day because every date is fixed.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. INTEGER-DATES.
      * Integer dates: day 1 is Monday 1 January 1601.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-DATE              PIC 9(8) VALUE 20261020.
       01  WS-JULIAN            PIC 9(7).
       01  WS-INT               PIC 9(7).
       01  WS-OUT               PIC 9(8).
       01  WS-OUT-JULIAN        PIC 9(7).
       01  WS-WEEKDAY           PIC 9.
       01  WS-DAYS              PIC S9(5).
       PROCEDURE DIVISION.
       MAIN-PARAGRAPH.
           COMPUTE WS-INT = FUNCTION INTEGER-OF-DATE(WS-DATE)
           DISPLAY "integer date of " WS-DATE " = " WS-INT
           COMPUTE WS-OUT = FUNCTION DATE-OF-INTEGER(WS-INT + 14)
           DISPLAY "14 days later          = " WS-OUT
           COMPUTE WS-OUT-JULIAN = FUNCTION DAY-OF-INTEGER(WS-INT)
           DISPLAY "Julian form            = " WS-OUT-JULIAN
           MOVE WS-OUT-JULIAN TO WS-JULIAN
           COMPUTE WS-INT = FUNCTION INTEGER-OF-DAY(WS-JULIAN)
           DISPLAY "back from Julian       = " WS-INT
           COMPUTE WS-WEEKDAY = FUNCTION MOD(WS-INT - 1, 7) + 1
           DISPLAY "day of week (1=Mon)    = " WS-WEEKDAY
           COMPUTE WS-DAYS = FUNCTION INTEGER-OF-DATE(20261231)
                           - FUNCTION INTEGER-OF-DATE(20261020)
           DISPLAY "days to 31 Dec 2026    = " WS-DAYS
           DISPLAY "day 1                  = "
                   FUNCTION DATE-OF-INTEGER(1)
           DISPLAY "day 3067671            = "
                   FUNCTION DATE-OF-INTEGER(3067671)
      *    29 February: only leap years have it.
           DISPLAY "2028-02-28 + 1         = "
                   FUNCTION DATE-OF-INTEGER(
                       FUNCTION INTEGER-OF-DATE(20280228) + 1)
           DISPLAY "2100-02-28 + 1         = "
                   FUNCTION DATE-OF-INTEGER(
                       FUNCTION INTEGER-OF-DATE(21000228) + 1)
           STOP RUN.
integer date of 20261020 = 0155521
14 days later          = 20261103
Julian form            = 2026293
back from Julian       = 0155521
day of week (1=Mon)    = 2
days to 31 Dec 2026    = +00072
day 1                  = 16010101
day 3067671            = 99991231
2028-02-28 + 1         = 20280229
2100-02-28 + 1         = 21000301

The output shows the leap-year rule at work: 2028 has a 29 February and 2100, being a multiple of 100 and not of 400, does not. The weekday expression MOD(integer - 1, 7) + 1 gives 1 for Monday to 7 for Sunday, the same numbering as ACCEPT FROM DAY-OF-WEEK; it is valid because day 1 is a Monday, and it needs care in Enterprise COBOL, where the INTDATE option can move the origin (see the table below).

Validate before converting

The conversion functions require a valid argument (the Enterprise COBOL manual states the valid forms and ranges, and TEST-DATE-YYYYMMDD exists to check them). The test function returns which part is wrong, in the order year, month, day, so a rejection message can be specific. Here are six candidates, the Julian test, and what GnuCOBOL’s INTEGER-OF-DATE does with an invalid date that nobody tested.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. TEST-DATES.
      * Validating dates before using them.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-CANDIDATES.
           05  FILLER PIC 9(8) VALUE 20261020.
           05  FILLER PIC 9(8) VALUE 20260230.
           05  FILLER PIC 9(8) VALUE 20261320.
           05  FILLER PIC 9(8) VALUE 15991231.
           05  FILLER PIC 9(8) VALUE 20280229.
           05  FILLER PIC 9(8) VALUE 21000229.
       01  WS-TABLE REDEFINES WS-CANDIDATES.
           05  WS-CANDIDATE     PIC 9(8) OCCURS 6 TIMES.
       01  WS-I                 PIC 9.
       01  WS-RESULT            PIC 9.
       01  WS-JULIAN-RESULT     PIC 9.
       01  WS-BAD               PIC 9(8) VALUE 20260230.
       01  WS-INT               PIC 9(7).
       PROCEDURE DIVISION.
       MAIN-PARAGRAPH.
           PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 6
               COMPUTE WS-RESULT =
                   FUNCTION TEST-DATE-YYYYMMDD(WS-CANDIDATE(WS-I))
               DISPLAY WS-CANDIDATE(WS-I) " -> " WS-RESULT
           END-PERFORM
           COMPUTE WS-JULIAN-RESULT = FUNCTION TEST-DAY-YYYYDDD(2027366)
           DISPLAY "Julian 2027366 -> " WS-JULIAN-RESULT
           COMPUTE WS-JULIAN-RESULT = FUNCTION TEST-DAY-YYYYDDD(2028366)
           DISPLAY "Julian 2028366 -> " WS-JULIAN-RESULT
      *    An unchecked invalid date reaches INTEGER-OF-DATE.
           COMPUTE WS-INT = FUNCTION INTEGER-OF-DATE(WS-BAD)
           DISPLAY "INTEGER-OF-DATE(20260230) = " WS-INT
           STOP RUN.
20261020 -> 0
20260230 -> 3
20261320 -> 2
15991231 -> 1
20280229 -> 0
21000229 -> 3
Julian 2027366 -> 2
Julian 2028366 -> 0
INTEGER-OF-DATE(20260230) = 0000000

The returned codes match the manual: 15991231 is year 1 (below 1601), 20261320 is month 2 and 20260230 is day 3. The last line is the trap: GnuCOBOL does not stop for 20260230; it returns 0, which a program that computes with it treats as "no such day". The Enterprise COBOL function descriptions state the valid range and do not promise a result outside it, so the portable rule is the same in both: call TEST-DATE-YYYYMMDD first and reject the record.

Where integer day 1 is: INTDATE

On z/OS the compiler option INTDATE(ANSI) (the default) makes day 1 January 1, 1601, as above. INTDATE(LILIAN) makes day 1 October 15, 1582, the origin used by Language Environment’s own date services. The Programming Guide tabulates which combinations agree:

Option Intrinsic function Language Environment service

INTDATE(ANSI)

INTEGER-OF-DATE

compatible with CEECBLDY; not with CEEDAYS

INTDATE(ANSI)

DATE-OF-INTEGER, DAY-OF-INTEGER

not compatible with CEEDATE

INTDATE(LILIAN)

INTEGER-OF-DATE

compatible with CEEDAYS; not with CEECBLDY

INTDATE(LILIAN)

DATE-OF-INTEGER, DAY-OF-INTEGER

compatible with CEEDATE

A program that exchanges integer dates with other programs or with those services must say which origin it uses, and the difference of two integer dates (a count of days) is the same in both. GnuCOBOL has no INTDATE option: its integer dates always start at 1600-12-31, like INTDATE(ANSI). These are z/OS facts and are not run here.

The ISO 8601 date and time functions

The standard of 2014 added a second family that reads and writes dates as text in ISO 8601 shapes. Enterprise COBOL 6.5 lists it among the 2002/2014 standard features it implements; the outline for this section dates it to the 6.3 service stream, a release this page could not re-check, so it names no release. GnuCOBOL 3.2 implements the family too. The functions are:

Function Purpose

FORMATTED-DATE(format, integer-date)

integer date to text in a date format

FORMATTED-TIME(format, seconds [, offset-minutes])

seconds past midnight to text in a time format

FORMATTED-DATETIME(format, integer-date, seconds [, offset-minutes])

both, to text in a combined format

FORMATTED-CURRENT-DATE(format)

the current date and time in a combined format

INTEGER-OF-FORMATTED-DATE(format, text)

text in a date (or combined) format to an integer date

SECONDS-FROM-FORMATTED-TIME(format, text)

text in a time (or combined) format to seconds past midnight

TEST-FORMATTED-DATETIME(format, text)

0 if the text fits the format, otherwise the position of the first bad character

COMBINED-DATETIME(integer-date, seconds)

one number: the integer date, a decimal point and the seconds as five digits

SECONDS-PAST-MIDNIGHT

the current local time of day in seconds

The format is an alphanumeric (or national, or UTF-8) literal built from the strings in the Language Reference. Date formats: YYYYMMDD and YYYY-MM-DD (calendar), YYYYDDD and YYYY-DDD (ordinal, the day of the year) and YYYYWwwD and YYYY-Www-D (ISO week date: year, week 01 to 52, day 1 to 7). Time formats: hhmmss and hh:mm:ss, a fraction after the seconds written .s to .sssssssss (one to nine digits), the suffix Z for UTC, and the offset forms hhmmss+hhmm and hh:mm:ss+hh:mm. A combined format joins a date format and a time format with an uppercase T, both in the basic or both in the extended style: YYYY-MM-DDThh:mm:ss+hh:mm is allowed and YYYYMMDDThh:mm:ss is not.

The program below formats a fixed date and time (14:30:25 is 52,225 seconds past midnight), shows the same instant as UTC for a time zone two hours ahead (offset 120 minutes), and parses text back. The TEST-FORMATTED-DATETIME results are positions: month 13 is wrong at character 7 of 2026-13-03, and 0 means valid.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. FORMATTED-DEMO.
      * ISO 8601 style date and time functions (Enterprise COBOL 6.x,
      * GnuCOBOL 3.2). Every value here is fixed, so the output is too.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-INT               PIC 9(7).
       01  WS-TEXT              PIC X(32).
       01  WS-SECONDS           PIC 9(5)V99.
       01  WS-CHECK             PIC 99.
       01  WS-COMBINED          PIC 9(7)V9(5).
       PROCEDURE DIVISION.
       MAIN-PARAGRAPH.
           COMPUTE WS-INT = FUNCTION INTEGER-OF-DATE(20261020)
           MOVE FUNCTION FORMATTED-DATE("YYYY-MM-DD", WS-INT)
               TO WS-TEXT
           DISPLAY "extended calendar : " WS-TEXT
           MOVE FUNCTION FORMATTED-DATE("YYYYDDD", WS-INT) TO WS-TEXT
           DISPLAY "basic ordinal     : " WS-TEXT
           MOVE FUNCTION FORMATTED-DATE("YYYY-Www-D", WS-INT)
               TO WS-TEXT
           DISPLAY "extended week     : " WS-TEXT
           MOVE FUNCTION FORMATTED-TIME("hh:mm:ss", 52225) TO WS-TEXT
           DISPLAY "time of day       : " WS-TEXT
           MOVE FUNCTION FORMATTED-DATETIME(
                    "YYYY-MM-DDThh:mm:ss", WS-INT, 52225)
               TO WS-TEXT
           DISPLAY "date and time     : " WS-TEXT
           MOVE FUNCTION FORMATTED-DATETIME(
                    "YYYY-MM-DDThh:mm:ssZ", WS-INT, 52225, 120)
               TO WS-TEXT
           DISPLAY "in UTC (+02:00)   : " WS-TEXT
           MOVE FUNCTION FORMATTED-DATETIME(
                    "YYYY-MM-DDThh:mm:ss+hh:mm", WS-INT, 52225, 120)
               TO WS-TEXT
           DISPLAY "with offset       : " WS-TEXT
      *    And back again.
           COMPUTE WS-INT = FUNCTION INTEGER-OF-FORMATTED-DATE(
                    "YYYY-MM-DD", "2026-11-03")
           DISPLAY "integer of text   : " WS-INT
           COMPUTE WS-SECONDS = FUNCTION SECONDS-FROM-FORMATTED-TIME(
                    "hh:mm:ss", "14:30:25")
           DISPLAY "seconds           : " WS-SECONDS
           COMPUTE WS-CHECK = FUNCTION TEST-FORMATTED-DATETIME(
                    "YYYY-MM-DD", "2026-13-03")
           DISPLAY "bad month at char : " WS-CHECK
           COMPUTE WS-CHECK = FUNCTION TEST-FORMATTED-DATETIME(
                    "YYYY-MM-DD", "2026-11-03")
           DISPLAY "good date         : " WS-CHECK
           COMPUTE WS-COMBINED = FUNCTION COMBINED-DATETIME(
                    155521, 52225)
           DISPLAY "combined          : " WS-COMBINED
           STOP RUN.
extended calendar : 2026-10-20
basic ordinal     : 2026293
extended week     : 2026-W43-2
time of day       : 14:30:25
date and time     : 2026-10-20T14:30:25
in UTC (+02:00)   : 2026-10-20T12:30:25Z
with offset       : 2026-10-20T14:30:25+02:00
integer of text   : 0155535
seconds           : 52225.00
bad month at char : 07
good date         : 00
combined          : 0155521.52225

Notes on the output. 2026-W43-2 is the ISO week date: week 43, Tuesday. With a Z format the result is the time converted to UTC (12:30:25 for 14:30:25 at +02:00); with an offset format the time is shown as given and the offset is appended. COMBINED-DATETIME(155521, 52225) is the integer date, a decimal point and the seconds padded to five digits. INTEGER-OF-FORMATTED-DATE("YYYY-MM-DD", "2026-11-03") returns 155535, fourteen days after 155521, the figure that the due date calculation below also reaches.

The functions that read the clock end the family. FORMATTED-CURRENT-DATE takes a combined format and returns the current date and time in it, and SECONDS-PAST-MIDNIGHT returns the local time of day as a number that supports arithmetic. The next program uses both and also computes the seconds between 23:55 and 00:05, with the extra step that crossing midnight requires. Its first three lines vary with every run.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. LIVE-CLOCK.
      * Clock-dependent functions: the output differs on every run.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-STAMP             PIC X(32).
       01  WS-NOW               PIC 9(5)V99.
       01  WS-START             PIC 9(5)V99 VALUE 86100.
       01  WS-END               PIC 9(5)V99 VALUE 300.
       01  WS-ELAPSED           PIC 9(5)V99.
       PROCEDURE DIVISION.
       MAIN-PARAGRAPH.
           MOVE FUNCTION FORMATTED-CURRENT-DATE(
                    "YYYY-MM-DDThh:mm:ss.ss+hh:mm") TO WS-STAMP
           DISPLAY "formatted now    : " WS-STAMP
           MOVE FUNCTION FORMATTED-CURRENT-DATE("YYYYMMDDThhmmssZ")
               TO WS-STAMP
           DISPLAY "formatted (UTC)  : " WS-STAMP
           COMPUTE WS-NOW = FUNCTION SECONDS-PAST-MIDNIGHT
           DISPLAY "seconds past midnight : " WS-NOW
      *    Elapsed time across midnight: 23:55:00 to 00:05:00.
           IF WS-END < WS-START
               COMPUTE WS-ELAPSED = 86400 - WS-START + WS-END
           ELSE
               COMPUTE WS-ELAPSED = WS-END - WS-START
           END-IF
           DISPLAY "elapsed seconds  : " WS-ELAPSED
           STOP RUN.
formatted now    : 2026-10-20T13:09:58.65+00:00
formatted (UTC)  : 20261020T130958Z
seconds past midnight : 47398.00
elapsed seconds  : 00600.00

Format literals are checked at compile time

Because the format is described by a literal, the compiler can check it. A format that is not in the tables is an error, not a runtime surprise. This deliberately wrong program uses a slash as the separator:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. BAD-FORMAT.
      * Deliberately wrong: a format literal that is not in the tables.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-TEXT              PIC X(12).
       PROCEDURE DIVISION.
       MAIN-PARAGRAPH.
           MOVE FUNCTION FORMATTED-DATE("YYYY/MM/DD", 155521)
               TO WS-TEXT
           DISPLAY WS-TEXT
           STOP RUN.

GnuCOBOL refuses to compile it:

bad-format.cob: in paragraph 'MAIN-PARAGRAPH':
bad-format.cob:9: error: FUNCTION 'FORMATTED-DATE' has invalid date/time format
    7 |        PROCEDURE DIVISION.
    8 |        MAIN-PARAGRAPH.
    9 >            MOVE FUNCTION FORMATTED-DATE("YYYY/MM/DD", 155521)
   10 |                TO WS-TEXT
   11 |            DISPLAY WS-TEXT

The Language Reference requires the first argument of FORMATTED-DATE, FORMATTED-DATETIME, FORMATTED-TIME and FORMATTED-CURRENT-DATE to be a literal. GnuCOBOL tolerates a data item but warns, and then reads it at run time. The next program does that, and also passes integer date 0, which is outside the valid range:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. VAR-FORMAT.
      * Deliberately questionable: the format is not a literal.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-FORMAT            PIC X(10) VALUE "YYYY-MM-DD".
       01  WS-TEXT              PIC X(10).
       PROCEDURE DIVISION.
       MAIN-PARAGRAPH.
           MOVE FUNCTION FORMATTED-DATE(WS-FORMAT, 155521) TO WS-TEXT
           DISPLAY "[" WS-TEXT "]"
           MOVE FUNCTION FORMATTED-DATE("YYYY-MM-DD", 0) TO WS-TEXT
           DISPLAY "[" WS-TEXT "]"
           STOP RUN.
var-format.cob: in paragraph 'MAIN-PARAGRAPH':
var-format.cob:10: warning: FUNCTION 'FORMATTED-DATE' has format in variable [-Wadditional]
    8 |        PROCEDURE DIVISION.
    9 |        MAIN-PARAGRAPH.
   10 >            MOVE FUNCTION FORMATTED-DATE(WS-FORMAT, 155521) TO WS-TEXT
   11 |            DISPLAY "[" WS-TEXT "]"
   12 |            MOVE FUNCTION FORMATTED-DATE("YYYY-MM-DD", 0) TO WS-TEXT
[2026-10-20]
[          ]

The second line shows what GnuCOBOL does with an integer date outside 1 to 3,067,671: the result is blank, with no message. A program that needs to know must test the integer first. Do not rely on a variable format: the portable form is the literal, which the compiler checks and which Enterprise COBOL requires.

Computing a due date and days overdue in Circulation

The Circulation rule is simple: a book is due 14 days after the checkout, and every day after the due date costs a daily fine, with a maximum per loan. The steps are the ones the page has built: validate the loan date, convert it to an integer date, add the loan period, convert back to print, subtract from the run date’s integer to count the days late (never below zero), and multiply. The run date is a fixed 20261105 so that the output is reproducible; a real run would replace its VALUE clause with MOVE FUNCTION CURRENT-DATE(1:8) TO WS-AS-OF, or, better, would read it from a control card so that a rerun after a failure uses the original date. The daily fine (0.25) and its cap (10.00) are values invented for the example.

A second sample file has five transactions chosen to hit the interesting cases: an ordinary loan, a loan from February whose due date is long past, a loan on the last day of the year (whose due date falls in the next year), a loan with an impossible date and a return.

O00010000000120261020
O00010100000220260227
O00010000000320261231
O00010200000420260230
R00010000000120261029

The record layout is LOANREC, 30 bytes: type, member, book, LOAN-DATE and nine bytes of filler.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. OVERDUE.
      * Circulation: due date and days overdue for checkouts.
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT LOANTRAN ASSIGN TO "LOANTRAN2"
               ORGANIZATION IS LINE SEQUENTIAL
               FILE STATUS IS WS-LOAN-STATUS.
       DATA DIVISION.
       FILE SECTION.
       FD  LOANTRAN.
           COPY LOANREC.
       WORKING-STORAGE SECTION.
       01  WS-LOAN-STATUS       PIC XX.
           88  LOAN-OK          VALUE "00".
           88  LOAN-EOF         VALUE "10".
       01  WS-LOAN-DAYS         PIC 9(3) VALUE 14.
       01  WS-DAILY-FINE        PIC 9V99 VALUE 0.25.
       01  WS-MAX-FINE          PIC 99V99 VALUE 10.00.
      * Fixed "today", so that the output below is reproducible.
      * A real run would use FUNCTION CURRENT-DATE(1:8) instead.
       01  WS-AS-OF             PIC 9(8) VALUE 20261105.
       01  WS-AS-OF-INT         PIC 9(7).
       01  WS-LOAN-INT          PIC 9(7).
       01  WS-DUE-INT           PIC 9(7).
       01  WS-DUE-DATE          PIC 9(8).
       01  WS-OVERDUE           PIC 9(5).
       01  WS-FINE              PIC 99V99.
       01  WS-FINE-OUT          PIC Z9.99.
       01  WS-OVERDUE-OUT       PIC ZZ9.
       PROCEDURE DIVISION.
       MAIN-PARAGRAPH.
           COMPUTE WS-AS-OF-INT = FUNCTION INTEGER-OF-DATE(WS-AS-OF)
           DISPLAY "Circulation run date " WS-AS-OF
           OPEN INPUT LOANTRAN
           PERFORM READ-LOAN
           PERFORM UNTIL LOAN-EOF
               EVALUATE TRUE
                   WHEN NOT LOAN-CHECKOUT
                       DISPLAY "member " LOAN-MEMBER-ID " book "
                               LOAN-BOOK-ID " returned " LOAN-DATE
                   WHEN FUNCTION TEST-DATE-YYYYMMDD(LOAN-DATE) NOT = 0
                       DISPLAY "member " LOAN-MEMBER-ID " book "
                               LOAN-BOOK-ID " REJECTED, bad date "
                               LOAN-DATE
                   WHEN OTHER
                       PERFORM CHECK-OVERDUE
               END-EVALUATE
               PERFORM READ-LOAN
           END-PERFORM
           CLOSE LOANTRAN
           STOP RUN.
       READ-LOAN.
           READ LOANTRAN
               AT END SET LOAN-EOF TO TRUE
           END-READ.
       CHECK-OVERDUE.
           COMPUTE WS-LOAN-INT = FUNCTION INTEGER-OF-DATE(LOAN-DATE)
           COMPUTE WS-DUE-INT = WS-LOAN-INT + WS-LOAN-DAYS
           MOVE FUNCTION DATE-OF-INTEGER(WS-DUE-INT) TO WS-DUE-DATE
           IF WS-AS-OF-INT > WS-DUE-INT
               COMPUTE WS-OVERDUE = WS-AS-OF-INT - WS-DUE-INT
           ELSE
               MOVE ZERO TO WS-OVERDUE
           END-IF
           COMPUTE WS-FINE = FUNCTION MIN(WS-OVERDUE * WS-DAILY-FINE,
                                          WS-MAX-FINE)
           MOVE WS-OVERDUE TO WS-OVERDUE-OUT
           MOVE WS-FINE TO WS-FINE-OUT
           DISPLAY "member " LOAN-MEMBER-ID " book " LOAN-BOOK-ID
                   " out " LOAN-DATE " due " WS-DUE-DATE
                   " overdue " WS-OVERDUE-OUT " days, fine "
                   WS-FINE-OUT.
Circulation run date 20261105
member 000100 book 000001 out 20261020 due 20261103 overdue   2 days, fine  0.50
member 000101 book 000002 out 20260227 due 20260313 overdue 237 days, fine 10.00
member 000100 book 000003 out 20261231 due 20270114 overdue   0 days, fine  0.00
member 000102 book 000004 REJECTED, bad date 20260230
member 000100 book 000001 returned 20261029

Reading the output. Member 100’s first loan (20 October) is due 3 November and is two days late on 5 November. The February loan has been out for 237 days past due, and the fine stops at the cap. The loan of 31 December 2026 is due 14 January 2027: adding 14 to the integer date handled the year change that 20261231 + 14 could never have produced. The loan dated 20260230 was rejected by TEST-DATE-YYYYMMDD instead of becoming a silent 0. The program is plain enough to run on z/OS with the same logic; only the file would be USERID.CIRC.LOANTRAN and the program would take its run date from PARM or a control card (see ACCEPT, DISPLAY and Console I/O).

The mistake this replaces

The wrong way is to subtract the two yyyymmdd numbers. It looks plausible, and it is right only inside one month. This deliberately wrong program does it next to the right way:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. DATE-SUBTRACTION.
      * Deliberately wrong: subtracting yyyymmdd numbers.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-LOAN              PIC 9(8) VALUE 20261020.
       01  WS-AS-OF             PIC 9(8) VALUE 20261105.
       01  WS-WRONG             PIC S9(8).
       01  WS-RIGHT             PIC S9(8).
       PROCEDURE DIVISION.
       MAIN-PARAGRAPH.
           COMPUTE WS-WRONG = WS-AS-OF - WS-LOAN
           COMPUTE WS-RIGHT = FUNCTION INTEGER-OF-DATE(WS-AS-OF)
                            - FUNCTION INTEGER-OF-DATE(WS-LOAN)
           DISPLAY "yyyymmdd subtraction : " WS-WRONG
           DISPLAY "integer dates        : " WS-RIGHT
           STOP RUN.
yyyymmdd subtraction : +00000085
integer dates        : +00000016

From 20 October to 5 November is 16 days, and the subtraction of the raw numbers says 85, because 20261105 - 20261020 is a difference between two digit strings that skips the whole gap between 31 and 100 in the month position. The compiler reports nothing, and a fine computed from it is wrong without anyone being told. Dates are converted to integer dates before they are subtracted or compared for distance (a plain IF DATE-A > DATE-B on two yyyymmdd values is fine for ordering).

Two-digit years and windowing (legacy)

Everything above uses four-digit years. Old programs, and records that still carry a yymmdd field, do not. Before the year 2000 the usual way to save two digits per date was ACCEPT ... FROM DATE, and the Y2K work of the late 1990s was largely the job of finding such fields. The two practical ways out were to expand the field to four digits (the permanent fix) or to window it: keep two digits and decide the century from a pivot. The standard has intrinsic functions for the second approach, which are the only part of the old Y2K tooling that is still current:

Function Argument Returns

YEAR-TO-YYYY(yy [, window])

two-digit year

four-digit year

DATE-TO-YYYYMMDD(yymmdd [, window])

six-digit date

yyyymmdd

DAY-TO-YYYYDDD(yyddd [, window])

five-digit Julian date

yyyyddd

The window argument, default 50, is added to the year at execution time to give the last year of a 100-year interval into which the two-digit year must fall. The IBM manual’s table makes the sliding rule concrete: in 2002, 851003 with window 120 is 20851003 (interval 2023 to 2122), with window -20 it is 18851003 (1883 to 1982) and with window 10 it is 19851003 (1913 to 2012). Because the interval moves with the clock, the same data gives different results in different years; the result is only stable when the data is regularly rewritten. GnuCOBOL’s functions accept a third argument, the "year of execution" (an extension, with no counterpart in the Enterprise COBOL syntax), which lets this program fix the year so that its output is repeatable. The first four lines reproduce the IBM table, and the last two reproduce the YEAR-TO-YYYY table.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. WINDOWING.
      * Legacy: expanding two-digit years with a sliding window.
      * The third argument fixes the "year of execution" so the
      * output does not depend on the date the program is run.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-YY                PIC 99.
       01  WS-YYYY              PIC 9(4).
       01  WS-YYMMDD            PIC 9(6).
       01  WS-YYYYMMDD          PIC 9(8).
       01  WS-YYDDD             PIC 9(5).
       01  WS-YYYYDDD           PIC 9(7).
       01  WS-PIVOT             PIC 99 VALUE 50.
       01  WS-YY-A              PIC 99 VALUE 99.
       01  WS-YY-B              PIC 99 VALUE 0.
       PROCEDURE DIVISION.
       MAIN-PARAGRAPH.
           MOVE 851003 TO WS-YYMMDD
           COMPUTE WS-YYYYMMDD =
               FUNCTION DATE-TO-YYYYMMDD(WS-YYMMDD, 120, 2002)
           DISPLAY "851003, window +120, in 2002 : " WS-YYYYMMDD
           COMPUTE WS-YYYYMMDD =
               FUNCTION DATE-TO-YYYYMMDD(WS-YYMMDD, -20, 2002)
           DISPLAY "851003, window -20,  in 2002 : " WS-YYYYMMDD
           COMPUTE WS-YYYYMMDD =
               FUNCTION DATE-TO-YYYYMMDD(WS-YYMMDD, 10, 2002)
           DISPLAY "851003, window +10,  in 2002 : " WS-YYYYMMDD
           MOVE 10004 TO WS-YYDDD
           COMPUTE WS-YYYYDDD =
               FUNCTION DAY-TO-YYYYDDD(WS-YYDDD, -20, 2002)
           DISPLAY "10004 Julian, -20,   in 2002 : " WS-YYYYDDD
           MOVE 26 TO WS-YY
           COMPUTE WS-YYYY = FUNCTION YEAR-TO-YYYY(WS-YY, 50, 2026)
           DISPLAY "26 with the default window 50 in 2026 : " WS-YYYY
           MOVE 76 TO WS-YY
           COMPUTE WS-YYYY = FUNCTION YEAR-TO-YYYY(WS-YY, 50, 2026)
           DISPLAY "76 with the default window 50 in 2026 : " WS-YYYY
           MOVE 77 TO WS-YY
           COMPUTE WS-YYYY = FUNCTION YEAR-TO-YYYY(WS-YY, 50, 2026)
           DISPLAY "77 with the default window 50 in 2026 : " WS-YYYY
      *    The same idea written by hand: a fixed pivot year.
           IF WS-YY-A < WS-YY-B
               DISPLAY "99 sorts before 00 : true"
           ELSE
               DISPLAY "99 sorts before 00 : false (two digits only)"
           END-IF
           MOVE WS-YY-B TO WS-YY
           IF WS-YY < WS-PIVOT
               COMPUTE WS-YYYY = 2000 + WS-YY
           ELSE
               COMPUTE WS-YYYY = 1900 + WS-YY
           END-IF
           DISPLAY "00 with a hand-made pivot of 50 : " WS-YYYY
           COMPUTE WS-YYYY = FUNCTION YEAR-TO-YYYY(4, 23, 1995)
           DISPLAY "IBM table: 4, +23, in 1995       : " WS-YYYY
           COMPUTE WS-YYYY = FUNCTION YEAR-TO-YYYY(98, -15, 2008)
           DISPLAY "IBM table: 98, -15, in 2008      : " WS-YYYY
           STOP RUN.
851003, window +120, in 2002 : 20851003
851003, window -20,  in 2002 : 18851003
851003, window +10,  in 2002 : 19851003
10004 Julian, -20,   in 2002 : 1910004
26 with the default window 50 in 2026 : 2026
76 with the default window 50 in 2026 : 2076
77 with the default window 50 in 2026 : 1977
99 sorts before 00 : false (two digits only)
00 with a hand-made pivot of 50 : 2000
IBM table: 4, +23, in 1995       : 2004
IBM table: 98, -15, in 2008      : 1898

The default window of 50 maps 26 to 2026, 76 to 2076 and 77 to 1977 when run in 2026 (the interval is 1977 to 2076); the same program run in 2027 would map 77 to 2077. The last block of the program also shows the two things that go wrong with two digits: 99 compares as greater than 00, so two-digit dates do not sort or compare in time order, and the hand-made IF with a fixed pivot of 50 works until the pivot is wrong for the data, which is why a fixed pivot is old code that needs a note in the program rather than a pattern to copy.

The Millennium Language Extensions: removed

Old code you may meet. Earlier Enterprise COBOL releases (up to V4) shipped the Millennium Language Extensions: the DATE FORMAT clause on a data item (for example DATE FORMAT YYXXXX for a yymmdd-like field), the DATEPROC compiler option and YEARWINDOW, with which the compiler windowed such fields automatically during comparisons and moves. IBM removed them in Enterprise COBOL V5 and V6, and searching the 6.5 Language Reference and Programming Guide for DATEPROC, YEARWINDOW and the DATE FORMAT clause finds nothing. A program that uses them does not compile on 6.5; the migration is one of:

  • widen the field to PIC 9(8) or PIC 9(7) with a four-digit year and convert the stored data once (the preferred one);

  • keep the stored yymmdd and call DATE-TO-YYYYMMDD or YEAR-TO-YYYY where the program needs a full date, as the program above does;

  • leave a note beside the field naming the window and the planned year when it must be revisited.

The details of the V4 options are not documented for 6.5 and are not repeated here; the description above is from memory of the V4 documentation and is not re-verified.

Differences between the standard, Enterprise COBOL and GnuCOBOL

Feature ISO/IEC 1989 Enterprise COBOL 6.5 GnuCOBOL 3.2

ACCEPT FROM DATE, DAY, DAY-OF-WEEK, TIME

long-standing (COBOL 85 or earlier)

yes

yes

DATE YYYYMMDD, DAY YYYYDDD

COBOL 2002

yes

yes

CURRENT-DATE, integer-date functions, TEST-DATE-YYYYMMDD

CURRENT-DATE and the integer-date functions: 1989 addendum; TEST-DATE-YYYYMMDD: COBOL 2002

yes; INTDATE option moves day 1

yes; day 1 fixed at 1601-01-01

FORMATTED-*, INTEGER-OF-FORMATTED-DATE, COMBINED-DATETIME, SECONDS-PAST-MIDNIGHT

COBOL 2014

yes; the format must be a literal

yes; a variable format is accepted with a warning

YEAR-TO-YYYY, DATE-TO-YYYYMMDD, DAY-TO-YYYYDDD

COBOL 2002

yes, two arguments

yes, plus an optional third (year of execution)

Millennium Language Extensions (DATE FORMAT, DATEPROC, YEARWINDOW)

not in the standard

removed in V5 and V6

not implemented

LOCALE-DATE, LOCALE-TIME, MODULE-DATE, MODULE-TIME, MODULE-FORMATTED-DATE

extension

not listed

present

Where the book is outdated

The book consulted for this section (Taulli, Modern Mainframe Development, 2022) is a survey of mainframe work and is used here only for the topic list and the Y2K narrative; nothing is copied from it. The points to carry into new code are:

  • Two-digit years are not a design to follow. Teaching material that shows ACCEPT ... FROM DATE and then stores or compares the six digits is preserving a Y2K-era habit; new code uses FUNCTION CURRENT-DATE(1:8) or ACCEPT ... FROM DATE YYYYMMDD and a four-digit year in the record layout.

  • The Millennium Language Extensions that the Y2K literature describes no longer exist in the current compiler (above); windowing survives only as the three intrinsic functions.

  • The ISO 8601 functions are newer than most introductory books and than COBOL 2002; they are the way to produce and read the text dates of JSON and REST payloads, which the data-interchange pages of this section depend on.

  • The book’s Enterprise COBOL baseline (6.3) predates 6.4 and 6.5; the date functions above are in the 6.5 reference, which is the version this page follows.