/core/externals/update-engine/externals/gdata-objectivec-client/Source/Elements/GDataDateTime.m

http://macfuse.googlecode.com/ · Objective C · 434 lines · 299 code · 88 blank · 47 comment · 76 complexity · bdbebcf4e944a2d0974ac28e7c3b2a0b MD5 · raw file

  1. /* Copyright (c) 2007 Google Inc.
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. //
  16. // GDataDateTime.m
  17. //
  18. #import "GDataDateTime.h"
  19. static NSMutableDictionary *gCalendarsForTimeZones = nil;
  20. @implementation GDataDateTime
  21. + (void)initialize {
  22. // Note that initialize is guaranteed by the runtime to be called in a
  23. // thread-safe manner.
  24. gCalendarsForTimeZones = [[NSMutableDictionary alloc] init];
  25. }
  26. + (GDataDateTime *)dateTimeWithRFC3339String:(NSString *)str {
  27. return [[[GDataDateTime alloc] initWithRFC3339String:str] autorelease];
  28. }
  29. + (GDataDateTime *)dateTimeWithDate:(NSDate *)date timeZone:(NSTimeZone *)tz {
  30. return [[[GDataDateTime alloc] initWithDate:date
  31. timeZone:tz] autorelease];
  32. }
  33. - (id)initWithRFC3339String:(NSString *)str {
  34. self = [super init];
  35. if (self) {
  36. [self setFromRFC3339String:str];
  37. }
  38. return self;
  39. }
  40. - (id)initWithDate:(NSDate *)date timeZone:(NSTimeZone *)tz {
  41. self = [super init];
  42. if (self) {
  43. [self setFromDate:date timeZone:tz];
  44. }
  45. return self;
  46. }
  47. - (void)dealloc {
  48. [dateComponents_ release];
  49. [timeZone_ release];
  50. [super dealloc];
  51. }
  52. - (id)copyWithZone:(NSZone *)zone {
  53. GDataDateTime *newObj = [[GDataDateTime alloc] init];
  54. [newObj setIsUniversalTime:[self isUniversalTime]];
  55. [newObj setTimeZone:[self timeZone] withOffsetSeconds:[self offsetSeconds]];
  56. NSDateComponents *newDateComponents;
  57. NSDateComponents *oldDateComponents = [self dateComponents];
  58. // TODO: Experiments show it lies in 10.4.8 commented out for now.
  59. if (NO && [NSDateComponents conformsToProtocol:@protocol(NSCopying)]) {
  60. newDateComponents = [[oldDateComponents copyWithZone:zone] autorelease];
  61. } else {
  62. // NSDateComponents doesn't implement NSCopying in 10.4. We'll just retain
  63. // it, which is fine since we never set individual components
  64. // except after allocating a new NSDateCompoents instance.
  65. newDateComponents = oldDateComponents;
  66. }
  67. [newObj setDateComponents:newDateComponents];
  68. return newObj;
  69. }
  70. // until NSDateComponent implements isEqual, we'll use this
  71. - (BOOL)doesDateComponents:(NSDateComponents *)dc1
  72. equalDateComponents:(NSDateComponents *)dc2 {
  73. return [dc1 era] == [dc2 era]
  74. && [dc1 year] == [dc2 year]
  75. && [dc1 month] == [dc2 month]
  76. && [dc1 day] == [dc2 day]
  77. && [dc1 hour] == [dc2 hour]
  78. && [dc1 minute] == [dc2 minute]
  79. && [dc1 second] == [dc2 second]
  80. && [dc1 week] == [dc2 week]
  81. && [dc1 weekday] == [dc2 weekday]
  82. && [dc1 weekdayOrdinal] == [dc2 weekdayOrdinal];
  83. }
  84. - (BOOL)isEqual:(GDataDateTime *)other {
  85. if (self == other) return YES;
  86. if (![other isKindOfClass:[GDataDateTime class]]) return NO;
  87. return [self offsetSeconds] == [other offsetSeconds]
  88. && [self isUniversalTime] == [other isUniversalTime]
  89. && [self timeZone] == [other timeZone]
  90. && [self doesDateComponents:[self dateComponents]
  91. equalDateComponents:[other dateComponents]];
  92. }
  93. - (NSString *)description {
  94. return [NSString stringWithFormat:@"%@ %p: {%@}",
  95. [self class], self, [self RFC3339String]];
  96. }
  97. - (NSTimeZone *)timeZone {
  98. if (timeZone_) {
  99. return timeZone_;
  100. }
  101. if ([self isUniversalTime]) {
  102. NSTimeZone *ztz = [NSTimeZone timeZoneWithName:@"Universal"];
  103. return ztz;
  104. }
  105. NSInteger offsetSeconds = [self offsetSeconds];
  106. if (offsetSeconds != NSUndefinedDateComponent) {
  107. NSTimeZone *tz = [NSTimeZone timeZoneForSecondsFromGMT:offsetSeconds];
  108. return tz;
  109. }
  110. return nil;
  111. }
  112. - (void)setTimeZone:(NSTimeZone *)timeZone {
  113. [timeZone_ release];
  114. timeZone_ = [timeZone retain];
  115. if (timeZone) {
  116. NSInteger offsetSeconds = [timeZone secondsFromGMTForDate:[self date]];
  117. [self setOffsetSeconds:offsetSeconds];
  118. } else {
  119. [self setOffsetSeconds:NSUndefinedDateComponent];
  120. }
  121. }
  122. - (void)setTimeZone:(NSTimeZone *)timeZone withOffsetSeconds:(NSInteger)val {
  123. [timeZone_ release];
  124. timeZone_ = [timeZone retain];
  125. offsetSeconds_ = val;
  126. }
  127. - (NSCalendar *)calendarForTimeZone:(NSTimeZone *)tz {
  128. NSCalendar *cal = nil;
  129. @synchronized(gCalendarsForTimeZones) {
  130. id tzKey = (tz ? tz : [NSNull null]);
  131. cal = [gCalendarsForTimeZones objectForKey:tzKey];
  132. if (cal == nil) {
  133. cal = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease];
  134. if (tz) {
  135. [cal setTimeZone:tz];
  136. }
  137. [gCalendarsForTimeZones setObject:cal forKey:tzKey];
  138. }
  139. }
  140. return cal;
  141. }
  142. - (NSCalendar *)calendar {
  143. NSTimeZone *tz = self.timeZone;
  144. return [self calendarForTimeZone:tz];
  145. }
  146. - (NSDate *)date {
  147. NSDateComponents *dateComponents = [self dateComponents];
  148. NSCalendar *cal;
  149. if (![self hasTime]) {
  150. // we're not keeping track of a time, but NSDate always is based on
  151. // an absolute time. We want to avoid returning an NSDate where the
  152. // calendar date appears different from what was used to create our
  153. // date-time object.
  154. //
  155. // We'll make a copy of the date components, setting the time on our
  156. // copy to noon GMT, since that ensures the date renders correctly for
  157. // any time zone
  158. //
  159. // Note that on 10.4 NSDateComponents does not implement NSCopying, so we'll
  160. // assemble an NSDateComponents manually here
  161. NSDateComponents *noonDateComponents = [[[NSDateComponents alloc] init] autorelease];
  162. [noonDateComponents setYear:[dateComponents year]];
  163. [noonDateComponents setMonth:[dateComponents month]];
  164. [noonDateComponents setDay:[dateComponents day]];
  165. [noonDateComponents setHour:12];
  166. [noonDateComponents setMinute:0];
  167. [noonDateComponents setSecond:0];
  168. dateComponents = noonDateComponents;
  169. NSTimeZone *gmt = [NSTimeZone timeZoneWithName:@"Universal"];
  170. cal = [self calendarForTimeZone:gmt];
  171. } else {
  172. cal = self.calendar;
  173. }
  174. NSDate *date = [cal dateFromComponents:dateComponents];
  175. return date;
  176. }
  177. - (NSString *)stringValue {
  178. return [self RFC3339String];
  179. }
  180. - (NSString *)RFC3339String {
  181. NSDateComponents *dateComponents = [self dateComponents];
  182. NSInteger offset = [self offsetSeconds];
  183. NSString *timeString = @""; // timeString like "T15:10:46-08:00"
  184. if ([self hasTime]) {
  185. NSString *timeOffsetString; // timeOffsetString like "-08:00"
  186. if ([self isUniversalTime]) {
  187. timeOffsetString = @"Z";
  188. } else if (offset == NSUndefinedDateComponent) {
  189. // unknown offset is rendered as -00:00 per
  190. // http://www.ietf.org/rfc/rfc3339.txt section 4.3
  191. timeOffsetString = @"-00:00";
  192. } else {
  193. NSString *sign = @"+";
  194. if (offset < 0) {
  195. sign = @"-";
  196. offset = -offset;
  197. }
  198. timeOffsetString = [NSString stringWithFormat:@"%@%02ld:%02ld",
  199. sign, (long)(offset/(60*60)) % 24, (long)(offset / 60) % 60];
  200. }
  201. timeString = [NSString stringWithFormat:@"T%02ld:%02ld:%02ld%@",
  202. (long)[dateComponents hour], (long)[dateComponents minute],
  203. (long)[dateComponents second], timeOffsetString];
  204. }
  205. // full dateString like "2006-11-17T15:10:46-08:00"
  206. NSString *dateString = [NSString stringWithFormat:@"%04ld-%02ld-%02ld%@",
  207. (long)[dateComponents year], (long)[dateComponents month],
  208. (long)[dateComponents day], timeString];
  209. return dateString;
  210. }
  211. - (void)setFromDate:(NSDate *)date timeZone:(NSTimeZone *)tz {
  212. NSCalendar *cal = [self calendarForTimeZone:tz];
  213. if (tz) {
  214. [cal setTimeZone:tz];
  215. }
  216. NSUInteger const kComponentBits = (NSYearCalendarUnit | NSMonthCalendarUnit
  217. | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit
  218. | NSSecondCalendarUnit);
  219. NSDateComponents *components = [cal components:kComponentBits fromDate:date];
  220. [self setDateComponents:components];
  221. [self setIsUniversalTime:NO];
  222. NSInteger offset = NSUndefinedDateComponent;
  223. if (tz) {
  224. offset = [tz secondsFromGMTForDate:date];
  225. if (offset == 0 && [tz isEqualToTimeZone:[NSTimeZone timeZoneWithName:@"Universal"]]) {
  226. [self setIsUniversalTime:YES];
  227. }
  228. }
  229. [self setOffsetSeconds:offset];
  230. // though offset seconds are authoritative, we'll retain the time zone
  231. // since we can't regenerate it reliably from just the offset
  232. timeZone_ = [tz retain];
  233. }
  234. static inline BOOL ScanInteger(NSScanner *scanner, NSInteger *targetInteger) {
  235. return [scanner scanInteger:targetInteger];
  236. }
  237. - (void)setFromRFC3339String:(NSString *)str {
  238. NSInteger year = NSUndefinedDateComponent;
  239. NSInteger month = NSUndefinedDateComponent;
  240. NSInteger day = NSUndefinedDateComponent;
  241. NSInteger hour = NSUndefinedDateComponent;
  242. NSInteger minute = NSUndefinedDateComponent;
  243. NSInteger sec = NSUndefinedDateComponent;
  244. float secFloat = -1.0f;
  245. NSString* sign = nil;
  246. NSInteger offsetHour = 0;
  247. NSInteger offsetMinute = 0;
  248. NSScanner* scanner = [NSScanner scannerWithString:str];
  249. NSCharacterSet* dashSet = [NSCharacterSet characterSetWithCharactersInString:@"-"];
  250. NSCharacterSet* tSet = [NSCharacterSet characterSetWithCharactersInString:@"Tt "];
  251. NSCharacterSet* colonSet = [NSCharacterSet characterSetWithCharactersInString:@":"];
  252. NSCharacterSet* plusMinusZSet = [NSCharacterSet characterSetWithCharactersInString:@"+-zZ"];
  253. // for example, scan 2006-11-17T15:10:46-08:00
  254. // or 2006-11-17T15:10:46Z
  255. if (// yyyy-mm-dd
  256. ScanInteger(scanner, &year) &&
  257. [scanner scanCharactersFromSet:dashSet intoString:NULL] &&
  258. ScanInteger(scanner, &month) &&
  259. [scanner scanCharactersFromSet:dashSet intoString:NULL] &&
  260. ScanInteger(scanner, &day) &&
  261. // Thh:mm:ss
  262. [scanner scanCharactersFromSet:tSet intoString:NULL] &&
  263. ScanInteger(scanner, &hour) &&
  264. [scanner scanCharactersFromSet:colonSet intoString:NULL] &&
  265. ScanInteger(scanner, &minute) &&
  266. [scanner scanCharactersFromSet:colonSet intoString:NULL] &&
  267. [scanner scanFloat:&secFloat] &&
  268. // Z or +hh:mm
  269. [scanner scanCharactersFromSet:plusMinusZSet intoString:&sign] &&
  270. ScanInteger(scanner, &offsetHour) &&
  271. [scanner scanCharactersFromSet:colonSet intoString:NULL] &&
  272. ScanInteger(scanner, &offsetMinute)) {
  273. }
  274. NSDateComponents *dateComponents = [[[NSDateComponents alloc] init] autorelease];
  275. [dateComponents setYear:year];
  276. [dateComponents setMonth:month];
  277. [dateComponents setDay:day];
  278. [dateComponents setHour:hour];
  279. [dateComponents setMinute:minute];
  280. if (secFloat < -1.0f || secFloat > -1.0f) sec = (NSInteger)secFloat;
  281. [dateComponents setSecond:sec];
  282. [self setDateComponents:dateComponents];
  283. // determine the offset, like from Z, or -08:00:00.0
  284. [self setTimeZone:nil];
  285. NSInteger totalOffset = NSUndefinedDateComponent;
  286. [self setIsUniversalTime:NO];
  287. if ([sign caseInsensitiveCompare:@"Z"] == NSOrderedSame) {
  288. [self setIsUniversalTime:YES];
  289. totalOffset = 0;
  290. } else if (sign != nil) {
  291. totalOffset = (60 * offsetMinute) + (60 * 60 * offsetHour);
  292. if ([sign isEqual:@"-"]) {
  293. if (totalOffset == 0) {
  294. // special case: offset of -0.00 means undefined offset
  295. totalOffset = NSUndefinedDateComponent;
  296. } else {
  297. totalOffset *= -1;
  298. }
  299. }
  300. }
  301. [self setOffsetSeconds:totalOffset];
  302. }
  303. - (BOOL)hasTime {
  304. NSDateComponents *dateComponents = [self dateComponents];
  305. BOOL hasTime = ([dateComponents hour] != NSUndefinedDateComponent
  306. && [dateComponents minute] != NSUndefinedDateComponent);
  307. return hasTime;
  308. }
  309. - (void)setHasTime:(BOOL)shouldHaveTime {
  310. // we'll set time values to zero or NSUndefinedDateComponent as appropriate
  311. BOOL hadTime = [self hasTime];
  312. if (shouldHaveTime && !hadTime) {
  313. [dateComponents_ setHour:0];
  314. [dateComponents_ setMinute:0];
  315. [dateComponents_ setSecond:0];
  316. offsetSeconds_ = NSUndefinedDateComponent;
  317. isUniversalTime_ = NO;
  318. } else if (hadTime && !shouldHaveTime) {
  319. [dateComponents_ setHour:NSUndefinedDateComponent];
  320. [dateComponents_ setMinute:NSUndefinedDateComponent];
  321. [dateComponents_ setSecond:NSUndefinedDateComponent];
  322. offsetSeconds_ = NSUndefinedDateComponent;
  323. isUniversalTime_ = NO;
  324. [self setTimeZone:nil];
  325. }
  326. }
  327. - (NSInteger)offsetSeconds {
  328. return offsetSeconds_;
  329. }
  330. - (void)setOffsetSeconds:(NSInteger)val {
  331. offsetSeconds_ = val;
  332. }
  333. - (BOOL)isUniversalTime {
  334. return isUniversalTime_;
  335. }
  336. - (void)setIsUniversalTime:(BOOL)flag {
  337. isUniversalTime_ = flag;
  338. }
  339. - (NSDateComponents *)dateComponents {
  340. return dateComponents_;
  341. }
  342. - (void)setDateComponents:(NSDateComponents *)dateComponents {
  343. [dateComponents_ autorelease];
  344. dateComponents_ = [dateComponents retain]; // NSDateComponents doesn't implement NSCopying in 10.4
  345. }
  346. @end