How to convert a long to date? - ruby-on-rails

I have a long value in Rails, 134740800, which is the number of milliseconds since the epoch.
How do I convert that to a date in mm-dd-yyyy format?
I figure the formatting would be done with something like strftime but I can't seem to find the right method to convert the long into a valid date.

secs = 134740800/1000 # millisecs / 1000
t = Time.at(secs)
t.strftime("%m-%d-%Y")
Output
"01-02-1970"

Try this:
require 'date'
DateTime.strptime("1318996912",'%s')

I assume you mean seconds since the epoch.
Time.at seconds_since_epoch
You can also pass a float. If you have milliseconds, divide by 1000.0 first.
You can then call strftime on the returned Time object.

Use Time.at:
irb(main):003:0> Time.at(134740800)
=> Tue Apr 09 08:00:00 -0400 1974

This is an advisory... It's often a good idea to look at how fast some answers run. Here's a simple benchmark:
require 'benchmark'
require 'date'
SECS = 134740800
LOOPS = 1_000_000
puts Time.at(SECS).strftime('%m-%d-%Y')
puts Date.strptime(SECS.to_s, '%s').strftime('%m-%d-%Y')
Benchmark.bm(14) do |x|
x.report('Time.at:') { LOOPS.times { Time.at(SECS) }}
x.report('Date.strptime:') { LOOPS.times { Date.strptime(SECS.to_s, '%s') }}
end
And the output is:
04-09-1974
04-09-1974
user system total real
Time.at: 0.370000 0.020000 0.390000 ( 0.392761)
Date.strptime: 6.320000 0.050000 6.370000 ( 6.373248)

Related

Rails - Convert time to string

