Recent

Author Topic: YearsBetween Problem - Wrong answer!  (Read 16618 times)

Bart

  • Hero Member
  • *****
  • Posts: 5610
    • Bart en Mariska's Webstek
Re: YearsBetween Problem - Wrong answer!
« Reply #15 on: January 15, 2017, 05:25:57 pm »
Comparing bylaardt's code with wp's:

Code: [Select]
12-1-2014 9:00:00 --> 12-1-2016 8:00:00
  expected: 1
  WP      : 1
  BY      : 2 <--- ERROR

Note: both codes can return negative values, where YearsBetween (currently) returns only positive values.

Here's a dumbass approach:

Code: [Select]
function BB_YearsBetween(DateNow, DateThen: TDateTime): Integer;
var
  yt, mt, dt: Word;
  yn, mn, dn, dummy: Word;
  TempD: TDateTime;
begin
  if (DateNow < -DateDelta) or (DateThen < -DateDelta) then
  begin
    //DecodeDateTime will return all zero's!
    Result := YearsBetween(DateNow, DateThen);
    Exit;
  end;
  if DateThen < DateNow then
  begin
    TempD := DateNow;
    DateNow := DateThen;
    DateThen := TempD;
  end;
  DecodeDate(DateThen, yt, mt, dt);
  DecodeDate(DateNow, yn, mn, dn);
  Result := yt - yn;
  if (mn > mt) then
  begin
    Dec(Result);
  end
  else
  begin
    if (mn = mt) then
    begin
      if (dn > dt) then
      begin
        Dec(Result)
      end
      else
      begin
        if (dn = dt) then
        begin
          if (Frac(DateNow) > Frac(DateThen)) then
          begin
            Dec(Result);
          end;
        end;
      end;
    end;
  end;
end;

Bart

wp

  • Hero Member
  • *****
  • Posts: 13195
Re: YearsBetween Problem - Wrong answer!
« Reply #16 on: January 15, 2017, 06:04:57 pm »
Quote
So back to my original thought, if it works correctly except only on identical days/month combinations then can't I just flag those combinations with a warning message?
On identical days/month combinations technically the year has passed, so you can add one to the original date and it will work fine. More over one won't go to pension exactly on his birthday(end of month/end of week perhaps?).
In principle adding 1 day to a 365-day time period makes a quotient of 366/365.25 > 1 --> after truncation we have 1 year. Even adding 1 day to a 364-day period (1 full year - 1 day) returns the correct number because after adding 1 day we have the quotient (364+1) / 365.25 = 365 / 365.25 < 1 --> 0 year.

The only exception is a full leap year minus 1 day, for exacmple a period starting on Jan 2 of a leap year (e.g. 2016) and ending on Jan 1 of the next year. Use Excel (or fpc) to verify that this period covers 365 days. But it is not a full year - a full leap year would have 366 days. Now after adding 1 day we would have the quotient (365 +1) / 365.25 > 1 --> 1 year in contrast to expectation.

Here's another solution directly from dateutils: PeriodBetween - it returns the number of full years, months, and days between two date:
Code: Pascal  [Select][+][-]
  1.   function MyYearsBetweenY(ANow, AThen: TDateTime): Integer;
  2.   var
  3.     y, m, d: Word;
  4.   begin
  5.     PeriodBetween(ANow, AThen, y, m, d);
  6.     Result := y;
  7.   end;
It is correct for all cases that I tested, back into 18th century. The only point one could talk about is a time period starting at Feb 29 of a leap year and ending on Feb 28 next year. It is a matter of definition: does this year end on Feb 28 or March 1? If it ends on Feb 28 (which seems to be wrong to me anyway) then the function returns a false value (0 years instead of 1). If it ends on March 1 then even this borderline case is handled correctly.
« Last Edit: January 15, 2017, 06:33:32 pm by wp »

bylaardt

  • Sr. Member
  • ****
  • Posts: 310
Re: YearsBetween Problem - Wrong answer!
« Reply #17 on: January 15, 2017, 06:24:56 pm »
@bart
You are right, if compares with fracs, but frac wasn't included on results.

sorry, my mistake!

new version:
 
Code: Pascal  [Select][+][-]
  1. function CorrectYearsBetween(DateNow, DateThen: TDateTime): Integer;
  2. var
  3.   yt, mt, dt: Word;
  4.   yn, mn, dn: Word;
  5. begin
  6.   DecodeDate(DateThen, yt,mt,dt);
  7.   DecodeDate(DateNow, yn, mn, dn);
  8.   if (mt*31+dt+frac(DateThen)<mn*31+dn+frac(DateNow))xor(datethen<datenow) then
  9.     result:=yt-yn+Sign((mt-mn)*31+dt+frac(DateThen)-dn-frac(DateNow))  //<-- this line was changed
  10.   else
  11.     result:=yt-yn
  12. end;
  13.  

wp

  • Hero Member
  • *****
  • Posts: 13195
