Slide 1

Slide 1 text

@AaronLasseigne 12 6 9 3 1 2 11 10 5 4 7 8 Time, Time Zones and Rails It’s About Time

Slide 2

Slide 2 text

@AaronLasseigne Ruby

Slide 3

Slide 3 text

@AaronLasseigne •Stores a year, month, and day. •Part of the standard library. require  'date' http://www.ruby-doc.org/stdlib-2.0/libdoc/date/rdoc/Date.html Date

Slide 4

Slide 4 text

@AaronLasseigne •Stores a year, month, day, hour, minute, second, fraction of a second, and a UTC offset. •Part of the standard library. require  'date' http://www.ruby-doc.org/stdlib-2.0/libdoc/date/rdoc/DateTime.html DateTime

Slide 5

Slide 5 text

@AaronLasseigne http://www.ruby-doc.org/core-2.0/Time.html •Stores a year, month, day, hour, minute, second, fraction of a second, and a UTC offset or a time zone (only local time or UTC). •Stored as the number of seconds since the Epoch, January 1, 1970 00:00 UTC. •Part of the Ruby core. Time

Slide 6

Slide 6 text

@AaronLasseigne • Implemented in Ruby • Large Date Range • UTC Offset Support • Small API •Implemented in C (POSIX) •Limited Overall Date Range •UTC Offset Support •Limited Zone Support •Large API vs Time DateTime 1.8.7 Edition

Slide 7

Slide 7 text

@AaronLasseigne • Implemented in Ruby • Large Date Range • UTC Offset Support • Small API •Implemented in C (POSIX) •Limited Overall Date Range •UTC Offset Support •Limited Zone Support •Large API vs Time DateTime 1.9.2 Edition

Slide 8

Slide 8 text

@AaronLasseigne • Implemented in Ruby • Large Date Range • UTC Offset Support • Small API •Implemented in C (POSIX) •Limited Overall Date Range •UTC Offset Support •Limited Zone Support •Large API vs Time DateTime 1.9.3+ Edition

Slide 9

Slide 9 text

@AaronLasseigne Winner: Time

Slide 10

Slide 10 text

@AaronLasseigne Time Offset vs Time Zone

Slide 11

Slide 11 text

@AaronLasseigne Jan Feb Mar Apr May Jun July Aug Sept Oct Nov Dec “CST” – Central Standard Time (-6:00) “CDT” – Central Daylight Time (-5:00) Jan - Dec – “Central Time (US & Canada)”

Slide 12

Slide 12 text

@AaronLasseigne DateTime doesn’t support time zones it supports offsets from UTC. Time offsets? irb>  DateTime.new(2013,  1,  1,  0,  0,  0,  'CST').to_s  #  'CST'  or  '-­‐6:00' #  =>  "2013-­‐01-­‐01T00:00:00-­‐06:00" irb>  DateTime.new(2013,  1,  1,  0,  0,  0,  'CDT').to_s  #  'CDT'  or  '-­‐5:00' #  =>  "2013-­‐01-­‐01T00:00:00-­‐05:00" irb>  DateTime.new(2013,  1,  1,  0,  0,  0,  'Central  Time  (US  &  Canada)').to_s #  =>  "2013-­‐01-­‐01T00:00:00+00:00"

Slide 13

Slide 13 text

@AaronLasseigne Time doesn’t even support the abbreviated names. Time offsets? irb>  Time.new(2013,  1,  1,  0,  0,  0,  'CST').to_s #  =>  ArgumentError:  "+HH:MM"  or  "-­‐HH:MM"  expected  for  utc_offset irb>  Time.new(2013,  1,  1,  0,  0,  0,  '-­‐06:00').to_s #  =>  "2013-­‐01-­‐01  00:00:00  -­‐0600" irb>  Time.new(2013,  1,  1,  0,  0,  0,  '-­‐05:00').to_s #  =>  "2013-­‐01-­‐01  00:00:00  -­‐0500"

Slide 14

Slide 14 text

@AaronLasseigne Time offsets? irb>  Time.new(2013,  1,  1,  0,  0,  0).to_s #  =>  "2013-­‐01-­‐01  00:00:00  -­‐0600" irb>  Time.new(2013,  6,  1,  0,  0,  0).to_s #  =>  "2013-­‐06-­‐01  00:00:00  -­‐0500" irb>  Time.utc(2013,  1,  1,  0,  0,  0).to_s #  =>  "2013-­‐01-­‐01  00:00:00  UTC"

