Ruby - How to round Unix Timestamp (Epoch) to nearest month - ruby

I am attempting to round UNIX timestamps in Ruby to the nearest whole month. I have the following UNIX timestamps which I'd like to convert as shown--basically if the day of the month is the 15th and onward, it should round up to the next month (e.g. February 23rd rounds up to March 1st; February 9th rounds down to February 1st).
Here are the timestamps I have and the result I need help achieving:
1455846925 (Feburary 19th, 2016) => 1456790400 (March 1st, 2016)
1447476352 (November 14th, 2015) => 1446336000 (November 1st, 2015)
1242487963 (May 16th, 2009) => 1243814400 (June 1st, 2009).
I am okay solely relying on the logic of 1-14 (round down) / 15+ (round up). I realize this won't always take into account the days in a month and I can accept that for this if needed (although a solution that always takes into account the days in a given month is a bonus).
Ruby's DateTime module may be able to do it in combination with modulo of the number of seconds in a month but I'm not quite sure how to put it all together. If I can convert the UNIX timestamp directly without first translating it to a Ruby Date, that is perfectly fine too.
Thank you in advance for your assistance.

This rounds to the nearest second.
require 'time'
def round_to_month(secs)
t1 = Time.at secs
t2 = (t1.to_datetime >> 1).to_time
s1 = Time.new(t1.year, month=t1.month)
s2 = Time.new(t2.year, month=t2.month)
(t1-s1) < (s2-t1) ? s1 : s2
end
round_to_month(1455846925) # round 2016-02-18 17:55:25 -0800
#=> 2016-03-01 00:00:00 -0800
round_to_month(1447476352) # round 2015-11-13 20:45:52 -0800
#=> 2015-11-01 00:00:00 -0700
round_to_month(1242487963) # round 2009-05-16 08:32:43 -0700
#=> 2009-05-01 00:00:00 -0700
Consider
secs = 1455846925
The calculations are as follows:
t1 = Time.at secs
#=> 2016-02-18 17:55:25 -0800
dt = t1.to_datetime
#=> #<DateTime: 2016-02-18T17:55:25-08:00 ((2457438j,6925s,0n),-28800s,2299161j)>
dt_next = dt >> 1
#=> #<DateTime: 2016-03-18T17:55:25-08:00 ((2457467j,6925s,0n),-28800s,2299161j)>
t2 = dt_next.to_time
#=> 2016-03-18 18:55:25 -0700
s1 = Time.new(t1.year, month=t1.month)
#=> Time.new(2016, month=2)
#=> 2016-02-01 00:00:00 -0800
s2 = Time.new(t2.year, month=t2.month)
# Time.new(2016, month=3)
#=> 2016-03-01 00:00:00 -0800
(t1-s1) < (s2-t1) ? s1 : s2
#=> 1533325.0 < 972275.0 ? 2016-02-18 17:55:25 -0800 : 2016-03-01 00:00:00 -0800
#=> 2016-03-01 00:00:00 -0800

It would be easy to convert it to Time object and then convert it back to timestamp
If you're using Rails, this method should do, what you want:
def nearest_month(t)
time = Time.at(t).utc
time = time.next_month if time.day >= 15
time.beginning_of_month.to_i
end

I don't know if this is as accurate as #CarySwoveland's solution, but I like it:
require 'time'
FIFTEEN_DAYS = 15 * 24 * 60 * 60
def round_to_month(secs)
t1 = Time.at(secs + FIFTEEN_DAYS)
Time.new(t1.year, t1.month)
end
p round_to_month(1455846925) # round 2016-02-18 17:55:25 -0800
# => 2016-03-01 00:00:00 -0800
p round_to_month(1447476352) # round 2015-11-13 20:45:52 -0800
# => 2015-11-01 00:00:00 -0700
p round_to_month(1242487963) # round 2009-05-16 08:32:43 -0700
# => 2009-05-01 00:00:00 -0700
If you want it to return a UNIX timestamp instead just tack .to_i onto the last line in the method.

Something like this will work if you use ActiveSupport in Rails:
require 'date'
def round_to_nearest_month(timestamp)
# Convert the unix timestamp into a Ruby DateTime object
datetime = timestamp.to_datetime
# Get the day of the month from the datetime object
day_of_month = datetime.mday
if day_of_month < 15
datetime.at_beginning_of_month
else
datetime.at_beginning_of_month.next_month
end
return datetime
end

Related

How to dehumanize ago time from humanize strings in Ruby?