Re: YearsBetween Problem - Wrong answer!
« Reply #18 on: January 15, 2017, 06:46:27 pm »
If I use 12/01/2016 and 12/01/1956 it gives the correct answer as 60. If I use 12/01/2015 and 12/01/1956 it is giving me 58 instead of 59.
Can anybody confirm the observation of the OP that YearsBetween(12/01/2015, 12/01/1956) returns 58 instead of 59? I can't (Laz trunk, fpc 3.0, Win10)

Bart

  • Hero Member
  • *****
  • Posts: 5610
    • Bart en Mariska's Webstek
Re: YearsBetween Problem - Wrong answer!
« Reply #19 on: January 15, 2017, 06:49:16 pm »
Here's another solution directly from dateutils: PeriodBetween - it returns the number of full years, months, and days between two date:
Code: Pascal  [Select][+][-]
  1.   function MyYearsBetweenY(ANow, AThen: TDateTime): Integer;
  2.   var
  3.     y, m, d: Word;
  4.   begin
  5.     PeriodBetween(ANow, AThen, y, m, d);
  6.     Result := y;
  7.   end;

That does not correctly handle dates before 1899-12-30, nor does it take Time into account AFAICS.

Bart
« Last Edit: January 15, 2017, 06:59:08 pm by Bart »

Bart

  • Hero Member
  • *****
  • Posts: 5610
    • Bart en Mariska's Webstek
Re: YearsBetween Problem - Wrong answer!
« Reply #20 on: January 15, 2017, 06:52:29 pm »
Can anybody confirm the observation of the OP that YearsBetween(12/01/2015, 12/01/1956) returns 58 instead of 59? I can't (Laz trunk, fpc 3.0, Win10)

On Win32, fcp versions 2.6.4, 3.0.0, 3.0.2RC1 and trunk all return 59.

Bart

bylaardt

  • Sr. Member
  • ****
  • Posts: 310
Re: YearsBetween Problem - Wrong answer!
« Reply #21 on: January 15, 2017, 06:57:06 pm »
@wp

FPC 3.0 returns 58.  (linux 64)

@bart
'12/01/1958' can be confuse
i tested "01 dec 1958"...
"12 jan 1958"... returns 59.

balazsszekely

  • Guest
Re: YearsBetween Problem - Wrong answer!
« Reply #22 on: January 15, 2017, 06:59:40 pm »
@wp
Yes! I can confirm that: Win 7(32 bit)/Lazarus trunk/ FPC 3.0.0).

@Bart
Quote
That does not correctly handle dates before 1899-12-30.
The OP explicitly stated he's not interested in dates > 100 years.

Bart

  • Hero Member
  • *****
  • Posts: 5610
    • Bart en Mariska's Webstek
Re: YearsBetween Problem - Wrong answer!
« Reply #23 on: January 15, 2017, 07:06:20 pm »
@bart
'12/01/1958' can be confuse
i tested "01 dec 1958"...
"12 jan 1958"... returns 59.

I used this code:
Code: [Select]
  D1 := (EncodeDate(1956, 1, 12));
  D2 := (EncodeDate(2015, 1, 12));
  writeln('FPC: ',YearsBetween(D1, D2));

This:
Code: [Select]
  D1 := (EncodeDate(1956, 12, 1));
  D2 := (EncodeDate(2015, 12, 1));

actually returns 58 for fpc's implementation, 59 for all of our codes.

bart

Bart

  • Hero Member
  • *****
  • Posts: 5610
    • Bart en Mariska's Webstek
Re: YearsBetween Problem - Wrong answer!
« Reply #24 on: January 15, 2017, 07:14:06 pm »
Code: [Select]
  D1 := (EncodeDate(1956, 12, 1));
  D2 := (EncodeDate(2015, 12, 1)) + EncodeTime(1,0,0,0);

  writeln('FPC: ',YearsBetween(D1, D2));
  writeln('WP : ',WP_YearsBetween(D1,D2));
  writeln('WP2: ',WP2_YearsBetween(D1,D2));
  writeln('BB : ',BB_YearsBetween(D1, D2));
  writeln('BY : ',BY_YearsBetween(D1, D2));       

Gives:

Code: [Select]
FPC: 58
WP : 60
WP2: 59
BB : 59
BY : 59

Where WP2 is the code using PeriodBetween.
So WP's original code is off by one in the opposite direction from FPC in this case.

Bart

Bart

  • Hero Member
  • *****
  • Posts: 5610
    • Bart en Mariska's Webstek
Re: YearsBetween Problem - Wrong answer!
« Reply #25 on: January 15, 2017, 07:16:56 pm »
Code: [Select]
  D1 := (EncodeDate(1956, 12, 1)) + EncodeTime(1,0,0,0); //1 hour short of 59 years
  D2 := (EncodeDate(2015, 12, 1));

  writeln('FPC: ',YearsBetween(D1, D2));
  writeln('WP : ',WP_YearsBetween(D1,D2));
  writeln('WP2: ',WP2_YearsBetween(D1,D2));
  writeln('BB : ',BB_YearsBetween(D1, D2));
  writeln('BY : ',BY_YearsBetween(D1, D2));

