get time until hour in user timezone - ruby-on-rails

I'm trying to get the time until 12:00 AM in the users timezone. How can I do this in rails?
I want to get the time until 12:00 AM and then add that time to the current time in rails to store it in the database because I want to have a field with the GMT time at is the equivalent to 12:00 AM in the users timezone
I'm using this gem:
gem 'time_difference', '~> 0.5.0'
In order to get the time difference between two timestamps
TimeDifference.between(DateTime.now, created_at)
But I'm not sure how to get the time until 12:00 AM in the users timezone.
Thanks

Given a particular timezone, you can use Rails' tools for dealing with timezones to just directly find out when midnight is for a particular timezone. This example supposes you have a time_zone column on your User model with an appropriate value (e.g., America/Chicago, or anything else Rails supports):
midnight_for_user = ActiveSupport::TimeZone[user.time_zone].now.midnight.tomorrow
For example, I can use the same logic to find when midnight is for a person in New York:
pry(main)> Time.current
=> Thu, 05 Jan 2017 10:34:02 CST -06:00
pry(main)> ActiveSupport::TimeZone['America/New_York'].now.midnight.tomorrow
=> Fri, 06 Jan 2017 00:00:00 EST -05:00
Note that I'm looking for midnight tomorrow; remember that "midnight" for a given day is actually the very first minute of the day, not the last.

Related

Create TimeWithZone object from integer (unix epoch time with millisecond precision) and with specified zone (string)

I have an integer that represents a unix epoch time in millisecond precision and a time zone string. I need to create a TimeWithZone object with them.
epoch_ms_integer = 1586653140000
time_zone = "America/Los_Angeles"
Trying to convert to:
Sat, 11 Apr 2020 20:59:00 PDT -07:00
I was able to accomplish this by doing:
Time.at(epoch_ms_integer/1000).asctime.in_time_zone("America/Los_Angeles")
but was wondering if this is the best way to achieve this. The app I'm working on is configured to EST/EDT time zone so Time.at(epoch_ms_integer/1000) returns 2020-04-11 20:59:00 -0400.
I was able to find the asctime solution in one of the answers here Ruby / Rails - Change the timezone of a Time, without changing the value
the same question was asked here but no answer converting epoch time with milliseconds to datetime.
Assuming that the timestamp is in milliseconds, then 1586653140000 is
Epoch: 1586653140
GMT: Sunday, April 12, 2020 12:59:00 AM
PDT: Saturday, April 11, 2020 17:59:00 PM -in time zone America/Los Angeles
These are just 3 different ways to refer to a specific point in time around the world.
Sat, 11 Apr 2020 20:59:00 PDT -07:00 and 2020-04-11 20:59:00 -0400 each refer to different points in time and not the same as epoch(1586653140)
Since the Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 (midnight UTC/GMT), it wouldn't make sense to take 1586653140 and only change the time zone without adding the zone's offset because now you are talking about another point in time.
To get the right "translation" from the epoch to any time zone you could just do
Time.zone = "GMT"
Time.zone.at(1586653140)
=> Sun, 12 Apr 2020 00:59:00 GMT +00:00
Time.zone = "America/Los_Angeles"
Time.zone.at(1586653140)
=> Sat, 11 Apr 2020 17:59:00 PDT -07:00
When working with dates in time zones in rails it is important to only use functions that take the set time zone into account:
DON’T USE
Time.now
Date.today
Date.today.to_time
Time.parse("2015-07-04 17:05:37")
Time.strptime(string, "%Y-%m-%dT%H:%M:%S%z")
DO USE
Time.current
2.hours.ago
Time.zone.today
Date.current
1.day.from_now
Time.zone.parse("2015-07-04 17:05:37")
Time.strptime(string, "%Y-%m-%dT%H:%M:%S%z").in_time_zone
Also keep in mind that in a Rails app, we have three different time zones:
system time,
application time, and
database time.
This post by thoughtbot explains things clearly.

Daylight Savings Time ignored using in_time_zone Rails 4

