How to manage different time_zones in ruby on rails - ruby-on-rails

Attributes of users are start_time,end_time and time_zone.
I am saving only time not date and user time_zone
e.g
start_time => "09:00"
end_time => "02:00"
time_zone => "Samoa"
User is only called b/w given start_time and end_time according to given time_zone. How I check this?
My research , I found this
in_time_zone('Eastern Time (US & Canada)')
I want to check that user is available or not according to its start_time and end_time based on time_zone

server_tz = Time.zone # zone on server
server_now = server_tz.now # time on server right now
Time.use_zone(time_zone) do # ⇐ user’s time zone, 'Samoa'
server_start, server_end = %i|start_time end_time|.map do |t|
# ⇓ parse in user time zone and convert to server tz
Time.zone.parse("#{Date.today} #{t}").in_time_zone(server_tz)
end
# ⇓ use case-triple-equal to check if time is in between
(server_start..server_end) === server_now
end

Related

How to convert datetime on a per user basis

I have left my application.rb as it was and by default it is storing datetime in UTC.
Now at the UI level (for displaying only) I want to convert the datetime to the user specific timezone.
How can I convert the UTC datetime I get from postgresql and then convert it to the timezone that I will store for each user.
I want to make this conversion using the Offset so like: -5:00 or +4:00
Is it ok to do this, because I was just checking some locations and it seems their offset changes during the season.
E.g. virginia goes from UTC -5 to UTC -4 depending on the month.
http://www.timeanddate.com/time/zone/usa/richmond
If UTC is not consistant then I should just store the zone in the database for each user?
When I made an app with scheduling events, I had to add timezones to the User model to offset their scheduling.
When I recorded the offset, I used the following code:
<%= time_zone_select "user",
"timezone",
ActiveSupport::TimeZone.us_zones,
{default: "Mountain Time (US & Canada)"},
{class: "form-control"} %>
Because Rails has time zones built into their ActiveSupport model. You could use ActiveSupport::TimeZone.all if you wanted users to be global.
Here's some info on time_zone_select
Then depending on how you use it, you can just set
Time.zone = current_user.timezone unless current_user.nil?
or something similar in your application.rb file.
UPDATE
I personally used it to set the timezone in the controller on the only 2 actions where it was necessary.
def index
#user = current_user
#pending = #user.share_events.where("time > ?", DateTime.now.in_time_zone(#user.timezone))
#complete = #user.share_events.where("time <= ?", DateTime.now.in_time_zone(#user.timezone))
end
def new
#user = current_user
Time.zone = #user.timezone
#post = Post.find(params[:post_id])
#share_event = ShareEvent.new
end

Filter for Time Zone ignored in controller and view

I defined a method in my ApplicationController that looks like such:
def use_time_zone(&block)
Time.use_zone('Pacific Time (US & Canada)', &block)
end
I then used,
around_filter: use_time_zone
to ensure that the method was applied to all times across the entire application. Despite my method and filter when I print
<%= Time.now %> <%= Date.today %>
in any view it prints the time value in the improper time zone. I read recently in the API documentation that around_filter: has been deprecated, if this is true what is now used in its place?
You can define Time zone in config of your application
config.time_zone = 'Pacific Time (US & Canada)'
# or
# config.active_record.default_timezone = 'Pacific Time (US & Canada)'

Rails datetime_select posting my current time in UTC

I'm trying to compare what the user selects for a start date and end date to the current time to prevent the user from selecting a time in the past. It works except you have to pick a time, in my case, 4 hours ahead in order for it to pass the validation.
View:
datetime_select(:start_date, ampm: true)
Controller:
if self.start_date < DateTime.now || self.end_date < DateTime.now
errors.add(:date, 'can not be in the past.')
end
self.start_date is returning my current time but in utc which is wrong. DateTime.now is returning my current time but with an offset of -0400 which is correct.
Example:
My current time is 2013-10-03 09:00:00.000000000 -04:00
self.start_date is 2013-10-03 09:00:00.000000000 Z
DateTime.now is 2013-10-03 09:00:00.000000000 -04:00
Why is this happening and what would be the best way to fix it?
you can do something like this
around_filter :set_time_zone
private
def set_time_zone
old_time_zone = Time.zone
Time.zone = current_user.time_zone if logged_in?
yield
ensure
Time.zone = old_time_zone
end
you can also do this
adding following to application.rb works
config.time_zone = 'Eastern Time (US & Canada)'
config.active_record.default_timezone = 'Eastern Time (US & Canada)'
I ended up fixing it by converting the start_date to a string and back to time. It was weird that I needed :local, as the documentation on to_time says it is the default, but it only works when it is present.
def not_past_date
current_time = DateTime.now
start_date_selected = self.start_date.to_s.to_time(:local)
end_date_selected = self.start_date.to_s.to_time(:local)
if start_date_selected < current_time || end_date_selected < current_time
errors.add(:date, 'can not be in the past.')
end
end

Get objects size between relative dates