Slide 15

Slide 15 text

@AaronLasseigne What is UTC?

Slide 16

Slide 16 text

@AaronLasseigne “The primary time standard by which the world regulates clocks and time.[1]” •Formalized in 1963 by the International Radio Consultative Committee in Recommendation 374 •Based on UT1 and kept in sync with leap seconds. UTC 1. ^ Wikipedia “Coordinated Universal Time” Retrieved 29 August 2013

Slide 17

Slide 17 text

@AaronLasseigne •Commonly denoted with a “z” or “Zulu”. •The “z” can also mean “zero”. UTC ± 00:00 irb>  Time.parse('2012-­‐1-­‐1  12:00:00z') #  =>  2012-­‐01-­‐01  12:00:00  UTC

Slide 18

Slide 18 text

@AaronLasseigne What does UTC stand for?

Slide 19

Slide 19 text

@AaronLasseigne International Telecommunication Union and the International Astronomical Union

Slide 20

Slide 20 text

@AaronLasseigne Coordinated Universal Time CUT

Slide 21

Slide 21 text

@AaronLasseigne Temps Universel Coordonné TUC

Slide 22

Slide 22 text

@AaronLasseigne Winner: UTC English: Universal Time Coordinated French: Universel Temps Coordonné

Slide 23

Slide 23 text

@AaronLasseigne Rails

Slide 24

Slide 24 text

@AaronLasseigne ActiveSupport

Slide 25

Slide 25 text

@AaronLasseigne ActiveSupport::TimeZone

Slide 26

Slide 26 text

@AaronLasseigne http://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html ActiveSupport::TimeZone #  application.rb: class  Application  <  Rails::Application    config.time_zone  =  'Central  Time  (US  &  Canada)' end Time.zone            #  =>  # Time.zone.name  #  =>  "Central  Time  (US  &  Canada)" Time.zone.now    #  =>  Tue,  03  Sep  2013  19:00:00  CDT  -­‐05:00 Time.zone  =  'Eastern  Time  (US  &  Canada)' Time.zone.now    #  =>  Tue,  03  Sep  2013  20:00:00  EDT  -­‐4:00

Slide 27

Slide 27 text

@AaronLasseigne ActiveSupport::TimeWithZone

Slide 28

Slide 28 text

@AaronLasseigne irb>  Time.now.class #  =>  Time  <  Object irb>  Time.zone.class #  =>  ActiveSupport::TimeZone  <  Object irb>  Time.zone.now.class #  =>  ActiveSupport::TimeWithZone  <  Object Let’s review!

Slide 29

Slide 29 text

@AaronLasseigne Provides second, minute, hour, day, week, fortnight, month, and year. Time Using Numbers irb>  1.second #  =>  1  second irb>  1.minute #  =>  60  seconds irb>  1.month #  =>  2592000  seconds

Slide 30

Slide 30 text

@AaronLasseigne •All have a plural version for better readability. •They work with Floats (except months and years). •All return seconds. Time Using Numbers

Slide 31

Slide 31 text

@AaronLasseigne irb>  1.year.ago #  =>  Mon,  03  Sep  2012  19:00:00  CDT  -­‐05:00 irb>  1.year.from_now #  =>  Wed,  03  Sep  2014  19:00:00  CDT  -­‐05:00 Time From Numbers

Slide 32

Slide 32 text

@AaronLasseigne irb>  Time.days_in_month(6)  #  optional  year  argument #  =>  30 irb>  Time.find_zone!('Eastern  Time  (US  &  Canada)') #  =>  # irb>  Time.use_zone('Eastern  Time  (US  &  Canada)')  do              Time.zone.now          end #  =>  Tue,  03  Sep  2013  20:00:00  EDT  -­‐4:00 Additions to Time

Slide 33

Slide 33 text

@AaronLasseigne ActiveRecord

Slide 34

Slide 34 text

@AaronLasseigne Database Mappings Migration MySQL PostgreSQL Ruby timestamp datetime timestamp without time zone TimeWithZone date date date Date time time time without time zone Time

Slide 35

Slide 35 text