Is there a way to dehumanize a duration string as follows to get the actual utc timestamp?
For example: "1.hour.ago 25.minute.ago 1.second.ago" convert back Fri, 18 Oct 2019 16:09:10 UTC +00:00
the site info has it as "17h 21m 1s ago" need to convert it to db datetime utc i manage to do this:
"17h 21m 1s ago".gsub("h", '.hour.ago').gsub("m", '.minute.ago').gsub("s ago", '.second.ago')
# ==> "17.hour.ago 21.minute.ago 1.second.ago"
But how to remove the string portion and convert to method datetime? Trying datetime parse did not work.
Assuming the "17h 21m 1s ago" format is what you're working with, then you can use Time#advance to get the job done:
def when_was(string)
time = Time.now.utc
string.split(/\s+/).each do |part|
case (part)
when /\A(\d+)h\z/
time = time.advance(hours: -$1.to_i)
when /\A(\d+)m\z/
time = time.advance(minutes: -$1.to_i)
when /\A(\d+)s\z/
time = time.advance(seconds: -$1.to_i)
end
end
time
end
p when_was("17h 21m 1s ago")
# => 2019-10-18 01:31:36 UTC
You can also do this with Time.now.utc - 17.hours - 21.minutes - 1.second but that involves dynamic dispatching with send so it's more messy.
A prepackaged solution using chronic_duration gem:
require 'chronic_duration'
time_in_seconds = ChronicDuration.parse('17h 21m 1s ago')
p Time.now - time_in_seconds # => 2019-10-18 03:50:47 +0200
It can understand m, min, etc out of the box.
I assume that the hours, minutes and seconds are sequential.
time_in_string = "1.hour.ago 25.minute.ago 1.second.ago"
time_info = time_in_string.scan(/\d+/).map(&:to_i)
#=> [1, 25, 1]
time_in_seconds = time_info.zip([3600, 60, 1]).sum{ |x, y| x * y }
=> 5101
Time.now
#=> 2019-10-18 22:56:14 +0300
Time.now - time_in_seconds.seconds
#=> 2019-10-18 21:31:13 +0300
As a method:
def time_from_string(time_in_string)
time_info = time_in_string.scan(/\d+/).map(&:to_i)
time_in_seconds = time_info.zip([3600, 60, 1]).sum{ |x, y| x * y }
Time.now - time_in_seconds.seconds
end
p time_from_string(time_in_string)
#=> 2019-10-18 21:37:10 +0300

how to get next month date from today using Time

I need to generate next month date from today. Should I manuallt check if month == 12 and add +1 to d.year or there is easy solution?
Time class did strange math:
>> d = Time.now
=> 2015-12-03 15:15:36 +0300
>> d.month
=> 12
>> d.month.next
=> 13
Date has a method next_month:
require 'date'
Date.today #=> #<Date: 2015-12-03 ((2457360j,0s,0n),+0s,2299161j)>
Date.today.next_month #=> #<Date: 2016-01-03 ((2457391j,0s,0n),+0s,2299161j)>
Date.today.next_month.month #=> 1
To convert a Time instance to a Date, use to_date.
Yes add+1
but do (this month + 1)%12
if ( thisMonth == 12)
nextMonth = 1;
else
nextMonth = thisMonth+1;
This is just to give an idea
I did not see for any edge cases yet
there are two commonly used possible solutions:
1: Date.today+1.month OR for month only (Date.today+1.month).month
2: Date.today.next_month OR for month only Date.today.next_month.month

Get an array including every 14th day from a given date

In ruby, how can I get every 14th day of the year, going backwards and forwards from a date.
So consider I'm billed for 2 weeks of recycling on today, 6-16-2015. How can I get an array of every recycling billing day this year based on that date.
Date has a step method:
require 'date'
d = Date.strptime("6-16-2015", '%m-%d-%Y') # strange date format
end_year = Date.new(d.year, -1, -1)
p d.step(end_year, 14).to_a
# =>[#<Date: 2015-06-16 ((2457190j,0s,0n),+0s,2299161j)>, #<Date: 2015-06-30 ((2457204j,0s,0n),+0s,2299161j)>, ...
# Going backward:
begin_year = Date.new(d.year, 1, 1)
p d.step(begin_year,-14).to_a
# =>[#<Date: 2015-06-16 ((2457190j,0s,0n),+0s,2299161j)>, #<Date: 2015-06-02 ((2457176j,0s,0n),+0s,2299161j)>,...
A more descriptive and easy to understand solution:
require 'date'
current_date = Date.parse "16-june-15"
start_date = Date.parse '1-jan-15'
end_date = Date.parse '31-dec-15'
interval = 14
result = current_date.step(start_date, -interval).to_a
result.sort!.pop
result += current_date.step(end_date, interval).to_a
You could do that as follows:
require 'date'
date_str = "6-16-2015"
d = Date.strptime(date_str, '%m-%d-%Y')
f = Date.new(d.year)
((f + (f-d).abs % 14)..Date.new(d.year,-1,-1)).step(14).to_a
#=> [#<Date: 2015-01-13 ((2457036j,0s,0n),+0s,2299161j)>,
# #<Date: 2015-01-27 ((2457050j,0s,0n),+0s,2299161j)>,
# ...
# #<Date: 2015-06-16 ((2457190j,0s,0n),+0s,2299161j)>,
# ...
# #<Date: 2015-12-29 ((2457386j,0s,0n),+0s,2299161j)>]
Based on the second sentence of your question, I assume you simply want an array of all dates in the given year that are two-weeks apart and include the given day.
I attempted a mathy modulus biased approach which turned out unexpectedly confusing.
require 'date'
a_recycle_date_string = "6-17-2015"
interval = 14
a_recycle_date = Date.strptime(a_recycle_date_string, '%m-%d-%Y')
current_year = a_recycle_date.year
end_of_year = Date.new(current_year, -1, -1)
# Find out which index of the first interval's days is the first recycle day
# of the year the (1 indexed)
remainder = (a_recycle_date.yday) % interval
# => 0
# make sure remainder 0 is treated as interval-1 so it doesn't louse
# the equation up
n_days_from_first_recycling_yday_of_year = (remainder - 1) % interval
first_recycle_date_this_year = Date.new(current_year,
1,
1 + n_days_from_first_recycling_yday_of_year)
first_recycle_date_this_year.step(end_of_year, interval).to_a

