A Journey through Time, Date, DateTime, and TimeWithZone

A Journey through Time, Date, DateTime, and TimeWithZone

A trip through the Date/Time methods that Ruby and Rails gives us to work with.

D7504721f8a4c7f42c5a61be72e52758?s=128

Wade Winningham

April 03, 2012
Tweet

Transcript

  1. A JOURNEY THROUGH TIME, DATE, DATETIME, AND TIMEWITHZONE Tuesday, April

    3, 12
  2. DEALING WITH TIMES IN RUBY CAN BE CONFUSING. *  It

     may  be  more  confusing  when  this  presenta1on  is  over. Tuesday, April 3, 12
  3. SUMMARY • Ruby – Time • Ruby  Standard  Library –

    Date – Time – DateTime • Rails  Ac1veSupport – dura1ons  (integers  and  numeric) – acts_like – conversions – calcula1ons – TimeZone – TimeWithZone Tuesday, April 3, 12
  4. DEFAULTS • Some  slides  refer  to  these  variables: d =

    Date.new(2012, 4, 3) dt = DateTime.new(2012, 4, 3, 19, 0, 0, 'CDT') t = Time.new(2012, 4, 3, 19, 0, 0, '-05:00') tz = Time.zone.local(2012, 4, 3, 19, 0, 0, 'CDT') Tuesday, April 3, 12
  5. PARTICIPATE SHARE YOUR STORIES Tuesday, April 3, 12

  6. RUBY TIME Ruby,  by  itself,  only  has  the  Time  class

     which  is  an  abstrac1on  of  dates  and  1mes. Time  is  stored  as  the  number  of  seconds  (with  frac1on)  since  the  Unix  Epoch which  is  January  1,  1970  00:00  UTC. Tuesday, April 3, 12
  7. Ruby:  Time TIME CAN BE DECEIVING • Times  do  have

     a  frac1on  part.  Be  aware  of  this  when  comparing  two  1mes.  They  could   appear  to  be  equal,  but  are  not. t1, t2 = Time.now, Time.now #=> [2012-04-03 19:00:00 -0500, 2012-04-03 19:00:00 -0500] t1 == t2 #=> false t1.usec #=> 594792 t2.usec #=> 594859 Tuesday, April 3, 12
  8. Ruby:  Time THE MANY WAYS OF MAKING TIME • Time.at

    creates  a  1me  object  based  on  a  passed  1me  or  number  of  seconds  in  local  1me. • Time.utc  (or  Time.gm)   creates    a  UTC  1me  based  on  a  passed  year,  month,  day,  etc. • Time.local  (or  Time.mktime)   creates  a  local  1me  like  utc  does. • Time.new In  Ruby  1.9  also  takes  year,  month,  day,  etc.  (Ruby  1.8  doesn’t  have  params) – Time.now  is  just  Time.new  without  any  parameters Tuesday, April 3, 12
  9. Ruby:  Time THE MANY WAYS OF MAKING TIME • Ruby

     only  really  deals  with  local  (server  1me)  and  UTC  1me. However.... • With  Ruby  1.9  you  can  call  Time.new  with  utc_offset  to  get  the  specific  1me  in  any   1mezone.  Ruby  1.8  doesn’t  have  this. Time.new(year, month, day, hour, min, sec, utc_offset) # utc_offset like “-06:00” Ruby  1.9‘s  utc, local and new are  supposed  to  have  a  secret  handshake  which  can  give   you  back  a  1me  in  any  1me  zone Time.utc(sec, min, hour, day, month, year, wday, yday, isdst, tz) but  the  wday,  yday,  isdst  and  tz  parameters  seem  to  get  ignored  as  they  don’t  do  anything. Tuesday, April 3, 12
  10. Ruby:  Time WHAT ELSE IS IN PLAIN O’ RUBY? •

    Comparison  with  <=>,  eql? • Simple  conversion  of  utc  -­‐>  local,  local  -­‐>  utc • .to_s,  .strftime (more  on  these  later,  but  we  won’t  delve  into  str[ime’s  format  strings) • .to_a,  .to_i,  .to_f • Pluck  methods  like  .month,  .hour,  .wday,  .yday,  etc. • .usec  for  just  the  microseconds • .zone  returns  the  1me  zone  like  “UTC”  or  “CDT” Tuesday, April 3, 12
  11. RUBY STANDARD LIBRARY require ‘date’ require ‘time’ The  standard  library

     gives  us  the  Date,  Time  and  DateTime  classes  we   typically  work  with.  There’s  a  li_le  but  of  1me  zone  stuff  here  but  only   enough  to  make  us  work  hard  to  get  it. Tuesday, April 3, 12
  12. Ruby  Standard  Library TERMS • Calendar  or  Civil  date  is

     what  most  of  us  likely  use...  our  standard  U.S.  Jan.-­‐Dec.  calendar   which  runs  from  Sunday  to  Saturday. Date.civil(2012, 4, 3) #=> Tue, 03 Apr 2012 • Ordinal  date  is  the  day  of  the  year  with  the  year. Date.ordinal(2012, 94) #=> Tue, 03 Apr 2012 • Commercial  date  starts  on  the  first  Monday  on  or  before  Jan  1  and  is  calculated  by  number   of  weeks  and  then  day  of  week.  Does  anyone  use  this? Date.commercial(2012, 14, 2)  #=> Tue, 03 Apr 2012 • Julian  vs.  Gregorian,  modified  Julian...  RTFD  for  a  history  lesson.  Anyone  use  this? Tuesday, April 3, 12
  13. Ruby  Standard  Library .PARSE • Pass  in  a  string  and

     it  does  what  it  can  to  convert  it.  Time  defaults  to  local  and  DateTime   defaults  to  UTC  unless  you  pass  the  zone. Date.parse("2012-04-03") # 4/3/2012 Time.parse(“20120403T190000-0500”) # 4/3/2012 7:00pm -05:00 Time.parse(“3rd Apr 2012 7:00PM”) # 4/3/2012 7:00pm -05:00 Time.parse(“3rd Apr 2012 7:00PM CST”) # 4/3/2012 8:00pm -05:00 (8pm!) Time.parse(“3rd Apr 2012 7:00PM CDT”) # 4/3/2012 7:00pm -05:00 DateTime.parse(“20120403T190000-0500”) # 4/3/2012 7:00pm -05:00 DateTime.parse(“3rd Apr 2012 7:00PM”) # 4/3/2012 7:00pm +00:00 (UTC) DateTime.parse(“3rd Apr 2012 7:00PM CST”) # 4/3/2012 7:00pm -06:00 (or 8pm -05:00!) DateTime.parse(“3rd Apr 2012 7:00PM CDT”) # 4/3/2012 7:00pm -05:00 Remember,  in  the  rest  of  the  world,  the  date  format  is   not  m/d/y  but  rather,  d/m/y. Date.parse("4/3/2012") # 3/4/2012 Tuesday, April 3, 12
  14. Rails  Ac1veSupport  calcula1ons TIME.PARSE • Ruby  1.9...  parsing  an  empty

     string Time.parse("") #=> ArgumentError: no time information in "" DateTime.parse("") #=> ArgumentError: invalid date • Ruby  1.8’s  Time  class  parses  an  empty  string  as  the  current  1me. Time.parse("") #=> Tue Apr 03 19:00:00 -0500 2012 DateTime.parse("") #=> ArgumentError: invalid date • Although  we’re  not  talking  about  it  yet,  Rails’  TimeWithZone  returns  a  nil  instead of  an  excep1on. Time.zone.parse("") #=> nil Tuesday, April 3, 12
  15. Ruby  Standard  Library OTHER WAYS TO PARSE A DATETIME •

    DateTime.httpdate  parses  a  string  “according  to  some  RFC  2616  format.”  That’s  what  the   docs  say.  Seriously. Ex.  “Tue, 03 Apr 2012 19:00:00 CDT” • DateTime.iso8601  parses  some  typical  ISO  8601  formats. Ex.  “2012-W14-2T19:00:00-05:00” • DateTime.xmlschema  parses  the  typical  XML  schema  formats. Ex.  “2012-04-03T19:00:00-05:00” • DateTime.strptime  is  the  most  robust,  allowing  you  to  define  a  template  the  string  should   match. Ex.  DateTime.strptime(“4/3/2012”, “%m/%d/%Y”)  # 4/3/2012 • DateTime.rfc2822, .rfc3339, .rfc822  for  whoever  uses  these. • and  DateTime.jd  and  .jisx0301  for  those  who  are  subject  to   that  pain. Tuesday, April 3, 12
  16. Ruby  Standard  Library TO_DATE, TO_TIME, TO_DATETIME • The  standard  library

     also  gives  us  a  way  to  convert  from  a  Time  to  a  DateTime,  a  DateTime  to   Date,  etc. dt = DateTime.parse(“2012-04-03 19:00:00 CDT”) dt.class #=> DateTime dt.to_time.class #=> Time dt.to_date.class #=> Date • Conver1ng  a  Date  to  a  Time  converts  to  beginning  of  day  local  1me. • Conver1ng  a  Date  to  a  DateTime  converts  to  UTC  1me. d = Date.parse("2012-04-03") => #<Date: 2012-04-03> d.to_time => 2012-04-03 00:00:00 -0500 d.to_datetime => #<DateTime: 2012-04-03T00:00:00+00:00> Tuesday, April 3, 12
  17. Ruby  Standard  Library VALIDATIONS • valid_date?  can  tell  you  if

     a  date  is  valid  before  you  a_empt  to  parse  it,  which  could  raise   an  excep1on. Date.valid_date?(2012, 4, 3) #=> true Date.valid_date?(2012, 2, 29) #=> true Date.valid_date?(2011, 2, 29) #=> false • There  are  also  methods  for  valid_commercial?, valid_ordinal?  and  valid_jd? Tuesday, April 3, 12
  18. Ruby  Standard  Library THINGS YOU MAY NOT KNOW • d

    << n  -­‐>  date Returns  a  date  object  poin1ng  n  months  before  self Date.new(2012, 4, 3) << 1 #=> Sat, 03 Mar 2012 DateTime.new(2012, 4, 3, 19, 0, 0, ‘CDT’) << 1 #=> Sat, 03 Mar 2012 19:00:00 -0500 • d >> n  Does  the  same  but  adds  a  month. • .cweek  returns  the  calendar  week  number • .cwday  returns  the  day  of  the  calendar  week  (1-­‐7,  Monday  being  1) • .cwyear  returns  the  calendar  week  based  year,  while  the  cwyear  for  1/1/2012  is  2012,  its   value  for  1/1/2011  is  2010. • .day_fraction  returns  the  frac1onal  part  of  a  day DateTime.new(2012,4,3,12).day_fraction #=> (1/2) • step  (or  upto)  to  provide  a  range dt = Date.new(2012, 4, 1) dt2 = Date.new(2012, 4, 30) dt.step(d2).select{|d| d.tuesday?}.size #=> 4 # of Tuesday’s in April 2012 Tuesday, April 3, 12
  19. RAILS ACTIVESUPPORT Ac1veSupport  gives  us  even  more  func1onality  with  the

     Date,  Time  and   DateTime  classes.  And  adds  TimeWithZone. Tuesday, April 3, 12
  20. Rails  Ac1veSupport CORE_EXT/TIME/MARSHAL.RB • Ruby  1.9.2  added  utc_offset  and  zone

     to  Time,  but  marshaling  only preserves  the  utc_offset.  This  appears  to  have  been  fixed  with  Ruby  1.9.3. Time.local(2012).zone != Marshal.load(Marshal.dump(Time.local(2012))).zone Rails  redefines  the  _load  and  _dump  methods  to  deal  with  this  situa1on if  you’re  using  a  version  of  Ruby  that  has  this  problem. Ac1veSupport’s  version  preserves  the  zone,  but  the  docs  say  it may  not  work  in  some  edge  cases. Tuesday, April 3, 12
  21. Rails  Ac1veSupport  dura1ons NUMERIC/INTEGER DURATIONS • 15.minutes,  2.weeks,  and  2.fortnights

    • .ago  (or  .until) • .from_now  (or  .since) Examples: 15.minutes.until(@meeting.start_time) 1.week.from_now Tuesday, April 3, 12
  22. Rails  Ac1veSupport  acts_like ACTS_LIKE? • Date  acts_like?(:date) • Time  acts_like?(:time)

    • DateTime  acts_like?(:date) && acts_like?(:time) • TimeWithZone  acts_like?(:date) && acts_like?(:time) All  this  duck  typing  mostly  so  it  knows  it  can  convert  a  Time  to  a  float  for  add/subtract.   Date’s  cannot  be  converted  to  a  float. Tuesday, April 3, 12
  23. RAILS ACTIVESUPPORT CONVERSIONS Methods  used  to  convert  1mes  to  strings

     and  other  date/1me  objects. Tuesday, April 3, 12
  24. Rails  Ac1veSupport  conversions DATE: TO_S • :db  is  likely  the

     most  familiar d.to_s(:db) #=> “2012-04-03” • Ac1veSupport  also  gives  us: d.to_s(:short) #=> “ 3 Apr” d.to_s(:long) #=> “April 3, 2012” d.to_s(:number) #=> “20120403” d.to_s(:long_ordinal) #=> “April 3rd, 2012” d.to_s(:rfc822) #=> “ 3 Apr 2012” Tuesday, April 3, 12
  25. Rails  Ac1veSupport  conversions TIME/DATETIME: TO_S • Time  and  DateTime  objects

     also  have  .to_s(:db) dt.to_s(:db) #=> “2012-04-03 19:00:00” HOWEVER,  it  drops  the  1me  zone.  You  have  to  convert  it  to  a  TimeWithZone  if  you  want  it  to   convert  a  local  1me  to  UTC. dt.in_time_zone.to_s(:db) #=> “2012-04-04 00:00:00” Rails  does  this  for  you  when  persis1ng  to  the  database  or  execu1ng  a  query.  If  you  are  using   the  raw  value  yourself,  it’s  easy  to  forget.  You  can  tell  Rails  to  not  do  this  for  specific  models   like  this: class MyModel < ActiveRecord::Base self.skip_time_zone_conversion_for_attributes = [:starts_at, :ends_at, :whenever] end Tuesday, April 3, 12
  26. Rails  Ac1veSupport  conversions TIME/DATETIME: TO_S • Ac1veSupport  also  gives  us

     these  Time  and  DateTime  defaults: dt.to_s(:short) #=> “03 Apr 19:00” dt.to_s(:long) #=> “April 3, 2012 19:00” dt.to_s(:number) #=> “20120403190000” dt.to_s(:time) #=> “19:00” dt.to_s(:long_ordinal) #=> “April 3rd, 2012 19:00” dt.to_s(:rfc822) #=> “Tue, 3rd Apr 2012 19:00:00 -0500” Tuesday, April 3, 12
  27. Rails  Ac1veSupport  conversions CUSTOM : TO_S • You  can  also

     create  custom  formats.  Ex.  config/ini1alizers/1me_formats.rb date_formats = { :month_year => “%B %Y”, :short_ordinal => lambda {|dt| dt.strftime("%B #{dt.day.ordinalize}") } } time_formats = { :short_time => lambda {|date| date.strftime("%l:%M#{date.strftime("%P")[0,1]}") } } ActiveSupport::CoreExtensions::Date::Conversions::DATE_FORMATS.update(date_formats) ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.update(date_formats) ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.update(time_formats) # This way you don’t have to specify formats separately for Date & Time. • Which  would  let  you  do  this... d.to_s(:month_year) #=> “April 2012” d.to_s(:short_ordinal) #=> “April 3rd” dt.to_s(:short_time) #=> “ 7:00p” Tuesday, April 3, 12
  28. Rails  Ac1veSupport  conversions DATE: TO_TIME/DATETIME • Ac1veSupport  converts  a  Date

     to  your  local  1me  zone  by  default,  but  allows  you  to  convert   to  UTC,  as  well.  The  1me  defaults  to  beginning  of  day. d.to_time #=> 2012-04-03 00:00:00 -0500 d.to_time(:utc) #=> 2012-04-03 00:00:00 UTC • However,  to_date1me  doesn’t  have  any  parameters... d.to_datetime #=> Tue, 03 Apr 2012 00:00:00 +0000 • To  a  TimeWithZone...  (more  on  this  class  later) d.to_time_in_current_zone #=> Tue, 03 Apr 2012 00:00:00 CDT -05:00 # or use Time.zone.parse(d.to_s) NOTE:  .to_time_in_current_zone  is  a  Date  method.  Calling  it  on  a  DateTime  object  excludes  the   1me.  It’s  like  seqng  it  to  beginning_of_day. Tuesday, April 3, 12
  29. Rails  Ac1veSupport  conversions DATETIME: TO_? • DateTime  has  .to_date  and

     .to_1me dt.to_date #=> Tue, 03 Apr 2012 dt.to_time #=> Tue, 03 Apr 2012 19:00:00 -0500 DateTime’s  .to_time  is  supposed  to  convert  itself  to  a  Ruby  Time  object  or  self  if  it’s  out  of   range  of  the  Ruby  Time  class.  However,  it  seems  to  just  always  return  self.  If  you  really  need   a  Time  though,  you  can  just  use: Time.parse(dt.to_s) Tuesday, April 3, 12
  30. Rails  Ac1veSupport  conversions TIME: TO_? • Time  has  .to_date  and

     .to_date1me t.to_date #=> Tue, 03 Apr 2012 t.to_datetime #=> Tue, 03 Apr 2012 19:00:00 -0500 Time’s  .to_datetime  converts  to  local    1mezone  whereas  Date’s  does  not. d.to_datetime #=> Tue, 03 Apr 2012 00:00:00 +0000 d.to_time.to_datetime #=> Tue, 03 Apr 2012 00:00:00 -0500 Tuesday, April 3, 12
  31. Rails  Ac1veSupport  conversions FORMATTED_OFFSET • DateTime  and  Time  have  .formatted_offset

     for  the  hour  offset dt.formatted_offset #=> "-06:00" dt.formatted_offset(false) #=> "-0600" Tuesday, April 3, 12
  32. RAILS ACTIVESUPPORT CALCULATIONS Date  and  1me  math. Tuesday, April 3,

    12
  33. Rails  Ac1veSupport  calcula1ons NOW VS. CURRENT • Ac1veSupport  adds  a

     .current  method  to  Time  that  returns  Time.zone.now  if  1me  zones   are  enabled,  otherwise  it  returns  Time.now.  Date  has  a  similar  method. Time.now #=> 2012-04-03 19:00:00 -0500 Time.current #=> Tue, 03 Apr 2012 19:00:00 CST -05:00 Date.current #=> Tue, 03 Apr 2012 – Time  zones  are  on  by  default  in  Rails  3.  Not  Rails  2. We’ll  get  to  the  config  for  this  in  a  bit. Tuesday, April 3, 12
  34. Rails  Ac1veSupport  calcula1ons PAST/FUTURE • Is  a  date  future/past? dt.past?

    dt.today? dt.future? Tuesday, April 3, 12
  35. Rails  Ac1veSupport  calcula1ons ADD/SUBTRACT TIME • Instead  of  3.days.ago(dt),  you

     can  use  this  format: dt.ago(3.days) #=> Tue, 27 Mar 2012 19:00:00 -0500 dt.in(1.week) #=> Tue, 10 Apr 2012 19:00:00 -0500 • Be  careful  with  daylight  savings.  Even  for  the  3.days.ago(dt)  format. # Time t.ago(1.month) #=> 2012-03-03 19:00:00 -0600 <-- takes into account DST 1.month.ago(t) #=> 2012-03-03 19:00:00 -0600 # DateTime dt.ago(1.month) #=> Sun, 04 Mar 2012 19:00:00 -0500 <-- DST AND day are wrong! 1.month.ago(dt) #=> Sat, 03 Mar 2012 19:00:00 -0500 <-- Day is right, but DST is still wrong?!? Tuesday, April 3, 12
  36. Rails  Ac1veSupport  calcula1ons BEGINNING/END • .yesterday, .tomorrow dt.yesterday #=> Mon,

    02 Apr 2012 19:00:00 -0500 dt.tomorrow #=> Wed, 04 Apr 2012 19:00:00 -0500 • [beginning|end]_of_day, .midnight • [beginning|end]_of_week, .sunday, • [beginning|end]_of_month • [beginning|end]_of_quarter • [beginning|end]_of_year • [prev|next]_[day|week|month|year] For  some  reason,  Time  and  TimeWithZone  do  not  have  [prev|next]_day  methods. Also,  no  [prev|next]_quarter  methods  for  any  class. Tuesday, April 3, 12
  37. Rails  Ac1veSupport  calcula1ons CHANGE • Behind-­‐the-­‐scenes,  the  .change  method  is

     one  of  the  workhorses  behind  most  of  the   beginning_of  and  end_of  methods. • .change  takes  a  date/1me  instance  and  changes  the  pieces  you  want: Date.new(2012,4,3).change(:month => 6) #=> Sun, 03 Jun 2012 = same as Date.new(2012,6,3) # For the time parts (hour, min, sec, usec, any resets cascade. # Pass only the :hour, the min/sec/usec values are set to 0. DateTime.new(2012,4,3,19,30,30,‘CDT’).change(:hour => 20) #=> Tue, 03 Apr 2012 20:00:00 -0500 Tuesday, April 3, 12
  38. Rails  Ac1veSupport  calcula1ons ADVANCE • .advance  is  similar  to  .change,

     but  adds/subtracts  the  pieces  you  want: Date.new(2012,4,3).advance(:months => 2) #=> Sun, 03 Jun 2012 DateTime.new(2012,4,3,19,30,30,'CDT').advance(:hours => 2) #=> Tue, 03 Apr 2012 21:30:30 -0500 • Note  that  advance’s  params  are  plural. Singular  params  are  ignored. Tuesday, April 3, 12
  39. Rails  Ac1veSupport  calcula1ons RANGES • Also  part  of  the  calcula1ons

     code  for  Time  and  TimeWithZone  are  some  useful  ranges. .all_day .all_week .all_month .all_quarter .all_year tz.all_day #=> 2012-04-03 00:00:00 UTC..2012-04-03 23:59:59 UTC • These  are  not  available  for  Date  or  DateTime  objects.  And  since   DateTime’s  .to_1me  method  returns  a  DateTime,  this  won’t  work: dt.to_time.all_day #=> NoMethodError: undefined method `all_day' Tuesday, April 3, 12
  40. Rails  Ac1veSupport  calcula1ons SECONDS VS. DAYS • Be  careful  when

     adding/subtrac1ng  numbers  from  a  Time  or  DateTime. Time  assumes  seconds,  DateTime  assumes  days. Time.current - 1 #=> 2012-04-03 18:59:59 -0500 DateTime.current - 1 #=> Mon, 02 Apr 2012 19:00:00 -0500 DateTime.current - 1.second #=> Tue, 03 Apr 2012 18:59:59 -0500 Tuesday, April 3, 12
  41. RAILS TIMEZONE Not  the  1mes,  just  the  zones. Tuesday, April

    3, 12
  42. Ac1veSupport::TimeZone IN THE ZONE • Where  “Central  Time  (US  &

     Canada)”  comes  from • Get  a  list  of  zones: ActiveSupport::TimeZone.all ActiveSupport::TimeZone.us_zones • Get  specific  1mes ActiveSupport::TimeZone["Paris"].now #=> Wed, 3 Apr 2012 03:19:04 CEST +02:00 ActiveSupport::TimeZone["Paris"].parse('2012-04-03 19:00 CDT') #=> Wed, 04 Apr 2012 02:00:00 CEST +02:00 Tuesday, April 3, 12
  43. Ac1veSupport::TimeZone DEFAULT ZONE • In  Rails,  Time.zone refers  to  the

     current  default  Ac1veSupport::TimeZone  object. • Rails  3  has  1me  zones  enabled  by  default.  In  Rails  2,  you  can  turn  this  on  in  your   environment.rb  file.  Rails  3,  it’s  in  applica1on.rb  if  you  want  to  change  it. config.time_zone = 'Central Time (US & Canada)' # For Rails 3, the default for ActiveRecord is to store UTC times in the database. # The Rails 2 default is :local, but you can change that with this: config.active_record.default_timezone = :utc Tuesday, April 3, 12
  44. Ac1veSupport::TimeZone USER ZONES • If  you  have  a  users  table

     with  a  1me_zone  field  in  it,  you  can  change  the  default  zone  for   that  user’s  current  request  with  a  before_filter. class ApplicationController < ActionController::Base before_filter :set_time_zone private def set_time_zone Time.zone = current_user.time_zone if current_user end end • In  your  views  you  can  use  Rails  1me  zone  helpers  for  a  dropdown  of  zones. time_zone_select('user', 'time_zone', ActiveSupport::TimeZone.us_zones) Tuesday, April 3, 12
  45. Ac1veSupport::TimeZone TIMEZONES FOR A TIME • In  one  of  my

     projects,  we  send  users  an  email  at  the  start  of  their  day. It’s  not  6am  everywhere  at  the  same  1me,  only  in  certain  1mezones. def timezones_between(from_hour, to_hour) zones = Array.new ActiveSupport::TimeZone.all.each do |z| time_check = Time.current.utc + z.utc_offset.seconds zones << z if time_check.hour >= from_hour && time_check.hour < to_hour end zones end # All users who are in a timezone where it’s currently between 6am and 7am. User.where(“wants_email > 0 AND time_zone IN (?)”, timezones_between(6,7)) Tuesday, April 3, 12
  46. RAILS TIMEWITHZONE Deals  with  1mes  in  any  1me  zone  since

     Ruby  1mes  are  limited  to  UTC  and   the  server’s  local  1me  zone. Represents  itself  as  a  Time  class.  An  Ac1veSupport::TimeZone  object   returns  true  for  .is_a?(Time). TimeWithZone  has  all  the  Time/DateTime  methods  with  some  addi1ons Tuesday, April 3, 12
  47. Ac1veSupport::TimeWithZone CREATING A TIMEWITHZONE • API  docs  say  you  should

     never  create  a  TimeWithZone  directly  with  .new Instead,  use  these: Time.zone.local(2012, 4, 3, 17) Time.zone.parse(“2012-04-03 19:00”) Time.zone.at(1333497600) Time.zone.now Time.now.in_time_zone # Same as Time.current when TimeWithZone is configured. – Remember...  Time.zone  is  the  current  default  Ac1veSupport::TimeZone. Tuesday, April 3, 12
  48. Ac1veSupport::TimeWithZone TO_TIME/DATETIME • TimeWithZone  has  a  .time  method,  which  is

     not  the  same  as  .to_time tz.time #=> 2012-04-03 19:00:00 UTC tz.localtime #=> 2012-04-03 19:00:00 -0500 tz.to_time #=> 2012-04-04 00:00:00 UTC tz.to_datetime #=> Tue, 03 Apr 2012 19:00:00 -0500 Tuesday, April 3, 12
  49. Ac1veSupport::TimeWithZone PIECES OF TIMEWITHZONE • tz.period  is  the  1mezone’s  underlying

     TZInfo::TimezonePeriod  object.  Rails  4  currently   appears  to  be  spliqng  this  up  into  .period_for_local and  .period_for_utc.  Used  so   TimeWithZone  instances  respond  like  TZInfo::Timezone  instances. • tz.zone  is  the  zone’s  abbrevia1on  as  a  string.  “CDT”,  “PST”,  etc. • tz.dst?  is  true  if  the  date/1me  is  within  observances  of  daylight  savings  1me. • tz.utc?  is  only  true  if  the  date/1me  is  UTC. Tuesday, April 3, 12
  50. Ac1veSupport::TimeWithZone IN_TIME_ZONE • You  can  use  .in_time_zone  to  convert  your

     1me  to  any  other  zone. tz.in_time_zone("Eastern Time (US & Canada)") #=> Tue, 03 Apr 2012 20:00:00 EDT -04:00 tz.in_time_zone("Tokyo") #=> Wed, 04 Apr 2012 09:00:00 JST +09:00 tz.in_time_zone("Paris") #=> Wed, 04 Apr 2012 02:00:00 CEST +02:00 # Not all zones are on the hour tz.in_time_zone("Mumbai") #=> Wed, 04 Apr 2012 05:30:00 IST +05:30 • Dump  out  the  list  of  zones  you  can  use  with  the  following  rake  task: rake time:zones:all # The info from ActiveSupport::TimeZone.all Tuesday, April 3, 12
  51. Ac1veSupport::TimeWithZone WHERE IS THE BEGINNING? • Don’t  get  confused tz.beginning_of_day.in_time_zone("Eastern

    Time (US & Canada)") #=> Tue, 03 Apr 2012 01:00:00 EDT -04:00 tz.in_time_zone("Eastern Time (US & Canada)").beginning_of_day #=> Tue, 03 Apr 2012 00:00:00 EDT -04:00 Tuesday, April 3, 12
  52. Ac1veSupport::TimeWithZone IN A SPECIFIC ZONE • .use_zone  allows  you  to

     override  the  default  1me  zone  inside  of  the  supplied  block  and  then   resets  back  to  the  exis1ng  value  when  done. Time.zone #=> (GMT-06:00) Central Time (US & Canada) Time.use_zone(“Eastern Time (US & Canada)”) do Time.zone #=> (GMT-05:00) Eastern Time (US & Canada) end Time.zone #=> (GMT-06:00) Central Time (US & Canada) Tuesday, April 3, 12
  53. GEMS AND LIBRARIES While  there  are  loads  of  various  libraries

     out  there  to  help  out  with  dates   and  1mes. Tuesday, April 3, 12
  54. Gems  and  Libraries CHRONIC • Natural  language  date  parser.  Much

     more  robust  than  using  Ruby’s  .parse  method. h_ps://github.com/mojombo/chronic require 'chronic' Time.now #=> Sun Aug 27 23:18:25 PDT 2006 Chronic.parse('tomorrow') #=> Mon Aug 28 12:00:00 PDT 2006 Chronic.parse('monday', :context => :past) #=> Mon Aug 21 12:00:00 PDT 2006 Chronic.parse('this tuesday 5:00') #=> Tue Aug 29 17:00:00 PDT 2006 Chronic.parse('this tuesday 5:00', :ambiguous_time_range => :none) #=> Tue Aug 29 05:00:00 PDT 2006 Chronic.parse('may 27th', :now => Time.local(2000, 1, 1)) #=> Sat May 27 12:00:00 PDT 2000 Chronic.parse('may 27th', :guess => false) #=> Sun May 27 00:00:00 PDT 2007..Mon May 28 00:00:00 PDT 2007 Tuesday, April 3, 12
  55. Gems  and  Libraries TESTING • Zonebie  -­‐  h_ps://github.com/highgroove/zonebie Randomly  assigns

     a  1mezone  on  every  test  run. Zonebie.set_random_timezone • Timecop  -­‐  h_ps://github.com/jtrupiano/1mecop Provides  “1me  travel”  and  “1me  freezing”  to  make  it  easier  to  test  1me-­‐dependent  code.   Basically  mocks  Time.now,  Date.today  and  DateTime.now  with  a  single  call. new_time = Time.local(2008, 9, 1, 12, 0, 0) Timecop.freeze(new_time) sleep(10) new_time == Time.now # ==> true Timecop.return # "turn off" Timecop Timecop.travel(new_time) sleep(10) new_time == Time.now # ==> false Tuesday, April 3, 12
  56. Gems  and  Libraries TIME CONVERSION IN THE BROWSER • You

     can  rely  on  javascript  to  convert  your  UTC  1mes  for  you  in  the  browser  using  the   browser’s  own  seqngs.  There  are  many  libraries  that  can  do  this. One  is  jquery-­‐local@me  -­‐  h_p://code.google.com/p/jquery-­‐local1me <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script> <script type="text/javascript" src="jquery.localtime-0.4.js"></script> <span class="localtime">2012-04-04 00:00:00Z</span> <script type="text/javascript"> $.localtime.setFormat({dateAndTime: "M-d-yyyy h:mmtt"}); </script> /* The date would display as “4/3/2012 7:00pm” assuming your browser says you’re in CDT */ Tuesday, April 3, 12
  57. SHARE YOUR FAVORITES Gems  and  Libraries Tuesday, April 3, 12

  58. THANK YOU! Credits: Map  on  1tle  page  is  from  “Time

     Bandits”  (1981) Hamster  images  from  the  “Oruchuban  Ebichu”  manga  and  anime  series.   (*not  for  kids) Tuesday, April 3, 12