-
Calculating exact time in Years, MOnths, Days
I am trying to calculate the exact amount of time in Years, Months and Days between 2 date values. I'm getting stuck on the day calculation. I've returned the # of months and it's value is 8.77. The .77 needs to be rounded to 8 which should give me the days. I'm not exactly sure what MATH function to use to do this. Any ideas?
Thanks,
-
Re: Calculating exact time in Years, MOnths, Days
Try this.
edit: 5 days later
If you have ever wondered why TimeSpan only reports to a resolution of days, this thread is for you. It is highly recommended that this thread be read.
The purpose of the code that follows is to add the concept of years, months, weeks and days as another way of looking at the difference between two dates. But this concept, and the code that follows, has definitions about years, months, and days which can best be shown through examples.
Let’s start with some easy ones:
0 years, 0 months, 1 days between 1/30/2004 1/31/2004
0 years, 3 months, 0 days between 1/30/2004 4/30/2004
1 years, 0 months, 0 days between 1/30/2004 1/30/2005
Let’s also take a look at leap year:
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 – A leapling’s (someone born on 2/29/leap_year) birthday is 3/1 for non-leap years in this implementation.
Now the concept gets controversial.
0 years, 3 months, 0 days between 1/31/2004 4/30/2004 which is the same result as 1/30/2004 4/30/2004. The explanation is that this code is designed to be “birthday” related, and if the birthday’s are different then the results appear inconsistent, but from the perspective of a birthday the results are consistent.
0 years, 2 months, 28 days between 1/31/2004 4/28/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, 3 months, 1 days between 1/31/2004 5/1/2004
0 years, 3 months, 2 days between 1/31/2004 5/2/2004
0 years, 2 months, 29 days between 1/30/2004 4/28/2004
0 years, 2 months, 30 days between 1/30/2004 4/29/2004
0 years, 3 months, 0 days between 1/30/2004 4/30/2004
0 years, 3 months, 1 days between 1/30/2004 5/1/2004
0 years, 3 months, 2 days between 1/30/2004 5/2/2004
This apparent inconsistency only occurs for “birth dates” with a day component greater than 28.
Test Code for AgeCalc class
Code:
Private Sub Button6_Click(sender As System.Object, e As System.EventArgs) Handles Button6.Click
Debug.WriteLine("<<<<< Birthday Concept >>>>>")
Dim foo As New AgeCalc()
Dim birthday As DateTime = #2/29/2004#
Dim aDate As DateTime = birthday
Dim endDate As DateTime = #4/1/2005#
Do While aDate <= endDate
Dim bar As Age = foo.AgeInYearsMonthsDays(birthday, aDate)
Debug.WriteLine(String.Format("{0} years, {1} months, {2} days between {3} {4} ", _
bar.Years, _
bar.Months, _
bar.Days, _
bar.DatePast.ToShortDateString, _
bar.DateFuture.ToShortDateString))
aDate = aDate.AddDays(1)
Loop
End Sub
AgeCalc class
Thanks to Sitten Spynne, .paul., Evil_Giraffe, jay20aiii, and techgnome.
Code:
''' <summary>
''' Calculates age based on two dates. Age is inclusive of one of the dates.
''' </summary>
''' <remarks></remarks>
'''
Class AgeCalc
Public Age As New Age
''' <summary>
''' New
''' </summary>
''' <param name="DatePast">oldest date i.e. 1/1/1901</param>
''' <param name="DateFuture">youngest date i.e. 1/1/2011</param>
''' <remarks></remarks>
Public Sub New(DatePast As DateTime, DateFuture As DateTime)
Me.Age.ClearAge()
Me.Age.SetDateTime(DatePast, DateFuture)
Me.PastIsPast()
End Sub
Public Sub New()
Dim foo As DateTime = DateTime.Now
Me.Age.ClearAge()
Me.Age.SetDateTime(foo, foo)
End Sub
''' <summary>
''' Calculates age in years and days
''' </summary>
''' <returns>Age</returns>
''' <remarks></remarks>
Public Function AgeInYearsDays() As Age
Me.PastIsPast()
Me.Age.ClearAge()
Me.nextBDay = Me.NextBDayCalc
If Me.sameDay Then
Return Me.GetCurrentAge
End If
Dim ts As TimeSpan = Me.Age.DateFuture - Me.nextBDay
Me.Age.Days = ts.Days
Return Me.GetCurrentAge
End Function
Public Function AgeInYearsDays(DatePast As DateTime, DateFuture As DateTime) As Age
Me.Age.SetDateTime(DatePast, DateFuture)
Return Me.AgeInYearsDays
End Function
''' <summary>
''' Calculates age in years, months, and days
''' </summary>
''' <returns>Age</returns>
''' <remarks></remarks>
Public Function AgeInYearsMonthsDays() As Age
Me.PastIsPast()
Me.Age.ClearAge()
Me.nextBDay = Me.NextBDayCalc 'next birthday
If Me.sameDay Then
Return Me.GetCurrentAge
End If
Me.Anniversary = Me.nextBDay
Me.nextMonth = Me.Anniversary.AddOneMonth
Me.checkNextMonth()
Do While Me.nextBDay < Me.Age.DateFuture
Me.DayPlus1()
Me.nextBDay = Me.nextBDay.AddDays(1)
Loop
Return Me.GetCurrentAge
End Function
Public Function AgeInYearsMonthsDays(DatePast As DateTime, DateFuture As DateTime) As Age
Me.Age.SetDateTime(DatePast, DateFuture)
Return Me.AgeInYearsMonthsDays
End Function
Public Function AgeInYearsMonthsWeeksDays() As Age
Me.AgeInYearsMonthsDays()
Me.Age.Weeks = Math.DivRem(Me.Age.Days, 7, Me.Age.Days)
Return Me.GetCurrentAge
End Function
Public Function AgeInYearsMonthsWeeksDays(DatePast As DateTime, DateFuture As DateTime) As Age
Me.Age.SetDateTime(DatePast, DateFuture)
Return Me.AgeInYearsMonthsWeeksDays
End Function
Public Function AgeInYearsWeeksDays() As Age
Me.AgeInYearsDays()
Me.Age.Weeks = Math.DivRem(Me.Age.Days, 7, Me.Age.Days)
Return Me.GetCurrentAge
End Function
Public Function AgeInYearsWeeksDays(DatePast As DateTime, DateFuture As DateTime) As Age
Me.Age.SetDateTime(DatePast, DateFuture)
Return Me.AgeInYearsWeeksDays
End Function
Private nextBDay As DateTime 'next birthday
Private Anniversary As DateTime
Private nextMonth As DateTime
Private Function GetCurrentAge() As Age
Return Me.Age
End Function
Private Sub checkNextMonth()
If Me.Age.DatePast.Day = 29 AndAlso Me.nextMonth.Day = 28 AndAlso Me.Age.DatePast.Month = 2 AndAlso Me.nextMonth.Month = 2 Then
Me.nextMonth = DateSerial(Me.nextMonth.Year, 3, 1)
End If
End Sub
Private Sub DayPlus1()
Me.Age.Days += 1
If Me.Anniversary.AddMonths(Me.Age.Months).AddDays(Me.Age.Days) >= Me.nextMonth Then
Me.MonthPlus1()
End If
End Sub
Private Sub MonthPlus1()
Me.Age.Days = 0
Me.Age.Months += 1
Me.nextMonth = Me.Anniversary.AddMonths(Me.Age.Months + 1)
Me.checkNextMonth()
If Me.Age.Months >= 12 Then Me.YearPlus1()
End Sub
Private Sub YearPlus1()
Me.Age.Years += 1
Me.Age.Months = 0
Me.Age.Days = 0
End Sub
Private Sub PastIsPast()
If Me.Age.DatePast > Me.Age.DateFuture Then 'make sure the past is the past
Me.Age.SetDateTime(Me.Age.DateFuture, Me.Age.DatePast)
End If
End Sub
Private Function sameDay() As Boolean
'If same day different years, then return years
If Me.Age.DatePast.Month = Me.Age.DateFuture.Month AndAlso _
Me.Age.DatePast.Day = Me.Age.DateFuture.Day Then
'calculate years
Me.Age.Years = Me.Age.DateFuture.Year - Me.Age.DatePast.Year
Return True
End If
Return False
End Function
Private Function NextBDayCalc() As DateTime
Dim rv As DateTime
'create next "birthday"
'note - the wonderful thing about DateSerial is that it takes care of
'leap Birthdays, AKA leaplings, (2/29/leapyear) . A leaplings b-day is 3/1 for non-leap years
rv = DateSerial(Me.Age.DateFuture.Year, Me.Age.DatePast.Month, Me.Age.DatePast.Day) 'takes care of leap b-day
If rv > Me.Age.DateFuture AndAlso Me.Age.DateFuture <> Me.Age.DatePast Then 'is the next "birthday" > future date
'it is, adjust nextBDay
rv = DateSerial(Me.Age.DateFuture.Year - 1, Me.Age.DatePast.Month, Me.Age.DatePast.Day) 'takes care of leap b-day
End If
'calculate years
Me.Age.Years = rv.Year - Me.Age.DatePast.Year
Return rv
End Function
End Class
Class Age
'age
Public Years As Integer
Public Months As Integer
Public Weeks As Integer
Public Days As Integer
Public Sub New()
Dim foo As DateTime = DateTime.Now
Me.SetDateTime(foo, foo)
End Sub
Public Sub New(DatePast As DateTime, DateFuture As DateTime)
Me.SetDateTime(DatePast, DateFuture)
End Sub
Private _datepast As DateTime
Public Property DatePast() As DateTime
Get
Return Me._datepast
End Get
Set(ByVal value As DateTime)
Me.SetDateTime(value, Me.DateFuture)
End Set
End Property
Private _datefuture As DateTime
Public Property DateFuture() As DateTime
Get
Return Me._datefuture
End Get
Set(ByVal value As DateTime)
Me.SetDateTime(Me.DatePast, value)
End Set
End Property
Public ReadOnly Property Difference() As TimeSpan
Get
Return Me.DateFuture - Me.DatePast
End Get
End Property
Public Sub ClearAge()
Me.Years = 0
Me.Months = 0
Me.Weeks = 0
Me.Days = 0
End Sub
Public Sub SetDateTime(DatePast As DateTime, DateFuture As DateTime)
'note Time is not stored
If DatePast <= DateFuture Then
Me._datepast = DatePast.Date
Me._datefuture = DateFuture.Date
Else
Me._datepast = DateFuture.Date
Me._datefuture = DatePast.Date
End If
End Sub
End Class
Module DateExtensions
<System.Runtime.CompilerServices.Extension()> _
Public Function AddOneMonth(ByVal instance As Date) As DateTime
Dim rv As DateTime = DateSerial(instance.Year, instance.Month + 1, instance.Day)
If instance.Day <> rv.Day Then
rv = DateSerial(instance.Year, instance.Month + 1, Date.DaysInMonth(instance.Year, instance.Month + 1))
End If
Return rv
End Function
End Module
-
Re: Calculating exact time in Years, MOnths, Days
Here is one...it is for age, but you should be able to modify it for your needs.
http://www.vbforums.com/showthread.php?t=629750
-
Re: Calculating exact time in Years, MOnths, Days
Thanks bro, but I know it can be done using alot less code...
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
blakemckenna
Thanks bro, but I know it can be done using alot less code...
I bet it can.
I haven't examined it closely for a long time. One thing I do remember was that leaplings always seemed to mess with my results, until I this code.
-
Re: Calculating exact time in Years, MOnths, Days
Like I said, the only thing I'm stuck with now is converting the remainder (.77)of 8.77 into a whole number and I just can't remember how to do that!
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
blakemckenna
Like I said, the only thing I'm stuck with now is converting the remainder (.77)of 8.77 into a whole number and I just can't remember how to do that!
What is 8.77?
Why not show us the code? You could use the code I posted as a check to see if the results match.
-
Re: Calculating exact time in Years, MOnths, Days
Well, if you have .77 of a month, that would mean you have 77% of the days of a month. Depending on the month, it could be a couple different values. You can't just convert the .77 into an amount because you need to know what month that 77% is apart of:
Code:
Feburary with 28 days = 21.56
Feburary with 29 days = 22.33
Months with 30 days = 23.1
Months with 31 days = 23.87
Then you could use this "template" I guess to calculate the days:
Code:
'//your month calculation
Dim months = 8.77D
'//gets the 0.77
Dim remainder = months Mod Math.Floor(months)
'//find the year and month, then get the days of that month
Dim days = Date.DaysInMonth(year, month) * remainder
If the .77 is spanning across months, then it's a different ball game.
-
Re: Calculating exact time in Years, MOnths, Days
*edit* Note this code sample was proven wrong and corrected in #60, which has problems of its own. The discussion on page 2 is interesting. */edit*
I'd be wary of seeking "less code" in all circumstances. Smart programmers want "correct code". Good programmers care less about cramming everything into as few lines as possible and more about ensuring their results are provably correct.
.77 can be considered a percent. I don't think there's a reasonable way to convert that back to a number of days. Different months have different numbers of days. For example, if there were always 30 days in a month, you could say 8.77 months is 8 months and .77 * 30 = 23.1 days. But there's not 30 days in a month. Sometimes it's 31. Sometimes it's 30. Sometimes it's 28, and more rarely 29. Your first thought might be "I'll use how many days are in the 9th month", but if you pick September you'd only be right if your starting date is January 1. What you really want is "9 months from the beginning of the range", and even that can vary. What if the start date is July 31? I don't think there's any hope of getting correct results this way without writing dozens of extra lines of code for special cases.
In general, I feel like math against the calendar is always error-prone. The months are uneven, leap years are complicated (and change based on era!), and it's hard to understand if you're right. I prefer to lean on someone else's time implementation, especially if thousands of other programmers rely on it.
The easiest way to measure years, months, and days is to use the DateTime class and keep adding days. dbasnett's code makes a clever optimization I tried once and loops years, then months, then days. As you can see, that turns out to be more code. He does have an advantage I'll discuss after showing my code that loops only days. Here's a program, complete with tests, that seems to pull it off correctly without icky hard-to-verify calculations:
Code:
Module Module1
Sub Main()
TestWithinMonth()
TestAcrossMonths()
TestLeapYear()
TestAcrossYears()
End Sub
Function CalculateRange(ByVal startDate As DateTime, ByVal endDate As DateTime) As TimePeriod
Dim period As New TimePeriod
Dim currentDate As DateTime = startDate
While currentDate < endDate
' Let a day elapse
currentDate = currentDate.AddDays(1)
' Determine if a month or year has elapsed.
If currentDate.Day = startDate.Day Then
If currentDate.Month = startDate.Month Then
' It's the same day and month in a new year: a year has elapsed.
period.YearElapsed()
Else
' It's the same day in a different month: a month has elapsed
period.MonthElapsed()
End If
Else
' It's not a month or day boundary
period.DayElapsed()
End If
End While
Return period
End Function
Sub TestWithinMonth()
Dim startDate As DateTime = New DateTime(2011, 1, 1)
Dim endDate As DateTime = New DateTime(2011, 1, 31)
Dim elapsed = CalculateRange(startDate, endDate)
Console.WriteLine("Jan 1 to Jan 31 should be 30 days. Result:")
PrintPeriod(elapsed)
End Sub
Sub TestAcrossMonths()
Dim startDate As New DateTime(2011, 1, 15)
Dim endDate As New DateTime(2011, 2, 15)
Dim elapsed = CalculateRange(startDate, endDate)
Console.WriteLine("Jan 15 to Feb 15 should be 1 month, 0 days. Result:")
PrintPeriod(elapsed)
endDate = New DateTime(2011, 4, 18)
elapsed = CalculateRange(startDate, endDate)
Console.WriteLine("Jan 15 to Apr 18 should be 3 months, 3 days. Result:")
PrintPeriod(elapsed)
endDate = New DateTime(2011, 4, 12)
elapsed = CalculateRange(startDate, endDate)
Console.WriteLine("Jan 15 to Apr 12 should be 2 months, 28 days.")
PrintPeriod(elapsed)
End Sub
Sub TestLeapYear()
Dim startDate As New DateTime(2000, 2, 28)
Dim endDate As New DateTime(2000, 3, 1)
Dim elapsed = CalculateRange(startDate, endDate)
Console.WriteLine("Feb 28 2000 to Mar 1 2000 should be 2 days.")
PrintPeriod(elapsed)
End Sub
Sub TestAcrossYears()
Dim startDate As New DateTime(2011, 1, 1)
Dim endDate As New DateTime(2012, 1, 1)
Dim elapsed = CalculateRange(startDate, endDate)
Console.WriteLine("Jan 1 2011 to Jan 1 2012 should be 1 year.")
PrintPeriod(elapsed)
endDate = New DateTime(2011, 12, 31)
elapsed = CalculateRange(startDate, endDate)
Console.WriteLine("Jan 1 2011 to Dec 31 2011 should be 11 months, 30 days.")
PrintPeriod(elapsed)
startDate = New DateTime(2011, 1, 15)
endDate = New DateTime(2013, 5, 14)
elapsed = CalculateRange(startDate, endDate)
Console.WriteLine("Jan 15 2011 to May 14 2013 should be 2 years, 3 months, 29 days.")
PrintPeriod(elapsed)
End Sub
Private Sub PrintPeriod(ByVal period As TimePeriod)
Console.WriteLine("{0} years, {1} months, {2} days", period.Years, period.Months, period.Days)
End Sub
End Module
Class TimePeriod
Public Property Days As Integer
Public Property Months As Integer
Public Property Years As Integer
Public Sub DayElapsed()
Days += 1
End Sub
Public Sub MonthElapsed()
Months += 1
Days = 0
End Sub
Public Sub YearElapsed()
Years += 1
Months = 0
Days = 0
End Sub
End Class
My code's smaller and easier to understand, but it gets slow for very large timespans. The more days between the dates, the slower it gets. It's probably negligible for time periods of 100 years or so, but if you want more than that dbasnett's code is going to pull ahead. My technique's especially bad when you start wanting hours, minutes, and seconds for periods on the order of decades. dbasnett's is more likely to be correct than anything that tries to turn "8.77 months" into a reasonable time period.
I bet some work on the variable names in dbasnett's code could make it more approachable. Half of the complexity seems to stem from its support of some features that don't *need* to be there but are nice.
-
Re: Calculating exact time in Years, MOnths, Days
I updated post #2 in this thread. Still hoping to see less code.
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
blakemckenna
Thanks bro, but I know it can be done using alot less code...
There are times when attempting to do an operation with less code can make maintenance difficult which I have seen countless times with C developers who will do whatever it takes to condense a particular section of code, kind of like a badge of honor. Nothing wrong with this if you fully understand the code but if you do not because it is condensed then more code is better if you can understand that code. In addition, there are times when more lines of code can actually be faster.
-
Re: Calculating exact time in Years, MOnths, Days
here's my attempt at it:
vb Code:
Public Class Form1
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
'this counts inclusively. both the start day + end day are counted
Dim startDate As New Date(1969, 5, 3)
Dim endDate As Date = Now.Date
Dim totalYears As Integer
Dim totalMonths As Integer
Dim totalDays As Integer = CInt(Int((endDate - startDate).TotalDays))
If startDate.AddMonths(1) < endDate Then
totalDays -= (Date.DaysInMonth(startDate.Year, startDate.Month) - (startDate.Day - 1))
If totalDays > 0 Then totalMonths += 1 Else GoTo ignore
startDate = startDate.AddDays((Date.DaysInMonth(startDate.Year, startDate.Month) - startDate.Day))
Do
totalDays -= Date.DaysInMonth(startDate.Year, startDate.Month)
totalMonths += 1
startDate = startDate.AddMonths(1)
Loop While startDate <= endDate.AddMonths(-1)
totalYears = totalMonths \ 12
totalMonths = totalMonths Mod 12
Else
totalDays += 1
If totalDays >= Date.DaysInMonth(startDate.Year, startDate.Month) Then
totalDays -= Date.DaysInMonth(startDate.Year, startDate.Month)
totalMonths += 1
End If
End If
ignore:
MsgBox(String.Format("Years: {0}{3}Months: {1}{3}Days: {2}", totalYears, totalMonths, totalDays, Environment.NewLine))
End Sub
End Class
-
Re: Calculating exact time in Years, MOnths, Days
@paul - try these date pairs
2/29/1904 3/2/1904
2/29/1904 4/30/1904
1/1/2011 1/3/2011
1/1/2011 2/3/2011
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
dbasnett
@paul - try these date pairs
2/29/1904 3/2/1904
2/29/1904 4/30/1904
1/1/2011 1/3/2011
1/1/2011 2/3/2011
ok. it didn't work, but this does:D...
vb Code:
Public Class Form1
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
'this counts inclusively. both the start day + end day are counted
Dim startDate As New Date(2011, 1, 1)
Dim endDate As Date = New Date(2011, 3, 2)
Dim totalYears As Integer
Dim totalMonths As Integer = CInt(DateDiff(DateInterval.Month, startDate, endDate))
Dim totalDays As Integer = CInt(Int((endDate - startDate).TotalDays)) + 1
Dim monthDaySum As Integer = (Enumerable.Range(0, totalMonths).Select(Function(i) _
Date.DaysInMonth(startDate.AddMonths(i).Year, startDate.AddMonths(i).Month))).Sum
If monthDaySum > 0 Then totalDays -= monthDaySum
totalYears = totalMonths \ 12
totalMonths = totalMonths Mod 12
MsgBox(String.Format("Years: {0}{3}Months: {1}{3}Days: {2}", totalYears, totalMonths, totalDays, Environment.NewLine))
End Sub
End Class
-
Re: Calculating exact time in Years, MOnths, Days
But these show x months 1 day.
1/1/2011 2/1/2011
1/1/2011 3/1/2011
1/1/2011 4/1/2011
Shouldn't they be x months 0 days?
And these show negative days. (the start date is a leapling)
2/29/2004 3/1/2004
2/29/2004 3/2/2004
2/29/2004 3/3/2004
I struggled with these same issues.
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
dbasnett
But these show x months 1 day.
1/1/2011 2/1/2011
1/1/2011 3/1/2011
1/1/2011 4/1/2011
Shouldn't they be x months 0 days?
they're inclusive of the start + end day
Quote:
Originally Posted by
dbasnett
And these show negative days. (the start date is a leapling)
2/29/2004 3/1/2004
2/29/2004 3/2/2004
2/29/2004 3/3/2004
I struggled with these same issues.
this should work now:
vb Code:
Public Class Form1
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
'this counts inclusively. both the start day + end day are counted
Dim startDate As New Date(2004, 2, 29)
Dim endDate As New Date(2004, 3, 1)
Dim totalYears As Integer
Dim totalMonths As Integer = CInt(DateDiff(DateInterval.Month, startDate, endDate))
Dim totalDays As Integer = CInt(Int((endDate - startDate).TotalDays)) + 1
If totalDays < Date.DaysInMonth(startDate.Year, startDate.Month) Then totalMonths = 0
Dim monthDaySum As Integer = (Enumerable.Range(0, totalMonths).Select(Function(i) _
Date.DaysInMonth(startDate.AddMonths(i).Year, startDate.AddMonths(i).Month))).Sum
If monthDaySum > 0 Then totalDays -= monthDaySum
totalYears = totalMonths \ 12
totalMonths = totalMonths Mod 12
MsgBox(String.Format("Years: {0}{3}Months: {1}{3}Days: {2}", totalYears, totalMonths, totalDays, Environment.NewLine))
End Sub
End Class
-
Re: Calculating exact time in Years, MOnths, Days
2/29/2004 4/1/2004
2/29/2004 4/2/2004
2/29/2004 4/27/2004
2/29/2004 5/2/2004
negative days
-
Re: Calculating exact time in Years, MOnths, Days
On your birthday are you x years old or are you x years, 1 day old?
-
Re: Calculating exact time in Years, MOnths, Days
Datediff function excerpt from MSDN (http://msdn.microsoft.com/en-us/libr...(v=vs.80).aspx)
Quote:
Larger Intervals. If Interval is set to DateInterval.Year, the return value is calculated purely from the year parts of Date1 and Date2. Similarly, the return value for DateInterval.Month is calculated purely from the year and month parts of the arguments, and for DateInterval.Quarter from the quarters containing the two dates.
For example, when comparing December 31 to January 1 of the following year, DateDiff returns 1 for DateInterval.Year, DateInterval.Quarter, or DateInterval.Month, even though at most only one day has elapsed.
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
dbasnett
On your birthday are you x years old or are you x years, 1 day old?
on my next birthday i'll be x years old, but it'll be the first day of my (x + 1) year:D
-
Re: Calculating exact time in Years, MOnths, Days
Help please!!!!... My task is to make a VBA program in excel that will give the exact day on a date. For example, if the date is Feb. 1, 2011, the result must be 32, if the date is december 31, 2011, then the result must be 366. here is my code and it seems that it is not running,
Function DayOfYear(d as Integer)
Dim x as date
x=Range("A2")
d=month(x)
End Function
Sun mainDate()
Dim y as integer
MsgBox DayOFYEar(y)
End Sub
PLease HElp!!!
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
rj_yan31
Help please!!!!... My task is to make a VBA program in excel that will give the exact day on a date. For example, if the date is Feb. 1, 2011, the result must be 32, if the date is december 31, 2011, then the result must be 366. here is my code and it seems that it is not running,
Function DayOfYear(d as Integer)
Dim x as date
x=Range("A2")
d=month(x)
End Function
Sun mainDate()
Dim y as integer
MsgBox DayOFYEar(y)
End Sub
PLease HElp!!!
VBA question should be asked here:
http://www.vbforums.com/forumdisplay.php?f=37
-
Re: Calculating exact time in Years, MOnths, Days
Jeez let's not bring DateDiff into the equation. That function reeks of poor specification. "If you call it this way the return value is one less than the correct value, this is OK because we documented it." "Call it this way and we account for fractional values, call it this other way and simple math is used so you have to do your own fractional value detection." I'd rather calculate the values by spreading tea leaves in the bottom of a mug.
We've got two reasonable solutions that are guaranteed to work for leap years, differing dates, etc. because they rely on heavily-tested framework classes. Anything that involves manual calculations is less likely to be correct, and in a professional code base would increase the testing burden. I suppose that's what .paul. is attempting to prove in a roundabout manner: writing it yourself is madness.
-
Re: Calculating exact time in Years, MOnths, Days
and yet .paul. DID use the datediff function. Personally, I'd base the calculations using a TimeSpan object... it givesDays (and TotalDays) ... but doesn't give Months or Years as an option (really?) but between Days and TotalDays, it shouldn't be too hard to figure out the rest.
-tg
-
1 Attachment(s)
Re: Calculating exact time in Years, MOnths, Days
ok here's my complete working version (.dll + test app.):
vb Code:
Public Class calculate
Public Structure interval
Public startDate As Date
Public endDate As Date
Public years As Integer
Public months As Integer
Public days As Integer
End Structure
Public Shared Sub calculateTime(ByRef timeInterval As interval)
Dim ed As Date = timeInterval.endDate
Dim sd As Date = timeInterval.startDate
Dim totalDays As Integer = CInt((ed - sd).TotalDays)
Dim totalMonths As Integer = countMonths(totalDays, sd)
Dim monthDaySum As Integer = (Enumerable.Range(0, totalMonths).Select(Function(i) _
Date.DaysInMonth(sd.AddMonths(i).Year, sd.AddMonths(i).Month))).Sum
If sd.Day > Date.DaysInMonth(sd.AddMonths(totalMonths).Year, sd.AddMonths(totalMonths).Month) Then
monthDaySum -= (sd.Day - Date.DaysInMonth(sd.AddMonths(totalMonths).Year, sd.AddMonths(totalMonths).Month))
End If
If monthDaySum > 0 Then totalDays -= monthDaySum
Dim totalYears As Integer = totalMonths \ 12
totalMonths = totalMonths Mod 12
timeInterval.years = totalYears
timeInterval.months = totalMonths
timeInterval.days = totalDays
End Sub
Private Shared Function countMonths(ByVal totalDays As Integer, ByVal d1 As Date) As Integer
Dim sum As Integer = 0
Dim counter As Integer = 0
Do
sum += Date.DaysInMonth(d1.Year, d1.Month)
If sum <= totalDays Then
d1 = d1.AddMonths(1)
counter += 1
Else
Exit Do
End If
Loop
Return counter
End Function
End Class
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
Sitten Spynne
Jeez let's not bring DateDiff into the equation. That function reeks of poor specification. "If you call it this way the return value is one less than the correct value, this is OK because we documented it." "Call it this way and we account for fractional values, call it this other way and simple math is used so you have to do your own fractional value detection." I'd rather calculate the values by spreading tea leaves in the bottom of a mug.
Quote:
Originally Posted by
techgnome
and yet .paul. DID use the datediff function. Personally, I'd base the calculations using a TimeSpan object... it givesDays (and TotalDays) ... but doesn't give Months or Years as an option (really?) but between Days and TotalDays, it shouldn't be too hard to figure out the rest.
-tg
i wasn't aware how inaccurate DateDiff was until i'd tried it:D
Quote:
Originally Posted by
Sitten Spynne
We've got two reasonable solutions that are guaranteed to work for leap years, differing dates, etc. because they rely on heavily-tested framework classes. Anything that involves manual calculations is less likely to be correct, and in a professional code base would increase the testing burden. I suppose that's what .paul. is attempting to prove in a roundabout manner: writing it yourself is madness.
afraid of a little competition???
-
Re: Calculating exact time in Years, MOnths, Days
We agree on this
0 years, 0 months, 1 days between 2/29/2004 3/1/2004
0 years, 11 months, 30 days between 2/29/2004 2/28/2005
But not on this
1 years, 0 months, 1 days between 2/29/2004 3/1/2005
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
dbasnett
We agree on this
0 years, 0 months, 1 days between 2/29/2004 3/1/2004
0 years, 11 months, 30 days between 2/29/2004 2/28/2005
But not on this
1 years, 0 months, 1 days between 2/29/2004 3/1/2005
2/29/2004 to 3/1/2005 is 1 years, 0 months, 1 days
edit: this version is extensively tested + robust unlike my previous attempts
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
.paul.
2/29/2004 to 3/1/2005 is 1 years, 0 months, 1 days
edit: this version is extensively tested + robust unlike my previous attempts
Then why is this
1 years, 0 months, 0 days between 1/1/2010 1/1/2011
We both share this
'0 years, 6 months, 30 days between 7/29/2009 2/28/2010
'0 years, 7 months, 1 days between 7/29/2009 3/1/2010
-
Re: Calculating exact time in Years, MOnths, Days
2/29/2004 to 3/1/2004 is 0 years, 0 months, 1 days
so why is this wrong:
2/29/2004 to 3/1/2005 = 1 years, 0 months, 1 days
???
-
Re: Calculating exact time in Years, MOnths, Days
should be 1 year... since there is no 29th of Feb 2005... one year from 2/29/2004 is 3/1/2005 ... leap years are funny like that.
-tg
-
Re: Calculating exact time in Years, MOnths, Days
ok. try this then:
vb Code:
Dim d As Date = #2/29/2004#
MsgBox(d.AddYears(1))
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
.paul.
ok. try this then:
vb Code:
Dim d As Date = #2/29/2004#
MsgBox(d.AddYears(1))
It looks like a bug, for the reasons given by techgnome, doesn't it.
-
Re: Calculating exact time in Years, MOnths, Days
well, bearing in mind that the leapday has already occurred + the rest of the year is standard length, why is it a bug:
Dim d As Date = #2/29/2004#
MsgBox(d.AddDays(365))
?
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
.paul.
well, bearing in mind that the leapday has already occurred + the rest of the year is standard length, why is it a bug:
Dim d As Date = #2/29/2004#
MsgBox(d.AddDays(365))
?
How many days in 2004?
I get that this is not easy(as was suggested by blake). I have been screwing around with this since it was posted, and I am only close, and it all has to do with leap years.
Code:
Dim d As Date = #2/29/2004#
d = d.AddDays(366)
Debug.WriteLine(d.ToShortDateString)
d = #2/28/2005#
d = d.AddDays(365)
Debug.WriteLine(d.ToShortDateString)
-
1 Attachment(s)
Re: Calculating exact time in Years, MOnths, Days
ok i've grudgingly accepted i was wrong:D + i think i've solved it now:
edit: still unresolved... i've had enough...for now
-
Re: Calculating exact time in Years, MOnths, Days
DateDiff really works... i did it..
-
Re: Calculating exact time in Years, MOnths, Days
-
Re: Calculating exact time in Years, MOnths, Days
Hey this can be verry easy
I added two datetimepickers and one button
Code:
Public Class Form1
Dim day As Integer
Dim month As Integer
Dim year As Integer
Dim day1 As Integer
Dim month1 As Integer
Dim year1 As Integer
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Filter(DateTimePicker1.Value.ToShortDateString)
day1 = day
month1 = month
year1 = year
Filter(DateTimePicker2.Value.ToShortDateString)
MsgBox(day - day1 & "-" & month - month1 & "-" & year - year1)
End Sub
Public Function Filter(ByRef date1 As String)
Dim spliting As Object
spliting = Split(date1, "-")
day = spliting(0)
month = spliting(1)
year = spliting(2)
On Error GoTo 0
End Function
End Class
-
Re: Calculating exact time in Years, MOnths, Days
@rj - see the previous posts for the discussion of DateDiff. The TimeSpan provides a more accurate result.
@pieter - yeah, that's not going to work... first, ignoring the splitting of the date inaccurately, let's just look at this example:
Date 1 : 7/26/2011
Date 2 : 11/20/2011
breaking it down:
Day1 = 26
Month1 = 7
Year1 = 2011
Day = 20
Month = 11
Year = 2011
you used the following format:
day - day1 & "-" & month - month1 & "-" & year - year1
which comes out like so:
20 -26 = -6 incorrect...I don't think I need to go into why.
11 - 7 = 4 arbitrarily correct, but might not be
2011 - 2011 = 0 you kind of got lucky here... but if the second date had been 2011, then like the month, it would have been arbitrarily correct, but only depending on the rest of the date.
-tg
-
Re: Calculating exact time in Years, MOnths, Days
Hey my friend
Ju can adjust the day month and year how you want now the negative you can remove by splitting again because in this case the number is correct exept the negative.
If you have any questions I like to hear.
If you prefer emailing [email protected]
-
Re: Calculating exact time in Years, MOnths, Days
Still doesn't work... it appears to work... but consider the following dates:
2011-02-05 & 2011-03-03 .... by your numbers, that's 1 month (03-02) & 2 days (abs(03-05))... but it isn't... it's 28days... less than a month. your calcs work only under the assumption of 30day months.
consider still
2011-01-30 and 2011-02-05 ... your calc would have it as 1 month (02-01) & 25 days(abs(05-30)). Again, this isn't accurate.
Shall I go on? Try two dates that straddle the new year.
2011-12-15 & 2012-01-15... 1 year (2012-2011) 11 months (abs(01-12)) and 0 days (15-15) ... again...
-tg
-
Re: Calculating exact time in Years, MOnths, Days
This is how I test age (as years / days OR years / months / days) calculations.
I have a series of past dates and future dates that I use.
Code:
Dim dp() As Date = New Date() {#1/31/2004#, #2/29/2004#, #1/31/2005#}
Dim df() As Date = New Date() {#1/31/2009#, #2/28/2009#, #12/31/2009#}
The basic loop looks like this
Code:
For Each pastDate In dp
For Each futureDate In df
Dim stopAt As Date = futureDate
futureDate = pastDate.AddDays(1)
Do While futureDate <= stopAt
'calculate age then check
futureDate = futureDate.AddDays(1)
Loop
Next
Next
Basic age as years and days check
If days are greater than 365 it is an error.
If the years changes, then the days should be 0.
If the years are the same then the days should be one more than the previous day count.
Basic age as years, months, and days check
If days are greater than 31 it is an error.
If the year changes, then the days and months should be 0.
If the months changes, then the days should be 0.
If the months are the same then the days should be one more than the previous day count.
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
techgnome
Still doesn't work... it appears to work... but consider the following dates:
2011-02-05 & 2011-03-03 .... by your numbers, that's 1 month (03-02) & 2 days (abs(03-05))... but it isn't... it's 28days... less than a month. your calcs work only under the assumption of 30day months.
consider still
2011-01-30 and 2011-02-05 ... your calc would have it as 1 month (02-01) & 25 days(abs(05-30)). Again, this isn't accurate.
Shall I go on? Try two dates that straddle the new year.
2011-12-15 & 2012-01-15... 1 year (2012-2011) 11 months (abs(01-12)) and 0 days (15-15) ... again...
-tg
By my calculation 2011-02-05 & 2011-03-03 is 26 days (and 26 days when I use a calendar and count the days manually).
I didn't test pauls last submission because it did not lend itself to being put into my test rig.
-
Re: Calculating exact time in Years, MOnths, Days
So I figured out how to check pauls code using my test rig. Here is the first error sequence
0 years, 0 months, 28 days between 1/31/2004 2/28/2004
0 years, 0 months, 29 days between 1/31/2004 2/29/2004 <<<<<<<<<<<<<<<<<<<< s/b 1 month
0 years, 0 months, 30 days between 1/31/2004 3/1/2004 <<<<<<<<<<<<<<<<<<<< s/b 1 month 1 day
0 years, 1 months, 2 days between 1/31/2004 3/2/2004
and another
5 years, 0 months, -2 days between 2/29/2004 2/27/2009 A C
5 years, 0 months, -1 days between 2/29/2004 2/28/2009 A C
5 years, 0 months, 0 days between 2/29/2004 3/1/2009 C
5 years, 1 months, -28 days between 2/29/2004 3/2/2009 A C
5 years, 1 months, -27 days between 2/29/2004 3/3/2009 A C
-
Re: Calculating exact time in Years, MOnths, Days
My point was that the calculation that was being proposed of blindly day - day1, month - month1, year - year1 produces wrong results under some cases... enough of a problem to not be a viable solution imho.
-tg
-
Re: Calculating exact time in Years, MOnths, Days
Also... I'm not sure how you come to the conclusion that the results of Paul's code produces an error sequence... the values seem to reflect what I'd expect... ok... the second sequence isn't right... that's for sure... but the first sequence looks right to me... what's wrong with it?
-tg
-
Re: Calculating exact time in Years, MOnths, Days
Quote:
Originally Posted by
techgnome
Also... I'm not sure how you come to the conclusion that the results of Paul's code produces an error sequence... the values seem to reflect what I'd expect... ok... the second sequence isn't right... that's for sure... but the first sequence looks right to me... what's wrong with it?
-tg
I should have said
0 years, 0 months, 28 days between 1/31/2004 2/28/2004
0 years, 0 months, 29 days between 1/31/2004 2/29/2004 <<<<<<<<<<<<<<<<<<<< s/b 1 month
0 years, 0 months, 30 days between 1/31/2004 3/1/2004 <<<<<<<<<<<<<<<<<<<< s/b 1 month 1 day
0 years, 1 months, 2 days between 1/31/2004 3/2/2004
my mistake.
-
Re: Calculating exact time in Years, MOnths, Days
Hi guys,
Here's my attempt at this:
I tried a bunch of the Date Pairs from within this post and it seems to hold it's own.. so far!! Let me know if you can 'break' my function :)
VB.NET Code:
Public Class Form1
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Date1 As Date = DateTimePicker1.Value 'Lowest Date
Dim Date2 As Date = DateTimePicker2.Value 'Highest Date
'Use DateDiff for months. (Check the days values to see if an extra month was added when not needed).
Dim Months As Long = DateDiff(DateInterval.Month, Date1, Date2)
If Date2.Day < Date1.Day Then Months -= 1
'Workout years from months
Dim Years As Long = Nothing
If Months >= 12 Then
Do Until Months < 12
Years += 1
Months -= 12
Loop
End If
'Days
Dim Days As Long = Nothing
Dim FirstDay As Long = Date1.Day
Dim SecondDay As Long = Date2.Day
Select Case FirstDay
Case Is < SecondDay
'If the Day from Date1 is less than that of Date2 then we know datediff worked out the months upto the month of Date2.
'Therfore a simple subtraction will get the correct number of days.
Days = SecondDay - FirstDay
Case Is > SecondDay
'If Date1's day is greater than Date2's day then datediff looked at the months upto 1 month prior to Date2's month.
'That being the case, work out how many days are in that month and count the days from Date1's Day to the end of that month.
'Then we can just add the day value of Date2.
Dim MonthBeforeLast As Integer = Date2.Month
Dim Year As Integer = Date2.Year
If MonthBeforeLast = 1 Then
MonthBeforeLast = 12
Year -= 1
Else : MonthBeforeLast -= 1
End If
Dim DaysLeftInMonth As Long = Date.DaysInMonth(Year, MonthBeforeLast) - Date1.Day
Days = Date2.Day + DaysLeftInMonth
Case Is = SecondDay
'If the day values are equal then the number of days will always be 0
Days = 0
End Select
'Display results
Label1.Text = String.Format("Years: {1}{0}Months: {2}{0}Days: {3}{0}", {vbCrLf, Years, Months, Days})
End Sub
End Class
Hope this helps :)
Jay
-
Re: Calculating exact time in Years, MOnths, Days
@jay
0 years, 0 months, 29 days between 1/31/2004 2/29/2004
0 years, 1 months, -1 days between 1/31/2004 3/1/2004 A D
1 years, 0 months, 28 days between 1/31/2004 2/28/2005 C
1 years, 1 months, -2 days between 1/31/2004 3/1/2005 A C
1 years, 1 months, -1 days between 1/31/2004 3/2/2005 A C
1 years, 1 months, 0 days between 1/31/2004 3/3/2005 C
-
Re: Calculating exact time in Years, MOnths, Days
That was a little quicker than I hoped :)
I updated the function with something I had overlooked earlier. Can you test it for me now :thumb:
VB.NET Code:
Public Class Form1
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Date1 As Date = DateTimePicker1.Value 'Lowest Date
Dim Date2 As Date = DateTimePicker2.Value 'Highest Date
'Use DateDiff for months. (Check the days values to see if an extra month was added when not needed).
Dim Months As Long = DateDiff(DateInterval.Month, Date1, Date2)
If Date2.Day < Date1.Day Then Months -= 1
'Workout years from months
Dim Years As Long = Nothing
If Months >= 12 Then
Do Until Months < 12
Years += 1
Months -= 12
Loop
End If
'Days
Dim Days As Long = Nothing
Dim FirstDay As Long = Date1.Day
Dim SecondDay As Long = Date2.Day
If FirstDay > SecondDay And SecondDay = Date.DaysInMonth(Date2.Year, Date2.Month) And FirstDay = Date.DaysInMonth(Date1.Year, Date1.Month) Then
Months += 1
Days = 0
Else : Select Case FirstDay
Case Is < SecondDay
'If the Day from Date1 is less than that of Date2 then we know datediff worked out the months upto the month of Date2.
'Therfore a simple subtraction will get the correct number of days.
Days = SecondDay - FirstDay
Case Is > SecondDay
'If Date1's day is greater than Date2's day then datediff looked at the months upto 1 month prior to Date2's month.
'That being the case, work out how many days are in that month and count the days from Date1's Day to the end of that month.
'Then we can just add the day value of Date2.
Dim MonthBeforeLast As Integer = Date2.Month
Dim Year As Integer = Date2.Year
If MonthBeforeLast = 1 Then
MonthBeforeLast = 12
Year -= 1
Else : MonthBeforeLast -= 1
End If
'Edited code
Dim DaysLeftInMonth As Long = Date.DaysInMonth(Year, MonthBeforeLast)
If Date1.Day < DaysLeftInMonth Then DaysLeftInMonth -= Date1.Day Else : DaysLeftInMonth = 0
Days = Date2.Day + DaysLeftInMonth
Case Is = SecondDay
'If the day values are equal then the number of days will always be 0
Days = 0
End Select
End If
'Display results
Label1.Text = 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
0 years, 0 months, 29 days between 1/31/2004 2/29/2004
0 years, 1 months, 1 days between 1/31/2004 3/1/2004 D
0 years, 2 months, 30 days between 1/31/2005 4/30/2005
0 years, 3 months, 1 days between 1/31/2005 5/1/2005 D
What happened to x months 0 days?
0 years, 5 months, 0 days between 2/29/2004 7/29/2004
0 years, 5 months, 1 days between 2/29/2004 7/30/2004
0 years, 5 months, 2 days between 2/29/2004 7/31/2004
0 years, 5 months, 1 days between 2/29/2004 8/1/2004 D
I posted how I was testing this in Post #42. (Hint)
-
Re: Calculating exact time in Years, MOnths, Days
I edited the code to fix the x months 0 days after making that last post :)
Although I can see another problem with this now...
-
Re: Calculating exact time in Years, MOnths, Days
Right ok, I think my latest effort cracked it, fingers crossed :)
VB.NET Code:
Public Class Form1
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Date1 As Date = DateTimePicker1.Value 'Lowest Date
Dim Date2 As Date = DateTimePicker2.Value 'Highest Date
'Use DateDiff for months. (Check the days values to see if an extra month was added when not needed).
Dim Months As Long = DateDiff(DateInterval.Month, Date1, Date2)
If Date2.Day < Date1.Day Then Months -= 1
'Workout years from months
Dim Years As Long = Nothing
If Months >= 12 Then
Do Until Months < 12
Years += 1
Months -= 12
Loop
End If
'Days
Dim Days As Long = Nothing
Dim FirstDay As Long = Date1.Day
Dim SecondDay As Long = Date2.Day
'Check for end of the month dates (to be handled differently)
If SecondDay = Date.DaysInMonth(Date2.Year, Date2.Month) And FirstDay = Date.DaysInMonth(Date1.Year, Date1.Month) Then
Days = 0
ElseIf FirstDay = Date.DaysInMonth(Date1.Year, Date1.Month) And SecondDay < Date.DaysInMonth(Date2.Year, Date2.Month) Then
If SecondDay >= FirstDay Then Months -= 1
Days = SecondDay
Else : Select Case FirstDay
'Otherwise just calculate the days ourselves
Case Is < SecondDay
'If the Day from Date1 is less than that of Date2 then we know datediff worked out the months upto the month of Date2.
'Therfore a simple subtraction will get the correct number of days.
Days = SecondDay - FirstDay
Case Is > SecondDay
'If Date1's day is greater than Date2's day then datediff looked at the months upto 1 month prior to Date2's month.
'That being the case, work out how many days are in that month and count the days from Date1's Day to the end of that month.
'Then we can just add the day value of Date2.
Dim MonthBeforeLast As Integer = Date2.Month
Dim Year As Integer = Date2.Year
If MonthBeforeLast = 1 Then
MonthBeforeLast = 12
Year -= 1
Else : MonthBeforeLast -= 1
End If
Dim DaysLeftInMonth As Long = Date.DaysInMonth(Year, MonthBeforeLast) - Date1.Day
Days = Date2.Day + DaysLeftInMonth
Case Is = SecondDay
'If the day values are equal then the number of days will always be 0
Days = 0
End Select
End If
'Display results
Label1.Text = String.Format("Years: {1}{0}Months: {2}{0}Days: {3}{0}", {vbCrLf, Years, Months, Days})
End Sub
End Class
In any case, a better DateDiff function would be nice for the future :)
-
Re: Calculating exact time in Years, MOnths, Days
-
Re: Calculating exact time in Years, MOnths, Days
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
I count 2 correct results: #2 and #8. After that, 9 attempts to do the same thing other ways were proposed (.paul.'s tried and failed at least 5 times!) I stand my my assertion that my "increment days and update counter" approach is the most accurate way to do this. Everyone who's tried another technique has failed. Do we really need to keep trying?
There's an interesting discussion from #24-#34. Do we call 1/1/2005 to 1/1/2006 1 year exactly? If so, what about 1/1/2004-1/1/2005? 2004 was a leap year so that's technically 366 days. I say that's still 1 year, 0 days. Years are not 365 days every year. Some years they are 366 days. This is why you can only *approximate* days using years \ 365. Perhaps you might encounter some business logic that wants to call that span 1 year and 1 day; you can modify the algorithm accordingly. I don't think "I think you should have one day more for this leap year period" is a bug because in my eyes the behavior is correct.
Quote:
Originally Posted by
.paul.
afraid of a little competition???
Hardly. DateDiff() is objectively awful. Just read the documentation; .NET has entire classes with less documentation than this one little function. The documentation also outlines situations where the return value isn't what you'd expect and in some cases is inaccurate. It takes as long to discover its pitfalls as it does to implement another solution. As further evidence, I present your failed attempts to use it. If an MS Community Contributor with 11k posts and dozens of tutorials under his belt can't use DateDiff() properly, what hope does the average developer have? This is why I retch when I see it.
What I'm afraid of is giving users incorrect advice. Many people copy/paste forum examples and don't bother testing until deployment. That's a dumb approach to coding, but it's out there and odds are I'm going to be affected by it one day. I wanted to dismiss conversation of DateDiff() immediately because any errors involving DateDiff() are usually subtle and complex. Just look at the damage it's done: we've got nearly failed 10 attempts to wrangle DateDiff() into something predictable. You've got 11k posts to my 500 or so; that means you'll usually win the tiebreaker and your posts have more perceived authority. That makes it all the more tragic when your code is incorrect. I was also quite agitated that proposing new solutions implies the previous solutions have a flaw, though none have been raised against my code or dbasnett's. Your code's been attacked because it produces incorrect results. What's wrong with ours? It's odd there's so much discussion about a solved problem.
DateDiff() was designed for a structured programming world. Even in structured programming, it's generally agreed functions with multiple behaviors are confusing. In the OOP world, it would be best represented as many methods of a class: MonthsBetween(), YearsBetween(), etc. 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.
-
Re: Calculating exact time in Years, MOnths, Days
As of now there aren't any correct answers, IMHO. Here are some of the results, using your code, from my test (post #42).
0 years, 0 months, 29 days between 1/31/2004 2/29/2004
0 years, 0 months, 30 days between 1/31/2004 3/1/2004
0 years, 0 months, 31 days between 1/31/2004 3/2/2004
0 years, 0 months, 32 days between 1/31/2004 3/3/2004 A
0 years, 0 months, 33 days between 1/31/2004 3/4/2004 A
The problems are boundaries (for everyone as far as I can tell).
If you are in the Doctors office on 1/31/2004 and they say they will see you, in a month or in a year I take it to mean 2/29/2004 and 1/31/2005 respectively.
If you are in the Doctors office on 1/31/2005 and they say they will see you, in a month or in a year I take it to mean 2/28/2005 and 1/31/2006 respectively.
If anyone has issue with how I test, or how I define the boundaries, please let me know. This thread is painful proof that this is hard.
-
Re: Calculating exact time in Years, MOnths, Days
Using post #42 to test the output, I have tweaked my code to the following:
VB.NET Code:
Public Class Form1
'Date1 = LowestDate, Date2 = Highest Date
Private Sub DateCompare(ByVal Date1 As Date, ByVal date2 As Date)
'Get the Months first
Dim Months As Long = ((date2.Year * 12) + date2.Month) - ((Date1.Year * 12) + Date1.Month) - 1
'Days
Dim Days As Long = Nothing
Dim FirstDay As Long = Date1.Day
Dim SecondDay As Long = date2.Day
'Check for end of the month dates (to be handled differently)
If SecondDay = Date.DaysInMonth(date2.Year, date2.Month) And FirstDay = Date.DaysInMonth(Date1.Year, Date1.Month) Then
Months += 1
Days = 0
ElseIf FirstDay = Date.DaysInMonth(Date1.Year, Date1.Month) And SecondDay < Date.DaysInMonth(date2.Year, date2.Month) Then
If SecondDay >= FirstDay Then Months += 1
Days = SecondDay
Else : Select Case FirstDay
'Otherwise just calculate the days ourselves
Case Is < SecondDay
'If the Day from Date1 is less than that of Date2 then we know datediff worked out the months upto the month of Date2.
'Therfore a simple subtraction will get the correct number of days.
Days = SecondDay - FirstDay
Case Is > SecondDay
'If Date1's day is greater than Date2's day then datediff looked at the months upto 1 month prior to Date2's month.
'That being the case, work out how many days are in that month and count the days from Date1's Day to the end of that month.
'Then we can just add the day value of Date2.
Dim MonthBeforeLast As Integer = date2.Month
Dim Year As Integer = date2.Year
If MonthBeforeLast = 1 Then
MonthBeforeLast = 12
Year -= 1
Else : MonthBeforeLast -= 1
End If
Dim DaysLeftInMonth As Long = Date.DaysInMonth(Year, MonthBeforeLast) - Date1.Day
Days = date2.Day + DaysLeftInMonth
Case Is = SecondDay
'If the day values are equal then the number of days will always be 0
Days = 0
End Select
End If
'Workout years from months
Dim Years As Long = Nothing
If Months >= 12 Then
Do Until Months < 12
Years += 1
Months -= 12
Loop
End If
'Display results
MsgBox(String.Format("Years: {1}{0}Months: {2}{0}Days: {3}{0}", {vbCrLf, Years, Months, Days}))
End Sub
End Class
As far as I can see it gets the desired result every time now :)
-
Re: Calculating exact time in Years, MOnths, Days
Here is the code to test with(crude I know). The last attempt by jay did not work.
Code:
Dim dp() As Date = New Date() {#1/31/2004#, #2/29/2004#, #1/31/2005#}
Dim df() As Date = New Date() {#1/31/2009#, #2/28/2009#, #12/31/2009#}
Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click
Debug.WriteLine("")
Debug.WriteLine("")
Dim pastDate As DateTime
Dim futureDate As DateTime
Dim foo As New Age
Dim sb As New System.Text.StringBuilder
For Each pastDate In dp
Debug.WriteLine("")
For Each futureDate In df
Dim stopAt As Date = futureDate
futureDate = pastDate.AddDays(1)
Dim ly As Integer = 0
Dim ld As Integer = 0
Dim lm As Integer = 0
Dim isErr As String = ""
Do While futureDate <= stopAt
'checks
foo.DateFuture = futureDate
foo.DatePast = pastDate
'PUT YOUR CALL TO CALC HERE
foo = myAgeCalc.AgeInYearsMonthsDays()
'<<<<<<<<<
If foo.Days < 0 OrElse foo.Days > 31 Then
isErr &= "A "
End If
If foo.Months < 0 OrElse foo.Months > 11 Then
isErr &= "B "
End If
If foo.Years <> ly Then
If foo.Years = ly + 1 AndAlso foo.Months = 0 AndAlso foo.Days = 0 Then
ly = foo.Years
ld = 0
lm = 0
Debug.WriteLine("New Year")
Else
isErr &= "C "
End If
ElseIf foo.Months <> lm Then
If foo.Months = lm + 1 AndAlso foo.Days = 0 Then
lm = foo.Months
ld = 0
Else
isErr &= "D "
lm = foo.Months
ld = 0
End If
ElseIf foo.Days <> ld + 1 Then
isErr &= "D "
ld = foo.Days
Else
ld = foo.Days
End If
Debug.Write(String.Format("{0} years, {1} months, {2} days between {3} {4} ", foo.Years, foo.Months, foo.Days, pastDate.ToShortDateString, futureDate.ToShortDateString))
If isErr <> "" Then
Debug.WriteLine(isErr)
sb.AppendLine(isErr & pastDate.ToShortDateString & " " & futureDate.ToShortDateString)
isErr = "" 'for error stop
Else
Debug.WriteLine("")
End If
futureDate = futureDate.AddDays(1)
Loop
Next
Next
If sb.Length <> 0 Then
Stop
'My.Computer.Clipboard.SetText(sb.ToString)
End If
End Sub
Code:
Class Age
Private _Years As Integer
Public Property Years() As Integer
Get
Return Me._Years
End Get
Set(ByVal value As Integer)
Me._Years = value
End Set
End Property
Private _Months As Integer
Public Property Months() As Integer
Get
Return Me._Months
End Get
Set(ByVal value As Integer)
Me._Months = value
End Set
End Property
Private _Days As Integer
Public Property Days() As Integer
Get
Return Me._Days
End Get
Set(ByVal value As Integer)
Me._Days = value
End Set
End Property
Private _DatePast As DateTime
Public Property DatePast() As DateTime
Get
Return Me._DatePast
End Get
Set(ByVal value As DateTime)
Me._DatePast = value
Me.setInterval()
End Set
End Property
Private _DateFuture As DateTime
Public Property DateFuture() As DateTime
Get
Return Me._DateFuture
End Get
Set(ByVal value As DateTime)
Me._DateFuture = value
Me.setInterval()
End Set
End Property
Private Sub setInterval()
Me._Interval = (Me.DateFuture - Me.DatePast).Duration
End Sub
Private _Interval As TimeSpan
Public ReadOnly Property Interval() As TimeSpan
Get
Return Me._Interval
End Get
End Property
End Class
-
Re: Calculating exact time in Years, MOnths, Days
Just curious which dates it failed on?
I will implement your test later as I am on my way out :thumb: