ValiDate ISO 8601 di RX


16

Sfida

Trova il regex più breve che

  1. convalida, ad esempio le corrispondenze, tutte le possibili date nel calendario prololico gregoriano (che si applica anche a tutte le date prima della sua prima adozione nel 1582) e
  2. non corrisponde a nessuna data non valida.

Produzione

L'output è quindi verità o falsità.

Ingresso

L'input è in uno dei 3 formati di data ISO 8601 espansi - nessuna volta.

I primi due sono ±YYYY-MM-DD(anno, mese, giorno) e ±YYYY-DDD(anno, giorno). Entrambi hanno bisogno di un involucro speciale per il giorno bisestile. Sono ingenuamente abbinati separatamente da questi RX estesi:

(?<year>[+-]?\d{4,})-(?<month>\d\d)-(?<day>\d\d)
(?<year>[+-]?\d{4,})-(?<doy>\d{3})

Il terzo formato di input è ±YYYY-wWW-D(anno, settimana, giorno). È complicato a causa del complesso schema delle settimane bisestili.

(?<year>-?\d{4,})-W(?<week>\d\d)-(?<dow>\d)

Un controllo di validità di base, ma insufficiente per tutte e tre le combinazioni sarebbe simile a questo:

[+-]?\d{4,}-((0\d|1[0-2])-([0-2]\d|3[01]) ↩
            |([0-2]\d\d|3[0-5]\d|36[0-6]) ↩
            |(W([0-4]\d|5[0-3])-[1-7]))

condizioni

Un anno bisestile nel calendario prololico gregoriano contiene il giorno bisestile …-02-29 e quindi dura 366 giorni, quindi …-366esiste. Ciò accade in qualsiasi anno il cui numero ordinale è divisibile per 4, ma non per 100 a meno che non sia anche divisibile per 400. L' anno zero esiste in questo calendario ed è un anno bisestile.

Un lungo anno nel calendario delle settimane ISO contiene una 53a settimana, che si potrebbe definire una " settimana bisestile ". Ciò accade in tutti gli anni in cui il 1 ° gennaio è un giovedì e in tutti gli anni bisestili in cui è un mercoledì. Si presenta di solito ogni 5 o 6 anni, in uno schema apparentemente irregolare.

Un anno ha almeno 4 cifre. Non è necessario sostenere anni con più di 10 cifre, perché è abbastanza vicino all'età dell'universo (circa 14 miliardi di anni). Il segno più iniziale è facoltativo, sebbene lo standard attuale suggerisca che dovrebbe essere richiesto per anni con più di 4 cifre.

Non è possibile accettare date parziali o troncate, ovvero con precisione inferiore a quella giornaliera.

Le parti della notazione della data, ad esempio il mese, non devono essere abbinate da un gruppo a cui potrebbe essere fatto riferimento.

Regole

Questo è code-golf. Vince la regex più corta senza codice eseguito. Aggiornamento: è possibile utilizzare funzioni come ricorsione e gruppi bilanciati, ma verrà multato di un fattore 10, con il quale il numero di personaggi viene quindi moltiplicato! Questo è ora diverso dalle regole del golf Hard code: Regex per divisibilità per 7 . La risposta precedente vince un pareggio.

Casi test

Test validi

2015-08-10
2015-10-08
12015-08-10
-2015-08-10
+2015-08-10
0015-08-10
1582-10-10
2015-02-28
2016-02-29
2000-02-29
0000-02-29
-2000-02-29
-2016-02-29
200000-02-29
2016-366
2000-366
0000-366
-2016-366
-2000-366
2015-081
2015-W33-1
2015-W53-7
 2015-08-10 

L'ultimo è facoltativamente valido, vale a dire che possono essere tagliati spazi iniziali e finali nelle stringhe di input.

Formati non validi

