Comparing Ruby/Rails TimeWithZone Objects - ruby-on-rails

Apologies if this has already been answered, but I've looked over a few similar questions and haven't quite seen this issue addressed. I'm trying to compare two TimeWithZone objects one from a created_at from my model and the other is a time.ago object
The created_at datetime object:
entry.created_at
=> Mon, 27 Jul 2015 08:47:32 UTC +00:00
The time.ago object:
1.minute.ago
=> Mon, 27 Jul 2015 10:23:29 UTC +00:00
Comparing them returns incorrectly:
entry.created_at > 1.minute.ago
=> false
or
entry.created_at < 1.minute.ago
=> true
It seems like it's actually comparing these as strings, if that's true how could I convert them to a comparable format?

Mon, 27 Jul 2015 08:47:32 UTC +00:00 is earlier (therefore less) then Mon, 27 Jul 2015 10:23:29 UTC +00:00.
It compares it right.

Related

How can I change the offset of a timestamp and then store the change in postgresql?

I have a form on my rails app that allows you to create a campaign object where the user can set a start_date, end_date, and timezone for which the dates will officially start and end.
What I would like to do is to apply the offset of the selected timezone to both the start_date and end_date. I've tried a few combinations but I can't seem to make it work. For starters, when I select my start and end dates for 11:00 PM and select Central Time Zone CDT with offset of -0500, the end result in postgresql is the following timestamp.
# No offset is applied
Wed, 08 Jun 2022 23:00:00.000000000 UTC +00:00
In order to try to apply the offset, I've tried a few combinations in my rails code. Here are some examples in a before_validation callback. It's pseudocode but this is the gist of it.
Example #1
date_tmp = send("start_at") # Wed, 08 Jun 2022 23:00:00.000000000 UTC +00:00
date_string = date_tmp.to_datetime.change(offset: '-0500').to_s
assign_attributes("start_at" => date_string) # "2022-06-08T22:00:00-05:00"
The result is no change for example 1.
Example #2
date_tmp = send("start_at") # Wed, 08 Jun 2022 23:00:00.000000000 UTC +00:00
date_string = date_tmp.to_datetime.change(offset: '-0500').to_s
dt = Chronic.parse(date_string.to_time.to_s)
assign_attributes("start_at" => dt) # "2022-06-08 22:00:00 -0500"
None of these variations work. I've also tried to save to postgresql timestamps that look like this:
2022-06-08T22:00:00-05:00
2022-06-08 22:00:00 -0500
Wed, 08 Jun 2022 22:00:00 -0500
I don't understand why rails and postgresql can't save the timestamp with the adjusted utc_offset that I want. What can I be doing wrong?
Try new_offset method on your DateTime instance.
It duplicates datetime object and resets its offset.
Some examples:
date = DateTime.now
date # => Thu, 16 Jun 2022 20:14:58 +0000
date.new_offset('+03:00') # => Thu, 16 Jun 2022 23:14:58 +0300
date.new_offset('-03:00') # => Thu, 16 Jun 2022 17:14:58 -0300

strftime(%Z) returns wrong result

