Cómo comprobar si una cadena es una fecha válida

114

Tengo una cadena: "31-02-2010"y quiero comprobar si es una fecha válida o no. ¿Cuál es la mejor manera de hacerlo?

Necesito un método que devuelva verdadero si la cadena es una fecha válida y falso si no lo es.

Salil
fuente
2
¿Por qué no hacer un menú desplegable de fecha_hora en lugar de incluir una cadena que debe validar?
corroído
requisito del cliente. Ya lo sugiero, pero no puedo hacer eso :)
Salil
Como regla general, ¿no se supone que debe realizar la validación de entrada en la interfaz de una aplicación?
Seanny123
Respuestas mucho mejores aquí: así es como se hace. stackoverflow.com/questions/597328/…
n13
3
Valide siempre en el backend, no importa qué tan bueno sea su front-end, ¡no confíe en él!
SparK

Respuestas:

112
require 'date'
begin
   Date.parse("31-02-2010")
rescue ArgumentError
   # handle invalid date
end
mpd
fuente
27
No es una característica de la clase Date, es un manejo de excepciones.
Pier-Olivier Thibault
3
Date.parse ('2012-08-13 == 00:00:00') # => Lunes, 13 de agosto de 2012
Bob
13
El uso de un rescate "atrapa todo" debe considerarse un anti-patrón. Puede ocultar otros errores que no esperamos y hacer que la depuración del código sea extremadamente difícil.
yagooar
8
Entonces ... si de hecho es un anti-patrón, ¿cómo respondería a la pregunta?
Matthew Brown
11
Lo siento, esta es una respuesta incorrecta. No verifica si una cadena es una fecha válida, simplemente verifica que Date.parse puede analizar la cadena. Date.parse parece ser muy indulgente cuando se trata de fechas, por ejemplo, analizará "FOOBAR_09_2010" como la fecha 2012-09-09.
n13
54

Aquí hay un trazador de líneas simple:

DateTime.parse date rescue nil

Probablemente no recomendaría hacer exactamente esto en todas las situaciones de la vida real, ya que obliga a la persona que llama a verificar nulo, por ejemplo. particularmente al formatear. Si devuelve una fecha predeterminada | error, puede ser más amigable.

robert_murray
fuente
8
DateTime.parse "123" rescate nulo. Esto devuelve una fecha real ... 3 de mayo de 2017
baash05
3
Date.strptime(date, '%d/%m/%Y') rescue nil
bonafernando
1
Se rescuedesaconseja el uso de inline .
Smoyth
52
d, m, y = date_string.split '-'
Date.valid_date? y.to_i, m.to_i, d.to_i
Sohan
fuente
Esto es bastante simple y bueno para aplicar un formato xx-xx-YYYY
n13
Si desea aplicar una opción estricta de cadena de fecha de formato único, esta es la mejor opción, ya que evita Excepciones y es determinista. Date.valid_date? es el método a utilizar al menos para Ruby 2. ruby-doc.org/stdlib-2.0.0/libdoc/date/rdoc/…
Ashley Raiteri
4
¿Qué versión de Ruby admite is_valid?? Probé 1.8, 2.0 y 2.1. Ninguno de ellos parece tener ese. Sin valid_date?embargo, todos parecen haberlo hecho .
Henrik N
29

Las fechas de análisis pueden encontrarse con algunos errores, especialmente cuando están en formato MM / DD / YYYY o DD / MM / YYYY, como las fechas cortas utilizadas en EE. UU. O Europa.

Date#parse intenta averiguar cuál usar, pero hay muchos días en un mes durante todo el año en los que la ambigüedad entre los formatos puede causar problemas de análisis.

Recomendaría averiguar cuál es el LOCAL del usuario, luego, basado en eso, sabrá cómo analizar inteligentemente usando Date.strptime. La mejor manera de saber dónde se encuentra un usuario es preguntarle durante el registro y luego proporcionar una configuración en sus preferencias para cambiarla. Suponiendo que pueda desenterrarlo mediante alguna heurística inteligente y no molestar al usuario por esa información, es propenso a fallar, así que pregunte.

Esta es una prueba con Date.parse. Estoy en los estados unidos:

>> Date.parse('01/31/2001')
ArgumentError: invalid date

>> Date.parse('31/01/2001') #=> #<Date: 2001-01-31 (4903881/2,0,2299161)>

El primero fue el formato correcto para los EE. UU.: Mm / dd / aaaa, pero a Date no le gustó. La segunda era correcta para Europa, pero si sus clientes residen predominantemente en EE. UU., Obtendrá muchas fechas mal analizadas.

Ruby Date.strptimese usa como:

