Just in time RailsIsrael

Just in time RailsIsrael

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

492d339a2ec66fa8d80e937abddb58e6?s=128

Elle Meredith

November 24, 2015
Tweet

Transcript

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

    Meredith @aemeredith Just in time
  2. Just in time

  3. Just in time

  4. Just in time

  5. Just in time

  6. 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
  7. Time DateTime Just in time

  8. TZInfo Ruby timezone library, which provides daylight savings aware transformations

    between times in different timezones. Just in time
  9. > TZInfo::Timezone.all.count => 582 Just in time

  10. > TZInfo::Timezone.all.count => 582 > TZInfo::Country.get("IL").zone_identifiers => ["Asia/Jerusalem"] Just in

    time
  11. > 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", ... Just in time
  12. > 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
  13. ActiveSupport ::TimeZone Just in time

  14. Limit the set of zones provided by TZInfo to a

    meaningful subset of 146 zones Just in time
  15. Friendlier display "America/New_York" => "Eastern Time (US & Canada)" "Asia/Jerusalem"

    => Jerusalem Just in time
  16. ActiveSupport ::TimeWithZone Just in time

  17. $ 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
  18. 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
  19. Setting a custom time zone # in console > Time.zone

    = "Perth" Just in time
  20. Setting a custom time zone # in console > Time.zone

    = "Perth" # in config/application.rb config.time_zone = "Perth" Just in time
  21. 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
  22. Stick with UTC Just in time

  23. Custom user time zone create_table :users do |t| t.string :time_zone,

    default: "UTC" ... end Just in time
  24. No enums Yes strings Just in time

  25. Custom user time zone: forms # Simple Form <%= f.input

    :time_zone %> Just in time
  26. 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
  27. Custom user time zone: displaying <%= time.in_time_zone(curent_user.time_zone) %> Just in

    time
  28. ISO8601 and APIs > time = Time.now.utc.iso8601 => "2015-07-04T21:53:23Z" Just

    in time
  29. 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
  30. 3 different times — System time — Application time —

    Database time Just in time
  31. > Time.zone.name => "UTC" Just in time

  32. > Time.zone.name => "UTC" > Time.now => 2015-07-04 17:53:23 -0400

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

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

    > Time.zone = "Fiji" => "Fiji" > Time.zone.name => "Fiji" Just in time
  35. > 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
  36. > 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
  37. > 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
  38. > 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
  39. > 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
  40. > 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
  41. > Time.zone.name => "Fiji" Just in time

  42. > Time.zone.name => "Fiji" > Date.today => Sat, 04 Jul

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

    2015 > Time.zone.today => Sun, 05 Jul 2015 Just in time
  44. > 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
  45. > 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
  46. 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
  47. DON'T * Time.now DO * Time.current * 2.hours.ago Just in

    time
  48. DON'T * Date.today * Date.today.to_time DO * Time.zone.today * 1.day.from_now

    Just in time
  49. 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
  50. Testing time zones ActiveSupport::Testing::TimeHelpers travel_to 1.day do # do something

    tomorrow end travel_back Just in time
  51. 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
  52. Recent Project Just in time

  53. 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
  54. ScheduleRule Just in time

  55. 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
  56. 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
  57. So what was the problem? The scheduled tests were running

    at 2AM AEST regardless of the user’s time zone. Just in time
  58. 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
  59. ScheduleRule.rb every: string, wday: integer, hour: integer, time_zone: string, last_scheduled_run_at:

    timestamp, suite: references, ... Just in time
  60. 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
  61. 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
  62. 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
  63. Can we do better? Just in time

  64. Meet hour in utc Just in time

  65. Third go — Remove the .current_zones method. — Introduce the

    :hour_in_utc column in ScheduleRule class. Just in time
  66. Third go class AddHourInUtc < ActiveRecord::Migration def change add_column :schedule_rules,

    :hour_in_utc, :integer end end Just in time
  67. 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
  68. 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
  69. 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
  70. 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
  71. Currently 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
  72. Always work with UTC Just in time

  73. 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
  74. Almost done — robots.thoughtbot.com — speakerdeck.com/aemeredith — @aemeredith — https://www.youtube.com/watch?v=-5wpm-gesOY

    Just in time
  75. Thanks! Just in time