Ruby on rails Rails—如何按时间对日期进行分组;你在不同的时区吗?
我有一系列约会,其中日期和时间存储在Ruby on rails Rails—如何按时间对日期进行分组;你在不同的时区吗?,ruby-on-rails,postgresql,datetime,Ruby On Rails,Postgresql,Datetime,我有一系列约会,其中日期和时间存储在start\u dateas DateTime下。我想把它们归类为早上、白天或晚上开始。我创建了一个带有标签和范围的散列数组,用于SQL语句,其中我使用CAST(EXTRACT(EPOCH FROM start_time)AS INT)将start_date记录转换为秒数。 这将获取一天中的秒数(86400),并根据开始日期的模数将约会标记为86400。但是,许多约会发生在不同的时区,但它们都存储为UTC。因此,美国东部时间上午8:00的约会相当于美国中部标准
start\u date
as DateTime下。我想把它们归类为早上、白天或晚上开始。我创建了一个带有标签和范围的散列数组,用于SQL语句,其中我使用CAST(EXTRACT(EPOCH FROM start_time)AS INT)将start_date
记录转换为秒数。
这将获取一天中的秒数(86400),并根据开始日期的模数将约会标记为86400。但是,许多约会发生在不同的时区,但它们都存储为UTC。因此,美国东部时间上午8:00的约会相当于美国中部标准时间上午7:00的约会,但两者在内部存储为UTC下午12:00。这将导致约会被错误地标记为“白天”,而实际上是“上午”(从预订的用户的角度来看)
理想情况下,我希望有某种方法根据用户的时区转换开始日期
,使其看起来像是在UTC发生的。因此,我希望东部标准时间下午12:00的预约被标记为UTC下午12:00的预约,而不是UTC下午4:00的预约。更具体地说,在执行模运算之前,我想从转换的开始日期中减去14400秒
我可以加入对用户的任命,其中包含用户的时区。如何将这些信息合并到上面的查询中,以便根据用户在同一记录中的时区,为每条记录使用修改后的开始日期
我知道我可以通过每个时区的循环,在每个循环中增加/减少特定的秒数,然后组合所有循环的结果来完成这一点,但我想知道是否有一种方法可以在一个查询中完成此操作。如果您不需要严格基于SQL的解决方案,您可以使用Ruby的select
方法提取此约会,如本例所示:
(我假设约会模型中存在某种保存时区名称的tz_name
字段)
morning_Appointment=Appointment.all.select do|a|
a、 开始日期。在时区(a.tz\u名称)。小时>6和&a.start\u日期。在时区(a.tz\u名称)。小时<20
结束
编辑:
感谢@moveson指出我的错误,我稍微改变了解决方案。根据我的评论,我假设我们有三个表:约会
,用户
,和首选项
。在约会中
我们有开始日期
和用户id
。在users
中,我们有首选项\u id
。在preferences
中,我们有一些列命名时区,因此我将其称为tz_name
注意:Postgres时区函数比较混乱。我强烈建议你仔细阅读。这是一个很好的开始
可以使用纯SQL生成时间范围并返回分组结果。如果您需要一次标记和分组多个记录(数千条或更多),那么纯SQL解决方案将是最好的
假设您一次处理1000条或更少的记录,您可能会希望使用Rails作用域,因为这将给您一个ActiveRecord结果。然后使用Ruby的数组方法进行分组
该解决方案看起来是这样的:
#app/user.rb
类用户<应用程序记录
属于:偏好
你有很多约会吗
结束
#app/appointment.rb
班级预约<申请记录
属于:用户
范围:用秒,λ{
联接(用户::首选项)
.select('appoints.*,extract(时区的历元(tz_name,start_date::timestamptz)::time::int as seconds'))
}
#此方法是可选的。如果被排除,则呼叫#秒
#在约会实例上,如果
#.with_秒范围尚未应用。
def秒数
属性['seconds']
结束
def时间范围
返回零,除非秒。是否存在?
如果秒数介于?(0,32399)
“早上好”
elsif秒之间?(32_400,61_199)
“白天”
其他的
“晚上”
结束
结束
结束
范围的select
部分可能需要一些解释
start\u date::timestamptz
:获取存储为Postgrestimestamp
的开始日期,并将其转换为Postgres服务器时区(大概是UTC)中带时区的Postgres时间戳
时区(tz\u name,start\u date::timestamptz)
:将带有时区的时间戳
转换回时间戳
键入tz\u name
时区的本地时间
时区(tz\u name,start\u date::timestamptz)::time
:删除日期并保留时间组件
然后我们从时间分量中提取历元,并将其转换为秒
最后,我们将结果转换为整数,以避免在确定时间范围时出现任何问题
现在您可以执行以下操作:
Appointment.all.with_seconds.group_by(&:时间范围)
或
对于将返回按三个时间范围分组的ID的纯SQL解决方案,请将此方法添加到约会模型中:
def self.grouped\u by\u time\u range
当前\u范围=带有\u秒到\u sql
query=用户的时区是如何存储的?@moveson它实际上是以字符串的形式存储在“首选项”表中,名称基于tz数据库约定。因此,EST用户有一个关联偏好,即“美国/多伦多”。您可以通过以下方式访问它:Appointment.joins(user::preference)好的,为了清楚起见,我们有三个表:约会
,用户
,和首选项
。在约会中
我们有开始日期
和用户id
。在users
中,我们有首选项\u id
。在首选项中TIME_RANGES = [
{label: "Morning", min: 0, max: 32399},
{label: "Daytime", min: 32400, max: 61199},
{label: "Evening", min: 61200, max: 86399}
]
cases = TIME_RANGES.map do |r|
"when CAST (EXTRACT (EPOCH FROM start_date) AS INT) % 86400 between '#{r[:min]}' and '#{r[:max]}' then '#{r[:label]}'"
end
time_ranges = Appointment.select("count(*) as n, case #{time_cases.join(' ')} end as time_range").group('time_range')
morning_appointments = Appointment.all.select do |a|
a.start_date.in_time_zone(a.tz_name).hour > 6 && a.start_date.in_time_zone(a.tz_name).hour < 20
end
user = User.first
user.appointments.with_seconds.group_by(&:time_range)