get decimal amount of months for date range - ruby

I try to get a decimal amount of months for a date range. Example:
ruby-1.9.2-p0 > from = Date.new(2011, 7, 6)
=> Wed, 06 Jul 2011
ruby-1.9.2-p0 > to = Date.new(2011, 8, 31)
=> Wed, 31 Aug 2011
ruby-1.9.2-p0 > to - from
=> (56/1)
So the difference is 56 days. But I want and need the amount of months: 1.83
I have created the following piece of code which returns the correct result but doesn't feel like the ruby way:
months = Hash.new
(from..to).each do |date|
unless months.key? date.beginning_of_month
months[date.beginning_of_month] = 1
else
months[date.beginning_of_month] += 1
end
end
multiplicator = 0.0
months.each do |month, days|
multiplicator += days.to_f/month.end_of_month.day
end
return multiplicator.floor_to(2)
To be honest: It looks ugly and really inefficient. But I just cannot figure out any easier way.
Can you help me to find a better solution?
For further questions feel free to ask me.
Many thanks in advance!
Update/Solution: Solved the problem with the following piece of code:
months = 0.0
months += ((date_to < date_from.end_of_month ? date_to : date_from.end_of_month) - date_from + 1) / Time.days_in_month(date_from.month)
unless date_to.month == date_from.month
months += (date_to - date_to.beginning_of_month + 1) / Time.days_in_month(date_to.month)
months += date_to.month - date_from.month - 1
end
return months.floor_to(2)

a better way to do would be summation of
number of days left in from / number of days in from
number of days completed in to / number of days in to
number of months between from and to (from, to excluded)
This way you wont have iterations to do

Related

console output of the current calendar month in Ruby

