COBOL Y2K redux

36

Dans les années 1990, les ingénieurs informatiques COBOL ont travaillé un moyen d'étendre les champs de date à six chiffres en les convertissant YYYDDDYYYest le year - 1900et DDDest le jour de l'année [001 to 366]. Ce schéma pourrait prolonger la date maximum à 2899-12-31.

En 2898, les ingénieurs ont commencé à paniquer parce que leurs bases de code vieilles de 900 ans allaient échouer. À partir de l'année 2898, ils utilisaient simplement leur machine à voyager le temps pour envoyer un codinateur isolé à l'année 1998 avec cet algorithme et la tâche de le mettre en œuvre aussi largement que possible:

Utilisez un schéma PPQQRR01 ≤ QQ ≤ 12il s'agit d'une YYMMDDdate standard dans les années 1900, mais si QQ > 12elle représente les jours suivants 2000-01-01en base 100 pour PPet la RRbase 87 pour QQ - 13.

Ce système s'étend bien au-delà de l'année 2899 et est également rétrocompatible avec les dates standard. Aucune modification des archives existantes n'est donc nécessaire.

Quelques exemples:

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

Votre défi consiste à écrire un programme ou une fonction pour accepter les entrées PPQQRRet sorties en tant que date ISO YYYY-MM-DD. La méthode de saisie peut être un paramètre, une console ou une ligne de commande, le plus simple.

Pour votre amusement, voici une solution non compétitive dans 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.

la source
4
"Mais ne programmez pas en COBOL si vous pouvez l'éviter." - Le Tao de la programmation
tsh
1
@ user202729 parce que yymmddcela ne fonctionne pas pendant des années >=2000, c'est tout l'intérêt de la débâcle de l'an 2000.
JAD
2
@ Adám - Dans l'esprit de COBOL, très pointilleux en matière d'E / S, je dois dire que le yyyy-mm-ddformat doit être au format ISO .
4
@ Giuseppe - Dans l'esprit du COBOL qui ne différencie pas vraiment les chaînes et les chiffres, oui! Pourvu que vous puissiez saisir des zéros non significatifs, par exemple 001300.

Réponses:

5

T-SQL, 99 98 octets

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

Le saut de ligne est pour la lisibilité seulement. Dieu merci pour le casting implicite.

La saisie s'effectue via une table préexistante t avec la CHARcolonne i , conformément à nos règles IO .

Passe par les étapes suivantes:

  1. La vérification initiale se fait via la fonction SQL ISDATE(). (Le comportement de cette fonction change en fonction des paramètres de langue, il fonctionne comme prévu sur mon english-usserveur). Notez que ceci n’est qu’une vérification de validité. Si nous essayions de l’analyser directement, la carte correspondrait 250101au 2025-01-01 et non au 1925-01-01.
  2. Si la chaîne est correctement analysée comme une date, virez 19de bord (plutôt que de modifier le paramètre de limite d'année au niveau du serveur). La date de conversion finale viendra à la fin.
  3. Si la chaîne ne s'analyse pas en tant que date, convertissez-la en nombre. Le calcul le plus court que j'ai pu trouver était celui 8700*PP + QQRR - 1300qui évite la fonction (très longue) SQL SUBSTRING(). Ce calcul vérifie les échantillons fournis, je suis presque sûr que c'est correct.
  4. Utilisez DATEADDpour ajouter ce nombre de jours à 2000-01-01, ce qui peut être raccourci à 2000.
  5. Prenez ce résultat final (soit une chaîne de l’étape 2, soit une DATETIME de l’étape 4), et CONVERT()vous obtenez un résultat pur DATE.

Je pensais à un moment donné que j'ai trouvé une date problématique: 000229. C’est la seule date qui analyse différemment pour 19xx par rapport à 20xx (puisque 2000 était une année bissextile, mais 1900 ne l’était pas, en raison d’ étranges exceptions pour les années bissextiles ). À cause de cela, cependant, 000229n’est même pas une entrée valide (puisque, comme mentionné, 1900 n’était pas une année bissextile), elle n’a donc pas à être prise en compte.

BradC
la source
Bon produit. Dommage ISDATEqu'il ne retourne pas de booléen, ou que des entiers ne peuvent pas être convertis implicitement en booléens, IIFsinon vous pourriez économiser deux octets.
@YiminRong Oui, le transtypage implicite dans SQL est très aléatoire et fonctionne différemment dans certaines fonctions par ailleurs très similaires. J'ai de la chance de ne pas avoir explicitement jeté mes résultats LEFT()et RIGHT()function sur des entiers avant de les multiplier, cela aurait vraiment gâché le nombre d'octets
BradC
1
Je pense que vous pouvez supprimer un caractère supplémentaire en le remplaçant -1300,'2000'par -935,'1999'.
Razvan Socol
Bonne idée, @RazvanSocol. J'ai essayé de revenir en arrière de multiples multiples de 365 jours, mais je n'ai malheureusement rien trouvé de plus court.
BradC
5

R , 126 octets

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

Essayez-le en ligne!

  • -5 octets grâce à la suggestion de @Giuseppe de prendre une entrée numérique au lieu d'une chaîne
digEmAll
la source
4
Échec pour les entrées représentant des dates antérieures au premier janvier 1969 (par exemple 000101ou 681231)
Jonathan Allan
2
@ JonathanAllan: bien repéré, merci. Maintenant, il devrait être corrigé (nécessitant malheureusement 5 octets de plus ...)
digEmAll
4

JavaScript (SpiderMonkey) , 103 octets

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]

