Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails - handling multiple different time zones on the same request

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.

like image 441
Alex Ghiculescu Avatar asked Oct 03 '22 05:10

Alex Ghiculescu


1 Answers

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."

like image 104
JosephL Avatar answered Oct 07 '22 18:10

JosephL