Redux COBOL Y2K

36

W latach 90. inżynierowie COBOL opracowali sposób rozszerzenia sześciocyfrowych pól daty, przekształcając je YYYDDDtam, gdzie YYYjest year - 1900i gdzie jest DDDdzień roku [001 to 366]. Ten schemat może przedłużyć maksymalny termin do 2899-12-31.

W roku 2898 inżynierowie zaczęli panikować, ponieważ ich 900-letnie bazy kodów uległy awarii. Będąc od roku 2898, właśnie wykorzystali swój wehikuł czasu, aby wysłać samotnego Codeinatora do roku 1998 z tym algorytmem i zadaniem jego wdrożenia w jak najszerszym zakresie:

Użyj schematu PPQQRRgdzie jeśli 01 ≤ QQ ≤ 12to jest to standardowy YYMMDDtermin w 1900, ale czy QQ > 12to oznacza dni po 2000-01-01w bazie 100 dla PPa RRjednak podstawy 87 za QQ - 13.

Program ten wykracza daleko poza rok 2899 i jest również wstecznie zgodny ze standardowymi datami, więc nie są wymagane modyfikacje istniejących archiwów.

Kilka przykładów:

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

Wyzwanie polega na napisaniu programu lub funkcji, która będzie akceptować dane wejściowe PPQQRRi wyjściowe jako datę ISO YYYY-MM-DD. Metodą wprowadzania może być parametr, konsola lub wiersz poleceń, cokolwiek jest najłatwiejsze.

Dla twojej rozrywki, oto niekonkurencyjne rozwiązanie w 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.

źródło
4
„Ale nie programuj w języku COBOL, jeśli możesz tego uniknąć”. - Tao programowania
tsh
1
@ user202729, ponieważ yymmddnie działa przez lata >=2000, to jest sedno klęski Y2K.
JAD
2
@ Adám - W duchu COBOL, który jest bardzo wybredny we / wy, muszę powiedzieć, że musi być w yyyy-mm-ddformacie ISO .
4
@Giuseppe - W duchu języka COBOL, który tak naprawdę nie rozróżnia ciągów i liczb, tak! Pod warunkiem, że możesz wprowadzić zera wiodące, np 001300.

Odpowiedzi:

5

T-SQL, 99 98 bajtów

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

Podział linii służy wyłącznie do odczytu. Dzięki Bogu za niejawne rzucanie.

Dane wejściowe pochodzą z wcześniej istniejącej tabeli t z CHARkolumną i , zgodnie z naszymi zasadami IO .

Wykonuje następujące kroki:

  1. Wstępne sprawdzenie odbywa się za pomocą funkcji SQL ISDATE(). (Zachowanie tej funkcji zmienia się w zależności od ustawień języka, działa zgodnie z oczekiwaniami na moim english-usserwerze). Zauważ, że jest to tylko sprawdzenie poprawności, jeśli spróbujemy go bezpośrednio 250101parsować, zostanie zmapowany jako 2025-01-01, a nie 1925-01-01.
  2. Jeśli ciąg zostanie poprawnie przeanalizowany jako data, zaznacz opcję 19z przodu (zamiast zmieniać ustawienie wartości odcięcia roku na poziomie serwera). Ostateczna konwersja daty nastąpi na końcu.
  3. Jeśli ciąg nie jest analizowany jako data, zamień go na liczbę. Najkrótsza matematyka, jaką udało mi się znaleźć, pozwoliła 8700*PP + QQRR - 1300uniknąć (bardzo długiej) SUBSTRING()funkcji SQL . Ta matematyka sprawdza dostarczone próbki, jestem prawie pewien, że ma rację.
  4. Użyj, DATEADDaby dodać tyle dni, do 2000-01-01których można skrócić 2000.
  5. Weź ten końcowy wynik (albo ciąg z kroku 2, albo DATETIME z kroku 4), i CONVERT()to na czysto DATE.

Myślałem, że w pewnym momencie znalazłem problematyczny termin: 000229. Jest to jedyna data, która analizuje inaczej dla 19xx i 20xx (od 2000 roku był rok przestępny, ale 1900 nie był, z powodu dziwnych wyjątków z roku przestępnego ). Z tego powodu jednak 000229nie jest to nawet prawidłowy wkład (ponieważ, jak wspomniano, 1900 nie był rokiem przestępnym), więc nie trzeba go uwzględniać.