How to make Time.now less precise?

I simply want to return a less precise, truncated version of Time.now.
When I run Time.now I get a very precise object 2014-10-02 14:49:47 -0400tim. I only want
YYYY-MM-DD HH:MM:MM.
How can I (temporarily) cast the precision of a Time.now return object?
strftime() allows you to provide a format string for your time object. A list of format flags can be found in the documentation
Example from Docs:
t = Time.new(2007,11,19,8,37,48,"-06:00") #=> 2007-11-19 08:37:48 -0600
t.strftime("Printed on %m/%d/%Y") #=> "Printed on 11/19/2007"
t.strftime("at %I:%M%p") #=> "at 08:37AM"
Your Example:
t.strftime("%Y-%m-%d %T") #=> YYYY-MM-DD HH:MM:MM.
You could create a new Time object that is the original time rounded to the nearest minute:
require 'time'
t = Time.now
#=> 2014-10-02 12:40:22 -0700
new_time = t + (t.sec >= 30 ? 60-t.sec : -t.sec)
#=> 2014-10-02 12:40:00 -0700
t += 30
#=> 2014-10-02 12:40:52 -0700
new_time = t + (t.sec >= 30 ? 60-t.sec : -t.sec)
#=> 2014-10-02 12:41:00 -0700

Compound date statement in Ruby for first weekday after EOM?

I want to find the first weekday of the month after the 1st which also happens to fall on a weekday in Ruby, something like the following:
Min ( DayOfMonth in (2...8) && DayOfWeek in ( monday ... friday ) )
How can I state this appropriately?
We can simply start at the second day of the desired month, and keep going to the next day until we find a weekday. This will require 2 increments at most, since the worst case is when day 2 is a Saturday and 3 a Sunday. Our target then would be the 4th day. If Sunday is the 2nd day, our target is 3, and otherwise the desired day is the 2nd of the month. My first_wday method returns a Date object as well. You can use .day to get the day of the month, or .wday to get the day of the week on it.
require 'date'
def first_wday(date)
d = Date.new(date.year, date.month, 2)
d += 1 while d.wday == 6 || d.wday == 0 # Saturday or Sunday
d
end
(1..12).each do |month|
date = Date.new(2014, month, 1)
wday = first_wday(date)
puts wday.strftime('%9B %Y: %-d (%A)')
end
# January 2014: 2 (Thursday)
# February 2014: 3 (Monday)
# March 2014: 3 (Monday)
# April 2014: 2 (Wednesday)
# May 2014: 2 (Friday)
# June 2014: 2 (Monday)
# July 2014: 2 (Wednesday)
# August 2014: 4 (Monday)
# September 2014: 2 (Tuesday)
# October 2014: 2 (Thursday)
# November 2014: 3 (Monday)
# December 2014: 2 (Tuesday)
This would be one way of doing it.
Code
require 'date'
d = Date.today
d = (d-d.day+2).next_month
(d..d+2).find { |i| (1..5).cover?(i.wday) }.strftime("%m-%d-%Y %a")
Explanation
d = Date.today #=> #<Date:2014-04-28((2456776j,0s,0n),+0s,2299161j)>
d = (d-d.day+2) #=> #<Date:2014-04-02((2456750j,0s,0n),+0s,2299161j)>
d = d.next_month #=> #<Date:2014-05-02((2456780j,0s,0n),+0s,2299161j)>
d.month #=> 5
d.year #=> 2014
d.wday #=> 5 (Friday)
At least one of the first three days of the month is a weekday, so we need only consider the range (d..d+2). The first value of (d..d+2) passed to the block is d, so:
i = d #=> #<Date: 2014-05-02 ((2456780j,0s,0n),+0s,2299161j)>
i.wday #=> 5
As Date's week starts with 0 on Sunday, the five week days are 1..5:
(1..5).cover?(i.wday) #=> true
(1..5).cover?(4) #=> true
So
i.strftime("%m-%d-%Y %a") #=> "05-02-2014 Fri"
is returned.

Resources