How to store timezone info in rails - ruby-on-rails

In my rails application,users can create questions and publish that, anybody from any country can response for that.
We are designing database structure for that. so planning to get user timezone using some js and while answering converting that time and to store in a separate column(tz_created_at).
so in created at the date will be stored in utc format, and in another column say tz_created_at the datetime will be stored as user's timezone converted time. (ie) in created_at column i have
irb(main):013:0> DateTime.now.utc => Sun, 30 Mar 2014 18:54:46 +0000
And in tz_created_at column i have
irb(main):015:0> DateTime.now
+ 6.hours(from user's timezone) => Mon, 31 Mar 2014 06:58:31 +0600
we are using sunspot solr to show some statistics in response over the time period.
so while querying for responses for a particular question i will search in tz_created_at column. is this approach is correct . please correct me if i am wrong

Instead of doing that, you probably want to store the datetime as UTC, then store the user's timezone in another column. By doing that you can always change the timezone if you need to without affecting the true time (e.g. the user moves country). When you display a time to the user you can easily convert the time to their zone to display it.
If you're a subscriber of railscasts there's a video on it there, but if you're not a subscriber there's an older one that's free to watch that will give you a general idea of best practices.

Related

Date saved 1 day earlier than in form

I have a form with a booking_date and when I fill in the form with a date of 1 March 2016, these are the parameters sent:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"[FILTERED]", "pturnover"=>{"booking_date(3i)"=>"1", "booking_date(2i)"=>"3", "booking_date(1i)"=>"2016"}, "commit"=>"Opslaan", "id"=>"5"}
But when I look in the database after saving, it is saved as 29 february 2016:
=> #<Pturnover id: 5, booking_date: "2016-02-29 23:00:00"
How is this possible, is it related to timezone or something?
Rails saves datetimes in database with utc timezone by default. Datetime conversion from/to strings may take into account local timezone, either one of the OS or set manually. Conversion between local and utc is a probable reason of the discrepancy you are seeing.
As timezone handling in Rails is a sizeable topic, it's advisable to approach it in some depth. Here are some relevant resources:
RailsCasts #106 Time Zones (revised)
The Exhaustive Guide to Rails Time Zones
Working with time zones in Ruby on Rails
It's About Time (Zones)
You can convert the inputed time before saving or you can convert that time when showing in your show page.Conversion may looks like below :
ActiveSupport::TimeZone.new("Asia/Tokyo").local(your_inputed_time_here)
Hope that helps.

ActiveRecord's weird timezone handling when Daylight Saving Time changes