-0000-08-10     # that's an arbitrary decision
15-08-10        # year is at least 4 digits long
2015-8-10       # month (and day) is exactly two digits long, i.e. leading zero is required
015-08-10       # year is at least 4 digits long
20150810        # though a valid ISO format, we require separators; could also be interpreted as a 8-digit year
2015 08 10      # separator must be hyphen-minus
2015.08.10      # separator must be hyphen-minus
2015–08–10      # separator must be hyphen-minus
2015-0810
201508-10       # could be October in the year 201508
2015 - 08 - 10  # no internal spaces allowed
2015-w33-1      # letter ‘W’ must be uppercase
2015W33-1       # it would be unambiguous to omit the separator in front of a letter, but not in the standard
2015W331        # though a valid ISO format we require separators
2015-W331
2015-W33        # a valid ISO date, but we require day-precision
2015W33

Date non valide

2015        # a valid ISO format, but we require day-precision
2015-08     # a valid ISO format, but we require day-precision
2015-00-10  # month range is 1–12
2015-13-10  # month range is 1–12
2015-08-00  # day range is 1–28 through 31
2015-08-32  # max. day range is 1–31
2015-04-31  # day range for April is 1–30
2015-02-30  # day range for February is 1–28 or 29
2015-02-29  # day range for common February is 1–28
2100-02-29  # most century years are non-leap
-2100-02-29 # most century years are non-leap
2015-000    # day range is 1–365 or 366
2015-366    # day range is 1–365 in common years
2016-367    # day range is 1–366 in leap years
2100-366    # most century years are non-leap
-2100-366   # most century years are non-leap
2015-W00-1  # week range is 1–52 or 53
2015-W54-1  # week range is 1–53 in long years
2016-W53-1  # week range is 1–52 in short years
2015-W33-0  # day range is 1–7
2015-W33-8  # day range is 1–7

2
Questa domanda non è ben definita perché il linguaggio regex non è specificato.
orlp,

1
@orlp Se non specificato, la scelta non è limitata. Ho scritto "regex" o "RX" apposta, quindi si potrebbero usare dialetti che consentono la ricorsione, ecc. (Ad esempio CFG, non RG).
Crissov,

Ti consiglio vivamente di limitare il linguaggio regex, perché sarà molto acido per un concorrente lavorare per ore su una soluzione solo per essere banalmente battuto da un linguaggio che è fondamentalmente più potente. Se dovessi limitare la lingua all'attuale definizione CS di espressioni regolari (come DFA), il problema diventa un'interessante risposta di ottimizzazione.
orlp,

Convalidare le date ISO-8601 usando espressioni regolari è qualcosa che ho dovuto fare per lavoro. Ma d'accordo con orlp, penso che qui sia necessaria una lingua.
Alex A.

1
Regex eredita da Method in Perl 6, quindi è esso stesso una forma di codice eseguibile.
Brad Gilbert b2gills

Risposte:


4

PCRE (anche Perl), 778 byte

/^([+-]?\d*((([02468][048]|[13579][26]|\d\d(?!00))([02468][048]|[13579][26]))|\d{4}(?!-02-29|-366))-((?!02-3|(0[469]|11)-31|000)((0[1-9]|1[012])-(0[1-9]|[12]\d|30|31)|([012]\d\d|3([0-5]\d|6[0-6])))|(W(?!00)([0-4]\d|51|52)-[1-7]))|((\+?\d*([02468][048]|[13579][26])|-\d*([02468][159]|[13579][37]))(04|09|15|20|26|32|37|43|48|54|60|65|71|76|82|88|93|99)|(\+?\d*([02468][159]|[13579][37])|-\d*([02468][26]|[13579][048]))(05|11|16|22|28|33|39|44|50|56|61|67|72|78|84|89|95)|(\+?\d*([02468][26]|[13579][048])|-\d*([02468][37]|[13579][159]))(01|07|12|18|24|29|35|40|46|52|57|63|68|74|80|85|91|96)|\+?\d*(([02468][37]|[13579][159])(03|14|20|25|31|36|42|53|59|64|70|76|81|87|92|[049]8))|-\d*(([02468][048]|[13579][26])([059]2|08|13|19|24|30|36|41|47|58|64|69|75|80|86|97)))-W53-[1-7])$/

