In my MySQL database the updated_at field is being stored as UTC. Last week a record was entered at 7pm EDT and it's updated_at value is "2012-08-01 23:00:00". I'm trying to convert local time for a web client to UTC that can be compared against the updated_at field in the database.
As an example, I'd like to convert '08/01/2012 07:00 pm' to '2012-08-01 23:00:00' (accounting for me being in EDT) but I'm missing the time zone aspect to the conversion. '7:00 pm' is local time and could be from any time zone. My current code:
ruby-1.9.2-head :015 > from_date = DateTime.strptime('08/01/2012 07:00 pm', '%m/%d/%Y %H:%M %p')
=> Wed, 01 Aug 2012 19:00:00 +0000
ruby-1.9.2-head :016 > from_date = Time.zone.parse(from_date.to_s).utc
=> 2012-08-01 19:00:00 UTC
Any thoughts?
Edit: Also, I could use "%Z" in my strptime call but that's assuming I have the timezone in the value, which I currently do not. I guess I could use Javascript to send that along with the date/time value.
local to utc
created_at.utc
utc to local
created_at.localtime
First I would check your time zone setting.
In your environment.rb (Rails 2) or application.rb (Rails 3) file, you can set the default timezone with: config.time_zone = 'Eastern Daylight Time (EDT)'
Secondly I would look at this post for information and guidance and tips to meet your needs:
http://databasically.com/2010/10/22/what-time-is-it-or-handling-timezones-in-rails/
Related
I am working on a rail4 app. Where I want to store dates in all mysql tables in UTC. However I store user's timezone in a specific table, called users. When user logs in, I get user's timezone form user table and save in session.
I am able to save date in all tables in UTC as default value of config.time_zone is UTC for activerecords and activemodels. But while displaying I want to show dates in user's timezone. As well as, when any user inputs a date/time in any html form, then I want to save it in the equivalent UTC format.
What is the best way to achieve this?
Rails, activerecord and MySQL will save all the timestamp fields in UTC. Without you having to do anything.
In your application.rb file where the configuration of the Application is done, you define the default time zone if you want the display of timestamps to take place on time zone different from UTC.
Hence
config.time_zone = 'Central Time (US & Canada)'
will display the timestamp fields (without you having to do anything special in other piece of code) using the Central Time.
When you want each of your users to have timestamps displayed in different time zone you can store the time zone in a column along side the user data. The column can be called time_zone and can contain the string of the user preferred time zone.
But, you have to tell the timestamp object to display itself to the specific timezone. This is done with the help of the method in_time_zone(timezone) that DateTime object responds to.
Example (when the default time zone is UTC):
1.9.3-p194 :004 > d = DateTime.new(2012, 9, 1, 6, 30, 0)
=> Sat, 01 Sep 2012 06:30:00 +0000
1.9.3-p194 :005 > d.in_time_zone("Central Time (US & Canada)")
=> Sat, 01 Sep 2012 01:30:00 CDT -05:00
Or you can change the time zone globally for the request at hand on a before or around filter. There is a documentation on internet if you do a google on that.
Read also this one: http://api.rubyonrails.org/classes/ActiveSupport/TimeWithZone.html
for various alternatives to approach the problem.
You could store the time in UTC, and store the timezone separately. Timezones are commonly stored as a UTC-offset in seconds (seconds are the SI unit of time).
Then you can display it like so:
utime = Time.now.utc.to_i # this value can be any format that Time.at can understand. In this example I'll use a unix timestamp in UTC. Chances are any time format you store in your DB will work.
=> 1375944780
time = Time.at(utime) # parses the time value (by default, the local timezone is set, e.g. UTC+08:00)
=> 2013-08-08 14:53:00 +0800
time_in_brisbane = time.in_time_zone(ActiveSupport::TimeZone[36000]) # sets the timezone, in this case to UTC+10:00 (see http://stackoverflow.com/a/942865/72176)
=> Thu, 08 Aug 2013 16:53:00 EST +10:00
time_brisbane.strftime("%d %b %Y, %l:%M %p %z") # format with strftime however you like!
=> "08 Aug 2013, 4:53 PM +1000"
I have the timezone set.
config.time_zone = 'Mountain Time (US & Canada)'
Creating a Christmas event from the console...
c = Event.new(:title => "Christmas")
using Time:
c.start = Time.new(2012,12,25)
=> 2012-12-25 00:00:00 -0700 #has correct offset
c.end = Time.new(2012,12,25).end_of_day
=> 2012-12-25 23:59:59 -0700 #same deal
using DateTime:
c.start = DateTime.new(2012,12,25)
=> Tue, 25 Dec 2012 00:00:00 +0000 # no offset
c.end = DateTime.new(2012,12,25).end_of_day
=> Tue, 25 Dec 2012 23:59:59 +0000 # same
I've carelessly been using DateTime thinking that input was assumed to be in the config.time_zone but there's no conversion when this gets saved to the db. It's stored just the same as the return values (formatted for the db).
Using Time is really no big deal but do I need to manually offset anytime I'm using DateTime and want it to be in the correct zone?
Yep. Time.new will intepret parameters as local time in the absence of a specific timezone, and DateTime.new will intepret parameters as UTC in the absence of a specific timezone. As documented. You may want to replace Time.new with Time.local for clarity throughout your code.
What you can do is use DateTime's mixins for Time to invoke Time.local(2012,12,25).to_datetime. But if the year/month/day is coming from a user/browser, then perhaps you should get the user's/browser's timezone instead of using your server's.
If you need to create a migration to fix existing data in the DB, new_date_time = Time.local(old_date_time.year, old_date_time.mon, old_date_time.mday, old_date_time.hour, old_date_time.min, old_date_time.sec).to_datetime
Just adding to the million questions about time zone and DST issues out there.
I have a form with separate date and time fields that I combine to create a DateTime like so
start_time = DateTime.parse("#{parse_date(form_date)} #{form_start_time} #{Time.zone}")
If I fill out my form with 21 Aug 2012 and 15:00, then these are the values that I see when I reload my form. If I then look at my start_time attribute in my model it is correctly set to Tue, 21 Aug 2012 15:00:00 EST +10:00.
The problem I am having occurs if I use a date later this year once daylight savings kicks in (I am in Australia). If I use 21 Dec 2012 and 15:00 then check start_time I see Fri, 21 Dec 2012 16:00:00 EST +11:00.
My interpretation of the problem is that the date is being saved in my current time zone (+10:00) as this is what I have told DateTime.parse to do. However when the value is returned, Rails is looking at the date and saying 'hey, it's daylight savings time in December' and returning the time in the +11:00 time zone.
What I want to do is tell DateTime.parse to save the time in the +11:00 time zone if DST is in effect. Clearly passing Time.zone into my string doesn't achieve this. Is there a simple way of doing this? I can see ways of doing it using Time#dst? but I suspect that this is going to create some really ugly convoluted code. I thought there might be a built in way that I'm missing.
(Answer for Rails 4.2.4, didn't check for older or newer versions)
Instead of using fixed shift +01:00, +02:00, etc, I recommend to use the in_time_zone String method with time zone name as argument :
Summer time :
ruby :001 > "2016-07-02 00:00:00".in_time_zone('Paris')
=> Sat, 02 Jul 2016 00:00:00 CEST +02:00
Winter time :
ruby :002 > "2016-11-02 00:00:00".in_time_zone('Paris')
=> Wed, 02 Nov 2016 00:00:00 CET +01:00
String#in_time_zone is the equivalent of :
ruby :003 > Time.find_zone!("Paris").parse("2016-07-02 00:00:00")
=> Sat, 02 Jul 2016 00:00:00 CEST +02:00
ruby :004 > Time.find_zone!("Paris").parse("2016-11-02 00:00:00")
=> Wed, 02 Nov 2016 00:00:00 CET +01:00
You can get the time zone names by :
$ rake time:zones:all
Or in rails console :
ruby :001 > ActiveSupport::TimeZone.all.map(&:name)
Or build collection for select tag :
ActiveSupport::TimeZone.all.map do |timezone|
formatted_offset = Time.now.in_time_zone(timezone.name).formatted_offset
[ "(GMT#{formatted_offset}) #{timezone.name}", timezone.name ]
end
And store the time zone name instead of the shift.
Note : don't confuse String#in_time_zone method and the Time#in_time_zone method.
consider the time zone for my system is 'Paris'.
ruby :001 > Time.parse("2016-07-02 00:00:00")
=> 2016-07-02 00:00:00 +0200
ruby :002 > Time.parse("2016-07-02 00:00:00").in_time_zone("Nuku'alofa")
=> Sat, 02 Jul 2016 11:00:00 TOT +13:00
Here's my solution so far. I'm hoping someone has a better one.
start_time = DateTime.parse "#{date} #{(form_start_time || start_time)} #{Time.zone}"
start_time = start_time - 1.hour if start_time.dst? && !Time.now.dst?
start_time = start_time + 1.hour if Time.now.dst? && start_time.dst?
It seems to work but I haven't rigorously tested it. I suspect it could be prettied up and shortened but I think this is readable and understandable. Any improvements?
I ran into this exact issue. My app allows users to see upcoming events. In the US we fall of DST on November 2nd and all events on and after that date were showing times an hour early.
We require the opportunity to have the timezone selected and stored to its own field. Before I was using the following to store my datetime:
timezone_offset = Time.now.in_time_zone(params[:opportunity][:time_zone]).strftime("%z") #-0700
DateTime.parse("#{params[:opportunity][:start_datetime]} #{timezone_offset}")
To fix the issue I have changed to:
start_datetime = Time.zone.parse(params[:opportunity][:start_datetime])
To display the correct times we use:
#opportunity.start_datetime.in_time_zone(#opportunity.time_zone)
I wouuld try and use
Australian Eastern Standard Time (AEST) (UTC +10).
Australian Central Standard Time (ACST) (UTC +9 ½).
Australian Western Standard Time (AWST) (UTC +8).
which adjust for Daylight Savings.
With Rails, we can use ActiveSupport::TimeZone for this:
tz = ActiveSupport::TimeZone.new 'Pacific Time (US & Canada)'
tz.parse(date_str_without_zone).to_datetime
I use TZip to get TimeZone strings (e.g. "Pacific Time (US & Canada)") from zip codes.
In case you have a custom date/time format, different than the supported by String#in_time_zone, you could also use (since rails 5) strptime like:
Time.find_zone!('Auckland').strptime('2021-02-02 08.00.00', '%Y-%m-%d %H.%M.%S')
i've got a Course model, which has a datetime attribute. If I look at it from the database i get one time, and if i look at it from the object i get a different date & time.
>> Course.last.attribute_for_inspect :datetime
=> "\"2012-01-04 01:00:00\""
>> Course.last.datetime
=> Tue, 03 Jan 2012 19:00:00 CST -06:00
Does anyone know why this value is different, and what I can do to fix it? The time from Course.last.datetime is correct, but my queries on the course table aren't working correctly due to the mix-up.
From the fine manual:
attribute_for_inspect(attr_name)
Returns an #inspect-like string for the value of the attribute attr_name.
[...]
Date and Time attributes are returned in the :db format.
So, when attribute_for_inspect is used for a datetime, it will return the string that the database uses for that value. Rails stores times in the database in UTC and any sensible database will use ISO 8601 for formatting timestamps on the way in and out.
2012-01-04 01:00:00 is the UTC timestamp in ISO 8601 format. When Rails returns a datetime, it converts it to an ActiveSupport::TimeWithZone instance and includes a timezone adjustment based on the timezone that you have configured in your application.rb. You're using the CST timezone and that's six hours behind UTC; subtracting six hours from 01:00 gives you 19:00 and you lose a day from crossing midnight. The human friendly format, Tue, 03 Jan 2012 19:00:00 CST -06:00, is just how ActiveSupport::TimeWithZone represents itself when inspected and the console uses x.inspect to display x.
As far as fixing your queries goes, just use t.utc before sending in a time t and you should be good.
Configuring your Rails App Timezone in application.rb
set config.active_record.default_timezone to :local as it is set to :utc by default in the application.rb
paste this code in your application.rb
config.active_record.default_timezone = :local #or :utc
config.time_zone = "Singapore" #your timezone
If I have #today = Date.today.to_s, how do I convert #today into UTC (with the appropriate date only)?
Here I need is only date for example 2011-03-08 ie 08 March 2011. Please suggest something ?
Acutally I am looking for Yesterday date ??
You'll need to convert it to a Time object (or just use Time anyway) and then call Time#utc:
irb > Time.now
=> Tue Mar 08 15:32:36 +1100 2011
irb > Time.now.utc
=> Tue Mar 08 04:32:40 UTC 2011
You can then format it however you need it:
irb > #today = Time.now.utc
=> Tue Mar 08 04:34:25 UTC 2011
irb > #today.strftime("%Y-%m-%d")
=> "2011-03-08"
If you want to convert #today into UTC.
Then try this
>> #today = Date.today.to_s
>> DateTime.parse(#today)
Try
1.day.ago.utc
or
1.day.ago.utc.strftime('%b %B, %Y')
The format below should give you the date format of Yesterday which you are looking for formatted as 07 March, 2011. Look into the ruby Time class manual for more information on strftime time formating function. Good luck!
I strongly recommend you to move towards time zones in rails. Its easier and lot more convenient to work with than Time.now. You should be able to set the time zone in environment.rb with config.time_zone = "Chennai" or your time zone. After doing this, you should be able to get the time with UTC information by Doing Time.zone.now. To find the UTC offset, you could type Time.zone.