Difference between 2 dates in days - ruby

I have 2 dates and difference between them can be over a month. I want to find a difference between them in day. However, b.days - a.days turns a blind eye to to the months and, possibly, years too.
require 'date'
a = Date.parse("20141030")
b = Date.parse("20141230")
b.day - a.day #=> 0
What's the easier way to find such a difference?

Just subtract the one from the other:
(b - a)
# => (61/1)
(b - a).to_i
# => 61
The reason you got 0 is b.day and a.day returns day of the month: 30. (30 - 30 = 0)
b.day
# => 30
a.day
# => 30

Related

Calculate days between two dates [always keeping months of max 30 days]

Assuming each month always has 30 days, I'd like to calculate the days between two given dates.
FROM 05/04/2020
TO 20/12/2020
result: 256 days (NOT 259 days if we considered months with 31 days)
With the simple mathematical subtraction between dates I get the wrong risult:
(Date.new(2019,12,20) - Date.new(2019,4,5)).floor
=> 259
To overcome this I had to create a pretty complex alghoritm:
days += inclusive_days_in_range(
position_data[:workFrom],
position_data[:workFrom].at_end_of_month
)
months = inclusive_months_in_range(
position_data[:workFrom].at_beginning_of_month.next_month,
position_data[:workTo].at_end_of_month.prev_month
)
days += months * MAX_DAYS_IN_MONTHS
days += inclusive_days_in_range(
position_data[:workTo].at_beginning_of_month,
position_data[:workTo]
)
Is there a simple way?
Similar to #CarySwoveland's answer but uses dot product:
require 'matrix'
def ndays str
Vector[*str.split('/').map(&:to_i)].dot [1,30,360]
end
> ndays('20/12/2020') - ndays('05/04/2020') + 1
=> 256
Add +1 since it seems like you want the number of days, inclusive.
Another approach would be to count the number of months, multiply by 30, then subtract the days into the month of the FROM date, and add in the days of the TO date.
Counting months has already been answered on stack overflow here: Find number of months between two Dates in Ruby on Rails
so I'll use that as a reference to get the months. Then it's just a matter of addition and subtraction
from_date = Date.new(2019,4,5)
to_date = Date.new(2019,12,20)
num_months = (12*(to_date.year-from_date.year))+(to_date.month-from_date.month)
# We add 1 to make it inclusive, otherwise you get 255
num_days = (num_months*30) + to_date.day - from_date.day + 1
def days_from_zero(date_str)
d, m, y = date_str.split('/').map(&:to_i)
d + 30*(m + 12*y)
end
days_from_zero("05/04/2020") - days_from_zero("4/04/2020") #=> 1
days_from_zero("20/12/2020") - days_from_zero("05/04/2020") #=> 255
days_from_zero("05/04/2020") - days_from_zero("20/12/2020") #=> -255
days_from_zero("05/04/2020") - days_from_zero("3/6/20") #=> 719942

How do I compare month-year combinations in Ruby?

I’m using Rails 4.2.7. I have two pairs of numbers …
month1 # A number between 1 and 12
year1 # a four digit year
month2 # A number between 1 and 12
year2 # A four digit year
How do I write a comparison expression to determine if the “month2-year2” combination is greater than or equal to the “month1-year1” combination? For instance if month2 = 1 and year2 = 2017 and month1 = 12 and year1 = 2016, the month2-year2 combination is greater than the month1-year1 combination.
month1, month2, year1, year2 = 12, 1, 2016, 2017
=> [12, 1, 2016, 2017]
Time.new(year1, month1) >= Time.new(year2, month2)
=> false
Time.new(year2, month2) >= Time.new(year1, month1)
=> true
reference: https://ruby-doc.org/core-2.2.0/Time.html#class-Time-label-Creating+a+new+Time+instance
It's pretty easy and there's no need to create date or time objects.
def first_smaller?(ym1, ym2)
(ym1 <=> ym2) == -1
end
first_smaller? [2016,12], [2017,1]
#=> true
first_smaller? [2017,1], [2016,12]
#=> false
first_smaller? [2017,1], [2017,1]
#=> false
See the third paragraph of the doc for Array#<=> to see how Ruby orders arrays.
If you also wish to know if the two arrays are equal, you could write something like the following:
def ordering(ym1, ym2)
case ym1 <=> ym2
end
which returns -1 if ym1 is smaller, +1 if ym2 is smaller or 0 if the arrays are equal.

DateTime subtraction in ruby 2?

