-
Re: Calculating exact time in Years, MOnths, Days
I feel like some of the "failed" cases are correct, but it depends on when you want to say "a month has elapsed."
If it's 31 Jan 2004 and my doctor says I need an appointment scheduled in "a month", I wouldn't be surprised by an appointment anywhere between 28 Feb 2004 and anything in March 2004. The semantics are "roughly 30 days should elapse before you see me again." So 29 days is as accurate a description as 1 month, seeing as a month means anything from 28-31 days.
That's not to say it's an unimportant distinction. My banking contracts speak either in terms of days or include an end date for vague concepts like "30-year mortgage". 90 days is unambiguous. 21 October 2042 is unambiguous. "2 months" can mean anything from 59 to 62 days; resolution of the exact period requires the start date. My credit card interest is determined by average daily balance, and every statement explicitly states the number of days in the billing period.
This issue doesn't matter so much in less important scenarios. If my buddy asks for $10 and promises to pay me back "next month", I tend to assume that means some time in the next 30-60 days. Even something precise like "meet me in 5 minutes" has some assumed leeway. For these scenarios, simple estimations will work. "1 month from now" on January 31 can be Feb 28, Feb 29, Mar 1, or any of a handful of other days and most people won't bat an eye. Even if you're calculating a loan term, "1 year 6 months" is close enough for most people to properly visualize 3,842 days.
So I'm not sure if it's ever worth the effort to work out all the kinks. If the software needs to be accurate, then all time spans should be reported in days; anything with years and months should be qualified as an estimate only (and optionally allow display of the exact number of days.) I think we've stumbled upon an explanation for why TimeSpan only goes up to days: days are the largest constant unit of time. (Technically, leap seconds take even that away!)
It's definitely wrong that I calculate 32 days between 31 Jan 2004 and 3 Mar 2004. If the start date is on a 31 day, this error happens in many other places. I might be able to correct it, but I'd rather attack it from a different angle.
Here's a second attempt that gets many boundary cases right; I'll include the beginnings of my test harness so you can see what I tested.
Code:
Function CalculateRange2(ByVal startDate As DateTime, ByVal endDate As DateTime) As TimePeriod
Dim period As New TimePeriod()
Dim currentDate As DateTime = startDate
' Add years until we're at the same year
Do While currentDate.Year <> endDate.Year
currentDate = currentDate.AddYears(1)
If currentDate > endDate Then
' We overshot by some amount; take the year back and exit the loop. This happens when going from say
' 24 Feb 2003 to 1 Jan 2004.
currentDate = currentDate.AddYears(-1)
Exit Do
Else
' We haven't overshot yet; add a year to the reckoning
period.Years += 1
End If
Loop
' Add months until we're at the same month; it's possible we're the same month in a previous year
Do While currentDate.Month <> endDate.Month OrElse currentDate.Year <> endDate.Year
currentDate = currentDate.AddMonths(1)
If currentDate > endDate Then
' Overshot; take the month back and exit.
currentDate = currentDate.AddMonths(-1)
Exit Do
Else
period.Months += 1
End If
Loop
' Add days until we're at the same date
Do Until currentDate.Day = endDate.Day
currentDate = currentDate.AddDays(1)
period.Days += 1
Loop
Return period
End Function
Test cases (I'm sure you can figure out the TestCase class):
Code:
Dim testCases() As TestCase = {New TestCase(#2/28/2004#, #3/1/2004#, New TimePeriod() With {.Days = 2}),
New TestCase(#2/28/2004#, #3/2/2004#, New TimePeriod() With {.Days = 3}),
New TestCase(#2/28/2005#, #3/1/2005#, New TimePeriod() With {.Days = 1}),
New TestCase(#2/28/2005#, #3/2/2005#, New TimePeriod() With {.Days = 2}),
New TestCase(#1/28/2004#, #3/1/2004#, New TimePeriod() With {.Months = 1, .Days = 2}),
New TestCase(#1/28/2005#, #3/1/2005#, New TimePeriod() With {.Months = 1, .Days = 1}),
New TestCase(#2/28/2004#, #2/28/2005#, New TimePeriod() With {.Years = 1}),
New TestCase(#2/28/2005#, #2/28/2006#, New TimePeriod() With {.Years = 1}),
New TestCase(#1/31/2004#, #2/28/2004#, New TimePeriod() With {.Days = 30}),
New TestCase(#1/31/2004#, #2/29/2004#, New TimePeriod() With {.Months = 1}),
New TestCase(#1/31/2005#, #2/28/2005#, New TimePeriod() With {.Months = 1}),
New TestCase(#12/15/2003#, #12/14/2004#, New TimePeriod() With {.Months = 11, .Days = 29}),
New TestCase(#12/15/2003#, #12/16/2004#, New TimePeriod() With {.Years = 1, .Days = 1}),
New TestCase(#12/15/2004#, #12/15/2005#, New TimePeriod() With {.Years = 1}),
New TestCase(#12/15/2004#, #12/14/2005#, New TimePeriod() With {.Months = 11, .Days = 29}),
New TestCase(#12/15/2004#, #12/16/2005#, New TimePeriod() With {.Years = 1, .Days = 1})
}
The most interesting test case in my opinion is 31 Jan 2004 to 28 Feb 2004. Technically, 1 month from the start date is 29 Feb 2004, so rather than the intuitive 1 month span it's actually a 30 day span. This is not true in 2005, where the distance between the two dates is 1 month again. Many people would argue this is *still* a distance of one month. I think both sides are right and business logic should be the deciding factor; if your boss wants it to be a month special case it to a month.
I'm less confident this one is correct in all cases than my original because it's far more complicated. It took me about 45 minutes to get it right, whereas #2 took me 10 minutes. It looks like a reimagining of AgeInYearsMonthsDays() from your post #2, so it's hardly a new idea. And whether it is "correct" still depends on when 28 days counts as a month. I think the hardest part of verifying correctness is the sheer number of edge cases; I'm not sure if I've covered all of them yet.
On #57: it's complicated and less intuitive than an incremental approach; that makes me superstitious it's got hidden errors. My guess is it started simple, then lots of special cases got added. That's why I made a different attempt rather than tweaking #2. Incremental approaches are easier to understand and thus easier to maintain. Others might not share my opinion, but I don't like math-based approach to date differencing. Been burned too many times. I'm too lazy to throw test cases at it; I'm out of break time today.
-
Re: Calculating exact time in Years, MOnths, Days
@dBasnett
what about this:
Start: 23/08/2008
End: 22/07/2010
Years: 1
Months: 10
Days: 28
should be 1 yr, 10 months, 29 days
but allowing for the 366 day year in 2008 it's wrong.???
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
Sitten Spynne
I don't know what's going on in this thread. If anything it's a painful proof of my assertion that my technique's the best :p
Quote:
Originally Posted by
dbasnett
As of now there aren't any correct answers, IMHO.
it'd help if Microsoft had a consensus of opinion on this. their methods are all over the place...
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
Sitten Spynne
I think the
noda-time project has some functionality to do this that might present an API example. It's a shame they haven't found the time to write documentation so I could prove it.
It's also a shame they haven't reached a stable release either, as that precludes it from most production code. My first thought yesterday when reading this thread was "Ah! Use Noda Time!", only to find they're still a little way off a 1.0 release.
Quote:
Originally Posted by
Sitten Spynne
I think both sides are right and business logic should be the deciding factor; if your boss wants it to be a month special case it to a month.
I'm slightly disappointed it took 60 posts before someone threw in that phrase. The requirements for this seem very simple but they hide a huge number of edge and corner cases where we need clarification on the desired behaviour. Yes, I have an idea of what makes sense to me, but that doesn't mean my idea of the obvious behaviour in those cases is correct for this application. (And as to why I haven't posted the code for my answer? I got bored after the fifth test case :))
The real answer here, IMO, is "That's hard to get correct in all cases, are you sure you can't just have the number of days, or just display the start and end date?"
-
Re: Calculating exact time in Years, MOnths, Days
I have taken a leaf out of SittenSpynne's book and rewritten my code from the beggining. Although it is mostly the same, it helped me find a bug and lay the code out in an easier to follow way.
I've tested it with a bunch of dates and so far it seems to be working as it should be (or at least according to the 'rules' laid out by dbasnett, which I am onboard with).
Let's hope none of you manage to find a date pair to ruin my moment of self gratifaction :bigyello:
Here's the code:
VB.NET Code:
Public Class Form1
'Date1 is the lowest date, Date2 is the highest date
Private Sub DateCompare(ByVal Date1 As Date, ByVal Date2 As Date)
Dim Days As Integer = Nothing
Dim Months As Integer = Nothing
Dim Years As Integer = Nothing
'Convert Date1/Date2 months and years into months.
Dim Date1Months As Integer = Date1.Year * 12 + Date1.Month
Dim Date2Months As Integer = Date2.Year * 12 + Date2.Month
'Get the number of Months between the two dates. Subtract 1 (we will add this back later where necessary)
Months = Date2Months - Date1Months - 1
'See how many days are within the Month Date1/Date2 reside.
Dim DaysInDate1Month As Integer = Date.DaysInMonth(Date1.Year, Date1.Month)
Dim DaysInDate2Month As Integer = Date.DaysInMonth(Date2.Year, Date2.Month)
If Date1.Day = DaysInDate1Month And Date2.Day = DaysInDate2Month Then
'If Both Date1 and Date2 occur on the LastDayOfTheMonth.
'We count this as a month rather than x amount of days.
'Therefore add 1 to Months and set Days to zero.
'nb: We subtracted a month when we first grabbed the number of Months. Add it back here.
Months += 1
Days = 0
ElseIf Date1.Day = DaysInDate1Month And Date2.Day < DaysInDate2Month Then
'If Date1 occur's on the LastDayOfTheMonth but Date2 resides on a day prior to the end of it's month.
'We know the number of Days between the two dates will be the number of Days in Date2.
Days = Date2.Day
Else
'If Neither Date1 or Date2 occur on the LastDayOfTheMonth then we can do a day count ourselves.
Select Case Date1.Day
Case Is < Date2.Day
'If Date2's Day is higher then Date1's Day then the 'Months' value will represent the number of months upto the Month that Date2 resides.
'The number of days will therefore be the difference between Date1/Date2's Day values.
Days = Date2.Day - Date1.Day
'nb: We subtracted a month when we first grabbed the number of Months. Add it back here.
Months += 1
Case Is > Date2.Day
'If Date2's Day is lower then Date1's Day then the 'Months' value will represent the number of months upto the Month prior to that which Date2 resides.
'First we need to find out the month prior to Date2's month value.
Dim MonthBeforeDate2 As Integer = Date2.Month
'If Date2 occurs in January, then we will also need to use the previous year in our calculation.
Dim Date2Year As Integer = Date2.Year
If MonthBeforeDate2 = 1 Then
MonthBeforeDate2 = 12
Date2Year -= 1
Else : MonthBeforeDate2 -= 1
End If
'Next we will see how many days occur between the value of Date1's Day and the end of the month prior to Date2.
Dim DaysLeftInMonthBeforeDate2 As Long = Date.DaysInMonth(Date2Year, MonthBeforeDate2) - Date1.Day
'Finally we can add the number of Days in Date2.
Days = DaysLeftInMonthBeforeDate2 + Date2.Day
Case Is = Date2.Day
'If the Day value in Date1/Date2 matches.
'nb: We subtracted a month when we first grabbed the number of Months. Add it back here.
Months += 1
End Select
End If
'The last step is to work out the number of Years. We can work this out from the 'Months'.
'nb: We left this until last because there may have been a few neccessary changes to the Months value along the way.
If Months >= 12 Then
Do Until Months < 12
Years += 1
Months -= 12
Loop
End If
'Now just format the result:
MsgBox(String.Format("Years: {1}{0}Months: {2}{0}Days: {3}", {vbCrLf, Years, Months, Days}))
End Sub
End Class
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
jay20aiii
I have taken a leaf out of SittenSpynne's book and rewritten my code from the beggining.
i haven't tested your code, but if you had taken a leaf out of SittenSpynne's book you'd have just written an essay + no code:D
-
Re: Calculating exact time in Years, MOnths, Days
I could paste some Wikipedia excerpts if that would help :bigyello:
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
.paul.
i haven't tested your code, but if you had taken a leaf out of SittenSpynne's book you'd have just written an essay + no code:D
I think a leaf from SittenSpynne's book would be more like "post some pretty robust code, then discuss why it's robust and the thought processes behind making it robust so that next time, the people asking for help might be able to get a bit further by themselves."
Compare that with a leaf from another person's book:
"Post some code that seems to work when given a cursory test with some really easy values. When it is pointed out the code has flaws, tweak the code to handle each bug. Hope and pray you don't introduce new bugs with the changes, or regress previously working cases. Give up and then try and suggest SittenSpynne is the one posturing."
I know whose book I'd rather be reading.
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
Evil_Giraffe
I think a leaf from SittenSpynne's book would be more like "post some pretty robust code, then discuss why it's robust and the thought processes behind making it robust so that next time, the people asking for help might be able to get a bit further by themselves."
Compare that with a leaf from another person's book:
"Post some code that seems to work when given a cursory test with some really easy values. When it is pointed out the code has flaws, tweak the code to handle each bug. Hope and pray you don't introduce new bugs with the changes, or regress previously working cases. Give up and then try and suggest SittenSpynne is the one posturing."
I know whose book I'd rather be reading.
hmmm...
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
Evil_Giraffe
I think a leaf from SittenSpynne's book would be more like "post some pretty robust code, then discuss why it's robust and the thought processes behind making it robust so that next time, the people asking for help might be able to get a bit further by themselves."
Compare that with a leaf from another person's book:
"Post some code that seems to work when given a cursory test with some really easy values. When it is pointed out the code has flaws, tweak the code to handle each bug. Hope and pray you don't introduce new bugs with the changes, or regress previously working cases. Give up and then try and suggest SittenSpynne is the one posturing."
I know whose book I'd rather be reading.
you should obviously be made aware that SittenSpynne's code doesn't work reliably either...
check the history. my reference to SittenSpynne's essays was based on his past performance.
-
Re: Calculating exact time in Years, MOnths, Days
@Evil Giraffe. see post#62
-
Re: Calculating exact time in Years, MOnths, Days
@.paul.
Apologies, my last post was incredibly snarky towards you, and it looks like it's derailed the thread into mud-slinging - for which I apologise to everyone involved. If you want to continue that discussion (I'm not particularly keen to, but since I started it...) PM me and we'll take it off-thread.
Sorry again, everyone.
-
Re: Calculating exact time in Years, MOnths, Days
I managed to find a date that makes my code in post #64 redundant. Better to find the bug myself I guess... but way to soil in my own sand.
Back to the drawing board I guess :)
-
Re: Calculating exact time in Years, Months, Days
What sitten had to say was needed for perspective. This thread was started with this, "I am trying to calculate the exact amount of time in Years, Months and Days between 2 date values." We have all found out how incredibly hard to know what that means, and how hard it is to do with any consistency.
Personal attacks don't have a place here, no matter how frustrated you are.
-
Re: Calculating exact time in Years, MOnths, Days
Success!
0 years, 0 months, 28 days between 2/29/2004 3/28/2004
0 years, 1 months, 0 days between 2/29/2004 3/29/2004
0 years, 1 months, 30 days between 2/29/2004 4/28/2004
0 years, 2 months, 0 days between 2/29/2004 4/29/2004
0 years, 11 months, 30 days between 2/29/2004 2/28/2005
1 years, 0 months, 0 days between 2/29/2004 3/1/2005
0 years, 0 months, 28 days between 1/31/2004 2/28/2004
0 years, 1 months, 0 days between 1/31/2004 2/29/2004
0 years, 2 months, 29 days between 1/31/2004 4/29/2004
0 years, 3 months, 0 days between 1/31/2004 4/30/2004
0 years, 1 months, 0 days between 1/31/2005 2/28/2005
0 years, 1 months, 1 days between 1/31/2005 3/1/2005
2 years, 0 months, 29 days between 1/29/2005 2/27/2007
2 years, 1 months, 0 days between 1/29/2005 2/28/2007
2 years, 1 months, 1 days between 1/29/2005 3/1/2007
3 years, 0 months, 30 days between 1/29/2005 2/28/2008
3 years, 1 months, 0 days between 1/29/2005 2/29/2008
-
Re: Calculating exact time in Years, MOnths, Days
congrats dbasnett, can we see the code your referring to?
The main reason I have been following this thread is because I made a datediff function years ago to work out my son's age in Years, Months, Weeks, Days and would like to update it with a more robust version :)
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
jay20aiii
congrats dbasnett, can we see the code your referring to?
The main reason I have been following this thread is because I made a datediff function years ago to work out my son's age in Years, Months, Weeks, Days and would like to update it with a more robust version :)
As you might imagine the code is a mess at this point. Let me clean it up and I'll post it.
Weeks! :eek:
-
Re: Calculating exact time in Years, MOnths, Days
Still needs work but the code is here
http://www.vbforums.com/showpost.php...40&postcount=2
Sitten was right about keeping the date manipulation as simple as possible.
-
Re: Calculating exact time in Years, MOnths, Days
Cool, I will take a look over it :)
I think I have just managed to irradicate the last of the issues with my code. So here it is as well. A bit of competition never hurts and hopefully that will make 2 working DateDiff functions!
VB.NET Code:
Public Class Form1
'Date1 is the lowest date, Date2 is the highest date
Private Sub DateCompare(ByVal Date1 As Date, ByVal Date2 As Date)
Dim Days As Integer = Nothing
Dim Months As Integer = Nothing
Dim Years As Integer = Nothing
'Convert Date1/Date2 months and years into months.
Dim Date1Months As Integer = Date1.Year * 12 + Date1.Month
Dim Date2Months As Integer = Date2.Year * 12 + Date2.Month
'Get the number of Months between the two dates. Subtract 1 (we will add this back later where necessary)
Months = Date2Months - Date1Months - 1
'See how many days are within the Month Date1/Date2 reside.
Dim DaysInDate1Month As Integer = Date.DaysInMonth(Date1.Year, Date1.Month)
Dim DaysInDate2Month As Integer = Date.DaysInMonth(Date2.Year, Date2.Month)
If Date1.Day = DaysInDate1Month And Date2.Day = DaysInDate2Month Then
'If Both Date1 and Date2 occur on the LastDayOfTheMonth.
'We count this as a month rather than x amount of days.
'Therefore add 1 to Months and set Days to zero.
'nb: We subtracted a month when we first grabbed the number of Months. Add it back here.
Months += 1
Days = 0
ElseIf Date1.Day = DaysInDate1Month And Date2.Day < DaysInDate2Month Then
'If Date1 occur's on the LastDayOfTheMonth but Date2 resides on a day prior to the end of it's month.
'We know the number of Days between the two dates will be the number of Days in Date2.
Days = Date2.Day
Else
'If Neither Date1 or Date2 occur on the LastDayOfTheMonth then we can do a day count ourselves.
Select Case Date1.Day
Case Is < Date2.Day
'If Date2's Day is higher then Date1's Day then the 'Months' value will represent the number of months upto the Month that Date2 resides.
'The number of days will therefore be the difference between Date1/Date2's Day values.
Days = Date2.Day - Date1.Day
'nb: We subtracted a month when we first grabbed the number of Months. Add it back here.
Months += 1
Case Is > Date2.Day
'If Date2's Day is lower then Date1's Day then the 'Months' value will represent the number of months upto the Month prior to that which Date2 resides.
'First we need to find out the month prior to Date2's month value.
Dim MonthBeforeDate2 As Integer = Date2.Month
'If Date2 occurs in January, then we will also need to use the previous year in our calculation.
Dim Date2Year As Integer = Date2.Year
If MonthBeforeDate2 = 1 Then
MonthBeforeDate2 = 12
Date2Year -= 1
Else : MonthBeforeDate2 -= 1
End If
'If there are more days in Date1 than occur in the month prior to Date2 then we want to avoid calculating the days that remain in that month.
'As that would lead to a neagtive value.
If Date1.Day > Date.DaysInMonth(Date2Year, MonthBeforeDate2) Then
'In this case we only need the number of days in Date2.
Days = Date2.Day
Else
'Otherwise next we will see how many days occur between the value of Date1's Day and the end of the month prior to Date2.
Dim DaysLeftInMonthBeforeDate2 As Long = Date.DaysInMonth(Date2Year, MonthBeforeDate2) - Date1.Day
'Finally we can add the number of Days in Date2.
Days = DaysLeftInMonthBeforeDate2 + Date2.Day
End If
Case Is = Date2.Day
'If the Day value in Date1/Date2 matches.
'nb: We subtracted a month when we first grabbed the number of Months. Add it back here.
Months += 1
End Select
End If
'The last step is to work out the number of Years. We can work this out from the 'Months'.
'nb: We left this until last because there may have been a few neccessary changes to the Months value along the way.
If Months >= 12 Then
Do Until Months < 12
Years += 1
Months -= 12
Loop
End If
'Now just format the result:
MsgBox(String.Format("Years: {1}{0}Months: {2}{0}Days: {3}", {vbCrLf, Years, Months, Days}))
End Sub
End Class
Now, about those weeks... :bigyello:
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
jay20aiii
Cool, I will take a look over it :) ....Now, about those weeks... :bigyello:
When you look it over you might find weeks.
BTW - IMHO this is an error
0 years, 0 months, 30 days between 1/29/2005 2/28/2005
0 years, 1 months, 1 days between 1/29/2005 3/1/2005
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
dbasnett
When you look it over you might find weeks.
BTW - IMHO this is an error
0 years, 0 months, 30 days between 1/29/2005 2/28/2005
0 years, 1 months, 1 days between 1/29/2005 3/1/2005
1/29/2005 is a non existent date
-
Re: Calculating exact time in Years, MOnths, Days
I think some good points are in #63, #67 (personal comments aside), and #73. I think .paul. missed the two points in #69. As of #60 I decided to explicitly state a lack of confidence that I'd hit all edge cases and I'm no longer claiming correctness. I tested many edge cases but knew there were likely several I missed.
My "essays" (ask dbasnett about me, he's seen my *real* essays) are to highlight the points I felt were pertinent. I like word-heavy posts because I think it's nearly criminal to aid and abet copypasta programmers. In this case it doesn't matter; odds are any example won't have the domain-specific logic the programmer wants. Here's what I think is important now:
- This is a tough algorithm to get right; simple code is probably a better idea than complicated code.
- Days are the only unit easily and accurately counted. All others require difficult logic.
- There is no consensus on the "right" answer to certain time spans except when expressed in days. The domain specifies expected behavior.
- DateDiff() is often harder to use than rewrite yourself.
- I'm not sure if all edge cases have been enumerated; I think that's the best first step.
- Based on #5, the best we can do is test *many* edge cases and use that as our confidence all cases are covered.
I'm not interested in a third attempt because of #5. Call me chicken, but at this point I think it'd be a bit hypocritical to post another code sample if I'm not absolutely certain it works. Also, I'm not certain anyone's posted a critique of #60 so I'm going to assume it works until I've run dbasnett's test cases from #74. Plus, I want to see dbasnett's approach; I haven't opened VS 2010 yet today. I wouldn't want to waste time writing something he's already written.
I think the only "good" way to verify this would be a brute-force approach. It's a simple matter to keep adding days to an end date and period, then comparing the results to the expected value. The trick is figuring out how long a time span is adequate for correctness. I think I have an idea and I'm going to work on that test harness instead of any further attempts at a "correct" algorithm.
The concept of counting weeks makes me shiver, but I don't think it's as complicated on its own as it is incorporated to the other counter. Pseudocode:
Code:
Let weeks = 0
Let currentDate = startDate
While currentDate < endDate
Add 7 days to currentDate
If currentDate < endDate Then
Add 1 to weeks
End If
End While
Of course this has the same problems with domain-specific requirements as the rest. You might be picky about whether this is off by 1, or you might only want to count full weeks, or you might only want full business weeks. Each of those has its own complexities.
This is why I think DateDiff() fails. It tries to address *all* of these scenarios rather than addressing very specific scenarios. I don't think it's wise to write a general-purpose *function* to calculate these ranges. A class with several specific functions seems better.
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
Sitten Spynne
...My "essays" (ask dbasnett about me, he's seen my *real* essays) are to highlight the points I felt were pertinent.
- This is a tough algorithm to get right; simple code is probably a better idea than complicated code.
- Days are the only unit easily and accurately counted. All others require difficult logic.
- There is no consensus on the "right" answer to certain time spans except when expressed in days. The domain specifies expected behavior.
- DateDiff() is often harder to use than rewrite yourself.
- I'm not sure if all edge cases have been enumerated; I think that's the best first step.
- Based on #5, the best we can do is test *many* edge cases and use that as our confidence all cases are covered.
.... Also, I'm not certain anyone's posted a critique of #60 so I'm going to assume it works until I've run dbasnett's test cases from #74. Plus, I want to see dbasnett's approach; I haven't opened VS 2010 yet today. I wouldn't want to waste time writing something he's already written.
I think the only "good" way to verify this would be a brute-force approach. It's a simple matter to keep adding days to an end date and period, then comparing the results to the expected value. The trick is figuring out how long a time span is adequate for correctness. I think I have an idea and I'm going to work on that test harness instead of any further attempts at a "correct" algorithm.
"I'm not sure if all edge cases have been enumerated; I think that's the best first step." I have an idea of what I mean and I will post my definition.
I agree about the brute force method. My attempt at that is in post #58.
I am still cleaning up the code and will post it in post #2 as I work on it. After 5 or 6 days of fooling with this I am in no hurry.
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
.paul.
1/29/2005 is a non existent date
You might want to rethink that. :p
In other news, brute force fell flat. In order for my technique to work, you have to have a way to calculate the "expected" range. Calculating the expected range is what the algorithm is trying to do; you can't test something with itself. I'm not sure I trust #58 either; all you're doing is testing one algorithm for calculating the range against another. Rats.
Haven't spent much time on the revised #2; I can't figure out which pieces are important and which pieces aren't. I'll wait until you've got something presentable while I ruminate on the test cases.
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
Sitten Spynne
You might want to rethink that. :p
In other news, brute force fell flat. In order for my technique to work, you have to have a way to calculate the "expected" range. Calculating the expected range is what the algorithm is trying to do; you can't test something with itself. I'm not sure I trust #58 either; all you're doing is testing one algorithm for calculating the range against another. Rats.
Haven't spent much time on the revised #2; I can't figure out which pieces are important and which pieces aren't. I'll wait until you've got something presentable while I ruminate on the test cases.
yep.. i was thinking 2/29/2005
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
Sitten Spynne
You might want to rethink that. :p
In other news, brute force fell flat. In order for my technique to work, you have to have a way to calculate the "expected" range. Calculating the expected range is what the algorithm is trying to do; you can't test something with itself. I'm not sure I trust #58 either; all you're doing is testing one algorithm for calculating the range against another. Rats.
Haven't spent much time on the revised #2; I can't figure out which pieces are important and which pieces aren't. I'll wait until you've got something presentable while I ruminate on the test cases.
1/29/2005 does (did) exist.
Read #42 to see why I think #58 works.
Here is a simple example of how to use the code in #2
Code:
Private Sub Button4_Click(sender As System.Object, e As System.EventArgs) Handles Button4.Click
Try
Dim foo As New AgeCalc(Date.Parse(TextBox1.Text), Date.Parse(TextBox2.Text))
Dim bar As Age = foo.AgeInYearsMonthsDays
Label1.Text = bar.Years.ToString & " " & bar.Months.ToString & " " & bar.Days.ToString
Catch ex As Exception
End Try
End Sub
Try these date pairs for controversy:
1/30/2005 - 4/30/2005
1/31/2005 - 4/30/2005
-
1 Attachment(s)
Re: Calculating exact time in Years, MOnths, Days
I'm still not convinced. The algorithm you describe in #42 is very similar to my #8. If so, and #8 has flaws, how can you trust checking against a flawed algorithm?
In other news, I just sat down and banged out what I think is a fairly exhaustive list of test cases. I listed 98 (I think!) but could have thought of nearly 150 if I felt really paranoid. To test day calculations, I picked all for month lengths and picked easy dates within the month. To test month calculations, I noted there were several combinations of month lengths. First, I crossed each month boundary (31, 30, 29, and 28 days) and verified the (1m, 0d) date and the two boundary dates. (The * before some tests indicates previous tests have technically covered that case.) For fun, I tested from July-September, one of the two 31-31 boundaries. I did a cursory test of the Dec-Jan boundary to catch erroneous year increments. Then I listed 31-30, 31-29, and 31-28. At this point I felt month calculations were adequately tested, but other possible tests include the boundaries 30-31, 31-31-30, 31-30-31, and 30-31-30 (they would have been more interesting in an algorithm attempting to go from days -> months directly.) Year calculations don't have to be as thorough: I tested Jan-Jan and Jan-Mar (leap and non-leap.) Finally, calculations across leap years are tested in all scenarios I could think of: first year is leap, 1 leap year is spanned, no leap year in range, end year is leap, and multiple leap years spanned.
So far I can't think of any relevant situations not listed.
It's possible I'm off by one on the "expected" dates. I triple-counted using a calendar, but the font was small and I tend to get cross-eyed trying to count dates like that. I'm too lazy to type up a harness that uses them all; I've spent enough time on forums today anyway :p Maybe I'll unleash it against my stuff later tonight.
-
Re: Calculating exact time in Years, MOnths, Days
For no other reason than, "I started so I'll finish" here is the latest revision of my own method which I am confident will pass any stress tests :)
I made a better test function to help me conclude this. It specifies a startdate. It then tests this against every possible end date upto 366 days from the start date. It will then increase the startdate by 1 day and cycle this again. It ends when the startdate reaches 366 days from the given date. OK I think i've confused us all enough now!
dbasnett, you will be glad to know I tested it on your code too, and there are no errors in yours either :)
Here's the code:
VB.NET Code:
Public Class Form1
'Date1 is the lowest date, Date2 is the highest date
Private Sub DateCompare(ByVal Date1 As Date, ByVal Date2 As Date)
Dim Days As Integer = Nothing
Dim Months As Integer = Nothing
Dim Years As Integer = Nothing
'Convert Date1/Date2 months and years into months.
Dim Date1Months As Integer = Date1.Year * 12 + Date1.Month
Dim Date2Months As Integer = Date2.Year * 12 + Date2.Month
'Get the number of Months between the two dates. Subtract 1 (we will add this back later where necessary)
Months = Date2Months - Date1Months - 1
'See how many days are within the Month Date1/Date2 reside.
Dim DaysInDate1Month As Integer = Date.DaysInMonth(Date1.Year, Date1.Month)
Dim DaysInDate2Month As Integer = Date.DaysInMonth(Date2.Year, Date2.Month)
If Date1.Day = DaysInDate1Month And Date2.Day = DaysInDate2Month Then
'If Both Date1 and Date2 occur on the LastDayOfTheMonth.
'We count this as a month rather than x amount of days.
'Therefore add 1 to Months and set Days to zero.
'nb: We subtracted a month when we first grabbed the number of Months. Add it back here.
Months += 1
Days = 0
ElseIf Date1.Day = DaysInDate1Month And Date2.Day < DaysInDate2Month Then
'If Date1 occur's on the LastDayOfTheMonth but Date2 resides on a day prior to the end of it's month.
'We know the number of Days between the two dates will be the number of Days in Date2.
Days = Date2.Day
ElseIf Date1.Day > DaysInDate2Month And Date2.Day = DaysInDate2Month Then
Months += 1
Days = 0
Else
'If Neither Date1 or Date2 occur on the LastDayOfTheMonth then we can do a day count ourselves.
Select Case Date1.Day
Case Is < Date2.Day
'If Date2's Day is higher then Date1's Day then the 'Months' value will represent the number of months upto the Month that Date2 resides.
'The number of days will therefore be the difference between Date1/Date2's Day values.
Days = Date2.Day - Date1.Day
'nb: We subtracted a month when we first grabbed the number of Months. Add it back here.
Months += 1
Case Is > Date2.Day
'If Date2's Day is lower then Date1's Day then the 'Months' value will represent the number of months upto the Month prior to that which Date2 resides.
'First we need to find out the month prior to Date2's month value.
Dim MonthBeforeDate2 As Integer = Date2.Month
'If Date2 occurs in January, then we will also need to use the previous year in our calculation.
Dim Date2Year As Integer = Date2.Year
If MonthBeforeDate2 = 1 Then
MonthBeforeDate2 = 12
Date2Year -= 1
Else : MonthBeforeDate2 -= 1
End If
'If there are more days in Date1 than occur in the month prior to Date2 then we want to avoid calculating the days that remain in that month.
'As that would lead to a neagtive value.
If Date1.Day > Date.DaysInMonth(Date2Year, MonthBeforeDate2) Then
'In this case we only need the number of days in Date2.
Days = Date2.Day
Else
'Next we will see how many days occur between the value of Date1's Day and the end of the month prior to Date2.
Dim DaysLeftInMonthBeforeDate2 As Long = Date.DaysInMonth(Date2Year, MonthBeforeDate2) - Date1.Day
'Finally we can add the number of Days in Date2.
Days = DaysLeftInMonthBeforeDate2 + Date2.Day
End If
Case Is = Date2.Day
'If the Day value in Date1/Date2 matches.
'nb: We subtracted a month when we first grabbed the number of Months. Add it back here.
Months += 1
End Select
End If
'The last step is to work out the number of Years. We can work this out from the 'Months'.
'nb: We left this until last because there may have been a few neccessary changes to the Months value along the way.
If Months >= 12 Then
Do Until Months < 12
Years += 1
Months -= 12
Loop
End If
'Now just format the result:
MsgBox(String.Format("Years: {1}{0}Months: {2}{0}Days: {3}{0}", {vbCrLf, Years, Months, Days}))
End Sub
End Class
-
Re: Calculating exact time in Years, MOnths, Days
@jay - Depending on how you define a leaplings birthday this may or may not be an error
1 years, 0 months, 0 days between 2/29/2004 2/28/2005
This is how I define it
0 years, 11 months, 30 days between 2/29/2004 2/28/2005
1 years, 0 months, 0 days between 2/29/2004 3/1/2005
-
Re: Calculating exact time in Years, MOnths, Days
@sitten et al, I am trying to nail down my definition.
http://www.vbforums.com/showpost.php...40&postcount=2
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
dbasnett
@jay - Depending on how you define a leaplings birthday this may or may not be an error
1 years, 0 months, 0 days between 2/29/2004 2/28/2005
This is how I define it
0 years, 11 months, 30 days between 2/29/2004 2/28/2005
1 years, 0 months, 0 days between 2/29/2004 3/1/2005
That's the kind of thing I'm complaining about though; to some people you're wrong and Feb 29 N to Mar 1 N + 1 is a year. If that's the only miscalculation in an algorithm I say the calculation's correct; it's easy enough to correct the "flaw" with an If statement.
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
dbasnett
@jay - Depending on how you define a leaplings birthday this may or may not be an error
1 years, 0 months, 0 days between 2/29/2004 2/28/2005
This is how I define it
0 years, 11 months, 30 days between 2/29/2004 2/28/2005
1 years, 0 months, 0 days between 2/29/2004 3/1/2005
'Leaplings' can celebrate a birthday on either 28/02 or 01/03 in any standard year. It is common to opt for the latter because it is the day following Feb 28th as was the case of the day of their birth.
I did weigh up the leapling idea, but decided to leave it as is. My reasoning is that if you count the days between these dates:
28/2/2003 - 29/2/2004 = 366 days
29/2/2004 - 28/2/2005 = 365 days
28/2/2005 - 28/2/2006 = 365 days
28/2/2006 - 28/2/2007 = 365 days
28/2/2007 - 29/2/2008 = 366 days
by definition a leap year occurs every 4 years. I take that to mean a year is equal to 365 days for 3 consecutive years, followed by a non-standard year of 366 days.
If we were to take the leapling route then the dates above would be written:
28/2/2003 - 29/2/2004 = 366 days
29/2/2004 - 01/3/2005 = 366 days
Upto now we have 2 consective years that calculate as 366 days. Now since 01/03 has been used to define the end of year that begins on a leapling, do we now consider the start of the next year from the 01/03? If so we would be looking at losing a day in order to re-align the years:
01/3/2005 - 28/2/2006 = 364 days
28/2/2006 - 28/2/2007 = 365 days
28/2/2007 - 29/2/2008 = 366 days
and therefore the consistency of the year by definition has been thrown out of the window. Perhaps my view is wrong though, but as Sitten Spynne has maintained throughout, details such as these really come down to invidual interpretation because there is no set standard to govern this.
A couple of results from your function:
28/2/2003 - 29/2/2004 = 1 year, 1 day (366 days)
29/2/2004 - 28/2/2005 = 11 Months, 30 Days (11 months = 01/03 to 31/01, that is 336 days. Then add the 30.)
-
Re: Calculating exact time in Years, MOnths, Days
@jay - However you decide to treat a leapling's birthday is fine with me. I think we all have come to the conclusion that time expressed in anything other than days(TimeSpan accuracy) is arbitrary.