Как проверить, является ли строка действительной датой

114

У меня есть строка: "31-02-2010"и я хочу проверить, действительна ли это дата. Как лучше всего это сделать?

Мне нужен метод, который возвращает true, если строка является действительной датой, и false, если это не так.

Salil
источник
2
почему бы просто не сделать раскрывающийся список date_time вместо того, чтобы принимать строку, которую нужно проверить?
ржавчина
требование клиента. я уже предлагаю это, но не могу этого сделать :)
Салил
Как правило, разве вы не должны выполнять проверку ввода во внешнем интерфейсе приложения?
Seanny123,
Здесь гораздо лучшие ответы - вот как это сделать. stackoverflow.com/questions/597328/…
n13 04
3
Всегда проверяйте на бэкэнде, независимо от того, насколько хорош ваш интерфейс, не доверяйте ему!
SparK

Ответы:

112
require 'date'
begin
   Date.parse("31-02-2010")
rescue ArgumentError
   # handle invalid date
end
mpd
источник
27
Это не функция класса Date, это обработка исключений.
Пьер-Оливье Тибо,
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

Я бы, вероятно, не рекомендовал делать именно это в каждой ситуации в реальной жизни, поскольку вы заставляете вызывающего абонента проверять ноль, например. особенно при форматировании. Если вы вернете дату по умолчанию | ошибка, это может быть удобнее.

robert_murray
источник
8
DateTime.parse "123" спасение ноль. Это возвращает реальную дату .. 3 мая 2017 г.
baash05
3
Date.strptime(date, '%d/%m/%Y') rescue nil
bonafernando
1
Использование inline rescueне рекомендуется.
smoyth
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?Хотя, похоже , все имеют .
Henrik N
29

При анализе дат могут возникать некоторые проблемы, особенно когда они указаны в формате ММ / ДД / ГГГГ или ДД / ММ / ГГГГ, например, короткие даты, используемые в США или Европе.

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

Первым был правильный формат для США: мм / дд / гггг, но Дате он не понравился. Второе было правильным для Европы, но если ваши клиенты преимущественно проживают в США, вы получите много плохо разобранных дат.

Ruby 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, но это не кажется хорошей идеей по причинам, указанным в комментарии.
cesoid 06
Известна ситуация, когда нельзя подтвердить дату. Здесь значения дня и месяца неоднозначны, и вы должны знать формат входящей строки даты. И вот что говорит ответ. Если похоже 01/01/2014, какой месяц, а какой день? В США месяц будет первым, в остальном мире - вторым.
Железный Человек
Неоднозначность, которая мешает вам проверить дату, делает это только в той степени, в которой она также мешает вам проанализировать дату, но вы сделали хорошую попытку проанализировать ее с помощью известной информации. Было бы неплохо использовать то, что у вас есть, чтобы попытаться проверить как можно лучше. Можете ли вы придумать, как это сделать без использования исключений, создаваемых Date.parse и Date.strptime?
cesoid
Для синтаксического анализа даты в формате «мм / дд / гггг» или «дд / мм / гггг» ТРЕБУЕТСЯ предварительное знание формата даты. Без этого мы не сможем определить, какой именно, если у нас нет выборки из одного источника / пользователя, затем проанализируем с использованием обеих форм, а затем увидим, какая форма сгенерировала больше всего исключений, и не воспользуемся другой. Это не работает при синтаксическом анализе дат из нескольких источников, особенно если они глобальные или введены вручную. Единственный точный способ работать с датами - запретить ввод вручную, заставить пользователей использовать средства выбора даты или разрешить только однозначные форматы даты ISO.
Железный Человек
26

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

Самер Буна
источник
Отлично, спасибо. Кажется, что он доступен как минимум в версиях 1.8, 2.0 и 2.1. Обратите внимание, что вам нужно require "date"сначала, иначе вы получите «неопределенный метод».
Henrik N
2
Этот метод может вызвать исключение «ArgumentError: неправильное количество аргументов» вместо возврата false. Например, если ваша строка содержит косые черты вместо
дефисов
2
Может быть, мы можем сделать что-то вроде этого для обработки плохо отформатированной даты:Date.valid_date? *Array.new(3).zip(date_string.split('-')).transpose.last.reverse.map(&:to_i)
vincentp 01
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/

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

2004-02-01
2004/02/01
01-02-2004
1-2-2004
2004-2-1
MrFox
источник
2
Эти регулярные выражения соответствуют только некоторым фиксированным форматам, не заботясь о семантике даты. Ваш сопоставитель допускает такие даты, как " 32-13-2010который неверен".
yagooar
2
Достаточно хорошо, если вам нужна приблизительная оценка, если любая произвольная строка может быть проанализирована как дата.
Нил Гудман
Где «приблизительная оценка» означает такие значения '00/00/0000'и '99-99/9999'являются возможными кандидатами.
Железный Человек
1
Блестящий пример того, когда пользовательское регулярное выражение - ПЛОХАЯ ИДЕЯ!
Том Лорд
3

Более строгое решение

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

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

Ясно, что по крайней мере одна из этих строк указывает неправильный день недели, но Ruby с радостью игнорирует это.

Вот метод, который этого не делает; это подтверждает , что 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", что является допустимой датой, но strftime делает это 01.01.2019
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)}

Это возвращает истину или ложь, если config [: date] = "12.10.2012, 05.09.1520"

Templum Iovis
источник