Ho incluso i delimitatori nel conteggio dei byte per dimostrare che non si basa su alcun flag.

Essa non corrisponde date valide all'interno di altre stringhe, come ad esempio 1234-56-89 2016-02-29 9876-54-32. Il regex è più corto non controllando un massimo di 10 cifre per l'anno.

Esteso con commenti:

/^  # Start of pattern (no leading space)
  (
    # YEAR
    # Optional sign and digits if more than 4 in year
    [+-]?\d*(
      # Years 00??, 04??, 08?? ... 92??, 96?? OR dd not followed by 00
      # followed by 00, 04, 08 ... 92, 96 OR
      (([02468][048]|[13579][26]|\d\d(?!00))([02468][048]|[13579][26])) |
      # any year not followed by 29 February or day 366
      \d{4}(?!-02-29|-366)
    # dash
    ) -
    # MONTH AND DAY, or DAY OF YEAR, or WEEK OF YEAR AND DAY if less than 53 weeks
    (
      # Not (30 or 31 February OR 31 April, June, September or December OR day 0)
      (?!02-3|(0[469]|11)-31|000)
      (
        # Month         dash         day         OR
        (0[1-9]|1[012]) - (0[1-9]|[12]\d|30|31) |
        # 001-299 OR 300-359 OR 360-366
        ([012]\d\d | 3([0-5]\d | 6[0-6]))
      # OR
      ) |
      (
        # W    01-52    dash    1-7
        W(?!00)([0-4]\d|51|52)-[1-7]
      )
    # OR
    ) |
    # WEEK OF YEAR AND DAY only if week is 53
    (
      # Optional plus and extra year digits
      \+?\d*(
        # Years +0303 - +9998
        ([02468][37]|[13579][159])(03|14|20|25|31|36|42|53|59|64|70|76|81|87|92|[049]8)
      ) |
      # Minus and extra year digits
      -\d*(
        # Years -0002 - -9697
        ([02468][048]|[13579][26])([059]2|08|13|19|24|30|36|41|47|58|64|69|75|80|86|97)
      ) |
      # Years +0004 - +9699, -0104 - -9799
      (\+?\d*([02468][048]|[13579][26])|-\d*([02468][159]|[13579][37]))
          (04|09|15|20|26|32|37|43|48|54|60|65|71|76|82|88|93|99) |
      # Years +0105 - +9795, -0205 - -9895
      (\+?\d*([02468][159]|[13579][37])|-\d*([02468][26]|[13579][048]))
          (05|11|16|22|28|33|39|44|50|56|61|67|72|78|84|89|95) |
      # Years +0201 - +9896, -0301 - -9996
      (\+?\d*([02468][26]|[13579][048])|-\d*([02468][37]|[13579][159]))
          (01|07|12|18|24|29|35|40|46|52|57|63|68|74|80|85|91|96)
    # dash W 53 dash 1-7
    )-W53-[1-7]
  # End of pattern (no trailing space)
  )$/x

Non ho ancora controllato tutto, ma sembra che tu guadagni il maggior numero di byte dalle (?!…)espressioni rispetto alla mia soluzione.
Crissov

1
@Crissov Le (?!…)espressioni salvano solo pochi byte ciascuna. Ho ridotto molti byte combinando tre dei modelli dell'anno positivo / negativo della settimana dell'anno / giorno della settimana in uno ciascuno. Gli ultimi non corrispondono l'uno all'altro. Quindi ho ottenuto 8 sottotitoli lunghi fino a 5. Inoltre, poiché |20|25|è la stessa lunghezza |2[05]|dell'opzione più leggibile.
CJ Dennis,