Essayez-le en ligne!


.toJSONéchouera sur un fuseau horaire UTC + X. Ce code fonctionne, mais plus longtemps (+11 octets):

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
la source
Vous pouvez économiser 13 octets avec .toJSON().
Arnauld
Et vous pouvez économiser 9 octets supplémentaires en scindant la chaîne d'entrée en trois sous-chaînes de 2 caractères.
Arnauld
@Arnauld J'essayais à l'origine ceci sur ma machine. Mais cela ne fonctionne pas car mon fuseau horaire est UTC + 8. Mais ça marche au moins sur TIO.
tsh
Puisque nous définissons les langues par leur implémentation (ici 'Node.js s'exécutant sur TIO'), est-ce vraiment invalide?
Arnauld
Pour la version à l'épreuve des balles, vous pouvez le faire de cette façon en sauvegardant 1 octet.
Arnauld
2

Python 2 , 159 octets

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]

Essayez-le en ligne!

Chas Brown
la source
Belle astuce utilisant ... and ... or ...au lieu de ... if ... else ....
Alexander Revo
2

ABAP, 173 171 octets

Sauvegardé 2 octets en optimisant davantage la sortie

Selon la légende, un client SAP du début du 21ème siècle a déclaré:

Après une guerre nucléaire de destruction totale, il ne restera plus que SAPGUI.

Il avait raison. Aujourd'hui, en 2980, il n'y a plus de C ++, plus de COBOL. Après la guerre, tout le monde devait réécrire son code dans SAP ABAP. Pour offrir une compatibilité ascendante aux restes des programmes COBOL du 2800, nos scientifiques l'ont reconstruit en tant que sous-programme dans 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.

Il peut être appelé par un programme comme celui-ci:

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

Explication de mon 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.

Le type Date de ABAP a la propriété impaire à mettre en forme en tant que JJMMAAAA si vous utilisez WRITE- cela peut dépendre des paramètres régionaux même si le format interne est AAAAMMJJ. Mais lorsque nous utilisons un sélecteur de sous-chaîne comme d(4)celui-ci, il sélectionne les 4 premiers caractères du format interne , nous donnant donc YYYY.

Mise à jour : le format de sortie dans l'explication est maintenant obsolète, je l'ai optimisé de 2 octets dans la version avec 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
la source
Excellent, j'aime bien. Maintenant, tout ce dont nous avons besoin est une version MUMPSet nous survivrons à tout!
1
@ YiminRong Merci! Votre question basée sur COBOL demandait essentiellement quelque chose comme ça, je n'avais pas le choix.
Maz
1

Kotlin , 222 octets

Les champs du calendrier codés en dur nomment les constantes pour économiser 49 octets.

{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))}}

Essayez-le en ligne!

JohnWells
la source