Rails - handling multiple different time zones on the same request - ruby-on-rails

I'm looking to display data across more than one time zone in the same view in a Rails app for a time and attendance system. A bit of context:
We make electronic time clocks. People but them in their businesses. Staff clock in and out of work and it records their hours.
The time clock pushes the time that someone clocked in/out to our API as a unix time (for example, our Javascript time clock implementation grabs the clock in time like so: moment().unix()). The API then stores this in a Postgres database as a timestamp without time zone.
When a user logs in to the site, an around_filter sets the appropriate time zone for the request based on a setting for this user's organisation.
The problem occurs if we have an organisation that spans multiple time zones. For example, a business that has an office in every Australian capital city will span three time zones (more during DST). However, there will be one person in a central office who will need to check data across the organisation - we'll call them our manager.
Suppose our manager is based in Sydney, and it's 11am. They manage three offices - one in Sydney, one in Brisbane (an hour behind Sydney during DST), and one in Adelaide (half an hour behind Sydney during DST). Staff clocked in at the three offices at 9am in their local times. So, on the manager's dashboard, all the times of the clock-ins should show up as 9am. However, the current implementation (using an around_filter) will show the times as 9am, 8am, and 8:30am, respectively, because they will be offset using the Sydney time zone.
There is a layer of filtering applied to staff from different cities, so it is possible to tell the system that person A is from Sydney, person B is from Adelaide, and person C is from Brisbane. The issue - which I'd like advice on - is how best to get Rails to display offset to different time zones as efficiently as possible.
Bonus credit: as well as showing times, we also need to read input. For example, someone may have clocked in 5 minutes early, and their timesheet needs to be corrected. If a local manager (ie. someone in Brisbane) corrects the timesheet for a Brisbane employee then that should be relatively easy to manage - given we know they are in Brisbane, we can just set the request's time zone to Brisbane and let ActiveRecord do the offsetting for us. But if the general manager (who is based in Sydney but manages all time zones) wants to make the change, then we need to be able to correctly convert their input back into UTC based on their time zone. Any suggestions on how best to do this would be wonderful.
Concrete example of the issue
In my database, my clock_ins table looks like this:
user_id (integer) | time (timestamp without time zone)
------------------|-----------------------------------
1 | "2012-09-25 22:00:00.0"
2 | "2012-09-25 22:30:00.0"
3 | "2012-09-25 23:00:00.0"
And my users table looks like this:
user_id (integer) | time_zone (varchar)
------------------|-----------------------------------
1 | "Sydney"
2 | "Adelaide"
3 | "Brisbane"
(this is a simplification, in reality there is another join between a user and their time zone)
If we apply each user's time zone to the time of their clock in, we find they are all at 9am local time. ie. 2012-09-25 23:00:00.0 at UTC is 2012-09-26 09:00:00.0 in Brisbane (+1000). The general approach in Rails is to use an around_filter to set the time zone for a request; if I did that here, each of the times would be displayed half an hour apart, which is not correct. So I'm looking on advice on best practices when working with times from various zones.

The simplest way I can see is to use the Time.use_zone method when rendering your times. e.g.
Time.use_zone('Sydney') { Time.current }
Time.use_zone(person.office.time_zone) { person.clock_ins.last.time_stamp }
This "Allows override of Time.zone locally inside supplied block; resets Time.zone to existing value when done."

Related

Querying by timezone vs location

