Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Just in time RailsIsrael

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

Just in time RailsIsrael

Talking about time zones in Ruby and Rails and working through a case study in time zones

Avatar for Elle Meredith

Elle Meredith

November 24, 2015
Tweet

More Decks by Elle Meredith

Other Decks in Programming

Transcript

  1. Just in Time A case study in time zones Elle

    Meredith @aemeredith Just in time
  2. Today — Touch on time related libraries — Set up

    Rails to work with user's time zone — Play with Time in Rails — Introduce a feature and work through it Just in time
  3. > TZInfo::Timezone.all.count => 582 > TZInfo::Country.get("IL").zone_identifiers => ["Asia/Jerusalem"] > TZInfo::Country.get("AU").zone_identifiers

    => ["Australia/Lord_Howe", "Antarctica/Macquarie", "Australia/Hobart", ... > TZInfo::Country.get("US").zone_identifiers.count => 29 Just in time
  4. Limit the set of zones provided by TZInfo to a

    meaningful subset of 146 zones Just in time
  5. $ rake time:zones:all * UTC -11:00 * American Samoa International

    Date Line West Midway Island Samoa * UTC -10:00 * Hawaii * UTC -09:00 * Alaska ... Just in time
  6. Checking current time zone # in console > Time.zone =>

    #<ActiveSupport::TimeZone:0x007fbf46947b38 @current_period=#<TZInfo::TimezonePeriod: nil,nil,#<TZInfo::TimezoneOffset: 0,0,UTC>>>, @name="UTC", @tzinfo=#<TZInfo::TimezoneProxy: Etc/UTC>, @utc_offset=nil> Just in time
  7. Setting a custom time zone # in console > Time.zone

    = "Perth" # in config/application.rb config.time_zone = "Perth" Just in time
  8. Setting a custom time zone # in console > Time.zone

    = "Perth" # in config/application.rb config.time_zone = "Perth" # ^ the default is "utc" Just in time
  9. Custom user time zone: setting # app/controllers/application_controller.rb around_action :set_time_zone, if:

    :current_user private def set_time_zone(&block) Time.use_zone(current_user.time_zone, &block) end Just in time
  10. ISO8601 and APIs > time = Time.now.utc.iso8601 => "2015-07-04T21:53:23Z" >

    Time.iso8601(time) => 2015-07-04 21:53:23 UTC Just in time
  11. > Time.zone.name => "UTC" > Time.now => 2015-07-04 17:53:23 -0400

    > Time.zone = "Fiji" => "Fiji" Just in time
  12. > Time.zone.name => "UTC" > Time.now => 2015-07-04 17:53:23 -0400

    > Time.zone = "Fiji" => "Fiji" > Time.zone.name => "Fiji" Just in time
  13. > Time.zone.name => "UTC" > Time.now => 2015-07-04 17:53:23 -0400

    > Time.zone = "Fiji" => "Fiji" > Time.zone.name => "Fiji" > Time.now => 2015-07-04 17:53:37 -0400 Just in time
  14. > Time.zone.name => "UTC" > Time.now => 2015-07-04 17:53:23 -0400

    > Time.zone = "Fiji" => "Fiji" > Time.zone.name => "Fiji" > Time.now => 2015-07-04 17:53:37 -0400 > Time.zone.now Just in time
  15. > Time.zone.name => "UTC" > Time.now => 2015-07-04 17:53:23 -0400

    > Time.zone = "Fiji" => "Fiji" > Time.zone.name => "Fiji" > Time.now => 2015-07-04 17:53:37 -0400 > Time.zone.now => Sun, 05 Jul 2015 09:53:42 FJT +12:00 Just in time
  16. > Time.zone = "Fiji" => "Fiji" > Time.zone.name => "Fiji"

    > Time.now => 2015-07-04 17:53:37 -0400 > Time.zone.now => Sun, 05 Jul 2015 09:53:42 FJT +12:00 > Time.current Just in time
  17. > Time.zone.name => "Fiji" > Time.now => 2015-07-04 17:53:37 -0400

    > Time.zone.now => Sun, 05 Jul 2015 09:53:42 FJT +12:00 > Time.current => Sun, 05 Jul 2015 09:54:17 FJT +12:00 Just in time
  18. > Time.zone = "Fiji" => "Fiji" > Time.zone.name => "Fiji"

    > Time.now => 2015-07-04 17:53:37 -0400 > Time.zone.now => Sun, 05 Jul 2015 09:53:42 FJT +12:00 > Time.current => Sun, 05 Jul 2015 09:54:17 FJT +12:00 > Time.now.in_time_zone => Sun, 05 Jul 2015 09:56:57 FJT +12:00 Just in time
  19. > Time.zone.name => "Fiji" > Date.today => Sat, 04 Jul

    2015 > Time.zone.today => Sun, 05 Jul 2015 Just in time
  20. > Time.zone.name => "Fiji" > Date.today => Sat, 04 Jul

    2015 > Time.zone.today => Sun, 05 Jul 2015 > Time.zone.tomorrow => Mon, 06 Jul 2015 Just in time
  21. > Time.zone.name => "Fiji" > Date.today => Sat, 04 Jul

    2015 > Time.zone.today => Sun, 05 Jul 2015 > Time.zone.tomorrow => Mon, 06 Jul 2015 > 1.day.from_now => Mon, 06 Jul 2015 10:00:56 FJT +12:00 Just in time
  22. Time zone related querying Post.where("published_at > ?", Time.current) # SELECT

    "posts".* FROM "posts" # WHERE (published_at > # '2015-07-04 17:45:01.452465') Just in time
  23. DON'T * Time.parse("2015-07-04 17:05:37") * Time.strptime(string, "%Y-%m-%dT%H:%M:%S%z") DO * Time.zone.parse("2015-07-04

    17:05:37") * Time.strptime( string, "%Y-%m-%dT%H:%M:%S%z" ).in_time_zone Just in time
  24. Testing time zones: gems # Timecop Timecop.freeze new_time Timecop.travel new_time

    Timecop.return Time.use_zone("Sydney") do … end # Delorean Delorean.time_travel_to("1 month ago") do … end Delorean.back_to_the_present # Zonebie Zonebie.set_random_timezone Just in time
  25. First go — Test suites that needed to run daily

    or weekly, — at a set time (1AM or 2AM), — and use ResqueScheduler to set the schedule to run the background workers. Just in time
  26. ResqueScheduler # config/resque_schedule.yml weekly_test: cron: "0 1 * * 0"

    class: ScheduledWeeklyRunsWorker args: description: 'Run test suites weekly' daily_test: cron: "0 2 * * *" class: ScheduledDailyRunsWorker args: description: 'Run test suites daily' Just in time
  27. Cron Syntax * * * * * command to execute

    ┬ ┬ ┬ ┬ ┬ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └───── day of week (0 - 7) from Sunday │ │ │ └────────── month (1 - 12) │ │ └─────────────── day of month (1 - 31) │ └──────────────────── hour (0 - 23) └───────────────────────── min (0 - 59) Just in time
  28. So what was the problem? The scheduled tests were running

    at 2AM AEST regardless of the user’s time zone. Just in time
  29. Second go — Test runs according to user's settings, at

    a specific week day, hour, and time zone. — ResqueScheduler to run hourly and look for test suites that are due to run. — The ResqueScheduler to look for test runs where the background job failed and thus due to be run as well. Just in time
  30. Still second go # lib/extensions.rb module ActiveSupport class TimeZone def

    self.current_zones(hour) all.select { |zone| t = Time.current.in_time_zone(zone) t.hour == hour }.map(&:tzinfo).map(&:name) end end end Just in time
  31. Still second go # app/models/schedule_rule.rb class ScheduleRule < ActiveRecord::Base def

    self.find_rules(options={}) hour = options[:hour] where(zone: ActiveSupport::TimeZone.current_zones(hour)). where(options) end def self.suites_to_run(time_ago=Time.current, options={}) find_rules(options). older_than(time_ago). map(&:suite) end end Just in time
  32. Still second go # app/workers/scheduled_runs_worker.rb class ScheduledRunsWorker def self.perform ScheduleRule.

    run_scheduled. daily. suites_to_run(yesterday, {hour: Time.now.utc.hour}) end end Just in time
  33. Third go — Remove the .current_zones method. — Introduce the

    :hour_in_utc column in ScheduleRule class. Just in time
  34. Still third go # app/models/schedule_rule.rb class ScheduleRule < ActiveRecord::Base before_save

    :set_hour_in_utc private def set_hour_in_utc self.hour_in_utc = ActiveSupport::TimeZone[zone]. parse("#{hour}:00:00"). utc. hour end end Just in time
  35. Still third go # app/models/schedule_rule.rb class ScheduleRule < ActiveRecord::Base def

    self.suites_to_run(time_ago=Time.current, options={}) where(options). older_than(time_ago). map(&:suite) end end Just in time
  36. Worker didn't change # app/workers/scheduled_runs_worker.rb class ScheduledRunsWorker def self.perform ScheduleRule.

    run_scheduled. daily. suites_to_run(yesterday, {hour: Time.now.utc.hour}) end end Just in time
  37. Previously # app/models/schedule_rule.rb class ScheduleRule < ActiveRecord::Base def self.find_rules(options={}) hour

    = options[:hour] where(zone: ActiveSupport::TimeZone.current_zones(hour)). where(options) end def self.suites_to_run(time_ago=Time.current, options={}) find_rules(options). older_than(time_ago). map(&:suite) end end Just in time
  38. A couple more takeaways — Use Time.current or Time.zone.today. —

    Freeze the time in your tests, preferably by using a block. Just in time