@AaronLasseigne •The date is set to Jan 1, 2000. •The time zone is set to UTC. How does a time field become a Time?

Slide 36

Slide 36 text

@AaronLasseigne rake  time:zones:all        Displays  all  time  zones,  also  available:  time:zones:us,  time:zones:local  -­‐-­‐        filter  with  OFFSET  parameter,  e.g.,  OFFSET=-­‐6            rake  -­‐D  time:zones:all term> What time zones are available?

Slide 37

Slide 37 text

@AaronLasseigne term>            rake  time:zones:local *  UTC  -­‐06:00  * Central  America Central  Time  (US  &  Canada) Guadalajara Mexico  City Monterrey Saskatchewan What time zones are available?

Slide 38

Slide 38 text

@AaronLasseigne term> term>  rake  time:zones:us What time zones are available? *  UTC  -­‐10:00  * Hawaii *  UTC  -­‐09:00  * Alaska *  UTC  -­‐08:00  * Pacific  Time  (US  &  Canada) *  UTC  -­‐07:00  * Arizona ...

Slide 39

Slide 39 text

@AaronLasseigne Adding Time Zones to Your Rails Project

Slide 40

Slide 40 text

@AaronLasseigne 1  -­‐>  2  -­‐>  3  -­‐>  4  -­‐>  5 Configure an application time zone. #  config/application.rb: class  Application  <  Rails::Application    ...    config.time_zone  =  'Central  Time  (US  &  Canada)'    ... end

Slide 41

Slide 41 text

@AaronLasseigne 1  -­‐>  2  -­‐>  3  -­‐>  4  -­‐>  5 Add a time zone field to the user table. term>  rails  generate  migration  add_time_zone_to_users  time_zone:string class  AddTimeZoneToUsers  <  ActiveRecord::Migration    def  change        add_column  :users,  :time_zone,  :string    end end

Slide 42

Slide 42 text

@AaronLasseigne 1  -­‐>  2  -­‐>  3  -­‐>  4  -­‐>  5 Add a validation on the user model. #  app/models/user.rb validates  :time_zone,    inclusion:  {in:  ActiveSupport::TimeZone.all.map(&:name)}

Slide 43

Slide 43 text

@AaronLasseigne 1  -­‐>  2  -­‐>  3  -­‐>  4  -­‐>  5 Let the user select a time zone. #  app/views/user_profile.html.erb <%=  form.time_zone_select(:time_zone,  ActiveSupport::TimeZone.us_zones)  %>

Slide 44

Slide 44 text

@AaronLasseigne 1  -­‐>  2  -­‐>  3  -­‐>  4  -­‐>  5 Use the time zone. #  app/controllers/application_controller.rb around_filter  :user_time_zone private def  user_time_zone(&block)    Time.use_zone(current_user.time_zone,  &block) end

Slide 45

Slide 45 text

@AaronLasseigne Gotchas and Best Practices

Slide 46

Slide 46 text

@AaronLasseigne Date

Slide 47

Slide 47 text

@AaronLasseigne Date.today

Slide 48

Slide 48 text

@AaronLasseigne Date.today

Slide 49

Slide 49 text

@AaronLasseigne Date.current

Slide 50

Slide 50 text

@AaronLasseigne DateTime

Slide 51

Slide 51 text

@AaronLasseigne Don’t use it.

Slide 52

Slide 52 text

@AaronLasseigne Time

Slide 53

Slide 53 text

@AaronLasseigne Don’t use it.

Slide 54

Slide 54 text

@AaronLasseigne Don’t use it. * Some parts are acceptable (e.g. use_zone) *

Slide 55

Slide 55 text

@AaronLasseigne Time.zone (ActiveSupport::TimeWithZone)

Slide 56

Slide 56 text

@AaronLasseigne Time.now.utc is the same as Time.zone.now.utc

Slide 57

Slide 57 text

@AaronLasseigne Time.now.utc is the same as Time.zone.now.utc

Slide 58

Slide 58 text

@AaronLasseigne irb>  ActiveSupport::TimeWithZone.name #  =>  "Time" It Lies!

Slide 59

Slide 59 text

@AaronLasseigne irb>  t1  =  Time.zone.now;  t2  =  Time.zone.now #  =>  Tue,  03  Sep  2013  19:00:00  CDT  -­‐05:00 irb>  t1  ==  t2 #  =>  false irb>  t1.usec #  =>  381876 irb>  t2.usec #  =>  381911 Comparing Time

