Class: TimeZoneScheduler

Inherits:
Object
  • Object
show all
Defined in:
lib/time_zone_scheduler.rb,
lib/time_zone_scheduler/view.rb,
lib/time_zone_scheduler/version.rb

Overview

A Ruby library that assists in scheduling events whilst taking time zones into account. E.g. when to best deliver notifications such as push notifications or emails.

It relies on ActiveSupport’s time and time zone functionality and expects a current system time zone to be specified through Time.zone.

Terminology

Consider a server sending notifications to a user:

  • system time: The local time of the server in the current time zone, as specified with Time.zone.
  • reference time: The time that needs to be e.g. converted into the user’s destination time zone.
  • destination time zone: The time zone that the user resides in.
  • destination time: The local time of the time zone that the user resides in.

Defined Under Namespace

Modules: View

Constant Summary collapse

VERSION =
"0.2.0"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(destination_time_zone) ⇒ TimeZoneScheduler

Returns a new instance of TimeZoneScheduler.

Parameters:

  • destination_time_zone (String, ActiveSupport::TimeZone)

    the destination time zone that calculations will be performed in.



30
31
32
# File 'lib/time_zone_scheduler.rb', line 30

def initialize(destination_time_zone)
  @destination_time_zone = Time.find_zone!(destination_time_zone)
end

Instance Attribute Details

#destination_time_zoneActiveSupport::TimeZone (readonly)

Returns the destination time zone for the various calculations this class performs.

Returns:

  • (ActiveSupport::TimeZone)

    the destination time zone for the various calculations this class performs.



25
26
27
# File 'lib/time_zone_scheduler.rb', line 25

def destination_time_zone
  @destination_time_zone
end

Instance Method Details

#in_timeframe?(reference_time, timeframe) ⇒ Boolean

This checks if the reference time falls in the given timeframe in the destination time zone.

For instance, you could use this to disable playing a sound for notifications that have to be scheduled in real time, but you don’t necessarily want to e.g. wake the user.

Examples:

Return that 1PM in the Europe/Amsterdam time zone falls in the timeframe.


Time.zone      = "UTC" # Set the system time zone
scheduler      = TimeZoneScheduler.new("Europe/Amsterdam")
reference_time = Time.parse("2015-10-25 12:00 UTC")

p scheduler.in_timeframe?(reference_time, "08:00".."14:00") # => true

Return that 3PM in the Europe/Moscow time zone falls outside the timeframe.


Time.zone      = "UTC" # Set the system time zone
scheduler      = TimeZoneScheduler.new("Europe/Moscow")
reference_time = Time.parse("2015-10-25 12:00 UTC")

p scheduler.in_timeframe?(reference_time, "08:00".."14:00") # => true

Parameters:

  • reference_time (Time)

    the reference time that’s to be checked if it falls in the timeframe in the destination time zone.

  • timeframe (Range<String..String>)

    a range of times (of the day) in which the reference time should fall.

Returns:

  • (Boolean)

    whether or not the reference time falls in the specified timeframe in the destination time zone.



172
173
174
# File 'lib/time_zone_scheduler.rb', line 172

def in_timeframe?(reference_time, timeframe)
  TimeFrame.new(@destination_time_zone, reference_time, timeframe).reference_in_timeframe?
end

#schedule_in_timeframe(reference_time, timeframe) ⇒ Time

This calculation schedules the time to be at the same time as the reference time (real time), except when that time, in the destination time zone, falls outside of the specified timeframe. In that case it delays the time until the next minimum time of the timeframe is reached.

For instance, you could use this to schedule notifications about an event starting in either real-time, if that’s a good time for the user in their time zone, or otherwise delay it to the next good time.

Examples:

Return the real time, as the reference time falls in the specified timeframe in the Europe/Amsterdam time zone.


Time.zone      = "UTC" # Set the system time zone
scheduler      = TimeZoneScheduler.new("Europe/Amsterdam")
reference_time = Time.parse("2015-10-25 12:00 UTC")
system_time    = scheduler.schedule_in_timeframe(reference_time, "10:00".."14:00")
local_time     = system_time.in_time_zone("Europe/Amsterdam")

p reference_time # => Sun, 25 Oct 2015 12:00:00 UTC +00:00
p system_time    # => Sun, 25 Oct 2015 12:00:00 UTC +00:00
p local_time     # => Sun, 25 Oct 2015 13:00:00 CET +01:00

