Commit adcccd0f authored by Thomas Van Lenten's avatar Thomas Van Lenten

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.
parent feb78fb2
...@@ -50,6 +50,15 @@ static int32_t SecondsAndNanosFromTimeIntervalSince1970(NSTimeInterval time, ...@@ -50,6 +50,15 @@ static int32_t SecondsAndNanosFromTimeIntervalSince1970(NSTimeInterval time,
int64_t *outSeconds) { int64_t *outSeconds) {
NSTimeInterval seconds; NSTimeInterval seconds;
NSTimeInterval nanos = modf(time, &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; nanos *= 1e9;
*outSeconds = (int64_t)seconds; *outSeconds = (int64_t)seconds;
return (int32_t)nanos; return (int32_t)nanos;
......
...@@ -46,39 +46,54 @@ static const NSTimeInterval kTimeAccuracy = 1e-9; ...@@ -46,39 +46,54 @@ static const NSTimeInterval kTimeAccuracy = 1e-9;
@implementation WellKnownTypesTest @implementation WellKnownTypesTest
- (void)testTimeStamp { - (void)testTimeStamp {
// Test Creation. // Test a pre-Unix epoch date with fractional seconds.
NSDate *date = [NSDate date]; NSTimeInterval interval = -428027599.0 + -483999967/1e9;
GPBTimestamp *timeStamp = [[GPBTimestamp alloc] initWithDate:date]; NSDate *preEpochDate = [NSDate dateWithTimeIntervalSince1970:interval];
NSDate *timeStampDate = timeStamp.date; NSDate *now = [NSDate date];
NSDate *future = [NSDate dateWithTimeIntervalSinceNow:kFutureOffsetInterval];
// Comparing timeIntervals instead of directly comparing dates because date NSArray *datesToTest = @[preEpochDate, now, future];
// equality requires the time intervals to be exactly the same, and the
// timeintervals go through a bit of floating point error as they are for (NSDate *date in datesToTest) {
// converted back and forth from the internal representation. // Test Creation.
XCTAssertEqualWithAccuracy(date.timeIntervalSince1970, GPBTimestamp *timeStamp = [[GPBTimestamp alloc] initWithDate:date];
timeStampDate.timeIntervalSince1970, NSDate *timeStampDate = timeStamp.date;
kTimeAccuracy);
XCTAssertGreaterThanOrEqual(timeStamp.nanos, 0,
NSTimeInterval time = [date timeIntervalSince1970]; @"|nanos| must be >= 0. Failing date: %@", date);
GPBTimestamp *timeStamp2 = XCTAssertLessThan(timeStamp.nanos, 1e9, @"|nanos| must be < 1e9. Failing date: %@", date);
[[GPBTimestamp alloc] initWithTimeIntervalSince1970:time];
NSTimeInterval durationTime = timeStamp2.timeIntervalSince1970; // Comparing timeIntervals instead of directly comparing dates because date
XCTAssertEqualWithAccuracy(time, durationTime, kTimeAccuracy); // equality requires the time intervals to be exactly the same, and the
[timeStamp release]; // timeintervals go through a bit of floating point error as they are
// converted back and forth from the internal representation.
// Test Mutation. XCTAssertEqualWithAccuracy(date.timeIntervalSince1970,
date = [NSDate dateWithTimeIntervalSinceNow:kFutureOffsetInterval]; timeStampDate.timeIntervalSince1970,
timeStamp2.date = date; kTimeAccuracy,
timeStampDate = timeStamp2.date; @"Failing date: %@", date);
XCTAssertEqualWithAccuracy(date.timeIntervalSince1970,
timeStampDate.timeIntervalSince1970, NSTimeInterval time = [date timeIntervalSince1970];
kTimeAccuracy); GPBTimestamp *timeStamp2 =
[[GPBTimestamp alloc] initWithTimeIntervalSince1970:time];
time = date.timeIntervalSince1970; NSTimeInterval durationTime = timeStamp2.timeIntervalSince1970;
timeStamp2.timeIntervalSince1970 = time; XCTAssertEqualWithAccuracy(time, durationTime, kTimeAccuracy, @"Failing date: %@", date);
durationTime = timeStamp2.timeIntervalSince1970; [timeStamp release];
XCTAssertEqualWithAccuracy(time, durationTime, kTimeAccuracy); [timeStamp2 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 { - (void)testDuration {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment