# COBOL Y2K redux

In the 1990s, COBOL computer engineers worked out a way to extend six-digit date fields by converting them to YYYDDD where YYY is the year - 1900 and DDD is the day of the year [001 to 366]. This scheme could extend the maximum date to 2899-12-31.

In the year 2898, the engineers started panicking because their 900 year old code bases were going to fail. Being from the year 2898, they just used their time machine to send a lone Codeinator to the year 1998 with this algorithm and the task of getting it implemented as widely as possible:

Use a scheme PPQQRR where if 01 ≤ QQ ≤ 12 then it's a standard YYMMDD date in the 1900s, but if QQ > 12 then it represents the days after 2000-01-01 in base 100 for PP and RR but base 87 for QQ - 13.

This scheme extends far beyond year 2899 and is also backwards compatible with standard dates, so no modifications of existing archives are required.

Some examples:

PPQQRR  YYYY-MM-DD
000101  1900-01-01  -- minimum conventional date suggested by J. Allen
010101  1901-01-01  -- edge case suggested by J. Allen
681231  1968-12-31  -- as above
991231  1999-12-31  -- maximum conventional date
001300  2000-01-01  -- zero days after 2000-01-01
008059  2018-07-04  -- current date
378118  2899-12-31  -- maximum date using YYYDDD scheme
999999  4381-12-23  -- maximum date using PPQQRR scheme


Your challenge is to write a program or function to accept input as PPQQRR and output as an ISO date YYYY-MM-DD. Input method can be parameter, console or command line, whatever is easiest.

For your amusement, here is a noncompeting solution in COBOL-85:

IDENTIFICATION DIVISION.
PROGRAM-ID. DATE-CONVERSION.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 T PIC 9(8).
01 U PIC 9(8).
01 D VALUE '999999'.
05 P PIC 9(2).
05 Q PIC 9(2).
05 R PIC 9(2).
01 F.
05 Y PIC 9(4).
05 M PIC 9(2).
05 D PIC 9(2).
PROCEDURE DIVISION.
IF Q OF D > 12 THEN
MOVE FUNCTION INTEGER-OF-DATE(20000101) TO T
COMPUTE U = R OF D + 100 * ((Q OF D - 13) + 87 * P OF D) + T
MOVE FUNCTION DATE-OF-INTEGER(U) TO F
DISPLAY "Date: " Y OF F "-" M OF F "-" D OF F
ELSE
DISPLAY "Date: 19" P OF D "-" Q OF D "-" R OF D
END-IF.
STOP RUN.

• "But do not program in COBOL if you can avoid it." -- The Tao of Programming
– tsh
Jul 5, 2018 at 7:57
• – pipe
Jul 5, 2018 at 9:14
• @user202729 because yymmdd doesn't work for for years >=2000, that's the entire point of the Y2K debacle.
Jul 5, 2018 at 11:15
• @Adám -- In the spirit of COBOL which is very fussy w.r.t. I/O, I'll have to say it needs to be in ISO yyyy-mm-dd format.
– user15259
Jul 5, 2018 at 11:51
• @Giuseppe -- In the spirit of COBOL which doesn't really differentiate strings and numbers, yes! Provided you can input leading zeros, e.g. 001300.
– user15259
Jul 5, 2018 at 12:04

# T-SQL, 99 98 bytes

