Batir las expresiones regulares puras al validar las fechas ISO 8601

12

En ValiDate ISO 8601 de RX , el desafío consistía en utilizar solo expresiones regulares estándar para validar formatos y valores de fecha estándar (el primero es un trabajo común para RX, el último era inusual). La respuesta ganadora usó 778 bytes. Este desafío es superar eso usando cualquier idioma de su elección, pero sin funciones especiales de fecha o clases .

Desafío

Encuentra el código más corto que

  1. valida todas las fechas posibles en el calendario gregoriano proleptico (que también se aplica a todas las fechas antes de su primera adopción en 1582),
  2. no coincide con ninguna fecha inválida y
  3. no utiliza funciones, métodos, clases, módulos o similares predefinidos para manejar fechas (y horas), es decir, se basa en operaciones de cadena y numéricas.

Salida

La salida es veraz o falsa. No es necesario generar o convertir la fecha.

Entrada

La entrada es una sola cadena en cualquiera de los 3 formatos de fecha ISO 8601 expandidos , sin tiempos.

Los dos primeros son ±YYYY-MM-DD(año, mes, día) y ±YYYY-DDD(año, día). Ambos necesitan una carcasa especial para el día bisiesto. Son ingenuamente emparejados por separado por estos RX extendidos:

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

El tercer formato de entrada es ±YYYY-wWW-D(año, semana, día). Es el complicado debido al complejo patrón de semana bisiesto.

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

Condiciones

Un año bisiesto en el calendario gregoriano proleptico contiene el dia bisiesto …-02-29 y por lo tanto es de 366 dias, por lo tanto …-366existe. Esto sucede en cualquier año cuyo número ordinal (posiblemente negativo) es divisible por 4, pero no por 100 a menos que también sea divisible por 400. El año cero existe en este calendario y es un año bisiesto.

Un año largo en el calendario de la semana ISO contiene una 53ª semana …-W53-…, que se podría llamar una " semana bisiesta ". Esto sucede en todos los años donde el 1 de enero es jueves y, además, en todos los años bisiestos donde es miércoles. 0001-01-01y 2001-01-01son los lunes Resulta que ocurre cada 5 o 6 años por lo general, en un patrón aparentemente irregular.

Un año tiene al menos 4 dígitos. Los años con más de 10 dígitos no tienen que ser compatibles, porque eso es lo suficientemente cercano a la edad del universo (aproximadamente 14 mil millones de años). El signo más inicial es opcional, aunque el estándar real sugiere que debería requerirse durante años con más de 4 dígitos.

No se deben aceptar fechas parciales o truncadas, es decir, con una precisión inferior al día. -Se requieren guiones de separación en todos los casos. (Estas condiciones previas hacen posible que el liderazgo +sea ​​siempre opcional).

Reglas

Este es el código de golf. El código más corto en bytes gana. La respuesta anterior gana un empate.

Casos de prueba

Pruebas válidas

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
+2016-02-29
200000-02-29
-200000-02-29
+200000-02-29
2016-366
2000-366
0000-366
-2000-366
-2016-366
+2016-366
2015-081
2015-W33-1
2015-W53-7
+2015-W53-7
+2015-W33-1
-2015-W33-1
 2015-08-10 

El último es opcionalmente válido, es decir, se pueden recortar los espacios iniciales y finales en las cadenas de entrada.

Formatos inválidos

-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         # though a valid ISO format we require separators and day-precision
2015-08         # a valid ISO format, but we require day-precision
201508          # a valid but ambiguous ISO format
2015            # a valid ISO format, but we require day-precision

Fechas inválidas

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
Crissov
fuente
2
fuera de tema, pero que puede resultar útil - desbordamiento de pila: stackoverflow.com/questions/28020805/... (si no debería publicar que, dime)
Daniele D
¿Qué pasa si el programador es un YEC (Creacionista de la Tierra Joven)?
Leaky Nun
-0000-08-10¿ Sobre qué es exactamente la decisión arbitraria? ¿No permite que el año sea negativo 0?
edc65
@ edc65 Sí, +0000-08-10y 0000-08-10debería usarse en su lugar. Sin embargo, tenga en cuenta que la respuesta aceptada en la variante de expresión regular de este desafío falla en este caso de prueba en particular, por lo que no es realmente una condición fallida (es decir, un deber , no un deber ).
Crissov
@KennyLau Entonces el programador está equivocado .
Arcturus