Slide 60

Slide 60 text

@AaronLasseigne irb>  t1  =  Time.zone.now #  =>  Tue,  03  Sep  2013  19:00:00  CDT  -­‐05:00 irb>  t2  =  t1.in_time_zone('Eastern  Time  (US  &  Canada)') #  =>  Tue,  03  Sep  2013  20:00:00  EDT  -­‐04:00 irb>  t1  ==  t2 #  =>  true Comparing Time

Slide 61

Slide 61 text

@AaronLasseigne irb>  Time.zone.local(2013,  2,  1)  +  1.year #  =>  Sat,  01  Feb  2014  00:00:00  CST  -­‐06:00 irb>  t  =  Time.zone.local(2016,  2,  1) #  =>  Mon,  01  Feb  2016  00:00:00  CST  -­‐06:00 irb>  t.leap_year? #  =>  true irb>  t  +  1.year #  =>  Wed,  01  Feb  2017  00:00:00  CST  -­‐06:00 Let’s do some math.

Slide 62

Slide 62 text

@AaronLasseigne irb>  1.month  /  1.day #  =>  30 irb>  Time.zone.local(2013,  2,  1)  +  1.month #  =>  Fri,  01  Mar  2013  00:00:00  CST  -­‐06:00 irb>  Time.zone.local(2013,  2,  1)  +  (1.year  /  12) #  =>  Sun,  03  Mar  2013  10:30:00  CST  -­‐06:00 irb>  DateTime.new(2013,  2,  1)  +  (1.year  /  12) #  =>  Wed,  27  Mar  9213  00:00:00  +0000 Let’s do some math.

Slide 63

Slide 63 text

@AaronLasseigne irb>  t  =  Time.zone.local(2012,  6,  30).all_day #  =>  Sat,  30  Jun  2012  00:00:00  CDT  -­‐05:00..Sat,  30  Jun  2012  23:59:59  CDT  -­‐05:00 irb>  t.end.usec #  =>  999999 where(created_at:  Time.zone.now.all_day) #  =>  Records  created  that  day  relative  to  the  user's  time  zone. Ranges

Slide 64

Slide 64 text

@AaronLasseigne Testing

Slide 65

Slide 65 text

@AaronLasseigne Timecop

Slide 66

Slide 66 text

@AaronLasseigne #  app/models/assignment.rb: def  open?    (available_at..due_at).cover?(Time.zone.now) end Assignment

Slide 67

Slide 67 text

@AaronLasseigne #  spec/models/assignment_spec.rb: describe  '#open?'  do    context  'before  the  assignment  is  open'  do        it  'returns  false'  do            Timecop.freeze(assignment.available_at  -­‐  1.second)  do                expect(assignment.open?).to  be_false            end        end    end    ... end Testing Assignment

Slide 68

Slide 68 text

@AaronLasseigne #  app/models/assignment.rb: scope  :open,  -­‐>  do    where('available_at  <=  NOW()  AND  NOW()  <=  due_at') end Assignment

Slide 69

Slide 69 text

@AaronLasseigne #  app/models/assignment.rb: scope  :open,  -­‐>  do    current_time  =  Time.zone.now    where('available_at  <=  ?  AND  ?  <=  due_at',  current_time,  current_time) end Assignment

Slide 70

Slide 70 text

@AaronLasseigne #  spec/models/assignment_spec.rb: describe  '.open'  do    subject(:assignment)  {  create(:assignment)  }    context  'before  any  assignments  are  open'  do        it  'returns  no  records'  do            Timecop.freeze(assignment.available_at  -­‐  1.second)  do                expect(assignment.open).to  have(0).records            end        end    end    ... end Testing Assignment

Slide 71

Slide 71 text

@AaronLasseigne describe  '#yada'  do    context  'before  1900'  do        before  {  Timecop.freeze(Time.zone.local(1899))  }        after    {  Timecop.return  }        it  '.....'  {}        it  '.....'  {}        it  '.....'  {}    end    context  'after  1900'  do        ...    end end DRY it up.

Slide 72

Slide 72 text

@AaronLasseigne •Always use Time.zone. •Prefer Ruby/Rails over the database. •Test, test, test. Recap