Jak sprawdzić, czy ciąg jest prawidłową datą

114

Mam ciąg: "31-02-2010"i chcę sprawdzić, czy jest to ważna data. Jak najlepiej to zrobić?

Potrzebuję metody, która zwraca prawdę, jeśli ciąg jest prawidłową datą i fałsz, jeśli nie jest.

Salil
źródło
2
dlaczego nie zrobić po prostu listy rozwijanej date_time zamiast pobierać ciąg, który musisz zweryfikować?
skorodował
wymagania klienta. już to sugeruję, ale nie mogę tego zrobić :)
Salil
Co do zasady, czy nie powinno się przeprowadzać sprawdzania poprawności danych wejściowych w interfejsie aplikacji?
Seanny123,
O wiele lepsze odpowiedzi tutaj - oto jak to zrobić. stackoverflow.com/questions/597328/…
n13
3
Zawsze sprawdzaj poprawność na zapleczu, bez względu na to, jak dobry jest twój interfejs, nie ufaj mu!
SparK

Odpowiedzi:

112
require 'date'
begin
   Date.parse("31-02-2010")
rescue ArgumentError
   # handle invalid date
end
mpd
źródło
27
To nie jest funkcja klasy Date, to obsługa wyjątków.
Pier-Olivier Thibault
3
Date.parse ('2012-08-13 == 00:00:00') # => pon, 13 sierpnia 2012
Bob
13
Korzystanie z metody ratunkowej typu „catch-it-all” powinno być traktowane jako anty-wzór. Może ukryć inne błędy, których się nie spodziewamy, i bardzo utrudnić debugowanie kodu.
yagooar
8
Więc ... jeśli to rzeczywiście jest anty-wzór, jak odpowiedziałbyś na to pytanie?
Matthew Brown,
11
Przepraszamy, to zła odpowiedź. Nie sprawdza, czy ciąg jest prawidłową datą, a jedynie sprawdza Date.parse może przeanalizować ciąg. Date.parse wydaje się być bardzo wyrozumiała, jeśli chodzi o daty, np. Przeanalizuje "FOOBAR_09_2010" jako datę 2012-09-09.
n13
54

Oto prosta jedna wkładka:

DateTime.parse date rescue nil

Prawdopodobnie nie polecałbym robienia tego dokładnie w każdej sytuacji w prawdziwym życiu, ponieważ zmuszasz dzwoniącego do sprawdzenia, czy nie ma zera, np. szczególnie podczas formatowania. Jeśli zwrócisz domyślną datę | błąd, może to być bardziej przyjazne.

robert_murray
źródło
8
DateTime.parse "123" ratunek zero. Zwraca to prawdziwą datę. 3 maja 2017
baash05
3
Date.strptime(date, '%d/%m/%Y') rescue nil
bonafernando
1
Używanie inline rescuejest odradzane.
smoyth
52
d, m, y = date_string.split '-'
Date.valid_date? y.to_i, m.to_i, d.to_i
Sohan
źródło
Jest to dość proste i dobre do egzekwowania formatu xx-xx-RRRR
n13
Jeśli chcesz wymusić ścisłą opcję łańcucha daty w jednym formacie, jest to najlepsza opcja, ponieważ pozwala uniknąć wyjątków i jest deterministyczna. Date.valid_date? jest metodą przynajmniej dla Ruby 2. ruby-doc.org/stdlib-2.0.0/libdoc/date/rdoc/…
Ashley Raiteri
4
Która wersja Rubiego obsługuje is_valid?? Wypróbowano 1.8, 2.0 i 2.1. Żaden z nich nie wydaje się mieć tego. Wydaje się jednak valid_date?, że wszyscy mają .
Henrik N
29

Parsowanie dat może napotkać pewne problemy, zwłaszcza gdy są one w formacie MM / DD / RRRR lub DD / MM / RRRR, na przykład krótkie daty używane w USA lub Europie.

Date#parse próbuje dowiedzieć się, którego użyć, ale jest wiele dni w miesiącu w ciągu roku, kiedy niejednoznaczność między formatami może powodować problemy z analizą.

