Skip to content

Commit 44dc72e

Browse files
committed
Fix rounding error with work-around. Issue #44
1 parent 42b6f2e commit 44dc72e

File tree

2 files changed

+144
-23
lines changed

2 files changed

+144
-23
lines changed

src/MsgPack/MessagePackConvert.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public static class MessagePackConvert
3737
{
3838
private static readonly Encoding _utf8NonBomStrict = new UTF8Encoding( false, true );
3939
private static readonly Encoding _utf8NonBom = new UTF8Encoding( false, false );
40+
private const long _ticksToMilliseconds = 10000;
4041

4142
internal static Encoding Utf8NonBom
4243
{
@@ -136,7 +137,8 @@ public static DateTime ToDateTime( long value )
136137
/// </returns>
137138
public static long FromDateTimeOffset( DateTimeOffset value )
138139
{
139-
return ( long )value.ToUniversalTime().Subtract( _unixEpocUtc ).TotalMilliseconds;
140+
// Note: microseconds and nanoseconds should always truncated, so deviding by integral is suitable.
141+
return value.ToUniversalTime().Subtract( _unixEpocUtc ).Ticks / _ticksToMilliseconds;
140142
}
141143

142144
/// <summary>
@@ -148,7 +150,8 @@ public static long FromDateTimeOffset( DateTimeOffset value )
148150
/// </returns>
149151
public static long FromDateTime( DateTime value )
150152
{
151-
return ( long )value.ToUniversalTime().Subtract( _unixEpocUtc ).TotalMilliseconds;
153+
// Note: microseconds and nanoseconds should always truncated, so deviding by integral is suitable.
154+
return value.ToUniversalTime().Subtract( _unixEpocUtc ).Ticks / _ticksToMilliseconds;
152155
}
153156
}
154157
}

test/MsgPack.UnitTest/MessagePackConvertTest.cs

Lines changed: 139 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// MessagePack for CLI
44
//
5-
// Copyright (C) 2010-2012 FUJIWARA, Yusuke
5+
// Copyright (C) 2010-2014 FUJIWARA, Yusuke
66
//
77
// Licensed under the Apache License, Version 2.0 (the "License");
88
// you may not use this file except in compliance with the License.
@@ -41,7 +41,13 @@ public class MessagePackConvertTest
4141
// 1byte, 3bytes, 2bytes chars.
4242
private const string _testValue = "A\u3000\u00C0";
4343

44-
private static readonly DateTime _utcEpoc = new DateTime( 1970, 1, 1, 0, 0, 0, DateTimeKind.Utc );
44+
private const long TicksToMilliseconds = 10000;
45+
46+
private static readonly DateTime UtcEpoc = new DateTime( 1970, 1, 1, 0, 0, 0, DateTimeKind.Utc );
47+
48+
private static readonly DateTime UtcMaxValue = new DateTime( DateTime.MaxValue.Ticks, DateTimeKind.Utc );
49+
50+
private static readonly DateTime UtcMinValue = new DateTime( DateTime.MinValue.Ticks, DateTimeKind.Utc );
4551

4652
[Test]
4753
public void TestEncodeString_Normal_EncodedAsUtf8NonBom()
@@ -106,6 +112,26 @@ private static void AssertIsUnixEpocDateTimeOffset( DateTimeOffset actual, long
106112
Assert.That( actual, Is.EqualTo( epoc.AddMilliseconds( millisecondsOffset ) ) );
107113
}
108114