Questa espressione corrisponde al test case -0000-08-10 e non corrisponde ␠2015-08-10␠allo spazio bianco iniziale e finale, ma poiché entrambe sono state decisioni arbitrarie o funzionalità opzionali, lo lascerò scorrere.
Crissov,

Penso che questa soluzione abbia un bug per le date all'interno di W50.
Crissov,

W(?!00)([0-4]\d|51|52)-[1-7]deve essere qualcosa di equivalente a W(?!00)([0-4]\d|5[0-2])-[1-7]. Questo aggiunge un carattere alla lunghezza. 779
Crissov,

9

PCRE: 603 940 947 949 956 byte

^\s*[+-]?(\d{4,10}-((00[1-9]|0[1-9]\d|[12]\d\d|3[0-5]\d|36[0-5])|(0[1-9]|1[0-2])-(0[1-9]|1\d|2[0-8])|(0[13-9]|1[0-2])-(29|30)|(0[13578]|1[02])-31|W(0[1-9]|[1-4]\d|5[0-2])-[1-7]))|((\d{2,8}([13579][26]|[2468][048]|0[48])|(\d{0,6}([13579][26]|[02468][048])00))-(366|02-29))|(\+?\d{0,6}(([02468][048]|[13579][26])([26]0|71|[38]2|[49]3|[05]4|15|[27]6|37|[48]8|[09]9)|([02468][159]|[13579][37])(50|[16]1|[27]2|33|[48]4|[09]5|[15]6|67|[27]8|[38]9)|([02468][26]|[13579][048])([48]0|[09]1|[15]2|63|[27]4|[38]5|[49]6|[05]7|[16]8|29)|([02468][37]|[13579][159])([27]0|[38]1|[49]2|[05]3|[16]4|25|[37]6|87|[049]8|[5]9))|-\d{0,6}(([02468][048]|[13579][26])(0[28]|1[39]|24|3[06]|4[17]|5[28]|6[49]|75|8[06]|9[27])|([02468][159]|[13579][37])(0[49]|15|2[06]|3[27]|4[38]|54|6[05]|7[16]|8[28]|9[39])|([02468][26]|[13579][048])(0[51]|16|2[28]|3[39]|44|5[06]|6[17]|7[28]|8[49]|95)|([02468][37]|[13579][159])(0[17]|1[28]|2[49]|35|4[06]|5[27]|6[38]|74|8[05]|9[16])))-W53-[1-7]\s*$

Nota: alcune coppie di parentesi potrebbero essere eliminate.

Divisibilità per 4

I multipli di 4 si ripetono in un modello semplice:

  • 00, 04, 08, 12, 16,
    20, 24, 28, 32, 36,
    40, 44, 48, 52, 56,
    60, 64, 68, 72, 76,
    80, 84, 88, 92, 96, ...

Questo, o l'inverso, potrebbe essere abbinato a un'espressione regolare altrettanto semplice per tutti i numeri a due cifre con zero iniziale:

(?<divisible-by-four>[13579][26]|[02468][048])
(?<not-divisible-by-four>[13579][048]|[02468][26]|\d[13579])

Potrebbe salvare alcuni byte se ci fossero classi di caratteri per cifre pari e dispari (come \oe \e), ma non ci sono per quanto ne so.

Anni

Quell'espressione sarebbe sufficiente per il calendario giuliano, ma la rilevazione dell'anno bisestile gregoriano ha bisogno di trascinarsi in casi speciali 00con divisibilità del secolo per 4:

