From adcccd0f8161394f61e39fd9c26eada35e091a8f Mon Sep 17 00:00:00 2001 From: Thomas Van Lenten Date: Thu, 12 Jan 2017 15:39:53 -0500 Subject: Fix Timestamps with dates before the Unix epoch that contain fractional seconds. The Timestamp proto does not allow for negative nanos fields, so the seconds must be shifted and a positive nanos then applied. --- objectivec/GPBWellKnownTypes.m | 9 ++++ objectivec/Tests/GPBWellKnownTypesTest.m | 81 +++++++++++++++++++------------- 2 files changed, 57 insertions(+), 33 deletions(-) (limited to 'objectivec') diff --git a/objectivec/GPBWellKnownTypes.m b/objectivec/GPBWellKnownTypes.m index ed798a2e..83b1c833 100644 --- a/objectivec/GPBWellKnownTypes.m +++ b/objectivec/GPBWellKnownTypes.m @@ -50,6 +50,15 @@ static int32_t SecondsAndNanosFromTimeIntervalSince1970(NSTimeInterval time, int64_t *outSeconds) { NSTimeInterval seconds; NSTimeInterval nanos = modf(time, &seconds); + + // Per Timestamp.proto, nanos is non-negative and "Negative second values with + // fractions must still have non-negative nanos values that count forward in + // time. Must be from 0 to 999,999,999 inclusive." + if (nanos < 0) { + --seconds; + nanos = 1.0 + nanos; + } + nanos *= 1e9; *outSeconds = (int64_t)seconds; return (int32_t)nanos; diff --git a/objectivec/Tests/GPBWellKnownTypesTest.m b/objectivec/Tests/GPBWellKnownTypesTest.m index 041841dd..99ab3e3c 100644 --- a/objectivec/Tests/GPBWellKnownTypesTest.m +++ b/objectivec/Tests/GPBWellKnownTypesTest.m @@ -46,39 +46,54 @@ static const NSTimeInterval kTimeAccuracy = 1e-9; @implementation WellKnownTypesTest - (void)testTimeStamp { - // Test Creation. - NSDate *date = [NSDate date]; - GPBTimestamp *timeStamp = [[GPBTimestamp alloc] initWithDate:date]; - NSDate *timeStampDate = timeStamp.date; - - // Comparing timeIntervals instead of directly comparing dates because date - // equality requires the time intervals to be exactly the same, and the - // timeintervals go through a bit of floating point error as they are - // converted back and forth from the internal representation. - XCTAssertEqualWithAccuracy(date.timeIntervalSince1970, - timeStampDate.timeIntervalSince1970, - kTimeAccuracy); - - NSTimeInterval time = [date timeIntervalSince1970]; - GPBTimestamp *timeStamp2 = - [[GPBTimestamp alloc] initWithTimeIntervalSince1970:time]; - NSTimeInterval durationTime = timeStamp2.timeIntervalSince1970; - XCTAssertEqualWithAccuracy(time, durationTime, kTimeAccuracy); - [timeStamp release]; - - // Test Mutation. - date = [NSDate dateWithTimeIntervalSinceNow:kFutureOffsetInterval]; - timeStamp2.date = date; - timeStampDate = timeStamp2.date; - XCTAssertEqualWithAccuracy(date.timeIntervalSince1970, - timeStampDate.timeIntervalSince1970, - kTimeAccuracy); - - time = date.timeIntervalSince1970; - timeStamp2.timeIntervalSince1970 = time; - durationTime = timeStamp2.timeIntervalSince1970; - XCTAssertEqualWithAccuracy(time, durationTime, kTimeAccuracy); - [timeStamp2 release]; + // Test a pre-Unix epoch date with fractional seconds. + NSTimeInterval interval = -428027599.0 + -483999967/1e9; + NSDate *preEpochDate = [NSDate dateWithTimeIntervalSince1970:interval]; + NSDate *now = [NSDate date]; + NSDate *future = [NSDate dateWithTimeIntervalSinceNow:kFutureOffsetInterval]; + NSArray *datesToTest = @[preEpochDate, now, future]; + + for (NSDate *date in datesToTest) { + // Test Creation. + GPBTimestamp *timeStamp = [[GPBTimestamp alloc] initWithDate:date]; + NSDate *timeStampDate = timeStamp.date; + + XCTAssertGreaterThanOrEqual(timeStamp.nanos, 0, + @"|nanos| must be >= 0. Failing date: %@", date); + XCTAssertLessThan(timeStamp.nanos, 1e9, @"|nanos| must be < 1e9. Failing date: %@", date); + + // Comparing timeIntervals instead of directly comparing dates because date + // equality requires the time intervals to be exactly the same, and the + // timeintervals go through a bit of floating point error as they are + // converted back and forth from the internal representation. + XCTAssertEqualWithAccuracy(date.timeIntervalSince1970, + timeStampDate.timeIntervalSince1970, + kTimeAccuracy, + @"Failing date: %@", date); + + NSTimeInterval time = [date timeIntervalSince1970]; + GPBTimestamp *timeStamp2 = + [[GPBTimestamp alloc] initWithTimeIntervalSince1970:time]; + NSTimeInterval durationTime = timeStamp2.timeIntervalSince1970; + XCTAssertEqualWithAccuracy(time, durationTime, kTimeAccuracy, @"Failing date: %@", date); + [timeStamp release]; + [timeStamp2 release]; + + // Test Mutation. + GPBTimestamp *timeStamp3 = [[GPBTimestamp alloc] init]; + timeStamp3.date = date; + timeStampDate = timeStamp3.date; + XCTAssertEqualWithAccuracy(date.timeIntervalSince1970, + timeStampDate.timeIntervalSince1970, + kTimeAccuracy, + @"Failing date: %@", date); + + time = date.timeIntervalSince1970; + timeStamp3.timeIntervalSince1970 = time; + durationTime = timeStamp3.timeIntervalSince1970; + XCTAssertEqualWithAccuracy(time, durationTime, kTimeAccuracy, @"Failing date: %@", date); + [timeStamp3 release]; + } } - (void)testDuration { -- cgit v1.2.3