| File: | Other/Adium Spotlight Importer/../../Frameworks/AIUtilities Framework/Source/NSCalendarDate+ISO8601Parsing.m |
| Location: | line 236, column 4 |
| Description: | dead store |
| 1 | /*NSCalendarDate+ISO8601Parsing.m |
| 2 | * |
| 3 | *Created by Peter Hosey on 2006-02-20. |
| 4 | *Copyright 2006 Peter Hosey. All rights reserved. |
| 5 | */ |
| 6 | |
| 7 | #include <ctype.h> |
| 8 | #include <string.h> |
| 9 | |
| 10 | #import "NSCalendarDate+ISO8601Parsing.h" |
| 11 | |
| 12 | #ifndef DEFAULT_TIME_SEPARATOR |
| 13 | # define DEFAULT_TIME_SEPARATOR ':' |
| 14 | #endif |
| 15 | unichar ISO8601ParserDefaultTimeSeparatorCharacter = DEFAULT_TIME_SEPARATOR':'; |
| 16 | |
| 17 | static unsigned read_segment(const unsigned char *str, const unsigned char **next, unsigned *out_num_digits) { |
| 18 | unsigned num_digits = 0U; |
| 19 | unsigned value = 0U; |
| 20 | |
| 21 | while(isdigit__isctype ( ( * str ) , 0x00000400L )(*str)) { |
| 22 | value *= 10U; |
| 23 | value += *str - '0'; |
| 24 | ++num_digits; |
| 25 | ++str; |
| 26 | } |
| 27 | |
| 28 | if(next) *next = str; |
| 29 | if(out_num_digits) *out_num_digits = num_digits; |
| 30 | |
| 31 | return value; |
| 32 | } |
| 33 | static unsigned read_segment_4digits(const unsigned char *str, const unsigned char **next, unsigned *out_num_digits) { |
| 34 | unsigned num_digits = 0U; |
| 35 | unsigned value = 0U; |
| 36 | |
| 37 | if(isdigit__isctype ( ( * str ) , 0x00000400L )(*str)) { |
| 38 | value += *(str++) - '0'; |
| 39 | ++num_digits; |
| 40 | } |
| 41 | |
| 42 | if(isdigit__isctype ( ( * str ) , 0x00000400L )(*str)) { |
| 43 | value *= 10U; |
| 44 | value += *(str++) - '0'; |
| 45 | ++num_digits; |
| 46 | } |
| 47 | |
| 48 | if(isdigit__isctype ( ( * str ) , 0x00000400L )(*str)) { |
| 49 | value *= 10U; |
| 50 | value += *(str++) - '0'; |
| 51 | ++num_digits; |
| 52 | } |
| 53 | |
| 54 | if(isdigit__isctype ( ( * str ) , 0x00000400L )(*str)) { |
| 55 | value *= 10U; |
| 56 | value += *(str++) - '0'; |
| 57 | ++num_digits; |
| 58 | } |
| 59 | |
| 60 | if(next) *next = str; |
| 61 | if(out_num_digits) *out_num_digits = num_digits; |
| 62 | |
| 63 | return value; |
| 64 | } |
| 65 | static unsigned read_segment_2digits(const unsigned char *str, const unsigned char **next) { |
| 66 | unsigned value = 0U; |
| 67 | |
| 68 | if(isdigit__isctype ( ( * str ) , 0x00000400L )(*str)) |
| 69 | value += *str - '0'; |
| 70 | |
| 71 | if(isdigit__isctype ( ( * ++ str ) , 0x00000400L )(*++str)) { |
| 72 | value *= 10U; |
| 73 | value += *(str++) - '0'; |
| 74 | } |
| 75 | |
| 76 | if(next) *next = str; |
| 77 | |
| 78 | return value; |
| 79 | } |
| 80 | |
| 81 | //strtod doesn't support ',' as a separator. This does. |
| 82 | static double read_double(const unsigned char *str, const unsigned char **next) { |
| 83 | double value = 0.0; |
| 84 | |
| 85 | if(str) { |
| 86 | unsigned int_value = 0; |
| 87 | |
| 88 | while(isdigit__isctype ( ( * str ) , 0x00000400L )(*str)) { |
| 89 | int_value *= 10U; |
| 90 | int_value += (*(str++) - '0'); |
| 91 | } |
| 92 | value = int_value; |
| 93 | |
| 94 | if(((*str == ',') || (*str == '.'))) { |
| 95 | ++str; |
| 96 | |
| 97 | register double multiplier, multiplier_multiplier; |
| 98 | multiplier = multiplier_multiplier = 0.1; |
| 99 | |
| 100 | while(isdigit__isctype ( ( * str ) , 0x00000400L )(*str)) { |
| 101 | value += (*(str++) - '0') * multiplier; |
| 102 | multiplier *= multiplier_multiplier; |
| 103 | } |
| 104 | } |
| 105 | } |
| 106 | |
| 107 | if(next) *next = str; |
| 108 | |
| 109 | return value; |
| 110 | } |
| 111 | |
| 112 | static BOOL is_leap_year(unsigned year) { |
| 113 | return \ |
| 114 | ((year % 4U) == 0U) |
| 115 | && (((year % 100U) != 0U) |
| 116 | || ((year % 400U) == 0U)); |
| 117 | } |
| 118 | |
| 119 | @implementation NSCalendarDate(ISO8601Parsing) |
| 120 | |
| 121 | /*Valid ISO 8601 date formats: |
| 122 | * |
| 123 | *YYYYMMDD |
| 124 | *YYYY-MM-DD |
| 125 | *YYYY-MM |
| 126 | *YYYY |
| 127 | *YY //century |
| 128 | * //Implied century: YY is 00-99 |
| 129 | * YYMMDD |
| 130 | * YY-MM-DD |
| 131 | * -YYMM |
| 132 | * -YY-MM |
| 133 | * -YY |
| 134 | * //Implied year |
| 135 | * --MMDD |
| 136 | * --MM-DD |
| 137 | * --MM |
| 138 | * //Implied year and month |
| 139 | * ---DD |
| 140 | * //Ordinal dates: DDD is the number of the day in the year (1-366) |
| 141 | *YYYYDDD |
| 142 | *YYYY-DDD |
| 143 | * YYDDD |
| 144 | * YY-DDD |
| 145 | * -DDD |
| 146 | * //Week-based dates: ww is the number of the week, and d is the number (1-7) of the day in the week |
| 147 | *yyyyWwwd |
| 148 | *yyyy-Www-d |
| 149 | *yyyyWww |
| 150 | *yyyy-Www |
| 151 | *yyWwwd |
| 152 | *yy-Www-d |
| 153 | *yyWww |
| 154 | *yy-Www |
| 155 | * //Year of the implied decade |
| 156 | *-yWwwd |
| 157 | *-y-Www-d |
| 158 | *-yWww |
| 159 | *-y-Www |
| 160 | * //Week and day of implied year |
| 161 | * -Wwwd |
| 162 | * -Www-d |
| 163 | * //Week only of implied year |
| 164 | * -Www |
| 165 | * //Day only of implied week |
| 166 | * -W-d |
| 167 | */ |
| 168 | + (NSCalendarDate *)calendarDateWithString:(NSString *)str strictly:(BOOL)strict timeSeparator:(unichar)timeSep getRange:(out NSRange *)outRange { |
| 169 | if (!str || ![str length]) { |
| 170 | if (outRange) { |
| 171 | outRange->location = NSNotFound; |
| 172 | outRange->length = 0U; |
| 173 | } |
| 174 | |
| 175 | return nil0; |
| 176 | } |
| 177 | |
| 178 | NSCalendarDate *now = [NSCalendarDate calendarDate]; |
| 179 | unsigned |
| 180 | //Date |
| 181 | year = 0U, |
| 182 | month_or_week = 0U, |
| 183 | day = 0U, |
| 184 | //Time |
| 185 | hour = 0U; |
| 186 | NSTimeInterval |
| 187 | minute = 0.0, |
| 188 | second = 0.0; |
| 189 | //Time zone |
| 190 | signed tz_hour = 0; |
| 191 | signed tz_minute = 0; |
| 192 | |
| 193 | enum { |
| 194 | monthAndDate, |
| 195 | week, |
| 196 | dateOnly |
| 197 | } dateSpecification = monthAndDate; |
| 198 | |
| 199 | if(strict) timeSep = ISO8601ParserDefaultTimeSeparatorCharacter; |
| 200 | NSAssertdo { if ( ! ( ( timeSep != '\0' ) ) ) { [ [ NSAssertionHandler currentHandler ] handleFailureInMethod : _cmd object : self file : [ NSString stringWithCString : "/Users/ryan/Projects/Adium/Other/Adium Spotlight Importer/../../Frameworks/AIUtilities Framework/Source/NSCalendarDate+ISO8601Parsing.m" ] lineNumber : 200 description : ( ( @ "Time separator must not be NUL." ) ) , ( 0 ) , ( 0 ) , ( 0 ) , ( 0 ) , ( 0 ) ] ; } } while ( 0 )(timeSep != '\0', @"Time separator must not be NUL."); |
| 201 | |
| 202 | BOOL isValidDate = ([str length] > 0U); |
| 203 | NSTimeZone *timeZone = nil0; |
| 204 | NSCalendarDate *date = nil0; |
| 205 | |
| 206 | const unsigned char *ch = (const unsigned char *)[str UTF8String]; |
| 207 | |
| 208 | NSRange range = { 0U, 0U }; |
| 209 | const unsigned char *start_of_date = 0; |
| 210 | if (strict && isspace__istype ( ( * ch ) , 0x00004000L )(*ch)) { |
| 211 | range.location = NSNotFound; |
| 212 | isValidDate = NO( BOOL ) 0; |
| 213 | } else { |
| 214 | //Skip leading whitespace. |
| 215 | unsigned i = 0U; |
| 216 | for(unsigned len = strlen((const char *)ch); i < len; ++i) { |
| 217 | if(!isspace__istype ( ( ch [ i ] ) , 0x00004000L )(ch[i])) |
| 218 | break; |
| 219 | } |
| 220 | |
| 221 | range.location = i; |
| 222 | ch += i; |
| 223 | start_of_date = ch; |
| 224 | |
| 225 | unsigned segment; |
| 226 | unsigned num_leading_hyphens = 0U, num_digits = 0U; |
| 227 | |
| 228 | if (*ch == 'T') { |
| 229 | //There is no date here, only a time. Set the date to now; then we'll parse the time. |
| 230 | isValidDate = isdigit__isctype ( ( * ++ ch ) , 0x00000400L )(*++ch); |
| 231 | |
| 232 | year = [now yearOfCommonEra]; |
| 233 | month_or_week = [now monthOfYear]; |
| 234 | day = [now dayOfMonth]; |
| 235 | } else { |
Value stored to 'segment' is never read | |
| 236 | segment = 0U; |
| 237 | |
| 238 | while(*ch == '-') { |
| 239 | ++num_leading_hyphens; |
| 240 | ++ch; |
| 241 | } |
| 242 | |
| 243 | segment = read_segment(ch, &ch, &num_digits); |
| 244 | switch (num_digits) { |
| 245 | case 0: |
| 246 | if(*ch == 'W') { |
| 247 | if((ch[1] == '-') && isdigit__isctype ( ( ch [ 2 ] ) , 0x00000400L )(ch[2]) && ((num_leading_hyphens == 1U) || ((num_leading_hyphens == 2U) && !strict))) { |
| 248 | year = [now yearOfCommonEra]; |
| 249 | month_or_week = 1U; |
| 250 | ch += 2; |
| 251 | goto parseDayAfterWeek; |
| 252 | } else if(num_leading_hyphens == 1U) { |
| 253 | year = [now yearOfCommonEra]; |
| 254 | goto parseWeekAndDay; |
| 255 | } else |
| 256 | isValidDate = NO( BOOL ) 0; |
| 257 | } else |
| 258 | isValidDate = NO( BOOL ) 0; |
| 259 | break; |
| 260 | |
| 261 | case 8: //YYYY MM DD |
| 262 | if(num_leading_hyphens > 0U) |
| 263 | isValidDate = NO( BOOL ) 0; |
| 264 | else { |
| 265 | day = segment % 100U; |
| 266 | segment /= 100U; |
| 267 | month_or_week = segment % 100U; |
| 268 | year = segment / 100U; |
| 269 | } |
| 270 | break; |
| 271 | |
| 272 | case 6: //YYMMDD (implicit century) |
| 273 | if(num_leading_hyphens > 0U) |
| 274 | isValidDate = NO( BOOL ) 0; |
| 275 | else { |
| 276 | day = segment % 100U; |
| 277 | segment /= 100U; |
| 278 | month_or_week = segment % 100U; |
| 279 | year = [now yearOfCommonEra]; |
| 280 | year -= (year % 100U); |
| 281 | year += segment / 100U; |
| 282 | } |
| 283 | break; |
| 284 | |
| 285 | case 4: |
| 286 | switch(num_leading_hyphens) { |
| 287 | case 0: //YYYY |
| 288 | year = segment; |
| 289 | |
| 290 | if(*ch == '-') ++ch; |
| 291 | |
| 292 | if(!isdigit__isctype ( ( * ch ) , 0x00000400L )(*ch)) { |
| 293 | if(*ch == 'W') |
| 294 | goto parseWeekAndDay; |
| 295 | else |
| 296 | month_or_week = day = 1U; |
| 297 | } else { |
| 298 | segment = read_segment(ch, &ch, &num_digits); |
| 299 | switch(num_digits) { |
| 300 | case 4: //MMDD |
| 301 | day = segment % 100U; |
| 302 | month_or_week = segment / 100U; |
| 303 | break; |
| 304 | |
| 305 | case 2: //MM |
| 306 | month_or_week = segment; |
| 307 | |
| 308 | if(*ch == '-') ++ch; |
| 309 | if(!isdigit__isctype ( ( * ch ) , 0x00000400L )(*ch)) |
| 310 | day = 1U; |
| 311 | else |
| 312 | day = read_segment(ch, &ch, NULL( ( void * ) 0 )); |
| 313 | break; |
| 314 | |
| 315 | case 3: //DDD |
| 316 | day = segment % 1000U; |
| 317 | dateSpecification = dateOnly; |
| 318 | if(strict && (day > (365U + is_leap_year(year)))) |
| 319 | isValidDate = NO( BOOL ) 0; |
| 320 | break; |
| 321 | |
| 322 | default: |
| 323 | isValidDate = NO( BOOL ) 0; |
| 324 | } |
| 325 | } |
| 326 | break; |
| 327 | |
| 328 | case 1: //YYMM |
| 329 | month_or_week = segment % 100U; |
| 330 | year = segment / 100U; |
| 331 | |
| 332 | if(*ch == '-') ++ch; |
| 333 | if(!isdigit__isctype ( ( * ch ) , 0x00000400L )(*ch)) |
| 334 | day = 1U; |
| 335 | else |
| 336 | day = read_segment(ch, &ch, NULL( ( void * ) 0 )); |
| 337 | |
| 338 | break; |
| 339 | |
| 340 | case 2: //MMDD |
| 341 | day = segment % 100U; |
| 342 | month_or_week = segment / 100U; |
| 343 | year = [now yearOfCommonEra]; |
| 344 | |
| 345 | break; |
| 346 | |
| 347 | default: |
| 348 | isValidDate = NO( BOOL ) 0; |
| 349 | } //switch(num_leading_hyphens) (4 digits) |
| 350 | break; |
| 351 | |
| 352 | case 1: |
| 353 | if(strict) { |
| 354 | //Two digits only - never just one. |
| 355 | if(num_leading_hyphens == 1U) { |
| 356 | if(*ch == '-') ++ch; |
| 357 | if(*++ch == 'W') { |
| 358 | year = [now yearOfCommonEra]; |
| 359 | year -= (year % 10U); |
| 360 | year += segment; |
| 361 | goto parseWeekAndDay; |
| 362 | } else |
| 363 | isValidDate = NO( BOOL ) 0; |
| 364 | } else |
| 365 | isValidDate = NO( BOOL ) 0; |
| 366 | break; |
| 367 | } |
| 368 | case 2: |
| 369 | switch(num_leading_hyphens) { |
| 370 | case 0: |
| 371 | if(*ch == '-') { |
| 372 | //Implicit century |
| 373 | year = [now yearOfCommonEra]; |
| 374 | year -= (year % 100U); |
| 375 | year += segment; |
| 376 | |
| 377 | if(*++ch == 'W') |
| 378 | goto parseWeekAndDay; |
| 379 | else if(!isdigit__isctype ( ( * ch ) , 0x00000400L )(*ch)) { |
| 380 | goto centuryOnly; |
| 381 | } else { |
| 382 | //Get month and/or date. |
| 383 | segment = read_segment_4digits(ch, &ch, &num_digits); |
| 384 | NSLog(@"(%@) parsing month; segment is %u and ch is %s", str, segment, ch); |
| 385 | switch(num_digits) { |
| 386 | case 4: //YY-MMDD |
| 387 | day = segment % 100U; |
| 388 | month_or_week = segment / 100U; |
| 389 | break; |
| 390 | |
| 391 | case 1: //YY-M; YY-M-DD (extension) |
| 392 | if(strict) { |
| 393 | isValidDate = NO( BOOL ) 0; |
| 394 | break; |
| 395 | } |
| 396 | case 2: //YY-MM; YY-MM-DD |
| 397 | month_or_week = segment; |
| 398 | if(*ch == '-') { |
| 399 | if(isdigit__isctype ( ( * ++ ch ) , 0x00000400L )(*++ch)) |
| 400 | day = read_segment_2digits(ch, &ch); |
| 401 | else |
| 402 | day = 1U; |
| 403 | } else |
| 404 | day = 1U; |
| 405 | break; |
| 406 | |
| 407 | case 3: //Ordinal date. |
| 408 | day = segment; |
| 409 | dateSpecification = dateOnly; |
| 410 | break; |
| 411 | } |
| 412 | } |
| 413 | } else if(*ch == 'W') { |
| 414 | year = [now yearOfCommonEra]; |
| 415 | year -= (year % 100U); |
| 416 | year += segment; |
| 417 | |
| 418 | parseWeekAndDay: //*ch should be 'W' here. |
| 419 | if(!isdigit__isctype ( ( * ++ ch ) , 0x00000400L )(*++ch)) { |
| 420 | //Not really a week-based date; just a year followed by '-W'. |
| 421 | if(strict) |
| 422 | isValidDate = NO( BOOL ) 0; |
| 423 | else |
| 424 | month_or_week = day = 1U; |
| 425 | } else { |
| 426 | month_or_week = read_segment_2digits(ch, &ch); |
| 427 | if(*ch == '-') ++ch; |
| 428 | parseDayAfterWeek: |
| 429 | day = isdigit__isctype ( ( * ch ) , 0x00000400L )(*ch) ? read_segment_2digits(ch, &ch) : 1U; |
| 430 | dateSpecification = week; |
| 431 | } |
| 432 | } else { |
| 433 | //Century only. Assume current year. |
| 434 | centuryOnly: |
| 435 | year = segment * 100U + [now yearOfCommonEra] % 100U; |
| 436 | month_or_week = day = 1U; |
| 437 | } |
| 438 | break; |
| 439 | |
| 440 | case 1:; //-YY; -YY-MM (implicit century) |
| 441 | NSLog(@"(%@) found %u digits and one hyphen, so this is either -YY or -YY-MM; segment (year) is %u", str, num_digits, segment); |
| 442 | unsigned current_year = [now yearOfCommonEra]; |
| 443 | unsigned century = (current_year % 100U); |
| 444 | year = segment + (current_year - century); |
| 445 | if(num_digits == 1U) //implied decade |
| 446 | year += century - (current_year % 10U); |
| 447 | |
| 448 | if(*ch == '-') { |
| 449 | ++ch; |
| 450 | month_or_week = read_segment_2digits(ch, &ch); |
| 451 | NSLog(@"(%@) month is %u", str, month_or_week); |
| 452 | } |
| 453 | |
| 454 | day = 1U; |
| 455 | break; |
| 456 | |
| 457 | case 2: //--MM; --MM-DD |
| 458 | year = [now yearOfCommonEra]; |
| 459 | month_or_week = segment; |
| 460 | if(*ch == '-') { |
| 461 | ++ch; |
| 462 | day = read_segment_2digits(ch, &ch); |
| 463 | } |
| 464 | break; |
| 465 | |
| 466 | case 3: //---DD |
| 467 | year = [now yearOfCommonEra]; |
| 468 | month_or_week = [now monthOfYear]; |
| 469 | day = segment; |
| 470 | break; |
| 471 | |
| 472 | default: |
| 473 | isValidDate = NO( BOOL ) 0; |
| 474 | } //switch(num_leading_hyphens) (2 digits) |
| 475 | break; |
| 476 | |
| 477 | case 7: //YYYY DDD (ordinal date) |
| 478 | if(num_leading_hyphens > 0U) |
| 479 | isValidDate = NO( BOOL ) 0; |
| 480 | else { |
| 481 | day = segment % 1000U; |
| 482 | year = segment / 1000U; |
| 483 | dateSpecification = dateOnly; |
| 484 | if(strict && (day > (365U + is_leap_year(year)))) |
| 485 | isValidDate = NO( BOOL ) 0; |
| 486 | } |
| 487 | break; |
| 488 | |
| 489 | case 3: //--DDD (ordinal date, implicit year) |
| 490 | //Technically, the standard only allows one hyphen. But it says that two hyphens is the logical implementation, and one was dropped for brevity. So I have chosen to allow the missing hyphen. |
| 491 | if((num_leading_hyphens < 1U) || ((num_leading_hyphens > 2U) && !strict)) |
| 492 | isValidDate = NO( BOOL ) 0; |
| 493 | else { |
| 494 | day = segment; |
| 495 | year = [now yearOfCommonEra]; |
| 496 | dateSpecification = dateOnly; |
| 497 | if(strict && (day > (365U + is_leap_year(year)))) |
| 498 | isValidDate = NO( BOOL ) 0; |
| 499 | } |
| 500 | break; |
| 501 | |
| 502 | default: |
| 503 | isValidDate = NO( BOOL ) 0; |
| 504 | } |
| 505 | } |
| 506 | |
| 507 | if (isValidDate) { |
| 508 | if (isspace__istype ( ( * ch ) , 0x00004000L )(*ch) || (*ch == 'T')) ++ch; |
| 509 | |
| 510 | if (isdigit__isctype ( ( * ch ) , 0x00000400L )(*ch)) { |
| 511 | hour = read_segment_2digits(ch, &ch); |
| 512 | if(*ch == timeSep) { |
| 513 | ++ch; |
| 514 | if((timeSep == ',') || (timeSep == '.')) { |
| 515 | //We can't do fractional minutes when '.' is the segment separator. |
| 516 | //Only allow whole minutes and whole seconds. |
| 517 | minute = read_segment_2digits(ch, &ch); |
| 518 | if(*ch == timeSep) { |
| 519 | ++ch; |
| 520 | second = read_segment_2digits(ch, &ch); |
| 521 | } |
| 522 | } else { |
| 523 | //Allow a fractional minute. |
| 524 | //If we don't get a fraction, look for a seconds segment. |
| 525 | //Otherwise, the fraction of a minute is the seconds. |
| 526 | minute = read_double(ch, &ch); |
| 527 | second = modf(minute, &minute); |
| 528 | if(second > DBL_EPSILON2.2204460492503131e-16) |
| 529 | second *= 60.0; //Convert fraction (e.g. .5) into seconds (e.g. 30). |
| 530 | else if(*ch == timeSep) { |
| 531 | ++ch; |
| 532 | second = read_double(ch, &ch); |
| 533 | } |
| 534 | } |
| 535 | } |
| 536 | |
| 537 | switch(*ch) { |
| 538 | case 'Z': |
| 539 | timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"]; |
| 540 | break; |
| 541 | |
| 542 | case '+': |
| 543 | case '-':; |
| 544 | BOOL negative = (*ch == '-'); |
| 545 | if(isdigit__isctype ( ( * ++ ch ) , 0x00000400L )(*++ch)) { |
| 546 | //Read hour offset. |
| 547 | segment = *ch - '0'; |
| 548 | if(isdigit__isctype ( ( * ++ ch ) , 0x00000400L )(*++ch)) { |
| 549 | segment *= 10U; |
| 550 | segment += *(ch++) - '0'; |
| 551 | } |
| 552 | tz_hour = (signed)segment; |
| 553 | if(negative) tz_hour = -tz_hour; |
| 554 | |
| 555 | //Optional separator. |
| 556 | if(*ch == timeSep) ++ch; |
| 557 | |
| 558 | if(isdigit__isctype ( ( * ch ) , 0x00000400L )(*ch)) { |
| 559 | //Read minute offset. |
| 560 | segment = *ch - '0'; |
| 561 | if(isdigit__isctype ( ( * ++ ch ) , 0x00000400L )(*++ch)) { |
| 562 | segment *= 10U; |
| 563 | segment += *ch - '0'; |
| 564 | } |
| 565 | tz_minute = segment; |
| 566 | if(negative) tz_minute = -tz_minute; |
| 567 | } |
| 568 | |
| 569 | timeZone = [NSTimeZone timeZoneForSecondsFromGMT:(tz_hour * 3600) + (tz_minute * 60)]; |
| 570 | } |
| 571 | } |
| 572 | } |
| 573 | } |
| 574 | |
| 575 | if (isValidDate) { |
| 576 | switch (dateSpecification) { |
| 577 | case monthAndDate: |
| 578 | date = [NSCalendarDate dateWithYear:year |
| 579 | month:month_or_week |
| 580 | day:day |
| 581 | hour:hour |
| 582 | minute:minute |
| 583 | second:second |
| 584 | timeZone:timeZone]; |
| 585 | break; |
| 586 | |
| 587 | case week:; |
| 588 | //Adapted from <http://personal.ecu.edu/mccartyr/ISOwdALG.txt>. |
| 589 | //This works by converting the week date into an ordinal date, then letting the next case handle it. |
| 590 | unsigned prevYear = year - 1U; |
| 591 | unsigned YY = prevYear % 100U; |
| 592 | unsigned C = prevYear - YY; |
| 593 | unsigned G = YY + YY / 4U; |
| 594 | unsigned isLeapYear = (((C / 100U) % 4U) * 5U); |
| 595 | unsigned Jan1Weekday = (isLeapYear + G) % 7U; |
| 596 | enum { monday, tuesday, wednesday, thursday/*, friday, saturday, sunday*/ }; |
| 597 | day = ((8U - Jan1Weekday) + (7U * (Jan1Weekday > thursday))) + (day - 1U) + (7U * (month_or_week - 2)); |
| 598 | |
| 599 | case dateOnly: //An "ordinal date". |
| 600 | date = [NSCalendarDate dateWithYear:year |
| 601 | month:1 |
| 602 | day:1 |
| 603 | hour:hour |
| 604 | minute:minute |
| 605 | second:second |
| 606 | timeZone:timeZone]; |
| 607 | date = [date dateByAddingYears:0 |
| 608 | months:0 |
| 609 | days:(day - 1) |
| 610 | hours:0 |
| 611 | minutes:0 |
| 612 | seconds:0]; |
| 613 | break; |
| 614 | } |
| 615 | } |
| 616 | } //if (!(strict && isdigit(ch[0]))) |
| 617 | |
| 618 | if(outRange) { |
| 619 | if(isValidDate) |
| 620 | range.length = ch - start_of_date; |
| 621 | else |
| 622 | range.location = NSNotFound; |
| 623 | |
| 624 | *outRange = range; |
| 625 | } |
| 626 | |
| 627 | return date; |
| 628 | } |
| 629 | |
| 630 | + (NSCalendarDate *)calendarDateWithString:(NSString *)str { |
| 631 | return [self calendarDateWithString:str strictly:NO( BOOL ) 0 getRange:NULL( ( void * ) 0 )]; |
| 632 | } |
| 633 | + (NSCalendarDate *)calendarDateWithString:(NSString *)str strictly:(BOOL)strict { |
| 634 | return [self calendarDateWithString:str strictly:strict getRange:NULL( ( void * ) 0 )]; |
| 635 | } |
| 636 | + (NSCalendarDate *)calendarDateWithString:(NSString *)str strictly:(BOOL)strict getRange:(out NSRange *)outRange { |
| 637 | return [self calendarDateWithString:str strictly:strict timeSeparator:ISO8601ParserDefaultTimeSeparatorCharacter getRange:NULL( ( void * ) 0 )]; |
| 638 | } |
| 639 | |
| 640 | + (NSCalendarDate *)calendarDateWithString:(NSString *)str timeSeparator:(unichar)timeSep getRange:(out NSRange *)outRange { |
| 641 | return [self calendarDateWithString:str strictly:NO( BOOL ) 0 timeSeparator:timeSep getRange:outRange]; |
| 642 | } |
| 643 | + (NSCalendarDate *)calendarDateWithString:(NSString *)str timeSeparator:(unichar)timeSep { |
| 644 | return [self calendarDateWithString:str strictly:NO( BOOL ) 0 timeSeparator:timeSep getRange:NULL( ( void * ) 0 )]; |
| 645 | } |
| 646 | + (NSCalendarDate *)calendarDateWithString:(NSString *)str getRange:(out NSRange *)outRange { |
| 647 | return [self calendarDateWithString:str strictly:NO( BOOL ) 0 timeSeparator:ISO8601ParserDefaultTimeSeparatorCharacter getRange:outRange]; |
| 648 | } |
| 649 | |
| 650 | @end |