Як перевірити, чи є рядок дійсною датою


114

У мене є рядок: "31-02-2010"і хочу перевірити, чи це дійсна дата чи ні. Який найкращий спосіб це зробити?

Мені потрібен метод, який повертає true, якщо рядок є дійсною датою, а false - якщо ні.


2
Чому б не просто зробити падіння date_time, а не взяти рядок, який потрібно перевірити?
роз’їдає

вимога клієнта. Я вже пропоную це, але не можу цього зробити :)
Саліл

Як правило, чи не слід робити перевірку вводу на передньому кінці програми?
Seanny123

Тут набагато кращі відповіді - ось як це зробити. stackoverflow.com/questions/597328/…
n13

3
Завжди перевіряйте вміст бекенда, незалежно від того, наскільки хороший ваш фронт-енд, не довіряйте цьому!
SparK

Відповіді:


112
require 'date'
begin
   Date.parse("31-02-2010")
rescue ArgumentError
   # handle invalid date
end

27
Це не особливість класу Дата, це обробка виключень.
П’єр-Олів'є Тібо

3
Date.parse ('2012-08-13 == 00:00:00') # => Пн, 13 серпня 2012
Боб

13
Використання порятунку "спіймати все" слід вважати анти-схемою. Це може приховати інші помилки, яких ми не очікуємо, і зробити налагодження коду надзвичайно важким.
yagooar

8
Отже ... якщо це дійсно анти-модель, як би ви відповіли на питання?
Меттью Браун

11
Вибачте, це неправильна відповідь. Він не перевіряє, чи є рядок дійсною датою чи ні, вона просто перевіряє Date.parse може проаналізувати рядок. Date.parse, здається, дуже прощає, коли мова йде про дати, наприклад, він буде розбирати "FOOBAR_09_2010" як дата 2012-09-09.
n13

54

Ось простий один вкладиш:

DateTime.parse date rescue nil

Я, напевно, не рекомендував би робити це саме в будь-якій ситуації реального життя, оскільки ви змушуєте абонента перевірити наявність нуля, наприклад. особливо при форматуванні. Якщо ви повернете дату за замовчуванням | помилка, це може бути дружнішим.


8
DateTime.parse "123" рятувальний нуль. Це повертає реальну дату .. 3 травня 2017
baash05

3
Date.strptime(date, '%d/%m/%Y') rescue nil
bonafernando

1
Використання inline rescueне рекомендується.
смойт

52
d, m, y = date_string.split '-'
Date.valid_date? y.to_i, m.to_i, d.to_i

Це досить просто і добре для застосування формату xx-xx-YYYY
n13

Якщо ви хочете застосувати строгий параметр рядка дати в одному форматі, то це найкращий варіант, оскільки це дозволяє уникнути винятків і детерміновано. Date.valid_date? є методом використання принаймні для Ruby 2. ruby-doc.org/stdlib-2.0.0/libdoc/date/rdoc/…
Ешлі Райтері

4
Яка версія Ruby підтримує is_valid?? Пробували 1,8, 2,0 та 2,1. Ніхто з них, схоже, не має цього. valid_date?Однак, схоже , все є.
Генрік N

29

Дати розбору можуть стикатися з деякими готчами, особливо коли вони є у форматі MM / DD / YYYY або DD / MM / YYYY, наприклад короткі дати, які використовуються в США або Європі.

Date#parse спроби з'ясувати, які використовувати, але є багато днів у місяці протягом року, коли неоднозначність між форматами може спричинити проблеми розбору.

Я рекомендую дізнатися, що таке LOCALE користувача, то виходячи з цього, ви дізнаєтесь, як розумно розібратися, використовуючи Date.strptime. Найкращий спосіб знайти місце знаходження користувача - це запитати їх під час реєстрації, а потім надати налаштування у своїх налаштуваннях, щоб змінити його. Якщо припустити, що ви зможете розкопати її за допомогою якогось розумного евристичного характеру і не турбувати користувача за цю інформацію, це схильність до відмови, тому просто запитайте.

Це тест із використанням Date.parse. Я в США:

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

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

Першим був правильний формат для США: mm / dd / yyyy, але Date не сподобався. Друге було правильним для Європи, але якщо ваші клієнти переважно базуються в США, ви отримаєте багато погано проаналізованих дат.

Ruby's Date.strptimeвикористовується як:

>> Date.strptime('12/31/2001', '%m/%d/%Y') #=> #<Date: 2001-12-31 (4904549/2,0,2299161)>

Здається, ваш LOCALE вимкнено. Я отримую це >> Date.parse ('01 / 31/2001 ') => Ср, 31 січня 2001 >>?> Date.parse ('31 / 01/2001') ArgumentError: недійсна дата
hoyhoy

Не впевнений, чи я тут прохожу, але це, здається, пояснює лише те, як проаналізувати дату, а не як перевірити її. У прийнятій відповіді використовується виняток ArgumentError, але це не здається гарною ідеєю з причин, зазначених у коментарі.
цезоїд