I have many User objects with created_at attribute e.g.
I get objects with #users = User.all
I want get the count of User objects with various ages from creation with
#users.size
for these date ranges:
yesterday
last week
last month
last year.
How can I do it?
I use mongoid.
You can write scopes for this:
class User
include Mongoid::Document
...
## Scopes for calculating relative users
scope :created_yesterday, lambda { where(:created_at.gte => (Time.now - 1.day)) }
scope :created_last_week, lambda { where(:created_at.gte => (Time.now - 1.week)) }
scope :created_last_month, lambda { where(:created_at.gte => (Time.now - 1.month)) }
scope :created_last_year, lambda { where(:created_at.gte => (Time.now - 1.year)) }
...
end
The reason we need to use a lambda here is that it delays the evaluation of the Time.now argument to when the scope is actually invoked. Without the lambda the time that would be used in the query logic would be the time that the class was first evaluated, not the scope itself.
Now we can get the counts, by simply calling:
User.created_yesterday.count
User.created_last_week.count
...
If you want the objects:
#users_created_yesterday = User.created_yesterday
You can use #users_created_yesterday in the views.
Update
Well with yesterday, if you mean time between, yesterday beginning of day 0:00 and yesterday end of day 23:59, you will have to take Time zones into consideration.
Fo example, in your application.rb, if you have written:
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
config.time_zone = 'Central Time (US & Canada)'
If you have use this, all the times fetched by activerecord queries will be converted to this time zone, Central Time (US & Canada). In the database, all the times will be stored in UTC and will be converted to the time zone on fetching from the database. If you have commented out this line, default will be UTC. You can get all the time zones using the rake task rake time:zones:all or only the US timezones using rake time:zones:us.
If you want the time zone specified in the application.rb, you need to use Time.zone.now, in the following code. If you use Time.now in the following code, you will get the time zone according to the time zone of your server machine.
class User
include Mongoid::Document
...
scope :created_between, lambda { |start_time, end_time| where(:created_at => (start_time...end_time)) }
class << self
## Class methods for calculating relative users
def created_yesterday
yesterday = Time.zone.now - 1.day
created_between(yesterday.beginning_of_day, yesterday.end_of_day)
end
def created_last_week
start_time = (Time.zone.now - 1.week).beginning_of_day
end_time = Time.zone.now
created_between(start_time, end_time)
end
def created_last_month
start_time = (Time.zone.now - 1.month).beginning_of_day
end_time = Time.zone.now
created_between(start_time, end_time)
end
def created_last_year
start_time = (Time.zone.now - 1.year).beginning_of_day
end_time = Time.zone.now
created_between(start_time, end_time)
end
end
..
..
end
So, you can write class methods and calculate start time and end time, supply it to the scope created_between, and you will be able to call them like User.created_yesterday, like we called before.
Credits: EdgeRails. Since it is Mongoid, I had to look up at Mongoid docs

Rails time zone not overrides as well

I have a noisy problems with UTC on my Rails project.
class ApplicationController < ActionController::Base
before_filter :set_timezone
def set_timezone
Time.zone = current_user.time_zone if current_user
end
Cool. I overrided the time zone.
And now, server' time zone is +3. User's time zone is +5. I hope that any requests to Time should get the User's time zone, but this code returns not expected values:
render :text => Time.zone.to_s + "<br/>" +
Time.now.to_s + "<br/>" +
Time.now.in_time_zone.to_s
RESULT:
(GMT+05:00) Tashkent
Thu Oct 20 19:41:11 +0300 2011
2011-10-20 21:41:11 +0500
Where does from +0300 offset comes??
To get the current time in the currently set timezone you can use
Time.zone.now
Your server' time zone is +3 and
Time.now.to_s is returning this
saha! Sorry, but I have not a 15 points of reputation to give you the level-up)).
Anyway thanks for your help.
I wrote a TimeUtil helper, an uses it for time correction. This is my current pseudo-code:
class RacesController < ApplicationController
def create
#race = Race.new(params[:race])
#race.correct_time_before_save #can be before_save
#race.save
end
class Race < ActiveRecord::Base
def correct_time_before_save
date = self.attributes["race_date"]
time = self.attributes["race_time"]
datetime = Time.local(date.year, date.month, date.day, time.hour, time.min, time.sec)
datetime_corrected = TimeUtil::override_offset(datetime)
self.race_date = datetime_corrected.to_date
self.race_time = datetime_corrected.to_time
end
# TimeUtil is uses for time correction. It should be very clear, please read description before using.
# It's for time correction, when server's and user's time zones are different.
# Example: User lives in Madrid where UTC +1 hour, Server had deployed in New York, UTC -5 hours.
# When user say: I want this race to be started in 10:00.
# Server received this request, and say: Okay man, I can do it!
# User expects to race that should be started in 10:00 (UTC +1hour) = 09:00 UTC+0
# Server will start the race in 10:00 (UTC -5 hour) = 15:00 UTC+0
#
# This module brings the workaround. All that you need is to make a preprocess for all incoming Time data from users.
# Check the methods descriptions for specific info.
#
# The Time formula is:
# UTC + offset = local_time
# or
# UTC = local_time - offset
#
module TimeUtil
# It returns the UTC+0 DateTime object, that computed from incoming parameter "datetime_arg".
# The offset data in "datetime_arg" is ignored - it replaces with Time.zone offset.
# Time.zone offset initialized in ApplicationController::set_timezone before-filter method.
#
def self.override_offset datetime_arg
Time.zone.parse(datetime_arg.strftime("%D %T")).utc
end
ActiveRecord getters adapted to user's time zones too. Time is stored in database (mysql) in "utc+0" format, and we want to get this time in current user's timezone format:
class Race < ActiveRecord::Base
def race_date
date = self.attributes["race_date"]
time = self.attributes["race_time"]
datetime = Time.utc(date.year, date.month, date.day, time.hour, time.min, time.sec).in_time_zone
datetime.to_date
end
def race_time
date = self.attributes["race_date"]
time = self.attributes["race_time"]
datetime = Time.utc(date.year, date.month, date.day, time.hour, time.min, time.sec).in_time_zone
datetime.to_time
end

Resources