Ruby: comparing dates of two Time objects - ruby

What is the best way to compare the dates of two Time objects in Ruby?
I have two objects such as:
time_1 = Time.new(2012,12,10,10,10)
time_2 = Time.new(2012,12,11,10,10)
In this example, the date comparison should return false.
Otherwise, same date, but different times, should return true:
time_1 = Time.new(2012,12,10,10,10)
time_2 = Time.new(2012,12,10,11,10)
I have tried to use .to_date that works for DateTime objects, but it is not supported by Time.

Just require the 'date' part of stdlib, then compare the dates:
require "date"
time1.to_date == time2.to_date
Job done.

I have verified that this works for me:
time_1.strftime("%F") == time_2.strftime("%F")
The %F format returns the date portion only.

Maybe just testing this way:
time_1.year == time_2.year && time_1.yday == time_2.yday
It'll be less resource consuming than string comparison.
monkey patch the class Time with this method and it i'll be nice to read
class Time
def date_compare(time)
year == time.year && yday == time_2.yday
end
end
time_1.date_compare time_2

to_date works just fine in ruby 2.0 and ruby 1.9.3 and ruby 1.9.2 http://ruby-doc.org/stdlib-1.9.2/libdoc/date/rdoc/Time.html
>> time_1.to_date
=> #<Date: 2012-12-10 ((2456272j,0s,0n),+0s,2299161j)>
but it's not in the stdlib of ruby 1.8.7 http://ruby-doc.org/stdlib-1.8.7/libdoc/time/rdoc/Time.html - but then, your way of creating a time object doesn't work in that version either:
> time_1 = Time.new(2012,12,10,10,10)
ArgumentError: wrong number of arguments (5 for 0)

Related

Ruby Time.parse incorrectly returns timestamp for String

I am facing a weird bug (?) in Ruby
Time.parse("David").to_i returns "no time information in "David"`
Time.parse("David1").to_i returns "no time information in "David1"`
However
Time.parse("David10").to_i returns 1570654800
It seems that any string with more than 2 numbers at the end manages to pass the Time conversion in Ruby. Is this a bug?
I am trying to create a single method than can handle conversion of strings to Timestamps where relevant or simply back to strings if conversion is not possible but for instances where my string includes 2+ numbers, it fails
if value.is_a? String
# if it's string of a date format
begin
Time.parse(value).to_i
rescue StandardError => e
value.downcase
end
# it's another object type - probably DateTime, Time or Date
else
value.nil? ? 0 : value.to_f
end
Internally time.rb uses, following,
def parse(date, now=self.now)
comp = !block_given?
d = Date._parse(date, comp)
year = d[:year]
year = yield(year) if year && !comp
make_time(date, year, d[:mon], d[:mday], d[:hour], d[:min], d[:sec], d[:sec_fraction], d[:zone], now)
end
It used to parse day, month later year by precision, Range of digits when exeed to 3, it consider it as year

Getting accurate Julian date number for current time in Ruby

Date.today.jd returns a rounded number. Is there a way to get more precision in Ruby?
I want to return a Julian date for the current time in UTC.
The Date#amjd method does what you're asking for, but it returns a Rational; converting to a Float gives you something easier to work with:
require 'date'
DateTime.now.amjd.to_f # => 56759.82092321331
require "date"
p jdate = DateTime.now.julian #=> #<DateTime: 2014-03-30T21:28:30+02:00 (...)
p jdate.julian? # => true

Ruby nil converted to_date

