How to show time updated at in Rails? - ruby-on-rails

Right now I have a function time_since
def time_since
seconds = Time.now - self.updated_at
if seconds < 15
display = "Just Now"
elsif seconds < 59.5 #less than 59.5 seconds
display = seconds.round(0).to_s + "s"
elsif seconds < (3600-30) #less than 59.5 minutes
display = (seconds/60).round(0).to_s + "m"
elsif seconds < (86400-1800) #less than 23.5 hours
display = (seconds/3600).round(0).to_s + "h"
elsif seconds < 86400*2 #less than 2 days
display = "Yesterday"
elsif seconds < 86400*7 #less than 1 week
display = self.updated_at.strftime("%A")
else #more than 1 week
mon = self.updated_at.month
day = self.updated_at.day.to_s
display = day + convert_month_number_to_month_name(mon).to_s
end
return display
end
And I want to be able to hover over this and have it display the exact updated_at timestamp. Is there a gem I can use easily to do this?

Ok! I ended up using this:
<span title=<%= Object.updated_at.localtime.asctime.split.join('-') %>>
<%= distance_of_time_in_words_to_now(Object.updated_at, include_seconds = true) %>
</span>
I couldn't get title to show more than the first word of the time so I joined it with dashes (even when I used strftime). This is functional and I'll clean it up later

Related

How to get start and end date from quarters?

I am building a rails 5 app.
I need to be able to get the dates that is from a current quarter. With that I mean the user will provide me with a selected quarter (1 to 4) and I will convert that number to a start and end date for that selected quarter. How can I do that?
This is how I tried it but it is good?
def quarter_date(quarter, year)
if quarter == 1
where(date_at: Time.parse("01-01-#{year}")..Time.parse("01-03-#{year}"))
elsif quarter == 2
where(date_at: Time.parse("01-04-#{year}")..Time.parse("01-06-#{year}"))
elsif quarter == 3
where(date_at: Time.parse("01-07-#{year}")..Time.parse("01-09-#{year}"))
elsif quarter == 4
where(date_at: Time.parse("01-10-#{year}")..Time.parse("01-12-#{year}"))
end
end
You mean something like this?
require 'date'
today = Date.today
=> #<Date: 2018-07-02 ((2458302j,0s,0n),+0s,2299161j)>
year = today.year
=> 2018
input = 3
start_date = Date.new(2018, input * 3 - 2, 1)
=> #<Date: 2018-07-01 ((2458301j,0s,0n),+0s,2299161j)>
end_date = Date.new(2018, input * 3 + 1, 1) - 1
=> #<Date: 2018-09-30 ((2458392j,0s,0n),+0s,2299161j)>
It returns the start and end dates for the given quarter of current year.
Update
Updated with method from your attempt:
def quarter_date_range(quarter, year)
start_date = Time.parse("#{year}-#{quarter * 3 - 2}-1")
end_date = (start_date + 2.months).end_of_month
where(date_at: start_date..end_date)
end

Any option to post-process returned value in long conditional, other than setting variables for each statement?

def some_method(x)
if x == 1
date = Date.today
elsif x == 5
date = Date.today + 2
else
date = Date.today - 2
end
date + 20
end
For visual clarity, is it possible somehow to omit date = for each statement and catch whatever the returned value is from the conditional and add 20 to it?
(The code is for example purpose, my own code has 10 if-statements.)
def some_method(x)
date = if x == 1
Date.today
elsif x == 5
Date.today + 2
else
Date.today - 2
end
date + 20
end
If you have 10 if statements it is probably better to refactor code using case-when like this:
def some_method(x)
date = case x
when 1; Date.today
when 5; Date.today + 2
else; Date.today - 2
end
date + 20
end

change the TimeStamp return into mm:sec not hour