115+
116+
private static void AssertIsUnixEpocDateTimeOffset( DateTimeOffset expected, DateTimeOffset actual )
117+
{
118+
var expectedInMilliseconds =
119+
new DateTimeOffset(
120+
expected.Year,
121+
expected.Month,
122+
expected.Day,
123+
expected.Hour,
124+
expected.Minute,
125+
expected.Second,
126+
expected.Millisecond,
127+
expected.Offset
128+
);
129+
130+
Assert.That( actual.Offset, Is.EqualTo( TimeSpan.Zero ) );
131+
Assert.That( actual, Is.EqualTo( expectedInMilliseconds ) );
132+
}
133+
134+
109135
[Test]
110136
public void TestToDateTimeOffset_Zero_IsUtcEpoc()
111137
{
@@ -127,28 +153,59 @@ public void TestToDateTimeOffset_MinuOne_IsUtcEpoc()
127153
[Test]
128154
public void TestToDateTimeOffset_Maximum_IsUtcEpoc()
129155
{
130-
var offset = checked( ( long )DateTime.MaxValue.Subtract( _utcEpoc ).TotalMilliseconds ) - 1L;
156+
var offset = checked( UtcMaxValue.Subtract( UtcEpoc ).Ticks / TicksToMilliseconds );
131157
AssertIsUnixEpocDateTimeOffset( MessagePackConvert.ToDateTimeOffset( offset ), offset );
132158
}
133159

134160
[Test]
135161
public void TestToDateTimeOffset_Minimum_IsUtcEpoc()
136162
{
137-
var offset = checked( ( long )DateTime.MinValue.Subtract( _utcEpoc ).TotalMilliseconds );
163+
var offset = checked( UtcMinValue.Subtract( UtcEpoc ).Ticks / TicksToMilliseconds );
138164
AssertIsUnixEpocDateTimeOffset( MessagePackConvert.ToDateTimeOffset( offset ), offset );
139165
}
140166

167+
[Test]
168+
public void TestToDateTimeOffsetRoundTrip_Zero_IsUtcEpoc()
169+
{
170+
AssertIsUnixEpocDateTimeOffset( new DateTimeOffset( UtcEpoc ), MessagePackConvert.ToDateTimeOffset( 0 ) );
171+
}
172+
173+
[Test]
174+
public void TestToDateTimeOffsetRoundTrip_One_IsUtcEpoc()
175+
{
176+
AssertIsUnixEpocDateTimeOffset( new DateTimeOffset( UtcEpoc ).AddMilliseconds( 1 ), MessagePackConvert.ToDateTimeOffset( 1 ) );
177+
}
178+
179+
[Test]
180+
public void TestToDateTimeOffsetRoundTrip_MinuOne_IsUtcEpoc()
181+
{
182+
AssertIsUnixEpocDateTimeOffset( new DateTimeOffset( UtcEpoc ).AddMilliseconds( -1 ), MessagePackConvert.ToDateTimeOffset( -1 ) );
183+
}
184+
185+
[Test]
186+
public void TestToDateTimeOffsetRoundTrip_Maximum_IsUtcEpoc()
187+
{
188+
var offset = checked( UtcMaxValue.Subtract( UtcEpoc ).Ticks / TicksToMilliseconds );
189+
AssertIsUnixEpocDateTimeOffset( DateTimeOffset.MaxValue, MessagePackConvert.ToDateTimeOffset( offset ) );
190+
}
191+
192+
[Test]
193+
public void TestToDateTimeOffsetRoundTrip_Minimum_IsUtcEpoc()
194+
{
195+
var offset = checked( UtcMinValue.Subtract( UtcEpoc ).Ticks / TicksToMilliseconds );
196+
AssertIsUnixEpocDateTimeOffset( DateTimeOffset.MinValue, MessagePackConvert.ToDateTimeOffset( offset ) );
197+
}
141198
[Test]
142199
public void TestToDateTimeOffset_MaximumPlusOne_IsUtcEpoc()
143200
{
144-
var offset = checked( ( long )DateTime.MaxValue.Subtract( _utcEpoc ).TotalMilliseconds + 1L );
201+
var offset = checked( UtcMaxValue.Subtract( UtcEpoc ).Ticks / TicksToMilliseconds + 1L );
145202
Assert.Throws<ArgumentOutOfRangeException>( () => MessagePackConvert.ToDateTimeOffset( offset ) );
146203
}
147204

148205
[Test]
149206
public void TestToDateTimeOffset_MinimumMinusOne_IsUtcEpoc()
150207
{
151-
var offset = checked( ( long )DateTime.MinValue.Subtract( _utcEpoc ).TotalMilliseconds - 1L );
208+
var offset = checked( UtcMinValue.Subtract( UtcEpoc ).Ticks / TicksToMilliseconds - 1L );
152209
Assert.Throws<ArgumentOutOfRangeException>( () => MessagePackConvert.ToDateTimeOffset( offset ) );
153210
}
154211

@@ -160,6 +217,24 @@ private static void AssertIsUnixEpocDateTime( DateTime actual, long milliseconds
160217
Assert.That( actual, Is.EqualTo( epoc.AddMilliseconds( millisecondsOffset ) ) );
161218
}
162219

220+
private static void AssertIsUnixEpocDateTime( DateTime expected, DateTime actual )
221+
{
222+
var expectedInMilliseconds =
223+
new DateTime(
224+
expected.Year,
225+
expected.Month,
226+
expected.Day,
227+
expected.Hour,
228+
expected.Minute,
229+
expected.Second,
230+
expected.Millisecond,
231+
expected.Kind
232+
);
233+
234+
Assert.That( actual.Kind, Is.EqualTo( DateTimeKind.Utc ) );
235+
Assert.That( actual, Is.EqualTo( expectedInMilliseconds ) );
236+
}
237+
163238
[Test]
164239
public void TestToDateTime_Zero_IsUtcEpoc()
165240
{
@@ -173,36 +248,68 @@ public void TestToDateTime_One_IsUtcEpoc()
173248
}
174249

175250
[Test]
176-
public void TestToDateTime_MinuOne_IsUtcEpoc()
251+
public void TestToDateTime_MinusOne_IsUtcEpoc()
177252
{
178253
AssertIsUnixEpocDateTime( MessagePackConvert.ToDateTime( -1 ), -1 );
179254
}
180255

181256
[Test]
182257
public void TestToDateTime_Maximum_IsUtcEpoc()
183258
{
184-
var offset = checked( ( long )DateTime.MaxValue.Subtract( _utcEpoc ).TotalMilliseconds ) - 1L;
259+
var offset = checked( UtcMaxValue.Subtract( UtcEpoc ).Ticks / TicksToMilliseconds );
185260
AssertIsUnixEpocDateTime( MessagePackConvert.ToDateTime( offset ), offset );
186261
}
187262

188263
[Test]
189264
public void TestToDateTime_Minimum_IsUtcEpoc()
190265
{
191-
var offset = checked( ( long )DateTime.MinValue.Subtract( _utcEpoc ).TotalMilliseconds );
266+
var offset = checked( UtcMinValue.Subtract( UtcEpoc ).Ticks / TicksToMilliseconds );
192267
AssertIsUnixEpocDateTime( MessagePackConvert.ToDateTime( offset ), offset );
193268
}
194269

270+
[Test]
271+
public void TestToDateTimeRoundTrip_Zero_IsUtcEpoc()
272+
{
273+
AssertIsUnixEpocDateTime( UtcEpoc, MessagePackConvert.ToDateTime( 0 ) );
274+
}
275+
276+
[Test]
277+
public void TestToDateTimeRoundTrip_One_IsUtcEpoc()
278+
{
279+
AssertIsUnixEpocDateTime( UtcEpoc.AddMilliseconds( 1 ), MessagePackConvert.ToDateTime( 1 ) );
280+
}
281+
282+
[Test]
283+
public void TestToDateTimeRoundTrip_MinusOne_IsUtcEpoc()
284+
{
285+
AssertIsUnixEpocDateTime( UtcEpoc.AddMilliseconds( -1 ), MessagePackConvert.ToDateTime( -1 ) );
286+
}
287+
288+
[Test]
289+
public void TestToDateTimeRoundTrip_Maximum_IsUtcEpoc()
290+
{
291+
var offset = checked( UtcMaxValue.Subtract( UtcEpoc ).Ticks / TicksToMilliseconds );
292+
AssertIsUnixEpocDateTime( DateTime.MaxValue, MessagePackConvert.ToDateTime( offset ) );
293+
}
294+
295+
[Test]
296+
public void TestToDateTimeRoundTrip_Minimum_IsUtcEpoc()
297+
{
298+
var offset = checked( UtcMinValue.Subtract( UtcEpoc ).Ticks / TicksToMilliseconds );
299+
AssertIsUnixEpocDateTime( DateTime.MinValue, MessagePackConvert.ToDateTime( offset ) );
300+
}
301+
195302
[Test]
196303
public void TestToDateTime_MaximumPlusOne_IsUtcEpoc()
197304
{
198-
var offset = checked( ( long )DateTime.MaxValue.Subtract( _utcEpoc ).TotalMilliseconds + 1L );
305+
var offset = checked( UtcMaxValue.Subtract( UtcEpoc ).Ticks / TicksToMilliseconds + 1L );
199306
Assert.Throws<ArgumentOutOfRangeException>( () => MessagePackConvert.ToDateTime( offset ) );
200307
}
201308

202309
[Test]
203310
public void TestToDateTime_MinimumMinusOne_IsUtcEpoc()
204311
{
205-
var offset = checked( ( long )DateTime.MinValue.Subtract( _utcEpoc ).TotalMilliseconds - 1L );
312+
var offset = checked( UtcMinValue.Subtract( UtcEpoc ).Ticks / TicksToMilliseconds - 1L );
206313
Assert.Throws<ArgumentOutOfRangeException>( () => MessagePackConvert.ToDateTime( offset ) );
207314
}
208315

@@ -211,7 +318,7 @@ public void TestToDateTime_MinimumMinusOne_IsUtcEpoc()
211318
public void TestFromDateTimeOffset_UtcNow_AsUnixEpoc()
212319
{
213320
Assert.AreEqual(
214-
checked( ( long )DateTime.UtcNow.Subtract( _utcEpoc ).TotalMilliseconds ),
321+
checked( DateTime.UtcNow.Subtract( UtcEpoc ).Ticks / TicksToMilliseconds ),
215322
MessagePackConvert.FromDateTimeOffset( DateTimeOffset.UtcNow )
216323
);
217324
}
@@ -221,7 +328,7 @@ public void TestFromDateTimeOffset_Now_AsUtcUnixEpoc()
221328
{
222329
// LocalTime will be converted to UtcTime
223330
Assert.AreEqual(
224-
checked( ( long )DateTime.UtcNow.Subtract( _utcEpoc ).TotalMilliseconds ),
331+
checked( DateTime.UtcNow.Subtract( UtcEpoc ).Ticks / TicksToMilliseconds ),
225332
MessagePackConvert.FromDateTimeOffset( DateTimeOffset.Now )
226333
);
227334
}
@@ -239,7 +346,7 @@ public void TestFromDateTimeOffset_UtcEpoc_Zero()
239346
public void TestFromDateTimeOffset_MinValue_AsUnixEpoc()
240347
{
241348
Assert.AreEqual(
242-
checked( ( long )( DateTimeOffset.MinValue.ToUniversalTime().Subtract( _utcEpoc ).TotalMilliseconds ) ),
349+
checked( ( DateTimeOffset.MinValue.ToUniversalTime().Subtract( UtcEpoc ).Ticks / TicksToMilliseconds ) ),
243350
MessagePackConvert.FromDateTimeOffset( DateTimeOffset.MinValue.ToUniversalTime() )
244351
);
245352
}
@@ -248,7 +355,7 @@ public void TestFromDateTimeOffset_MinValue_AsUnixEpoc()
248355
public void TestFromDateTimeOffset_MaxValue_AsUnixEpoc()
249356
{
250357
Assert.AreEqual(
251-
checked( ( long )( DateTimeOffset.MaxValue.ToUniversalTime().Subtract( _utcEpoc ).TotalMilliseconds ) ),
358+
checked( ( DateTimeOffset.MaxValue.ToUniversalTime().Subtract( UtcEpoc ).Ticks / TicksToMilliseconds ) ),
252359
MessagePackConvert.FromDateTimeOffset( DateTimeOffset.MaxValue.ToUniversalTime() )
253360
);
254361
}
@@ -258,7 +365,7 @@ public void TestFromDateTimeOffset_MaxValue_AsUnixEpoc()
258365
public void TestFromDateTime_UtcNow_AsUnixEpoc()
259366
{
260367
Assert.AreEqual(
261-
checked( ( long )DateTime.UtcNow.Subtract( _utcEpoc ).TotalMilliseconds ),
368+
checked( DateTime.UtcNow.Subtract( UtcEpoc ).Ticks / TicksToMilliseconds ),
262369
MessagePackConvert.FromDateTime( DateTime.UtcNow )
263370
);
264371
}
@@ -269,7 +376,7 @@ public void TestFromDateTime_Now_AsUtcUnixEpoc()
269376
// LocalTime will be converted to UtcTime
270377
var now = DateTime.Now;
271378
Assert.AreEqual(
272-
checked( ( long )now.ToUniversalTime().Subtract( _utcEpoc ).TotalMilliseconds ),
379+
checked( now.ToUniversalTime().Subtract( UtcEpoc ).Ticks / TicksToMilliseconds ),
273380
MessagePackConvert.FromDateTime( now )
274381
);
275382
}
@@ -287,17 +394,28 @@ public void TestFromDateTime_UtcEpoc_Zero()
287394
public void TestFromDateTime_MinValue_AsUnixEpoc()
288395
{
289396
Assert.AreEqual(
290-
checked( ( long )( DateTime.MinValue.ToUniversalTime().Subtract( _utcEpoc ).TotalMilliseconds ) ),
291-
MessagePackConvert.FromDateTime( DateTime.MinValue.ToUniversalTime() )
397+
checked( ( UtcMinValue.ToUniversalTime().Subtract( UtcEpoc ).Ticks / TicksToMilliseconds ) ),
398+
MessagePackConvert.FromDateTime( UtcMinValue.ToUniversalTime() )
292399
);
293400
}
294401

295402
[Test]
296403
public void TestFromDateTime_MaxValue_AsUnixEpoc()
297404
{
298405
Assert.AreEqual(
299-
checked( ( long )( DateTime.MaxValue.ToUniversalTime().Subtract( _utcEpoc ).TotalMilliseconds ) ),
300-
MessagePackConvert.FromDateTime( DateTime.MaxValue.ToUniversalTime() )
406+
checked( ( UtcMaxValue.ToUniversalTime().Subtract( UtcEpoc ).Ticks / TicksToMilliseconds ) ),
407+
MessagePackConvert.FromDateTime( UtcMaxValue.ToUniversalTime() )
408+
);
409+
}
410+
411+
[Test]
412+
public void TestIssue43()
413+
{
414+
var expected = new DateTime( 9999, 12, 31, 23, 59, 59, 999, DateTimeKind.Utc );
415+
var actual = new DateTime( 3155378975999999999L, DateTimeKind.Utc );
416+
Assert.AreEqual(
417+
MessagePackConvert.ToDateTime( MessagePackConvert.FromDateTime( expected ) ),
418+
MessagePackConvert.ToDateTime( MessagePackConvert.FromDateTime( actual ) )
301419
);
302420
}
303421
}

0 commit comments

Comments
 (0)