Calculate accurate differences between date/times

Share your advanced PureBasic knowledge/code with the community.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8425
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Calculate accurate differences between date/times

Post by netmaestro »

Code updated for 5.20+

I originally posted a version of this yesterday that seemed to work fine, until I noticed some problems. I fixed them, or so I thought, only to discover that more problems existed. It was so unreliable and my attempts at fixing it were so stupid I thought it best to take it down for the time being and see if I could get if working. I damn near despaired, I don't mind admitting. However, a minor brainwave washed over me this morning and I came up with a better approach. After a fair bit of testing I think I have something that can be relied upon to be accurate. If someone can break this, please do and post the results:

Code: Select all

;=================================================== 
; Program:          DateDiff library function 
; Author:           netmaestro 
; Date:             December 20, 2006 
; Target OS:        Windows All 
; Target Compiler:  PureBasic 4.0 
; License:          Free, unrestricted, credit 
;                   appreciated but not required 
;=================================================== 

Structure TimeDiff 
  totaldays.l
  years.l 
  months.l 
  daysremaining.l 
  hours.l 
  minutes.l 
  seconds.l 
EndStructure 

Procedure DateDiff(dateearly, datelate, *diff.TimeDiff) 
  
  Protected totaldays,years,months,daysremaining,hours,minutes,seconds 
  
  curdate = dateearly 
  testdate = dateearly 
  startday = Day(dateearly) 
  totaldays = 0
  daysremaining = 0 
  
  While testdate <= datelate 
    testdate = AddDate(curdate, #PB_Date_Day, 1) 
    If testdate <= datelate 
      curdate = testdate 
      totaldays+1 
      daysremaining+1
      If Day(curdate) = startday 
        months+1 
        daysremaining=0 
      EndIf 
    EndIf 
  Wend 
  
  testdate = curdate 
  hours = 0 
  While testdate<datelate 
    testdate = AddDate(curdate, #PB_Date_Hour, 1) 
    If testdate <= datelate 
      curdate = testdate 
      hours+1 
    EndIf 
  Wend 
  
  testdate = curdate 
  minutes = 0 
  While testdate<datelate 
    testdate = AddDate(curdate, #PB_Date_Minute, 1) 
    If testdate <= datelate 
      curdate = testdate 
      minutes+1 
    EndIf 
  Wend 
  
  testdate = curdate 
  seconds = 0 
  While testdate<datelate 
    testdate = AddDate(curdate, #PB_Date_Second, 1) 
    If testdate <= datelate 
      curdate = testdate 
      seconds+1 
    EndIf 
  Wend 
  
  years = months/12 
  If years 
    months % 12 
  EndIf 
  
  *diff\totaldays = totaldays
  *diff\years = years 
  *diff\months = months 
  *diff\daysremaining = daysremaining 
  *diff\hours = hours 
  *diff\minutes = minutes 
  *diff\seconds = seconds 
  
EndProcedure 

dateearly = ParseDate("%yyyy/%mm/%dd/%hh:%ii:%ss", "2005/9/9/12:30:00") 
datelate = Date()

MyDiff.TimeDiff 
DateDiff(dateearly,datelate,@MyDiff) 

Debug "Total Days: "+Str(MyDiff\totaldays)
Debug "Years: "+Str(MyDiff\years) 
Debug "Months: "+Str(MyDiff\months) 
Debug "Days: "+Str(MyDiff\daysremaining) 
Debug "Hours: "+Str(MyDiff\hours) 
Debug "Minutes: "+Str(MyDiff\minutes) 
Debug "Seconds: "+Str(MyDiff\seconds) 
BERESHEIT
Tranquil
Addict
Addict
Posts: 949
Joined: Mon Apr 28, 2003 2:22 pm
Location: Europe

Post by Tranquil »

I thought about this methode too but there must be a way without doing it in loops or?
Tranquil
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8425
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

Well, you can always fall back on the good old windows scripting host. VBScript has a native DateDiff function and you can build a command string, save it to a file and execute it with RunProgram. There's an example of using it to solve an expression here:

http://www.purebasic.fr/english/viewtop ... 57&start=3

It would just be a matter of modifying it to do DateDiff, shouldn't be hard.
BERESHEIT
PB
PureBasic Expert
PureBasic Expert
Posts: 7581
Joined: Fri Apr 25, 2003 5:24 pm

Post by PB »

> there must be a way without doing it in loops

DoubleDutch posted a similar tip in 2005 that doesn't use loops:
http://www.purebasic.fr/english/viewtopic.php?t=18366

@netmaestro: Why doesn't your tip show weeks? :)
I compile using 5.31 (x86) on Win 7 Ultimate (64-bit).
"PureBasic won't be object oriented, period" - Fred.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8425
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

I'm only looping for one reason, and that's to count accurately the number of calendar months that passed between the two dates. Everything up to weeks can be got easily with arithmetic, as their lengths never change. I'm working on a better version of this using a SYSTEMTIME structure which will handle dates before 1970 and can do stuff like tell you "You've been a human being for 62 years, 3 months, 22 days, 4 hours, 27 minutes and 13 seconds"
BERESHEIT
jear
User
User
Posts: 20
Joined: Sun Dec 28, 2003 12:27 am
Location: Ammerland

Post by jear »

What's about this code

Code: Select all

; TimeDiffString : jear Dez 2005

;#TimeUnits = "Woche|n,Tag|e,Stunde|n,Minute|n,Sekunde|n"
#TimeUnits = "week|s,day|s,hour|s,minute|s,second|s"
  
Procedure.s AddTimeUnit(number.l, unit.l) 
  Protected Result.s, sUnit.s
  If number = 0 : ProcedureReturn "" : EndIf
  If number < 0 : number * -1 : EndIf
  sUnit = StringField(#TimeUnits, unit, ",")
  If number > 1
    sUnit = RemoveString(sUnit, "|")
  Else
    sUnit = StringField(sUnit, 1, "|")
  EndIf
  Result + Space(1) + Str(number) + Space(1) + sUnit  
  ProcedureReturn Result 
EndProcedure 

Procedure.s sTimeDiff(Seconds.l) 
  Protected Result.s
  Protected Weeks.l, Days.l, Hours.l, Minutes.l
  Weeks   = Seconds / 604800 : Seconds = Seconds % 604800
  Days    = Seconds / 86400  : Seconds = Seconds % 86400
  Hours   = Seconds / 3600   : Seconds = Seconds % 3600 
  Minutes = Seconds / 60     : Seconds = Seconds % 60 
  Result = AddTimeUnit(Weeks,1) 
  Result + AddTimeUnit(Days,2) : Result + AddTimeUnit(Hours,3) 
  Result + AddTimeUnit(Minutes,4) : Result + AddTimeUnit(Seconds,5) 
  ProcedureReturn Result
EndProcedure

PastDate.l = ParseDate("%dd.%mm.%yyyy", "20.12.2005 12:00")
Debug Date() - PastDate
Debug sTimeDiff(Date() - PastDate)
Delay(2000)
Debug PastDate - Date()
Debug sTimeDiff(PastDate - Date())
rsts
Addict
Addict
Posts: 2736
Joined: Wed Aug 24, 2005 8:39 am
Location: Southwest OH - USA

Post by rsts »

netmaestro wrote:"You've been a human being for 62 years, 3 months, 22 days, 4 hours, 27 minutes and 13 seconds"
Once again, you amaze me. How the heck did you know that?

cheers
rsts
Addict
Addict
Posts: 2736
Joined: Wed Aug 24, 2005 8:39 am
Location: Southwest OH - USA

Re: Calculate accurate differences between date/times

Post by rsts »

If I run the datediff routine above as is, seems OK.

If I change the "test" from
dateearly = ParseDate("%yyyy/%mm/%dd/%hh:%ii:%ss", "2005/9/9/12:30:00")
to
dateearly = ParseDate("%yyyy/%mm/%dd/%hh:%ii:%ss", "2010/12/31/12:30:00") or even
dateearly = ParseDate("%yyyy/%mm/%dd/%hh:%ii:%ss", "2005/10/30/12:30:00")

it appears to fail.

Did anything weird happen on the update to 5.20+ or was it always like this?

cheers
User avatar
Demivec
Addict
Addict
Posts: 4086
Joined: Mon Jul 25, 2005 3:51 pm
Location: Utah, USA

Re: Calculate accurate differences between date/times

Post by Demivec »

rsts wrote:If I run the datediff routine above as is, seems OK.

If I change the "test" from
dateearly = ParseDate("%yyyy/%mm/%dd/%hh:%ii:%ss", "2005/9/9/12:30:00")
to
dateearly = ParseDate("%yyyy/%mm/%dd/%hh:%ii:%ss", "2010/12/31/12:30:00") or even
dateearly = ParseDate("%yyyy/%mm/%dd/%hh:%ii:%ss", "2005/10/30/12:30:00")

it appears to fail.
When I tested, it appears to work correctly for the dates you gave. Why are you saying it fails?
rsts
Addict
Addict
Posts: 2736
Joined: Wed Aug 24, 2005 8:39 am
Location: Southwest OH - USA

Re: Calculate accurate differences between date/times

Post by rsts »

For the original test date I get 8 years 5 months
for the test date of dateearly = ParseDate("%yyyy/%mm/%dd/%hh:%ii:%ss", "2005/10/30/12:30:00")
slightly over 1 month different, I get 7 years 7 months. Or have I gone mad (very possible at this stage :)
User avatar
Demivec
Addict
Addict
Posts: 4086
Joined: Mon Jul 25, 2005 3:51 pm
Location: Utah, USA

Re: Calculate accurate differences between date/times

Post by Demivec »

rsts wrote:For the original test date I get 8 years 5 months
for the test date of dateearly = ParseDate("%yyyy/%mm/%dd/%hh:%ii:%ss", "2005/10/30/12:30:00")
slightly over 1 month different, I get 7 years 7 months. Or have I gone mad (very possible at this stage :)
I see the problem now. When I did my tests I looked solely at the day count, which is correct.

The month count is incorrectly calculated. And the year count is based on the possibly faulty month count.


When I have a moment I will post a solution.
rsts
Addict
Addict
Posts: 2736
Joined: Wed Aug 24, 2005 8:39 am
Location: Southwest OH - USA

Re: Calculate accurate differences between date/times

Post by rsts »

My apologies, Demivec. I should have been clearer as to what the fault was. I was just wondering if the error was introduced in the update to 5.2 or if it had been there all along. I imagine only netmaestro can answer that since I do not have the original source.

Thanks for your assistance in looking at it.

cheers
User avatar
Demivec
Addict
Addict
Posts: 4086
Joined: Mon Jul 25, 2005 3:51 pm
Location: Utah, USA

Re: Calculate accurate differences between date/times

Post by Demivec »

rsts wrote:My apologies, Demivec. I should have been clearer as to what the fault was. I was just wondering if the error was introduced in the update to 5.2 or if it had been there all along. I imagine only netmaestro can answer that since I do not have the original source.

Thanks for your assistance in looking at it.
The error wasn't due to anything that changed in v5.21 LTS. The error was in the original code.

As I said earlier, the error is in how the calendar months are counted. Netmaestro compared the starting calendar day to each sequential day in turn, if the day in the month matched the number of months that occurred thus far was incremented.

Unfortunately not all months are the same length. This means if you started on a day (i.e. 1/31 of any year) and counted 40 more days you still wouldn't come to the day '31' of the next month because February would have only 28 or 29 days. This happens at various places around the calendar year and can make the month count inaccurate if the starting day is the 29th, 30th, or 31st of the month.

Because the year count is based off of the month count it will also be affected.

The end result is that the TotalDays count was correct, as were the hours, minutes, seconds. I believe the day count was also correct.


To correct the error you can change line 37 from:

Code: Select all

If Day(curdate) = startday
to

Code: Select all

If Day(curdate) = startday Or (startday > 28 And Day(curdate) = 1 And daysremaining > 3)
rsts
Addict
Addict
Posts: 2736
Joined: Wed Aug 24, 2005 8:39 am
Location: Southwest OH - USA

Re: Calculate accurate differences between date/times

Post by rsts »

I did not catch that.

Gratitude for your assistance ;)
User avatar
Demivec
Addict
Addict
Posts: 4086
Joined: Mon Jul 25, 2005 3:51 pm
Location: Utah, USA

Re: Calculate accurate differences between date/times

Post by Demivec »

rsts wrote:I did not catch that.

Gratitude for your assistance ;)
Your welcome. :)


I forgot to mention what was involved in the change. To properly count a full month of days the line checks for three additional conditions if the simple case fail. The simple case is if the day in the month is the same as the starting day.

Having failed the simple case, the three additional cases are checked for and all need to be met to mark off another month. These conditions are seeing if the starting day is one of the problematic ones (29th, 30th, or 31st), and if the current day is now day 1 (this signals we went from the end of a possibly shorther month to the start of the next month) and finally, if we have counted more than 3 days since the last time the month count was incremented (this last test makes sure the count won't increment unnecessarily for the first few days after a start day of 29, or 30 if this is a long month with 30 or 31 days).
Post Reply