在Rails中验证时区是否有效

20
我正在使用tsjzt: http://pellepim.bitbucket.org/jstz/ 来获取客户端的时区,并将其存储在我的用户对象中。这很好用,可以给我像“Europe/London”这样的时区。当这个时区传递到模型中时,我想验证它是否是有效的时区,以防发生意外情况。因此,我找到了这个问题:Issue validating user time zone for Rails app on Heroku,并尝试了这个验证方法:
validates_inclusion_of :timezone, :in => { in: ActiveSupport::TimeZone.zones_map(&:name) }

然而,名称与tzinfo不同。我认为我的客户端检测到的时区字符串"Europe/London"本质上是TimeZone类中的TimeZone映射的值组件,而不是名称 - 在这种情况下将设置为"London"。

因此,我尝试了以下方法:

validates_inclusion_of :timezone, :in => { in: ActiveSupport::TimeZone.zones_map(&:tzinfo) }

在其他SO问题的原始答案或我修改后的答案中使用:tzinfo时,两者都无法通过验证,当:timezone为"Europe/London"时,显然这是一个有效的时区!

我在时区验证方面做错了什么,如何修复?


在那个映射类中似乎没有明确支持“Europe/London”,但是伦敦是支持的。 - Makoto
如果您查看ActiveSupport :: TimeZone.zones_map的输出,您可以看到包含“Europe / London”的输出 - 这就是我想要将我的输入字段与之进行比较的内容...我只是不知道如何! - RenegadeAndy
3个回答

36

看起来你想要这个:

validates_inclusion_of :timezone,
                       :in => ActiveSupport::TimeZone.all.map { |tz| tz.tzinfo.name }

在我的电脑上,该列表包括以下名称:

...
"Europe/Istanbul",
"Europe/Kaliningrad",
"Europe/Kiev",
"Europe/Lisbon",
"Europe/Ljubljana",
"Europe/London",
...

然而,更清晰的解决方案是使用自定义验证方法,例如:

validates_presence_of :timezone
validate :timezone_exists

private

def timezone_exists
  return if timezone? && ActiveSupport::TimeZone[timezone].present?
  errors.add(:timezone, "does not exist")
end

这种方式在接受的值方面更加灵活:

ActiveSupport::TimeZone["London"].present? # => true
ActiveSupport::TimeZone["Europe/London"].present? # => true
ActiveSupport::TimeZone["Pluto"].present? # => false

第一个建议不起作用,因为它似乎有语法错误:语法错误,意外的 '}', 期望 =>。 - RenegadeAndy
第二个建议确实可行,但我很好奇看到第一个建议能否工作! - RenegadeAndy
@RenegadeAndy 我已经修复了语法错误。发现得好! - Matt Brictson
很好 - 它被阻止了,但它不应该被阻止? - RenegadeAndy
我是通过复制粘贴您的问题而没有仔细查看来完成的。周围的 { } 是用于哈希字面量或块的,但在这种情况下都不适用,因此我将它们删除了。 - Matt Brictson
这并不适用于所有情况,例如旧浏览器可能会返回America/Buenos_Aires,而Rails 4则期望America/Argentinia/Buenos_Aires - 是否有任何已知的解决方法?或者在具有两个'/'分隔符的时区中去除中间部分是否可接受? - Tilo

6

一种更轻量级且性能更好的解决方案是使用TZInfo::Timezone.all_identifiers而不是操作来自ActiveSupport::TimeZone.all的列表。

validates :timezone, presence: true, inclusion: { in: TZInfo::Timezone.all_identifiers }

1
我们之前使用并存储了时区的唯一名称,所以我改用了 ActiveSupport::TimeZone.all.map( &:name ) - Joshua Pinter

1

其他答案都很好,特别是Matt Brictson的。当你让某人将像“巴黎”这样的东西传递给时区时,这个答案可以工作,这可以被Rails使用,但不在正在检查的列表(ActiveSupport::TimeZone)中。此验证检查它是否对Rails有效,并允许“巴黎”有效:

  validates_each :timezone do |record, attr, value|
    !!DateTime.new(2000).in_time_zone(value)
  rescue
    record.errors.add(attr, 'was not valid, please select one from the dropdown')
  end

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