gpt4 book ai didi

ruby-on-rails - 在 UTC 中保存日期时间有时不准确

转载 作者:行者123 更新时间:2023-12-03 22:35:32 28 4
gpt4 key购买 nike

一般来说,处理日期的最佳实践是 store them in UTC并转换回应用层中用户期望的任何内容。

这不一定适用于 future 的日期,特别是在日期/时间特定于其时区的用户的情况下。基于本地时间的计划需要存储本地时间。

在我的例子中,有一个属性是包含 future 事件的 start_time 的时间戳,与现在或过去的所有其他内容(包括 created_atupdated_at 时间戳)相比。

为什么

此特定字段是 的时间戳。 future 事件用户选择时间的地方。

对于 future 的事件,最好的做法似乎是 not to store UTC .

Instead of saving the time in UTC along with the time zone, developers can save what the user expects us to save: the wall time.



当用户选择上午 10 点时,即使用户与 UTC 的偏移量因夏令时在创建日期和事件日期之间发生变化,它也需要保持在上午 10 点。

因此,在 2016 年 6 月,如果用户在 2017 年 1 月 1 日午夜在悉尼创建一个事件,则该时间戳将作为 2017-01-01 00:00 存储在数据库中。 .创建时的偏移量将是 +10:00,但在事件发生时,它将是 +11:00.. 除非政府决定同时改变它。

同样,我希望我在 2016 年 1 月 1 日午夜在布里斯类创建的单独事件也存储为 2017-01-01 00:00 .我存储时区,即 Australia/Brisbane在一个单独的领域。

在 Rails 中执行此操作的最佳实践方法是什么?

我尝试了很多选择,但都没有成功:

1.跳过转换

问题,这只会跳过读取转换,而不是写入。
self.skip_time_zone_conversion_for_attributes = [:start_time]

2.将整个应用配置更改为使用 config.default_timestamp :local
为此,我设置:

配置/应用程序.rb
config.active_record.default_timezone = :local
config.time_zone = 'UTC'

应用程序/模型/事件.rb
...
self.skip_time_zone_conversion_for_attributes = [:start_time]
before_save :set_timezone_to_location
after_save :set_timezone_to_default

def set_timezone_to_location
Time.zone = location.timezone
end

def set_timezone_to_default
Time.zone = 'UTC'
end
...

坦率地说,我不确定这是在做什么……但不是我想要的。

我认为它正在工作,因为我的布里斯类事件存储为 2017-01-01 00:00但是当我为悉尼创建一个新事件时,它被存储为 2017-01-01 01:00即使它在 View 中正确显示为午夜。

既然如此,我担心悉尼事件仍然存在我试图避免的相同问题。

3.覆盖模型的getter和setter以存储为整数

我还尝试将事件 start_time 作为整数存储在数据库中。

我尝试通过猴子修补 Time 类并添加 before_validates 来做到这一点。回调进行转换。

配置/初始化程序/time.rb
class Time
def time_to_i
self.strftime('%Y%m%d%H%M').to_i
end
end

应用程序/模型/事件.rb
before_validation :change_start_time_to_integer
def change_start_time_to_integer
start_time = start_time.to_time if start_time.is_a? String
start_time = start_time.time_to_i
end

# read value from DB
# TODO: this freaks out with an error currently
def start_time
#take integer YYYYMMDDHHMM and convert it to timestamp
st = self[:start_time]
Time.new(
st / 100000000,
st / 1000000 % 100,
st / 10000 % 100,
st / 100 % 100,
st % 100,
0,
offset(true)
)
end

理想的解决方案

我希望能够将时间戳以其自然数据类型存储在数据库中,这样查询就不会在我的 Controller 中变得困惑,但我不知道如何存储不转换的“墙上时间”。

其次,如果必须,我会选择整数选项。

其他人如何处理这个问题?我错过了什么?特别是使用上面的“整数转换”选项,我让事情变得比需要的复杂得多。

最佳答案

我建议您仍然使用第一个选项,但有一点技巧:本质上,您可以关闭所需属性的时区转换,并使用自定义 setter 来克服属性写入期间的转换。

