Como verificar se uma string é uma data válida

114

Eu tenho uma string: "31-02-2010"e quero verificar se é ou não uma data válida. Qual é a melhor maneira de fazer isso?

Preciso de um método que retorne verdadeiro se a string for uma data válida e falso se não for.

Salil
fonte
2
por que não apenas fazer um drop down date_time em vez de pegar uma string que você tem que validar?
corroído em
exigência do cliente. já sugeri, mas não posso fazer isso :)
Salil
Como regra geral, você não deve fazer a validação de entrada no front end de um aplicativo?
Seanny123
Respostas muito melhores aqui - é assim que se faz. stackoverflow.com/questions/597328/…
n13
3
Sempre valide no back-end, não importa o quão bom seja o seu front-end, não confie nele!
SparK

Respostas:

112
require 'date'
begin
   Date.parse("31-02-2010")
rescue ArgumentError
   # handle invalid date
end
mpd
fonte
27
Não é um recurso de classe Date, é o tratamento de exceções.
Pier-Olivier Thibault
3
Date.parse ('2012-08-13 == 00:00:00') # => Seg, 13 de agosto de 2012
Bob
13
Usar um resgate "pega-tudo" deve ser considerado um antipadrão. Ele pode ocultar outros erros que não esperamos e tornar a depuração do código extremamente difícil.
yagooar
8
Então ... se for realmente um antipadrão, como você responderia à pergunta?
Matthew Brown de
11
Desculpe, esta é uma resposta errada. Ele não verifica se uma string é ou não uma data válida, ele apenas verifica se Date.parse pode analisar a string. Date.parse parece ser muito complacente quando se trata de datas, por exemplo, ele analisa "FOOBAR_09_2010" como a data 09/09/2012.
n13
54

Aqui está um liner simples:

DateTime.parse date rescue nil

Eu provavelmente não recomendaria fazer exatamente isso em todas as situações da vida real, ao forçar o chamador a verificar se há zero, por exemplo. particularmente durante a formatação. Se você retornar um erro de data | padrão, pode ser mais amigável.

robert_murray
fonte
8
DateTime.parse "123" resgate nulo. Isso retorna uma data real. 3 de maio de 2017
baash05
3
Date.strptime(date, '%d/%m/%Y') rescue nil
bonafernando
1
O uso de inline não rescueé recomendado.
smoyth,
52
d, m, y = date_string.split '-'
Date.valid_date? y.to_i, m.to_i, d.to_i
Sohan
fonte
Isso é muito simples e bom para impor um formato xx-xx-YYYY
n13
Se você deseja impor uma opção de string de data de formato único estrito, esta é a melhor opção, pois evita Exceções e é determinística. Date.valid_date? é o método a ser usado pelo menos para Ruby 2. ruby-doc.org/stdlib-2.0.0/libdoc/date/rdoc/…
Ashley Raiteri
4
Qual versão do Ruby suporta is_valid?? Tentei 1.8, 2.0 e 2.1. Nenhum deles parece ter esse. Todos parecem ter valid_date?, no entanto.
Henrik N
29

As datas de análise podem ser complicadas, especialmente quando estão no formato MM / DD / AAAA ou DD / MM / AAAA, como datas curtas usadas nos EUA ou na Europa.

Date#parse tenta descobrir qual usar, mas há muitos dias em um mês ao longo do ano em que a ambiguidade entre os formatos pode causar problemas de análise.

Eu recomendo descobrir qual é a LOCALE do usuário, então, com base nisso, você saberá como analisar de forma inteligente usando Date.strptime . A melhor maneira de descobrir onde um usuário está localizado é perguntar a ele durante a inscrição e, em seguida, fornecer uma configuração em suas preferências para alterá-la. Supondo que você possa desenterrá-lo por meio de alguma heurística inteligente e não incomodar o usuário com essa informação, está sujeito a falhas, então é só perguntar.

Este é um teste usando Date.parse . Estou nos EUA:

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

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

O primeiro era o formato correto para os EUA: mm / dd / aaaa, mas Date não gostou. A segunda estava correta para a Europa, mas se seus clientes residirem predominantemente nos EUA, você obterá muitas datas mal analisadas.

Ruby Date.strptimeé usado como:

>> Date.strptime('12/31/2001', '%m/%d/%Y') #=> #<Date: 2001-12-31 (4904549/2,0,2299161)>
o homem de lata
fonte
Seu LOCALE parece desligado. Eu recebo este >> Date.parse ('01 / 31/2001 ') => Quarta, 31 de janeiro de 2001 >>?> Date.parse ('31 / 01/2001') ArgumentError: data inválida
hoyhoy
Não tenho certeza se estou sendo idiota aqui, mas isso parece explicar apenas como analisar uma data, não como validá-la. A resposta aceita usa a exceção ArgumentError, mas isso não parece uma boa ideia, pelos motivos observados em um comentário lá.
cesóide
Existe uma situação conhecida em que você não pode validar uma data. É onde os valores de dia e mês são ambíguos e você precisa saber o formato da string de data de entrada. E é isso que a resposta está dizendo. Se for assim 01/01/2014, qual é o mês e qual é o dia? No mês dos EUA seria o primeiro, no resto do mundo seria o segundo.
The Tin Man
A ambigüidade que o impede de validar a data apenas o faz na medida em que também o impede de analisar a data, mas você fez uma boa tentativa de analisá-la com informações conhecidas. Seria bom usar um pouco do que você tem para tentar validar da melhor maneira possível. Você consegue pensar em como fazer isso sem usar exceções criadas por Date.parse e Date.strptime?
cesóide de
A análise da data no formato "mm / dd / aaaa" ou "dd / mm / aaaa" REQUER o conhecimento prévio do formato da data. Sem isso, não podemos determinar qual é, a menos que tenhamos uma amostra de uma fonte / usuário, então analise usando ambos os formulários e então veja qual formulário gerou mais exceções e use o outro. Isso quebra ao analisar datas de fontes múltiplas, especialmente quando são globais ou foram inseridas manualmente. A única maneira precisa de lidar com datas é não permitir a entrada manual, forçar os usuários a usar selecionadores de data ou permitir apenas formatos de data ISO que não sejam ambíguos.
The Tin Man de
26

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

Samer Buna
fonte
Excelente, obrigado. Este parece estar disponível pelo menos em 1.8, 2.0 e 2.1. Observe que você precisa require "date"primeiro ou obterá "método indefinido".
Henrik N
2
Este método pode gerar uma exceção 'ArgumentError: número incorreto de argumentos' em vez de retornar falso. Por exemplo, se sua string contém barras em vez de travessões
vincentp
2
Talvez possamos fazer algo assim, para lidar com data formatada incorretamente:Date.valid_date? *Array.new(3).zip(date_string.split('-')).transpose.last.reverse.map(&:to_i)
vincentp
9

Eu gostaria de estender a Dateaula.

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

exemplo

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'
ironsand
fonte
5

Outra forma de validar a data:

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
fonte
3

Experimente regex para todas as datas:

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

Apenas para o seu formato com zeros à esquerda, último ano e travessões:

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

o [- /] significa - ou /, a barra deve ter escape. Você pode testar isso em http://gskinner.com/RegExr/

adicione as seguintes linhas, todas elas serão destacadas se você usar a primeira regex, sem a primeira e a última / (elas são para uso em código ruby).

2004-02-01
2004/02/01
01-02-2004
1-2-2004
2004-2-1
MrFox
fonte
2
Essas expressões regulares correspondem apenas a alguns formatos fixos, sem se preocupar com a semântica de uma data. Seu matcher permite datas 32-13-2010erradas.
yagooar
2
Bom o suficiente se você quiser uma estimativa aproximada se qualquer string arbitrária puder ser analisada como uma data.
Neil Goodman de
Onde "estimativa aproximada" significa valores semelhantes '00/00/0000'e '99-99/9999'são possíveis candidatos.
The Tin Man
1
Um exemplo brilhante de quando o regex personalizado é uma RUIM IDEIA!
Tom Lord
3

Uma solução mais rígida

É mais fácil verificar se uma data está correta se você especificar o formato de data esperado. No entanto, mesmo assim, Ruby é um pouco tolerante demais para o meu 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, pelo menos uma dessas strings especifica o dia da semana errado, mas Ruby felizmente ignora isso.

Aqui está um método que não funciona; ele valida que date.strftime(format)convertidos de volta à mesma cadeia de entrada que ele seja analisado com Date.strptimesegundo 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
fonte
Isso é exatamente o que eu estava procurando. Obrigado!
Lucas Caton
isso falha para casos como "2019-1-1", que é uma data válida, mas o strftime torna-o 01-01-2019
saGii
@saGii que não se ajusta ao formato especificado (iso8601 - veja Date.today().iso8601); %mé preenchido com zeros. Se você quiser algo diferente, consulte ruby-doc.org/stdlib-2.4.0/libdoc/date/rdoc/…
Nathan Long
2

Postar isso porque pode ser útil para alguém mais tarde. Não tenho ideia se essa é uma "boa" maneira de fazer isso ou não, mas funciona para mim e é extensível.

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 a classe String permite que você especifique sua lista de delimitadores na linha 4 e, em seguida, sua lista de formatos válidos na linha 5. Não é nada complicado, mas torna muito fácil estender e permite que você simplesmente verifique uma string como:

"test".is_date?
"10-12-2010".is_date?
params[:some_field].is_date?
etc.
Dave Sanders
fonte
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
lixadeira
fonte
0

Você pode tentar o seguinte, que é a maneira mais simples:

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

Mas você precisa lidar com a exceção.

Bruno silva
fonte
0

Date.parse não gerou exceção para estes exemplos:

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

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

Eu prefiro esta solução:

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

Isso retorna verdadeiro ou falso se config [: datas] = "10/12/2012, 05/09/1520"

Templum Iovis
fonte