Gives:

Code: [Select]
FPC: 58
WP : 59
WP2: 59
BB : 58
BY : 59

58 is the correct answer IMO here.

Bart

Bart

  • Hero Member
  • *****
  • Posts: 5610
    • Bart en Mariska's Webstek
Re: YearsBetween Problem - Wrong answer!
« Reply #26 on: January 15, 2017, 07:40:17 pm »
Submitted as Issue #31233.

Bart

Fungus

  • Sr. Member
  • ****
  • Posts: 354
Re: YearsBetween Problem - Wrong answer!
« Reply #27 on: January 15, 2017, 07:47:08 pm »
The years between the date-times 01-01-2015 00:00:00 and 01-01-2016 00:00:00 is mathematically one second shy of being one. No one would probably argue with the fact that on 01-01-2016 a year has elapsed since 01-01-2015 - it's all a question about how math not always work as minds :)

Considering a previous post: http://forum.lazarus.freepascal.org/index.php/topic,35329.msg233332.html#msg233332 you could create the following "mind oriented" calculation:

Code: Pascal  [Select][+][-]
  1. Function DateToDaysSince0(Const Y, M, D: Word; Const MindLeapYears: Boolean = True): Int64;
  2. Const cDaysInMonth : Array[1..12] Of Byte = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
  3. Var I: Integer;
  4. Begin
  5.  
  6.   //First we need to convert years into days minding the extra day in leap years
  7.   If Not MindLeapYears Then Result:= Y * 365
  8.   Else Result:= (Y * 365) + (Y Div 4) - (Y Div 100) + (Y Div 400);
  9.  
  10.   //Then we must add the days of the months passed, not including current month
  11.   For I:= 1 To M - 1 Do Result:= Result + cDaysInMonth[I];
  12.  
  13.   //If we are in leap year and feb 29 has passed, add a day
  14.  
  15.   If MindLeapYears And ( (M > 2) And ( (Y Mod 400 = 0) Or ((Y Mod 4 = 0) And (Y Mod 100 <> 0)) ) ) Then Inc(Result);
  16.  
  17.   //Add days
  18.   Result:= Result + D;
  19.  
  20. End;
  21.  
  22. Function Years_Between(Const DateFrom, DateTo: TDateTime): Integer;
  23.  
  24.     Function ToDays(Const Date: TDateTime): Int64;
  25.     Var D, M, Y: Word;
  26.     Begin
  27.       DecodeDate(Date, Y, M, D);
  28.       Result:= DateToDaysSince0(Y, M, D, False); //We do not mind leap years, since the extra day does not matter
  29.     End;
  30.  
  31. Begin
  32.   Result:= (ToDays(DateTo) - ToDays(DateFrom)) Div 365;
  33. End;

This should work for all dates since the elusive year 0 :)
« Last Edit: January 15, 2017, 07:49:44 pm by Fungus »

wp

  • Hero Member
  • *****
  • Posts: 13195
Re: YearsBetween Problem - Wrong answer!
« Reply #28 on: January 15, 2017, 07:53:59 pm »
Here's another solution directly from dateutils: PeriodBetween - it returns the number of full years, months, and days between two date:
Code: Pascal  [Select][+][-]
  1.   function MyYearsBetweenY(ANow, AThen: TDateTime): Integer;
  2.   var
  3.     y, m, d: Word;
  4.   begin
  5.     PeriodBetween(ANow, AThen, y, m, d);
  6.     Result := y;
  7.   end;

That does not correctly handle dates before 1899-12-30, nor does it take Time into account AFAICS.
Why?
  n := MyYearsBetweenY(EncodeDate(1891, 1, 1), EncodeDate(1890, 1, 1));  --> 1 as expected

Time is ignored in the original YearsBetween as well.

Bart

  • Hero Member
  • *****
  • Posts: 5610
    • Bart en Mariska's Webstek
Re: YearsBetween Problem - Wrong answer!
« Reply #29 on: January 15, 2017, 09:59:31 pm »
Why?
  n := MyYearsBetweenY(EncodeDate(1891, 1, 1), EncodeDate(1890, 1, 1));  --> 1 as expected

You are right.
It does not handle dates before 0001-01-01 correctly (because DecodeDate will return 0000-00-00 in such a case).
Your original code will crash on such a date.

Time is ignored in the original YearsBetween as well.

Ty it with Date := -697243.0 (10 years before 01-01-01) and 2017-01-01.
It will return 2017, where it whould well be over 2017 (2026, 2027 or 2028: I still get confused about what it should be).
FPC will return 2025, as does Delphi.

Yes it is, and maybe this is by design, but you brouht up the time issue (or am I mistaken?).

Bart

 

TinyPortal © 2005-2018