I have this date
date = Mon, 15 Aug 2016 13:00:00 UTC +00:00
which is ActiveSupport::TimeWithZone class
Then, I need to get the time in time zone "Fiji"
start_in_time_zone = date.in_time_zone("Fiji")
This returns Tue, 16 Aug 2016 01:00:00 +12 +12:00
Then, I need to present the date with the name of the time zone, so
time_zone_abbr = start_in_time_zone.strftime("%Z")
It should return "FJT"
but returns "+12"
Any idea why?
I am using ruby 2.3.7 and rails 4.2.7
UPDATE
If I do
start_in_time_zone = date.in_time_zone("Madrid")
it returns
"CEST"
UPDATE 2
I have tried to see where the problem is by setting different time.
date=Time.utc(2018, 07, 25, 20, 30, 45)
date.class #=> Time
date.in_time_zone("Madrid") #=> Wed, 25 Jul 2018 22:30:45 CEST +02:00
date.in_time_zone("Fiji") #=> Thu, 26 Jul 2018 08:30:45 +12 +12:00
date.in_time_zone("EST") #=> Wed, 25 Jul 2018 15:30:45 EST -05:00
Sadly, it seems there is no 'FJT' abbreviation assigned to 'Fiji' in timezone data used by Rails. Also, support for those abbreviations seems patchy regarding Pacific timezones.
irb(main):002:0> DateTime.now.in_time_zone('Samoa').strftime('%Z')
=> "+13"
irb(main):003:0> DateTime.now.in_time_zone('Midway Island').strftime('%Z')
=> "SST"
irb(main):004:0> DateTime.now.in_time_zone('Samoa').strftime('%Z')
=> "+13"
irb(main):005:0> DateTime.now.in_time_zone('Tokelau Is.').strftime('%Z')
=> "+13"
irb(main):006:0> DateTime.now.in_time_zone('Wellington').strftime('%Z')
=> "NZST"
UTC offset is displayed as fallback. If it's any help, remember that full name and additional information can be retrieved with .time_zone.tzinfo on ActiveSupport::TimeWithZone objects. 'FJ' code is recognized by TZInfo::Country.
irb(main):056:0> TZInfo::Country.get('FJ')
=> #<TZInfo::Country: FJ>
irb(main):057:0> TZInfo::Country.get('FJ').zone_info
=> [#<TZInfo::CountryTimezone: Pacific/Fiji>]

Ordering Rails Array of Json's by the key "updated_at" 's value

I am trying to order an array of jsons by a value.
I cannot just query the database with .order('created_at DESC') because my array is filled with information from 2 different tables.
Here is the code that I have so far:
#user_items = UserItem.where(user_id: #friends.to_a).order('created_at DESC')
#user_pics = Pic.where(user_id: #friends.to_a).order('created_at DESC')
#all_things = #user_pics + #user_items
#all_things.sort_by{"updated_at"}
This does not error but when I run the code the first things in the list are always all the pictures followed by all the items. So, it seems the list line of code is doing nothing.
Here is a sample of what user_pics[0] looks like:
{"id"=>15, "user_id"=>2, "name"=>"New titles", "creator"=>nil, "size"=>nil, "created_at"=>Sun, 14 Feb 2016 02:41:12 UTC +00:00, "updated_at"=>Sun, 14 Feb 2016 02:41:12 UTC +00:00 ...}
EDIT 1
Sadly, the suggested answer does not seem to be working. Below are a few different outputs. Firstly, a pic that was updated Feb 14, then items that were updated Feb 24 and Feb 13 respectively. It seems the array is still ordered with all pictures first then all items.
#all_things.sort_by { |a| a[:updated_at] } [0]
=> {"id"=>15, "user_id"=>2, "name"=>"New titles", "creator"=>nil, "size"=>nil, "mark_count"=>nil, "created_at"=>Sun, 14 Feb 2016 02:41:12 UTC +00:00, "updated_at"=>Sun, 14 Feb 2016 02:41:12 UTC +00:00 ... }
#all_things.sort_by { |a| a[:updated_at] } [4]
=> {"id"=>846, "item_id"=>669, "user_id"=>2, "created_at"=>Wed, 24 Feb 2016 16:43:54 UTC +00:00, "updated_at"=>Wed, 24 Feb 2016 16:43:54 UTC +00:00, ...}
#all_things.sort_by { |a| a[:updated_at] } [50]
=> {"id"=>797, "item_id"=>623, "user_id"=>2, "created_at"=>Sat, 13 Feb 2016 03:58:29 UTC +00:00, "updated_at"=>Sat, 13 Feb 2016 03:58:29 UTC +00:00, ...}
You are using the sort in the worng way. Try with that:
#all_things.sort_by { |a| a[:updated_at] }
I think you're just calling #sort_by incorrectly:
#all_things.sort_by { |thing| thing["updated_at"] }
Since you're sorting by datetime strings, you can parse them into DateTime objects:
#all_things.sort_by { |thing| DateTime.httpdate(thing["updated_at"]) }
I'd like to suggest as well, that this might be a design problem. For this specific problem of union'ing two classes, you could consider single-table inheritance.

How do get a random DateTime rounded to beginning of hour in Rails?

Basically I'd like to get a random datetime within the last year:
rand(1.year).ago #=> Sun, 22 Sep 2013 18:37:44 UTC +00:00 (example)
But how do I go about specifying or limiting this to times on the hour? For example:
Sun, 22 Sep 2013 18:00:00 UTC +00:00
Sat, 02 Nov 2013 10:00:00 UTC +00:00
Fri, 12 Apr 2013 21:00:00 UTC +00:00
I finally found what I was looking for. #Stoic's answer is very good but I found this available method (http://api.rubyonrails.org/classes/DateTime.html):
rand(1.year).ago.beginning_of_hour
Does exactly the same thing but looks neater and prevents you from having to write your own function.
Rounding datetime to the nearest hour in Rails would be
(DateTime.now + 30.minutes).beginning_of_hour
Not the answer to the actual question, but it does answer the title of the question (which is how i got here).
Try this:
def random_time_to_nearest_hour
time = rand(1.year).ago
time - time.sec - 60 * time.min
end
Examples:
[1] pry(main)> random_time_to_nearest_hour
=> Sun, 28 Apr 2013 16:00:00 UTC +00:00
[2] pry(main)> random_time_to_nearest_hour
=> Sat, 08 Jun 2013 15:00:00 UTC +00:00
[3] pry(main)> random_time_to_nearest_hour
=> Thu, 22 Aug 2013 23:00:00 UTC +00:00
[4] pry(main)> random_time_to_nearest_hour
=> Tue, 29 Jan 2013 14:00:00 UTC +00:00
[5] pry(main)> random_time_to_nearest_hour
=> Tue, 13 Aug 2013 06:00:00 UTC +00:00
[6] pry(main)> random_time_to_nearest_hour
=> Mon, 03 Jun 2013 08:00:00 UTC +00:00
[7] pry(main)>
Note that, this method will always floor down to the nearest hour, but since you are anyways generating a random time, it wont matter if this time is getting floor'ed down or getting round'ed. :)