I need to subtract two DateTime objects in order to find out the difference in hours between them.
I try to do the following:
a = DateTime.new(2015, 6, 20, 16)
b = DateTime.new(2015, 6, 21, 16)
puts a - b
I get (-1/1), the object of class Rational.
So, the question is, how do I find out what the difference betweent the two dates is? In hours or days, or whatever.
And what does this Rational mean/represent when I subtract DateTimes just like that?
BTW:
When I try to subtract DateTime's with the difference of 1 year, I get (366/1), so when I do (366/1).to_i, I get the number of days. But when I tried subtracting two DateTime's with the difference of 1 hour, it gave me -1, the number of hours. So, how do I also find out the meaning of the returned value (hours, days, years, seconds)?
When you substract two datetimes, you'll get the difference in days, not hours.
You get a Rational type for the precision (some float numbers cannot be expressed exactly with computers)
To get a number of hours, multiply the result by 24, for minutes multiply by 24*60 etc...
a = DateTime.new(2015, 6, 20, 16)
b = DateTime.new(2015, 6, 21, 16)
(a - b).to_i
# days
# => -1
((a - b)* 24).to_i
# hours
# => -24
# ...
Here's a link to the official doc
If you do subtraction on them as a Time object it will return the result in seconds and then you can multiply accordingly to get minutes/hours/days/whatever.
a = DateTime.new(2015, 6, 20, 16)
b = DateTime.new(2015, 6, 21, 16)
diff = b.to_time - a.to_time # 86400
hours = diff / 60 / 60 # 24

how do I add N days to time T (accounting for Daylight Savings Time)?

I have a Time object T. What's a reasonable way to add N days to T?
The best I've come up with feels somewhat tortured:
require 'date'
def add_days(time, days)
time.to_date.next_day(days).to_time
end
P.S.: If you are in the US, a correct answer must satisfy:
add_days(Time.new(2013, 3, 10, 0), 1) == Time.new(2013, 3, 11, 0)
and if you are in the EU, a correct answer must satisfy:
add_days(Time.new(2013, 3, 31, 0), 1) == Time.new(2013, 4, 1, 0)
P.P.S: This is a Ruby question, not a Rails question.
Time has a + method which accepts seconds.
N = 3
t = Time.now + N * 86400 # 24 * 60 * 60
Or, if you bring ActiveSupport in, it's easier
require 'active_support/core_ext'
t = Time.now + N.days
You can obviously make your own helper
class Fixnum
def days
self * 86400
end
end
t = Time.now # => 2013-01-31 16:06:31 +0700
t + 3.days # => 2013-02-03 16:06:31 +0700
ActiveSupport::TimeWithZone seems to handle this well
> t1 = ActiveSupport::TimeZone['Eastern Time (US & Canada)'].parse('2013-03-10')
=> Sun, 10 Mar 2013 00:00:00 EST -05:00
Notice the class type below:
> t1.class
=> ActiveSupport::TimeWithZone
Notice the change from EST above to EDT below:
> t1 + 1.day
=> Mon, 11 Mar 2013 00:00:00 EDT -04:00
As appears to have become my style, I am answering my own question.
Since the transition across DST / ST is rather rare (and in many parts of the world, nonexistent), a more efficient approach is to first add (n_days * 24 * 60 * 60) seconds and then check if the UTC offset has changed. If it has, then create a corrected time object.
Like this:
def add_days(time, n_days)
t2 = time + (n_days * 24 * 60 * 60)
utc_delta = time.utc_offset - t2.utc_offset
(utc_delta == 0) ? t2 : t2 + utc_delta
end
This approach and avoids a lot of extra object creation, and handles transitions across Daylight Savings properly (at least in my current time zone, Pacific Time):
>> t1 = Time.new(2013, 3, 10, 0, 0, 0)
=> 2013-03-10 00:00:00 -0800 # midnight Mar 3, 2013 Pacific Standard Time
>> t2 = add_days(t1, 1)
=> 2013-03-11 00:00:00 -0700 # midnight Mar 4, 2013 Pacific Daylight Time
>> t2 - t1
=> 82800.0 # a shorter than usual day
>> u1 = Time.new(2013, 11, 3, 0, 0, 0)
=> 2013-11-03 00:00:00 -0700 # midnight Nov 3, 2013 Pacific Daylight Time
>> u2 = add_days(u1, 1)
=> 2013-11-04 00:00:00 -0800 # midnight Nov 4, 2013 Pacific Standard Time
>> u2 - u1
=> 90000.0 # a longer than usual day
This is somewhat of a lateral answer but because in your original question you weren't concerned about the HMS section of Time, wouldn't you be better off using Date objects instead?
require 'date'
t=Time.now
d=Date.parse(t.to_s)
puts d+1 # => gives you tomorrow's day (YMD)
Edit: Added require 'date' to improve answer comprehensiveness, as pointed out in the comments section.