SELECT CONVERT(DATE,IIF(ISDATE(i)=1,'19'+i,


Line break is for readability only. Thank goodness for implicit casting.

Input is via a pre-existing table t with CHAR column i, per our IO rules.

Goes through the following steps:

1. Initial check is via the SQL function ISDATE(). (The behavior of this function changes based on language settings, it works as expected on my english-us server). Note this this is just a check for validity, if we tried to parse it directly, it would map 250101 as 2025-01-01, not 1925-01-01.
2. If the string parses correctly as a date, tack 19 on the front (rather than change the server-level year cutoff setting). Final date conversion will come at the end.
3. If the string does not parse as a date, convert it to a number instead. The shortest math I could find was 8700*PP + QQRR - 1300, which avoids the (very long) SQL SUBSTRING() function. This math checks out for the provided samples, I'm pretty sure it is right.
4. Use DATEADD to add that many days to 2000-01-01, which can be shorted to 2000.
5. Take that final result (either a string from step 2, or a DATETIME from step 4), and CONVERT() it to a pure DATE.

I thought at one point that I found a problematic date: 000229. This is the only date which parses differently for 19xx vs 20xx (since 2000 was a leap year, but 1900 was not, due to weird leap-year exceptions). Because of that, though, 000229 isn't even a valid input (since, as mentioned, 1900 was not a leap year), so doesn't have to be accounted for.

• Good stuff. It's too bad ISDATE doesn't return a boolean, or that integers can't be implicitly converted to boolean in IIF otherwise you could save two bytes.
– user15259
Jul 6, 2018 at 12:18
• @YiminRong Yep, implicit casting in SQL is very trial-and-error, and works differently in some functions that are otherwise very similar. I'm lucky I didn't have to explicitly cast my LEFT() and RIGHT() function results to integers before multiplying them, that would really have messed up my byte count Jul 6, 2018 at 13:26
• I think you can remove an extra character by replacing -1300,'2000' with -935,'1999'. Jul 11, 2018 at 4:51
• Cool idea, @RazvanSocol. I tried going back further multiples of 365 days, but unfortunately couldn't find anything shorter than that. Jul 18, 2018 at 16:12

# R, 126 bytes

function(x,a=x%/%100^(2:0)%%100,d=as.Date)'if'(a[2]<13,d(paste(19e6+x),'%Y%m%d'),d(a[3]+100*((a[2]-13)+87*a[1]),'2000-01-01'))


Try it online!

• -5 bytes thanks to @Giuseppe suggestion to take a numeric input instead of string
• Fails for inputs representing dates prior to January the first 1969 (e.g. 000101 or 681231) Jul 5, 2018 at 14:33
• @JonathanAllan: well spotted, thanks. Now it should be fixed (unfortunately requiring 5 more bytes...) Jul 5, 2018 at 18:13

# JavaScript (SpiderMonkey), 103 bytes

s=>new Date(...([a,b,c]=s.match(/../g),b>12?[2e3,0,(b-13+a*87)*100-~c]:[a,b-1,c])).toJSON().splitT[0]


Try it online!

.toJSON will failed on a UTC+X timezone. This code works, but longer (+11bytes):

s=>Intl.DateTimeFormatii.format(new Date(...([a,b,c]=s.match(/../g),b>12?[2e3,0,(b-13+a*87)*100-~c]:[a,b-1,c])))

• You can save 13 bytes with .toJSON(). Jul 5, 2018 at 9:52
• And you can save 9 more bytes by splitting the input string into three 2-char substrings. Jul 5, 2018 at 9:57
• @Arnauld I was originally trying this on my machine. But it does not work since my timezone is UTC+8. But it at least works on TIO.
– tsh
Jul 5, 2018 at 10:03
• Since we define languages by their implementation (here 'Node.js running on TIO'), is it really invalid? Jul 5, 2018 at 10:20
• For the bullet-proof version, you can do it that way to save 1 byte. Jul 5, 2018 at 10:28

# Python 2, 159 bytes

from datetime import*
def f(s):D=datetime;p,q,r=map(int,(s[:2],s[2:4],s[4:]));return str(q>12and D(2000,1,1)+timedelta(100*(q-13+87*p)+r)or D(1900+p,q,r))[:10]


Try it online!

• Nice trick using ... and ... or ... instead of ... if ... else .... Jul 12, 2018 at 16:11

## ABAP, 173 171 bytes

Saved 2 bytes by further optimizing the output

According to the legends, an SAP customer in the early 21st century once said:

After a nuclear war of total destruction, the one thing remaining will be SAPGUI.

He was right. Today, in 2980, there is no more C++, no more COBOL. After the war everyone had to rewrite their code in SAP ABAP. To provide backwards compatibility to the leftovers of the 2800's COBOL programs, our scientists rebuilt it as a subroutine in ABAP.

FORM x USING s.DATA d TYPE d.IF s+2 < 1300.d ='19'&& s.ELSE.d ='20000101'.d = d + s+4 + 100 * ( ( s+2(2) - 13 ) + 87 * s(2) ).ENDIF.WRITE:d(4),d+4,9 d+6,8'-',5'-'.ENDFORM.


It can be called by a program like this:

REPORT z.
PARAMETERS date(6) TYPE c. "Text input parameter
PERFORM x USING date.      "Calls the subroutine


Explanation of my code:

FORM x USING s.     "Subroutine with input s
DATA d TYPE d.    "Declare a date variable (internal format: YYYYMMDD)
IF s+2 < 1300.    "If substring s from index 2 to end is less than 1300
d ='19'&& s.    "the date is 19YYMMDD
ELSE.             "a date past 2000
d ='20000101'.  "Initial d = 2000 01 01 (yyyy mm dd)

"The true magic. Uses ABAPs implicit chars to number cast
"and the ability to add days to a date by simple addition.
"Using PPQQRR as input:
" s+4 = RR, s+2(2) = QQ, s(2) = PP
d = d + s+4 + 100 * ( ( s+2(2) - 13 ) + 87 * s(2) ).
ENDIF.
"Make it an ISO date by splitting, concatenating and positioning the substrings of our date.
WRITE:             "Explanation:
d(4),            "d(4) = YYYY portion. WRITE adds a space after each parameter, so...
5 '-' && d+4,    "place dash at absolute position 5. Concatenate '-' with MMDD...
8 '-' && d+6.    "place dash at absolute position 8, overwriting DD. Concatenate with DD again.
ENDFORM.


ABAP's Date type has the odd property to be formatted as DDMMYYYY when using WRITE - might be locale dependent even - despite the internal format being YYYYMMDD. But when we use a substring selector like d(4) it selects the first 4 characters of the internal format, hence giving us YYYY.

Update: The output formatting in the explanation is now outdated, I optimized it by 2 bytes in the golfed version:

WRITE:  "Write to screen, example for 2000-10-29
d(4),   "YYYY[space]                =>  2000
d+4,    "MMDD[space]                =>  2000 1029
9 d+6,  "Overwrites at position 9   =>  2000 10229
8'-',   "Place dash at position 8   =>  2000 10-29
5'-'.   "Place dash at position 5   =>  2000-10-29

• Excellent, I like it. Now all we need is a version in MUMPS and we'll survive anything!
– user15259
Jul 10, 2018 at 12:52
– Maz
Jul 10, 2018 at 13:25

# Kotlin, 222 bytes

Hard coded Calendar field names constants to save 49 bytes.

{d:Int->val p=d/10000
val q=d/100%100
val r=d%100
if(q<13)"19%02d-%02d-%02d".format(p,q,r)
else{val c=Calendar.getInstance()
c.set(2000,0,1)