How to retrieve data in database with UTC datetime - ruby-on-rails

I need to fetch a data in our database as a report using Ruby on Rails but I have a problem with the date-time ex:
This order was made on:
but on our database it's dated plus 8hours because we saved it as UTC:
Then I have a query:
Spree::Order
.joins(user:[:corporate_account])
.includes(line_items:[:variant])
.where('spree_orders.completed_at IS NOT NULL')
.ransack(#params[:q])
start_date and end_date are converted into:
#params[:q][start_date] = params[:q][start_date].to_datetime.beginning_of_day
#params[:q][end_date] = params[:q][end_date].to_datetime.end_of_day
to get the orders starting at 12:00 am til 11:59:59 pm end of day.
I am not allowed to change how we saved the date-time in our database and we can't make a cut-off.
I can't also add 1 day at end of the day because the orders will have a discrepancy.
I also tried converting PST to UTC doesn't work because let's say I want to generate a report at 5 pm right now with a date of 2021/12/01, I have to convert it to end of the day so that's 2021/12/01 11:59:59 it won't include the orders made at 5 pm 2021/12/01 since it'll be offset on the database with 8hrs, it'll be saved as 2021/12/02 1am
So my question is how would you query a data with such date-time offset?

You could try something like this:
timezone = "Pacific Time (US & Canada)"
start_date = Date.parse(#params[:q][:start_date])
end_date = Date.parse(#params[:q][:end_date])
#params[:q][:start_date] = Time.find_zone(timezone).local(start_date.year, start_date.month, start_date.day, 0, 0, 0)
#params[:q][:end_date] = Time.find_zone(timezone).local(end_date.year, end_date.month, end_date.day, 23, 59, 59)
For example:
timezone = "Pacific Time (US & Canada)"
start_date = Date.parse("2021-11-30")
end_date = Date.parse("2021-11-30")
#params[:q][:start_date] = Time.find_zone(timezone).local(2021, 11, 30, 0, 0, 0) # => Tue, 30 Nov 2021 00:00:00.000000000 PST -08:00
#params[:q][:start_date].utc # 2021-11-30 08:00:00 UTC
#params[:q][:end_date] = Time.find_zone(timezone).local(2021, 11, 30, 23, 59, 59) # Tue, 30 Nov 2021 23:59:59.000000000 PST -08:00
#params[:q][:end_date].utc # 2021-12-01 07:59:59 UTC

Related

Set the right UTC DateTime considering clock changes

I have a ruby on rails app that creates event, from the frontend the date of the event is generated in Mountain Time and then the application transforms it in UTC.
The issue is that since the events are generated in the future sometimes we have issues with the clock change.
Let's say the event should happen:
Day X at 9:30 MT
It would be transformed in:
Day X at 14:30 UTC
But if we create an event in the future that fall in the week the clock change we would have an event configured at the wrong time, because it does not take into consideration the clock change.
Is there a way to generate a UTC dateTime from a specific TimeZone considering if the clock change would happen in that date?
According to Rails API
if your app has its time zone configured as Mountain Time(MT),
Daylight Saving Time(DST) works by default
# application.rb:
class Application < Rails::Application
config.time_zone = 'Mountain Time (US & Canada)'
end
Time.zone # => #<ActiveSupport::TimeZone:0x000000...>
Time.zone.name # => "Mountain Time (US & Canada)"
Time.zone.now # => Tue, 14 Dec 2021 09:46:09 MST -07:00
So if the event date falls after
the DST change, parsing (see ActiveSupport::TimeWithZone)
that date should return the appropiate time
time = Time.zone.parse('2022-03-13 01:00:00') # the parsed datetime is in the same timezone
=> Sun, 13 Mar 2022 01:00:00 MST -07:00
time.dst?
=> false
time = Time.zone.parse('2022-03-13 02:00:00')
=> Sun, 13 Mar 2022 03:00:00 MDT -06:00
time.dst?
=> true
You mention that the application transforms it to UTC. So if I assume,
the correct UTC date is passed to the backend(maybe as an ISO8601 encoded string), you should parse it and convert it to the app time zone by doing something like this:
date = params[:date]
# => "2021-12-14 18:05:05"
utc_datetime = DateTime.parse(date, "%Y-%m-%d %H:%M:%S")
=> 2021-12-14 18:05:05 +0000
mt_datetime = utc_datetime.in_time_zone
=> 2021-12-14 11:05:05 MST -07:00
...
end

Convert Time.zone.now to number OLE

I need convert the DateTime to OLE, Actually I have this code
def self.convert_time(t=42941.6102054745)
Time.at((t - 25569) * 86400).utc
end
but this converts to DateTime now I want this solution :
def self.date_time_ole(dt= Time.zone.now)
# her convert to number ole from datetime
end
could you please help me ?
Ruby's Date class subtraction returns number of days between 2 dates, so you just need to subtract your "custom epoch" (December 30, 1899 at midnight) from the other date:
ole_epoch = DateTime.new(1899, 12, 30, 0, 0, 0)
now = DateTime.now # => 2017-08-18 21:03:28 UTC
(now - ole_epoch).to_f # => 42965.87741847492
and that should be OLE and you can also just add it to go the other way:
ole_epoch + 42965.87741847492 # => Fri, 18 Aug 2017 21:03:28 +0000

Make Rails ignore daylight saving time when displaying a date

I have a date stored in UTC in my Rails app and am trying to display it to a user who has "Eastern Time (US & Canada)" as their timezone. The problem is that rails keeps converting it to Eastern Daylight Time (EDT) so midnight is being displayed as 8am when it should be 7am. Is there anyway to prevent the DST conversion?
>> time = DateTime.parse("2013-08-26T00:00:00Z")
=> Mon, 26 Aug 2013 00:00:00 +0000
>> time.in_time_zone("Eastern Time (US & Canada)")
=> Sun, 25 Aug 2013 20:00:00 EDT -04:00
Update
I eventually went with a twist on #zeantsoi 's approach. I'm not a huge fan of adding too many rails helpers so I extended active support's TimeWithZone class.
class ActiveSupport::TimeWithZone
def no_dst
if self.dst?
self - 1.hour
else
self
end
end
end
Now I can do time.in_time_zone("Eastern Time (US & Canada)").no_dst
Create a helper that utilizes the dst? method on TimeZone to check whether the passed timezone is currently in DST. If it is, then subtract an hour from the supplied DateTime instance:
# helper function
module TimeConversion
def no_dst(datetime, timezone)
Time.zone = timezone
if Time.zone.now.dst?
return datetime - 1.hour
end
return datetime
end
end
Then, render the adjusted (or non-adjusted) time in your view:
# in your view
<%= no_dst(DateTime.parse("2013-08-26T00:00:00Z"), 'Eastern Time (US & Canada)') %>
#=> Sun, 25 Aug 2013 19:00:00 EDT -04:00

Given a DateTime, how do I get the range of times with the same date in a particular time zone?

I have a DateTime object representing a particular date, as in "2011-01-15 00:00:00 UTC" to represent January 15th. I would like to produce the range of times in a particular time zone that have the same date.
The method signature would probably be something like
def day_range_for(date, tz)
# ...
end
For example, if I have range_for(DateTime.parse('2011-01-15'), 'CST'), then I want the result to be a range like 2011-01-15 00:00:00 -0600 .. 2011-01-15 23:59:59 -0600.
Can you take the input string instead of the object? If you pass in a DateTime object, its a tad counter intuitive because the time the object represents isn't the actual time you are looking for. It would conform to my expectations if you either passed in the correct, absolute DateTime, or the string itself. The actual meat of the problem is entirely handled by ActiveSupport, which I think you are using since you tagged this question with Rails. How does this look?
def range_for(input, tz=nil)
if tz.is_a?(String)
tz = ActiveSupport::TimeZone.new(tz)
else
tz = Time.zone
end
if input.acts_like?(:date) || input.acts_like?(:time)
d = input.in_time_zone(tz)
else
d = tz.parse(input)
end
return d.beginning_of_day..d.end_of_day
end
Have a look:
ruby-1.9.2-p0 > range_for('2011-01-15', 'Alaska')
=> Sat, 15 Jan 2011 00:00:00 AKST -09:00..Sat, 15 Jan 2011 23:59:59 AKST -09:00
ruby-1.9.2-p0 > range_for(Time.zone.now)
=> Mon, 10 Jan 2011 00:00:00 EST -05:00..Mon, 10 Jan 2011 23:59:59 EST -05:00
ruby-1.9.2-p0 > range_for('2011-01-15', 'EST')
=> Sat, 15 Jan 2011 00:00:00 EST -05:00..Sat, 15 Jan 2011 23:59:59 EST -05:00
How about:
def day_range_for date, zone
(DateTime.new(date.year,date.month,date.day,0,0,0,zone)..DateTime.new(date.year,date.month,date.day,23,59,59,zone))
end
day_range_for(DateTime.parse('2011-01-15'), 'CST')
#=> #<DateTime: 2011-01-15T00:00:00-06:00 (9822307/4,-1/4,2299161)>..#<DateTime: 2011-01-15T23:59:59-06:00 (212161917599/86400,-1/4,2299161)>

Rails ActiveSupport Time Parsing?

Rails' ActiveSupport module extends the builtin ruby Time class with a number of methods.
Notably, there is the to_formatted_s method, which lets you write Time.now.to_formatted_s(:db) to get a string in Database format, rather than having to write ugly strftime format-strings everywhere.
My question is, is there a way to go backwards?
Something like Time.parse_formatted_s(:db) which would parse a string in Database format, returning a new Time object. This seems like something that rails should be providing, but if it is, I can't find it.
Am I just not able to find it, or do I need to write it myself?
Thanks
It looks like ActiveSupport does provide the parsing methods you are looking for (and I was looking for too), after all! — at least if the string you are trying to parse is a standard, ISO-8601-formatted (:db format) date.
If the date you're trying to parse is already in your local time zone, it's really easy!
> Time.zone.parse('2009-09-24 08:28:43')
=> Thu, 24 Sep 2009 08:28:43 PDT -07:00
> Time.zone.parse('2009-09-24 08:28:43').class
=> ActiveSupport::TimeWithZone
and that time-zone-aware time can then easily be converted to UTC
> Time.zone.parse('2009-09-24 08:28:43').utc
=> 2009-09-24 15:28:43 UTC
or to other time zones:
> ActiveSupport::TimeZone.us_zones.map(&:name)
=> ["Hawaii", "Alaska", "Pacific Time (US & Canada)", "Arizona", "Mountain Time (US & Canada)", "Central Time (US & Canada)", "Eastern Time (US & Canada)", "Indiana (East)"]
> Time.zone.parse('2009-09-24 08:28:43').utc.in_time_zone('Eastern Time (US & Canada)')
=> Thu, 24 Sep 2009 11:28:43 EDT -04:00
If the date string you're trying to parse is in UTC, on the other hand, it doesn't look like there's any method to parse it directly into a TimeWithZone, but I was able to work around that be first using DateTime.strptime...
If the date you're trying to parse is in UTC and you want it to stay as UTC, you can use:
> DateTime.strptime('2009-09-24 08:28:43', '%Y-%m-%d %H:%M:%S').to_time
=> 2009-09-24 08:28:43 UTC
If the date you're trying to parse is in UTC and you want it converted to your default time zone, you can use:
> DateTime.strptime('2009-09-24 08:28:43', '%Y-%m-%d %H:%M:%S').to_time.in_time_zone
=> Thu, 24 Sep 2009 01:28:43 PDT -07:00
It looks like it can even parse other formats, such as the strange format that Time#to_s produces:
irb -> Time.zone.parse('Wed, 23 Sep 2009 02:18:08').to_s(:db)
=> "2009-09-23 09:18:08"
irb -> Time.zone.parse('Wed, 23 Sep 2009 02:18:08 EDT').to_s(:db)
=> "2009-09-23 06:18:08"
I'm quite impressed.
Here are some more examples from [http://api.rubyonrails.org/classes/ActiveSupport/TimeWithZone.html][1]:
Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
Time.zone.local(2007, 2, 10, 15, 30, 45) # => Sat, 10 Feb 2007 15:30:45 EST -05:00
Time.zone.parse('2007-02-10 15:30:45') # => Sat, 10 Feb 2007 15:30:45 EST -05:00
Time.zone.at(1170361845) # => Sat, 10 Feb 2007 15:30:45 EST -05:00
Time.zone.now # => Sun, 18 May 2008 13:07:55 EDT -04:00
Time.utc(2007, 2, 10, 20, 30, 45).in_time_zone # => Sat, 10 Feb 2007 15:30:45 EST -05:00
More documentation links for reference:
http://api.rubyonrails.org/classes/ActiveSupport/TimeWithZone.html
http://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html
ActiveSupport::TimeZone.new('UTC').parse('2009-09-23 09:18:08')
=> Wed, 23 Sep 2009 09:18:08 UTC +00:00
Rails 5 finally provides strptime!
value = '1999-12-31 14:00:00'
format = '%Y-%m-%d %H:%M:%S'
Time.zone.strptime(value, format)
# => Fri, 31 Dec 1999 14:00:00 HST -10:00
ActiveSupport::TimeZone.all.sample.strptime(value, format)
# => Fri, 31 Dec 1999 14:00:00 GST +04:00
I just ran into this as well and none of the above answers were satisfactory to me. Ideally one could use ActiveSupport::TimeZone just like Time and call .strptime on it with any arbitrary format and get back the correct TimeZone object. ActiveSupport::TimeZone.strptime doesn't exist so I created this monkeypatch:
class ActiveSupport::TimeZone
def strptime(str, fmt, now = self.now)
date_parts = Date._strptime(str, fmt)
return if date_parts.blank?
time = Time.strptime(str, fmt, now) rescue DateTime.strptime(str, fmt, now)
if date_parts[:offset].nil?
ActiveSupport::TimeWithZone.new(nil, self, time)
else
time.in_time_zone(self)
end
end
end
>> "2009-09-24".to_date
=> Thu, 24 Sep 2009
>> "9/24/2009".to_date
=> Thu, 24 Sep 2009
Works great unless your date is in some weird format.

Resources