Getting accurate Julian date number for current time in Ruby - 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

Related

Build an inverted array of dates

I have given list of dates like this:
['2020-02-01', '2020-02-05', '2020-02-08']
I want the inverse of this range like this (the missing dates in that range):
['2020-02-02', '2020-02-03', '2020-02-04', '2020-02-06', '2020-02-07']
I am sure I can build some sort of loop that starts at the first date then iterates through to build that second array. Any chance there is a ruby method / trick to do this faster?
You can use Array#difference or Array#- on range of Date objects. For example, with Ruby 2.7.1:
require "date"
dates = ['2020-02-01', '2020-02-05', '2020-02-08']
# convert sorted strings to date objects
dates.sort.map! { Date.strptime _1, "%Y-%m-%d" }
# use first and last date to build an array of dates
date_range = (dates.first .. dates.last).to_a
# remove your known dates from the range - dates).map &:to_s
(date_range - dates).map &:to_s
#=> ["2020-02-02", "2020-02-03", "2020-02-04", "2020-02-06", "2020-02-07"]
For compactness, and assuming your date strings in dates are already sorted, you could also use a train wreck like this:
((dates[0]..dates[-1]).to_a - dates).map &:to_s
require 'date'
arr = ['2020-02-26', '2020-03-02', '2020-03-04']
first, last = arr.map { |s| Date.strptime(s, '%Y-%m-%d') }.minmax
#=> [#<Date: 2020-02-26 ((2458906j,0s,0n),+0s,2299161j)>,
# #<Date: 2020-03-04 ((2458913j,0s,0n),+0s,2299161j)>]
(first..last).map { |d| d.strftime('%Y-%m-%d') } - arr
#=> ["2020-02-27", "2020-02-28", "2020-02-29", "2020-03-01",
# "2020-03-03"]
See Date::strptime and Date#strftime
You can use Array#min, Array#max and Array#- to easily solve this:
dates = ['2020-02-01', '2020-02-05', '2020-02-08']
missing_dates = ((dates.min..dates.max).to_a) - dates
=> [‘2020-02-02’, ‘2020-02-03’, ‘2020-02-04’, ‘2020-02-06’, ‘2020-02-07’]

Convert a time to nanoseconds in Ruby

I have the following time format:
require 'time'
input = "2016-10-04_00.50.31.147"
format = "%Y-%m-%d_%H.%M.%S.%N"
time = Time.strptime(input, format)
How do I get the number of nanoseconds since the epoch?
This should give you the value:
time.to_f * (10 ** 9)
If you want an integer, apply to_i or whatever to it.
However, notice that your time is wrong, so it would not give the right results.
Try this https://apidock.com/ruby/Time/nsec
t = Time.now #=> 2007-11-17 15:18:03 +0900
"%10.9f" % t.to_f #=> "1195280283.536151409"
t.nsec #=> 536151406

Ruby: comparing dates of two Time objects

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)

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

How to get the current time as 13-digit integer in Ruby?

I have this jQuery function that returns the current time as the number of milliseconds since the epoch (Jan 1, 1970):
time = new Date().getTime();
Is there a way to do the same in Ruby?
Right now, I am using Ruby's Time.now.to_i which works great but returns a 10-digit integer (number of seconds)
How can I get it to display the number of milliseconds, as in jQuery?
require 'date'
p DateTime.now.strftime('%s') # "1384526946" (seconds)
p DateTime.now.strftime('%Q') # "1384526946523" (milliseconds)
Javascript's gettime() returns the number of milliseconds since epoch.
Ruby's Time.now.to_i will give you the number of seconds since epoch. If you change that to Time.now.to_f, you still get seconds but with a fractional component. Just multiply that by 1,000 and you have milliseconds. Then use #to_i to convert it to an integer. And you end up with:
(Time.now.to_f * 1000).to_i
(Time.now.to_f * 1000).to_i should do the same thing.
Using strftime, you can get the number of seconds and append fractional milliseconds (or smaller units, if needed):
2.2.2 :001 > t = Time.new
=> 2015-06-02 12:16:56 -0700
2.2.2 :002 > t.strftime('%s%3N')
=> "1433272616888"
Note though that this doesn't round, it truncates, as you can see with to_f or if you go out to microseconds:
2.2.2 :003 > t.to_f
=> 1433272616.888615
2.2.2 :004 > t.usec
=> 888615
and the to_f / to_i solution has the same problem (to_i doesn't round, it truncates):
2.2.2 :009 > (t.to_f * 1000).to_i
=> 1433272616888
so if you really care about millisecond accuracy, a better bet may be to_f with round:
2.2.2 :010 > (t.to_f * 1000).round
=> 1433272616889
That said, as noted in the docs, "IEEE 754 double is not accurate enough to represent the number of nanoseconds since the Epoch", so if you really really care, consider to_r instead of to_f --
2.2.2 :011 > (t.to_r * 1000).round
=> 1433272616889
-- although if you're only rounding to milliseconds you're probably fine.
Be careful, don't get confused. The fact that Ruby supports the idea of fractional seconds as a float doesn't actually make it a floating point number. I got into trouble with this when I was doing Wireshark timestamp time comparisons in Python... the time calculations in the pcap-ng just weren't working. It was only when I treated the two parts (integral seconds and integral nanoseconds) as both integers was I able to get proper numbers.
That's because floating point numbers have Accuracy problems. Indeed, a quick bit of Ruby will show you that to_f does not equal, say, nsec:
irb(main):019:0> t=Time.now
=> 2015-04-10 16:41:35 -0500
irb(main):020:0> puts "#{t.to_f}; #{t.nsec}"
1428702095.1435847; 143584844
Caveat Programmer. You may be safe to 3 significant digits, but the fact remains: Floating point numbers on computers are approximations. The nanosecond counters on modern computers are integers.
Get a Time object with Time.now, calling #to_i returns a Unix timestamp (seconds from epoch). #to_f gives fractional seconds which you can use to get milliseconds from epoch:
Time.now.to_f * 1000
Use Process.clock_gettime:
>> Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond)
=> 1644588106765
See https://ruby-doc.org/core-3.1.0/Process.html#method-c-clock_gettime
The typecast Integer(1e6*Time.now.to_f) returns a Bignum that can hold the milliseconds

Resources