Leap year calculation

In order to find leap years, why must the year be indivisible by 100 and divisible by 400?
I understand why it must be divisible by 4. Please explain the algorithm.
The length of a year is (more or less) 365.242196 days.
So we have to subtract, more or less, a quarter of a day to make it fit :
365.242196 - 0.25 = 364.992196 (by adding 1 day in 4 years) : but oops, now it's too small!! lets add a hundreth of a day (by not adding that day once in a hundred year :-))
364.992196 + 0,01 = 365.002196 (oops, a bit too big, let's add that day anyway one time in about 400 years)
365.002196 - 1/400 = 364.999696
Almost there now, just play with leapseconds now and then, and you're set.
(Note : the reason no more corrections are applied after this step is because a year also CHANGES IN LENGTH!!, that's why leapseconds are the most flexible solution, see for examlple here)
That's why i guess
There's an algorithm on wikipedia to determine leap years:
function isLeapYear (year):
if ((year modulo 4 is 0) and (year modulo 100 is not 0))
or (year modulo 400 is 0)
then true
else false
There's a lot of information about this topic on the wikipedia page about leap years, inclusive information about different calendars.
In general terms the algorithm for calculating a leap year is as follows...
A year will be a leap year if it is divisible by 4 but not by 100. If a year is divisible by 4 and by 100, it is not a leap year unless it is also divisible by 400.
Thus years such as 1996, 1992, 1988 and so on are leap years because they are divisible by 4 but not by 100. For century years, the 400 rule is important. Thus, century years 1900, 1800 and 1700 while all still divisible by 4 are also exactly divisible by 100. As they are not further divisible by 400, they are not leap years
this is enough to check if a year is a leap year.
if( (year%400==0 || year%100!=0) &&(year%4==0))
cout<<"It is a leap year";
else
cout<<"It is not a leap year";
a) The year is 365.242199 days.
b) If every year was 365 days, in 100 years we would lose 24.2199 days.
That's why we add 24 days per century (every 4 years EXCEPT when divisible by 100)
c) But still we lose 0.21299 days/century. So in 4 centuries we lose 0.8796 days.
That's why we add 1 day per 4 centuries (every fourth century we DO count a leap year).
d) But that means we lose -0.1204 days (we go forward) per quadricentennial (4 centuries). So in 8 quadricentennial (3200 years) we DO NOT count a leap year.
e) But that means we lose 0.0368 days per 3200 years. So in 24x3200 years (=76800years) we lose 0.8832 days. That's why we DO count a leap year.
and so on... (by then we will have destroyed the planet, so it doesn't matter)
What I cannot understand though, is why we don't count a leap year every 500 years instead of 400. In that way we would converge more rapidly to the correct time (we would lose 2.3 hours/500 years).
I'm sure Wikipedia can explain it better than I can, but it is basically to do with the fact that if you added an extra day every four years we'd get ahead of the sun as its time to orbit the sun is less than 365.25 days so we compensate for this by not adding leap days on years that are not divisible by 400 eg 1900.
Hope that helps
Here is a simple implementation of the wikipedia algorithm, using the javascript ternary operator:
isLeapYear = (year % 100 === 0) ? (year % 400 === 0) : (year % 4 === 0);
Return true if the input year is a leap year
Basic modern day code:
If year mod 4 = 0, then leap year
if year mod 100 then normal year
if year mod 400 then leap year
else normal year
Todays rule started 1582 AD
Julian calendar rule with every 4th year started 46BC but is not coherent before 10 AD as declared by Cesar.
They did however add some leap years every 3rd year now and then in the years before:
Leap years were therefore 45 BC, 42 BC, 39 BC, 36 BC, 33 BC, 30 BC, 27 BC, 24 BC, 21 BC, 18 BC, 15 BC, 12 BC, 9 BC, 8 AD, 12 AD
Before year 45BC leap year was not added.
The year 0 do not exist as it is ...2BC 1BC 1AD 2AD... for some calculation this can be an issue.
function isLeapYear(year: Integer): Boolean;
begin
result := false;
if year > 1582 then // Todays calendar rule was started in year 1582
result := ((year mod 4 = 0) and (not(year mod 100 = 0))) or (year mod 400 = 0)
else if year > 10 then // Between year 10 and year 1582 every 4th year was a leap year
result := year mod 4 = 0
else //Between year -45 and year 10 only certain years was leap year, every 3rd year but the entire time
case year of
-45, -42, -39, -36, -33, -30, -27, -24, -21, -18, -15, -12, -9:
result := true;
end;
end;
You really should try to google first.
Wikipedia has a explanation of leap years. The algorithm your describing is for the Proleptic Gregorian calendar.
More about the math around it can be found in the article Calendar Algorithms (PDF).
Will it not be much better if we make one step further.
Assuming every 3200 year as no leap year,
the length of the year will come
364.999696 + 1/3200 = 364.999696 + .0003125 = 365.0000085
and after this the adjustment will be required after around 120000 years.
In Java Below code calculates leap year count between two given year. Determine starting and ending point of the loop.
Then if parameter modulo 4 is equal 0 and parameter modulo 100 not equal 0 or parameter modulo 400 equal zero then it is leap year and increase counter.
static int calculateLeapYearCount(int year, int startingYear) {
int min = Math.min(year, startingYear);
int max = Math.max(year, startingYear);
int counter = 0;
for (int i = min; i < max; i++) {
if ((i % 4 == 0 && i % 100 != 0) || i % 400 == 0) {
counter = counter + 1;
}
}
return counter;
}
PHP:
// is number of days in the year 366? (php days of year is 0 based)
return ((int)date('z', strtotime('Dec 31')) === 365);
Leap years are arbitrary, and the system used to describe them is a man made construct. There is no why.
What I mean is there could have been a leap year every 28 years and we would have an extra week in those leap years ... but the powers that be decided to make it a day every 4 years to catch up.
It also has to do with the earth taking a pesky 365.25 days to go round the sun etc. Of course it isn't really 365.25 is it slightly less (365.242222...), so to correct for this discrepancy they decided drop the leap years that are divisible by 100.
If you're interested in the reasons for these rules, it's because the time it takes the earth to make exactly one orbit around the sun is a long imprecise decimal value. It's not exactly 365.25. It's slightly less than 365.25, so every 100 years, one leap day must be eliminated (365.25 - 0.01 = 365.24). But that's not exactly correct either. The value is slightly larger than 365.24. So only 3 out of 4 times will the 100 year rule apply (or in other words, add back in 1 day every 400 years; 365.25 - 0.01 + 0.0025 = 365.2425).
There are on average, roughly 365.2425 days in a year at the moment (the Earth is slowing down but let's ignore that for now).
The reason we have leap years every 4 years is because that gets us to 365.25 on average [(365+365+365+366) / 4 = 365.25, 1461 days in 4 years].
The reason we don't have leap years on the 100-multiples is to get us to 365.24 `[(1461 x 25 - 1) / 100 = 365.24, 36,524 days in 100 years.
Then the reason we once again have a leap year on 400-multiples is to get us to 365.2425 [(36,524 x 4 + 1) / 400 = 365.2425, 146,097 days in 400 years].
I believe there may be another rule at 3600-multiples but I've never coded for it (Y2K was one thing but planning for one and a half thousand years into the future is not necessary in my opinion - keep in mind I've been wrong before).
So, the rules are, in decreasing priority:
multiple of 400 is a leap year.
multiple of 100 is not a leap year.
multiple of 4 is a leap year.
anything else is not a leap year.
Here comes a rather obsqure idea.
When every year dividable with 100 gets 365 days, what shall be done at this time? In the far future, when even years dividable with 400 only can get 365 days.
Then there is a possibility or reason to make corrections in years dividable with 80.
Normal years will have 365 day and those dividable with 400 can get 366 days. Or is this a loose-loose situation.
You could just check if the Year number is divisible by both 4 and 400. You dont really need to check if it is indivisible by 100. The reason 400 comes into question is because according to the Gregorian Calendar, our "day length" is slightly off, and thus to compensate that, we have 303 regular years (365 days each) and 97 leap years (366 days each). The difference of those 3 extra years that are not leap years is to stay in cycle with the Gregorian calendar, which repeats every 400 years. Look up Christian Zeller's congruence equation. It will help understanding the real reason. Hope this helps :)
In the Gregorian calendar 3 criteria must be taken into account to identify leap years:
The year is evenly divisible by 4;
If the year can be evenly divided by 100, it is NOT a leap year, unless;
The year is also evenly divisible by 400. Then it is a leap year. Why the year divided by 100 is not leap year
Python 3.5
def is_leap_baby(year):
if ((year % 4 is 0) and (year % 100 is not 0)) or (year % 400 is 0):
return "{0}, {1} is a leap year".format(True, year)
return "{0} is not a leap year".format(year)
print(is_leap_baby(2014))
print(is_leap_baby(2012))
Simply
Because year 2000 is a leap year and it is divisible by 100 and dividable by 4.
SO to guarantee it is correct leap we need to ensure it is divisible by 400.
2000 % 4 = 0
2000 % 100 = 0
According to algorithm it's not leap, but it is dividable by 400
2000 % 400 = 0
so it is leap.
I found this problem in the book "Illustrated Guide to Python 3". It was in a very early chapter that only discussed the math operations, no loops, no comparisons, no conditionals. How can you tell if a given year is a leap year?
Below is what I came up with:
y = y % 400
a = y % 4
b = y % 100
c = y // 100
ly = (0**a) * ((1-(0**b)) + 0**c) # ly is not zero for leap years, else 0
This is the most efficient way, I think.
Python:
def leap(n):
if n % 100 == 0:
n = n / 100
return n % 4 == 0
C# implementation
public bool LeapYear()
{
int year = 2016;
return year % 4 == 0 && year % 100 != 0 || year % 400 == 0 ;
}
From 1700 to 1917, official calendar was the Julian calendar. Since then they we use the Gregorian calendar system. The transition from the Julian to Gregorian calendar system occurred in 1918, when the next day after January 31st was February 14th. This means that 32nd day in 1918, was the February 14th.
In both calendar systems, February is the only month with a variable amount of days, it has 29 days during a leap year, and 28 days during all other years. In the Julian calendar, leap years are divisible by 4 while in the Gregorian calendar, leap years are either of the following:
Divisible by 400.
Divisible by 4 and not divisible by 100.
So the program for leap year will be:
Python:
def leap_notleap(year):
yr = ''
if year <= 1917:
if year % 4 == 0:
yr = 'leap'
else:
yr = 'not leap'
elif year >= 1919:
if (year % 400 == 0) or (year % 4 == 0 and year % 100 != 0):
yr = 'leap'
else:
yr = 'not leap'
else:
yr = 'none actually, since feb had only 14 days'
return yr
In shell you can use cal -j YYYY which prints the julian day of the year, If the last julian day is 366, then it is a leap year.
$ function check_leap_year
{
year=$1
if [ `cal -j $year | awk 'NF>0' | awk 'END { print $NF } '` -eq 366 ];
then
echo "$year -> Leap Year";
else
echo "$year -> Normal Year" ;
fi
}
$ check_leap_year 1900
1900 -> Normal Year
$ check_leap_year 2000
2000 -> Leap Year
$ check_leap_year 2001
2001 -> Normal Year
$ check_leap_year 2020
2020 -> Leap Year
$
Using awk, you can do
$ awk -v year=1900 ' BEGIN { jul=strftime("%j",mktime(year " 12 31 0 0 0 ")); print jul } '
365
$ awk -v year=2000 ' BEGIN { jul=strftime("%j",mktime(year " 12 31 0 0 0 ")); print jul } '
366
$ awk -v year=2001 ' BEGIN { jul=strftime("%j",mktime(year " 12 31 0 0 0 ")); print jul } '
365
$ awk -v year=2020 ' BEGIN { jul=strftime("%j",mktime(year " 12 31 0 0 0 ")); print jul } '
366
$
BIS will be 1 if the year is leap, otherwise 0 in this boolean logic:
BIS = A MOD 4=0 - (A MOD 100=0 AND A>1600) + (A MOD 400=0 AND A>1600)
just wrote this in Coffee-Script:
is_leap_year = ( year ) ->
assert isa_integer year
return true if year % 400 == 0
return false if year % 100 == 0
return true if year % 4 == 0
return false
# parseInt? that's not even a word.
# Let's rewrite that using real language:
integer = parseInt
isa_number = ( x ) ->
return Object.prototype.toString.call( x ) == '[object Number]' and not isNaN( x )
isa_integer = ( x ) ->
return ( isa_number x ) and ( x == integer( x ) )
of course, the validity checking done here goes a little further than what was asked for, but i find it a necessary thing to do in good programming.
note that the return values of this function indicate leap years in the so-called proleptic gregorian calendar, so for the year 1400 it indicates false, whereas in fact that year was a leap year, according to the then-used julian calendar. i will still leave it as such in the datetime library i'm writing because writing correct code to deal with dates quickly gets surprisingly involved, so i will only ever support the gregorian calendar (or get paid for another one).

Resources