该技巧将时间节省为 假UTC时间 .虽然从技术上讲它有一个 UTC 区域(因为所有时间都保存在 UTC 的数据库中)但是 根据定义无论当前时区如何,它都应解释为本地时间。

class Model < ActiveRecord::Base
self.skip_time_zone_conversion_for_attributes = [:start_time]

def start_time=(time)
write_attribute(:start_time, time ? time + time.utc_offset : nil)
end
end

让我们在 Rails 控制台中测试一下:
$ rails c
>> future_time = Time.local(2020,03,30,11,55,00)
=> 2020-03-30 11:55:00 +0200

>> Model.create(start_time: future_time)
D, [2016-03-15T00:01:09.112887 #28379] DEBUG -- : (0.1ms) BEGIN
D, [2016-03-15T00:01:09.114785 #28379] DEBUG -- : SQL (1.4ms) INSERT INTO `models` (`start_time`) VALUES ('2020-03-30 11:55:00')
D, [2016-03-15T00:01:09.117749 #28379] DEBUG -- : (2.7ms) COMMIT
=> #<Model id: 6, start_time: "2020-03-30 13:55:00">

请注意,Rails 将时间保存为 11:55,在“假”UTC 区域中。

另请注意,从 create 返回的对象中的时间是错误的,因为在这种情况下,区域是从“UTC”转换而来的。每次设置 start_time 后,您都必须计算并重新加载对象。属性,以便可以发生区域转换跳过:
>> m = Model.create(start_time: future_time).reload
D, [2016-03-15T00:08:54.129926 #28589] DEBUG -- : (0.2ms) BEGIN
D, [2016-03-15T00:08:54.131189 #28589] DEBUG -- : SQL (0.7ms) INSERT INTO `models` (`start_time`) VALUES ('2020-03-30 11:55:00')
D, [2016-03-15T00:08:54.134002 #28589] DEBUG -- : (2.5ms) COMMIT
D, [2016-03-15T00:08:54.141720 #28589] DEBUG -- : Model Load (0.3ms) SELECT `models`.* FROM `models` WHERE `models`.`id` = 10 LIMIT 1
=> #<Model id: 10, start_time: "2020-03-30 11:55:00">

>> m.start_time
=> 2020-03-30 11:55:00 UTC

加载对象后, start_time属性是正确的,无论实际时区如何,都可以手动解释为本地时间。

我真的不明白为什么 Rails 的行为方式与 skip_time_zone_conversion_for_attributes 相关。配置选项...

更新:添加阅读器

我们还可以添加一个阅读器,以便我们自动解释本地时间保存的“假”UTC时间,而不会因时区变化而改变时间:
class Model < ActiveRecord::Base
# interprets time stored in UTC as local time without shifting time
# due to time zone change
def start_time
t = read_attribute(:start_time)
t ? Time.local(t.year, t.month, t.day, t.hour, t.min, t.sec) : nil
end
end

在 Rails 控制台中测试:
>> m = Model.create(start_time: future_time).reload
D, [2016-03-15T08:10:54.889871 #28589] DEBUG -- : (0.1ms) BEGIN
D, [2016-03-15T08:10:54.890848 #28589] DEBUG -- : SQL (0.4ms) INSERT INTO `models` (`start_time`) VALUES ('2020-03-30 11:55:00')
D, [2016-03-15T08:10:54.894413 #28589] DEBUG -- : (3.1ms) COMMIT
D, [2016-03-15T08:10:54.895531 #28589] DEBUG -- : Model Load (0.3ms) SELECT `models`.* FROM `models` WHERE `models`.`id` = 12 LIMIT 1
=> #<Model id: 12, start_time: "2020-03-30 11:55:00">

>> m.start_time
=> 2020-03-30 11:55:00 +0200

IE。 start_time以本地时间正确解释,即使它被存储为相同的小时和分钟,但在 UTC。

关于ruby-on-rails - 在 UTC 中保存日期时间有时不准确,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35859549/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com