I need to output to the console the calendar of the current month in Ruby. The result should be similar to ncal on UNIX-like systems. I found a solution for C ++ but can't adapt for Ruby. So far, I only realized that I need to use nested loops to output the height and width. Tell me in which direction to move?
require 'date'
days = %w[Mun Tue Wed Thu Fri Sat Sun]
puts " #{Date::MONTHNAMES[Date.today.month]} #{Date.today.year}"
i = 0
start_month = (Date.today - Date.today.mday + 1).strftime("%a")
while i < days.size
print days[i]
j = 1
while j <= 31
if days[i] == start_month
print " #{j}"
end
j += 7
end
i += 1
puts
end
I'll take your solution so far, and try to give some specific pointers for how to progress with it - but of course, there are many different ways to approach this problem in general, so this is by no means the only approach!
The first critical issue (as you're aware!) is that you're only printing things for the row starting on the 1st of the month, due to this line:
if days[i] == start_month
Sticking with the current overall design, we know we'll need to print something for every line, so clearly a conditional like this isn't going to work. Let's try removing it.
Firstly, it will be more convenient to know which day of the week the month started on as a number, not a string, so we can easily calculate offsets against another day. Let's do that with:
# e.g. for 1st July 2021 this was a Thursday, so we get `4`.
start_of_month_weekday = (Date.today - Date.today.mday + 1).cwday
Next (and this is the crucial step!), we can use the above information to find out "which day of the month is it, on this day of the week?"
Here a first version of that calculation, incorporated into your solution so far:
require 'date'
days = %w[Mon Tue Wed Thu Fri Sat Sun]
puts " #{Date::MONTHNAMES[Date.today.month]} #{Date.today.year}"
i = 0
# e.g. for 1st July 2021 this was a Thursday, so we get `4`.
start_of_month_weekday = (Date.today - Date.today.mday + 1).cwday
while i < days.size
print days[i]
day_of_month = i - start_of_month_weekday + 2 # !!!
while day_of_month <= 31
print " #{day_of_month}"
day_of_month += 7
end
i += 1
puts
end
This outputs:
July 2021
Mon -2 5 12 19 26
Tue -1 6 13 20 27
Wed 0 7 14 21 28
Thu 1 8 15 22 29
Fri 2 9 16 23 30
Sat 3 10 17 24 31
Sun 4 11 18 25
Not bad! Now we're getting somewhere!
I'll leave you to figure out the rest 😉 .... But here are some clues, for what I'd tackle next:
This code, print " #{day_of_month}", needs to print a "blank space" if the day number is less than 1. This could be done with a simple if statement.
Similarly, since you want this calendar to line up neatly in a grid, you need this code to always print a something two characters wide. sprintf is your friend here! Check out the "Examples of width", about halfway down the page.
You've hardcoded 31 for the number of days in the month. This should be fixed, of course. (Use the Date library!)
It's funny how you used strftime("%a") in one place, yet constructed the calendar title awkwardly in the line above! 😄 Take a look at the documentation for formatting dates; it's extremely flexible. I think you can use: Date.today.strftime("%B %Y").
If you'd like to add some colour (or background colour?) to the current day of the month, consider doing something like this, or use a library to assist.
Using while loops works OK, but is quite un-rubyish. In 99% of cases, ruby has even better tools for the job; it's a very expressive language - iterators are king! (I'm guessing you first learned another language, before ruby? Seeing while loops, and/or for loops, is a dead giveaway that you're more familiar with a different language.) Instead of the outer while loop (while i < days.size), you could use days.each_with_index. And instead of the inner while loop (while j < 31), you could use day_of_month.step(31, 7) (how cool is that!!).
This is one way:
Construct a one-dimensional array, beginning with the daynames (Mon Tue ...).
Figure out a way to determine with how many "blanks" the month starts (these are days from the previous month. wday might help). Attach that amount of empty strings to the array.
Determine how many days the month has (hint Date.new(2021,7,-1), and attach all these daynumbers to the array.
Attach empty strings to the array until the size of the array is divisible by 7 (or better, calculate). Skip this if you're skipping the last bullet.
Convert all elements of this array to right-adjusted strings of size 3 or some-such.
Use each_slice(7) to slice the array into weeks.
If desired, transpose this array of week-slices to mimic the ncal output.
Thank you for your help, literally 10 hours and I figured it out thanks to you. I apologize once again for the initially incorrectly posed question.
With the help of hints, I assembled such a solution.
require 'date'
days = %w[Mon Tue Wed Thu Fri Sat Sun]
p days
blanks = Date.new(2021,7,1).wday - 1
blanks.times do
days.push(' ')
end
days_in_month = Date.new(2021, 7, -1).day
days_in_month
day = 1
while day <= days_in_month
days.push(day)
day += 1
end
unless (days.size % 7) == 0
days.push(' ')
end
days.join(', ')
new_arr = days.each_slice(7).to_a
puts"Массив дней: #{new_arr}"
for i in 0...7
for j in 0...new_arr.size
print " #{new_arr[j][i]}"
end
puts
end
require 'date'
# init
DAYS_ORDER = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
today = Date.today
month = today.month
year = today.year
first_day = Date.new(year, month, 1)
last_day = Date.new(year, month, -1)
hash_days = {}
# get all current months days and add to hash_days
first_day.upto(last_day) { |day| hash_days[day.day] = day.strftime('%a') }
# group by wday
grouped_hash = hash_days.group_by { |day| day.pop }.transform_values { |days| days.flatten }
# sort by wday from DAYS_ORDER
sorted_arr = grouped_hash.sort_by { |k, v| DAYS_ORDER.index(k) }
# rendering current month's calendar with mark current day
## title
print "\x1b[4m#{today.strftime("%B %Y")}\x1b[0m\n"
## calendar
indent = true
sorted_arr.each do |wday, days|
print wday
if days[0] != 1 && indent == true
print " "
else
indent = false
end
days.each do |value|
spaces = " " * (value > 9 ? 1 : 2)
str_day = spaces + value.to_s
current_day = "\x1b[1;31m#{str_day}\x1b[0m"
print value == today.day ? current_day : str_day
end
puts
end
view

Ruby - Integer to Time conversion for specific format (y m d)

given
today = 20150307
and
t= Time.at(today).strftime("%Y%m%d")
why this does not return
20150307
but instead
19700822
I ma triyng to check if the difference of thwo date is more than 7 days but those two values are converted into integer in the first place
example
a = 20150227 #(25th February 2015)
x = 20150307 #(7tharch 2015)
if (x-a > 7)
puts "This Item is overdue"
else
puts "All good"
end
my original today is given by this
today = Time.now.strftime("%Y%m%d").to_i
oneweek = (Time.now + (60 * 60 * 24 * 7)).strftime("%Y%m%d").to_i
if i do oneweek - today it will be an integer difference not a date one...
how can i achieve this???
Time.at spect the number of seconds from 1970-01-01 (Epoch).
To do what you want, try something like: t = Date.strptime("20150307", "%Y%m%d")

inscrutable behaviour of date in ruby

I have a variable which has some future date(for example I have taken 30 days after date here), I want to have a conditional statement based on if this future date is 10 days advanced, I was trying following code, but it doesn't work, what am I doing wrong here?
> future_date = Date.today + 30.day
=> Wed, 16 Jul 2014
> future_date - Date.today > 10.day
=> false # Shouldn't this has been true
future_date - Date.today will always give you an answer in days (30)
You can see this by
future_date - Date.today
=> 30
So you can do...
future_date - Date.today > 10
And that will work fine.
10.days converts the interval into seconds: 864_000 (number of seconds in ten days) and 30 is not greater than 864_000!

Get last day of the month in Ruby

I made new object Date.new with args (year, month). After create ruby added 01 number of day to this object by default. Is there any way to add not first day, but last day of month that i passed as arg(e.g. 28 if it will be 02month or 31 if it will be 01month) ?
use Date.civil
With Date.civil(y, m, d) or its alias .new(y, m, d), you can create a new Date object. The values for day (d) and month (m) can be negative in which case they count backwards from the end of the year and the end of the month respectively.
=> Date.civil(2010, 02, -1)
=> Sun, 28 Feb 2010
>> Date.civil(2010, -1, -5)
=> Mon, 27 Dec 2010
To get the end of the month you can also use ActiveSupport's helper end_of_month.
# Require extensions explicitly if you are not in a Rails environment
require 'active_support/core_ext'
p Time.now.utc.end_of_month # => 2013-01-31 23:59:59 UTC
p Date.today.end_of_month # => Thu, 31 Jan 2013
You can find out more on end_of_month in the Rails API Docs.
So I was searching in Google for the same thing here...
I wasn't happy with above so my solution after reading documentation
in RUBY-DOC was:
Example to get 10/31/2014
Date.new(2014,10,1).next_month.prev_day
require "date"
def find_last_day_of_month(_date)
if(_date.instance_of? String)
#end_of_the_month = Date.parse(_date.next_month.strftime("%Y-%m-01")) - 1
else if(_date.instance_of? Date)
#end_of_the_month = _date.next_month.strftime("%Y-%m-01") - 1
end
return #end_of_the_month
end
find_last_day_of_month("2018-01-01")
This is another way to find
You can do something like that:
def last_day_of_month?
(Time.zone.now.month + 1.day) > Time.zone.now.month
end
Time.zone.now.day if last_day-of_month?
This is my Time based solution. I have a personal preference to it compared to Date although the Date solutions proposed above read somehow better.
reference_time ||= Time.now
return (Time.new(reference_time.year, (reference_time.month % 12) + 1) - 1).day
btw for December you can see that year is not flipped. But this is irrelevant for the question because december always has 31 day. And for February year does not need flipping. So if you have another use case that needs year to be correct, then make sure to also change year.
Here is taking the first and third answers to find the last day of the previous month.
today_c = Date.civil(Date.today.prev_month.year, -1, -1)
p today_c

Number of days between two Time instances

How can I determine the number of days between two Time instances in Ruby?
> earlyTime = Time.at(123)
> laterTime = Time.now
> time_difference = laterTime - earlyTime
I'd like to determine the number of days in time_difference (I'm not worried about fractions of days. Rounding up or down is fine).
Difference of two times is in seconds. Divide it by number of seconds in 24 hours.
(t1 - t2).to_i / (24 * 60 * 60)
require 'date'
days_between = (Date.parse(laterTime.to_s) - Date.parse(earlyTime.to_s)).round
Edit ...or more simply...
require 'date'
(laterTime.to_date - earlyTime.to_date).round
earlyTime = Time.at(123)
laterTime = Time.now
time_difference = laterTime - earlyTime
time_difference_in_days = time_difference / 1.day # just divide by 1.day
[1] pry(main)> earlyTime = Time.at(123)
=> 1970-01-01 01:02:03 +0100
[2] pry(main)> laterTime = Time.now
=> 2014-04-15 11:13:40 +0200
[3] pry(main)> (laterTime.to_date - earlyTime.to_date).to_i
=> 16175
To account for DST (Daylight Saving Time), you'd have to count it by the days. Note that this assumes less than a day is counted as 1 (rounded up):
num = 0
cur = start_time
while cur < end_time
num += 1
cur = cur.advance(:days => 1)
end
return num
Here is a simple answer that works across DST:
numDays = ((laterTime - earlyTime)/(24.0*60*60)).round
60*60 is the number of seconds in an hour
24.0 is the number of hours in a day. It's a float because some days are a little more than 24 hours, some are less. So when we divide by the number of seconds in a day we still have a float, and round will round to the closest integer.
So if we go across DST, either way, we'll still round to the closest day. Even if you're in some weird timezone that changes more than an hour for DST.
in_days (Rails 6.1+)
Rails 6.1 introduces new ActiveSupport::Duration conversion methods like in_seconds, in_minutes, in_hours, in_days, in_weeks, in_months, and in_years.
As a result, now, your problem can be solved as:
date_1 = Time.parse('2020-10-18 00:00:00 UTC')
date_2 = Time.parse('2020-08-13 03:35:38 UTC')
(date_2 - date_1).seconds.in_days.to_i.abs
# => 65
Here is a link to the corresponding PR.
None of these answers will actually work if you don't want to estimate and you want to take into account daylight savings time.
For instance 10 AM on Wednesday before the fall change of clocks and 10 AM the Wednesday afterwards, the time between them would be 1 week and 1 hour. During the spring it would be 1 week minus 1 hour.
In order to get the accurate time you can use the following code
def self.days_between_two_dates later_time, early_time
days_between = (later_time.to_date-early_time.to_date).to_f
later_time_time_of_day_in_seconds = later_time.hour*3600+later_time.min*60+later_time.sec
earlier_time_time_of_day_in_seconds = early_time.hour*3600+early_time.min*60+early_time.sec
days_between + (later_time_time_of_day_in_seconds - early_time_time_of_day_in_seconds)/1.0.day
end

Resources