I have a form that looks like
= form.datetime_select :due_at
= form.time_zone_select :time_zone, ActiveSupport::TimeZone.us_zones
When this object is saved, we need it to apply the time zone that was selected. For example if the user selects "03-01-2013 11:00" and "Central Time (US & Canada)", I want the date/time, in this case 11AM to be as 11AM CST instead of our application default time zone of "Pacific Time (US & Canada)".
This is actually nested within another form so we cannot simply change Time.zone in the controller with an around_filter. We are trying to figure out a solution that works during daylight savings time as well.
If due_at and time_zone are both attributes of a single model, then you could use a before_save hook to make sure due_at is encoded in the time zone of time_zone before being stored in the database.
class YourModel < ActiveRecord::Base
before_save :apply_time_zone
protected
def apply_time_zone
self.due_at = ActiveSupport::TimeZone[time_zone].parse "#{due_at_start.to_s(:db)} UTC"
end
end
ActiveSupport::TimeZone[t].parse(s) will parse s into time zone t, and .to_s(:db) converts a time to string in UTC.
Related
I receive time-stamped data from some equipments, who are configured with a timezone (Europe/Paris, for example).
Problem is that some of them are not aware of DST (Daylight Saving Time) change, and some are, which causes us to have a offset problem when we're in DST (like right now).
Today I have the following code to parse this data:
def parse_timestamp(timestamp, format=nil)
case timestamp
when String then #timezone.local_to_utc(Time.strptime(timestamp, format))
when Integer then #timezone.local_to_utc(#timezone.at(timestamp))
else raise Etl::Parsers::TimeConversionError, "Can't parse timestamp #{timestamp.inspect}"
end
end
The #timezone variable is created like this: ActiveSupport::TimeZone[timezone].
I'm looking for a way to ignore the DST when the equipment have a #dst property (let's say) that tells wether its timestamps are DST aware.
ActiveSupport::TimeWithZone has a useful dst? method to check if the time is within Daylight Savings Time for the specified time zone:
Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
Time.zone.parse("2012-5-30").dst? # => true
Time.zone.parse("2012-11-30").dst? # => false
You can read more at http://api.rubyonrails.org/classes/ActiveSupport/TimeWithZone.html#method-i-dst-3F
So you can just check if time is within DST, or extend TimeWithZone, as explained here Make Rails ignore daylight saving time when displaying a date by #zeantsoi:
class ActiveSupport::TimeWithZone
def no_dst
self.dst? ? self - 1.hour : self
end
end
and use it this way always getting the time without DST:
time.in_time_zone("Eastern Time (US & Canada)").no_dst
I know there is a method to determine if a certain time is on Daylight Savings Time (Time.now.dst?) but is there a method to give us the next date when Daylight Savings will change?
For example, Google returns Sunday, November 1 as the next Daylight Savings Time change in 2015.
Since these are dates that are based on other values, like the timezone you are working with, it requires a module like ActiveSupport/TZInfo.
require 'active_support/core_ext/time/zones'
tz = TZInfo::Timezone.get('US/Pacific')
# pick a timezone to work with
tz.current_period #returns an object of the current period
=> #<TZInfo::TimezonePeriod: #<TZInfo::TimezoneTransitionDefinition:
#<TZInfo::TimeOrDateTime: 1425808800>,#<TZInfo::TimezoneOffset: -28800,3600,PDT>>,
#<TZInfo::TimezoneTransitionDefinition: #<TZInfo::TimeOrDateTime: 1446368400>,
#<TZInfo::TimezoneOffset: -28800,0,PST>>>
tz.current_period.local_start.to_s
# => "2015-03-08T03:00:00+00:00"
tz.current_period.local_end.to_s
# => "2015-11-01T02:00:00+00:00"
One thing I haven't figured out is that since regular Ruby Core does this:
Time.now.dst?
# => true
Where is it getting this info? I found the TZInfo classes through ActiveSupport. Is Ruby just getting a boolean value from the OS?
How about this extension of the Time class:
class Time
class << self
def next_dst_change
startdate = Date.today
enddate = Date.today.end_of_year
match = Date.today.to_time.dst? ? false : true
startdate.upto(enddate).find { |date| date.to_time if date.to_time.dst? == match }
end
end
end
Now you can do Time.next_dst_change. You can apply this on your own timezone only but it solves your problem.
Datetimes are being stored in MySQL as UTC.
In my app I've set: config.time_zone = 'UTC'
In my application_controller I set the users selected timezone:
around_filter :user_time_zone, :if => :current_user
def user_time_zone(&block)
Time.use_zone(current_user.time_zone, &block)
end
I can confirm this works by <%= Time.zone.now%> as it returns the user's set timezone
I then do a select query to grab the datetime field, and present it to the user as part of an array.
In my view, I have:
<%= Time.zone.parse(item[1]).strftime("%m/%d/%Y %I:%M %p") %>
which just outputs the datetime as-is (UTC), not in the user's specific time zone.
What do I need to change in order for the date and time to display correctly in the user's desired timezone?
Thanks for your time and assistance.
Edit:
Per Benjamin's suggestions, I had to modify it slightly to get it to work (was getting the same error) - Time.strptime(item[1], '%Y-%m-%d %H:%M:%S').in_time_zone(Time.zone) but 2 issues still remain.
1 - the original datetime is: 2013-07-25 22:27:50, but what is displayed is: 2013-07-25 16:27:50 -1000 my user timezone is Hawaii at -10 from UTC, but this only shows a 6 hr difference?
2 - How do I get this in a format that is easily readable by users (07/25/2013 12:27 PM)? Thanks again
Solved:
Thanks to Benjamin. I forgot to mention that I'm (stuck) using 1.8.7, so I had to work through a few minor differences between 1.8.7 and 1.9.3, but got it working with:
<%= DateTime.strptime(item[1], '%Y-%m-%d %H:%M:%S').in_time_zone(Time.zone).to_formatted_s(:long)%>
Updated: Got it into the format I wanted (06/20/2013 01:00 AM) using:
DateTime.strptime(item[1], '%Y-%m-%d %H:%M ').in_time_zone(Time.zone).strftime("%m/%d/%Y %I:%M %p")
Try this
Time.strptime(item[1], '%Y-%m-%dT%H:%M:%S%z').in_time_zone(Time.zone)
Answer to the bonus questions
Check the time zone of your db server and double check your rails data (default config / user TZ)
use to_formatted_s http://api.rubyonrails.org/classes/Time.html#method-i-to_formatted_s
I am tracking user activity:
def track
UserActivityTracker.new(date: Date.today.to_s).track
end
#application.rb
config.time_zone = 'UTC'
How to be sure that days are tracked in the Pacific Time (US & Canada)
time zone.
I don't want to change time zone in application.rb
Rails will store your data in the db using UTC (which is a good thing)
i don't think changing config.time_zone on an existing app is a good idea, the UTC default is probably best
When rails pulls data from the db using ActiveRecord, it will convert datetimes based on the Time.zone settings for that request
Date.today
# => server time, rails does not convert this (utc on a typical production server, probably local on dev machine)
Time.zone.now.to_date
# => rails time, based on current Time.zone settings
You can set the current users time zone in a before_filter on ApplicationController, then when you display datetimes use the I18n helpers
I18n.localize(user_activity_tracker.date, format: :short)
# => renders the date based on config/locals/en.yml datetime:short, add your own if you wish
# => it automatically offsets from UTC (database) to the current Time.zone set on the rails request
If you need to display a time that is not the same as the current Time.zone request setting, use Time.use_zone
# Logged on user is PST timezone, but we show local time for an event in Central
# Time.zone # => PST
<% Time.use_zone('Central Time (US & Canada)') do %>
<%= I18n.l(event.start_at, format: :time_only_with_zone) %>
<% end %>
when saving data, don't bother doing a conversion, let rails save it as UTC, you can display the value in any timezone you wish using the helpers
see also:
http://railscasts.com/episodes/106-time-zones-revised
http://guides.rubyonrails.org/i18n.html
http://api.rubyonrails.org/classes/Time.html#method-c-use_zone
http://www.ruby-doc.org/core-2.0/Time.html#method-i-strftime
Replace config.time_zone this way :
config.time_zone = 'PST'
If you don't want to change all the dates, you can use Time.zone_offset
good_date = bad_date + Time.zone_offset('PST')
You can add the offset in the initialize or in a before_xxx callback.
User model has time_zone, which stores say "Pacific Time (US & Canada)".
Scenario:
1. Current_user lives in Japan timezone
2. Current_user is viewing profile of John who lives in "Pacific Time (US & Canada)"
3. On John's profile, I need to show local time at "Pacific Time (US & Canada)" as 10:01 am
What I did?
ActiveSupport::TimeZone.new(#user.time_zone).now returns something like 2011-09-21 10:01:56 UTC
Questions:
1. How to do I convert this to 10:01 am?
2. Why is it UTC?
Please help
To set TimeZone, you need to use
Time.zone= #user.time_zone
You may want to set this in :before_filter in application.rb
OTOH, you can use use_zone method with a block
Time.use_zone(#user.time_zone) do
#.... This code will run as if you were in #user.time_zone
end
You can use this
Time.zone = 'Pacific Time (US & Canada)'
to get the local time
Time.zone.now #=> Wed, 21 Sep 2011 02:25:08 PDT -07:00
to get 10:00 am/pm
Time.zone.now.strftime("%I:%M %p") #=> 02:25 AM
Try smth like this, in you User controller:
def show
#user = find(params[:id])
Time.zone = #user.time_zone
end
To change format:
Time.now.strftime("at %I:%M%p")
The ideal way to handle with time is save the data in UTC format in the back end, this will make the conversion process simple.
As Chandra Patni mentioned above, you may use those methods for conversion.
To display the time object in formatted string you may use rails strftime method, http://apidock.com/rails/ActiveSupport/TimeWithZone/strftime