As of iOS 13.3.1, apps installed with free developer accounts that contain embedded frameworks fail to launch. To work around this, we now link all dependencies via Cocoapods as static libraries.
374 lines
12 KiB
Objective-C
374 lines
12 KiB
Objective-C
/* Copyright (c) 2011 Google Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#if !__has_feature(objc_arc)
|
|
#error "This file needs to be compiled with ARC enabled."
|
|
#endif
|
|
|
|
#import "GTLRDateTime.h"
|
|
|
|
static NSUInteger const kGTLRDateComponentBits = (NSCalendarUnitYear | NSCalendarUnitMonth
|
|
| NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute
|
|
| NSCalendarUnitSecond);
|
|
|
|
@interface GTLRDateTime ()
|
|
|
|
- (void)setFromDate:(NSDate *)date;
|
|
- (void)setFromRFC3339String:(NSString *)str;
|
|
|
|
@property(nonatomic, copy, readwrite) NSDateComponents *dateComponents;
|
|
@property(nonatomic, assign, readwrite) NSInteger milliseconds;
|
|
@property(nonatomic, strong, readwrite, nullable) NSNumber *offsetMinutes;
|
|
|
|
@property(nonatomic, assign, readwrite) BOOL hasTime;
|
|
|
|
@end
|
|
|
|
|
|
@implementation GTLRDateTime {
|
|
NSDate *_cachedDate;
|
|
NSString *_cachedRFC3339String;
|
|
}
|
|
|
|
// A note about _milliseconds:
|
|
// RFC 3339 has support for fractions of a second. NSDateComponents is all
|
|
// NSInteger based, so it can't handle a fraction of a second. NSDate is
|
|
// built on NSTimeInterval so it has sub-millisecond precision. GTLR takes
|
|
// the compromise of supporting the RFC's optional fractional second support
|
|
// by maintaining a number of milliseconds past what fits in the
|
|
// NSDateComponents. The parsing and string conversions will include
|
|
// 3 decimal digits (hence milliseconds). When going to a string, the decimal
|
|
// digits are only included if the milliseconds are non zero.
|
|
|
|
@dynamic date;
|
|
@dynamic RFC3339String;
|
|
@dynamic stringValue;
|
|
@dynamic hasTime;
|
|
|
|
@synthesize dateComponents = _dateComponents,
|
|
milliseconds = _milliseconds,
|
|
offsetMinutes = _offsetMinutes;
|
|
|
|
+ (instancetype)dateTimeWithRFC3339String:(NSString *)str {
|
|
if (str == nil) return nil;
|
|
|
|
GTLRDateTime *result = [[self alloc] init];
|
|
[result setFromRFC3339String:str];
|
|
return result;
|
|
}
|
|
|
|
+ (instancetype)dateTimeWithDate:(NSDate *)date {
|
|
if (date == nil) return nil;
|
|
|
|
GTLRDateTime *result = [[self alloc] init];
|
|
[result setFromDate:date];
|
|
return result;
|
|
}
|
|
|
|
+ (instancetype)dateTimeWithDate:(NSDate *)date
|
|
offsetMinutes:(NSInteger)offsetMinutes {
|
|
GTLRDateTime *result = [self dateTimeWithDate:date];
|
|
result.offsetMinutes = @(offsetMinutes);
|
|
return result;
|
|
}
|
|
|
|
+ (instancetype)dateTimeForAllDayWithDate:(NSDate *)date {
|
|
if (date == nil) return nil;
|
|
|
|
GTLRDateTime *result = [[self alloc] init];
|
|
[result setFromDate:date];
|
|
result.hasTime = NO;
|
|
return result;
|
|
}
|
|
|
|
+ (instancetype)dateTimeWithDateComponents:(NSDateComponents *)components {
|
|
NSCalendar *cal = components.calendar ?: [self calendar];
|
|
NSDate *date = [cal dateFromComponents:components];
|
|
|
|
return [self dateTimeWithDate:date];
|
|
}
|
|
|
|
- (id)copyWithZone:(NSZone *)zone {
|
|
// Object is immutable
|
|
return self;
|
|
}
|
|
|
|
- (BOOL)isEqual:(GTLRDateTime *)other {
|
|
if (self == other) return YES;
|
|
if (![other isKindOfClass:[GTLRDateTime class]]) return NO;
|
|
|
|
BOOL areDateComponentsEqual = [self.dateComponents isEqual:other.dateComponents];
|
|
if (!areDateComponentsEqual) return NO;
|
|
|
|
NSNumber *offsetMinutes = self.offsetMinutes;
|
|
NSNumber *otherOffsetMinutes = other.offsetMinutes;
|
|
if ((offsetMinutes == nil) != (otherOffsetMinutes == nil)
|
|
|| (offsetMinutes.integerValue != otherOffsetMinutes.integerValue)) return NO;
|
|
|
|
return (self.milliseconds == other.milliseconds);
|
|
}
|
|
|
|
- (NSUInteger)hash {
|
|
return [[self date] hash];
|
|
}
|
|
|
|
- (NSString *)description {
|
|
return [NSString stringWithFormat:@"%@ %p: {%@}",
|
|
[self class], self, self.RFC3339String];
|
|
}
|
|
|
|
- (NSDate *)date {
|
|
@synchronized(self) {
|
|
if (_cachedDate) return _cachedDate;
|
|
}
|
|
|
|
NSDateComponents *dateComponents = self.dateComponents;
|
|
NSTimeInterval extraMillisecondsAsSeconds = 0.0;
|
|
NSCalendar *cal = [[self class] calendar];
|
|
|
|
if (!self.hasTime) {
|
|
// We're not keeping track of a time, but NSDate always is based on
|
|
// an absolute time. We want to avoid returning an NSDate where the
|
|
// calendar date appears different from what was used to create our
|
|
// date-time object.
|
|
//
|
|
// We'll make a copy of the date components, setting the time on our
|
|
// copy to noon GMT, since that ensures the date renders correctly for
|
|
// any time zone.
|
|
NSDateComponents *noonDateComponents = [dateComponents copy];
|
|
[noonDateComponents setHour:12];
|
|
[noonDateComponents setMinute:0];
|
|
[noonDateComponents setSecond:0];
|
|
dateComponents = noonDateComponents;
|
|
} else {
|
|
// Add in the fractional seconds that don't fit into NSDateComponents.
|
|
extraMillisecondsAsSeconds = ((NSTimeInterval)self.milliseconds) / 1000.0;
|
|
}
|
|
|
|
NSDate *date = [cal dateFromComponents:dateComponents];
|
|
|
|
// Add in any milliseconds that didn't fit into the dateComponents.
|
|
if (extraMillisecondsAsSeconds > 0.0) {
|
|
date = [date dateByAddingTimeInterval:extraMillisecondsAsSeconds];
|
|
}
|
|
|
|
@synchronized(self) {
|
|
_cachedDate = date;
|
|
}
|
|
return date;
|
|
}
|
|
|
|
- (NSString *)stringValue {
|
|
return self.RFC3339String;
|
|
}
|
|
|
|
- (NSString *)RFC3339String {
|
|
@synchronized(self) {
|
|
if (_cachedRFC3339String) return _cachedRFC3339String;
|
|
}
|
|
|
|
NSDateComponents *dateComponents = self.dateComponents;
|
|
|
|
NSString *timeString = @""; // timeString like "T15:10:46-08:00"
|
|
|
|
if (self.hasTime) {
|
|
NSString *fractionalSecondsString = @"";
|
|
if (self.milliseconds > 0.0) {
|
|
fractionalSecondsString = [NSString stringWithFormat:@".%03ld", (long)self.milliseconds];
|
|
}
|
|
|
|
// If the dateTime was created from a string with a time offset, render that back in
|
|
// and adjust the time.
|
|
NSString *offsetStr = @"Z";
|
|
NSNumber *offsetMinutes = self.offsetMinutes;
|
|
if (offsetMinutes != nil) {
|
|
BOOL isNegative = NO;
|
|
NSInteger offsetVal = offsetMinutes.integerValue;
|
|
if (offsetVal < 0) {
|
|
isNegative = YES;
|
|
offsetVal = -offsetVal;
|
|
}
|
|
NSInteger mins = offsetVal % 60;
|
|
NSInteger hours = (offsetVal - mins) / 60;
|
|
offsetStr = [NSString stringWithFormat:@"%c%02ld:%02ld",
|
|
isNegative ? '-' : '+', (long)hours, (long)mins];
|
|
|
|
// Adjust date components back to account for the offset.
|
|
//
|
|
// This is the inverse of the adjustment done in setFromRFC3339String:.
|
|
if (offsetVal != 0) {
|
|
NSDate *adjustedDate =
|
|
[self.date dateByAddingTimeInterval:(offsetMinutes.integerValue * 60)];
|
|
NSCalendar *calendar = [[self class] calendar];
|
|
dateComponents = [calendar components:kGTLRDateComponentBits
|
|
fromDate:adjustedDate];
|
|
}
|
|
}
|
|
|
|
timeString = [NSString stringWithFormat:@"T%02ld:%02ld:%02ld%@%@",
|
|
(long)dateComponents.hour, (long)dateComponents.minute,
|
|
(long)dateComponents.second, fractionalSecondsString,
|
|
offsetStr];
|
|
}
|
|
|
|
// full dateString like "2006-11-17T15:10:46-08:00"
|
|
NSString *dateString = [NSString stringWithFormat:@"%04ld-%02ld-%02ld%@",
|
|
(long)dateComponents.year, (long)dateComponents.month,
|
|
(long)dateComponents.day, timeString];
|
|
|
|
@synchronized(self) {
|
|
_cachedRFC3339String = dateString;
|
|
}
|
|
return dateString;
|
|
}
|
|
|
|
- (void)setFromDate:(NSDate *)date {
|
|
NSCalendar *cal = [[self class] calendar];
|
|
|
|
NSDateComponents *components = [cal components:kGTLRDateComponentBits
|
|
fromDate:date];
|
|
self.dateComponents = components;
|
|
|
|
// Extract the fractional seconds.
|
|
NSTimeInterval asTimeInterval = [date timeIntervalSince1970];
|
|
NSTimeInterval worker = asTimeInterval - trunc(asTimeInterval);
|
|
self.milliseconds = (NSInteger)round(worker * 1000.0);
|
|
}
|
|
|
|
- (void)setFromRFC3339String:(NSString *)str {
|
|
static NSCharacterSet *gDashSet;
|
|
static NSCharacterSet *gTSet;
|
|
static NSCharacterSet *gColonSet;
|
|
static NSCharacterSet *gPlusMinusZSet;
|
|
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
gDashSet = [NSCharacterSet characterSetWithCharactersInString:@"-"];
|
|
gTSet = [NSCharacterSet characterSetWithCharactersInString:@"Tt "];
|
|
gColonSet = [NSCharacterSet characterSetWithCharactersInString:@":"];
|
|
gPlusMinusZSet = [NSCharacterSet characterSetWithCharactersInString:@"+-zZ"];
|
|
});
|
|
|
|
NSInteger year = NSDateComponentUndefined;
|
|
NSInteger month = NSDateComponentUndefined;
|
|
NSInteger day = NSDateComponentUndefined;
|
|
NSInteger hour = NSDateComponentUndefined;
|
|
NSInteger minute = NSDateComponentUndefined;
|
|
NSInteger sec = NSDateComponentUndefined;
|
|
NSInteger milliseconds = 0;
|
|
double secDouble = -1.0;
|
|
NSString* sign = nil;
|
|
NSInteger offsetHour = 0;
|
|
NSInteger offsetMinute = 0;
|
|
|
|
if (str.length > 0) {
|
|
NSScanner* scanner = [NSScanner scannerWithString:str];
|
|
// There should be no whitespace, so no skip characters.
|
|
[scanner setCharactersToBeSkipped:nil];
|
|
|
|
// for example, scan 2006-11-17T15:10:46-08:00
|
|
// or 2006-11-17T15:10:46Z
|
|
if (// yyyy-mm-dd
|
|
[scanner scanInteger:&year] &&
|
|
[scanner scanCharactersFromSet:gDashSet intoString:NULL] &&
|
|
[scanner scanInteger:&month] &&
|
|
[scanner scanCharactersFromSet:gDashSet intoString:NULL] &&
|
|
[scanner scanInteger:&day] &&
|
|
// Thh:mm:ss
|
|
[scanner scanCharactersFromSet:gTSet intoString:NULL] &&
|
|
[scanner scanInteger:&hour] &&
|
|
[scanner scanCharactersFromSet:gColonSet intoString:NULL] &&
|
|
[scanner scanInteger:&minute] &&
|
|
[scanner scanCharactersFromSet:gColonSet intoString:NULL] &&
|
|
[scanner scanDouble:&secDouble]) {
|
|
|
|
// At this point we got secDouble, pull it apart.
|
|
sec = (NSInteger)secDouble;
|
|
double worker = secDouble - ((double)sec);
|
|
milliseconds = (NSInteger)round(worker * 1000.0);
|
|
|
|
// Finish parsing, now the offset info.
|
|
if (// Z or +hh:mm
|
|
[scanner scanCharactersFromSet:gPlusMinusZSet intoString:&sign] &&
|
|
[scanner scanInteger:&offsetHour] &&
|
|
[scanner scanCharactersFromSet:gColonSet intoString:NULL] &&
|
|
[scanner scanInteger:&offsetMinute]) {
|
|
}
|
|
}
|
|
}
|
|
|
|
NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
|
|
[dateComponents setYear:year];
|
|
[dateComponents setMonth:month];
|
|
[dateComponents setDay:day];
|
|
[dateComponents setHour:hour];
|
|
[dateComponents setMinute:minute];
|
|
[dateComponents setSecond:sec];
|
|
|
|
BOOL isMinusOffset = [sign isEqual:@"-"];
|
|
if (isMinusOffset || [sign isEqual:@"+"]) {
|
|
NSInteger totalOffsetMinutes = ((offsetHour * 60) + offsetMinute) * (isMinusOffset ? -1 : 1);
|
|
self.offsetMinutes = @(totalOffsetMinutes);
|
|
|
|
// Minus offset means Universal time is that many hours and minutes ahead.
|
|
//
|
|
// This is the inverse of the adjustment done above in RFC3339String.
|
|
NSTimeInterval deltaOffsetSeconds = -totalOffsetMinutes * 60;
|
|
NSCalendar *calendar = [[self class] calendar];
|
|
NSDate *scannedDate = [calendar dateFromComponents:dateComponents];
|
|
NSDate *offsetDate = [scannedDate dateByAddingTimeInterval:deltaOffsetSeconds];
|
|
|
|
dateComponents = [calendar components:kGTLRDateComponentBits
|
|
fromDate:offsetDate];
|
|
}
|
|
|
|
self.dateComponents = dateComponents;
|
|
self.milliseconds = milliseconds;
|
|
}
|
|
|
|
- (BOOL)hasTime {
|
|
NSDateComponents *dateComponents = self.dateComponents;
|
|
|
|
BOOL hasTime = ([dateComponents hour] != NSDateComponentUndefined
|
|
&& [dateComponents minute] != NSDateComponentUndefined);
|
|
|
|
return hasTime;
|
|
}
|
|
|
|
- (void)setHasTime:(BOOL)shouldHaveTime {
|
|
// We'll set time values to zero or kUndefinedDateComponent as appropriate.
|
|
BOOL hadTime = self.hasTime;
|
|
|
|
if (shouldHaveTime && !hadTime) {
|
|
[_dateComponents setHour:0];
|
|
[_dateComponents setMinute:0];
|
|
[_dateComponents setSecond:0];
|
|
_milliseconds = 0;
|
|
} else if (hadTime && !shouldHaveTime) {
|
|
[_dateComponents setHour:NSDateComponentUndefined];
|
|
[_dateComponents setMinute:NSDateComponentUndefined];
|
|
[_dateComponents setSecond:NSDateComponentUndefined];
|
|
_milliseconds = 0;
|
|
}
|
|
}
|
|
|
|
+ (NSCalendar *)calendar {
|
|
NSCalendar *cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
|
|
cal.timeZone = (NSTimeZone * _Nonnull)[NSTimeZone timeZoneWithName:@"Universal"];
|
|
return cal;
|
|
}
|
|
|
|
@end
|