This is the time format I want to convert
time = Time.parse('2020-07-02 03:59:59.999 UTC')
#=> 2020-07-02 03:59:59 UTC
I want to convert to string in this format.
"2020-07-02T03:59:59.999Z"
I have tried.
time.strftime("%Y-%m-%dT%H:%M:%S.999Z")
Is this correct? Any better way?
You can just use Time#iso8601 with the desired number of fraction digits as an argument:
time = Time.current.end_of_hour
time.iso8601(3) #=> "2020-07-01T10:59:59.999Z"
If you want to handle the output format explicitly via strftime, there are some things to keep in mind:
Instead of hard-coding 999, you should use %L to get the actual milliseconds:
time = Time.parse('2020-07-02 03:59:59.999 UTC')
#=> 2020-07-02 03:59:59 UTC
time.strftime('%Y-%m-%dT%H:%M:%S.%LZ')
#=> "2020-07-02T03:59:59.999Z"
Use combinations for common formats, e.g. %F for %Y-%m-%d and %T for %H:%M:%S:
time.strftime('%FT%T.%LZ')
#=> "2020-07-02T03:59:59.999Z"
If you are dealing with time zones other than UTC (maybe your machine's local time zone), make sure to convert your time instance to utc first:
time = Time.parse('2020-07-02 05:59:59.999+02:00')
#=> 2020-07-02 05:59:59 +0200
time.utc
#=> 2020-07-02 03:59:59 UTC
time.strftime('%FT%T.%LZ')
#=> "2020-07-02T03:59:59.999Z"
or to use %z / %:z to append the actual time zone offset:
time = Time.parse('2020-07-02 05:59:59.999+02:00')
time.strftime('%FT%T.%L%:z')
#=> "2020-07-02T05:59:59.999+02:00"
For APIs you should use utc.iso8601:
> timestamp = Time.now.utc.iso8601
=> "2015-07-04T21:53:23Z"
See:
https://thoughtbot.com/blog/its-about-time-zones#working-with-apis

Parsing JSON datetime?

I am consuming a JSON API that returns datetime values as a string in the following format:
/Date(1370651105153)/
How can I parse a value like this into a datetime variable in rails?
That appears to be a UNIX timestamp (seconds since epoch). Additionally it appears to be milliseconds since epoch.
So you can convert it like so - given that value is a String that looks like:
value = "/Date(1370651105153)/"
if value =~ /\/Date\((\d+)\)\//
timestamp = $1.to_i
time = Time.at(timestamp / 1000)
# time is now a Time object
end
You need to divide by 1000 since Time#at expects its argument to be seconds and not milliseconds since the epoch.
There are a few errors in andyisnowskynet's answer (https://stackoverflow.com/a/25817039/5633460): There's a minor RegEx error which means it won't detect positive timezones; and it won't support timezones that aren't aligned to full hours such as -0430 (VET). Also, the Time object it returns could be honouring proper Ruby timezone handling instead of adding a time offset, which would be the more-conventional way to do this.
This is my improved version of that answer...
First, for datetime values expressed in this format, the time serial part (e.g. 1370651105153 in OP's example) is ALWAYS the number of milliseconds since 1970-01-01 00:00:00 GMT, and that is not influenced by the presence or absence of a timezone suffix. Hence, any timezone included as part of the string does not change the point in history that this represents. It only serves to state which timezone the "observer" was in at the time.
Ruby Time objects are able to handle these two pieces of information (i.e. the actual datetime "value" and the timezone "metadata"). To demonstrate:
a = Time.at(-1).utc
# => 1969-12-31 23:59:59 UTC
b = Time.at(-1).getlocal('+09:30')
# => 1970-01-01 09:29:59 +0930
a == b
# => true
As you can see, even though both look like different values (owing to their different timezones in which they are expressed), the == equality operator shows they actually reference the same moment in time.
Knowing this, we could improve on parse_json_datetime as follows (correcting the other errors too):
def parse_json_datetime(datetime)
# "/Date(-62135575200000-0600)/" seems to be the default date returned
# if the value is null:
if datetime == "/Date(-62135575200000-0600)/"
# Return nil because it is probably a null date that is defaulting to 0.
# To be more technically correct you could just return 0 here if you wanted:
return nil
elsif datetime =~ %r{/Date\(([-+]?\d+)([-+]\d+)?\)/}
# We've now parsed the string into:
# - $1: Number of milliseconds since the 1/1/1970 epoch.
# - $2: [Optional] timezone offset.
# Divide $1 by 1000 because it is in milliseconds and Time uses seconds:
seconds_since_epoch = $1.to_i / 1000.0
time = Time.at(seconds_since_epoch.to_i).utc
# We now have the exact moment in history that this represents,
# stored as a UTC-based "Time" object.
if $2
# We have a timezone, so convert its format (adding a colon)...
timezone = $2.gsub(/(...)(..)/, '\1:\2')
# ...then apply it to the Time object:
time = time.getlocal(timezone)
end
time
else
raise "Unrecognized date format."
end
end
Then we can test:
# See: http://momentjs.com/docs/#/parsing/asp-net-json-date/
parse_json_datetime("/Date(1198908717056-0700)/")
# => 2007-12-28 23:11:57 -0700
# 1 minute before the epoch, but in ACST:
parse_json_datetime("/Date(-60000+0930)/")
# => 1970-01-01 09:29:00 +0930
# Same as above, but converted naturally to its UTC equivalent:
parse_json_datetime("/Date(-60000+0930)/").utc
# => 1969-12-31 23:59:00 UTC
# Same as above, with timezone unspecified (implying UTC):
parse_json_datetime("/Date(-60000)/")
# => 1969-12-31 23:59:00 UTC
# OP's example:
parse_json_datetime("/Date(1370651105153)/")
# => 2013-06-08 00:25:05 UTC
# Same as above, but stated in two different timezones:
aaa = parse_json_datetime("/Date(1370651105153-0200)/")
# => 2013-06-07 22:25:05 -0200
bbb = parse_json_datetime("/Date(1370651105153-0800)/")
# => 2013-06-07 16:25:05 -0800
# As "rendered" strings they're not the same:
aaa.to_s == bbb.to_s
# => false
# But as moments in time, they are equivalent:
aaa == bbb
# => true
# And, for the sake of the argument, if they're both expressed in the
# same timezone (arbitrary '-04:00' in this case) then they also render
# as equal strings:
aaa.getlocal('-04:00').to_s == bbb.getlocal('-04:00').to_s
The integer seems to be a unixtime (in milliseconds). Just cut the last three digits off and feed the rest to Time.at:
Time.at(1370651105) # => 2013-06-08 04:25:05 +0400
For anyone who needed a bit more robust solution that included a timezone, this is the parser I came up with:
def parse_json_datetime(datetime)
# "/Date(-62135575200000-0600)/" seems to be the default date returned if the value is null
if datetime == "/Date(-62135575200000-0600)/"
# return nil because it is probably a null date that is defaulting to 0.
# to be more technically correct you could just return 0 here if you wanted.
return nil
elsif datetime =~ /\/Date\(([-+]?\d+)(-+\d+)?\)\// # parse the seconds and timezone (if present)
milliseconds_since_epoch = $1
time = Time.at(milliseconds_since_epoch.to_i/1000.0).utc # divide by 1000 because it is in milliseconds and Time uses seconds
if timezone_hourly_offset = $2
timezone_hourly_offset = timezone_hourly_offset.gsub("0","").to_i
time = time+(timezone_hourly_offset*60*60) # convert hourly timezone offset into seconds and add onto time
end
time
else
raise "Unrecognized date format."
end
end
Active Support makes it easy to do this:
#!/usr/bin/env ruby
require 'active_support/json'
require 'active_support/time'
Time.zone = 'Eastern Time (US & Canada)'
ActiveSupport.parse_json_times = true
puts ActiveSupport::JSON.decode('{"hi":"2009-08-10T19:01:02Z"}')
Which will output:
{"hi"=>Mon, 10 Aug 2009 15:01:02 EDT -04:00}
Once you turn the ActiveSupport.parse_json_times flag on, then any JSON payload you decode will get any times automatically convered into ActiveSupport::TimeWithZone objects.
Active Support uses a regex to detect times. You can read the tests to get a good idea of the time formats that it supports. Looks like it supports ISO 8601, but does not support Unix/POSIX/Epoch timestamps.
I was informed of this feature in Active Support when I created this issue against Rails.

How do I convert a 'Fixnum' to a date in Rails 3.1.3?

I am trying to display this output, as a date:
1296524384
But when I call .to_date on it, I am getting this error:
undefined method `to_date' for 1296524384:Fixnum
You can just do:
the_time = Time.at(1296524384)
That gives you:
2011-01-31 20:39:44 -0500
By the way, 1296524384 is referred to as UNIX or epoch time. It measures the number of seconds elapsed since January 1, 1970.
To format it a bit better, you can use Ruby's strftime method.
the_time = Time.at(1296524384).strftime("The date is %m/%d/%Y")
More info here: http://apidock.com/ruby/DateTime/strftime

Ruby's range step method causes very slow execution?

I've got this block of code:
date_counter = Time.mktime(2011,01,01,00,00,00,"+05:00")
#weeks = Array.new
(date_counter..Time.now).step(1.week) do |week|
logger.debug "WEEK: " + week.inspect
#weeks << week
end
Technically, the code works, outputting:
Sat Jan 01 00:00:00 -0500 2011
Sat Jan 08 00:00:00 -0500 2011
Sat Jan 15 00:00:00 -0500 2011
etc.
But the execution time is complete rubbish! It takes approximately four seconds to compute each week.
Is there some grotesque inefficiency that I'm missing in this code? It seems straight-forward enough.
I'm running Ruby 1.8.7 with Rails 3.0.3.
Assuming MRI and Rubinius use similar methods to generate the range the basic algorithm used with all the extraneous checks and a few Fixnum optimisations etc. removed is:
class Range
def each(&block)
current = #first
while current < #last
yield current
current = current.succ
end
end
def step(step_size, &block)
counter = 0
each do |o|
yield o if counter % step_size = 0
counter += 1
end
end
end
(See the Rubinius source code)
For a Time object #succ returns the time one second later. So even though you are asking it for just each week it has to step through every second between the two times anyway.
Edit: Solution
Build a range of Fixnum's since they have an optimised Range#step implementation.
Something like:
date_counter = Time.mktime(2011,01,01,00,00,00,"+05:00")
#weeks = Array.new
(date_counter.to_i..Time.now.to_i).step(1.week).map do |time|
Time.at(time)
end.each do |week|
logger.debug "WEEK: " + week.inspect
#weeks << week
end
Yes, you are missing a gross inefficiency. Try this in irb to see what you're doing:
(Time.mktime(2011,01,01,00,00,00,"+05:00") .. Time.now).each { |x| puts x }
The range operator is going from January 1 to now in increments of one second and that's a huge list. Unfortunately, Ruby isn't clever enough to combine the range generation and the one-week chunking into a single operation so it has to build the entire ~6million entry list.
BTW, "straight forward" and "gross inefficiency" are not mutually exclusive, in fact they're often concurrent conditions.
UPDATE: If you do this:
(0 .. 6000000).step(7*24*3600) { |x| puts x }
Then the output is produced almost instantaneously. So, it appears that the problem is that Range doesn't know how to optimize the chunking when faced with a range of Time objects but it can figure things out quite nicely with Fixnum ranges.

How to get a DateTime duration?

I'm baffled how to do this.
I need to take a datetime object and get the duration in hours, days, whatever, to the current time.
Thank you.
Getting the duration in seconds is easy:
>> foo = Time.new
=> Mon Dec 29 18:23:51 +0100 2008
>> bar = Time.new
=> Mon Dec 29 18:23:56 +0100 2008
>> print bar - foo
5.104063=> nil
So, a little over five seconds.
But to present this in a somewhat more human-friendly form, you'll need a third-party addition, like time_period_to_s, or the Duration package.
distance_of_time_in_words will give you a string representation of the time between two time/date objects.
You want the Time class rather than DateTime.
If you are using rails, then distance_of_time_in_words_to_now may be what you are looking for.
i just want to share my code in order to get more readable duration time.
instead of using distance_of_time_in_words like #Ryan Bigg suggest, i like #Sören Kuklau suggestion more.
but to show human-friendly form, i make something like this.
def get_duration opts
from = Time.parse opts[:from]
to = Time.parse opts[:to]
duration = to - from
durationLabel = []
separator = " "
hours = (duration / 3600).floor
restDuration = duration%3600
durationLabel << "#{hours}h" if hours > 0
return durationLabel.join separator if restDuration.floor == 0
minutes = (restDuration / 60).floor
restDuration = duration%60
durationLabel << "#{minutes}m" if minutes > 0
return durationLabel.join separator if restDuration.floor == 0
seconds = restDuration.floor
durationLabel << "#{seconds}s" if seconds > 0
durationLabel.join separator
end
get_duration from: "2018-03-15 11:50:43", to: "2018-03-15 11:51:50"
# return 1m 7s
it will calculate the hours, minutes, and seconds.
instead of getting a vague response like about 1 minutes or hard response like 67.0, we will get something more human-friendly but accurate until second like 1m 7s

Resources