Radziłbym dowiedzieć się, jakie jest LOCALE użytkownika, a następnie na tej podstawie będziesz wiedział, jak inteligentnie analizować za pomocą Date.strptime. Najlepszym sposobem, aby dowiedzieć się, gdzie znajduje się użytkownik, jest zapytanie go podczas rejestracji, a następnie podanie ustawienia w jego preferencjach, aby to zmienić. Zakładając, że możesz to wykopać za pomocą sprytnej heurystyki i nie zawracać głowy użytkownikowi tymi informacjami, jest podatne na niepowodzenie, więc po prostu zapytaj.

To jest test przy użyciu Date.parse. Jestem w USA:

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

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

Pierwszy był prawidłowym formatem dla Stanów Zjednoczonych: mm / dd / rrrr, ale data nie podobała się. Drugi był poprawny dla Europy, ale jeśli Twoi klienci pochodzą głównie z USA, otrzymasz wiele źle przeanalizowanych dat.

Ruby Date.strptimejest używany w następujący sposób:

>> Date.strptime('12/31/2001', '%m/%d/%Y') #=> #<Date: 2001-12-31 (4904549/2,0,2299161)>
Blaszany Człowiek
źródło
Twoja LOCALE wydaje się wyłączona. Dostaję to >> Date.parse ('01 / 31/2001 ') => środa, 31 stycznia 2001 >>?> Date.parse ('31 /
01/2001
Nie jestem pewien, czy jestem tu głupi, ale wydaje się, że wyjaśnia to tylko, jak przeanalizować datę, a nie jak ją zweryfikować. Zaakceptowana odpowiedź używa wyjątku ArgumentError, ale nie wydaje się to dobrym pomysłem z powodów wymienionych w komentarzu.
cesoid
Znana jest sytuacja, w której nie można zweryfikować daty. Tutaj wartości dnia i miesiąca są niejednoznaczne i musisz znać format ciągu nadchodzącej daty. I to właśnie mówi odpowiedź. Jeśli tak 01/01/2014, to który miesiąc, a który dzień? W Stanach Zjednoczonych miesiąc byłby pierwszy, reszta świata byłby drugi.
Tin Man
Niejednoznaczność, która uniemożliwia zweryfikowanie daty, powoduje to tylko w takim zakresie, że uniemożliwia również analizę daty, ale wykonałeś dobrą próbę przeanalizowania jej ze znanymi informacjami. Byłoby miło, gdybyś jak najlepiej wykorzystał to, co musisz zweryfikować. Czy możesz pomyśleć, jak to zrobić bez używania wyjątków tworzonych przez Date.parse i Date.strptime?
cesoid
Analiza daty w formacie „mm / dd / rrrr” lub „dd / mm / rrrr” WYMAGA znajomości formatu daty. Bez tego nie możemy określić, który to jest, chyba że mamy próbkowanie z jednego źródła / użytkownika, a następnie przeanalizujemy przy użyciu obu formularzy, a następnie zobaczmy, który formularz wygenerował najwięcej wyjątków i użyjemy drugiego. To się psuje podczas analizowania dat z wielu źródeł, zwłaszcza gdy są globalne lub zostały wprowadzone ręcznie. Jedynym dokładnym sposobem radzenia sobie z datami jest niedopuszczanie do ręcznego wprowadzania danych, zmuszanie użytkowników do korzystania z selektorów dat lub zezwalanie tylko na formaty daty ISO, które są jednoznaczne.
Tin Man
26

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

Samer Buna
źródło
Wspaniale, dziękuje. Ten wydaje się być dostępny co najmniej w wersjach 1.8, 2.0 i 2.1. Pamiętaj, że musisz to zrobić require "date"najpierw, albo otrzymasz „niezdefiniowaną metodę”.
Henrik N,
2
Ta metoda może zgłosić wyjątek „ArgumentError: niewłaściwa liczba argumentów” zamiast zwracać wartość false. Na przykład, jeśli Twój ciąg zawiera ukośniki zamiast myślników
vincentp
2
Może możemy zrobić coś takiego, aby Date.valid_date? *Array.new(3).zip(date_string.split('-')).transpose.last.reverse.map(&:to_i)
poradzić
9

Chciałbym przedłużyć Dateklasę.

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

przykład

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
źródło
5

Inny sposób weryfikacji daty:

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
źródło
3

Wypróbuj wyrażenie regularne dla wszystkich dat:

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

Tylko dla twojego formatu z wiodącymi zerami, ostatnim rokiem i myślnikami:

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

[- /] oznacza albo - albo /, ukośnik musi zostać zmieniony. Możesz to przetestować na http://gskinner.com/RegExr/

dodaj następujące wiersze, wszystkie zostaną podświetlone, jeśli użyjesz pierwszego wyrażenia regularnego, bez pierwszego i ostatniego / (są one używane w kodzie ruby).

2004-02-01
2004/02/01
01-02-2004
1-2-2004
2004-2-1
MrFox
źródło
2
Te wyrażenia regularne pasują tylko do niektórych ustalonych formatów, bez dbania o semantykę daty. Twój element dopasowujący zezwala na takie daty, jak 32-13-2010błędne.
yagooar
2
Wystarczająco dobre, jeśli chcesz zgrubnie oszacować, czy dowolny ciąg znaków może zostać przeanalizowany jako data.
Neil Goodman,
Gdzie „przybliżone oszacowanie” oznacza wartości takie jak '00/00/0000'i '99-99/9999'są potencjalnymi kandydatami.
Tin Man,
1
Doskonały przykład, kiedy niestandardowe wyrażenie regularne to ZŁY POMYSŁ!
Tom Lord
3

Bardziej rygorystyczne rozwiązanie

Łatwiej jest zweryfikować poprawność daty, jeśli określisz oczekiwany format daty. Jednak nawet wtedy Ruby jest trochę zbyt tolerancyjny dla mojego przypadku użycia:

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

Oczywiście co najmniej jeden z tych ciągów określa zły dzień tygodnia, ale Ruby szczęśliwie to ignoruje.

Oto metoda, która tego nie robi; sprawdza, czy date.strftime(format)konwertuje z powrotem na ten sam ciąg wejściowy, z którym był analizowany Date.strptimezgodnie z 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
źródło
Dokładnie tego szukałem. Dziękuję Ci!
Lucas Caton
to się nie udaje w przypadkach takich jak „2019-1-1”, który jest prawidłową datą, ale strftime sprawia, że ​​jest 01.01.2019
saGii
@saGii, który nie pasuje do określonego formatu (iso8601 - zobacz Date.today().iso8601); %mjest wypełniony zerami. Jeśli chcesz czegoś innego, zobacz ruby-doc.org/stdlib-2.4.0/libdoc/date/rdoc/ ...
Nathan Long
2

Publikowanie tego, ponieważ może to być przydatne dla kogoś później. Nie mam pojęcia, czy jest to „dobry” sposób, aby to zrobić, czy nie, ale działa dla mnie i można go rozszerzyć.

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

Ten dodatek do klasy String pozwala określić listę ograniczników w linii 4, a następnie listę prawidłowych formatów w linii 5. Nie jest to fizyka jądrowa, ale bardzo ułatwia jej rozszerzenie i pozwala po prostu sprawdzić ciąg w następujący sposób:

"test".is_date?
"10-12-2010".is_date?
params[:some_field].is_date?
etc.
Dave Sanders
źródło
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
szlifierka
źródło
0

Możesz spróbować wykonać następujące czynności, co jest prostym sposobem:

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

Ale musisz obsłużyć wyjątek.

Bruno Silva
źródło
0

Date.parse nie zgłoszono wyjątku dla tych przykładów:

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

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

Wolę takie rozwiązanie:

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
źródło
-1

Metoda:

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

Stosowanie:

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

Zwraca wartość true lub false, jeśli config [: dates] = "12/10 / 2012,05 / 09/1520"

Templum Iovis
źródło