Moment-timezone time has method momnet.tz that takes two params:
particular datetime
timezone name
It returns time shift (to UTC) that was at given timezone in given datetime. Ok.
The question that bothers me:
do all the locations that currently belong to a particular timezone were in the past belonging to that zone also?
so isn't it possible that if two locations even currently belonging to a certain timezone, in the past (even after 1970) had actually different timezones (shifts to UTC).
Is it possible in principle to query tz-db for specific some kind of location, not a timezone name.
Would be grateful if someone could eliminate my doubts.
Moment-timezone uses the data from the IANA time zone database (aka the TZDB, zoneinfo, or the Olson database). Most of your questions are addressed by that data, rather than by moment-timezone itself. You'll find that other implementations (for other languages, platforms, etc.) have similar behaviors.
There is a great deal of information about how the tzdb works in the theory file in the tzdb itself, and on Wikipedia, but I'll see if I can address your specific questions:
do all the locations that currently belong to a particular timezone were in the past belonging to that zone also?
The TZDB assigns time zones based on cities (because they are less likely to changes over time than other regional boundaries). Generally, one city within a given region whose clocks have been aligned since 1970 will be chosen to represent the time in that region.
When another part of that region changes their clocks differently than the rest of the region, a new time zone is created and a new city is chosen within that region to represent the zone. We call this a "zone split". Time before the split in both zones will match (except the LMT entry), and time at the split and forward will deviate. It doesn't matter if at some point in the future the time in these regions aligns again. There are now two zones and there will continue to be - because they deviated at some point in the past.
so isn't it possible that if two locations even currently belonging to a certain timezone, in the past (even after 1970) had actually different timezones (shifts to UTC).
If there is a distinct history of timekeeping in the region, then there will be two different time zone entries. So when you say "locations", if you mean two different cities with their own time zone names in the TZDB, then by definition they don't belong to the same time zone. For example, Europe/Moscow and Europe/Volgograd are both currently in UTC+3 year-round without DST. However at the start of 1992, Moscow was UTC+3 while Volgograd was UTC+4. Their histories before then deviate even further.
On the other hand, if you are talking about a location that is not specifically referenced in the TZDB, then there is a presumption of alignment. For example, Seattle is in the US Pacific time zone, all of which is represented by America/Los_Angeles. Because there is not a unique America/Seattle, the data is representing that Seattle does not have a unique time zone history than Los Angeles.
That said - there have been a few very minor edge cases that have come up in the past where a small town that is on the boundary line between two time zones has to chose between which zone to observe. It has also happened that a small town distinctly on one side of the boundary has chosen to unofficially follow the time zone in a neighboring larger city on the other side of a boundary. These changes are sometimes mentioned on the tzdb discussion list, but are rarely recorded in the data as a distinct zone.
With these edge cases, keep in mind that the TZDB only tracks cities - not regional boundaries that may divide cities or towns. For that, you'd have to go different data source. The best one I know of is Evan Siroky's timezone-boundary-builder project.
Is it possible in principle to query tz-db for specific some kind of location, not a timezone name.
You'll have to be more specific about what you mean by "location". If you mean a latitude/longitude coordinates - then the timezone-boundary-builder data, and the projects that use them, are the route to go. They will help you resolve a tzdb identifier, which you can then use with moment-timezone or other libraries.

Website with multiple timezones issue

I'm having a trouble when develop multiple timezones website.
Currently I'm storing time in UTC after some researches and it is working fine in most cases.
But there is one case that I couldn't find solution for it:
There are two kinds of user in two countries which are United States
and Thailand.
User in Thailand is worker (A).
User in US is manager (B).
When A starts working, their activities logged into our system and B
can watch those via a monitoring screen on web app and they can choose
the date on that.
Example user A starts working at 8 AM on 23 June with mobile
app, when B chooses 23 June date on the monitoring screen, they
can see the activities of user on Thailand on 23 June (because the results is queried by UTC time), but the
problem is he should see the activities on 22 June instead of 23
June because the time in Thailand is faster than United States 12
hours.
How can I show to user B activities of user A when he chooses the date 22 June?
You've not asked about any particular technology stack or implementation, so I can only answer from a general perspective.
Concepts worth understanding:
Thailand has a single time zone, which has an offset of UTC+7 all year.
The US has multiple time zones, whose offsets range from UTC-10 to UTC-4, depending on what part of the country you are referring to, whether or not daylight saving time is in effect, and whether or not a particular location observes daylight saving time. (Most of the country does, but all of Hawaii and much of Arizona does not.)
A "date" is just a year, month, and day on a calendar, but the time that which a date is observed is different depending on the time zone of the observer. There is a good visualization of this at everytimezone.com.
In your situation, you will have to decide the behavior you want depending on the specific needs of your application:
Do you want the period shown to represent all activities on the date as observed by the person choosing the date? If so, then determine the start of the current date and the start of the next date in the local time zone of the person selecting the date. Convert those to UTC, and query for all events in that UTC time range.
Example:
Example Activity Time: 2018-06-23T18:00:00+07:00 (Asia/Bangkok)
Stored as UTC: 2018-06-23T11:00:00Z
Date Selected: 2018-06-23 (America/New_York)
Local Range: [2018-06-23T00:00:00-04:00 , 2018-06-24T00:00:00-04:00 )
UTC Range: [2018-06-23T04:00:00Z , 2018-06-24T04:00:00Z )
Query: ... where ActivityUTC >= '2018-06-23 04:00:00' and ActivityUTC < '2018-06-24 04:00:00'
Or, do you want the date selected to always represent the date of the activity in the time zone of the person who recorded that activity, regardless of the time zone of the viewer? If so, then store that local date in a separate date-only column and just query on it without regard to time zone.
Example:
Example Activity Time: 2018-06-23T18:00:00+07:00 (Asia/Bangkok)
Local Date Stored: 2018-06-23
Date Selected: 2018-06-23
Query: ... where ActivityLocalDate = '2018-06-23'
Note, you might still store the UTC date and time in some other field, but it isn't relevant for this particular query.
From prior experience in the time and attendance industry, I can say that if it were me I would want the second option - as workers are typically paid based on their own time zones, not on those of their manager. However their are indeed edge cases and you'll have to decide for yourself which approach best matches your business requirements.
This Answer is specific to MySQL.
If you want B to see what A's clock says, use DATETIME; it will say 8AM.
If you want B to see A logging in in the middle of the night, use TIMESTAMP.
(This extends to A vs B, and to date as well as clock.)
Twice a year, DATETIME has a hiccup between 2AM and 3AM if there is a switch between standard and daylight-savings time.

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?)

