[AccessD] Date Difference As Completed Years-Months-Days

Gustav Brock Gustav at cactus.dk
Tue Mar 30 12:40:53 CDT 2010


Hi A.D.

I can see I missed your point in my first posting ...

However, to handle the 30/31 days problem (not to say the 28/29 days of February) when counting months and days, I have redesigned my old Months function to work the same way as the Years function:

<code>
Public Function Months( _
  ByVal datDate1 As Date, _
  ByVal datDate2 As Date, _
  Optional ByVal booLinear As Boolean) _
  As Integer

' Returns the difference in full months between datDate1 and datDate2.
'
' Calculates correctly for:
'   negative differences
'   leap years
'   dates of 29. February
'   date/time values with embedded time values
'   negative date/time values (prior to 1899-12-29)
'
' Optionally returns negative counts rounded down to provide a
' linear sequence of month counts.
' For a given datDate1, if datDate2 is decreased stepwise one month from
' returning a positive count to returning a negative count, one or two
' occurrences of count zero will be returned.
' If booLinear is False, the sequence will be:
'   3, 2, 1, 0,  0, -1, -2
' If booLinear is True, the sequence will be:
'   3, 2, 1, 0, -1, -2, -3
'
' If booLinear is False, reversing datDate1 and datDate2 will return
' results of same absolute Value, only the sign will change.
' This behaviour mimics that of Fix().
' If booLinear is True, reversing datDate1 and datDate2 will return
' results where the negative count is offset by -1.
' This behaviour mimics that of Int().

' DateAdd() is used for check for month end of February as it correctly
' returns Feb. 28. when adding a count of months to dates of Feb. 29.
' when the resulting year is a common year.
'
' 2010-03-30. Cactus Data ApS, CPH.

  Dim intDiff   As Integer
  Dim intSign   As Integer
  Dim intMonths As Integer
  
  ' Find difference in calendar months.
  intMonths = DateDiff("m", datDate1, datDate2)
  ' For positive resp. negative intervals, check if the second date
  ' falls before, on, or after the crossing date for a 1 month period
  ' while at the same time correcting for February 29. of leap years.
  If DateDiff("d", datDate1, datDate2) > 0 Then
    intSign = Sgn(DateDiff("d", DateAdd("m", intMonths, datDate1), datDate2))
    intDiff = Abs(intSign < 0)
  Else
    intSign = Sgn(DateDiff("d", DateAdd("m", -intMonths, datDate2), datDate1))
    If intSign <> 0 Then
      ' Offset negative count of months to continuous sequence if requested.
      intDiff = Abs(booLinear)
    End If
    intDiff = intDiff - Abs(intSign < 0)
  End If
  
  ' Return count of months as count of full 1 month periods.
  Months = intMonths - intDiff
  
End Function
</code>

Further, by counting the total months this can easily be converted to full years and remaining months
Now, all that is left is to find the count of remaining days. This is not easy as it will be zero for many date combinations where both dates are at ultimo of a month, for example for both of these intervals:

  From 2015-03-30 to 2016-04-30
  From 2015-03-31 to 2016-04-30

But I've created a function that does this:

<code>
Public Function YearsMonthsDays( _
  ByVal datDate1 As Date, _
  ByVal datDate2 As Date, _
  Optional ByRef lngYears As Long, _
  Optional ByRef lngMonths As Long, _
  Optional ByRef lngDays As Long) _
  As String
  
