comparing datetime and time produces a false result - ruby-on-rails

I am using Mongoid and Chronic gem. Chronic produces a Time object, and Mongoid Date object produces a DateTime object. So in Mongoid, when i want to get today, I do something like this:
Lead.last.send('lead date') # => {DateTime}2015-03-30T00:00:00-04:00
In Chronic, when I parse today, I get this:
Chronic.parse('today') # => {Time}2015-03-30 23:00:00 -0400
And I compare the two with ==, it produces false, even though they are the same date. I need the following query to give a result, when 'lead date' refers to today:
Lead.where("lead date" => Chronic.parse('today'))
What options do I have?

Does this code accurately replicate your issue?
require 'chronic'
require 'date'
text = "2015-03-30T00:00:00-04:00"
datetime = DateTime.parse(text)
time = Chronic.parse(text)
datetime == time
#=> false
Use the DateTime #to_time method, or the Time #to_datetime method:
datetime.to_time == time
#=> true
datetime == time.to_datetime
#=> true

Related

Ruby how to get 2018-08-25 from DateTime object?

I have a DateTime property on one of my model objects.
Example data: "2018-08-28T01:00:00.000+00:00"
I am creating a new JSON object based of the DateTime property, but I just want to put this part into it 2018-08-28
I also want to grab the hour part and put it into the JSON object also. For example 01
Currently what I have is this JSON passed over.
{"date":"2018-08-25T18:00:00.000+00:00"}
I want this
{"date":"2018-08-25", "hour":"01"}
Parse the string into the valid DateTime instance and then print it back in any format you need with DateTime#strftime:
require 'datetime'
DateTime.
iso8601("2018-08-28T01:00:00.000+00:00").
strftime('%Y-%m-%d')
#⇒ "2018-08-28"
To get both a date and an hour one might do:
date, hour =
DateTime.
iso8601("2018-08-28T01:00:00.000+00:00").
strftime('%Y-%m-%d,%H').
split(',')
#⇒ ["2018-08-28", "01"]
To get a desired hash:
%w|date hour|.
zip(
DateTime.
iso8601("2018-08-28T01:00:00.000+00:00").
strftime('%Y-%m-%d,%H').
split(',')
).
to_h
#⇒ {
# "date" => "2018-08-28",
# "hour" => "01"
# }

handling time distinct from datetime

