Comment vérifier si une chaîne est une date valide

114

J'ai une chaîne: "31-02-2010"et je veux vérifier si c'est une date valide ou non. Quelle est la meilleure façon de procéder?

J'ai besoin d'une méthode qui renvoie true si la chaîne est une date valide et false si ce n'est pas le cas.

Salil
la source
2
pourquoi ne pas simplement créer une liste déroulante date_time au lieu de saisir une chaîne que vous devez valider?
corrodé le
l'exigence du client. Je le suggère déjà mais je ne peux pas le faire :)
Salil
En règle générale, n'êtes-vous pas censé faire la validation d'entrée sur le front-end d'une application?
Seanny123
De bien meilleures réponses ici - voici comment procéder. stackoverflow.com/questions/597328/…
n13
3
Validez toujours sur le backend, quelle que soit la qualité de votre front-end, ne lui faites pas confiance!
SparK

Réponses:

112
require 'date'
begin
   Date.parse("31-02-2010")
rescue ArgumentError
   # handle invalid date
end
mpd
la source
27
Ce n'est pas une fonctionnalité de classe Date, c'est la gestion des exceptions.
Pier-Olivier Thibault
3
Date.parse ('2012-08-13 == 00:00:00') # => Lun, 13 août 2012
Bob
13
Utiliser un sauvetage «fourre-tout» devrait être considéré comme un anti-pattern. Il peut masquer d'autres erreurs auxquelles nous ne nous attendons pas et rendre le débogage du code extrêmement difficile.
yagooar le
8
Alors ... s'il s'agit bien d'un anti-pattern, comment répondriez-vous à la question?
Matthew Brown
11
Désolé, c'est une mauvaise réponse. Il ne vérifie pas si une chaîne est une date valide ou non, il vérifie simplement Date.parse peut analyser la chaîne. Date.parse semble être très indulgent en ce qui concerne les dates, par exemple, il analysera "FOOBAR_09_2010" comme étant la date 2012-09-09.
n13
54

Voici une simple doublure:

DateTime.parse date rescue nil

Je ne recommanderais probablement pas de faire exactement cela dans toutes les situations de la vie réelle, car vous forcez l'appelant à vérifier zéro, par exemple. en particulier lors du formatage. Si vous renvoyez une date par défaut | erreur, cela peut être plus convivial.

robert_murray
la source
8
DateTime.parse "123" sauvetage nul. Cela renvoie une date réelle. 3 mai 2017
baash05
3
Date.strptime(date, '%d/%m/%Y') rescue nil
bonafernando
1
L'utilisation de l'inline rescueest déconseillée.
smoyth
52
d, m, y = date_string.split '-'
Date.valid_date? y.to_i, m.to_i, d.to_i
Sohan
la source
C'est assez simple et bon pour appliquer un format xx-xx-YYYY
n13
Si vous souhaitez appliquer une option stricte de chaîne de date à format unique, c'est la meilleure option car elle évite les exceptions et est déterministe. Date.valid_date? est la méthode à utiliser au moins pour Ruby 2. ruby-doc.org/stdlib-2.0.0/libdoc/date/rdoc/…
Ashley Raiteri
4
Quelle version de Ruby prend en charge is_valid?? Essayé 1.8, 2.0 et 2.1. Aucun d'entre eux ne semble avoir celui-là. valid_date?Mais tout semble l'avoir fait .
Henrik N
29

L'analyse des dates peut se heurter à certains pièges, en particulier lorsqu'elles sont au format MM / JJ / AAAA ou JJ / MM / AAAA, comme les dates courtes utilisées aux États-Unis ou en Europe.

Date#parse tente de déterminer lequel utiliser, mais il y a plusieurs jours dans un mois tout au long de l'année où l'ambiguïté entre les formats peut causer des problèmes d'analyse.

Je recommanderais de savoir quelle est la LOCALE de l'utilisateur, puis, sur cette base, vous saurez comment analyser intelligemment en utilisant Date.strptime. La meilleure façon de trouver où se trouve un utilisateur est de lui demander lors de l'inscription, puis de fournir un paramètre dans ses préférences pour le modifier. En supposant que vous puissiez le découvrir par une heuristique intelligente et ne pas déranger l'utilisateur pour cette information, il est sujet à l'échec, alors demandez simplement.

Ceci est un test utilisant Date.parse. Je suis aux USA:

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

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

Le premier était le format correct pour les États-Unis: mm / jj / aaaa, mais Date ne l'aimait pas. La seconde était correcte pour l'Europe, mais si vos clients sont principalement basés aux États-Unis, vous obtiendrez beaucoup de dates mal analysées.

Ruby Date.strptimeest utilisé comme:

>> Date.strptime('12/31/2001', '%m/%d/%Y') #=> #<Date: 2001-12-31 (4904549/2,0,2299161)>
l'homme d'étain
la source
Votre LOCALE semble éteint. J'obtiens ceci >> Date.parse ('01 / 31/2001 ') => Wed, 31 Jan 2001 >>?> Date.parse ('31 / 01/2001') ArgumentError: invalid date
hoyhoy
Je ne sais pas si je suis stupide ici, mais cela ne semble expliquer que comment analyser une date, pas comment la valider. La réponse acceptée utilise l'exception ArgumentError, mais cela ne semble pas être une bonne idée, pour les raisons indiquées dans un commentaire.
cesoid
Il existe une situation connue où vous ne pouvez pas valider une date. C'est là que les valeurs du jour et du mois sont ambiguës, et vous devez connaître le format de la chaîne de date entrante. Et c'est ce que dit la réponse. Si ça ressemble 01/01/2014, quel est le mois et quel est le jour? Le mois américain serait le premier, le reste du monde serait le deuxième.
le Tin Man
L'ambiguïté qui vous empêche de valider la date ne le fait que dans la mesure où elle vous empêche également d'analyser la date, mais vous avez bien tenté de l'analyser avec des informations connues. Ce serait bien d'utiliser une partie de ce que vous devez essayer de valider du mieux que nous pouvons. Pouvez-vous penser à comment faire cela sans utiliser les exceptions créées par Date.parse et Date.strptime?
cesoïde
L'analyse de la date au format «mm / jj / aaaa» ou «jj / mm / aaaa» REQUIERT une connaissance préalable du format de la date. Sans cela, nous ne pouvons pas déterminer de quoi il s'agit à moins d'avoir un échantillon d'une source / utilisateur, puis analyser en utilisant les deux formulaires, puis voir quel formulaire a généré le plus d'exceptions et utiliser l'autre. Cela se décompose lors de l'analyse des dates de plusieurs sources, en particulier lorsqu'elles sont globales ou qu'elles ont été saisies à la main. Le seul moyen précis de traiter les dates est de ne pas autoriser la saisie manuelle, de forcer les utilisateurs à utiliser des sélecteurs de dates ou de n'autoriser que les formats de date ISO sans ambiguïté.
the Tin Man
26

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

Samer Buna
la source
Excellent, merci. Celui-ci semble être disponible au moins en 1.8, 2.0 et 2.1. Notez que vous devez d' require "date"abord ou vous obtiendrez "méthode non définie".
Henrik N
2
Cette méthode peut déclencher une exception «ArgumentError: mauvais nombre d'arguments» au lieu de renvoyer false. Par exemple, si votre chaîne contient des barres obliques au lieu de tirets
vincentp
2
Peut-être pouvons-nous faire quelque chose comme ça, pour gérer une date mal formatée:Date.valid_date? *Array.new(3).zip(date_string.split('-')).transpose.last.reverse.map(&:to_i)
vincentp
9

J'aimerais prolonger la Dateclasse.

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

exemple

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'
fer à repasser
la source
5

Une autre façon de valider la date:

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
la source
3

Essayez regex pour toutes les dates:

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

Pour uniquement votre format avec des zéros non significatifs, l'année dernière et des tirets:

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

le [- /] signifie soit - soit /, la barre oblique doit être échappée. Vous pouvez tester ceci sur http://gskinner.com/RegExr/

ajoutez les lignes suivantes, elles seront toutes mises en surbrillance si vous utilisez la première regex, sans le premier et le dernier / (ils sont à utiliser dans le code ruby).

2004-02-01
2004/02/01
01-02-2004
1-2-2004
2004-2-1
MrFox
la source
2
Ces expressions régulières ne correspondent qu'à certains formats fixes, sans se soucier de la sémantique d'une date. Votre matcher autorise des dates telles que celles 32-13-2010qui sont incorrectes.
yagooar
2
Assez bien si vous voulez une estimation approximative si une chaîne arbitraire peut être analysée comme une date.
Neil Goodman
Où «estimation approximative» signifie des valeurs similaires '00/00/0000'et '99-99/9999'sont des candidats possibles.
the Tin Man
1
Un exemple brillant de quand une expression régulière personnalisée est une mauvaise idée!
Tom Lord
3

Une solution plus stricte

Il est plus facile de vérifier l'exactitude d'une date si vous spécifiez le format de date attendu. Cependant, même dans ce cas, Ruby est un peu trop tolérant pour mon cas d'utilisation:

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

De toute évidence, au moins une de ces chaînes spécifie le mauvais jour de la semaine, mais Ruby ignore volontiers cela.

Voici une méthode qui ne le fait pas; il valide cette date.strftime(format)conversion en la même chaîne d'entrée avec laquelle il a analysé Date.strptimeselon 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
la source
C'est exactement ce que je cherchais. Je vous remercie!
Lucas Caton
cela échoue pour les cas comme "2019-1-1" qui est une date valide mais le strftime le rend 2019-01-01
saGii
@saGii qui ne correspond pas au format spécifié (iso8601 - voir Date.today().iso8601); %mest rempli de zéro. Si vous voulez quelque chose de différent, voir ruby-doc.org/stdlib-2.4.0/libdoc/date/rdoc/…
Nathan Long
2

Publier ceci parce qu'il pourrait être utile à quelqu'un plus tard. Aucune idée si c'est une "bonne" façon de le faire ou non, mais cela fonctionne pour moi et est extensible.

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

Ce module complémentaire pour la classe String vous permet de spécifier votre liste de délimiteurs à la ligne 4, puis votre liste de formats valides à la ligne 5. Ce n'est pas sorcier, mais il est vraiment facile à étendre et vous permet simplement de vérifier une chaîne comme ceci:

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

Vous pouvez essayer ce qui suit, ce qui est simple:

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

Mais vous devez gérer l'exception.

Bruno Silva
la source
0

Date.parse n'a pas soulevé d'exception pour ces exemples:

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

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

Je préfère cette solution:

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
la source
-1

Méthode:

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

Usage:

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

Cela renvoie vrai ou faux si config [: dates] = "12/10 / 2012,05 / 09/1520"

Templum Iovis
la source