我正在使用以下宝石:
timezone
tzinfo
我正在尝试在user
的时区发送一份时事通讯,以便他们在9:00:00收到时事通讯不是我的服务器所在的太平洋标准时间09:00:00。
我按名称存储时区,比如"太平洋时间(美国和加拿大)"。每个用户都有一个列user.time_zone
,其中存储了他们的特定时区。
我已经建立了一个job
,它每小时检查一次可以接收时事通讯的user
(即当地时间为上午9:00的user
)。
class NewsletterTimezoneJob < ActiveJob::Base
def self.perform
users = User.all.gets_newsletter
users.each do |user|
d = Date.current
t = "09:00:00 -0700"
t = t.to_time
newsletter_sendtime = DateTime.new(d.year, d.month, d.day, t.hour, t.min, t.sec, t.zone)
newsletter_sendtime = newsletter_sendtime.to_s
if user.is_in_time_around(newsletter_sendtime)
NewsletterMailer.send_mailer(user, newsletter_sendtime).deliver_later
end
end
end
end
我在user
上构建了一个方法,尝试查看用户的本地时间是否与newsletter_sendtime
匹配。
我添加了一个+/-5分钟的窗口来解释服务器速度慢的原因。
User.rb
:
def is_in_time_around(timeofday)
timeofday = timeofday.to_time
user_zoned = timeofday.in_time_zone(self.time_zone)
user_zoned = user_zoned.strftime("%r").to_time
timeofday = timeofday.strftime("%r").to_time
if user_zoned >= (timeofday - 5.minutes) && user_zoned <= (timeofday + 5.minutes)
true
end
end
此job
和method
应每小时运行一次,并每小时发送给不同时区的不同用户;然而,它目前只发送给"太平洋时间(美国和加拿大)"的用户,每个小时从不发送给任何其他"用户"。
我发现您的方法存在一些问题:代码似乎过于复杂,您很可能(正如Jon在上面的评论中所注意到的)只与太平洋时间上午9点进行比较。此外,在您的方法中,您需要遍历所有用户并比较每个用户的时区——当用户表增长时,这将导致糟糕的性能。
我建议一种完全相反的方法,简单地说就是:查找当前本地时间在上午9点左右的所有时区名称,然后选择这些时区中的用户
如果您正在使用Rails,那么使用内置方法一切皆有可能。有了这个答案的帮助,你可以在给定的时区(如太平洋时间)获得给定的时间(如上午9点),如下所示:
ActiveSupport::TimeZone["Pacific Time (US & Canada)"].parse("09:00:00")
# => Tue, 29 Mar 2016 09:00:00 PDT -07:00
TimeZone
模块还提供了all
方法来获取所有定义的时区。我们需要将所有时区的本地时间(四舍五入到小时)与所需时间(上午9点)进行比较,以选择当前时间为上午9点的区域。首先,让我们将当地时间四舍五入为小时:
Time.zone.now
# => Tue, 29 Mar 2016 13:46:25 CEST +02:00
Time.zone.parse("#{Time.zone.now.hour}:00:00")
# => Tue, 29 Mar 2016 13:00:00 CEST +02:00
接下来,将所有时区的四舍五入本地时间与上午9点进行比较,并获得匹配的时区名称:
local_time = Time.zone.parse("#{Time.zone.now.hour}:00:00")
ActiveSupport::TimeZone.all.select { |tz| tz.parse("09:00:00") == local_time }.map(&:name)
# => ["Greenland", "Mid-Atlantic"]
这一特殊结果表明,虽然布拉格的时间是13小时(下午1点),但例如,格陵兰地区的时间是9小时(上午9点)。
有了这些,剩下的就很容易了:
class NewsletterTimezoneJob < ActiveJob::Base
def self.perform
User.all.gets_newsletter.with_time_zone(zones_with_local_time("09:00")).each do |user|
NewsletterMailer.send_mailer(user).deliver_later
# for simplicity, I removed the `newsletter_sendtime` parameter from the mailer call
end
end
end
其中zones_with_local_time
是在任何地方定义的辅助函数,返回本地时间为给定时间的时区:
# if defined in the NewsletterTimezoneJob, it should be a class method
def self.zones_with_local_time(hhmm)
local_time = Time.zone.parse("#{Time.zone.now.hour}:00:00")
ActiveSupport::TimeZone.all.select { |tz| tz.parse("#{hhmm}:00") == local_time }.map(&:name)
end
with_time_zone
只是User
模型中作用于时区条件的一个范围:
# app/models/user.rb
scope :with_time_zone,
->(zones) { where("time_zone in (?)", zones) }
现在,您所要做的就是设置一个cron作业,使其每小时运行一次,例如一小时的第一分钟。
如果它帮助了某人,下面可能会帮助
将作业安排在前一天晚上8点(UTC)运行,因为在新西兰和其他一些时区,UTC晚上8点是上午9点。UTC晚上8点是任何时区的上午9点。
# group time_zones by utc_offset (In hours)
ActiveSupport::TimeZone.all.group_by { |tz| tz.utc_offset/3600.0 }.each do |utc_offset_in_hrs, time_zones|
User.where(time_zone: time_zones).each do |user|
# Schedule the email as per time_zone, using delayed_job's run_at param
# Using +13, as that is the highest/first timezone to have 9AM
NewsletterMailer.send_mailer(user).deliver_later(wait_until: 13.0 - utc_offset_in_hrs)
end
end