如何检查一个字符串是否为有效日期

128

我有一个字符串:"31-02-2010",想要检查它是否为有效日期。

我需要一个方法,如果字符串是有效日期则返回true,否则返回false。


2
为什么不直接制作一个日期时间下拉菜单,而不是输入一个需要验证的字符串? - corroded
客户有需求,我已经提出建议,但是无法实现 :) - Salil
作为一般规则,难道不应该在应用程序的前端进行输入验证吗? - Seanny123
这里有更好的答案 - 这就是如何做到的。 https://dev59.com/pnRB5IYBdhLWcg3wiHll - n13
8
无论你的前端有多好,始终要在后端进行验证,不要轻信它! - SparK
显示剩余2条评论
15个回答

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

30
这不是Date类的特性,而是异常处理。 - Pier-Olivier Thibault
4
Date.parse('2012-08-13== 00:00:00')

=> 星期一,2012年8月13日

- Bob
14
使用“万能救援”应该被视为反模式。它可能掩盖我们不期望的其他错误,并使调试代码变得极其困难。 - yagooar
9
如果这确实是一种反模式,你会如何回答这个问题? - Matthew Brown
17
抱歉,这个答案是错误的。它并没有检查一个字符串是否是一个合法的日期,它只是检查了Date.parse是否可以解析这个字符串。当涉及到日期时,Date.parse似乎很宽容,例如它会将"FOOBAR_09_2010"解析为日期2012-09-09。 - n13
显示剩余6条评论

61

这是一个简单的一行代码:

DateTime.parse date rescue nil

在现实生活中,我可能不会建议完全按照这种方式进行操作,因为这会强制调用方检查nil值,例如在格式化时。如果你返回一个默认的日期或错误信息,那么会更友好。


10
DateTime.parse "123" rescue nil。这将返回一个真实的日期:2017年5月3日。 - baash05
4
Date.strptime(date, '%d/%m/%Y') rescue nil 可以翻译为:尝试将字符串类型的日期转化为日期对象,格式为'%d/%m/%Y',如果无法转化则返回nil。 - bonafernando
2
不鼓励使用内联的rescue - smoyth

59
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 的方法。http://ruby-doc.org/stdlib-2.0.0/libdoc/date/rdoc/Date.html#method-c-valid_date-3F - Ashley Raiteri
4
哪个版本的Ruby支持is_valid??尝试了1.8、2.0和2.1,但它们似乎都没有这个函数。不过所有版本似乎都有valid_date? - Henrik N

30

解析日期可能会遇到一些棘手的问题,特别是当它们采用 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 的 Date.strptime 用法如下:

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

你的本地设置似乎有误。我得到了这个 >> Date.parse('01/31/2001') => 2001年1月31日 星期三
?> Date.parse('31/01/2001') ArgumentError: 无效日期
- hoyhoy
不确定我是否很傻,但这似乎只解释了如何解析日期,而不是如何验证它。被接受的答案使用ArgumentError异常,但出于评论中提到的原因,那似乎不是一个好主意。 - cesoid
有一种已知情况,无法验证日期。当日和月的值不明确,并且您必须知道传入日期字符串的格式时,就会出现这种情况。答案就是在说明这个。如果看起来像01/01/2014,哪一个是月,哪一个是日?在美国,月份是第一个,在世界其他地方,则是第二个。 - the Tin Man
防止您验证日期的歧义只是在防止解析日期方面也具有同样程度的作用,但您已经尝试使用已知信息对其进行了解析。如果能够利用您所拥有的一些东西尽可能地进行验证,那将是很好的。您能否想出如何在不使用 Date.parse 和 Date.strptime 创建的异常情况下实现呢? - cesoid
日期解析以“mm / dd / yyyy”或“dd / mm / yyyy”格式需要预先了解日期的格式。如果没有这种方式,我们无法确定它是哪种格式,除非我们从一个源/用户获得样本,然后使用两种形式进行解析,然后查看哪种形式生成了最多的异常并使用另一种形式。当解析来自多个源的日期时,特别是当它们是全球性的或者是手动输入时,这种方法就会失效。处理日期的唯一准确方法是不允许手动输入,强制用户使用日期选择器,或仅允许不含歧义的ISO日期格式。 - the Tin Man
可以说01/31/2001是一个无效的日期,解析函数按照预期工作。 - Qwertie

29

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

日期有效性判断?*将日期字符串以“-”分隔,翻转后,逐个应用to_i方法进行映射。


很好,谢谢。这个似乎至少在1.8、2.0和2.1可用。请注意,您需要先require“date”,否则会出现“未定义的方法”。 - Henrik N
2
这个方法可能会引发异常 'ArgumentError:参数数量错误',而不是返回 false。例如,如果您的字符串包含斜杠而不是破折号。 - vincentp
2
也许我们可以这样处理格式不正确的日期:Date.valid_date? *Array.new(3).zip(date_string.split('-')).transpose.last.reverse.map(&:to_i) - vincentp

11

我想扩展 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'

我个人最喜欢这种方法。在我的应用程序中,我选择重写字符串:https://dev59.com/03A85IYBdhLWcg3wEPRL#64190303 - csalvato

6

另一种验证日期的方法:

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
在此基础上,您可以将其简化为:Date.valid_date?(*Date._parse(date).values) - Sunny

4

更严格的解决方案

如果您指定预期的日期格式,则更容易验证日期的正确性。但即使如此,对于我的用例来说,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

这正是我在寻找的。谢谢! - Lucas Caton
这种情况下会失败,例如“2019-1-1”是一个有效的日期,但strftime会将其转换为“2019-01-01”。 - saGii
@saGii,这不符合指定的格式(iso8601 - 请参见Date.today().iso8601); %m是零填充的。如果您想要其他内容,请参见https://ruby-doc.org/stdlib-2.4.0/libdoc/date/rdoc/Date.html#method-c-strptime。 - Nathan Long

3

我发布这篇文章是因为它可能对某些人有用。不知道这是否是一种“好”的方法,但它适合我并且可以扩展。

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.

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

2
这个正则表达式只匹配一些固定格式,不关心日期的语义。 你的匹配器允许诸如32-13-2010这样的日期,这是错误的。 - yagooar
2
如果您想要一个粗略的估计,以确定任何任意字符串是否可以解析为日期,则足够好。 - Neil Goodman
“粗略估计”指的是像“'00/00/0000'”和“'99-99/9999'”这样的值可能是候选项。 - the Tin Man
1
自定义正则表达式是一个坏主意的绝妙例子! - Tom Lord

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接