Slide 1

Slide 1 text

Just in Time A case study in time zones Elle Meredith @aemeredith Just in time

Slide 2

Slide 2 text

4 Touch on time related libraries Just in time

Slide 3

Slide 3 text

4 Touch on time related libraries 4 Setup Rails to work with user's time_zone Just in time

Slide 4

Slide 4 text

4 Touch on time related libraries 4 Setup Rails to work with user's time zone 4 Play with Time in Rails Just in time

Slide 5

Slide 5 text

4 Touch on time related libraries 4 Setup Rails to work with user's time zone 4 Play with Time in Rails 4 Introduce a feature and work through it Just in time

Slide 6

Slide 6 text

Time DateTime Just in time

Slide 7

Slide 7 text

TZInfo Ruby timezone library, which provides daylight savings aware transformations between times in different timezones. Just in time

Slide 8

Slide 8 text

> TZInfo::Timezone.all.count => 582 Just in time

Slide 9

Slide 9 text

> TZInfo::Timezone.all.count => 582 > TZInfo::Country.get("GB").zone_identifiers => ["Europe/London"] Just in time

Slide 10

Slide 10 text

> TZInfo::Timezone.all.count => 582 > TZInfo::Country.get("GB").zone_identifiers => ["Europe/London"] > TZInfo::Country.get("AU").zone_identifiers => ["Australia/Lord_Howe", "Antarctica/Macquarie", "Australia/Hobart", ... Just in time

Slide 11

Slide 11 text

> TZInfo::Timezone.all.count => 582 > TZInfo::Country.get("GB").zone_identifiers => ["Europe/London"] > TZInfo::Country.get("AU").zone_identifiers => ["Australia/Lord_Howe", "Antarctica/Macquarie", "Australia/Hobart", ... > TZInfo::Country.get("US").zone_identifiers.count => 29 Just in time

Slide 12

Slide 12 text

ActiveSupport ::TimeZone Just in time

Slide 13

Slide 13 text

Limit the set of zones provided by TZInfo to a meaningful subset of 146 zones Just in time

Slide 14

Slide 14 text

Friendlier zones "America/New_York" => "Eastern Time (US & Canada)" Just in time

Slide 15

Slide 15 text

ActiveSupport ::TimeWithZone Just in time

Slide 16

Slide 16 text

$ 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

Slide 17

Slide 17 text

Checking current time zone # in console > Time.zone => #>>, @name="UTC", @tzinfo=#, @utc_offset=nil> Just in time

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

Stick with UTC Just in time

Slide 22

Slide 22 text

With user time zones create_table :users do |t| t.string :time_zone, default: "UTC" ... end Just in time

Slide 23

Slide 23 text

With user time zones 4 No: enums 4 Yes: strings Just in time

Slide 24

Slide 24 text

With user time zones: forms # Simple Form <%= f.input :time_zone %> Just in time

Slide 25

Slide 25 text

With user time zones: 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

Slide 26

Slide 26 text

With user time zones: displaying <%= time.in_time_zone(curent_user.time_zone) %> Just in time

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

3 different times 4 System time 4 Application time 4 Database time Just in time

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

> 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

Slide 35

Slide 35 text

> 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

Slide 36

Slide 36 text

> 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

Slide 37

Slide 37 text

> 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

Slide 38

Slide 38 text

> 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

Slide 39

Slide 39 text

> 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

Slide 40

Slide 40 text

> Time.zone.name => "Fiji" Just in time

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

> 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

Slide 44

Slide 44 text

> 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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

DON'T * Time.now DO * Time.current * 2.hours.ago Just in time

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

Testing time zones ActiveSupport::Testing::TimeHelpers travel_to 1.day do # do something tomorrow end travel_back Just in time

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

Recent Project Just in time

Slide 52

Slide 52 text

First go 4 Test suites that needed to run daily or weekly, 4 at a set time (1AM or 2AM), 4 and use ResqueScheduler to set the schedule to run the background workers. Just in time

Slide 53

Slide 53 text

ScheduleRule Just in time

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

So what was the problem? The scheduled tests were running at 2AM AEST regardless of the user’s time zone. Just in time

Slide 57

Slide 57 text

Second go 4 Test runs according to user's settings, at a specific week day, hour, and time zone. Just in time

Slide 58

Slide 58 text

Second go 4 Test runs according to user's settings, at a specific week day, hour, and time zone. 4 ResqueScheduler to run hourly and look for test suites that are due to run. Just in time

Slide 59

Slide 59 text

Second go 4 Test runs according to user's settings, at a specific week day, hour, and time zone. 4 ResqueScheduler to run hourly and look for test suites that are due to run. 4 The ResqueScheduler to look for test runs where the background job failed and thus due to be run as well. Just in time

Slide 60

Slide 60 text

ScheduleRule.rb every: string, wday: integer, hour: integer, time_zone: string, last_scheduled_run_at: timestamp, suite: references, ... Just in time

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

Can we do better? Just in time

Slide 65

Slide 65 text

Meet hour in utc Just in time

Slide 66

Slide 66 text

Third go 4 Remove the .current_zones method. 4 Introduce the :hour_in_utc column in ScheduleRule class. Just in time

Slide 67

Slide 67 text

Third go class AddHourInUtc < ActiveRecord::Migration def change add_column :schedule_rules, :hour_in_utc, :integer end end Just in time

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

Always work with UTC Just in time

Slide 74

Slide 74 text

A couple more takeaways 4 Use Time.current or Time.zone.today. 4 Use testing helper methods of your choice to freeze the time in your tests, preferably by using a block. Just in time

Slide 75

Slide 75 text

Thanks! 4 robots.thoughtbot.com 4 speakerdeck.com/aemeredith 4 @aemeredith Just in time