Відома ситуація, коли ви не можете перевірити дату. Тут знаходяться неоднозначні значення дня та місяця, і ви повинні знати формат вхідного рядка дати. І саме про це йдеться у відповіді. Якщо це схоже 01/01/2014, який місяць і який день? У США місяць був би першим, інший світ - другим.
Олов'яний чоловік

Неоднозначність, яка заважає вам перевірити дату, робить це лише в тій мірі, в якій вона також заважає вам проаналізувати дату, але ви добре зробили спробу розбору її з відомою інформацією. Було б непогано скористатись тим, що вам потрібно спробувати перевірити якнайкраще. Чи можете ви подумати, як це зробити без використання винятків, які створюють Date.parse та Date.strptime?
цезоїд

Аналіз дати у форматі "mm / dd / yyyy" або "dd / mm / yyyy" ПОТРІБНО вимагає передбачення формату дати. Без цього ми не можемо визначити, що це таке, якщо ми не матимемо вибірку з одного джерела / користувача, а потім розберемо обидві форми, а потім побачимо, яка форма створила більшість винятків та використала іншу. Це руйнується під час розбору дат із кількох джерел, особливо коли вони глобальні або вони були введені вручну. Єдиний точний спосіб розібратися з датами - це забороняти введення вручну, змушувати користувачів використовувати вибирачі дат або дозволяти лише однозначні формати дат ISO.
Олов'яний чоловік

26

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


Відмінно, дякую. Цей, здається, доступний принаймні в 1.8, 2.0 та 2.1. Зауважте, що вам потрібно require "date"спершу або ви отримаєте "не визначений метод".
Генрік Н

2
Цей метод може створити виняток "ArgumentError: неправильна кількість аргументів" замість повернення false. Наприклад, якщо ваша рядок містить
косої

2
Можливо, ми можемо зробити щось подібне для обробки погано відформатованої дати:Date.valid_date? *Array.new(3).zip(date_string.split('-')).transpose.last.reverse.map(&:to_i)
vincentp

9

Я хотів би продовжити Dateзаняття.

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

приклад

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'

5

Ще один спосіб підтвердження дати:

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)

3

Спробуйте регулярно встановити всі дати:

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

Тільки для вашого формату з першими нулями, останнім роком та тире:

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

[- /] означає або - або /, слід нахиляти пряму косу рису. Ви можете перевірити це на http://gskinner.com/RegExr/

додайте наступні рядки, всі вони будуть виділені, якщо ви будете використовувати перший регулярний вираз, без першого та останнього / (вони використовуються в коді рубіну).

2004-02-01
2004/02/01
01-02-2004
1-2-2004
2004-2-1

2
Цей регулярний вираз відповідає лише деяким фіксованим форматам, не піклуючись про семантику дати. Ваш матч дозволяє дати, наприклад, 32-13-2010неправильні.
yagooar

2
Досить добре, якщо ви хочете отримати приблизну оцінку, чи можна будь-який довільний рядок проаналізувати як дату.
Ніл Гудмен

Де "приблизна оцінка" означає значення, такі як '00/00/0000'і '99-99/9999'можливі кандидати.
Олов'яний чоловік

1
Яскравий приклад того, коли нестандартний регулярний вираз - це BAD IDEA!
Том Лорд

3

Суворіше рішення

Простіше перевірити правильність дати, якщо ви вкажете очікуваний формат дати. Однак навіть тоді Рубі трохи занадто толерантний до мого використання:

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

Зрозуміло, що принаймні один із цих рядків вказує неправильний будній день, але Рубі це радісно ігнорує.

Ось метод, який ні; він перевіряє, що date.strftime(format)перетворює назад в ту саму вхідну рядок, з якою він розбирався Date.strptimeвідповідно до 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

Саме це я і шукав. Дякую!
Лукас Катон

це не вдається для випадків на кшталт "2019-1-1", що є дійсною датою, але строковий результат робить це 2019-01-01
saGii

@saGii, який не відповідає заданому формату (iso8601 - див. Date.today().iso8601); %mнульова підкладка. Якщо ви хочете чогось іншого, дивіться ruby-doc.org/stdlib-2.4.0/libdoc/date/rdoc/…
Натан Лонг

2

Опублікуйте це, оскільки пізніше це може бути корисним комусь. Немає поняття, чи це "хороший" спосіб зробити це чи ні, але це працює для мене і є розтяжним.

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

Цей додаток для класу String дозволяє вам вказати свій список роздільників у рядку 4, а потім ваш список допустимих форматів у рядку 5. Не ракетна наука, але це дуже просто розширюється і дозволяє вам просто перевірити рядок так:

"test".is_date?
"10-12-2010".is_date?
params[:some_field].is_date?
etc.

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

0

Ви можете спробувати наступне, що є простим способом:

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

Але вам потрібно обробити виняток.


0

Date.parse не підвищив виняток для цих прикладів:

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

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

Я вважаю за краще таке рішення:

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

-1

Спосіб:

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

Використання:

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

Це повертає значення true або false, якщо config [: дати] = "12/10 / 2012,05 / 09/1520"

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.