I'm having a frustrating issue that I can't seem to narrow down. I have searched many similar articles but they are not close enough to my issue to resolve. I am trying to pull a time from the database and display it in more than one time zone. My Rails app is using UTC as default. Here is what I'm doing:
On the create action I take the string of time which will be saved in the time column in my DB:
params[:schedule][:start] = "09:00"
Time.zone = "Central Time (US & Canada)"
#schedule.start = Time.zone.parse(params[:schedule][:start])
The above formats the time as it is supposed to:
2016-04-12 09:00:00 -0500
This is saved in the DB as:
2000-01-01 14:00:00
This has no time offset which is fine since I know it's in UTC. The problem happens when I go to display the time:
#schedule.start.in_time_zone("Central Time (US & Canada)")
This returns:
Sat, 01 Jan 2000 08:00:00 CST -06:00
Now, since this is a time column, I don't care about the date. I plan on formatting the value to only show the time. However, it is showing CST when it is currently CDT.
I can't figure out why this is happening. As I said I am not setting the Time Zone anywhere in my application.rb or anywhere else and I only set the Time zone on the create action which should be fine when moving to a new action.
Any help on clarifying this would be awesome!
This seems to be because when the time is stored it is stored with the date in the year 2000-01-01 which seems to be why it is using CST. How can I ignore the date when converting it to a particular timezone or will I need to change the column type to DateTime to get this to work properly?
It is showing CST simply because the time is read from the database including the stored date, i.e. it's read as 09:00 of Jan 1st 2000.
I guess you'd have to parse the time upon reading the attribute back. You can use a helper method in your model, for example:
# schedule model
def start_in_zone(zone)
self.start.strftime("%H:%M").in_time_zone(zone)
end
This will take only the hours and minutes part of the stored time and parse it in the given time zone with the date set to today. See this example:
"Sat, 01 Jan 2000 08:00:00".to_time.
strftime("%H:%M").
in_time_zone("Central Time (US & Canada)")
# => Tue, 12 Apr 2016 08:00:00 CDT -05:00
The fact that it matters whether it's CST or CDT means you do, on some level, care about the date. While I'm not familiar with the exact rules of Daylight Savings in that region, I do know that Jan 1 is the middle of winter and will definitely not be on Daylight Savings time.
Add the relevant date into your #schedule before putting it into a time zone, and it should fix the problem.

Rails Records with times in different timestamps that occur on a date

