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

It's About Time

It's About Time

An introduction to time in Ruby and an explanation of what Rails brings to the party. Also covered are the exceptions, inconsistencies, and edge cases that make working with time so painful.

Aaron Lasseigne

September 03, 2013
Tweet

More Decks by Aaron Lasseigne

Other Decks in Programming

Transcript

  1. @AaronLasseigne 12 6 9 3 1 2 11 10 5

    4 7 8 Time, Time Zones and Rails It’s About Time
  2. @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
  3. @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
  4. @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
  5. @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
  6. @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
  7. @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
  8. @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)”
  9. @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"
  10. @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"
  11. @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"
  12. @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
  13. @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
  14. @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            #  =>  #<ActiveSupport::TimeZone:0x007fa6ee889cc0...> 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
  15. @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!
  16. @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
  17. @AaronLasseigne •All have a plural version for better readability. •They

    work with Floats (except months and years). •All return seconds. Time Using Numbers
  18. @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
  19. @AaronLasseigne irb>  Time.days_in_month(6)  #  optional  year  argument #  =>  30

    irb>  Time.find_zone!('Eastern  Time  (US  &  Canada)') #  =>  #<ActiveSupport::TimeZone:0x007fa6f86022b8...> 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
  20. @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
  21. @AaronLasseigne •The date is set to Jan 1, 2000. •The

    time zone is set to UTC. How does a time field become a Time?
  22. @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?
  23. @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?
  24. @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 ...
  25. @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
  26. @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
  27. @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)}
  28. @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)  %>
  29. @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
  30. @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
  31. @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
  32. @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.
  33. @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.
  34. @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
  35. @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
  36. @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
  37. @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
  38. @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.