>> Date.strptime('12/31/2001', '%m/%d/%Y') #=> #<Date: 2001-12-31 (4904549/2,0,2299161)>
el hombre de hojalata
fuente
Tu LOCALE parece estar apagado. Recibo esto >> Date.parse ('01 / 31/2001 ') => Mié, 31 de enero de 2001 >>?> Date.parse ('31 / 01/2001') ArgumentError: fecha inválida
hoyhoy
No estoy seguro de si estoy siendo tonto aquí, pero esto parece explicar solo cómo analizar una fecha, no cómo validarla. La respuesta aceptada usa la excepción ArgumentError, pero eso no parece una buena idea, por las razones que se indican en un comentario allí.
cesoide
Existe una situación conocida en la que no puede validar una fecha. Es donde los valores de día y mes son ambiguos, y debe conocer el formato de la cadena de fecha entrante. Y eso es lo que dice la respuesta. Si se ve así 01/01/2014, ¿cuál es el mes y cuál es el día? En los Estados Unidos el mes sería el primero, el resto del mundo sería el segundo.
The Tin Man
La ambigüedad que le impide validar la fecha solo lo hace en la medida en que también le impide analizar la fecha, pero ha hecho un buen intento de analizarla con información conocida. Sería bueno usar algo de lo que tiene para intentar validar lo mejor que podamos. ¿Puede pensar en cómo hacerlo sin usar las excepciones que crean Date.parse y Date.strptime?
cesoide
El análisis de fecha en formato "mm / dd / aaaa" o "dd / mm / aaaa" REQUIERE el conocimiento previo del formato de la fecha. Sin eso, no podemos determinar cuál es a menos que tengamos una muestra de una fuente / usuario, luego analicemos usando ambos formularios y luego veamos qué formulario generó la mayoría de las excepciones y usamos el otro. Esto se rompe cuando se analizan fechas de múltiples fuentes, especialmente cuando son globales o se ingresaron a mano. La única forma precisa de tratar las fechas es no permitir la entrada manual, obligar a los usuarios a usar selectores de fechas o solo permitir formatos de fecha ISO que no sean ambiguos.
el hombre de hojalata
26

Date.valid_date? *date_string.split('-').reverse.map(&:to_i)

Samer Buna
fuente
Excelente, gracias. Este parece estar disponible al menos en 1.8, 2.0 y 2.1. Tenga en cuenta que debe hacerlo require "date"primero o obtendrá un "método indefinido".
Henrik N
2
Este método puede generar una excepción 'ArgumentError: número incorrecto de argumentos' en lugar de devolver falso. Por ejemplo, si su cadena contiene barras inclinadas en lugar de guiones
vincentp
2
Tal vez podamos hacer algo como esto, para manejar una fecha con formato incorrecto:Date.valid_date? *Array.new(3).zip(date_string.split('-')).transpose.last.reverse.map(&:to_i)
vincentp
9

Me gustaría extender la Dateclase.

class Date
  def self.parsable?(string)
    begin
      parse(string)
      true
    rescue ArgumentError
      false
    end
  end
end

ejemplo

Date.parsable?("10-10-2010")
# => true
Date.parse("10-10-2010")
# => Sun, 10 Oct 2010
Date.parsable?("1")
# => false
Date.parse("1")
# ArgumentError: invalid date from (pry):106:in `parse'
arena de hierro
fuente
5

Otra forma de validar la fecha:

date_hash = Date._parse(date.to_s)
Date.valid_date?(date_hash[:year].to_i,
                 date_hash[:mon].to_i,
                 date_hash[:mday].to_i)
Slava Zharkov
fuente
3

Pruebe las expresiones regulares para todas las fechas:

/(\d{1,2}[-\/]\d{1,2}[-\/]\d{4})|(\d{4}[-\/]\d{1,2}[-\/]\d{1,2})/.match("31-02-2010")

Solo para su formato con ceros a la izquierda, último año y guiones:

/(\d{2}-\d{2}-\d{4})/.match("31-02-2010")

el [- /] significa - o /, la barra inclinada debe escaparse. Puede probar esto en http://gskinner.com/RegExr/

agregue las siguientes líneas, todas se resaltarán si usa la primera expresión regular, sin la primera y la última / (son para usar en código ruby).

2004-02-01
2004/02/01
01-02-2004
1-2-2004
2004-2-1
Señor Fox
fuente
2
Estas expresiones regulares solo coinciden con algunos formatos fijos, sin importar la semántica de una fecha. Su comparador permite fechas como 32-13-2010cuál es incorrecta.
yagooar
2
Lo suficientemente bueno si desea una estimación aproximada si cualquier cadena arbitraria se puede analizar como una fecha.
Neil Goodman
Donde "estimación aproximada" significa valores como '00/00/0000'y '99-99/9999'son posibles candidatos.
el hombre de hojalata
1
¡Un ejemplo brillante de cuando la expresión regular personalizada es una MALA IDEA!
Tom Lord
3