The goal is to compare timestamp to a range of times.
The range of times were defined in the rails schema as t.time for postgresql database. However the data returned upon querying the console attributes a date to the record's field...
start_time: "2000-01-01 08:00:00"
end_time: "2000-01-01 17:59:59"
Now if I want to validate whether a record created_at: "2017-03-18 03:44:04" is in the time range, I am also comparing the date, which is throwing the query into empty-array-land.
What rails or ruby tools can be used in this case in a database-agnostic manner?
def within_time_range?(start_time, end_time, time_check)
t = time_check[11..-1]
t >= start_time[11..-1] && t <= end_time[11..-1]
end
start_time = "2000-01-01 08:00:00"
end_time = "2000-01-01 17:59:59"
within_time_range?(start_time, end_time, "2017-03-18 03:44:04")
#=> false
within_time_range?(start_time, end_time, "2017-03-18 09:05:01")
#=> true
within_time_range?(start_time, end_time, "2017-03-18 19:05:01")
#=> false
Note
start_time[11..-1]
#=> "08:00:00"
I've used String#<= and String#>=, which are obtained from String#<=> and the inclusion of the module Comparable into the String class.
You could force every time object to be parsed at the same date (e.g. 1st of january 2000) :
require 'time'
def parse_time_not_date(string)
time = Time.parse(string)
Time.local(2000, 1, 1, time.hour, time.min, time.sec, time.usec)
end
start_time = parse_time_not_date("2000-01-01 08:00:00")
end_time = parse_time_not_date("2000-01-01 17:59:59")
my_time = parse_time_not_date("2017-03-18 03:44:04")
puts (start_time..end_time).cover?(my_time)
# false
puts (start_time..end_time).cover?(parse_time_not_date("2017-03-18 14:59"))
# true
I am going to chime in, as the discussion from the first answer was pertinent. Cary's answer works for that case. The question was ambivalent in terms of possible solutions: ruby or rails. And hence some potential gum ups. What follows is another way.
With rails, there is an issue in considering time zones. Calling an object of data type time actually stays in UTC. With date_time you are getting a string with + or - hours(or fraction thereof - yay Newfoundland!)
So, processing with rails, the proper way to handle UTC data is to assign it the rails application time zone with in_time_zone, chain to string and then extracting. Thus, comparisons ended up as:
p = Time.parse(#interruptions[0].pause.to_s[11,8])
p >= Time.parse(d_s.start_time.in_time_zone.to_s[11,8]) && p <= Time.parse(d_s.end_time.in_time_zone.to_s[11,8])
Note: could not get [11..-1] working in this context

Ruby - Getting the next Daylight Savings change

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.

Thinking Sphinx with a date range

I am implementing a full text search API for my rails apps, and so far have been having great success with Thinking Sphinx.
I now want to implement a date range search, and keep getting the "bad value for range" error.
Here is a snippet of the controller code, and i'm a bit stuck on what to do next.
#search_options = { :page => params[:page], :per_page => params[:per_page]||50 }
unless params[:since].blank?
# make sure date is in specified format - YYYY-MM-DD
d = nil
begin
d = DateTime.strptime(params[:since], '%Y-%m-%d')
rescue
raise ArgumentError, "Value for since parameter is not a valid date - please use format YYYY-MM-DD"
end
#search_options.merge!(:with => {:post_date => d..Time.now.utc})
end
logger.info #search_options
#posts = Post.search(params[:q], #search_options)
When I have a look at the log, I am seeing this bit which seems to imply the date hasn't been converted into the same time format as the Time.now.utc.
withpost_date2010-05-25T00:00:00+00:00..Tue Jun 01 17:45:13 UTC 2010
Any ideas? Basically I am trying to have the API request pass in a "since" date to see all posts after a certain date. I am specifying that the date should be in the YYYY-MM-DD format.
Thanks for your help.
Chris
EDIT: I just changed the date parameters merge statement to this
#search_options.merge!(:with => {:post_date => d.to_date..DateTime.now})
and now I get this error
undefined method `to_i' for Tue, 25 May 2010:Date
So obviously there is something still not setup right...
lets say d = "2010-12-10"
:post_date => (d.to_time.to_i..Time.now.to_i) would have gotten you there. I just did this in my project and it works great
I finally solved this, but it takes a slightly different approach but it works fine.
I was trying to put the date-range search inside a sphinx_scope (in the model) or as a :condition or :with (in the controller). This did not work, so instead I had to implement it inside the define_index in the model.
So what I did was put a check in the define_index to see if a record fell within a date range, the date range being defined by some SQL code, as shown below. In this case, I wanted to see if "start_date" fell within a date between now and 30 days ago, and an "end_date" fell within today and 30 days from now.
If the dates fell within the ranges, the code below causes the :live to be 0 or 1, depending on whether it falls outside or inside the date ranges (respectively):
define index do
# fields:
...
# attributes:
has "CASE WHEN start_date > DATE_ADD(NOW(), INTERVAL -30 DAY) AND end_date < DATE_ADD(NOW(), INTERVAL 30 DAY) THEN 1 ELSE 0 END", :type => :integer, :as => :live
...
# delta:
...
end
Then in your controller, all you have to do is check if :live => 1 to obtain all records that have start_dates and end_dates within the date ranges.
I used a sphinx_scope like this:
sphinx_scope(:live) {
{ :with => { :live => 1 } }
}
and then in my controller:
#models = Model.live.search(...)
To make sure it works well, you of course need to implement frequent reindexing to make sure the index is up to date, i.e. the correct records are :live => 1 or 0!
Anyway, this is probably a bit late for you now, but I implemented it and it works like a charm!!!
Wouldn't it work if you replaced
d = DateTime.strptime(params[:since], '%Y-%m-%d')
by
Time.parse(params[:since]).strftime("%Y-%m-%d")
(It seems the first one doesn't return a date in the expected format)

XML Serialization is not including milliseconds in datetime field from Rails model

By default, the datetime field from the database is being converted and stripping off the milliseconds:
some_datetime => "2009-11-11T02:19:36Z"
attribute_before_type_cast('some_datetime') => "2009-11-11 02:19:36.145"
If I try to overrride the accessor for this attribute like;
def some_datetime
attribute_before_type_cast('some_datetime')
end
when I try "to_xml" for that model, I get the following error:
NoMethodError (undefined method
`xmlschema' for "2009-11-11
02:19:36.145":String):
I have tried to parse the String to a Time object but can't get one to include the milliseconds;
def some_datetime
Time.parse(attribute_before_type_cast('some_datetime').sub(/\s/,"T").sub(/$/,"Z"))
end
Can anyone help get get a datetime with milliseconds rendered by to_xml?
As it turns out, I can exclude the original datetime field, and add a custom method which in turn renders the datetime as a string to the to_xml. This feels hackish, but it's working.. Is there another way to get milliseconds directly in the original datetime field?
In each model, I exclude "except" the field names that have datetimes that I want changed, and I include "methods" with the same name returning the attribute before it is typecasted.
def to_xml(options = {})
options[:methods] = [:some_datetime]
options[:except] = [:some_datetime]
super
end
def some_datetime
attribute_before_type_cast('some_datetime')
end
Rendering to_xml is working great with models included and any other options I pass in.
I have started to learn Ruby and was impressed by Mats "Principle of Least Surprise".
But the Date and Time implementation in Ruby ( and Rails ) is full of surprises:
Starting with a plain irb:
require 'time'
=> true
dt = Time.now
=> 2010-05-31 17:18:39 +0100
Time.parse(dt.to_s) == dt
=> false !?!?!?!?
dt.to_s(:db)
ArgumentError: wrong number of arguments(1 for 0)
from (irb):5:in to_s'
from (irb):5
from C:/Ruby19/bin/irb:12:in'
ok lets take some Rails:
sqlserver_test/development?: dt2 = Time.zone.now
=> Mon, 31 May 2010 17:24:54 CEST +02:00
sqlserver_test/development: dt2.class
=> ActiveSupport::TimeWithZone
sqlserver_test/development: Time.zone.parse(dt2.to_s) == dt2
=> false
sqlserver_test/development: dt2.to_s(:db)
=> "2010-05-31 15:24:54"
sqlserver_test/development: dt2.to_s(:iso8601)
=> "2010-05-31 17:24:54 +0200"
sqlserver_test/development: dt2.to_s(:iso8601) == dt2.iso8601
=> false
( all running on Ruby 1.9.1 with Rails 2.3.5 on Windows Xp )
Currently I only find several "hacks" regarding DateTime fields and databases
but no clean solution WITHOUT surprises ...

Resources