Timezone aware postgres query create a timeseries for minutes, hours, days

I am having a hard time to figure out how to deal with the following problem:
Our company is publishing posts to social media platforms. Those posts are stored within the database once they where successfully postet.
We want to provide a dashboard showing an overview of how many posts the user published over a time period grouped by minutes, hours and days.
I want to display the results as a time series graph.
This would work fine, but it gets very tricky once I have to support multiple time zones when I do aggregation/grouping by days. (apparently posts around midnight belong to different days depending on which time zone you are)
My current solution builds the postgres query using rails ActiveRecord. The problem I am facing is that I am struggling to deal with the timezone conversions...
Also I am not particular good at postgres...
The current implementation essentially looks like this (I removed irrelevant code):
Publication.select(
%{date_trunc('#{interval}',
published_at::timestamptz at time zone interval '#{time_zone_offset}')::timestamptz as time,
count(published_at)})
.where(%(published_at BETWEEN
timestamptz '#{start_date}' AND
timestamptz '#{end_date}'))
.group("1")
.order('time').limit(LIMIT)
For example:
I have one publication at 2016-03-15 10:19:24.219258 (Thats how it is stored inside the database therefore UTC time)
I create the following query:
SELECT date_trunc('hour',
published_at::timestamptz at time zone interval '+01:00')::timestamptz as time,
count(published_at) FROM "publications" WHERE (published_at BETWEEN
timestamptz '2016-03-15 10:00:00 +0100' AND
timestamptz '2016-03-15 12:00:00 +0100') GROUP BY 1
;
Which results in:
time | count
------------------------+-------
2016-03-15 10:00:00+01 | 1
(1 row)
Which should be:
time: "2016-03-15 10:00:00 UTC" or "2016-03-15 11:00:00+01" ( i don't care about the time zone representation but this is simply the wrong result)
Anybody knows what I am doing wrong here?
The main problem I got stuck is that I want to be able to group/aggregate publications per day, with respect to the time zone of the user requesting the query.
I don't care which time zone is returned as the front end can transform it to the user time zone.
Any feedback, help, or answer is highly appreciated.
Many thanks
Thanks to the discussion I had with devanand one solution is to split up the code and handle the daily interval with the query used in the question.
For the other intervals I use the following query:
Publication.select(
%{date_trunc('#{interval}',
published_at::timestamptz) as time,
count(published_at)})
.where(%(published_at BETWEEN
timestamptz '#{start_date}' AND
timestamptz '#{end_date}'))
.group('1')
.order('time').limit(LIMIT)
I am not happy with the solution though as it feels more like a workaround to me

Google Sheets sorting by time

So I am in charge of office hours for a class at my University. I have to send out a google form every week for the students to sign up. The way the form is sent back, the data is grouped by Name, and the times they are available are grouped by days.
To make it easier on myself, I want to group it by times. Basically, I want it so that each day/time will have its own row, and the names of the people who want the time slot would be grouped together in the adjacent column.
I already separated the groups of times so that each time has its own cell, but I cannot figure out how to regroup the data by time.
I should also mention that each session of OH is 30 minutes and that each student can only receive one session of office hours a week because of limited availability. I also need to group the sessions into blocks so that the teachers won't have idle time (basically a teacher will have office hours from 5:00-6:30 PM on Tuesday, instead of 5:00-5:30 PM and 6:00-6:30 PM on Tuesday and 8:00-8:30 PM on Thursday). Grouping the names of the people who want office hours by the times that they are available would make it a lot easier to set up that week's office hours schedule.
Here's a link to an example:
https://docs.google.com/spreadsheets/d/1fRYYNUoEcgynU9cDMoXzsyjtIir4hsRlfFYJau36a78/edit?usp=sharing
have a look at this sheet ( i have only turned on sharing temporarily, co copy this to your own drive)
Look at the "matrix choices" sheet.
I created a Matrix of students and times (which I think is better), but there is also a list of names for each timeslot on the end too.
have a play changing some of the 1's (sessions applied for) to "G" (short for Granted). Ive set up some conditional formatting and data validation.

Resources