(?<leap-year>[+-]?(\d{2,8}([13579][26]|[2468][048]|0[48])|(\d{0,6}([13579][26]|[02468][048])00))
(?<year>[+-]?\d{4,10})

Ciò richiederebbe alcune modifiche per mettere fuorilegge -0000-…(insieme a -00000-…ecc.) O per imporre il segno più per numeri dell'anno positivi con più di 4 cifre. Quest'ultimo sarebbe piuttosto semplice, ma non è richiesto:

(?<leap-year>([+-]?(\d\d([13579][26]|[2468][048]|0[48])|(([13579][26]|[02468][048])00)))|([+-](\d{3,8}([13579][26]|[2468][048]|0[48])|(\d{1,6}([13579][26]|[02468][048])00))))
(?<year>([+-]?\d{4})|([+-]\d{5,10}))

Giorno dell'anno

Le date ordinali a tre cifre sono piuttosto semplici, non ci resta che limitarci -366agli anni bisestili (e non consentire -000).

(?<ordinal-day>-(00[1-9]|0[1-9]\d|[12]\d\d|3[0-5]\d|36[0-5]))
(?<ordinal-leap-day>-366)

Giorno del mese dell'anno

I sette mesi con 31 giorni sono 01gennaio, 03marzo, 05maggio, 07luglio, 08agosto, 10ottobre e 12dicembre. Solo quattro mesi hanno esattamente 30 giorni, 04aprile, 06giugno, 09settembre e 11novembre. Infine, 02febbraio ha 28 giorni negli anni comuni e 29 negli anni bisestili. Siamo in grado di costruire prima un'espressione regolare per i giorni sempre validi 01attraverso 28e quindi aggiungere casi particolari.

(?<month-day>-(0[1-9]|1[0-2])-(0[1-9]|1\d|2[0-8]))
(?<short-month-day>-(0[13-9]|1[0-2])-(29|30))
(?<long-month-day>-(0[13578]|1[02])-31)
(?<month-leap-day>-02-29)

Né il mese né il giorno devono essere 00non coperti da una versione precedente.

Giorno della settimana dell'anno

Tutti gli anni includono 52 settimane

(?<week-day>-W(0[1-9]|[1-4]\d|5[0-2])-[1-7])

Lunghi anni che includono la-W53 ripetizione in un ciclo di 400 anni, ad esempio aggiungere 2000 per il ciclo corrente e trovare l'anno corrente nella terza voce:

  • 004, 009, 015, 020, 026, 032, 037, 043, 048, 054, 060, 065, 071, 076, 082, 088, 093, 099,
  • 105, 111, 116, 122, 128, 133, 139, 144, 150, 156, 161, 167, 172, 178, 184, 189, 195,
  • 201, 207, 212, 218, 224, 229, 235, 240, 246, 252, 257, 263, 268, 274, 280, 285, 291, 296,
  • 303, 308, 314, 320, 325, 331, 336, 342, 348, 353, 359, 364, 370, 376, 381, 387, 392, 398.

Ognuno dei quattro secoli ha un modello unico. Probabilmente non c'è molto spazio per l'ottimizzazione.

  1. 04|09|15|20|26|32|37|43|48|54|60|65|71|76|82|88|93|99
  2. 05|11|16|22|28|33|39|44|50|56|61|67|72|78|84|89|95
  3. 01|07|12|18|24|29|35|40|46|52|57|63|68|74|80|85|91|96
  4. 03|08|14|20|25|31|36|42|48|53|59|64|70|76|81|87|92|98

Possiamo raggruppare in base a una delle due cifre per scoprire che possiamo salvare circa due byte:

  • Raggruppati per 1a cifra.
    1. 0[49]|15|2[06]|3[27]|4[38]|54|6[05]|7[16]|8[28]|9[39]
    2. 05|1[16]|2[28]|3[39]|44|5[06]|6[17]|7[28]|8[49]|95
    3. 0[17]|1[28]|2[49]|35|4[06]|5[27]|6[38]|74|8[05]|9[16]
    4. 0[38]|14|2[05]|3[16]|4[28]|5[39]|64|7[06]|8[17]|9[28]
  • Raggruppati per la seconda cifra.
    1. [26]0|71|[38]2|[49]3|[05]4|15|[27]6|37|[48]8|[09]9
    2. 50|[16]1|[27]2|33|[48]4|[09]5|[15]6|67|[27]8|[38]9
    3. [48]0|[09]1|[15]2|63|[27]4|[38]5|[49]6|[05]7|[16]8|29
    4. [27]0|[38]1|[49]2|[05]3|[16]4|25|[37]6|87|[049]8|[5]9

Il numero del secolo è facilmente riscontrabile da una variazione dell'espressione di divisibilità.

  • 1 ° secolo: [02468][048]|[13579][26]
  • II secolo: [02468][159]|[13579][37]
  • 3 ° secolo: [02468][26]|[13579][048]
  • 4 ° secolo: [02468][37]|[13579][159]

Finora, questo funziona solo per anni positivi, incluso l'anno zero. Per anni negativi, dobbiamo sottrarre i valori dall'elenco sopra da 400 e fare di nuovo il resto, perché il modello non è simmetrico.

  1. 02|08|13|19|24|30|36|41|47|52|58|64|69|75|80|86|92|97
  2. 04|09|15|20|26|32|37|43|48|54|60|65|71|76|82|88|93|99
  3. 05|11|16|22|28|33|39|44|50|56|61|67|72|78|84|89|95
  4. 01|07|12|18|24|29|35|40|46|52|57|63|68|74|80|85|91|96

o

  1. 0[28]|1[39]|24|3[06]|4[17]|5[28]|6[49]|75|8[06]|9[27]
  2. 0[49]|15|2[06]|3[27]|4[38]|54|6[05]|7[16]|8[28]|9[39]
  3. 0[51]|16|2[28]|3[39]|44|5[06]|6[17]|7[28]|8[49]|95
  4. 0[17]|1[28]|2[49]|35|4[06]|5[27]|6[38]|74|8[05]|9[16]

Mettere tutto insieme

Ogni anno

[+-]?\d{4,10}-((00[1-9]|0[1-9]\d|[12]\d\d|3[0-5]\d|36[0-5])|(0[1-9]|1[0-2])-(0[1-9]|1\d|2[0-8])|(0[13-9]|1[0-2])-(29|30)|(0[13578]|1[02])-31|W(0[1-9]|[1-4]\d|5[0-2])-[1-7])

Aggiunte anno bisestile

[+-]?(\d{2,8}([13579][26]|[2468][048]|0[48])|(\d{0,6}([13579][26]|[02468][048])00))-(366|02-29)

Aggiunte per l'anno bisestile

+?\d{0,6}(([02468][048]|[13579][26])([26]0|71|[38]2|[49]3|[05]4|15|[27]6|37|[48]8|[09]9)|([02468][159]|[13579][37])(50|[16]1|[27]2|33|[48]4|[09]5|[15]6|67|[27]8|[38]9)|([02468][26]|[13579][048])([48]0|[09]1|[15]2|63|[27]4|[38]5|[49]6|[05]7|[16]8|29)|([02468][37]|[13579][159])([27]0|[38]1|[49]2|[05]3|[16]4|25|[37]6|87|[049]8|[5]9))-W53-[1-7]
-\d{0,6}(([02468][048]|[13579][26])(0[28]|1[39]|24|3[06]|4[17]|5[28]|6[49]|75|8[06]|9[27])|([02468][159]|[13579][37])(0[49]|15|2[06]|3[27]|4[38]|54|6[05]|7[16]|8[28]|9[39])|([02468][26]|[13579][048])(0[51]|16|2[28]|3[39]|44|5[06]|6[17]|7[28]|8[49]|95)|([02468][37]|[13579][159])(0[17]|1[28]|2[49]|35|4[06]|5[27]|6[38]|74|8[05]|9[16]))-W53-[1-7]

Il modello non è ancorato all'inizio e alla fine, quindi corrisponderà a date valide all'interno di una stringa altrimenti non valida.
CJ Dennis,

@CJDennis È vero, aggiungerò i due personaggi ora.
Crissov

Ho anche aggiunto spazi iniziali e finali opzionali \s*.
Crissov,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.