Get years, months, days for an elapsed span of tim

2019-02-19 14:45发布

问题:

How can I display an age as Years, Months, Days from a datetime picker value. Eg:

Datetimepicker value = #1/11/2014#
Today = #1/12/2015#.  

The final result would be "1 Year, 0 Months, 1Day(S)". But getting that result is more than just subtracting the DateTime.Year values and so forth. Does anybody know a mathematics formula or mathematics calculation about that?

回答1:

Here is a DateTimeSpan Type which returns an object like a TimeSpan representing the Yrs, Months, Days elapsed between 2 dates. It loops thru the time units to increment them until the smaller date matches the larger. The resulting increments are then returned in a new DateTimeSpan object.

Public Structure DateTimeSpan

    Private _Years As Integer
    Public ReadOnly Property Years As Integer
        Get
            Return _Years
        End Get
    End Property

    Private _Months As Integer
    Public ReadOnly Property Months As Integer
        Get
            Return _Months
        End Get
    End Property

    Private _Days As Integer
    Public ReadOnly Property Days As Integer
        Get
            Return _Days
        End Get
    End Property

    Private _Hours As Integer
    Public ReadOnly Property Hours As Integer
        Get
            Return _Hours
        End Get
    End Property

    Private _Minutes As Integer
    Public ReadOnly Property Minutes As Integer
        Get
            Return _Minutes
        End Get
    End Property

    Private _Seconds As Integer
    Public ReadOnly Property Seconds As Integer
        Get
            Return _Seconds
        End Get
    End Property

    Private _MilliSeconds As Integer
    Public ReadOnly Property MilliSeconds As Integer
        Get
            Return _MilliSeconds
        End Get
    End Property

    ' the ctor for the result
    Private Sub New(y As Integer, mm As Integer, d As Integer,
                    h As Integer, m As Integer, s As Integer,
                    ms As Integer)
        _Years = y
        _Months = mm
        _Days = d
        _Hours = h
        _Minutes = m
        _Seconds = Seconds
        _MilliSeconds = ms
    End Sub

    ' private time unit tracker when counting
    Private Enum Unit
        Year
        Month
        Day
        Complete
    End Enum

    Public Shared Function DateSpan(dt1 As DateTime, 
                                    dt2 As DateTime) As DateTimeSpan
        ' we dont do negatives
        If dt2 < dt1 Then
            Dim tmp = dt1
            dt1 = dt2
            dt2 = tmp
        End If

        Dim thisDT As DateTime = dt1
        Dim years As Integer = 0
        Dim months As Integer = 0
        Dim days As Integer = 0

        Dim level As Unit = Unit.Year
        Dim span As New DateTimeSpan()

        While level <> Unit.Complete
            Select Case level
                ' add a year until it is larger;
                ' then change the "level" to month
                Case Unit.Year
                    If thisDT.AddYears(years + 1) > dt2 Then
                        level = Unit.Month
                        thisDT = thisDT.AddYears(years)
                    Else
                        years += 1
                    End If

                Case Unit.Month
                    If thisDT.AddMonths(months + 1) > dt2 Then
                        level = Unit.Day
                        thisDT = thisDT.AddMonths(months)
                    Else
                        months += 1
                    End If

                Case Unit.Day
                    If thisDT.AddDays(days + 1) > dt2 Then
                        thisDT = thisDT.AddDays(days)
                        Dim thisTS = dt2 - thisDT
                        ' create a new DTS from the values caluclated
                        span = New DateTimeSpan(years, months, days, thisTS.Hours,
                                                thisTS.Minutes, thisTS.Seconds,
                                    thisTS.Milliseconds)
                        level = Unit.Complete
                    Else
                        days += 1
                    End If
            End Select
        End While

        Return span

    End Function

End Structure

Usage:

Dim dts As DateTimeSpan = DateTimeSpan.DateSpan(#2/11/2010#, #10/21/2013#)
Console.WriteLine("{0} Yrs, {1} Months and {2} Days",
                           dts.Years.ToString, dts.Months.ToString, dts.Days.ToString)

This will give the same result:

Dim dtStart As DateTime = #2/11/2010#
Dim dtEnd As new DateTime(2013, 10, 21)
' this is NOT a date and wont work:
Dim myDt = "2/11/2010"             ' its a string!

Dim dts As DateTimeSpan = DateTimeSpan.DateSpan(dtEnd, dtStart)

Result: 3 Yrs, 8 Months and 10 Days

Notes:
- It works only on proper DateTime types, not strings which look like dates. Use literals (#...#) or DateTime variables. When using a DateTimePicker, use .Value not .Text.
- It does not do negative values, so the order of values passed does not matter
- When calculating, it increments a DateTime variable so, it should properly account for leap days

This is based on some C# code I found long ago in a blog (or perhaps even from an SO answer or question). Cant find it just now to cite the original.



回答2:

This method uses loops to work this backwards, but I believe this is the only accurate way to do this and get the values always correct. This is because it is more complicated than you might think - you need to adjust for leap years, etc.

''' <summary>
''' Finds the Years Months and Days Span between two dates
''' </summary>
''' <param name="fromDate"></param>
''' <param name="toDate"></param>
''' <returns>A String formatted as i Years j Months k Days</returns>
''' <remarks></remarks>
Public Shared Function GetDateSpanText(ByVal fromDate As DateTime, Optional toDate As DateTime = Nothing) As String
        Dim years As Integer = 0, months As Integer = 0, days As Integer = 0
        If toDate = Nothing Then toDate = DateTime.Now

        Do Until toDate.AddYears(-1) < fromDate
            years += 1
            toDate = toDate.AddYears(-1)
        Loop

        Do Until toDate.AddMonths(-1) < fromDate
            months += 1
            toDate = toDate.AddMonths(-1)
        Loop

        Do Until toDate.AddDays(-1) < fromDate
            days += 1
            toDate = toDate.AddDays(-1)
        Loop

        Return String.Format("{0} Year(s) {1} Month(s) {2} Day(s)", years, months, days)
End Function

Usage:

Debug.WriteLine(GetDateSpanText(#1/11/2014#))

Prints:

1 Year(s) 0 Month(s) 1 Day(s)


回答3:

This method is very straight forward and simple.
Creating loops and class properties is not needed.

This will work with both real Date types and Strings that are dates:

   'Date Picker Value
    Dim dBirthDate As Date = "#01/11/2014#"

    'Today
    Dim dToday As Date = "#01/12/2015#"

    Dim dBirthDay As Date

    Dim sYears As String
    Dim sDays As String
    Dim sMonths As String
    Dim sResults As String


    sYears = DateDiff(DateInterval.Year, dBirthDate, dToday)

    dBirthDay = dBirthDate.AddYears(sYears)

    sMonths = DateDiff(DateInterval.Month, dBirthDay, dToday)

    sDays = DateDiff(DateInterval.Day, dBirthDay, dToday)


    sResults = sYears & " Year " & sMonths & " Months " & sDays & " Day(s)"

Example result: 1 Year 0 Months 1 Day(s)



回答4:

you must add this to get a complete result: sDays = (DateDiff(DateInterval.Day, dBirthDay, dToday)) - sMonths * 30