musium
musium

Reputation: 3072

Keeping date/time values consistent when converting UNIX timestamps to .Net DateTime and back

I need to convert a unix TimeStamp to a .Net DateTIme value and vice versa. This would be no problem, but I need to store the converted value as Int64 (Which means I lose the decimal places).

Losing the decimal places means I can get differences between the original and converted value. I would change the implementation and store the value as Double instead of Int64 if I could…but sadly I can’t (specification etc..).

Edit: I need to store the value as Int64 representing the seconds since 1970 (unix timestamp)

I think I’ve found a work around for this problem but I’m not sure if it works 100%. (It worked 100% in my tests). Rounding the Double value before casting into an int seams to solve the problem.

Does this solve the problem or not?

Here is my code:

[Test]
public void ConvertTest()
{
    const Int64 orignalUnixValue = 252000596321;
    var referenceTimeZero = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);

    //Test 1
    var convertedDateTime = referenceTimeZero.AddSeconds(orignalUnixValue);
    var convertedUnixValue = (convertedDateTime - referenceTimeZero).TotalSeconds;
    var convertedDateTime2 = referenceTimeZero.AddSeconds(convertedUnixValue);

    //This works
    convertedDateTime2.Should()
                      .Be( convertedDateTime );

    //Test 2
    convertedDateTime = referenceTimeZero.AddSeconds(orignalUnixValue);
    var convertedUnixValueCasted = (Int64)(convertedDateTime - referenceTimeZero).TotalSeconds;
    convertedDateTime2 = referenceTimeZero.AddSeconds(convertedUnixValueCasted);

    //Difference of 1 second
    convertedDateTime2.Should()
                      .Be(convertedDateTime);

    //Test 3
    convertedDateTime = referenceTimeZero.AddSeconds(orignalUnixValue);
    convertedUnixValueCasted = (Int64)Math.Ceiling((convertedDateTime - referenceTimeZero).TotalSeconds);
    convertedDateTime2 = referenceTimeZero.AddSeconds(convertedUnixValueCasted);

    //Possible workaround - works for this example
    convertedDateTime2.Should()
                      .Be(convertedDateTime);
}

The first tests shows the correct way of converting the values, the second shows the current implementation and the third contains my workaround.

Upvotes: 1

Views: 440

Answers (1)

Dmytro Shevchenko
Dmytro Shevchenko

Reputation: 34581

Edit

If you absolutely have to work with these values as with UNIX timestamp, then instead of Math.Ceiling I recommend using Math.Round. This way you ensure that the number is rounded to the closest integer.

Though, as suggested by Jon, it makes much more sense to use TimeSpan.Ticks instead of TimeSpan.TotalSeconds, since ticks are Int64 to begin with. You will have no conversion error this way.


Explanation

It seems that the problem you're seeing is caused by the way double is stored and then by what happens when casting double to long.

I have modified your code a bit:

const Int64 orignalUnixValue = 252000596321;
DateTime referenceTimeZero = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);

DateTime convertedDateTime = referenceTimeZero.AddSeconds(orignalUnixValue);
double convertedUnixValue = (convertedDateTime - referenceTimeZero).TotalSeconds;

Console.WriteLine(convertedUnixValue); // 252000596321
Console.WriteLine((long)convertedUnixValue); // 252000596320

As you see, the double variable seems to store the right value. But then there's some sort of a conversion error when casting to long.

By using Jon Skeet's DoubleConverter, it's possible to see that the variable's value is actually smaller than 252000596321, which is the cause of the conversion error.

Console.WriteLine(DoubleConverter.ToExactString(convertedUnixValue));
// 252000596320.999969482421875

Upvotes: 1

Related Questions