Trying out all the different ways of recognizing different types of timestamps from quite a long way away


Today's Little Program takes a 64-bit integer and tries to interpret it in all the various timestamp formats. This comes in handy when you have extracted a timestamp from a crash dump and want to see it in a friendly format.

using System;

class Program
{
 static void TryFormat(string format, Func<DateTime> func)
 {
  try
  {
   DateTime d = func();
   Console.WriteLine("{0} {1}", format, d);
  }
  catch (ArgumentException)
  {
   Console.WriteLine("{0} - invalid", format);
  }
 }

The Try­Format method executes the passed-in function inside a try/catch block. If the function executes successfully, then we print the result. If it raises an argument exception, then we declare the value as invalid.

 static DateTime DateTimeFromDosDateTime(long value)
 {
  if ((ulong)value > 0x00000000FFFFFFFF) {
   throw new ArgumentOutOfRangeException();
  }
  int intValue = (int)value;
  int year = (intValue >> 25) & 127;
  int month = (intValue >> 21) & 15;
  int day = (intValue >> 16) & 31;
  int hour = (intValue >> 11) & 31;
  int minute = (intValue >> 5) & 63;
  int second = (intValue << 1) & 63;
  return new DateTime(1980 + year, month, day, hour, minute, second);
 }

The Date­Time­From­Dos­Date­Time function treats the 64-bit value as a 32-bit date/time stamp in MS-DOS format. Assuming the value fits in a 32-bit integer, we extract the bitfields corresponding to the year, month, day, hour, minute, and second, and construct a Date­Time from it.

 public static void Main(string[] args)
 {
  if (args.Length < 1) return;

  long value = ParseLongSomehow(args[0]);

  Console.WriteLine("Timestamp {0} (0x{0:X}) could mean", value);

  TryFormat("Unix time",
    () => DateTime.FromFileTimeUtc(10000000 * value + 116444736000000000));
  TryFormat("UTC FILETIME",
    () => DateTime.FromFileTimeUtc(value));
  TryFormat("Local FILETIME",
    () => DateTime.FromFileTime(value));
  TryFormat("UTC DateTime",
    () => new DateTime(value, DateTimeKind.Utc));
  TryFormat("Local DateTime",
    () => new DateTime(value, DateTimeKind.Local));
  TryFormat("Binary DateTime",
    () => DateTime.FromBinary(value));
  TryFormat("MS-DOS Date/Time",
    () => DateTimeFromDosDateTime(value));
  TryFormat("OLE Automation Date/Time",
    () => DateTime.FromOADate(BitConverter.Int64BitsToDouble(value)));
 }
}

Once we have parsed out the command line, we pump the value through all the different conversion functions. Most of them are natively supported by the Date­Time structure, but we had to create a few of them manually.

Comments (13)
  1. anonymouscommenter says:

    The next step, of course, is to reject any dates that are far in the past or future automatically (since the date came from a crash dump, not some 12th century mathematician playing games with date formats).  This is left as an exercise for the reader.

  2. anonymouscommenter says:

    Kevin: that way you would filter out all users playing with date, trying Contoso App in 12th century :-)

  3. Brian_EE says:

    @Kevin: But what if you are debugging a crash dump that occurred while time traveling?

  4. anonymouscommenter says:

    @ Brian EE: Easy enough – go back to before the crash occurred and debug it in real time.

  5. anonymouscommenter says:

    Out of idle curiosity, is there a reason you're using decimal numbers for your bitmasks?  I've always had a habit of using hex values for masks (0x7F instead of 127, etc).  Just personal preference, or has your experience shown decimal is preferable?

  6. anonymouscommenter says:

    @Kevin, throwing out invalid dates could throw out useful information. What if the bug that caused the crash happens because a field of the date is set incorrectly earlier in the program? What if the timestamp is being used to store the difference between two dates?

  7. Dave Bacher says:

    Dates in the past and future could be meaningful, too.

    However, since you're projecting into a DateTime, the range is clamped anyway.  For the local time variants, DateTimeOffset might be more useful here, for displaying the information, since it can display information that the DateTime class cannot.  And presumably that information might be useful in debugging as well.

    There are a small but significantly non-zero number of apps that support astronomical/astrological scale dates.  If you're running a simulation, I could see storing time stamps in unix format (for example).  I'd think passing those to a date formatting API, say to get the year, could easily cause a crash dump, too, because it would be a case hardly anyone would actively test for.  But I could see an app doing that, crashing, and having to look at a crash dump for it.

    Same thing for ones in historic time frames, although that gets pretty tricky pretty fast even just last century, because of various rules.  You need a lot of data beyond just the date to be able to place it properly, in many of these systems.  IF they support it, you know, at all.

  8. anonymouscommenter says:

    @Nico RE decimal bitmasks

    For me personally it's just a matter of more compact notation, so I too would use decimal for bitmasks up to 10 bits and hex for larger ones (well, to be precise, I'd use decimal only for contiguous bitmasks because non-contiguous ones are more readable in hex, so it's 7 or 63 but 0x0c or 0xaa for me). Judging by Raimond's penchant for economic notation (e.g. the use of 9999 instead of 10000 as giving "more value per character" in one of the past posts) I'd guess a similar motivation.

  9. anonymouscommenter says:

    How to recognise different timestamps from quite a long way away.

    Number 1. The Unix timestamp.

    Number 1. The Unix timestamp.

    Number 1. The Unix timestamp.

    Number 3. The local FILETIME.

  10. anonymouscommenter says:

    My algorithm: if it's 10 or 13 digits long and starts with a 1, it's a Unix timestamp (10 digits for seconds, 13 digits for milliseconds).

  11. anonymouscommenter says:

    This activity reminds me of the "Guess the code page of my string" post.

    Only the code page guessing was roundly savaged, but for some reason we're having great fun guessing timestamp formats!

  12. anonymouscommenter says:

    Regarding filtering timestamps that are too far away from now, there's a way to make both inclusionists and exclusionists happy:

     – make it an option

    This is also left as an exercise for the reader. It's also up in the air what the default will be – so there's still some room for arguing.

  13. anonymouscommenter says:

    I liked the Monty Python reference in the title – see https://youtu.be/HPeFd5zQm_Y

Comments are closed.

Skip to main content