Respuestas:

2

JavaScript (ES6), 236

236 bytes que permiten 0 años negativos ( -0000). Devuelve verdadero o falso

s=>!!([,y,w,d]=s.match(/^([+-]?\d{4,})(-W?\d\d)?(-\d{1,3})$/)||[],n=y%100==0&y%400!=0|y%4!=0,l=((l=y-1)+8-~(l/4)+~(l/100)-~(l/400))%7,l=l==5|l==4&!n,+d&&(-w?d>`0${2+n}0101001010`[~w]-32:w?(w=w.slice(2),w>0&w<(53+l)&d>-8):d[3]&&d>n-367))

Agregar el cheque para 0 negativo corta 2 bytes pero agrega 13. Tenga en cuenta que en javascript el valor numérico -0existe, y es especial para ser igual a 0, pero 1/-0es -Infinity. Esta versión devuelve 0 o 1

s=>([,y,w,d]=s.match(/^([+-]?\d{4,})(-W?\d\d)?(-\d{1,3})$/)||[],n=y%100==0&y%400!=0|y%4!=0,l=((l=y-1)+8-~(l/4)+~(l/100)-~(l/400))%7,l=l==5|l==4&!n,+d&&(-w?d>`0${2+n}0101001010`[~w]-32:w?(w=w.slice(2),w>0&w<(53+l)&d>-8):d[3]&&d>n-367))&!(!+y&1/y<0)

Prueba

Check=
  s=>!! // to obtain a true/false 
  (
    // parse year in y, middle part in w, day in d
    // day will be negative with 1 or 3 numeric digits and could be 0
    // week will be '-W' + 2 digits
    // month will be negative with2 digits and could be 0
    // if the date is in format yyyy-ddd, then w is empty
    [,y,w,d] = s.match(/^([+-]?\d{4,})(-W?\d\d)?(-\d{1,3})$/) || [],
    n = y%100==0 & y%400!=0 | y%4!=0, // n: not leap year
    l = ((l=y-1) + 8 -~(l/4) +~(l/100) -~(l/400)) % 7, 
    l = l==5| l==4 & !n, // l: long year (see http://mathforum.org/library/drmath/view/55837.html)
    +d && ( // if d is not empty and not 0
     -w // if w is numeric and not 0, then it's the month (negative)
     ? d > `0${2+n}0101001010`[~w] - 32 // check month length (for leap year too)
      : w // if w is not empty, then it's the week ('-Wnn')
        ? ( w = w.slice(2), w > 0 & w < (53+l) & d >- 8) // check long year too
        : d[3] && d > n-367 // else d is the prog day, has to be 3 digits and < 367 o 366
    )
  )

console.log=x=>O.textContent += x +'\n'

OK=['1900-01-01','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','+2016-02-29','200000-02-29'
,'-200000-02-29','+200000-02-29','2016-366','2000-366','0000-366'
,'-2000-366','-2016-366','+2016-366','2015-081','2015-W33-1'
,'2015-W53-7','+2015-W53-7','+2015-W33-1','-2015-W33-1','2015-08-10']

KO=['-0000-08-10','15-08-10','2015-8-10','015-08-10','20150810','2015 08 10'
,'2015.08.10','2015–08–10','2015-0810','201508-10','2015 - 08 - 10','2015-w33-1'
,'2015W33-1','2015W331','2015-W331','2015-W33','2015W33','2015-08','201508'
,'2015','2015-00-10','2015-13-10','2015-08-00','2015-08-32','2015-04-31'
,'2015-02-30','2015-02-29','2100-02-29','-2100-02-29','2015-000'
,'2015-366','2016-367','2100-366','-2100-366','2015-W00-1'
,'2015-W54-1','2016-W53-1','2015-W33-0','2015-W33-8']

console.log('Valid')
OK.forEach(x=>console.log(Check(x)+' '+x))
console.log('Not valid')
KO.forEach(x=>console.log(Check(x)+' '+x))
<pre id=O></pre>

edc65
fuente