Why does TimeSpan not have a Years property?
Asked Answered
D

6

28

I was writing a converter that takes a person's date of birth and produces their age in years. I wrote something that looked like this:

public class DateOfBirthToAgeConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var date = value as DateTime?;
        if (date == null) return null;
        return (DateTime.Now - date).Years;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

I found that there is no Years property on the TimeSpan that results from the subtraction of two DateTime objects. I was somewhat surprised by this. I thought about why there might not be a Years. I figured that it might be because of the leap day, but by that logic, there shouldn't be Days because of daylight savings.

The absence of Months made sense, since there is no standard month length.

I was able to write some different code to get the correct age, but I still really want to know why there is no Years or Weeks property on TimeSpan. Does anyone know the reason?

Dominik answered 5/5, 2015 at 17:15 Comment(17)
It may be a continuum - leap days/years are universal within the Gregorian calendar - but not all cultures make use of daylight savings. Not trying to say either or both concepts should be built in, but one is more prevalent than the other.Scalp
I don't think TimeSpan fits your purpose. Check this out: Choosing Between DateTime, DateTimeOffset, TimeSpan, and TimeZoneInfoDogear
"Why does X not have Y" are usually not the best questions here. You already mentioned the difficulties in defining what a year is exactly.Monachism
Even though the difference between two dates can be 23hrs or 25hrs due to daylight savings, a "day" as a duration still has a fixed definition of 24hrs.Lamp
Do all calendars have 365 day years?Dominik
You could extend the question and ask why there isn't a decade, century, millenium etc. property. You need to make a cut somewhere. And making the cut at days seems to fit most common usages of Timespan.Safar
It is worth checking out the NodaTime library that is maintained by Jon Skeet. It is a much stronger abstraction than the built-in DateTime and Timespan classes. From "the horse's mouth": blog.nodatime.org/2011/08/what-wrong-with-datetime-anyway.htmlSearcy
@Lamp No, not necessarilyPostwar
@TimLong - Agreed. Here is how to calculate age using Noda Time. See also my blog post Handling Birthdays, and Other Anniversaries.Cupulate
@Postwar - leaps seconds and daylight saving times function the same way, they're properties of a calendar not properties of a duration of time. Unless you're advocating that we accept sometimes a second can be two seconds long. My point and example stand as far as I am concerned.Lamp
@Lamp That's exactly what it is though, the duration of a day changes on the DST shifts, as well as (very slightly) when a leap second is added. Just like the duration of a year changes on a leap year, except leap years are more predictable than leap seconds - it's just that the shift is largerPostwar
Here's the algorithm XSchema uses: w3.org/TR/xmlschema-2/#adding-durations-to-dateTimesHaiduk
@Postwar - No, the duration of 21st July changes (property of a calendar). The duration of a day does not (A unit of time).Lamp
@Lamp In the same way you can say the duration of a month is 30 days but the duration of August is 31 days? This is actually the way the programmers ignoring the leap seconds and daylight savings.Kutch
@EarthEngine except that you're plainly ignoring something I'm explicitly stating... There is NO standard definition of a unit of time called A Month, but there IS a unit of time called a DAY. You have conflated properties of a calendar with units of time. If you do that then you have NO definition for hours or minutes or seconds either; they would ALL be impacted by leap seconds. Just because YOU have that opinion doesn't negate the current fact that a Day DOES have a definition.Lamp
@EarthEngine An "SI Day" IS 86400 seconds. A "Civil Day" can be 86400 +/- 1s. But we DON'T measure time in Civil Units, we use SI Units.Lamp
youtube.com/watch?v=-5wpm-gesOYWirework
K
13

Programmer's life is really hard.

The length of year is variable. Some years have 365 days and some have 366 days. According to the calendar, some years could even have missing days. If talking about culture it becomes more difficult since Chinese lunar calendar can have 13 months a year.

The length of month is variable, and this is well-known. This is also to know that in other calendars things can get worse.

The length of day is variable, because of daylight savings and this is not just culture dependent but also geography dependent.

The length of hour and minute are variable, because of leap seconds.

It seems the only thing that is reliable is the length of a second. So internally, timespan is stored in seconds (or milliseconds, which is the same).

But the variability of time units makes the answer "how many (years/months/days/hours/minites) for n seconds?" being always inaccurate.

This is why the developers end up with a solution that is useful in practical but not precise. They simply ignore daylight savings and leap seconds. However, since people hardly ask about years and months, they just decided not to answer those questions.

Kutch answered 6/5, 2015 at 0:1 Comment(1)
Interesting fact: at google they vary the length of a second to accommodate leap smears: developers.google.com/time/smear#example_of_the_standard_smearCybill
B
23

A TimeSpan only contains the difference between two DateTime values. It is unknown which year this TimeSpan is in. That's also why it doesn't have a Months property.

Example:

TimeSpan.FromDays(60)

How many months are that? 1 or 2?


The absence of Months made sense since there is no standard month length.

There is no standard year length either because of leap years.

Workaround: If you really want to display an approximate value, then doing TimeSpan.TotalDays / 365.2425 will do just fine.

Edit: But only for rough estimations and not for birthdays. In birthday calculation, leap days will accumulate every 4 years as pointed out by Henk Holterman in the comments. Take a look here for calculation of birthdays.

