views:

1689

answers:

5

I have a date value, which I'm told is 8 bytes, a single "long" (aka int64) value, and converted to hex:

60f347d15798c901

How can I convert this and values like this, using PHP, into a time/date?

Converting it to decimal gives me: 96 243 71 209 87 152 201 1

A little more info: the original value is a C# DateTime, and should represent a time/date about 2 or 3 weeks ago.

+2  A: 

The value you've given is 36 hex digits, i.e. 18 bytes-worth. That's not an Int64 converted to hex.

Go back to whoever gave you the explanation of the format and ask them to tell you the truth this time :)

EDIT: Okay, next step: find out what that number is actually meant to mean. Ticks since 1970? Millis since AD 1? Do you know what your example string is meant to represent?

Jon Skeet
Edited question to contain the correct string, sorry about that!
lynn
+1  A: 

I believe you can convert it to a decimal int with hexdec(). That will be a timestap that you can work on with date().

Jeremy DeGroot
Hmm. I know it's supposed to be a long. How do I get the converted value: 96 243 71 209 87 152 201 1 back as a date/time?
lynn
Check my second answer - the hex characters may be little-endian, so wrong byte order for hexdec. Then, you may find that it is the number of ten-millionths of a second since January 1, 0001.
thomasrutter
A: 

If it is the same type of 64 bit hex NTP timestamp as described here, then you should be splitting the hex string equally in two, that is, imagine a separator between the first 8 hex digits and the other 8.

Convert the first half (first 32 bits' worth) into an integer with hexdec(), and this integer will be the date value in Unix timestamp format (seconds since Jan 1 1970 GMT) which you can then use in the date() function, etc.

The second part is fractional sections, useful if you need accuracy finer than one second (though not many applications do, and PHP's built in date and time functions don't except for microtime()). You would also convert this to decimal with hexdec().

thomasrutter
Using date() on the first half doesn't yield anything useful. I know it's supposed to be a long value, how does that become a time/date?
lynn
Ok, I think I found another solution, see other answer.
thomasrutter
+4  A: 

I found another reference which might point to some more relevant information. In this case, a Windows date and time as used in the registry. Apparently these are also stored as 64 bit values.

This is looking a bit more helpful, as it mentions that ending in C?01 is an indicator of this type of date. Note that this is little endian (ie, most significant bytes on the right) so the bytes are in the wrong byte order for hexdec() without reversing them 2 hex digits at a time (ie, start with right-most 2 hex digits, then next two, etc).

And, according to this source,

A single tick represents one hundred nanoseconds or one ten-millionth of a second. The value of this property represents the number of 100-nanosecond intervals that have elapsed since 12:00:00 midnight, January 1, 0001, which represents.

What might trip you up is that unfortunately PHP cannot handle 64 bit integers. It maxes out at 32 bit. Floats will help, but that doesn't stop hexdec() itself from only coping with ints. So it may be complicated to do this math; you may still need to split it into two, then combine them into a float by casting the more significant value to float and multiplying it by 2^32 also cast as float. It's possible that PHP built for 64-bit hardware may not have this limitation, but I don't know for sure.

thomasrutter
That's it, thanks!
lynn
+1 Well done, sir!
Jeremy DeGroot
+1  A: 

(Thanks to thomasrutter's post, which gave me the Windows filetime epoch):

The given date appears to be a Windows 64-bit little-endian file date-time, 60 f3 47 d1 57 98 c9 01, which is equivalent to the quadword 01c99857d147f360 which as an integer is 128801567297500000

This is "the number of 100-nanosecond intervals that have elapsed since 12:00 midnight, January 1, 1601 A.D. (C.E.) Coordinated Universal Time (UTC)"

After conversion, it gives Thu, 26 February 2009 21:18:49 UTC

Sample code:

<?php

    // strip non-hex characters
    function hexstring($str) {
     $hex = array(
      '0'=>'0', '1'=>'1', '2'=>'2', '3'=>'3', '4'=>'4',
      '5'=>'5', '6'=>'6', '7'=>'7', '8'=>'8', '9'=>'9',
      'a'=>'a', 'b'=>'b', 'c'=>'c', 'd'=>'d', 'e'=>'e', 'f'=>'f',
      'A'=>'a', 'B'=>'b', 'C'=>'c', 'D'=>'d', 'E'=>'e', 'F'=>'f'
     );

     $t = '';
     $len = strlen($str);
     for ($i=0; $i<$len; ++$i) {
      $ch = $str[$i];
      if (isset($hex[$ch]))
       $t .= $hex[$ch];
     }

     return $t;
    }

    // swap little-endian to big-endian
    function flip_endian($str) {
     // make sure #digits is even
     if ( strlen($str) & 1 )
      $str = '0' . $str;

     $t = '';
     for ($i = strlen($str)-2; $i >= 0; $i-=2)
      $t .= substr($str, $i, 2);

     return $t;
    }

    // convert hex string to BC-int
    function hex_to_bcint($str) {
     $hex = array(
      '0'=>'0', '1'=>'1', '2'=>'2', '3'=>'3', '4'=>'4',
      '5'=>'5', '6'=>'6', '7'=>'7', '8'=>'8', '9'=>'9',
      'a'=>'10', 'b'=>'11', 'c'=>'12', 'd'=>'13', 'e'=>'14', 'f'=>'15',
      'A'=>'10', 'B'=>'11', 'C'=>'12', 'D'=>'13', 'E'=>'14', 'F'=>'15'
     );

     $bci = '0';
     $len = strlen($str);
     for ($i=0; $i<$len; ++$i) {
      $bci = bcmul($bci, '16');

      $ch = $str[$i];
      if (isset($hex[$ch]))
       $bci = bcadd($bci, $hex[$ch]);
     }

     return $bci;
    }

    // WARNING! range clipping
    //   Windows date time has range from 29000 BC to 29000 AD
    //   Unix time only has range from 1901 AD to 2038 AD
    // WARNING! loss of accuracy
    //   Windows date time has accuracy to 0.0000001s
    //   Unix time only has accuracy to 1.0s
    function win64_to_unix($bci) {
     // Unix epoch as a Windows file date-time value
     $magicnum = '116444735995904000';

     $t = bcsub($bci, $magicnum); // Cast to Unix epoch
     $t = bcdiv($t, '10000000', 0); // Convert from ticks to seconds

     return $t;
    }

// get input
$dtval = isset($_GET["dt"]) ? strval($_GET["dt"]) : "0";
$dtval = hexstring($dtval);   // strip non-hex chars

// convert to quadword
$dtval = substr($dtval, 0, 16);  // clip overlength string
$dtval = str_pad($dtval, 16, '0');  // pad underlength string
$quad = flip_endian($dtval);

// convert to int
$win64_datetime = hex_to_bcint($quad);

// convert to Unix timestamp value
$unix_datetime = win64_to_unix($win64_datetime);

?><html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Windows datetime test code</title>
</head>

    <form method="get">
     <label>Datetime value: <input name="dt" type="text" value="<?php echo $dtval; ?>"/></label>
     <input type="submit" />
    </form>
    <hr />
    Result:
     Quad: <?php echo $quad; ?><br />
     Int: <?php echo $win64_datetime; ?><br />
     Unix timestamp: <?php echo $unix_datetime; ?><br />
     Date: <?php echo date("D, d F Y H:i:s e", $unix_datetime); ?><br />
<body>
</body>
</html>
Hugh Bothwell
Wow, this is awesome! Thanks!
lynn