I am writing a little updater for a app that will update the last time someone logs in to the app and then saves it. its in rails 3.2 and ruby 1.9.3p327
def update_last_seen
if current_account.present?
if (Date.current - 1.day) > current_account.last_login_at
current_account.last_login_at = Date.current
current_account.save
end
end
end
I stuck that into the application controller and call it with a before filter. The only thing is that sometimes i have dates that are nil. so comparing date to nil gives errors. you cant call to_date on a nil.
nil.to_f => 0.0
nil.to_i => 0
nil.to_s => ""
nil.to_date => NoMethodError: undefined method `to_date' for nil:NilClass
"2013/07/26".to_date => Fri, 26 Jul 2013
how can i have it set it to be accepted as a blank date as it were.
i could always do
if current_account.last_login_at.blank? || (Date.current - 1.day) > current_account.last_login_at
that way it will set it if its not there but is there a semantic way of doing it?
UPDATE:
You might think this is has no point. the reason i ask is because there are some engines that have a nil for a date. for example excel will return dates two ways 1. as text as in "06/12/2013" or 2. an integer as the number of days from 01/01/1900. that date is excels nil date i was hoping that there was a default date for nils for Ruby. if there isn't you can just comment nope there isn't sorry man. giving a downvote without explanation as to why means that you really don't care about helping/teaching anything you're just there pushing buttons. if i did something wrong with this question you can tell me ill try fix it, if it doesnt make sence?
You could add in another method to clean up the code a little bit.
def new_login_since?(last_login)
last_login.blank? || (Date.current - 1.day) > last_login
end
def update_last_seen
if current_account.present? && new_login_since?(current_account.last_login_at)
current_account.update_attributes { last_login_at: Date.current }
end
end
To answer the actual question ... you can monkey-patch the NilClass like this
class NilClass
def to_date
Date.today
end
end
nil.to_date # => #<Date: 2013-09-26 ((2456562j,0s,0n),+0s,2299161j)>
Of course, the accepted answer shows the better approach.

DateTime serialization and deserialization

I'd like to serialize a Ruby DateTime object to json. Unfortunately, my approach is not symetrical:
require 'date'
date = DateTime.now
DateTime.parse(date.to_s) == date
=> false
I could use some arbitrary strftime/parse string combination, but I believe there must be a better approach.
The accepted answer is not a good solution, unfortunately. As always, marshal/unmarshal is a tool you should only use as a last resort, but in this case it will probably break your app.
OP specifically mentioned serializing a date to JSON. Per RFC 7159:
JSON text SHALL be encoded in UTF-8, UTF-16, or UTF-32. The default encoding is UTF-8, and JSON texts that are encoded in UTF-8 are interoperable in the sense that they will be read successfully by the maximum number of implementations; there are many implementations that cannot successfully read texts in other encodings (such as UTF-16 and UTF-32).
Now let's look at what we get from Marshal:
marsh = Marshal.dump(DateTime.now)
# => "\x04\bU:\rDateTime[\vi\x00i\x03\xE0\x7F%i\x02s\xC9i\x04\xF8z\xF1\"i\xFE\xB0\xB9f\f2299161"
puts marsh.encoding
# -> #<Encoding:ASCII-8BIT>
marsh.encode(Encoding::UTF_8)
# -> Encoding::UndefinedConversionError: "\xE0" from ASCII-8BIT to UTF-8
In addition to returning a value that isn't human-readable, Marshal.dump gives us a value that can't be converted to UTF-8. That means the only way to put it into (valid) JSON is to encode it somehow, e.g. base-64.
There's no need to do that. There's already a very interoperable way to represent dates and times: ISO 8601. I won't go over why it's the best choice for JSON (and in general), but the answers here cover it well: What is the "right" JSON date format?.
Since Ruby 1.9.3 the DateTime class has had iso8601 class and instance methods to parse and format ISO 8601 dates, respectively. The latter takes an argument to specify precision for fractional seconds (e.g. 3 for milliseconds):
require "date"
date = DateTime.now
str = date.iso8601(9)
puts str
# -> 2016-06-28T09:35:58.311527000-05:00
DateTime.iso8601(str) == date
# => true
Note that if you specify a smaller precision, this might not work, because e.g. 58.311 is not equal to 58.311527. A precision of 9 (nanosecond) seems safe to me, since the DateTime docs say:
The fractional number’s precision is assumed at most nanosecond.
However, if you're interoperating with systems that might use greater precision, you should take that into consideration.
Finally, if you want to make Ruby's JSON library automatically use iso8601 for serialization, override the as_json and to_json methods:
unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
require 'json'
end
require 'date'
class DateTime
def as_json(*)
iso8601(9)
end
def to_json(*args)
as_json.to_json(*args)
end
end
puts DateTime.now.to_json
# -> "2016-06-28T09:35:58.311527000-05:00"
Both the to_s method and the to_json method (provided require 'json') ignore the nanoseconds which are stored by the DateTime object date. Good old Marshal delivers:
require 'date'
date = DateTime.now
m_date = Marshal.dump(date)
p Marshal.load(m_date) == date # => true
It is because date has sub second value, and #to_s method will return ISO time format in seconds, the comparison don't succeed.
1.9.3p327 :021 > date = DateTime.now
=> #<DateTime: 2012-11-28T07:32:40+09:00 ((2456259j,81160s,283019000n),+32400s,2299161j)>
1.9.3p327 :022 > DateTime.parse(date.to_s)
=> #<DateTime: 2012-11-28T07:32:40+09:00 ((2456259j,81160s,0n),+32400s,2299161j)>
so they're actually different.
If you don't care about sub-seconds, just forget whether comparison succeed or not.
Or, you can use DateTime#marshal_load and DateTime#marshal_dump for 1.9.3.
(I didn't know this till now.. )
It work as:
date1 = DateTime.now
dump = date1.marshal_dump
date2 = DateTime.new.marshal_load(dump)
date1 == date2 # => true

Time-of-day range in Ruby?

I want to know if a time belongs to an schedule or another.
In my case is for calculate if the time is in night schedule or normal schedule.
I have arrived to this solution:
NIGHT = ["21:00", "06:00"]
def night?( date )
date_str = date.strftime( "%H:%M" )
date_str > NIGHT[0] || date_str < NIGHT[1]
end
But I think is not very elegant and also only works for this concrete case and not every time range.
(I've found several similar question is SO but all of them make reference to Date ranges no Time ranges)
Updated
Solution has to work for random time ranges not only for this concrete one. Let's say:
"05:00"-"10:00"
"23:00"-"01:00"
"01:00"-"01:10"
This is actually more or less how I would do it, except maybe a bit more concise:
def night?( date )
!("06:00"..."21:00").include?(date.strftime("%H:%M"))
end
or, if your schedule boundaries can remain on the hour:
def night?(date)
!((6...21).include? date.hour)
end
Note the ... - that means, basically, "day time is hour 6 to hour 21 but not including hour 21".
edit: here is a generic (and sadly much less pithy) solution:
class TimeRange
private
def coerce(time)
time.is_a? String and return time
return time.strftime("%H:%M")
end
public
def initialize(start,finish)
#start = coerce(start)
#finish = coerce(finish)
end
def include?(time)
time = coerce(time)
#start < #finish and return (#start..#finish).include?(time)
return !(#finish..#start).include?(time)
end
end
You can use it almost like a normal Range:
irb(main):013:0> TimeRange.new("02:00","01:00").include?(Time.mktime(2010,04,01,02,30))
=> true
irb(main):014:0> TimeRange.new("02:00","01:00").include?(Time.mktime(2010,04,01,01,30))
=> false
irb(main):015:0> TimeRange.new("01:00","02:00").include?(Time.mktime(2010,04,01,01,30))
=> true
irb(main):016:0> TimeRange.new("01:00","02:00").include?(Time.mktime(2010,04,01,02,30))
=> false
Note, the above class is ignorant about time zones.
In Rails 3.2 it has added Time.all_day and similars as a way of generating date ranges. I think you must see how it works. It may be useful.

Resources