我正在使用以下宝石:
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
应每小时运行一次,每小时发送给不同时区的不同用户;但它目前只发送给“太平洋时间(美国和加拿大)”的用户,每个小时,而从不将其发送给任何其他`用户。
答案 0 :(得分:8)
我发现你的方法存在一些问题:代码看起来过于复杂,而你很可能(正如Jon在上面的评论中所注意到的那样)只比较9AM 太平洋时间。此外,使用您的方法,您需要遍历所有用户并比较每个用户的时区 - 这将导致用户表增长时性能不佳。
我建议采用完全相反的方法,用简单的话来说就是这样: 查找当前本地时间大约是上午9点的所有时区名称,然后在这些时区中选择用户。 强>
您正在使用Rails,然后使用内置方法可以实现一切。在this answer的帮助下,您可以在给定时区(例如太平洋时间)获得给定时间(例如上午9点),如下所示:
ActiveSupport::TimeZone["Pacific Time (US & Canada)"].parse("09:00:00")
# => Tue, 29 Mar 2016 09:00:00 PDT -07:00
TimeZone
模块还提供all
method以获取所有已定义的时区。我们需要将所有时区的当地时间(四舍五入到小时)与所需时间(9AM)进行比较,以选择当前时间为上午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
接下来,将这个舍入的本地时间与所有时区的9AM进行比较,并获得匹配的时区名称:
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作业,例如:一小时的第一分钟。
答案 1 :(得分:0)
如果它可以帮助某人,那么关注可能会有所帮助
安排工作在前一天的晚上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