I have this:
<(Time.now).to_i>
it returns a integer thing
and want to convert that integer value into (mins and seconds)
only as per my requirement
Try this
Time.now.strftime("%M:%S")
Hope you are trying to find this
You can get the minutes and seconds by this
delta = Time.now.to_i
%w[ weeks days hours minutes seconds].collect do |step|
seconds = 1.send(step)
(delta / seconds).to_i.tap do
delta %= seconds
end
end
It will return an array with having 5 elements . You can get the mins and secs from
arr[3] and arr[4]
Also you can take the whatever the time in integer format as delta .
If you want to show only mins and secs when hr = 0
#hr = timedef[0]
#mn = timedef[1]
#sec = timedef[2]
time_remains = ''
unless self.timedef.blank?
if #hr > 0
time_remains = time_remains + "#{#hr} #{'hour'.pluralize(#hr)} "
elsif #mn > 0
time_remains = time_remains + "#{#mn} #{'minute'.pluralize(#mn)} and #{#sec} #{'second'.pluralize(#sec)} "
else
time_remains = time_remains + "#{#sec} #{'second'.pluralize(#sec)} "
end
end
use strftime -- see this page http://www.dzone.com/snippets/date-time-format-ruby it has the formats (though its similar to c/c++ and Java)
Also, you will likely want to do some research into timezone handling on the time class. there is a function: in_time_zone that can convert the timezone for you. So typically you store the times or the times fetched are in UTC then you can dynamically change the timezone before you strftime.

Ruby next run in X minutes

I have a ruby on rails app, and there is a cron running in the background.
The cron job runs every 10 minutes on the 10 minutes, so 9:00, 9:10, 9:20, 9:30 and so on.
In my rails app, I want to show when the cron will next run.
So I will have, "Cron will next run at: 9:20PM"
I just can't figure out how to get this in ruby.
Thanks,
Andrew
def next_10_minutes
nxt = Time.now+(10-Time.now.min%10).minute
nxt.strftime("%H:%M")
end
next_10_minutes
#=> "00:30"
or little more flexible and monkey patching
class Time
def self.next_10_minutes
self.now+(10-Time.now.min%10).minute
end
end
Time.next_10_minutes.strftime("%H:%M")
#=> "00:40"
This is quite simple:
get the current minutes: min = DateTime.now.min
round to the upper ten minute:
nextTick = ((min/10.0).ceil*10)
print the difference:
diff = nextTick - min
hour = DateTime.now.hour
if nextTick == 60
nextTick = 0
hour = (hour + 1) % 24
end
print "Next run in #{diff} minutes (at #{hour}:#{nextTick})"
Try it here: http://codepad.org/5vxlg6kF
def next_tick(now)
min10 = ((now.min / 10) + 1)*10
if min10 == 60
Time.mktime(now.year, now.month, now.day, now.hour + 1, 0)
else
Time.mktime(now.year, now.month, now.day, now.hour, min10)
end
end
p next_tick(Time.now)

How to generate a human readable time range using ruby on rails