Una solución más estricta

Es más fácil verificar la exactitud de una fecha si especifica el formato de fecha que espera. Sin embargo, incluso entonces, Ruby es un poco demasiado tolerante para mi caso de uso:

Date.parse("Tue, 2017-01-17", "%a, %Y-%m-%d") # works
Date.parse("Wed, 2017-01-17", "%a, %Y-%m-%d") # works - !?

Claramente, al menos una de estas cadenas especifica el día de la semana incorrecto, pero Ruby felizmente lo ignora.

Aquí hay un método que no lo hace; valida que date.strftime(format)vuelve a convertir a la misma cadena de entrada con la que analizó de Date.strptimeacuerdo con format.

module StrictDateParsing
  # If given "Tue, 2017-01-17" and "%a, %Y-%m-%d", will return the parsed date.
  # If given "Wed, 2017-01-17" and "%a, %Y-%m-%d", will error because that's not
  # a Wednesday.
  def self.parse(input_string, format)
    date = Date.strptime(input_string, format)
    confirmation = date.strftime(format)
    if confirmation == input_string
      date
    else
      fail InvalidDate.new(
        "'#{input_string}' parsed as '#{format}' is inconsistent (eg, weekday doesn't match date)"
      )
    end
  end

  InvalidDate = Class.new(RuntimeError)
end
Nathan Long
fuente
Eso es exactamente lo que estaba buscando. ¡Gracias!
Lucas Caton
esto falla para los casos como "2019-1-1", que es una fecha válida, pero el tiempo de ejecución hace que sea 2019-01-01
saGii
@saGii que no se ajusta al formato especificado (iso8601 - ver Date.today().iso8601); %mse rellena con ceros. Si desea algo diferente, consulte ruby-doc.org/stdlib-2.4.0/libdoc/date/rdoc/…
Nathan Long
2

Publicar esto porque podría ser útil para alguien más adelante. No tengo idea de si esta es una "buena" forma de hacerlo o no, pero funciona para mí y es ampliable.

class String

  def is_date?
  temp = self.gsub(/[-.\/]/, '')
  ['%m%d%Y','%m%d%y','%M%D%Y','%M%D%y'].each do |f|
  begin
    return true if Date.strptime(temp, f)
      rescue
       #do nothing
    end
  end

  return false
 end
end

Este complemento para la clase String le permite especificar su lista de delimitadores en la línea 4 y luego su lista de formatos válidos en la línea 5. No es ciencia espacial, pero hace que sea realmente fácil de extender y le permite simplemente verificar una cadena como esta:

"test".is_date?
"10-12-2010".is_date?
params[:some_field].is_date?
etc.
Dave Sanders
fuente
0
require 'date'
#new_date and old_date should be String
# Note we need a ()
def time_between(new_date, old_date)
  new_date = (Date.parse new_date rescue nil)
  old_date = (Date.parse old_date rescue nil)
  return nil if new_date.nil? || old_date.nil?
  (new_date - old_date).to_i
end

puts time_between(1,2).nil?
#=> true
puts time_between(Time.now.to_s,Time.now.to_s).nil?
#=> false
lijadora
fuente
0

Puede probar lo siguiente, que es la forma más sencilla:

"31-02-2010".try(:to_date)

Pero debes manejar la excepción.

Bruno Silva
fuente
0

Date.parse no generó una excepción para estos ejemplos:

Date.parse("12!12*2012")
=> Thu, 12 Apr 2018

Date.parse("12!12&2012")
=> Thu, 12 Apr 2018

Prefiero esta solución:

Date.parse("12!12*2012".gsub(/[^\d,\.,\-]/, ''))
=> ArgumentError: invalid date

Date.parse("12-12-2012".gsub(/[^\d,\.,\-]/, ''))
=> Wed, 12 Dec 2012

Date.parse("12.12.2012".gsub(/[^\d,\.,\-]/, ''))
=> Wed, 12 Dec 2012
Igor Biryukov
fuente
-1

Método:

require 'date'
def is_date_valid?(d)
  Date.valid_date? *"#{Date.strptime(d,"%m/%d/%Y")}".split('-').map(&:to_i) rescue nil
end

Uso:

config[:dates].split(",").all? { |x| is_date_valid?(x)}

Esto devuelve verdadero o falso si config [: fechas] = "10/12 / 2012,05 / 09/1520"

Templum Iovis
fuente