在当地时区上午9:00向用户发送简报

时间:2016-03-28 18:45:16

标签: ruby-on-rails ruby ruby-on-rails-4 timezone

我正在使用以下宝石:

  • 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

jobmethod 应每小时运行一次,每小时发送给不同时区的不同用户;但它目前只发送给“太平洋时间(美国和加拿大)”的用户,每个小时,而从不将其发送给任何其他`用户。

2 个答案:

答案 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