active record query over datetime array

Given I have an array of datetimes:
array = [Sat, 30 Jul 2011 00:00:00 CEST +02:00, Sat, 30 Jul 2011 00:15:00 CEST +02:00, Sat, 30 Jul 2011 00:30:00 CEST +02:00, Sat, 30 Jul 2011 00:45:00 CEST +02:00
I want my model class method to return the datetimes that dont match (aren't scheduled)
Sat, 30 Jul 2011 00:00:00 CEST +02:00
#appointment.rb (with colum `date` as DateTime)
def self.booked(array)
where("date NOT IN (?)", array)
end
Thx for advise!
It's because your array is malformed:
array = [Sat, 30 Jul 2011 00:00:00 CEST +02:00, Sat, 30 Jul 2011 00:15:00 CEST +02:00, Sat, 30 Jul 2011 00:30:00 CEST +02:00, Sat, 30 Jul 2011 00:45:00 CEST +02:00
notice it's separating the days as individual elements. Or is this your doing? If not, you should probably convert them all to actual DateTime objects (using #parse probably) and then put them in the array.
Also, you might want to make that method into a scope:
scope :booked, lambda { |datetimes| where("date NOT IN (?)", datetimes) }
I'm guessing that date is actually a date in the database, not a datetime or timestamp. So, you need to convert your Ruby Datetimes to just Date instances:
def self.booked(array)
where("date NOT IN (?)", array.map(&:to_date))
end
If you don't convert them to Dates by hand, AR won't know that it is supposed to convert them to dates for the database; then, depending on the underlying database, you could get empty results or possibly errors.
Your code look fine to me, but you need to provide an array, like this:
where(['date NOT IN(?)', array])
Here is an example with a Webinar class with a date column
>> array = Webinar.all[0..2].map(&:date)
=> [Wed, 04 May 2011 02:16:00 PDT -07:00, Tue, 05 Apr 2011 06:00:00 PDT -07:00, Thu, 30 Jun 2011 07:30:00 PDT -07:00]
>> count_without_array = (Webinar.count - array.size)
>> Webinar.where(['date NOT IN(?)', array]).count == count_without_array
=> true
Keep in mind that date must be exact match, if there is 1 second difference its not going to work.

Resources