Rails 4.2 and PostgreSQL 9.4.
I have events with starts_at and time_zone columns.
I'd like a SQL query to return all events that start, in their local time zone, on a specified date.
I.e. if an event starts at 11pm in Hawaii on Jan 24th:
t1 = ActiveSupport::TimeZone['Hawaii'].parse("2015-01-24 23:00:00")
=> Sat, 24 Jan 2015 23:00:00 HST -10:00
t1.in_time_zone('UTC')
=> Sun, 25 Jan 2015 09:00:00 UTC +00:00
And if an event starts at 1am in Hong Kong on Jan 24th:
t2 = ActiveSupport::TimeZone['Hong Kong'].parse("2015-01-24 01:00:00")
=> Sat, 24 Jan 2015 01:00:00 HKT +08:00
t2.in_time_zone('UTC')
=> Fri, 23 Jan 2015 17:00:00 UTC +00:00
The events occur on the 25th and 23rd of Jan in Rails' implicit UTC database zone, but both events occur on the 24th in their own time zone.
I'd like to write a SQL query akin to:
SELECT * FROM events WHERE (events.starts_at at time zone 'UTC' at time zone events.time_zone)::date = '2015-01-24';
(The initial conversion to UTC is required for a timestamp I believe)
However I get an error ERROR: time zone "Hawaii" not recognized
This is because PostgreSQL time zone names do not match with that of Rails (SELECT * FROM pg_timezone_names; for a list.)
Is there an alternative method where I can still do the query in SQL but not have this problem? I don't want to load the entire events set into Rails and process it there as it is very large and this is a frequent query.
As a small context as to why I need this query: I have a membership pass valid over a certain date range which gives you access to events which can occur in different time zones.
If there is no other way to do this, is there any tool that converts between the Rails names for time zones and that used by PostgreSQL so that I can save both the Rails zone and the PostgreSQL zone in the database record?
Using time zone abbreviations such as PDT instead (I don't even know if these are consistent between Rails and PostgreSQL either) doesn't deal with daylight savings time (which, granted, may not be an issue here but is not satisfactory).

Correcting time for daylight savings in rails DateTime.parse

Just adding to the million questions about time zone and DST issues out there.
I have a form with separate date and time fields that I combine to create a DateTime like so
start_time = DateTime.parse("#{parse_date(form_date)} #{form_start_time} #{Time.zone}")
If I fill out my form with 21 Aug 2012 and 15:00, then these are the values that I see when I reload my form. If I then look at my start_time attribute in my model it is correctly set to Tue, 21 Aug 2012 15:00:00 EST +10:00.
The problem I am having occurs if I use a date later this year once daylight savings kicks in (I am in Australia). If I use 21 Dec 2012 and 15:00 then check start_time I see Fri, 21 Dec 2012 16:00:00 EST +11:00.
My interpretation of the problem is that the date is being saved in my current time zone (+10:00) as this is what I have told DateTime.parse to do. However when the value is returned, Rails is looking at the date and saying 'hey, it's daylight savings time in December' and returning the time in the +11:00 time zone.
What I want to do is tell DateTime.parse to save the time in the +11:00 time zone if DST is in effect. Clearly passing Time.zone into my string doesn't achieve this. Is there a simple way of doing this? I can see ways of doing it using Time#dst? but I suspect that this is going to create some really ugly convoluted code. I thought there might be a built in way that I'm missing.
(Answer for Rails 4.2.4, didn't check for older or newer versions)
Instead of using fixed shift +01:00, +02:00, etc, I recommend to use the in_time_zone String method with time zone name as argument :
Summer time :
ruby :001 > "2016-07-02 00:00:00".in_time_zone('Paris')
=> Sat, 02 Jul 2016 00:00:00 CEST +02:00
Winter time :
ruby :002 > "2016-11-02 00:00:00".in_time_zone('Paris')
=> Wed, 02 Nov 2016 00:00:00 CET +01:00
String#in_time_zone is the equivalent of :
ruby :003 > Time.find_zone!("Paris").parse("2016-07-02 00:00:00")
=> Sat, 02 Jul 2016 00:00:00 CEST +02:00
ruby :004 > Time.find_zone!("Paris").parse("2016-11-02 00:00:00")
=> Wed, 02 Nov 2016 00:00:00 CET +01:00
You can get the time zone names by :
$ rake time:zones:all
Or in rails console :
ruby :001 > ActiveSupport::TimeZone.all.map(&:name)
Or build collection for select tag :
ActiveSupport::TimeZone.all.map do |timezone|
formatted_offset = Time.now.in_time_zone(timezone.name).formatted_offset
[ "(GMT#{formatted_offset}) #{timezone.name}", timezone.name ]
end
And store the time zone name instead of the shift.
Note : don't confuse String#in_time_zone method and the Time#in_time_zone method.
consider the time zone for my system is 'Paris'.
ruby :001 > Time.parse("2016-07-02 00:00:00")
=> 2016-07-02 00:00:00 +0200
ruby :002 > Time.parse("2016-07-02 00:00:00").in_time_zone("Nuku'alofa")
=> Sat, 02 Jul 2016 11:00:00 TOT +13:00
Here's my solution so far. I'm hoping someone has a better one.
start_time = DateTime.parse "#{date} #{(form_start_time || start_time)} #{Time.zone}"
start_time = start_time - 1.hour if start_time.dst? && !Time.now.dst?
start_time = start_time + 1.hour if Time.now.dst? && start_time.dst?
It seems to work but I haven't rigorously tested it. I suspect it could be prettied up and shortened but I think this is readable and understandable. Any improvements?
I ran into this exact issue. My app allows users to see upcoming events. In the US we fall of DST on November 2nd and all events on and after that date were showing times an hour early.
We require the opportunity to have the timezone selected and stored to its own field. Before I was using the following to store my datetime:
timezone_offset = Time.now.in_time_zone(params[:opportunity][:time_zone]).strftime("%z") #-0700
DateTime.parse("#{params[:opportunity][:start_datetime]} #{timezone_offset}")
To fix the issue I have changed to:
start_datetime = Time.zone.parse(params[:opportunity][:start_datetime])
To display the correct times we use:
#opportunity.start_datetime.in_time_zone(#opportunity.time_zone)
I wouuld try and use
Australian Eastern Standard Time (AEST) (UTC +10).
Australian Central Standard Time (ACST) (UTC +9 ½).
Australian Western Standard Time (AWST) (UTC +8).
which adjust for Daylight Savings.
With Rails, we can use ActiveSupport::TimeZone for this:
tz = ActiveSupport::TimeZone.new 'Pacific Time (US & Canada)'
tz.parse(date_str_without_zone).to_datetime
I use TZip to get TimeZone strings (e.g. "Pacific Time (US & Canada)") from zip codes.
In case you have a custom date/time format, different than the supported by String#in_time_zone, you could also use (since rails 5) strptime like:
Time.find_zone!('Auckland').strptime('2021-02-02 08.00.00', '%Y-%m-%d %H.%M.%S')

Rails: how to create Time object in specific time zone

My app is working in "Moscow" (+04:00) timezone. But sometimes I need to create time object by only local time (for example "01 may 2012 13:45") and name of ActiveSupport::TimeZone object (for example "Berlin": +02:00 in Summer Time and +01:00 otherwise).
For example if I get "01 may 2012 13:45" and "Berlin" as input I want to yield "2012-05-01 13:45:00 +0200" or "2012-05-01 11:45:00 +0000". I create following function:
def from_local_datetime(local_datetime, time_zone)
offset = Time.now.in_time_zone(time_zone).formatted_offset
datetime = case local_datetime
when String
DateTime.parse(local_datetime)
else
DateTime.new(local_datetime)
end.change(:offset => offset)
return datetime
end
And at the first look it works as I expected. But is it a best practice for this kind of task? May be in some situation It works with errors. I'm not definitely sure.
I would be greatful to any comments.
UPD: I think bug may occur about time when DST changing the time. For example 26 march 2011 was GMT+1 in Berlin time zone and Time.now.in_time_zone("Berlin").formatted_offset returns "GMT+1", but it would be GMT+2 in 27 march 2011. So if I call from_local_datetime("28 march 2011", "Berlin") before 27 march it returns 28 march 2011 00:00:00 +0100, but If I call it after changing the time my function returns 28 march 2011 00:00:00 +0200 :(
Your conversion method is the right approach.
With web sites, you should make sure times are stored as UTC in the database. If you can get the UTC value out of the database, instead of the local time (or maybe you can set your web server's time zone to UTC) it won't have to convert the time from UTC to local time, when you are going to then convert it to the user's timezone anyway.
And, of course, you will have to store the user's time zone preference.
TZInfo::Timezone.get('Europe/London')
Find the time zone
http://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html

Resources