BradC
źródło
Dobry towar. Szkoda, ISDATEże nie zwraca wartości logicznej, ani że liczb całkowitych nie można niejawnie przekonwertować na wartość logiczną, w IIFprzeciwnym razie można zapisać dwa bajty.
@ YiminRong Tak, niejawne rzutowanie w SQL jest bardzo trudne i działa inaczej w niektórych funkcjach, które w przeciwnym razie są bardzo podobne. Mam szczęście, że nie musiałem jawnie rzutować wyników LEFT()i RIGHT()funkcji na liczby całkowite przed ich pomnożeniem, co naprawdę pomieszałoby moją liczbę bajtów
BradC
1
Myślę, że możesz usunąć dodatkową postać, zastępując -1300,'2000'-935,'1999'.
Razvan Socol
Fajny pomysł, @RazvanSocol. Próbowałem wrócić do kolejnych wielokrotności 365 dni, ale niestety nie mogłem znaleźć niczego krótszego.
BradC
5

R , 126 bajtów

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

Wypróbuj online!

  • -5 bajtów dzięki sugestii @Giuseppe do wprowadzenia danych liczbowych zamiast łańcucha
digEmAll
źródło
4
Nie odpowiada na dane wejściowe reprezentujące daty sprzed pierwszego stycznia 1969 r. (Np. 000101Lub 681231)
Jonathan Allan
2
@JonathanAllan: dobrze zauważony, dzięki. Teraz należy to naprawić (niestety wymaga 5 dodatkowych bajtów ...)
digEmAll
4

JavaScript (SpiderMonkey) , 103 bajty

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]

Wypróbuj online!


.toJSONzawiedzie w strefie czasowej UTC + X. Ten kod działa, ale dłużej (+ 11 bajtów):

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
źródło
Można zapisać 13 bajtów z .toJSON().
Arnauld,
Możesz zaoszczędzić jeszcze 9 bajtów , dzieląc łańcuch wejściowy na trzy podciągi 2-znakowe.
Arnauld,
@Arnauld Początkowo próbowałem tego na moim komputerze. Ale to nie działa, ponieważ moja strefa czasowa to UTC + 8. Ale to przynajmniej działa na TIO.
tsh
Skoro definiujemy języki według ich implementacji (tutaj „Node.js działający na TIO”), czy to naprawdę jest nieprawidłowe?
Arnauld,
W przypadku wersji kuloodpornej możesz to zrobić w ten sposób, aby zaoszczędzić 1 bajt.
Arnauld,
2

Python 2 , 159 bajtów

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]

Wypróbuj online!

Chas Brown
źródło
Fajna sztuczka przy użyciu ... and ... or ...zamiast ... if ... else ....
Alexander Revo,
2

ABAP, 173 171 bajtów

Zaoszczędzono 2 bajty dzięki dalszej optymalizacji danych wyjściowych

Według legend klient SAP na początku XXI wieku powiedział kiedyś:

Po wojnie nuklearnej o całkowitym zniszczeniu pozostanie tylko SAPGUI.

On miał rację. Dzisiaj, w 2980 roku, nie ma już C ++, nie ma już COBOL. Po wojnie wszyscy musieli przepisać swój kod w SAP ABAP. Aby zapewnić zgodność wsteczną z resztkami programów COBOL 2800, nasi naukowcy przebudowali go jako podprogram w 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.

Może być wywołany przez taki program:

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

Objaśnienie mojego kodu:

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.

Typ daty ABAP ma właściwość nieparzystą, którą należy sformatować jako DDMMRRRRR WRITE- może być zależna nawet od ustawień regionalnych - mimo że format wewnętrzny to RRRRMMDD. Ale kiedy używamy selektora podciągów, taki jak d(4)on, wybiera pierwsze 4 znaki wewnętrznego formatu , co daje nam RRRR.

Aktualizacja : Formatowanie wyjściowe w wyjaśnieniu jest teraz nieaktualne, zoptymalizowałem go o 2 bajty w wersji golfowej:

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
źródło
Doskonale, podoba mi się to. Teraz potrzebujemy tylko wersji MUMPSi przetrwamy wszystko!
1
@YiminRong Thanks! Twoje pytanie oparte na języku COBOL w zasadzie dotyczyło czegoś takiego, nie miałem wyboru.
Maz
1

Kotlin , 222 bajty

Zakodowane na stałe nazwy pól kalendarza pozwalają zaoszczędzić 49 bajtów.

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

Wypróbuj online!

JohnWells
źródło