How to convert BSON::Timestamp to ruby time and vice versa - ruby

I have a ruby hash
input = {"dateCreated"=>#<BSON::Timestamp:0x00007fe0b482ca98 #increment=3745289216, #seconds=229>}
how do we extract time from BSON timestamp class.
tried input["dateCreated"].as_json
=> {"$timestamp"=>{"t"=>244, "i"=>664779776}}
Not sure how to proceed.
Also would appreciate if there is any pointer on how to filter based on timestamp value to MongoDB using ruby driver
.find() method

You can convert a BSON::Timestamp to a BSON::ByteBuffer using the #to_bson method.
You can then convert the BSON::ByteBuffer to an integer (#get_int64) that represents the number of milliseconds since the epoch.
Then use Time::at to convert that integer to a Time object
date_time = DateTime.new(2021,8,30)
date_time.to_time
#=> 2021-08-30 00:00:00 +0000
date_time.to_time.to_i
#=> 1630281600
timestamp = BSON::Timestamp.from_bson(date_time.to_bson)
#=> #<BSON::Timestamp:0x00007fffe31da4a8 #seconds=379, #increment=2488994816>
timestamp.to_bson.get_int64 / 1000
#=> 1630281600
Time.at(timestamp.to_bson.get_int64 / 1000).utc
#=> 2021-08-30 00:00:00 UTC

It seems that you hace an object of this type:
https://www.rubydoc.info/github/mongodb/bson-ruby/BSON/Timestamp
from this gem
and this object
[6] pry(main)> bt = BSON::Timestamp.new(229, 3745289216)
=> #<BSON::Timestamp:0x00007fe338a79680 #increment=3745289216, #seconds=229>
[7] pry(main)> # you allready have when begin the object in second 229
[8] pry(main)> # you allready have when finish the even in secons in atribute increment 3745289216
may be this value are chage because time begins in 1970 son for translating settings and using a handy gem for manipulating times used in rails:
[10] pry(main)> DateTime.strptime(bt.seconds.to_s,'%s')
=> #<DateTime: 1970-01-01T00:03:49+00:00 ((2440588j,229s,0n),+0s,2299161j)>
[13] pry(main)> require 'active_support/core_ext/numeric/time.rb'
=> true
[14] pry(main)> DateTime.strptime(bt.seconds.to_s,'%s') + bt.increment.seconds
=> Mon, 06 Sep 2088 06:10:45 +0000

Related

What is the difference between Time.local and Time.new

Time.local vs Time.new
Both method return Time object.
irb(main):005:0> Time.new(2020,1,1).class
=> Time
irb(main):006:0> Time.local(2020, 1, 1).class
=> Time
irb(main):007:0> Time.local(2020, 1, 1)
=> 2020-01-01 00:00:00 +0900
irb(main):008:0> Time.new(2020,1,1)
=> 2020-01-01 00:00:00 +0900
irb(main):009:0>
I don't know when should I use each method.
Time.local vs Time.new
Time.local will give error if no argument is given, whereas Time.new is initialized to the current system time if no argument is given.
Time.local will always return values in the local time zone. Time.new accepts the timezone parameter & if given returns the value in in the respective time zone.
Check this
2.5.1 :018 > Time.new.zone
=> "IST"
2.5.1 :019 > Time.new
=> 2020-11-18 19:50:30 +0530
2.5.1 :020 > Time.local
Traceback (most recent call last):
3: from /Users/salilgaikwad/.rvm/rubies/ruby-2.5.1/bin/irb:11:in `<main>'
2: from (irb):20
1: from (irb):20:in `local'
ArgumentError (wrong number of arguments (given 0, expected 1..8))
2.5.1 :021 > Time.new(2020,11,18,15,25,0, "+09:00")
=> 2020-11-18 15:25:00 +0900
2.5.1 :022 > Time.local(2020,11,18,15,25,0, "+09:00")
=> 2020-11-18 15:25:00 +0530
It looks like you must pass arguments to #local,r whereas you don't need to pass arguments to #new and it would return current time.

String to_i or to_f dynamically

I'm dynamically creating attr_readers and instance variables based on a response_header received from an API.
The response_header hash comes back like this:
{
"server"=>"nginx",
"x-ua-compatible"=>"IE=edge,chrome=1",
"expires"=>"Thu, 01 Jan 1970 00:00:00 GMT",
"cache-control"=>"no-cache, must-revalidate",
"pragma"=>"no-cache",
"rate-limit"=>"1000",
"rate-limit-remaining"=>"986",
"rate-limit-reset"=>"1265",
"content-type"=>"application/json;charset=UTF-8",
"content-language"=>"en",
"transfer-encoding"=>"chunked",
"vary"=>"Accept-Encoding",
"date"=>"Fri, 23 Jan 2015 15:38:56 GMT",
"connection"=>"close",
"x-frame-options"=>"SAMEORIGIN"
}
This get's sent into a class called ResponseHeader
class ResponseHeader
def initialize(options = {})
options.each do |key, value|
instance_variable_set("##{key}", value)
self.send(:attr_reader, key)
end
end
end
This is working pretty well except for the fact that rate-limit, rate-limit-remaining and rate-limit-reset are clearly integers that came back as strings from the API.
I'd like for my class to be intelligent here and change those values to integers but the only method I've come up with thus far feels very un-ruby.
Basically I'm converting to an integer and then back to a string and seeing if it equals the original value.
[8] pry(main)> "ABC".to_i
=> 0
[9] pry(main)> _.to_s
=> "0"
[10] pry(main)> _ == "ABC"
=> false
[11] pry(main)> "100".to_i
=> 100
[12] pry(main)> _.to_s
=> "100"
[13] pry(main)> _ == "100"
=> true
Is this my only option?
Ruby Integer parses a string to an integer and raises an error if the parse fails:
i = Integer("123") #=> 1234
i = Integer("abc") #=> ArgumentError: invalid value for Integer(): "abc"
Heads up that binary, octal, and hex are also parsed:
i = Integer("0b10") #=> 2
i = Integer("010") #=> 8
i = Integer("0x10") #=> 16
To parse base 10, provide the base:
i = Integer("010",10) #=> 10
To match within a string, use a regexp:
s = "foo123bar"
i = s=~/-?\d+/ ? Integer($&,10) : nil #=> 123
To patch the String class, define a new method:
class String
def match_integer
self=~/-?\d+/ ? Integer($&,10) : nil
end
end
"foo123bar".match_integer #=> 123

Invalid date ArgumentError and incorrect parse on a valid date?

dates = ["11/12/08 10:47", "11/12/08 13:23", "11/12/08 13:30",
"11/25/08 19:21", "2/2/09 11:29", "11/12/08 15:00"]
This throws an invalid argument error:
dates.each do |date|
d = Date.parse(date)
d.mon
end
#=> ArgumentError: invalid date
But take the first date in dates and this is the output:
d = Date.parse('11/12/08 10:47')
puts d.mon
#=> #<Date: 2011-12-08 ((2455904j,0s,0n),+0s,2299161j)>
#=> 12 but this should be 11
In the first example why am I getting an invalid ArgumentError?
In example 2, why is the Date object created with the mon and day swapped?
Given your input, Date.parse is parsing your dates assuming they are in the format YY/MM/DD, so when it try to parse 11/25/08 it fails because 25 is not a valid month:
d = Date.parse('11/12/08 10:47')
d.year
# => 2011
d.month
# => 12
d.day
# => 8
Date.parse('11/25/08 19:21')
# ArgumentError: invalid date
Given that your dates are all in the same format, you should use the Date.strptime method instead:
d = Date.strptime('11/12/08 10:47', '%m/%d/%y')
d.year
# => 2008
d.month
# => 11
d.day
# => 12
Date.strptime('11/25/08 19:21', '%m/%d/%y')
# => #<Date: 2008-11-25 ((2454796j,0s,0n),+0s,2299161j)>
Edit Instead of the format string %m/%d/%y the shortcut %D can be used:
Date.strptime('11/25/08 19:21', '%D')
# => #<Date: 2008-11-25 ((2454796j,0s,0n),+0s,2299161j)>
Ruby's Date.parse is expecting either a YYYY-MM-DD (see also ISO8601 for more information) or a DD-MM-YYYY as well but not DD-MM-YY (i.e. 2 digits only for year). The last is treated instead as YY-MM-DD.

I am trying to convert UTC datetime to a specific timezone in ruby

I have an XML file which has a datetime value. I loop through the elements and get the attribute value timeutc.
I managed to convert it to local time zone, but what I am trying to do is convert it to any time zone, PST, CST, EST.
$Appointments = '<appointments><appointment timeutc="2013-10-10T06:00:00" /><appointment timeutc="2013-10-10T06:00:00" /><appointment timeutc="2013-10-10T15:00:00" /></appointments>'
index = 0
doc = REXML::Document.new("#{$Appointments}")
doc.elements.each("appointments/appointment") do |element|
index += 1
value = element.attribute("timeutc").value
to_datetime = DateTime.parse(value).to_s
to_UTC = Time.iso8601(to_datetime).to_s
local_time = Time.iso8601(to_datetime).localtime
puts local_time # => 2013-10-09 23:00:00 -0700
puts local_time.strftime("%A") # => Wednesday
puts local_time.strftime("%B") # => October
puts local_time.strftime("%-d") # => 9
puts local_time.strftime("%l") # => 11
puts local_time.strftime("%M") # => 00
puts local_time.strftime("%p") # => PM
end
You could use the in_time_zone method:
For CST:
local_time.in_time_zone('Central Time (US & Canada)')
For PST:
local_time.in_time_zone('Pacific Time (US & Canada)')
For EST:
local_time.in_time_zone('Eastern Time (US & Canada)')
Ruby's support for time zones hasn't been that great (not sure if this has changed with 2.0), hence the various ActiveSupport libs.
Ruby looks to the environment variable TZ to determine what zone to operate in:
irb [1.9.3]$ ENV["TZ"] # Ruby will look elsewhere and see that I'm in NYC
=> nil
irb [1.9.3]$ Time.iso8601 "2013-10-10T06:00:00"
=> 2013-10-10 06:00:00 -0400
irb [1.9.3]$ ENV["TZ"]="America/Los_Angeles"
=> "America/Los_Angeles"
irb [1.9.3]$ Time.now.zone
=> "PDT"
irb [1.9.3]$ Time.iso8601 "2013-10-10T06:00:00"
=> 2013-10-10 06:00:00 -0700
So... while I'd strongly encourage using the active support methods, you could use something like this:
irb [1.9.3]$ def parse_in_zone(date, zone)
1{ old = ENV["TZ"]
1{ ENV["TZ"] = zone
1{ Time.iso8601 date
1{ ensure
1* ENV["TZ"] = old
1{ end
=> nil
irb [1.9.3]$ parse_in_zone "2013-10-10T06:00:00", "America/Los_Angeles"
=> 2013-10-10 06:00:00 -0700
irb [1.9.3]$ parse_in_zone "2013-10-10T06:00:00", "America/New_York"
=> 2013-10-10 06:00:00 -0400
Also note that DateTime.parse(value) is not necessary. It considers the string's zone when parsing the date:
irb [1.9.3]$ DateTime.parse("2013-10-10T06:00:00").zone
=> "+00:00"
irb [1.9.3]$ DateTime.parse("2013-10-10T06:00+04:00").zone
=> "+04:00"
irb [1.9.3]$ DateTime.parse("2013-10-10T06:00+07:00").zone
=> "+07:00"
Note that in your case I don't think the string: 2013-10-10T06:00:00 is a valid iso date/time (extra :00) but it looks like ruby doesn't care:
irb [1.9.3]$ DateTime.parse("2013-10-10T06:00:0000:000000").to_s
=> "2013-10-10T06:00:00+00:00"

Is there an add_days in ruby datetime?

In C#, There is a method AddDays([number of days]) in DateTime class.
Is there any kind of method like this in ruby?
The Date class provides a + operator that does just that.
>> d = Date.today
=> #<Date: 4910149/2,0,2299161>
>> d.to_s
=> "2009-08-31"
>> (d+3).to_s
=> "2009-09-03"
>>
In Rails there are very useful methods of Fixnum class for this (here n is Fixnum. For example: 1,2,3.... ):
Date.today + n.seconds # you can use 1.second
Date.today + n.minutes # you can use 1.minute
Date.today + n.hours # you can use 1.hour
Date.today + n.days # you can use 1.day
Date.today + n.weeks # you can use 1.week
Date.today + n.months # you can use 1.month
Date.today + n.years # you can use 1.year
These are convenient for Time class too.
PS: require Active Support Core Extensions to use these in Ruby
require 'active_support/core_ext'
I think next_day is more readable than + version.
require 'date'
DateTime.new(2016,5,17)
# => #<DateTime: 2016-05-17T00:00:00+00:00 ((2457526j,0s,0n),+0s,2299161j)>
DateTime.new(2016,5,17).next_day(10)
# => #<DateTime: 2016-05-27T00:00:00+00:00 ((2457536j,0s,0n),+0s,2299161j)>
Date.new(2016,5,17)
# => #<Date: 2016-05-17 ((2457526j,0s,0n),+0s,2299161j)>
Date.new(2016,5,17).next_day(10)
# => #<Date: 2016-05-27 ((2457536j,0s,0n),+0s,2299161j)>
http://ruby-doc.org/stdlib-2.3.1/libdoc/date/rdoc/Date.html#method-i-next_day.
From the Date class:
+(n)
Return a new Date object that is n days later than the current one.
n may be a negative value, in which case the new Date is earlier than the current one; however, #-() might be more intuitive.
If n is not a Numeric, a TypeError will be thrown. In particular, two Dates cannot be added to each other.
Date.new(2001,9,01).next_day(30) # 30 - numbers of day
# => #<Date: 2001-10-01 ...
You can also use the advance (https://apidock.com/rails/DateTime/advance) method. I think it's more legible.
date = Date.today
# => Fri, 25 Oct 2019
date.advance(days: 10)
# => Mon, 04 Nov 2019
time = DateTime.now
# => Fri, 25 Oct 2019 14:32:53 +0200
time.advance(months:1, weeks: 2, days: 2, hours: 6, minutes: 6, seconds: 34)
# => Wed, 11 Dec 2019 20:39:27 +0200

Resources