Started out with that frustrating issue that bites many a Rails developer (judging from just a tad over 24hrs googling and reading blogs etc) - time_zone!
I'm on
ruby 1.9.2p290 (2011-07-09 revision 32553) [x86_64-darwin11.4.2] - Rails 3.2.8
config.time_zone = 'Copenhagen'
and initially, I wanted to bark up this three, 'with the pack'
[31] pry(main)> ent.enter_at=Time.now
=> 2012-11-13 11:52:45 +0100
[32] pry(main)> ent.save
(0.1ms) BEGIN
(0.4ms) UPDATE `entrances` SET `enter_at` = '2012-11-13 10:52:45', `updated_at` = '2012-11-13 10:52:55' WHERE `entrances`.`id` = 1
SQL (0.2ms) INSERT INTO `versions` (`created_at`, `event`, `item_id`, `item_type`, `object`, `whodunnit`) VALUES ('2012-11-13 10:52:55', 'update', 1, 'Entrance', '---\nox_id: 1\nemployee_id: 9\nwage_item_id: !!null \nenter_at: 2012-11-13 08:02:00.000000000Z\nleft_at: 2012-10-11 21:21:00.000000000Z\nreason: tyuio\nstate: drafted\nactive: true\ncreated_at: 2012-10-11 09:02:52.000000000Z\nupdated_at: 2012-11-13 06:39:07.000000000Z\nid: 1\n', NULL)
(0.5ms) COMMIT
=> true
[34] pry(main)> reload!
Reloading...
=> true
[35] pry(main)> Entrance.first
Entrance Load (0.3ms) SELECT `entrances`.* FROM `entrances` WHERE (entrances.ox_id='1') LIMIT 1
=> #<Entrance id: 1, ox_id: 1, employee_id: 9, wage_item_id: nil, enter_at: "2012-11-13 10:52:45", left_at: "2012-10-11 21:21:00", reason: "tyuio", state: "drafted", active: true, created_at: "2012-10-11 09:02:52", updated_at: "2012-11-13 10:52:55">
[37] pry(main)> Entrance.first.enter_at.to_s(:entrance)
Entrance Load (0.4ms) SELECT `entrances`.* FROM `entrances` WHERE (entrances.ox_id='1') LIMIT 1
=> "2012-11-13 11:52:45 +0100"
Everything is dandy ;)
[39] pry(main)> date=Date.today
=> Tue, 13 Nov 2012
[40] pry(main)> date=Date.today.to_datetime
=> Tue, 13 Nov 2012 00:00:00 +0000
[44] pry(main)> date += 1.hour + 30.minutes
=> Tue, 13 Nov 2012 01:30:00 +0000
[45] pry(main)> ent.enter_at
=> Tue, 13 Nov 2012 11:52:45 CET +01:00
[46] pry(main)> ent.enter_at = date
=> Tue, 13 Nov 2012 01:30:00 +0000
[47] pry(main)> ent.save
(0.2ms) BEGIN
(0.6ms) UPDATE `entrances` SET `enter_at` = '2012-11-13 01:30:00', `updated_at` = '2012-11-13 11:27:08' WHERE `entrances`.`id` = 1
SQL (0.3ms) INSERT INTO `versions` (`created_at`, `event`, `item_id`, `item_type`, `object`, `whodunnit`) VALUES ('2012-11-13 11:27:08', 'update', 1, 'Entrance', '---\nox_id: 1\nemployee_id: 9\nwage_item_id: !!null \nenter_at: 2012-11-13 10:52:45.000000000Z\nleft_at: 2012-10-11 21:21:00.000000000Z\nreason: tyuio\nstate: drafted\nactive: true\ncreated_at: 2012-10-11 09:02:52.000000000Z\nupdated_at: 2012-11-13 10:52:55.000000000Z\nid: 1\n', NULL)
(0.6ms) COMMIT
=> true
[48] pry(main)> reload!
Reloading...
=> true
[49] pry(main)> ent=Entrance.first
Entrance Load (0.5ms) SELECT `entrances`.* FROM `entrances` WHERE (entrances.ox_id='1') LIMIT 1
=> #<Entrance id: 1, ox_id: 1, employee_id: 9, wage_item_id: nil, enter_at: "2012-11-13 01:30:00", left_at: "2012-10-11 21:21:00", reason: "tyuio", state: "drafted", active: true, created_at: "2012-10-11 09:02:52", updated_at: "2012-11-13 11:27:08">
[50] pry(main)> ent.enter_at
=> Tue, 13 Nov 2012 02:30:00 CET +01:00
[51] pry(main)> date.class
=> DateTime
[52] pry(main)> ent.enter_at.class
=> ActiveSupport::TimeWithZone
Which is very much not dandy - but you see, then I took a very good look at date
[64] pry(main)> date.to_time
=> 2012-11-13 01:30:00 UTC
[65] pry(main)> date.in_time_zone
=> Tue, 13 Nov 2012 02:30:00 CET +01:00
and all at once I realized that the DB really was doing exactly what I was telling it to! It stored a point in time (in another time_zone) and was kind enough to show it correctly to me, once I asked for it.
Likewise - once I started storing the date/time correctly, everything was/is back to 'dandy'
[66] pry(main)> ent.enter_at = date.in_time_zone
=> Tue, 13 Nov 2012 02:30:00 CET +01:00
[67] pry(main)> ent.save
(0.1ms) BEGIN
(0.1ms) COMMIT
=> true
[68] pry(main)> ent.reload
Entrance Load (0.3ms) SELECT `entrances`.* FROM `entrances` WHERE `entrances`.`id` = 1 LIMIT 1
=> #<Entrance id: 1, ox_id: 1, employee_id: 9, wage_item_id: nil, enter_at: "2012-11-13 01:30:00", left_at: "2012-10-11 21:21:00", reason: "tyuio", state: "drafted", active: true, created_at: "2012-10-11 09:02:52", updated_at: "2012-11-13 11:27:08">
[69] pry(main)> ent.enter_at
=> Tue, 13 Nov 2012 02:30:00 CET +01:00
[70] pry(main)>
But wait - wasn't it 1 1/2 hr I wanted to add to 'start_of_day'? Sure - my offset was just - well - off ;)
[70] pry(main)> date=Date.today.to_datetime.in_time_zone
=> Tue, 13 Nov 2012 01:00:00 CET +01:00
[71] pry(main)> date+=1.hour+30.minutes
=> Tue, 13 Nov 2012 02:30:00 CET +01:00
In my opinion Rails is doing the right thing - I just had to "get up to speed on how to use it" <:(
I hope somebody will find this post sooner than I did :)
Cheers
ps.: Eventually I fabricated a small module to serve my DB right :)
#
# copylefted by design
# ALCO Company
# Walther Diechmann
# nov 2012
#
# usage:
#
# Add the following line to your controller,model, whatever
#
# include TimeZoneIo
#
# call the module from somewhere like:
#
# @some_active_record_model_instance.some_datetime_field = build_time_in_zone Time.now, { null_as_nil: true, add_days: 1, add_hours: 3, add_seconds: 45, subtract_months: 2 }
#
module TimeZoneIo
extend ActiveSupport::Concern
included do
#
# construct a time object
# and add/subtract time(range)
#
def build_time_in_zone start, options
tiz = time_zoned( Time.parse( start ).to_datetime, options ) || time_zoned(Time.now.at_beginning_of_day, options)
return tiz if options.empty?
delta = 0
options.each do |k,v|
next if v.blank?
op, range = k.to_s.split("_")
oper = "v.to_i.#{range}"
case op
when 'add'; delta += eval(oper)
when 'subtract'; delta -= eval(oper)
end
end
return nil if ( (delta==0) && (options.include?( :null_as_nil )) )
tiz += delta unless delta==0
return tiz
end
#
# make the time present itself in the correct time_zone
#
def time_zoned tm, options
@usr_tz ||= (options.delete(:time_zone) || Time.zone)
tm.in_time_zone @usr_tz
end
end
end
Thanks for the great gist. This datetime stuff is doing my head in.
When you say "once I started storing the date/time correctly", do you mean once you started using .in_time_zone ? I have tried this solution and it doesn't seem to be working for me.