I have an application providing agenda functionalities.
Disclaimer: I really love Rails and AR's functionalities, this is just meant to ask how can I exploit it functionalities better the next time.
Since it has to be used by a european team it has been developed using +0200 timing informations. I know that the timezone stuff is handled by AR and the base data is always stored in UTC.
The issue I get (but I already solved, just wanted to get some feedback to think it better the next time) is that when I store a new appointment I also enforce "+0200" at the end of the DateTime.parse call (es. DateTime.parse("#{day} #{hour} +0200") and then I set it as record attribute. Of course 10:00 gets stored as 08:00 inside the data layer, and that's fine.
Then when I retrieve that data using the configured AR timezone (Rome for instance) using something like #appointment.start_at it correctly gets converted back when we are under the CEST daylight time. Issues start happening when start_at are set to be CET (+0100) dates (but I always store it inside the database enforcing CEST +0200).
Inside the database start_at is always the same, so the UTC representation is always 08:00 when saving an appointment with 10:00 but the AR conversion uses the wrong daylight conversion (CET).
I know I could skip this by simply working all on UTC so I don't have to carry out any consideration about daylight savings times, but it seems a bit messy that AR itself is not able to remember somehow that I stored it while CEST is running.
When performing queries I do the same as when storing, so I append "+0200" to all DateTimes, and all works back again, but when displaying I need to check whether the TimeZone is "CET" or "CEST" and add a +1.hour to CET (otherwise it results the appointment has been signed 1 hour before than the intended hour).
Is it right I should handle this by myself?
Shouldn't be AR able to perform this kind of check since it wraps the data-layer?
How should I design an application the next time to avoid this kind of issue while still using local timezones? (as I said using UTC may also not be applicable by some requirements)
I thought I should change "+0200" with "in_time_zone" the next time, but I also allow users to write by hand an hour to search, and of course this will always be the same time either it is "CET" or "CEST" and of course I cannot enforce users to write following the UTC format.
Thank you,

Accounting for daylight savings in rails webapp and iCal

Right, this is a bit confusing for me, so I'm going to try and explain from the top!
I have a rails web app. It's an internal company app and will only be used in the UK.
One of the things the app does is manage meetings.
Meetings have a date & time when they start. There's a date/time picker on the form which allows the user to pick the date & time the meeting is for. I save this date AS IS into the database. All meetings last 2 hours, so the end time is simply start + 2 hours.
Example:
2013-06-23 6:45PM in the form is stored in the db as 2013-06-23 18:45:00
2013-12-23 6.45pm in the form is stored in the db as 2013-12-23 18:45:00
Note that the first date is during Daylight Savings (BST) and the second is during GMT. I don't actually care whether it is GMT or BST: the meeting happens at that time, absolutely.
Inside the rails webapp, I simply print out the exact date & time from the DB - formatted nicely, of course!
Now, at some point I send an email to the organiser of the meeting, and the person they're meeting with. This email tells them the the date & time of the meeting etc, and also includes an iCal (.ics) file for them to put into their (Outlook usually, but also Apple or gmail) calendar.
The issue I am having is that (using the above examples) Outlook shows the meetings like this:
Meeting #1: Start: 23/06/2013 7:45pm, End: 23/06/2013 9:45pm
Meeting #2: Start: 23/12/2013 6:45pm, End: 23/12/2013 8:45pm
Note that it has adjusted the first one because of the BST/GMT thing.
The text of the .ics file contains this code:
Meeting #1:
BEGIN:VCALENDAR
...
DTEND:20130623T204500Z
DTSTART:20130623T184500Z
...
END:VCALENDAR
Meeting #2:
BEGIN:VCALENDAR
...
DTEND:20131223T204500Z
DTSTART:20131223T184500Z
...
END:VCALENDAR
So I am encoding the dates/times using the Z timezone (UTC). I understand this is why Outlook mis converting the UTC time into the BST time for #1 and leaving #2 alone (because GMT == UTC)
My question is: how do I stop this happening? I want the time the meeting is scheduled for to be the absolute, actual time, regardless of GMT/BST: 6:45pm
Should I be storing the date-times as UTC in the DB? How would this be done (I assume it would apply to all dates, not just meeting start dates). And how to re-convert them back into the actual datetime when I display them in the webapp?
Extra:
I have an entry in my initializers/time_formats.rb like this:
:ical => "%Y%m%dT%H%M00Z"
So dates come out like "20130623T184500Z". I use this when building the ics. And this I think is the issue - if the date/time is during BST I don't want to be using Z, but something else?
Your problem is your date/time format. You have:
DTSTART:20130623T184500Z
in your .ics file and this corresponds to 19:45 BST (as British summer time is UTC+1).
There are a few things you should do. First, you can simply remove the 'Z' from the end of your dates. This means that the times inherit the timezone of the calendar, or the underlying application.
This will work assuming that the machines which are running Outlook are all in the Europe/London timezone. If not, or if you want to be a bit safer, you should also specify the following after your BEGIN: VCALENDAR line:
X-WR-TIMEZONE:Europe/London
This specifies the default timezone for all dates which are not specified explicitly.
Finally, if this does not work for any reason then you need to define your datetimes explicitly. First you need to add a timezone definition for Europe/London to the calendar. The info you need is available at http://www.tzurl.org/zoneinfo-outlook/Europe/London.ics. Then you need to ensure that all datetimes are of the format:
DTSTART;TZID=Europe/London:20130623T184500
This last approach is the best, as it means that if your requirements expand to other timezones you will be able to handle them relatively easily.
Sorry to answer this myself, but in case anyone else runs into this here's what I found was the cause of my particular issue. Note that the answer above re timezones also makes sense!
My rails app is storing UTC datetimes in the DB (as is default)
But, it also thought it's own timezone was UTC, which also seems to be the default.
The upshot of that is essentially it was storing local dates, local to UTC anyway. Changing the app to know it was sitting in Europe/London made it so the dates in the DB are all now accurately UTC (meaning, they're an hour off if I'm currently in BST)
I can now use the Z datetime format in iCals, and outlook and the rails app both convert the UTC date back into the actual datetime for the viewing-user's locale (Europe/London for everyone at the moment). This is what I wanted.

Rails/MySQL: Different user timezones

Question 1 - I have an app that is on shared hosting in Dallas, TX...so my database/web server are currently set to Central US time...I'm unable to change this, so UTC is off the table.
My application records and displays Date and Time data to the user, but every user could be in a different time zone.
If I configure my app (config.time_zone = 'Central Time (US & Canada)'), but then I allow users to select their timezone,
def user_time_zone(&block)
Time.use_zone(current_user.time_zone, &block)
end
will RoR display any returned data in the timezone of the user's choice? Or do I need to evaluate the data and modify it accordingly before it is displayed?
Question 1.1 - I have related mailers that send daily/weekly/monthly reports to users. If I run these using CRON, would I need to schedule my jobs to at different time intervals to allow for the timezones? In other words, if one job contains
when 'daily'
#dates = params[:f]['Date Range'] = "#{Date.current} - #{Date.current}"
Date.current will be 1 hour ahead on the East Coast, and 3 hours behind on the West and so forth. So (still using server timezone of Central), would I run one at 11pm (for East) and one at 2am (for West)?
Any and all advice/assistance is greatly appreciated. Thanks!
Very clear answer from Matt. You definitely want to save your time data in UTC TZ, then translate it to your user TZ (or server TZ for guests users)
You are using MySQL globally set to non-UTC TZ, you can override this setting with the following query
SET time_zone = 'UTC';
Problem is: this command is session wide in MySQL, you must set it again each time a new connection to you database is initialized. Rails ActiveRecord::ConnectionAdapters::ConnectionPool class can help you for that. See this answer: How do I execute queries upon DB connection in Rails?
Next you need to set up your server default TZ in application.rb (you did that) and set up each user to have his own TZ (and you did that as well)
Finaly you must follow Rails conventions each time you access time data. I found this article for you, full of very useful information it will answer all your question: http://www.elabs.se/blog/36-working-with-time-zones-in-ruby-on-rails
Extract from that article:
DOs
2.hours.ago # => Fri, 02 Mar 2012 20:04:47 JST +09:00
1.day.from_now # => Fri, 03 Mar 2012 22:04:47 JST +09:00
Date.today.to_time_in_current_zone # => Fri, 02 Mar 2012 22:04:47 JST +09:00
Date.current # => Fri, 02 Mar
Time.zone.parse("2012-03-02 16:05:37") # => Fri, 02 Mar 2012 16:05:37 JST +09:00
Time.zone.now # => Fri, 02 Mar 2012 22:04:47 JST +09:00
Time.current # Same thing but shorter. (Thank you Lukas Sarnacki pointing this out.)
Time.zone.today # If you really can't have a Time or DateTime for some reason
Time.zone.now.utc.iso8601 # When supliyng an API (you can actually skip .zone here, but I find it better to always use it, than miss it when it's needed)
Time.strptime(time_string, '%Y-%m-%dT%H:%M:%S%z').in_time_zone(Time.zone) # If you can't use Time#parse
DON’Ts
Time.now # => Returns system time and ignores your configured time zone.
Time.parse("2012-03-02 16:05:37") # => Will assume time string given is in the system's time zone.
Time.strptime(time_string, '%Y-%m-%dT%H:%M:%S%z') # Same problem as with Time#parse.
Date.today # This could be yesterday or tomorrow depending on the machine's time zone.
Date.today.to_time # => # Still not the configured time zone.
For best results, your application should be written such that the time zone of the computer that it is running on does not influence any data or behavior.
That usually means working in UTC in your code. For example, Time.now.utc instead of just Time.now. See this post for details for RoR.
It is a best practice to set the server time zone to UTC when possible, but that doesn't mean you should rely on that in your code.
Regarding your second question, keep in mind that every time zone has their own concept of a "day". See this site for a visualization.
You should run a separate job at midnight in each time zone that you are aligning your data to. Some applications (such as the StackExchange web sites) use a single time zone (often UTC) for all users. Other applications adjust the data for each user according to their own time zone. You need to decide what is appropriate for your application.
When scheduling your jobs, determine the UTC time that each time zone's midnight aligns to. Then schedule a job for that time. Be sure your scheduler understands that it is UTC. Don't try to adjust it to the server's local time zone, or you might introduce error when the local time zone goes through daylight saving time transitions.
Make sure that not only does the scheduled job run at the correct time, but that the report query uses the correct time zone in determining the UTC range that it should run for.
Note that this does mean that two users from different time zones reporting over the same data may end up producing different results - because each of their "days" is for a slightly different period.

Ruby on Rails timezone

My server is in US, rails default timezone is set to 'UTC', a user from India submits the form,Will rails convert that user's time zone from India to UTC and save it in created_at column or it will save the UTC current time?
It will save the current time in UTC. Created_at and updated_at are Active Records internal timestamp columns, and as such they are never user input.
Also, basic security protocol dictates that you should never use the client's time settings, always the servers.
Rails will do one thing though - it will use the webserver's current time in UTC and store it in the database instead of the database servers current time.
By default, times are stored in UTC + offset format. When you specify the default timezone as lets say "Mumbai" it will return the datetime objects converted for that timezone.
You can also do:
Time.zone.parse(#post.created_at.zone)

Resources