I'm trying to find the best way to generate the following output
<name> job took 30 seconds
<name> job took 1 minute and 20 seconds
<name> job took 30 minutes and 1 second
<name> job took 3 hours and 2 minutes
I started this code
def time_range_details
time = (self.created_at..self.updated_at).count
sync_time = case time
when 0..60 then "#{time} secs"
else "#{time/60} minunte(s) and #{time-min*60} seconds"
end
end
Is there a more efficient way of doing this. It seems like a lot of redundant code for something super simple.
Another use for this is:
<title> was posted 20 seconds ago
<title> was posted 2 hours ago
The code for this is similar, but instead i use Time.now:
def time_since_posted
time = (self.created_at..Time.now).count
...
...
end
If you need something more "precise" than distance_of_time_in_words, you can write something along these lines:
def humanize(secs)
[[60, :seconds], [60, :minutes], [24, :hours], [Float::INFINITY, :days]].map{ |count, name|
if secs > 0
secs, n = secs.divmod(count)
"#{n.to_i} #{name}" unless n.to_i==0
end
}.compact.reverse.join(' ')
end
p humanize 1234
#=>"20 minutes 34 seconds"
p humanize 12345
#=>"3 hours 25 minutes 45 seconds"
p humanize 123456
#=>"1 days 10 hours 17 minutes 36 seconds"
p humanize(Time.now - Time.local(2010,11,5))
#=>"4 days 18 hours 24 minutes 7 seconds"
Oh, one remark on your code:
(self.created_at..self.updated_at).count
is really bad way to get the difference. Use simply:
self.updated_at - self.created_at
There are two methods in DateHelper that might give you what you want:
time_ago_in_words
time_ago_in_words( 1234.seconds.from_now ) #=> "21 minutes"
time_ago_in_words( 12345.seconds.ago ) #=> "about 3 hours"
distance_of_time_in_words
distance_of_time_in_words( Time.now, 1234.seconds.from_now ) #=> "21 minutes"
distance_of_time_in_words( Time.now, 12345.seconds.ago ) #=> "about 3 hours"
chronic_duration parses numeric time to readable and vice versa
If you want to show significant durations in the seconds to days range, an alternative would be (as it doesn't have to perform the best):
def human_duration(secs, significant_only = true)
n = secs.round
parts = [60, 60, 24, 0].map{|d| next n if d.zero?; n, r = n.divmod d; r}.
reverse.zip(%w(d h m s)).drop_while{|n, u| n.zero? }
if significant_only
parts = parts[0..1] # no rounding, sorry
parts << '0' if parts.empty?
end
parts.flatten.join
end
start = Time.now
# perform job
puts "Elapsed time: #{human_duration(Time.now - start)}"
human_duration(0.3) == '0'
human_duration(0.5) == '1s'
human_duration(60) == '1m0s'
human_duration(4200) == '1h10m'
human_duration(3600*24) == '1d0h'
human_duration(3600*24 + 3*60*60) == '1d3h'
human_duration(3600*24 + 3*60*60 + 59*60) == '1d3h' # simple code, doesn't round
human_duration(3600*24 + 3*60*60 + 59*60, false) == '1d3h59m0s'
Alternatively you may be only interested in stripping the seconds part when it doesn't matter (also demonstrating another approach):
def human_duration(duration_in_seconds)
n = duration_in_seconds.round
parts = []
[60, 60, 24].each{|d| n, r = n.divmod d; parts << r; break if n.zero?}
parts << n unless n.zero?
pairs = parts.reverse.zip(%w(d h m s)[-parts.size..-1])
pairs.pop if pairs.size > 2 # do not report seconds when irrelevant
pairs.flatten.join
end
Hope that helps.
There is problem with distance_of_time_in_words if u ll pass there 1 hour 30 min it ll return about 2 hours
Simply add in helper:
PERIODS = {
'day' => 86400,
'hour' => 3600,
'minute' => 60
}
def formatted_time(total)
return 'now' if total.zero?
PERIODS.map do |name, span|
next if span > total
amount, total = total.divmod(span)
pluralize(amount, name)
end.compact.to_sentence
end
Basically just pass your data in seconds.
Rails has a DateHelper for views. If that is not exactly what you want, you may have to write your own.
#Mladen Jablanović has an answer with good sample code. However, if you don't mind continuing to customize a sample humanize method, this might be a good starting point.
def humanized_array_secs(sec)
[[60, 'minutes '], [60, 'hours '], [24, 'days ']].inject([[sec, 'seconds']]) do |ary, (count, next_name)|
div, prev_name = ary.pop
quot, remain = div.divmod(count)
ary.push([remain, prev_name])
ary.push([quot, next_name])
ary
end.reverse
end
This gives you an array of values and unit names that you can manipulate.
If the first element is non-zero, it is the number of days. You may want to write code to handle multiple days, like showing weeks, months, and years. Otherwise, trim off the leading 0 values, and take the next two.
def humanized_secs(sec)
return 'now' if 1 > sec
humanized_array = humanized_array_secs(sec.to_i)
days = humanized_array[-1][0]
case
when 366 <= days
"#{days / 365} years"
when 31 <= days
"#{days / 31} months"
when 7 <= days
"#{days / 7} weeks"
else
while humanized_array.any? && (0 == humanized_array[-1][0])
humanized_array.pop
end
humanized_array.reverse[0..1].flatten.join
end
end
The code even finds use for a ruby while statement.

Resources