Reputation: 3072
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
Reputation: 34581
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.
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