Autodetect timezone in Rails given UTC offset and DST - ruby-on-rails

I basically want to autodetect a user's timezone using Rails. I can use this JS code at the user's browser (http://www.onlineaspect.com/2007/06/08/auto-detect-a-time-zone-with-javascript/) to send a form with the UTC offset and the fact that the time zone observes DST during summer or not, in the user's time zone.
Once I have that info in the server, I want to select the matching time zone. In Rails, I can get a list of time zones with ActiveSupport::TimeZone.all. Also, I can filter zones by utf offset thanks to the utc_offset method. However, I don't know how to filter the timezones that do/don't observe DST.
E.g. suppose a user lives in Amsterdam. Filtering by UTC offset will return Berlin, Belgrado, Madrid, etc timezones, as well as West Central Africa. All of them, but West Central Africa, would be appropriate timezones for a user in Amsterdam (as they provide the same time/date), but I need to filter West Central Africa, which does not perform DST in summer.
How can I do this in Rails? Also, are any of my assumptions wrong?

I thought maybe http://geokit.rubyforge.org/ would be helpful. You could get the coordinates of user location (or city/ country). The only thing you would have to do is guess the time zone based on city/country.

Related

How to handle timezones in Rails when using fixed time ranges

I feel like I'm going round the houses on this one.
In summary, I have a Location model which has opening hours, modelled as opens_at and closes_at in the DB.
All times are stored as UTC in the database.
If I create a location in New York with opens_at set at 08:30 in my form and it gets saved to the DB as 08:30 UTC, why would I need to worry about timezones? I don't want to display the time in local time because the user would then see opening hours of 03:30.
Am I overthinking this? Do I only need to worry about timezones if I have users in multiple countries attending the same event at the same time?
As this is about physical locations, it feels to me that it wouldn't be needed.
If I create a location in New York with opens_at set at 08:30 in my form and it gets saved to the DB as 08:30 UTC, why would I need to worry about timezones?
Because 08:30 in New York is not 08:30 UTC. It's not even the same time for all dates.
It's 13:30 UTC when New York is observing Eastern Standard Time (EST, UTC-5)
It's 12:30 UTC when New York is observing Eastern Daylight Time (EDT, UTC-4)
So, if the location opens at the same time every day, then you must store it in local time. Otherwise you'll be off by an hour one way or the other following the next DST transition.
This can be an issue even for time zones that don't use DST, and even for non-recurring events, because the various governments of the world control the time zones within their borders. They can change whether to start observing DST or stop observing it, the dates and times that DST transitions occur, or their standard offset from UTC. Some give adequate notice when they make such changes, and others do not. There's always some non-zero chance that a given time zone might change its behavior between when you initially recorded the event and when the event takes place. If you recorded using local time, all you need to do is update the time zone data on your systems (which often happens automatically). However, if you recorded the event using UTC then you could be off when converting back after such a change.
In general, future events should always be stored in terms that the event is described - which is almost always in local time of some particular time zone. Store the IANA time zone ID (e.g., "America/New_York") with the location or event so that you can convert when you need to, but converting to UTC prematurely can lead to losing sight of the original information.
Save the "Always UTC" mindset for past or present events, such as timestamping a transaction.
Yes, I actually have run into this. If the user is only going to see the time in the local time zone, then it is not really necessary to save in UTC with time zone. You can save it in local time without time zone.
Reference: Postgres Time without Time Zone
Some developers will say that you should save it in UTC with the time zone, but I can see two arguments against it:
If you don't need it now and you may never need it.
You may not have access to the Time Zone now or may need extra code/complexity to implement and validate.
You can probably add it later if you know the time zone or lat/lon or address.

What is the opposite of an AoE expiry?

I'm speccing an application that displays time periods to the user. The goal is to present periods in a simple view (no time, no timezones) and detailed view (date and time, with timezone data). The simple view should be unambiguous, in other words the user can glance at it and their assumptions about what they see are correct (they are valid in the local timezone).
For the end of the global period, displaying the date in the AoE timezone [1] will solve this problem. For example, a submission deadline might display as 2018-04-03 (actually 2018-04-03 23:59:59 AoE). This means submissions are accepted as long as it is April 3 somewhere on the planet.
But I also want to indicate that start of a global period. For example, if submissions open on April 2 2018 00:01, they are accepted as soon as it is April 2 somewhere on the planet. (This would currently be at UTC+14, matching the Line Islands.)
I can't see a way to use AoE to derive a global start time. Is there an equivalent to AoE (a standardized semantic timezone) that tracks the global start time?
Notes:
Hardcoding UTC-12 and UTC+14 is the simple answer for the modern day. But I'm looking for semantic timezones that would be updated if the values changed (and not reference non-existent historical datetimes).
I thought I'd seen Etc/AoE in the tz database but this is not the case.
References:
AoE
UTC-12:00
UTC+14:00
[1] The Anywhere on Earth (AoE) timezone represents the moment a datetime expires "anywhere on Earth". It currently matches time at Howland Island (UTC-12). If a UTC-13 timezone were invented, it would be updated to track that.
As far as I could understand, AoE is not a timezone as defined by IANA (AFAIK, a list of all offsets from some geographic region during history).
It's more like a "concept", an idea of a specific date being valid in any place on earth. As you said, this notion of "being valid" will change if more timezones are created or removed.
I don't even know if date/time API's can properly handle AoE automatically - maybe I should study more. But my conclusion is that the only way to achieve your goal is to check manually:
you could check all available timezones and see if the date is valid there, comparing to the current date/time at that zone
you could configure the UTC+14 as the offset to be compared, and make some scheduled job (daily/weekly/every-time-IANA-publishes-a-new-version?) to check all zones and set the correct one (with the biggest offset?). You must also take care if this zone has Daylight Saving changes, because the offset will change as well (and what to do with overlaps, when clocks shift 1 hour back and a local time may exist twice?)

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.

How do I get the utc offset from an address?

I have a RoR application where I store locations using gmaps4rails. Based on the address of the location, I'd like to guess the UTC offset ("+00:00", "-05:00", ..).
Is it possible or I have to ask users (and trust them) to set the correct timezone offset?
By the way, I know that there are some places where the timezone offset may change during the year. In this case, I should use the zone name instead of the offset. Is there a gem that helps to deal with these issues?
Thank you,
Geolocate the address to lat/lon coordinates using any number of web services for this. Then see the options in How to get a time zone from a location using latitude and longitude coordinates? The Ruby gem that Yevgeniy mentioned is listed there, along with other options.
Don't try to get an offset for the location - it could change periodically for DST. Get the time zone instead, such as Asia/Singapore or America/New_York. Then use that to determine the offset for a particular point in time.
See "Time Zone != Offset" in the timezone tag wiki.
Even if you get the solution, it is better to ask them from user. If user in the US wants to see his/her datetime according to timezone in Singapore (because that is his/her homeown), he/she should be shown it like that.

How to handle time difference in Rails application?

In my Rails application I would like to store the last_login time of a user in a database. Then I would like to display the last_login time to user based on his local time (i.e. it should be X if user is in Canada now, and Y if user is in Australia now).
Question 1
How would you recommend to handle the time difference ? Should I store in database the last_login translated to UTC, and when displaying translate it again to the local time, or is there a better method ?
Question 2
How could I identify the local timezone for displaying the last_login ?
I think you are right about storing last_login_at in UTC (or whatever server time is). It will be stored in that time by default. Then you can convert it to user local time:
user.last_login_at.in_time_zone('Eastern Time (US & Canada)')
In Rails Api you can find more about timezones and about time with zone.
To determine user timezone you can use some GeoIP tool, for example this one (follow description there). But it's not 100% accurate detection. The most reliable way to ask user select his timezone when he signing up.
To collect all timezones you can run:
ActiveSupport::TimeZone.all.map(&:name)

Resources