Skip to content

Commit d0bc096

Browse files
committed
Timestamp helper fix, Duration helper cleanup.
- The Timestamp proto does not allow for negative nanos fields, so the seconds must be shifted and a positive nanos then applied. - Tweak the helpers on Duration to make it clear there is no "base" time involved. - Update the unittests for duration and timestamp to cover positive and negative NSTimeIntervals and what their impact is on the protos.
1 parent c9cd6ac commit d0bc096

File tree

3 files changed

+160
-71
lines changed

3 files changed

+160
-71
lines changed

objectivec/GPBWellKnownTypes.h

+14-3
Original file line numberDiff line numberDiff line change
@@ -112,16 +112,27 @@ typedef NS_ENUM(NSInteger, GPBWellKnownTypesErrorCode) {
112112
* @note: Not all second/nanos combinations can be represented in a
113113
* NSTimeInterval, so getting this could be a lossy transform.
114114
**/
115-
@property(nonatomic, readwrite) NSTimeInterval timeIntervalSince1970;
115+
@property(nonatomic, readwrite) NSTimeInterval timeInterval;
116116

117117
/**
118118
* Initializes a GPBDuration with the given NSTimeInterval.
119119
*
120-
* @param timeIntervalSince1970 Time interval to configure the GPBDuration with.
120+
* @param timeInterval Time interval to configure the GPBDuration with.
121121
*
122122
* @return A newly initialized GPBDuration.
123123
**/
124-
- (instancetype)initWithTimeIntervalSince1970:(NSTimeInterval)timeIntervalSince1970;
124+
- (instancetype)initWithTimeInterval:(NSTimeInterval)timeInterval;
125+
126+
// These next two methods are deprecated because GBPDuration has no need of a
127+
// "base" time. The older methods were about symmetry with GBPTimestamp, but
128+
// the unix epoch usage is too confusing.
129+
130+
/** Deprecated, use timeInterval instead. */
131+
@property(nonatomic, readwrite) NSTimeInterval timeIntervalSince1970
132+
__attribute__((deprecated("Use timeInterval")));
133+
/** Deprecated, use initWithTimeInterval: instead. */
134+
- (instancetype)initWithTimeIntervalSince1970:(NSTimeInterval)timeIntervalSince1970
135+
__attribute__((deprecated("Use initWithTimeInterval:")));
125136

126137
@end
127138

objectivec/GPBWellKnownTypes.m

+37-15
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,25 @@
4141

4242
static NSString *kTypePrefixGoogleApisCom = @"type.googleapis.com/";
4343

44-
static NSTimeInterval TimeIntervalSince1970FromSecondsAndNanos(int64_t seconds,
45-
int32_t nanos) {
44+
static NSTimeInterval TimeIntervalFromSecondsAndNanos(int64_t seconds,
45+
int32_t nanos) {
4646
return seconds + (NSTimeInterval)nanos / 1e9;
4747
}
4848

49-
static int32_t SecondsAndNanosFromTimeIntervalSince1970(NSTimeInterval time,
50-
int64_t *outSeconds) {
49+
static int32_t SecondsAndNanosFromTimeInterval(NSTimeInterval time,
50+
int64_t *outSeconds,
51+
BOOL nanosMustBePositive) {
5152
NSTimeInterval seconds;
5253
NSTimeInterval nanos = modf(time, &seconds);
54+
55+
if (nanosMustBePositive && (nanos < 0)) {
56+
// Per Timestamp.proto, nanos is non-negative and "Negative second values with
57+
// fractions must still have non-negative nanos values that count forward in
58+
// time. Must be from 0 to 999,999,999 inclusive."
59+
--seconds;
60+
nanos = 1.0 + nanos;
61+
}
62+
5363
nanos *= 1e9;
5464
*outSeconds = (int64_t)seconds;
5565
return (int32_t)nanos;
@@ -88,8 +98,8 @@ - (instancetype)initWithDate:(NSDate *)date {
8898
- (instancetype)initWithTimeIntervalSince1970:(NSTimeInterval)timeIntervalSince1970 {
8999
if ((self = [super init])) {
90100
int64_t seconds;
91-
int32_t nanos = SecondsAndNanosFromTimeIntervalSince1970(
92-
timeIntervalSince1970, &seconds);
101+
int32_t nanos = SecondsAndNanosFromTimeInterval(
102+
timeIntervalSince1970, &seconds, YES);
93103
self.seconds = seconds;
94104
self.nanos = nanos;
95105
}
@@ -105,13 +115,13 @@ - (void)setDate:(NSDate *)date {
105115
}
106116

107117
- (NSTimeInterval)timeIntervalSince1970 {
108-
return TimeIntervalSince1970FromSecondsAndNanos(self.seconds, self.nanos);
118+
return TimeIntervalFromSecondsAndNanos(self.seconds, self.nanos);
109119
}
110120

111121
- (void)setTimeIntervalSince1970:(NSTimeInterval)timeIntervalSince1970 {
112122
int64_t seconds;
113123
int32_t nanos =
114-
SecondsAndNanosFromTimeIntervalSince1970(timeIntervalSince1970, &seconds);
124+
SecondsAndNanosFromTimeInterval(timeIntervalSince1970, &seconds, YES);
115125
self.seconds = seconds;
116126
self.nanos = nanos;
117127
}
@@ -122,29 +132,41 @@ - (void)setTimeIntervalSince1970:(NSTimeInterval)timeIntervalSince1970 {
122132

123133
@implementation GPBDuration (GBPWellKnownTypes)
124134

125-
- (instancetype)initWithTimeIntervalSince1970:(NSTimeInterval)timeIntervalSince1970 {
135+
- (instancetype)initWithTimeInterval:(NSTimeInterval)timeInterval {
126136
if ((self = [super init])) {
127137
int64_t seconds;
128-
int32_t nanos = SecondsAndNanosFromTimeIntervalSince1970(
129-
timeIntervalSince1970, &seconds);
138+
int32_t nanos = SecondsAndNanosFromTimeInterval(
139+
timeInterval, &seconds, NO);
130140
self.seconds = seconds;
131141
self.nanos = nanos;
132142
}
133143
return self;
134144
}
135145

136-
- (NSTimeInterval)timeIntervalSince1970 {
137-
return TimeIntervalSince1970FromSecondsAndNanos(self.seconds, self.nanos);
146+
- (instancetype)initWithTimeIntervalSince1970:(NSTimeInterval)timeIntervalSince1970 {
147+
return [self initWithTimeInterval:timeIntervalSince1970];
138148
}
139149

140-
- (void)setTimeIntervalSince1970:(NSTimeInterval)timeIntervalSince1970 {
150+
- (NSTimeInterval)timeInterval {
151+
return TimeIntervalFromSecondsAndNanos(self.seconds, self.nanos);
152+
}
153+
154+
- (void)setTimeInterval:(NSTimeInterval)timeInterval {
141155
int64_t seconds;
142156
int32_t nanos =
143-
SecondsAndNanosFromTimeIntervalSince1970(timeIntervalSince1970, &seconds);
157+
SecondsAndNanosFromTimeInterval(timeInterval, &seconds, NO);
144158
self.seconds = seconds;
145159
self.nanos = nanos;
146160
}
147161

162+
- (NSTimeInterval)timeIntervalSince1970 {
163+
return self.timeInterval;
164+
}
165+
166+
- (void)setTimeIntervalSince1970:(NSTimeInterval)timeIntervalSince1970 {
167+
self.timeInterval = timeIntervalSince1970;
168+
}
169+
148170
@end
149171

150172
#pragma mark - GPBAny

objectivec/Tests/GPBWellKnownTypesTest.m

+109-53
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,9 @@
3232

3333
#import <XCTest/XCTest.h>
3434

35+
#import "GPBTestUtilities.h"
3536
#import "google/protobuf/AnyTest.pbobjc.h"
3637

37-
// A basically random interval into the future for testing with.
38-
static const NSTimeInterval kFutureOffsetInterval = 15000;
39-
4038
// Nanosecond time accuracy
4139
static const NSTimeInterval kTimeAccuracy = 1e-9;
4240

@@ -46,59 +44,117 @@ @interface WellKnownTypesTest : XCTestCase
4644
@implementation WellKnownTypesTest
4745

4846
- (void)testTimeStamp {
49-
// Test Creation.
50-
NSDate *date = [NSDate date];
51-
GPBTimestamp *timeStamp = [[GPBTimestamp alloc] initWithDate:date];
52-
NSDate *timeStampDate = timeStamp.date;
53-
54-
// Comparing timeIntervals instead of directly comparing dates because date
55-
// equality requires the time intervals to be exactly the same, and the
56-
// timeintervals go through a bit of floating point error as they are
57-
// converted back and forth from the internal representation.
58-
XCTAssertEqualWithAccuracy(date.timeIntervalSince1970,
59-
timeStampDate.timeIntervalSince1970,
60-
kTimeAccuracy);
61-
62-
NSTimeInterval time = [date timeIntervalSince1970];
63-
GPBTimestamp *timeStamp2 =
64-
[[GPBTimestamp alloc] initWithTimeIntervalSince1970:time];
65-
NSTimeInterval durationTime = timeStamp2.timeIntervalSince1970;
66-
XCTAssertEqualWithAccuracy(time, durationTime, kTimeAccuracy);
67-
[timeStamp release];
68-
69-
// Test Mutation.
70-
date = [NSDate dateWithTimeIntervalSinceNow:kFutureOffsetInterval];
71-
timeStamp2.date = date;
72-
timeStampDate = timeStamp2.date;
73-
XCTAssertEqualWithAccuracy(date.timeIntervalSince1970,
74-
timeStampDate.timeIntervalSince1970,
75-
kTimeAccuracy);
76-
77-
time = date.timeIntervalSince1970;
78-
timeStamp2.timeIntervalSince1970 = time;
79-
durationTime = timeStamp2.timeIntervalSince1970;
80-
XCTAssertEqualWithAccuracy(time, durationTime, kTimeAccuracy);
81-
[timeStamp2 release];
47+
// Test negative and positive values.
48+
NSTimeInterval values[] = {
49+
-428027599.483999967, -1234567.0, -0.5, 0, 0.75, 54321.0, 2468086,483999967
50+
};
51+
for (size_t i = 0; i < GPBARRAYSIZE(values); ++i) {
52+
NSTimeInterval value = values[i];
53+
54+
// Test Creation - date.
55+
NSDate *date = [NSDate dateWithTimeIntervalSince1970:value];
56+
GPBTimestamp *timeStamp = [[GPBTimestamp alloc] initWithDate:date];
57+
58+
XCTAssertGreaterThanOrEqual(timeStamp.nanos, 0,
59+
@"Offset %f - Date: %@", (double)value, date);
60+
XCTAssertLessThan(timeStamp.nanos, 1e9,
61+
@"Offset %f - Date: %@", (double)value, date);
62+
63+
// Comparing timeIntervals instead of directly comparing dates because date
64+
// equality requires the time intervals to be exactly the same, and the
65+
// timeintervals go through a bit of floating point error as they are
66+
// converted back and forth from the internal representation.
67+
XCTAssertEqualWithAccuracy(value, timeStamp.date.timeIntervalSince1970,
68+
kTimeAccuracy,
69+
@"Offset %f - Date: %@", (double)value, date);
70+
[timeStamp release];
71+
72+
// Test Creation - timeIntervalSince1970.
73+
timeStamp = [[GPBTimestamp alloc] initWithTimeIntervalSince1970:value];
74+
75+
XCTAssertGreaterThanOrEqual(timeStamp.nanos, 0,
76+
@"Offset %f - Date: %@", (double)value, date);
77+
XCTAssertLessThan(timeStamp.nanos, 1e9,
78+
@"Offset %f - Date: %@", (double)value, date);
79+
80+
XCTAssertEqualWithAccuracy(value, timeStamp.timeIntervalSince1970,
81+
kTimeAccuracy,
82+
@"Offset %f - Date: %@", (double)value, date);
83+
[timeStamp release];
84+
85+
// Test Mutation - date.
86+
timeStamp = [[GPBTimestamp alloc] init];
87+
timeStamp.date = date;
88+
89+
XCTAssertGreaterThanOrEqual(timeStamp.nanos, 0,
90+
@"Offset %f - Date: %@", (double)value, date);
91+
XCTAssertLessThan(timeStamp.nanos, 1e9,
92+
@"Offset %f - Date: %@", (double)value, date);
93+
94+
XCTAssertEqualWithAccuracy(value, timeStamp.date.timeIntervalSince1970,
95+
kTimeAccuracy,
96+
@"Offset %f - Date: %@", (double)value, date);
97+
[timeStamp release];
98+
99+
// Test Mutation - timeIntervalSince1970.
100+
timeStamp = [[GPBTimestamp alloc] init];
101+
timeStamp.timeIntervalSince1970 = value;
102+
103+
XCTAssertGreaterThanOrEqual(timeStamp.nanos, 0,
104+
@"Offset %f - Date: %@", (double)value, date);
105+
XCTAssertLessThan(timeStamp.nanos, 1e9,
106+
@"Offset %f - Date: %@", (double)value, date);
107+
108+
XCTAssertEqualWithAccuracy(value, timeStamp.date.timeIntervalSince1970,
109+
kTimeAccuracy,
110+
@"Offset %f - Date: %@", (double)value, date);
111+
112+
[timeStamp release];
113+
}
82114
}
83115

84116
- (void)testDuration {
85-
// Test Creation.
86-
NSTimeInterval time = [[NSDate date] timeIntervalSince1970];
87-
GPBDuration *duration =
88-
[[GPBDuration alloc] initWithTimeIntervalSince1970:time];
89-
NSTimeInterval durationTime = duration.timeIntervalSince1970;
90-
XCTAssertEqualWithAccuracy(time, durationTime, kTimeAccuracy);
91-
[duration release];
92-
93-
// Test Mutation.
94-
GPBDuration *duration2 =
95-
[[GPBDuration alloc] initWithTimeIntervalSince1970:time];
96-
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:kFutureOffsetInterval];
97-
time = date.timeIntervalSince1970;
98-
duration2.timeIntervalSince1970 = time;
99-
durationTime = duration2.timeIntervalSince1970;
100-
XCTAssertEqualWithAccuracy(time, durationTime, kTimeAccuracy);
101-
[duration2 release];
117+
// Test negative and positive values.
118+
NSTimeInterval values[] = { -1000.0001, -500.0, -0.5, 0, 0.75, 1000.0, 2000.0002 };
119+
for (size_t i = 0; i < GPBARRAYSIZE(values); ++i) {
120+
NSTimeInterval value = values[i];
121+
122+
// Test Creation.
123+
GPBDuration *duration =
124+
[[GPBDuration alloc] initWithTimeInterval:value];
125+
XCTAssertEqualWithAccuracy(value, duration.timeInterval, kTimeAccuracy,
126+
@"For interval %f", (double)value);
127+
if (value > 0) {
128+
XCTAssertGreaterThanOrEqual(duration.seconds, 0,
129+
@"For interval %f", (double)value);
130+
XCTAssertGreaterThanOrEqual(duration.nanos, 0,
131+
@"For interval %f", (double)value);
132+
} else {
133+
XCTAssertLessThanOrEqual(duration.seconds, 0,
134+
@"For interval %f", (double)value);
135+
XCTAssertLessThanOrEqual(duration.nanos, 0,
136+
@"For interval %f", (double)value);
137+
}
138+
[duration release];
139+
140+
// Test Mutation.
141+
duration = [[GPBDuration alloc] init];
142+
duration.timeInterval = value;
143+
XCTAssertEqualWithAccuracy(value, duration.timeInterval, kTimeAccuracy,
144+
@"For interval %f", (double)value);
145+
if (value > 0) {
146+
XCTAssertGreaterThanOrEqual(duration.seconds, 0,
147+
@"For interval %f", (double)value);
148+
XCTAssertGreaterThanOrEqual(duration.nanos, 0,
149+
@"For interval %f", (double)value);
150+
} else {
151+
XCTAssertLessThanOrEqual(duration.seconds, 0,
152+
@"For interval %f", (double)value);
153+
XCTAssertLessThanOrEqual(duration.nanos, 0,
154+
@"For interval %f", (double)value);
155+
}
156+
[duration release];
157+
}
102158
}
103159

104160
- (void)testAnyHelpers {

0 commit comments

Comments
 (0)