COBOL Y2K redux

36

En la década de 1990, los ingenieros informáticos de COBOL descubrieron una forma de ampliar los campos de fecha de seis dígitos al convertirlos a YYYDDDdónde YYYes year - 1900y DDDes el día del año [001 to 366]. Este esquema podría extender la fecha máxima a 2899-12-31.

En el año 2898, los ingenieros comenzaron a entrar en pánico porque sus bases de código de 900 años iban a fallar. Siendo del año 2898, simplemente usaron su máquina del tiempo para enviar un Codeinator solitario al año 1998 con este algoritmo y la tarea de implementarlo lo más ampliamente posible:

Utilice un esquema PPQQRRen el que si 01 ≤ QQ ≤ 12es una YYMMDDfecha estándar en el siglo XX, pero si QQ > 12representa los días posteriores 2000-01-01en la base 100 para PPy la RRbase 87 para QQ - 13.

Este esquema se extiende mucho más allá del año 2899 y también es compatible con las fechas estándar, por lo que no se requieren modificaciones de los archivos existentes.

Algunos ejemplos:

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

Su desafío es escribir un programa o función para aceptar la entrada PPQQRRy la salida como una fecha ISO YYYY-MM-DD. El método de entrada puede ser parámetro, consola o línea de comando, lo que sea más fácil.

Para su diversión, aquí hay una solución no competitiva en 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.

fuente
44
"Pero no programe en COBOL si puede evitarlo". - El Tao de la programación
tsh
1
@ user202729 porque yymmddno funciona durante años >=2000, ese es el objetivo de la debacle de Y2K.
JAD
2
@ Adám: en el espíritu de COBOL, que es muy exigente con la E / S, debo decir que debe estar en yyyy-mm-ddformato ISO .
44
@Giuseppe - ¡En el espíritu de COBOL que realmente no diferencia cadenas y números, sí! Siempre que pueda ingresar ceros a la izquierda, por ejemplo 001300.

Respuestas:

5

T-SQL, 99 98 bytes

SELECT CONVERT(DATE,IIF(ISDATE(i)=1,'19'+i,
       DATEADD(d,8700*LEFT(i,2)+RIGHT(i,4)-935,'1999')))FROM t

El salto de línea es solo para legibilidad. Gracias a Dios por el casting implícito.

La entrada es a través de una tabla t preexistente con la CHARcolumna i , según nuestras reglas de IO .

Sigue los siguientes pasos:

  1. La verificación inicial es a través de la función SQL ISDATE(). (El comportamiento de esta función cambia según la configuración del idioma, funciona como se esperaba en mi english-usservidor). Tenga en cuenta que esto es solo una verificación de validez, si intentamos analizarlo directamente, se mapearía250101 asignaría como 2025-01-01, no como 1925-01-01.
  2. Si la cadena se analiza correctamente como una fecha, agregue 19 el frente (en lugar de cambiar la configuración de corte de año a nivel del servidor). La conversión de la fecha final llegará al final.
  3. Si la cadena no se analiza como una fecha, conviértala en un número. La matemática más corta que pude encontrar fue 8700*PP + QQRR - 1300, lo que evita el SQL (muy largo)SUBSTRING() función . Esta matemática verifica las muestras proporcionadas, estoy bastante seguro de que es correcto.
  4. Use DATEADDpara agregar tantos días a 2000-01-01, que se pueden acortar a 2000 .
  5. Tome ese resultado final (ya sea una cadena del paso 2 o un DATETIME del paso 4) y CONVERT()póngalo a un nivel puro DATE.

Pensé en un momento que me encontré con una fecha problemática: 000229. Esta es la única fecha que se analiza de manera diferente para 19xx frente a 20xx (ya que 2000 fue un año bisiesto, pero 1900 no lo fue, debido a las extrañas excepciones del año bisiesto ). Sin embargo, debido a eso, 000229ni siquiera es una entrada válida (ya que, como se mencionó, 1900 no fue un año bisiesto), por lo que no tiene que tenerse en cuenta.

BradC
fuente
Buen material. Es una lástima ISDATEque no devuelva un valor booleano, o que los enteros no se puedan convertir implícitamente a un valor booleano; de lo IIFcontrario, podría guardar dos bytes.
@YiminRong Sí, la conversión implícita en SQL es muy prueba y error, y funciona de manera diferente en algunas funciones que, de lo contrario, son muy similares. Soy yo la suerte no tener que convertir explícitamente mis LEFT()y RIGHT()función tiene como resultado a enteros antes de multiplicar ellos, que realmente habría perdió mi cuenta de bytes
BradC
1
Creo que puedes eliminar un personaje adicional reemplazándolo -1300,'2000'con -935,'1999'.
Razvan Socol
Buena idea, @RazvanSocol. Intenté retroceder más de 365 días, pero desafortunadamente no pude encontrar nada más corto que eso.
BradC
5

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'))

Pruébalo en línea!

  • -5 bytes gracias a la sugerencia de @Giuseppe de tomar una entrada numérica en lugar de una cadena
digEmAll
fuente
44
Falla para las entradas que representan fechas anteriores a enero del primer 1969 (por ejemplo, 000101o 681231)
Jonathan Allan
2
@ JonathanAllan: bien visto, gracias. Ahora debería ser reparado (desafortunadamente requiere 5 bytes más ...)
digEmAll
4

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().split`T`[0]

Pruébalo en línea!


.toJSONfallará en una zona horaria UTC + X. Este código funciona, pero más tiempo (+ 11bytes):

s=>Intl.DateTimeFormat`ii`.format(new Date(...([a,b,c]=s.match(/../g),b>12?[2e3,0,(b-13+a*87)*100-~c]:[a,b-1,c])))
tsh
fuente
Puede guardar 13 bytes con .toJSON().
Arnauld
Y puede guardar 9 bytes más dividiendo la cadena de entrada en tres subcadenas de 2 caracteres.
Arnauld
@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
Since we define languages by their implementation (here 'Node.js running on TIO'), is it really invalid?
Arnauld
For the bullet-proof version, you can do it that way to save 1 byte.
Arnauld
2

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!

Chas Brown
fuente
Nice trick using ... and ... or ... instead of ... if ... else ....
Alexander Revo
2

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.

Él estaba en lo correcto. Hoy, en 2980, no hay más C ++, no más COBOL. Después de la guerra, todos tuvieron que reescribir su código en SAP ABAP. Para proporcionar compatibilidad con las sobras de los programas COBOL del 2800, nuestros científicos lo reconstruyeron como una subrutina en 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.

Puede ser llamado por un programa como este:

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

Explicación de mi código:

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.

El tipo de fecha de ABAP tiene la propiedad impar para formatearse como DDMMYYYY cuando se usa WRITE, puede ser dependiente de la configuración regional, a pesar de que el formato interno sea AAAAMMDD. Pero cuando usamos un selector de subcadena como d(4)si selecciona los primeros 4 caracteres del formato interno , por lo tanto, nos da AAAA.

Actualización : el formato de salida en la explicación ahora está desactualizado, lo optimicé en 2 bytes en la versión de golf:

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
Maz
fuente
Excellent, I like it. Now all we need is a version in MUMPS and we'll survive anything!
1
@YiminRong Thanks! Your COBOL-based question basically asked for something like this, I had no choice.
Maz
1

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)
c.add(5,(p*87+q-13)*100+r)
"%4d-%02d-%02d".format(c.get(1),c.get(2)+1,c.get(5))}}

Try it online!

JohnWells
fuente