Delay the reference time so that it’s not scheduled before 10AM in the Pacific/Kiritimati time zone.


Time.zone      = "UTC" # Set the system time zone
scheduler      = TimeZoneScheduler.new("Pacific/Kiritimati")
reference_time = Time.parse("2015-10-25 12:00 UTC")
system_time    = scheduler.schedule_in_timeframe(reference_time, "10:00".."14:00")
local_time     = system_time.in_time_zone("Pacific/Kiritimati")

p reference_time # => Sun, 25 Oct 2015 12:00:00 UTC +00:00
p system_time    # => Sun, 25 Oct 2015 20:00:00 UTC +00:00
p local_time     # => Mon, 26 Oct 2015 10:00:00 LINT +14:00

Delay the reference time so that it’s not scheduled after 2PM in the Europe/Moscow time zone.


Time.zone      = "UTC" # Set the system time zone
scheduler      = TimeZoneScheduler.new("Europe/Moscow")
reference_time = Time.parse("2015-10-25 12:00 UTC")
system_time    = scheduler.schedule_in_timeframe(reference_time, "10:00".."14:00")
local_time     = system_time.in_time_zone("Europe/Moscow")

p reference_time # => Sun, 25 Oct 2015 12:00:00 UTC +00:00
p system_time    # => Mon, 26 Oct 2015 07:00:00 UTC +00:00
p local_time     # => Mon, 26 Oct 2015 10:00:00 MSK +03:00

Parameters:

  • reference_time (Time)

    the reference time that’s to be re-scheduled in the destination time zone if it falls outside the timeframe.

  • timeframe (Range<String..String>)

    a range of times (of the day) in which the scheduled time should fall.

Returns:

  • (Time)

    either the original reference time, if it falls in the timeframe, or the delayed time.



131
132
133
134
135
136
137
138
139
140
# File 'lib/time_zone_scheduler.rb', line 131

def schedule_in_timeframe(reference_time, timeframe)
  timeframe = TimeFrame.new(@destination_time_zone, reference_time, timeframe)
  if timeframe.reference_before_timeframe?
    timeframe.min
  elsif timeframe.reference_after_timeframe?
    timeframe.min.tomorrow
  else
    reference_time
  end.in_time_zone(Time.zone)
end

#schedule_on_date(reference_time, raise_if_time_has_passed = true) ⇒ Time

This calculation takes the local date and time of day of the reference time and converts that to the exact same date and time of day in the destination time zone and returns it in the system time. In other words, you’d use this to calculate the system time at which a specific date and time of day occurs in the destination time zone.

For instance, you could use this to schedule notifications that should be sent to users on specific days of the week at times of the day that they are most likely to be good for the user. E.g. every Thursday at 10AM.

Examples:

Calculate the system time that corresponds to Sunday 2015-10-25 at 10AM in the Pacific/Niue time zone.


Time.zone      = "Pacific/Kiritimati" # Set the system time zone
scheduler      = TimeZoneScheduler.new("Pacific/Niue")
reference_time = Time.parse("2015-10-25 10:00 UTC")
system_time    = scheduler.schedule_on_date(reference_time, false)

p reference_time # => Sun, 25 Oct 2015 10:00:00 UTC +00:00
p system_time    # => Mon, 26 Oct 2015 11:00:00 LINT +14:00

p system_time.sunday? # => false
p system_time.hour    # => 11

p local_time = system_time.in_time_zone("Pacific/Niue")
p local_time.sunday? # => true
p local_time.hour    # => 10

Parameters:

  • reference_time (Time)

    the reference date and time of day that’s to be scheduled in the destination time zone.

  • raise_if_time_has_passed (Boolean) (defaults to: true)

    whether or not to check if the time in the destination time zone has already passed.

Returns:

  • (Time)

    the system time that corresponds to the time scheduled in the destination time zone.

Raises:

  • (ArgumentError)

    in case the check is enabled, this is raised if the time in the destination time zone has already passed.



70
71
72
73
74
75
76
77
# File 'lib/time_zone_scheduler.rb', line 70

def schedule_on_date(reference_time, raise_if_time_has_passed = true)
  destination_time = @destination_time_zone.parse(reference_time.strftime('%F %T'))
  system_time = destination_time.in_time_zone(Time.zone)
  if raise_if_time_has_passed && system_time < Time.zone.now
    raise ArgumentError, "The specified time has already passed in the #{@destination_time_zone.name} timezone."
  end
  system_time
end