Tweaking our computation of the interval between two moments in time

We can take our computation of the interval between two moments in time and combine it with the trick we developed for using the powers of mathematics to simplify multi-level comparisons to reduce the amount of work we impose upon the time/date engine.

static void PrintAge(DateTime bday, DateTime asof)
 // Accumulate years without going over.
 int years = asof.Year - bday.Year;
 if (asof.Month*32 + asof.Day < bday.Month*32 + bday.Day) years--;
 DateTime t = bday.AddYears(years);

 // Accumulate months without going over.
 int months = asof.Month - bday.Month;
 if (asof.Day < bday.Day) months--;
 months = (months + 12) % 12;
 t = t.AddMonths(months);

 // Days are constant-length, woo-hoo!
 int days = (asof - t).Days;

 SC.WriteLine("{0} years, {1} months, {2} days",
              years, months, days);

Observe that we avoided a call to the AddYears method (which is presumably rather complicated because years are variable-length) by replacing it with a multi-level comparison to determine whether the ending month/day falls later in the year than the starting month/day. Since no month has 32 days, a multiplier of 32 is enough to avoid an overflow of the day into the month field of the comparison key.

Comments (6)
  1. df says:

    // Days are constant-length, woo-hoo!

    Because of leap-seconds this is not entirely accurate.

    There is a known law of software that anything to do with dates and times is suspect.

  2. Jonathan Payne says:

    Does anything in Windows take account of leap seconds? If I used an embedded version of Windows for my time machine and wanted to go back to 1601, should I pass the time machine the number of nano-seconds in the FILETIME structure for the current time or should I add on a few more for leap seconds? I hope Microsoft isn’t ignoring the market for time machine operating systems.

  3. Norman Diamond says:

    This has been discussed before. Posix-compatible systems are required to have time-of-day clocks that are already incorrect by around 20 seconds, and Windows systems are compatible with that whether or not their Posix subsystems are being used.

    The part I can’t figure out is why, after a Windows system uses NTP to nearly synchronize itself with an atomic clock (after two or three intermediaries), how come it doesn’t adjust itself to 20 seconds in the future. Also what happens to a Posix system if it happens to execute an NTP operation during a leap second.

  4. Merle says:

    That’s a neat trick. Took me a few sample cases to see that it really worked, but neat. Could be very useful in environments like SQL Server, where the datediff() methods are way too basic: the year datediff() just subtracts the years, ignoring month and day.

    I would, of course, expect a *much* better comment in the source code than the one you provided.

    And, err, you didn’t avoid a call to AddYears(). Maybe you meant some other method?

    One other question: how does .net handle daylight savings time? I recall having to do brutal hacks to get past the fact that IE6.0 ignored DST, but 6.0a suddenly became the *only* browser to automatically adjust datetimes to reflect DST. (meaning the timeless date arithmetic I was trying to perform lost or gained a day after rounding).

  5. Ben Hutchings says:

    Norman: NTP has a flag that the time server can use to warn that a leap second is coming up. Typical NTP clients on POSIX systems make the clock run a bit slow for a few minutes before and after that time, so the leap second is never visible.

  6. mousic says:

    More math?

    static void PrintAge(DateTime bday, DateTime asof)


    int diffSortOf = 32*(asof.Month + 12*asof.Year) + asof.Day

    – 32*(bday.Month + 12*bday.Year) – bday.Day;

    int years = diffSortOf/32/12;

    int months = (diffSortOf % (32*12)) / 32;

    DateTime t = bday.AddYears(years).AddMonths(months);

    int days = (asof – t).Days;

    SC.WriteLine( "{0} years, {1} months, {2} days"

    , years, months, days);


Comments are closed.

Skip to main content