' Returns the difference in years, months, and days between datDate1 and datDate2.
'
' Calculates correctly for:
'   negative differences
'   leap years
'   dates of 29. February
'   date/time values with embedded time values
'   negative date/time values (prior to 1899-12-29)
'
' Gustav Brock, Cactus Data ApS.
' 2010-03-30.

  ' Count of months in a calendar year.
  Const cintMonths  As Integer = 12
  
  Dim datDateMonth  As Date
  Dim intDays       As Integer
  
  ' No special error handling.
  On Error Resume Next
  
  lngMonths = Months(datDate1, datDate2)
  
  datDateMonth = DateAdd("m", lngMonths, datDate1)
  lngDays = DateDiff("d", datDateMonth, datDate2)
  intDays = Sgn(lngDays)
  If intDays <> 0 Then
    If intDays <> Sgn(DateDiff("d", datDate1, datDate2)) Then
      lngDays = 0
    End If
  End If
  
  lngYears = lngMonths \ cintMonths
  lngMonths = lngMonths Mod cintMonths
  
  YearsMonthsDays = CStr(lngYears) & " year(s), " & CStr(lngMonths) & " month(s), " & CStr(lngDays) & " day(s)"

End Function
</code>

It will even count correctly for negative periods - where date1 is later than date2.
To allow for general use, it also returns ByRef the count of years, months, and days while the normal return value is a formatted string like:

  12 year(s), 7 month(s), 3 day(s)

I think this should fulfil your requirements.

/gustav


>>> adtp at airtelmail.in 30-03-2010 11:35 >>>
Gustav,

    Thanks for such interesting insight. For this thread, let us say that the result is to be returned as a self contained string of completed years, months and days (in style: y years, m months, d days). 

    Kevin has provided Chip Pearson's Age function, which we could examine further. Would you be in a position to suggest a suitable function, duly taking into account the various factors outlined in your post.

    Ideally, the proposed function should be able to  handle all types of special date combinations that are not straightaway amenable to application of fixed formula. Some examples:
    
    30-Apr-2005 (Month End) to 31-Mar-2009 (Month End) should resolve to 3 years, 11 months, 0 days (8 complete months in 2005, plus 3 complete years 2006 to 2008, plus 3 complete months in 2009).
    (However, as per Chip Pearson's function posted by Kevin, it evaluates to 3 years 11 months 1 days)

    30-Apr-2005 (Month End) to 30-Mar-2009 should resolve to 3 years, 10 months, 30 days (8 complete months in 2005, plus 3 complete years 2006 to 2008, plus 2 complete months in 2009, plus 30 elapsed days of Mar-2009).
    (However, as per Chip Pearson's function posted by Kevin, it evaluates to 3 years 11 months 0 days)

    28-Feb-2005 (Month End) to 29-Feb-2008 (Month End) should resolve to 3 years, 0 months, 0 days (10 complete months in 2005, plus 2 complete years 2006 to 2007, plus 2 complete months in 2008).
    (However, as per Chip Pearson's function posted by Kevin, it evaluates to 3 years 0 months 1 days)

    29-Feb-2008 (Month End) to 28-Feb-2010 (Month End) should resolve to 2 years, 0 months, 0 days (10 complete months in 2008, plus 1 complete year 2009, plus 2 complete months in 2010).
    (However, as per Chip Pearson's function posted by Kevin, it evaluates to 1 years 11 months 28 days)

    Other interested members might also like to kindly offer their views.

Best wishes,
A.D. Tejpal
------------

  ----- Original Message ----- 
  From: Gustav Brock 
  To: accessd at databaseadvisors.com 
  Sent: Monday, March 29, 2010 12:36
  Subject: Re: [AccessD] Date Difference As Completed Years-Months-Days


  Hi A.D.

  <<Snipped>>

  The main problem when calculation age in years is that a month is not a month and a year is not a year. Both have varying count of days. 

  This can lead to many worries until you realise that the best method is to turn it upside down - by adding a found interval of years (age) to the first date to prove that the second date is the right. That could lead to a new problem if you should consider how to add years but that is not the case as VBA features the DateAdd function which calculates correctly. Thus:

    Age = Years(Date1, Date2) <=> Date2 = DateAdd("yyyy", Age, Date1)

  So the simple answer to your question is to apply DateAdd to check your calculation and correct when needed. After a lengthy discussion and input from several members at Experts Exchange which also introduced the topic "Linear Age", this is how it turned out:

  <<Snipped>>

  /gustav




More information about the AccessD mailing list