aboutsummaryrefslogtreecommitdiffhomepage
path: root/objectivec
diff options
context:
space:
mode:
authorGravatar Thomas Van Lenten <thomasvl@google.com>2017-01-12 15:39:53 -0500
committerGravatar Thomas Van Lenten <thomasvl@google.com>2017-01-12 15:46:50 -0500
commitadcccd0f8161394f61e39fd9c26eada35e091a8f (patch)
treea5151cc514710a1b7bd40bd85732ec606f71a91e /objectivec
parentfeb78fb293ac891baa4a25047581f34589f33a2f (diff)
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.
Diffstat (limited to 'objectivec')
-rw-r--r--objectivec/GPBWellKnownTypes.m9
-rw-r--r--objectivec/Tests/GPBWellKnownTypesTest.m81
2 files changed, 57 insertions, 33 deletions
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 {