Branch answered 5/5, 2015 at 17:23 Comment(5)
With "approximately" I mean something like "User X has been for 7 years on StackOverflow". In this case, it's good. For birthdays (ages) it's probably not, but I would like to point that out anyway.Branch
365.2425, the mean average Gregorian year length, works well enough.Haiduk
@HenkHolterman For anything that uses the Gregorian calendar. Doesn't help for lunar, Jewish, Chinese, sidereal, &c calendars.Haiduk
@HenkHolterman It's not an approximation. It's the definition of the Gregorian calendar. You may be defining "exact" in a way that calendar doesn't support. Do you have an example where it fails?Haiduk
The definition of the Greg calendar does not use fractions. This is an approximation, valid results are a coincidence.Monachism
K
13

Programmer's life is really hard.

The length of year is variable. Some years have 365 days and some have 366 days. According to the calendar, some years could even have missing days. If talking about culture it becomes more difficult since Chinese lunar calendar can have 13 months a year.

The length of month is variable, and this is well-known. This is also to know that in other calendars things can get worse.

The length of day is variable, because of daylight savings and this is not just culture dependent but also geography dependent.

The length of hour and minute are variable, because of leap seconds.

It seems the only thing that is reliable is the length of a second. So internally, timespan is stored in seconds (or milliseconds, which is the same).

But the variability of time units makes the answer "how many (years/months/days/hours/minites) for n seconds?" being always inaccurate.

This is why the developers end up with a solution that is useful in practical but not precise. They simply ignore daylight savings and leap seconds. However, since people hardly ask about years and months, they just decided not to answer those questions.

Kutch answered 6/5, 2015 at 0:1 Comment(1)
Interesting fact: at google they vary the length of a second to accommodate leap smears: developers.google.com/time/smear#example_of_the_standard_smearCybill
L
6

Rhetorical question: Without a point of reference, how long is a year?

Because a TimeSpan does not have a fixed point in time, it is not possible to unambiguously say how long a year at an unknown time will be. In the simplest case, it might be 365 or 366 days. There are considerably more cases that would affect the outcome.

Leolaleoline answered 5/5, 2015 at 17:21 Comment(8)
Already stated in the question. And you don't need a PoR for for example a Julian Year.Monachism
According to Google, it's 365.242199 days.Thionate
And according to Wikipedia it's 365.2425.Monachism
@IllidanS4 here is a nice and short video explaining some of the difficulties in simply defining what a year is, how long it might be, and why coming up with a number isn't quite so easy.Cannon
@Cannon - you really think I need to watch that?Monachism
@HenkHolterman You certainly don't need to. You may or may not want to.Cannon
@IllidanS4 - From a calendering perspective, some years have 365 days, and some years have 366 (in the Gregorian calendar). Fractions of a year do not make sense for many scenarios. For example, how many days are there between midnight Jan 1st of one year and midnight Jan 1st the next? It depends on the year, 365 or 366. It would not make any sense to answer with a fraction, unless you intended to land on a different time of day.Cupulate
@MattJohnson You round off the fraction after dividing the number of days.Haiduk
H
2

I figured that it might be because of the leap day, but by that logic, there shouldn't be Days because of daylight savings.

You have a point there; subtracting two dates doesn't handle daylight savings ideally. If the dates are local time, you may get an unexpected result.

A change in daylight savings time means a gap or overlap in the local time, and that is ignored if you do calculations with the dates. So, if you want to get the exact difference between two DateTime values that are local time, you should convert them to UTC first as that has linear time:

TimeSpan diff = date1.ToUniversalTime() - date2.ToUniversalTime();

The reason that the TimeSpan doesn't have years is that years differ in length. The daylight savings issue is an effect of how you calculate the TimeSpan and can be circumvented, but there is no "linear years" that you can use to circumvent leap years.

Hagride answered 5/5, 2015 at 17:30 Comment(1)
Why the downvote? If you don't explain what it is that you think is wrong, it can't improve the answer.Hagride
M
1

Timespan simply stores number of milliseconds. If you have (1000 * 60 * 60 * 24 * 365.5) 365.5 days worth of milliseconds it's impossible to know if that number of milliseconds spans an entire year and into the next, if it's just short of a year, or if it spans across three years. Same with 30.5 days worth of milliseconds, could span into a second month, could be less than a month, could span across three months.

Monohydric answered 5/5, 2015 at 17:51 Comment(1)
30.5 days could be 2 months, if it starts on the last day in January, for instance.Tini
D
1

Assuming you can start with 2 dates and not a single TimeSpan, then you can get the difference in years like this...

The DateTime.AddYears() function has already solved the majority of issues you may encounter with leap years etc. So for a difference in years, start with the earliest of the two dates and repeatedly add 1 year using .AddYears(1) and increment a counter till you have a date which exceeds the later date of the two and the difference in years will be the value of the counter -1.

If you think the 2 dates may be thousands of years apart, you could adapt this logic to repeatedly +100 years, when the later date is exceeded, repeatedly -10 years, when you go below the higher date, finally +1 again till the higher date is exceeded. If you + and - from the counter the number of years you are adding and taking away, you'll end up with counter -1 being the number of whole years difference again.

public int AgeInYears(DateTime date1, DateTime date2)
{
    int age = 0;
    DateTime low;
    DateTime high;
    DateTime test;
    if(date1 < date2)
    {
        low = date1;
        high = date2;
    }
    else
    {
        low = date2;
        high = date1;
    }
    
    test = low;
    while(test < high)
    {
        test = test.AddYears(100);
        age += 100;
    }
    
    while (test > high)
    {
        test = test.AddYears(-10);
        age -= 10;
    }
    
    while(test <= high)
    {
        test = test.AddYears(1);
        age++;
    }

    return age - 1;
}
Draught answered 6/2, 2023 at 13:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.