更正rails DateTime.parse中夏令时的时间

bra*_*rad 17 timezone ruby-on-rails ruby-on-rails-3

只是增加了关于时区和DST问题的百万个问题.

我有一个具有单独的日期和时间字段的表单,我结合起来创建一个DateTime,就像这样

start_time = DateTime.parse("#{parse_date(form_date)} #{form_start_time} #{Time.zone}")
Run Code Online (Sandbox Code Playgroud)

如果我在2012年8月21日和15:00填写表单,那么这些是我在重新加载表单时看到的值.如果我然后在模型中查看我的start_time属性,则将其正确设置为Tue, 21 Aug 2012 15:00:00 EST +10:00.

我遇到的问题是,如果我在今年晚些时候使用日期,那么夏令时就会开始(我在澳大利亚).如果我使用2012年12月21日和15:00然后检查start_time我看到了Fri, 21 Dec 2012 16:00:00 EST +11:00.

我对这个问题的解释是日期保存在我当前的时区(+10:00),因为这是我告诉DateTime.parse要做的事情.但是当返回该值时,Rails会查看日期并说"嘿,它是12月的夏令时"并返回+11:00时区的时间.

我想要做的是告诉DateTime.parse如果DST生效,将时间保存在+11:00时区.显然将Time.zone传递给我的字符串并不能实现这一点.有一个简单的方法吗?我可以看到使用Time#dst做到这一点的方法吗?但我怀疑这会产生一些非常丑陋的错综复杂的代码.我以为可能会有一种我错过的内置方式.

小智 18

(Rails 4.2.4的答案,未检查旧版本或更新版本)

我建议使用in_time_zone带时区名称的String方法作为参数,而不是使用固定移位+01:00,+ 02:00等:

夏令时:

ruby :001 > "2016-07-02 00:00:00".in_time_zone('Paris')
 => Sat, 02 Jul 2016 00:00:00 CEST +02:00 
Run Code Online (Sandbox Code Playgroud)

冬季时间:

ruby :002 > "2016-11-02 00:00:00".in_time_zone('Paris')
 => Wed, 02 Nov 2016 00:00:00 CET +01:00 
Run Code Online (Sandbox Code Playgroud)

String#in_time_zone 相当于:

ruby :003 > Time.find_zone!("Paris").parse("2016-07-02 00:00:00")
 => Sat, 02 Jul 2016 00:00:00 CEST +02:00 

ruby :004 > Time.find_zone!("Paris").parse("2016-11-02 00:00:00")
 => Wed, 02 Nov 2016 00:00:00 CET +01:00 
Run Code Online (Sandbox Code Playgroud)

您可以通过以下方式获取时区名称:

$ rake time:zones:all
Run Code Online (Sandbox Code Playgroud)

或者在rails控制台中:

ruby :001 > ActiveSupport::TimeZone.all.map(&:name)
Run Code Online (Sandbox Code Playgroud)

或者为select标签构建集合:

ActiveSupport::TimeZone.all.map do |timezone|
  formatted_offset = Time.now.in_time_zone(timezone.name).formatted_offset
  [ "(GMT#{formatted_offset}) #{timezone.name}", timezone.name ]
end
Run Code Online (Sandbox Code Playgroud)

并存储时区名称而不是班次.

注意:不要混淆String#in_time_zone方法和Time#in_time_zone方法.

考虑我的系统的时区是'巴黎'.

ruby :001 > Time.parse("2016-07-02 00:00:00")
 => 2016-07-02 00:00:00 +0200 
ruby :002 > Time.parse("2016-07-02 00:00:00").in_time_zone("Nuku'alofa")
 => Sat, 02 Jul 2016 11:00:00 TOT +13:00 
Run Code Online (Sandbox Code Playgroud)


bra*_*rad 10

到目前为止,这是我的解决方案.我希望有人有一个更好的.

start_time = DateTime.parse "#{date} #{(form_start_time || start_time)} #{Time.zone}"
start_time = start_time - 1.hour if start_time.dst? && !Time.now.dst?
start_time = start_time + 1.hour if Time.now.dst? && start_time.dst?
Run Code Online (Sandbox Code Playgroud)

它似乎工作,但我没有严格测试它.我怀疑它可以被修饰和缩短,但我认为这是可读和可理解的.有什么改进?

  • @brad,我意识到这是一个非常古老的问题,但你有没有找到更好的处理方法呢?如果没有,你发现这效果很好吗?谢谢! (2认同)