Tuukka Norri avatar Tuukka Norri committed 685cbf2

Added the ISO 8601 parser

Comments (0)

Files changed (15)

Framework/Contrib/ISO-8601-parser/LICENSE.txt

+Copyright © 2006 Peter Hosey
+ All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+Neither the name of Peter Hosey nor the names of his contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Framework/Contrib/ISO-8601-parser/Makefile

+CFLAGS+=-std=c99 -g -Wall
+LDFLAGS+=-framework Foundation
+
+all: testparser unparse-weekdate unparse-ordinaldate unparse-date
+test: all parser-test unparser-test
+parser-test: testparser testparser.sh
+	./testparser.sh
+unparser-test: testunparser.sh unparse-weekdate unparse-ordinaldate unparse-date
+	./testunparser.sh > testunparser.out
+	diff -qs testunparser.out testunparser-expected.out
+.PHONY: all test parser-test unparser-test
+
+testparser: testparser.o NSCalendarDate+ISO8601Parsing.o
+
+testparser.sh: testparser.sh.in
+	python testparser.sh.py
+
+unparse-weekdate: unparse-weekdate.o NSCalendarDate+ISO8601Parsing.o NSCalendarDate+ISO8601Unparsing.o
+unparse-ordinaldate: unparse-ordinaldate.o NSCalendarDate+ISO8601Parsing.o NSCalendarDate+ISO8601Unparsing.o
+unparse-date: unparse-date.o NSCalendarDate+ISO8601Parsing.o NSCalendarDate+ISO8601Unparsing.o

Framework/Contrib/ISO-8601-parser/NSCalendarDate+ISO8601Parsing.h

+/*NSCalendarDate+ISO8601Parsing.h
+ *
+ *Created by Peter Hosey on 2006-02-20.
+ *Copyright 2006 Peter Hosey. All rights reserved.
+ */
+
+#import <Foundation/Foundation.h>
+
+/*This addition parses ISO 8601 dates. A good introduction: <http://www.cl.cam.ac.uk/~mgk25/iso-time.html>
+ *
+ *Parsing can be done strictly, or not. When you parse loosely, leading whitespace is ignored, as is anything after the date.
+ *The loose parser will return an NSCalendarDate for this string: @" \t\r\n\f\t  2006-03-02!!!"
+ *Leading non-whitespace will not be ignored; the string will be rejected, and nil returned. See the README that came with this addition.
+ *
+ *The strict parser will only accept a string if the date is the entire string. The above string would be rejected immediately, solely on these grounds.
+ *Also, the loose parser provides some extensions that the strict parser doesn't.
+ *For example, the standard says for "-DDD" (an ordinal date in the implied year) that the logical representation (meaning, hierarchically) would be "--DDD", but because that extra hyphen is "superfluous", it was omitted.
+ *The loose parser will accept the extra hyphen; the strict parser will not.
+ *A full list of these extensions is in the README file.
+ */
+
+//The default separator for time values. Currently, this is ':'.
+extern unichar ISO8601ParserDefaultTimeSeparatorCharacter;
+
+@interface NSCalendarDate(ISO8601Parsing)
+
+//This method is the one that does all the work. All the others are convenience methods.
++ (NSCalendarDate *)calendarDateWithString:(NSString *)str strictly:(BOOL)strict getRange:(out NSRange *)outRange;
++ (NSCalendarDate *)calendarDateWithString:(NSString *)str strictly:(BOOL)strict;
+
+//Strictly: NO.
++ (NSCalendarDate *)calendarDateWithString:(NSString *)str timeSeparator:(unichar)timeSep getRange:(out NSRange *)outRange;
++ (NSCalendarDate *)calendarDateWithString:(NSString *)str timeSeparator:(unichar)timeSep;
++ (NSCalendarDate *)calendarDateWithString:(NSString *)str getRange:(out NSRange *)outRange;
++ (NSCalendarDate *)calendarDateWithString:(NSString *)str;
+
+@end

Framework/Contrib/ISO-8601-parser/NSCalendarDate+ISO8601Parsing.m

+/*NSCalendarDate+ISO8601Parsing.m
+ *
+ *Created by Peter Hosey on 2006-02-20.
+ *Copyright 2006 Peter Hosey. All rights reserved.
+ */
+
+#include <ctype.h>
+#include <string.h>
+
+#import "NSCalendarDate+ISO8601Parsing.h"
+
+#ifndef DEFAULT_TIME_SEPARATOR
+#	define DEFAULT_TIME_SEPARATOR ':'
+#endif
+unichar ISO8601ParserDefaultTimeSeparatorCharacter = DEFAULT_TIME_SEPARATOR;
+
+static unsigned read_segment(const unsigned char *str, const unsigned char **next, unsigned *out_num_digits) {
+	unsigned num_digits = 0U;
+	unsigned value = 0U;
+
+	while(isdigit(*str)) {
+		value *= 10U;
+		value += *str - '0';
+		++num_digits;
+		++str;
+	}
+
+	if(next) *next = str;
+	if(out_num_digits) *out_num_digits = num_digits;
+
+	return value;
+}
+static unsigned read_segment_4digits(const unsigned char *str, const unsigned char **next, unsigned *out_num_digits) {
+	unsigned num_digits = 0U;
+	unsigned value = 0U;
+
+	if(isdigit(*str)) {
+		value += *(str++) - '0';
+		++num_digits;
+	}
+
+	if(isdigit(*str)) {
+		value *= 10U;
+		value += *(str++) - '0';
+		++num_digits;
+	}
+
+	if(isdigit(*str)) {
+		value *= 10U;
+		value += *(str++) - '0';
+		++num_digits;
+	}
+
+	if(isdigit(*str)) {
+		value *= 10U;
+		value += *(str++) - '0';
+		++num_digits;
+	}
+
+	if(next) *next = str;
+	if(out_num_digits) *out_num_digits = num_digits;
+
+	return value;
+}
+static unsigned read_segment_2digits(const unsigned char *str, const unsigned char **next) {
+	unsigned value = 0U;
+
+	if(isdigit(*str))
+		value += *str - '0';
+
+	if(isdigit(*++str)) {
+		value *= 10U;
+		value += *(str++) - '0';
+	}
+
+	if(next) *next = str;
+
+	return value;
+}
+
+//strtod doesn't support ',' as a separator. This does.
+static double read_double(const unsigned char *str, const unsigned char **next) {
+	double value = 0.0;
+
+	if(str) {
+		unsigned int_value = 0;
+
+		while(isdigit(*str)) {
+			int_value *= 10U;
+			int_value += (*(str++) - '0');
+		}
+		value = int_value;
+
+		if(((*str == ',') || (*str == '.'))) {
+			++str;
+
+			register double multiplier, multiplier_multiplier;
+			multiplier = multiplier_multiplier = 0.1;
+
+			while(isdigit(*str)) {
+				value += (*(str++) - '0') * multiplier;
+				multiplier *= multiplier_multiplier;
+			}
+		}
+	}
+
+	if(next) *next = str;
+
+	return value;
+}
+
+static BOOL is_leap_year(unsigned year) {
+	return \
+	    ((year %   4U) == 0U)
+	&& (((year % 100U) != 0U)
+	||  ((year % 400U) == 0U));
+}
+
+@implementation NSCalendarDate(ISO8601Parsing)
+
+/*Valid ISO 8601 date formats:
+ *
+ *YYYYMMDD
+ *YYYY-MM-DD
+ *YYYY-MM
+ *YYYY
+ *YY //century 
+ * //Implied century: YY is 00-99
+ *  YYMMDD
+ *  YY-MM-DD
+ * -YYMM
+ * -YY-MM
+ * -YY
+ * //Implied year
+ *  --MMDD
+ *  --MM-DD
+ *  --MM
+ * //Implied year and month
+ *   ---DD
+ * //Ordinal dates: DDD is the number of the day in the year (1-366)
+ *YYYYDDD
+ *YYYY-DDD
+ *  YYDDD
+ *  YY-DDD
+ *   -DDD
+ * //Week-based dates: ww is the number of the week, and d is the number (1-7) of the day in the week
+ *yyyyWwwd
+ *yyyy-Www-d
+ *yyyyWww
+ *yyyy-Www
+ *yyWwwd
+ *yy-Www-d
+ *yyWww
+ *yy-Www
+ * //Year of the implied decade
+ *-yWwwd
+ *-y-Www-d
+ *-yWww
+ *-y-Www
+ * //Week and day of implied year
+ *  -Wwwd
+ *  -Www-d
+ * //Week only of implied year
+ *  -Www
+ * //Day only of implied week
+ *  -W-d
+ */
++ (NSCalendarDate *)calendarDateWithString:(NSString *)str strictly:(BOOL)strict timeSeparator:(unichar)timeSep getRange:(out NSRange *)outRange {
+	NSCalendarDate *now = [NSCalendarDate calendarDate];
+	unsigned
+		//Date
+		year,
+		month_or_week,
+		day,
+		//Time
+		hour = 0U;
+	NSTimeInterval
+		minute = 0.0,
+		second = 0.0;
+	//Time zone
+	signed tz_hour = 0;
+	signed tz_minute = 0;
+
+	enum {
+		monthAndDate,
+		week,
+		dateOnly
+	} dateSpecification = monthAndDate;
+
+	if(strict) timeSep = ISO8601ParserDefaultTimeSeparatorCharacter;
+	NSAssert(timeSep != '\0', @"Time separator must not be NUL.");
+
+	BOOL isValidDate = ([str length] > 0U);
+	NSTimeZone *timeZone = nil;
+	NSCalendarDate *date = nil;
+
+	const unsigned char *ch = (const unsigned char *)[str UTF8String];
+
+	NSRange range = { 0U, 0U };
+	const unsigned char *start_of_date;
+	if(strict && isspace(*ch)) {
+		range.location = NSNotFound;
+		isValidDate = NO;
+	} else {
+		//Skip leading whitespace.
+		unsigned i = 0U;
+		for(unsigned len = strlen((const char *)ch); i < len; ++i) {
+			if(!isspace(ch[i]))
+				break;
+		}
+
+		range.location = i;
+		ch += i;
+		start_of_date = ch;
+
+		unsigned segment;
+		unsigned num_leading_hyphens = 0U, num_digits = 0U;
+
+		if(*ch == 'T') {
+			//There is no date here, only a time. Set the date to now; then we'll parse the time.
+			isValidDate = isdigit(*++ch);
+
+			year = [now yearOfCommonEra];
+			month_or_week = [now monthOfYear];
+			day = [now dayOfMonth];
+		} else {
+			segment = 0U;
+
+			while(*ch == '-') {
+				++num_leading_hyphens;
+				++ch;
+			}
+
+			segment = read_segment(ch, &ch, &num_digits);
+			switch(num_digits) {
+				case 0:
+					if(*ch == 'W') {
+						if((ch[1] == '-') && isdigit(ch[2]) && ((num_leading_hyphens == 1U) || ((num_leading_hyphens == 2U) && !strict))) {
+							year = [now yearOfCommonEra];
+							month_or_week = 1U;
+							ch += 2;
+							goto parseDayAfterWeek;
+						} else if(num_leading_hyphens == 1U) {
+							year = [now yearOfCommonEra];
+							goto parseWeekAndDay;
+						} else
+							isValidDate = NO;
+					} else
+						isValidDate = NO;
+					break;
+
+				case 8: //YYYY MM DD
+					if(num_leading_hyphens > 0U)
+						isValidDate = NO;
+					else {
+						day = segment % 100U;
+						segment /= 100U;
+						month_or_week = segment % 100U;
+						year = segment / 100U;
+					}
+					break;
+
+				case 6: //YYMMDD (implicit century)
+					if(num_leading_hyphens > 0U)
+						isValidDate = NO;
+					else {
+						day = segment % 100U;
+						segment /= 100U;
+						month_or_week = segment % 100U;
+						year  = [now yearOfCommonEra];
+						year -= (year % 100U);
+						year += segment / 100U;
+					}
+					break;
+
+				case 4:
+					switch(num_leading_hyphens) {
+						case 0: //YYYY
+							year = segment;
+
+							if(*ch == '-') ++ch;
+
+							if(!isdigit(*ch)) {
+								if(*ch == 'W')
+									goto parseWeekAndDay;
+								else
+									month_or_week = day = 1U;
+							} else {
+								segment = read_segment(ch, &ch, &num_digits);
+								switch(num_digits) {
+									case 4: //MMDD
+										day = segment % 100U;
+										month_or_week = segment / 100U;
+										break;
+	
+									case 2: //MM
+										month_or_week = segment;
+
+										if(*ch == '-') ++ch;
+										if(!isdigit(*ch))
+											day = 1U;
+										else
+											day = read_segment(ch, &ch, NULL);
+										break;
+	
+									case 3: //DDD
+										day = segment % 1000U;
+										dateSpecification = dateOnly;
+										if(strict && (day > (365U + is_leap_year(year))))
+											isValidDate = NO;
+										break;
+	
+									default:
+										isValidDate = NO;
+								}
+							}
+							break;
+
+						case 1: //YYMM
+							month_or_week = segment % 100U;
+							year = segment / 100U;
+
+							if(*ch == '-') ++ch;
+							if(!isdigit(*ch))
+								day = 1U;
+							else
+								day = read_segment(ch, &ch, NULL);
+
+							break;
+
+						case 2: //MMDD
+							day = segment % 100U;
+							month_or_week = segment / 100U;
+							year = [now yearOfCommonEra];
+
+							break;
+
+						default:
+							isValidDate = NO;
+					} //switch(num_leading_hyphens) (4 digits)
+					break;
+
+				case 1:
+					if(strict) {
+						//Two digits only - never just one.
+						if(num_leading_hyphens == 1U) {
+							if(*ch == '-') ++ch;
+							if(*++ch == 'W') {
+								year  = [now yearOfCommonEra];
+								year -= (year % 10U);
+								year += segment;
+								goto parseWeekAndDay;
+							} else
+								isValidDate = NO;
+						} else
+							isValidDate = NO;
+						break;
+					}
+				case 2:
+					switch(num_leading_hyphens) {
+						case 0:
+							if(*ch == '-') {
+								//Implicit century
+								year  = [now yearOfCommonEra];
+								year -= (year % 100U);
+								year += segment;
+
+								if(*++ch == 'W')
+									goto parseWeekAndDay;
+								else if(!isdigit(*ch)) {
+									goto centuryOnly;
+								} else {
+									//Get month and/or date.
+									segment = read_segment_4digits(ch, &ch, &num_digits);
+									NSLog(@"(%@) parsing month; segment is %u and ch is %s", str, segment, ch);
+									switch(num_digits) {
+										case 4: //YY-MMDD
+											day = segment % 100U;
+											month_or_week = segment / 100U;
+											break;
+
+										case 1: //YY-M; YY-M-DD (extension)
+											if(strict) {
+												isValidDate = NO;
+												break;
+											}
+										case 2: //YY-MM; YY-MM-DD
+											month_or_week = segment;
+											if(*ch == '-') {
+												if(isdigit(*++ch))
+													day = read_segment_2digits(ch, &ch);
+												else
+													day = 1U;
+											} else
+												day = 1U;
+											break;
+
+										case 3: //Ordinal date.
+											day = segment;
+											dateSpecification = dateOnly;
+											break;
+									}
+								}
+							} else if(*ch == 'W') {
+								year  = [now yearOfCommonEra];
+								year -= (year % 100U);
+								year += segment;
+
+							parseWeekAndDay: //*ch should be 'W' here.
+								if(!isdigit(*++ch)) {
+									//Not really a week-based date; just a year followed by '-W'.
+									if(strict)
+										isValidDate = NO;
+									else
+										month_or_week = day = 1U;
+								} else {
+									month_or_week = read_segment_2digits(ch, &ch);
+									if(*ch == '-') ++ch;
+								parseDayAfterWeek:
+									day = isdigit(*ch) ? read_segment_2digits(ch, &ch) : 1U;
+									dateSpecification = week;
+								}
+							} else {
+								//Century only. Assume current year.
+							centuryOnly:
+								year = segment * 100U + [now yearOfCommonEra] % 100U;
+								month_or_week = day = 1U;
+							}
+							break;
+
+						case 1:; //-YY; -YY-MM (implicit century)
+							NSLog(@"(%@) found %u digits and one hyphen, so this is either -YY or -YY-MM; segment (year) is %u", str, num_digits, segment);
+							unsigned current_year = [now yearOfCommonEra];
+							unsigned century = (current_year % 100U);
+							year = segment + (current_year - century);
+							if(num_digits == 1U) //implied decade
+								year += century - (current_year % 10U);
+
+							if(*ch == '-') {
+								++ch;
+								month_or_week = read_segment_2digits(ch, &ch);
+								NSLog(@"(%@) month is %u", str, month_or_week);
+							}
+
+							day = 1U;
+							break;
+
+						case 2: //--MM; --MM-DD
+							year = [now yearOfCommonEra];
+							month_or_week = segment;
+							if(*ch == '-') {
+								++ch;
+								day = read_segment_2digits(ch, &ch);
+							}
+							break;
+
+						case 3: //---DD
+							year = [now yearOfCommonEra];
+							month_or_week = [now monthOfYear];
+							day = segment;
+							break;
+
+						default:
+							isValidDate = NO;
+					} //switch(num_leading_hyphens) (2 digits)
+					break;
+
+				case 7: //YYYY DDD (ordinal date)
+					if(num_leading_hyphens > 0U)
+						isValidDate = NO;
+					else {
+						day = segment % 1000U;
+						year = segment / 1000U;
+						dateSpecification = dateOnly;
+						if(strict && (day > (365U + is_leap_year(year))))
+							isValidDate = NO;
+					}
+					break;
+
+				case 3: //--DDD (ordinal date, implicit year)
+					//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.
+					if((num_leading_hyphens < 1U) || ((num_leading_hyphens > 2U) && !strict))
+						isValidDate = NO;
+					else {
+						day = segment;
+						year = [now yearOfCommonEra];
+						dateSpecification = dateOnly;
+						if(strict && (day > (365U + is_leap_year(year))))
+							isValidDate = NO;
+					}
+					break;
+
+				default:
+					isValidDate = NO;
+			}
+		}
+
+		if(isValidDate) {
+			if(isspace(*ch) || (*ch == 'T')) ++ch;
+
+			if(isdigit(*ch)) {
+				hour = read_segment_2digits(ch, &ch);
+				if(*ch == timeSep) {
+					++ch;
+					if((timeSep == ',') || (timeSep == '.')) {
+						//We can't do fractional minutes when '.' is the segment separator.
+						//Only allow whole minutes and whole seconds.
+						minute = read_segment_2digits(ch, &ch);
+						if(*ch == timeSep) {
+							++ch;
+							second = read_segment_2digits(ch, &ch);
+						}
+					} else {
+						//Allow a fractional minute.
+						//If we don't get a fraction, look for a seconds segment.
+						//Otherwise, the fraction of a minute is the seconds.
+						minute = read_double(ch, &ch);
+						second = modf(minute, &minute);
+						if(second > DBL_EPSILON)
+							second *= 60.0; //Convert fraction (e.g. .5) into seconds (e.g. 30).
+						else if(*ch == timeSep) {
+							++ch;
+							second = read_double(ch, &ch);
+						}
+					}
+				}
+
+				switch(*ch) {
+					case 'Z':
+						timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"];
+						break;
+
+					case '+':
+					case '-':;
+						BOOL negative = (*ch == '-');
+						if(isdigit(*++ch)) {
+							//Read hour offset.
+							segment = *ch - '0';
+							if(isdigit(*++ch)) {
+								segment *= 10U;
+								segment += *(ch++) - '0';
+							}
+							tz_hour = (signed)segment;
+							if(negative) tz_hour = -tz_hour;
+
+							//Optional separator.
+							if(*ch == timeSep) ++ch;
+
+							if(isdigit(*ch)) {
+								//Read minute offset.
+								segment = *ch - '0';
+								if(isdigit(*++ch)) {
+									segment *= 10U;
+									segment += *ch - '0';
+								}
+								tz_minute = segment;
+								if(negative) tz_minute = -tz_minute;
+							}
+
+							timeZone = [NSTimeZone timeZoneForSecondsFromGMT:(tz_hour * 3600) + (tz_minute * 60)];
+						}
+				}
+			}
+		}
+
+		if(isValidDate) {
+			switch(dateSpecification) {
+				case monthAndDate:
+					date = [NSCalendarDate dateWithYear:year
+												  month:month_or_week
+													day:day
+												   hour:hour
+												 minute:minute
+												 second:second
+											   timeZone:timeZone];
+					break;
+
+				case week:;
+					//Adapted from <http://personal.ecu.edu/mccartyr/ISOwdALG.txt>.
+					//This works by converting the week date into an ordinal date, then letting the next case handle it.
+					unsigned prevYear = year - 1U;
+					unsigned YY = prevYear % 100U;
+					unsigned C = prevYear - YY;
+					unsigned G = YY + YY / 4U;
+					unsigned isLeapYear = (((C / 100U) % 4U) * 5U);
+					unsigned Jan1Weekday = (isLeapYear + G) % 7U;
+					enum { monday, tuesday, wednesday, thursday/*, friday, saturday, sunday*/ };
+					day = ((8U - Jan1Weekday) + (7U * (Jan1Weekday > thursday))) + (day - 1U) + (7U * (month_or_week - 2));
+
+				case dateOnly: //An "ordinal date".
+					date = [NSCalendarDate dateWithYear:year
+												  month:1
+													day:1
+												   hour:hour
+												 minute:minute
+												 second:second
+											   timeZone:timeZone];
+					date = [date dateByAddingYears:0
+											months:0
+											  days:(day - 1)
+											 hours:0
+										   minutes:0
+										   seconds:0];
+					break;
+			}
+		}
+	} //if(!(strict && isdigit(ch[0])))
+
+	if(outRange) {
+		if(isValidDate)
+			range.length = ch - start_of_date;
+		else
+			range.location = NSNotFound;
+
+		*outRange = range;
+	}
+	return date;
+}
+
++ (NSCalendarDate *)calendarDateWithString:(NSString *)str {
+	return [self calendarDateWithString:str strictly:NO getRange:NULL];
+}
++ (NSCalendarDate *)calendarDateWithString:(NSString *)str strictly:(BOOL)strict {
+	return [self calendarDateWithString:str strictly:strict getRange:NULL];
+}
++ (NSCalendarDate *)calendarDateWithString:(NSString *)str strictly:(BOOL)strict getRange:(out NSRange *)outRange {
+	return [self calendarDateWithString:str strictly:strict timeSeparator:ISO8601ParserDefaultTimeSeparatorCharacter getRange:NULL];
+}
+
++ (NSCalendarDate *)calendarDateWithString:(NSString *)str timeSeparator:(unichar)timeSep getRange:(out NSRange *)outRange {
+	return [self calendarDateWithString:str strictly:NO timeSeparator:timeSep getRange:outRange];
+}
++ (NSCalendarDate *)calendarDateWithString:(NSString *)str timeSeparator:(unichar)timeSep {
+	return [self calendarDateWithString:str strictly:NO timeSeparator:timeSep getRange:NULL];
+}
++ (NSCalendarDate *)calendarDateWithString:(NSString *)str getRange:(out NSRange *)outRange {
+	return [self calendarDateWithString:str strictly:NO timeSeparator:ISO8601ParserDefaultTimeSeparatorCharacter getRange:outRange];
+}
+
+@end

Framework/Contrib/ISO-8601-parser/NSCalendarDate+ISO8601Unparsing.h

+/*NSCalendarDate+ISO8601Unparsing.h
+ *
+ *Created by Peter Hosey on 2006-05-29.
+ *Copyright 2006 Peter Hosey. All rights reserved.
+ */
+
+#import <Foundation/Foundation.h>
+
+/*This addition unparses dates to ISO 8601 strings. A good introduction to ISO 8601: <http://www.cl.cam.ac.uk/~mgk25/iso-time.html>
+ */
+
+//The default separator for time values. Currently, this is ':'.
+extern unichar ISO8601UnparserDefaultTimeSeparatorCharacter;
+
+@interface NSCalendarDate(ISO8601Unparsing)
+
+- (NSString *)ISO8601DateStringWithTime:(BOOL)includeTime timeSeparator:(unichar)timeSep;
+- (NSString *)ISO8601WeekDateStringWithTime:(BOOL)includeTime timeSeparator:(unichar)timeSep;
+- (NSString *)ISO8601OrdinalDateStringWithTime:(BOOL)includeTime timeSeparator:(unichar)timeSep;
+
+- (NSString *)ISO8601DateStringWithTime:(BOOL)includeTime;
+- (NSString *)ISO8601WeekDateStringWithTime:(BOOL)includeTime;
+- (NSString *)ISO8601OrdinalDateStringWithTime:(BOOL)includeTime;
+
+//includeTime: YES.
+- (NSString *)ISO8601DateStringWithTimeSeparator:(unichar)timeSep;
+- (NSString *)ISO8601WeekDateStringWithTimeSeparator:(unichar)timeSep;
+- (NSString *)ISO8601OrdinalDateStringWithTimeSeparator:(unichar)timeSep;
+
+//includeTime: YES.
+- (NSString *)ISO8601DateString;
+- (NSString *)ISO8601WeekDateString;
+- (NSString *)ISO8601OrdinalDateString;
+
+@end
+

Framework/Contrib/ISO-8601-parser/NSCalendarDate+ISO8601Unparsing.m

+/*NSCalendarDate+ISO8601Unparsing.m
+ *
+ *Created by Peter Hosey on 2006-05-29.
+ *Copyright 2006 Peter Hosey. All rights reserved.
+ */
+
+#import <Foundation/Foundation.h>
+
+#ifndef DEFAULT_TIME_SEPARATOR
+#	define DEFAULT_TIME_SEPARATOR ':'
+#endif
+unichar ISO8601UnparserDefaultTimeSeparatorCharacter = DEFAULT_TIME_SEPARATOR;
+
+static BOOL is_leap_year(unsigned year) {
+	return \
+	    ((year %   4U) == 0U)
+	&& (((year % 100U) != 0U)
+	||  ((year % 400U) == 0U));
+}
+
+@interface NSString(ISO8601Unparsing)
+
+//Replace all occurrences of ':' with timeSep.
+- (NSString *)prepareDateFormatWithTimeSeparator:(unichar)timeSep;
+
+@end
+
+@implementation NSCalendarDate(ISO8601Unparsing)
+
+#pragma mark Public methods
+
+- (NSString *)ISO8601DateStringWithTime:(BOOL)includeTime timeSeparator:(unichar)timeSep {
+	NSString *dateFormat = [(includeTime ? @"%Y-%m-%dT%H:%M:%S" : @"%Y-%m-%d") prepareDateFormatWithTimeSeparator:timeSep];
+	NSDateFormatter *formatter = [[NSDateFormatter alloc] initWithDateFormat:dateFormat allowNaturalLanguage:NO];
+	NSString *str = [formatter stringForObjectValue:self];
+	[formatter release];
+	if(includeTime) {
+		int offset = [[self timeZone] secondsFromGMT];
+		offset /= 60;  //bring down to minutes
+		if(offset == 0)
+			str = [str stringByAppendingString:@"Z"];
+		if(offset < 0)
+			str = [str stringByAppendingFormat:@"-%02d:%02d", -offset / 60, offset % 60];
+		else
+			str = [str stringByAppendingFormat:@"+%02d:%02d", offset / 60, offset % 60];
+	}
+	return str;
+}
+/*Adapted from:
+ *	Algorithm for Converting Gregorian Dates to ISO 8601 Week Date
+ *	Rick McCarty, 1999
+ *	http://personal.ecu.edu/mccartyr/ISOwdALG.txt
+ */
+- (NSString *)ISO8601WeekDateStringWithTime:(BOOL)includeTime timeSeparator:(unichar)timeSep {
+	enum {
+		monday, tuesday, wednesday, thursday, friday, saturday, sunday
+	};
+	enum {
+		january = 1U, february, march,
+		april, may, june,
+		july, august, september,
+		october, november, december
+	};
+
+	unsigned year = [self yearOfCommonEra];
+	unsigned week = 0U;
+	unsigned dayOfWeek = ([self dayOfWeek] + 6U) % 7U;
+	unsigned dayOfYear = [self dayOfYear];
+
+	unsigned prevYear = year - 1U;
+
+	BOOL yearIsLeapYear = is_leap_year(year);
+	BOOL prevYearIsLeapYear = is_leap_year(prevYear);
+
+	unsigned YY = prevYear % 100U;
+	unsigned C = prevYear - YY;
+	unsigned G = YY + YY / 4U;
+	unsigned Jan1Weekday = (((((C / 100U) % 4U) * 5U) + G) % 7U);
+
+	unsigned weekday = ((dayOfYear + Jan1Weekday) - 1U) % 7U;
+
+	if((dayOfYear <= (7U - Jan1Weekday)) && (Jan1Weekday > thursday)) {
+		week = 52U + ((Jan1Weekday == friday) || ((Jan1Weekday == saturday) && prevYearIsLeapYear));
+		--year;
+	} else {
+		unsigned lengthOfYear = 365U + yearIsLeapYear;
+		if((lengthOfYear - dayOfYear) < (thursday - weekday)) {
+			++year;
+			week = 1U;
+		} else {
+			unsigned J = dayOfYear + (sunday - weekday) + Jan1Weekday;
+			week = J / 7U - (Jan1Weekday > thursday);
+		}
+	}
+
+	NSString *timeString;
+	if(includeTime) {
+		NSDateFormatter *formatter = [[NSDateFormatter alloc] initWithDateFormat:[@"T%H:%M:%S%z" prepareDateFormatWithTimeSeparator:timeSep] allowNaturalLanguage:NO];
+		timeString = [formatter stringForObjectValue:self];
+		[formatter release];
+	} else
+		timeString = @"";
+
+	return [NSString stringWithFormat:@"%u-W%02u-%02u%@", year, week, dayOfWeek + 1U, timeString];
+}
+- (NSString *)ISO8601OrdinalDateStringWithTime:(BOOL)includeTime timeSeparator:(unichar)timeSep {
+	NSString *timeString;
+	if(includeTime) {
+		NSDateFormatter *formatter = [[NSDateFormatter alloc] initWithDateFormat:[@"T%H:%M:%S%z" prepareDateFormatWithTimeSeparator:timeSep] allowNaturalLanguage:NO];
+		timeString = [formatter stringForObjectValue:self];
+		[formatter release];
+	} else
+		timeString = @"";
+
+	return [NSString stringWithFormat:@"%u-%03u%@", [self yearOfCommonEra], [self dayOfYear], timeString];
+}
+
+#pragma mark -
+
+- (NSString *)ISO8601DateStringWithTime:(BOOL)includeTime {
+	return [self ISO8601DateStringWithTime:includeTime timeSeparator:ISO8601UnparserDefaultTimeSeparatorCharacter];
+}
+- (NSString *)ISO8601WeekDateStringWithTime:(BOOL)includeTime {
+	return [self ISO8601WeekDateStringWithTime:includeTime timeSeparator:ISO8601UnparserDefaultTimeSeparatorCharacter];
+}
+- (NSString *)ISO8601OrdinalDateStringWithTime:(BOOL)includeTime {
+	return [self ISO8601OrdinalDateStringWithTime:includeTime timeSeparator:ISO8601UnparserDefaultTimeSeparatorCharacter];
+}
+
+#pragma mark -
+
+- (NSString *)ISO8601DateStringWithTimeSeparator:(unichar)timeSep {
+	return [self ISO8601DateStringWithTime:YES timeSeparator:timeSep];
+}
+- (NSString *)ISO8601WeekDateStringWithTimeSeparator:(unichar)timeSep {
+	return [self ISO8601WeekDateStringWithTime:YES timeSeparator:timeSep];
+}
+- (NSString *)ISO8601OrdinalDateStringWithTimeSeparator:(unichar)timeSep {
+	return [self ISO8601OrdinalDateStringWithTime:YES timeSeparator:timeSep];
+}
+
+#pragma mark -
+
+- (NSString *)ISO8601DateString {
+	return [self ISO8601DateStringWithTime:YES timeSeparator:ISO8601UnparserDefaultTimeSeparatorCharacter];
+}
+- (NSString *)ISO8601WeekDateString {
+	return [self ISO8601WeekDateStringWithTime:YES timeSeparator:ISO8601UnparserDefaultTimeSeparatorCharacter];
+}
+- (NSString *)ISO8601OrdinalDateString {
+	return [self ISO8601OrdinalDateStringWithTime:YES timeSeparator:ISO8601UnparserDefaultTimeSeparatorCharacter];
+}
+
+@end
+
+@implementation NSString(ISO8601Unparsing)
+
+//Replace all occurrences of ':' with timeSep.
+- (NSString *)prepareDateFormatWithTimeSeparator:(unichar)timeSep {
+	NSString *dateFormat = self;
+	if(timeSep != ':') {
+		NSMutableString *dateFormatMutable = [[dateFormat mutableCopy] autorelease];
+		[dateFormatMutable replaceOccurrencesOfString:@":"
+		                               	   withString:[NSString stringWithCharacters:&timeSep length:1U]
+	                                      	  options:NSBackwardsSearch | NSLiteralSearch
+	                                        	range:(NSRange){ 0U, [dateFormat length] }];
+		dateFormat = dateFormatMutable;
+	}
+	return dateFormat;
+}
+
+@end

Framework/Contrib/ISO-8601-parser/README.txt

+How to use in your program
+==========================
+
+Add the source files to your project.
+
+Parsing
+-------
+
+Call +[NSCalendarDate calendarDateWithString:myString]. The method will return either an NSCalendarDate or nil.
+
+There are a total of four parser methods. The one that contains the actual parser is +[NSCalendarDate calendarDateWithString:strictly:getRange:]. The other three are based on this one.
+
+The "strict" option, when set to YES, enforces sanity checks on the string; without it, or when set to NO, the parser will afford you quite a bit of leeway.
+
+The "outRange" parameter, when not set to NULL, is a pointer to NSRange storage. You will receive the range of the parsed substring in that storage.
+
+Unparsing
+---------
+
+When you want to unparse a calendar date to ISO 8601 date format, call [myDate ISO8601DateString].
+
+When you want to unparse a calendar date to ISO 8601 week-date format, call [myDate ISO8601WeekDateString].
+
+When you want to unparse a calendar date to ISO 8601 ordinal-date format, call [myDate ISO8601OrdinalDateString].
+
+All three methods give you the time as well as the date. All three versions also come in versions that let you not get the time; for example, ISO8601DateStringWithTime:. Pass NO to not get the time.
+
+How to test that this code works
+================================
+
+'make test' will perform all tests. If you want to perform only *some* tests:
+
+Parsing
+-------
+
+Type 'make parser-test'. make will build the test program (testparser), then invoke testparser.sh.py to generate testparser.sh. Then make will invoke testparser.sh, which will invoke the test program with various dates.
+
+If you don't want to use my tests, 'make testparser' will create the test program without running it. You can then invoke testparser yourself with any date you want to. If it doesn't give you the result you expected, contact me, making sure to provide me with both the input and the output.
+
+Unparsing
+---------
+
+Type 'make unparser-test'. make will build the test programs, then invoke testunparser.sh. This shell script invokes each test program for -01-01 of every year from 1991 to 2010, writing the output to a file, and then runs diff -qs between that file (testunparser.out) and a file (testunparser-expected.out) containing known correct output. diff should report that the files are identical.
+
+Three test programs are included: unparse-date, unparse-weekdate, and unparse-ordinal date. If you don't want to use my tests, you can make these test programs separately. Each takes a date specified by ISO 8601 (parsed with my own ISO 8601 parser), and outputs a string that should represent the same date.
+
+Notes
+=====
+
+Version history
+---------------
+
+This version is 0.3. Changes from 0.2:
+* Colin Barrett noticed that I used %m instead of %M when creating the time strings. Oops.
+* Colin also noticed that I had the ?: in -ISO8601DateStringWithTime: the wrong way around. Oops again.
+
+Changes in 0.2 from 0.1:
+* The unparser is new. The  has been munged to allow both components together, 
+* The parser has not changed.
+
+Parsing
+-------
+
+Whitespace before a date, and anything after a date, is ignored. Thus, "    T23 and all's well" is a valid date for the purpose of this method. (Yes, T23 is a valid ISO 8601 date. It means 23:00:00, or 11 PM.)
+
+All of the frills of ISO 8601 are supported, except for extended dates (years longer than 4 digits). Specifically, you can use week-based dates (2006-W2 for the second week of 2006), ordinal dates (2006-365 for December 31), decimal minutes (11:30.5 == 11:30:30), and decimal seconds (11:30:10.5). All methods of specifying a time zone are supported.
+
+ISO 8601 leaves quite a bit up to the parties exchanging dates. I hope I've chosen reasonable defaults. For example (note that I'm writing this on 2006-02-24):
+
+• If the month or month and date are missing, 1 is assumed. "2006" == "2006-01-01".
+• If the year or year and month are missing, the current ones are assumed. "--02-01" == "2006-02-01". "---28" == "2006-02-28".
+• In the case of week-based dates, with  the day missing, this implementation returns the first day of that week: 2006-W1 is 2006-01-01, 2006-W2 is 2006-01-08, etc.
+• For any date without a time, midnight on that date is used.
+• ISO 8601 permits the choice of either T0 or T24 for midnight. This implementation uses T0. T24 will get you T0 on the following day.
+• If no time-zone is specified, local time (as returned by [NSTimeZone localTimeZone]) is used.
+
+When a date is parsed that has a year but no century, this implementation adds the current century.
+
+The implementation is tolerant of out-of-range numbers. For example, "2005-13-40T24:62:89" == 1:02 AM on 2006-02-10. Notice that the month (13 > 12), date (40 > 31), hour (24 > 23), minute (62 > 59), and second (89 > 59) are all out-of-range.
+
+As mentioned above, there is a "strict" mode that enforces sanity checks. In particular, the date must be the entire contents of the string, and numbers are range-checked. If you have any suggestions on how to make this mode more strict, contact me.
+
+Unparsing
+---------
+
+I use Rick McCarty's algorithm for converting calendar dates to week dates (http://personal.ecu.edu/mccartyr/ISOwdAlg.txt), slightly tweaked.
+
+Bugs
+====
+
+Parsing
+-------
+
+* This method won't extract a date from just anywhere in a string, only immediately after the start of the string (or any leading whitespace). There are two solutions: either require that -calendarDateValue be invoked on a string that is only an ISO 8601 date, with nothing before or after (bad for parsing purposes), or find an ISO 8601 date as a substring. I won't do the first one, and barring a patch, I probably won't do the second one either.
+
+* Date ranges (also specified by ISO 8601) are not supported; this method will only return one date. To handle ranges would require at least one more method.
+
+* There is no method to analyze a date string and tell you what was found in it (year, month, week, day, ordinal day, etc.). Feel free to submit a patch.
+
+Copyright
+=========
+
+This code is copyright 2006 Peter Hosey. It is under the BSD license; see LICENSE.txt for the full text of the license.

Framework/Contrib/ISO-8601-parser/testparser.m

+#import <Foundation/Foundation.h>
+#import "NSCalendarDate+ISO8601Parsing.h"
+
+int main(int argc, const char **argv) {
+	NSAutoreleasePool *pool = [NSAutoreleasePool new];
+
+	BOOL parseStrictly = NO;
+	if((argc > 1) && (strcmp(argv[1], "--strict") == 0)) {
+		--argc;++argv;
+		parseStrictly = YES;
+	}
+
+	while(--argc) {
+		NSString *str = [NSString stringWithUTF8String:*++argv];
+		NSLog(@"Parsing strictly: %hhi", parseStrictly);
+		NSDate *date = [NSCalendarDate calendarDateWithString:str strictly:parseStrictly];
+		fputs([[NSString stringWithFormat:@"%@ %C %@\n", str, 0x2192, date] UTF8String], stdout);
+	}
+
+	[pool release];
+	return 0;
+}

Framework/Contrib/ISO-8601-parser/testparser.sh

+#!/bin/sh
+
+echo
+echo 'Tests with a date only:'
+
+echo '% ./testparser 2006-02-24 2006-12 2006 2006- 2006-02- 2006-0224 200602-24 20060224 2001-W1-1 2002-W1-1 2003-W1-1 2004-W1-1 2010-W1-1 2000-W1-1 2006-W1-1 2006-W1-2 2006-W02 2006-W2 2006-W02-01 2006-W02-1 2006-W2-01 2006-W2-1 2006-W2-2 2006-W2-8 2006-W3-1 2006-W22 2006-W22-11 06-02-24 06-12 06-02- 06-0224 0602-24 060224 06-W22 06-W2 06-W22-11 06-W2-1 -6-02-24 -6-12 -6 -6- -6-02- -6-0224 -602-24 -6-W22 -6-W2 -6-W22-11 -6-W2-1 --0224 --02-24 --2-24 --02-2 --02 ---24 -W2 -W2-11 -W-3 --W-3 2006-001 2006-002 2006-055 2006-365 2004-001 2004-366 -055 --055 2006T'
+./testparser 2006-02-24 2006-12 2006 2006- 2006-02- 2006-0224 200602-24 20060224 2001-W1-1 2002-W1-1 2003-W1-1 2004-W1-1 2010-W1-1 2000-W1-1 2006-W1-1 2006-W1-2 2006-W02 2006-W2 2006-W02-01 2006-W02-1 2006-W2-01 2006-W2-1 2006-W2-2 2006-W2-8 2006-W3-1 2006-W22 2006-W22-11 06-02-24 06-12 06-02- 06-0224 0602-24 060224 06-W22 06-W2 06-W22-11 06-W2-1 -6-02-24 -6-12 -6 -6- -6-02- -6-0224 -602-24 -6-W22 -6-W2 -6-W22-11 -6-W2-1 --0224 --02-24 --2-24 --02-2 --02 ---24 -W2 -W2-11 -W-3 --W-3 2006-001 2006-002 2006-055 2006-365 2004-001 2004-366 -055 --055 2006T
+
+echo
+echo 'Current year in x century:'
+
+echo '% ./testparser 20 1'
+./testparser 20 1
+
+echo
+echo 'x year in current century:'
+
+echo '% ./testparser -06'
+./testparser -06
+
+echo
+echo 'x year and month in current century:'
+
+echo '% ./testparser -06-02'
+./testparser -06-02
+
+echo
+echo 'x year, month, and date in current century:'
+
+echo '% ./testparser 06-02-24'
+./testparser 06-02-24
+
+echo
+echo 'x month and date in current year:'
+
+echo '% ./testparser --02-24'
+./testparser --02-24
+
+echo
+echo 'x date in current year and month:'
+
+echo '% ./testparser ---24'
+./testparser ---24
+
+echo
+echo 'Tests with a time only:'
+
+echo '% ./testparser T22:63:24-11:21 T22:63:24+50:70 T22:1:2 T22:1Z T22: T22 T2 T2:2:2'
+./testparser T22:63:24-11:21 T22:63:24+50:70 T22:1:2 T22:1Z T22: T22 T2 T2:2:2
+
+echo
+echo 'Tests with both a date and a time:'
+
+echo '% ./testparser 2006-02-24T02:43:24 2006-02-24T22:43:24 2006-02-24T22:63:24 2006-12T12:34 2006T22'
+./testparser 2006-02-24T02:43:24 2006-02-24T22:43:24 2006-02-24T22:63:24 2006-12T12:34 2006T22
+
+echo
+echo 'Tests with a date, a time, and a time zone:'
+
+echo '% ./testparser 2006-02-24T22:63:24-01:00 2006-02-24T22:63:24Z      2006-02-24T22:63:24-1 2006-02-24T22:63:24-01 2006-02-24T22:63:24-01:32 2006-02-24T22:63:24-01:0  2006-02-24T22:63:24-01:00 2006-02-24T22:63:24-01:01 2006-02-24T22:63:24-01:11 2006-02-24T22:63:24-11:21'
+./testparser 2006-02-24T22:63:24-01:00 2006-02-24T22:63:24Z      2006-02-24T22:63:24-1 2006-02-24T22:63:24-01 2006-02-24T22:63:24-01:32 2006-02-24T22:63:24-01:0  2006-02-24T22:63:24-01:00 2006-02-24T22:63:24-01:01 2006-02-24T22:63:24-01:11 2006-02-24T22:63:24-11:21
+
+echo
+echo 'Invalid dates:'
+
+echo '% ./testparser '' T 2006-W 2006-366 2006-400 2004-367 -2006-02-24T02:43:24 -2006-02-24T22:43:24 -2006-02-24T22:63:24 -2006-12T12:34 -2006T22 -60224 --2006-02-24T02:43:24 --2006-02-24T22:43:24 --2006-02-24T22:63:24 --2006-12T12:34 --2006T22 ---2006-02-24T02:43:24 ---2006-02-24T22:43:24 ---2006-02-24T22:63:24 ---2006-12T12:34 ---2006T22'
+./testparser '' T 2006-W 2006-366 2006-400 2004-367 -2006-02-24T02:43:24 -2006-02-24T22:43:24 -2006-02-24T22:63:24 -2006-12T12:34 -2006T22 -60224 --2006-02-24T02:43:24 --2006-02-24T22:43:24 --2006-02-24T22:63:24 --2006-12T12:34 --2006T22 ---2006-02-24T02:43:24 ---2006-02-24T22:43:24 ---2006-02-24T22:63:24 ---2006-12T12:34 ---2006T22

Framework/Contrib/ISO-8601-parser/testparser.sh.in

+
+#Tests with a date only
+./testparser 2006-02-24 \
+2006-12 \
+2006 \
+2006- \
+2006-02- \
+2006-0224 \
+200602-24 \
+20060224 \
+2001-W1-1 \
+2002-W1-1 \
+2003-W1-1 \
+2004-W1-1 \
+2010-W1-1 \
+2000-W1-1 \
+2006-W1-1 \
+2006-W1-2 \
+2006-W02 \
+2006-W2 \
+2006-W02-01 \
+2006-W02-1 \
+2006-W2-01 \
+2006-W2-1 \
+2006-W2-2 \
+2006-W2-8 \
+2006-W3-1 \
+2006-W22 \
+2006-W22-11 \
+06-02-24 \
+06-12 \
+06-02- \
+06-0224 \
+0602-24 \
+060224 \
+06-W22 \
+06-W2 \
+06-W22-11 \
+06-W2-1 \
+-6-02-24 \
+-6-12 \
+-6 \
+-6- \
+-6-02- \
+-6-0224 \
+-602-24 \
+-6-W22 \
+-6-W2 \
+-6-W22-11 \
+-6-W2-1 \
+--0224 \
+--02-24 \
+--2-24 \
+--02-2 \
+--02 \
+---24 \
+-W2 \
+-W2-11 \
+-W-3 \
+--W-3 \
+2006-001 \
+2006-002 \
+2006-055 \
+2006-365 \
+2004-001 \
+2004-366 \
+-055 \
+--055 \
+2006T
+#Current year in x century
+./testparser 20 \
+1
+#x year in current century
+./testparser -06
+#x year and month in current century
+./testparser -06-02
+#x year, month, and date in current century
+./testparser 06-02-24
+#x month and date in current year
+./testparser --02-24
+#x date in current year and month
+./testparser ---24
+
+#Tests with a time only
+./testparser T22:63:24-11:21 \
+T22:63:24+50:70 \
+T22:1:2 \
+T22:1Z \
+T22: \
+T22 \
+T2 \
+T2:2:2
+#Tests with both a date and a time
+./testparser 2006-02-24T02:43:24 \
+2006-02-24T22:43:24 \
+2006-02-24T22:63:24 \
+2006-12T12:34 \
+2006T22
+#Tests with a date, a time, and a time zone
+./testparser 2006-02-24T22:63:24-01:00 \
+2006-02-24T22:63:24Z      \
+2006-02-24T22:63:24-1 \
+2006-02-24T22:63:24-01 \
+2006-02-24T22:63:24-01:32 \
+2006-02-24T22:63:24-01:0  \
+2006-02-24T22:63:24-01:00 \
+2006-02-24T22:63:24-01:01 \
+2006-02-24T22:63:24-01:11 \
+2006-02-24T22:63:24-11:21
+
+#Invalid dates
+./testparser '' \
+T \
+2006-W \
+2006-366 \
+2006-400 \
+2004-367 \
+-2006-02-24T02:43:24 \
+-2006-02-24T22:43:24 \
+-2006-02-24T22:63:24 \
+-2006-12T12:34 \
+-2006T22 \
+-60224 \
+--2006-02-24T02:43:24 \
+--2006-02-24T22:43:24 \
+--2006-02-24T22:63:24 \
+--2006-12T12:34 \
+--2006T22 \
+---2006-02-24T02:43:24 \
+---2006-02-24T22:43:24 \
+---2006-02-24T22:63:24 \
+---2006-12T12:34 \
+---2006T22

Framework/Contrib/ISO-8601-parser/testparser.sh.py

+#!/usr/bin/env python
+
+outfile = file('testparser.sh', 'w')
+
+outfile.write('#!/bin/sh\n')
+
+hash = '#'
+colon = ':'
+shell_prompt = '% '
+echo_format = "echo '%s'\n"
+newline = '\n'
+echo_by_itself = 'echo\n'
+
+import re
+bs_exp = re.compile('(\\\\*)\n')
+escape_newline_exp = re.compile('\\\\\n')
+empty = ''
+
+import fileinput
+holding = []
+for line in fileinput.input(['testparser.sh.in']):
+	if len(line) <= 1:
+		#Empty line.
+		continue
+
+	if(len(bs_exp.search(line).group(1)) % 2):
+		holding.append(line[:-1])
+		continue
+	elif holding:
+		holding.append(line)
+		line = '\n'.join(holding)
+		del holding[:]
+
+	line = escape_newline_exp.sub(empty, line)
+
+	is_comment = line.startswith(hash)
+	if is_comment:
+		line_for_display = line[:-1].strip(hash) + colon
+	else:
+		line_for_display = shell_prompt + line[:-1]
+
+	echo_line = echo_format % (line_for_display,)
+
+	lines = [newline, echo_line]
+	if is_comment:
+		lines.insert(1, echo_by_itself)
+	else:
+		lines.append(line)
+	outfile.writelines(lines)
+
+outfile.close()
+
+# Make it executable.
+import os
+os.chmod('testparser.sh', 0755)

Framework/Contrib/ISO-8601-parser/testunparser.sh

+#!/usr/bin/env zsh -f
+echo './unparse-date 199{1,2,3,4,5,6,7,8,9}-01-01 200{0,1,2,3,4,5,6,7,8,9}-01-01 2010-01-01'
+./unparse-date 199{1,2,3,4,5,6,7,8,9}-01-01 200{0,1,2,3,4,5,6,7,8,9}-01-01 2010-01-01
+echo
+echo './unparse-weekdate 199{1,2,3,4,5,6,7,8,9}-01-01 200{0,1,2,3,4,5,6,7,8,9}-01-01 2010-01-01'
+./unparse-weekdate 199{1,2,3,4,5,6,7,8,9}-01-01 200{0,1,2,3,4,5,6,7,8,9}-01-01 2010-01-01
+echo
+echo './unparse-ordinaldate 199{1,2,3,4,5,6,7,8,9}-01-01 200{0,1,2,3,4,5,6,7,8,9}-01-01 2010-01-01'
+./unparse-ordinaldate 199{1,2,3,4,5,6,7,8,9}-01-01 200{0,1,2,3,4,5,6,7,8,9}-01-01 2010-01-01

Framework/Contrib/ISO-8601-parser/unparse-date.m

+#import "NSCalendarDate+ISO8601Parsing.h"
+#import "NSCalendarDate+ISO8601Unparsing.h"
+
+int main(int argc, const char **argv) {
+	NSAutoreleasePool *pool = [NSAutoreleasePool new];
+
+	while(--argc) {
+		NSString *arg = [NSString stringWithUTF8String:*++argv];
+		printf("%s\n", [[NSString stringWithFormat:@"%@:\t%@", arg, [[NSCalendarDate calendarDateWithString:arg] ISO8601DateStringWithTime:YES]] UTF8String]);
+	}
+
+	[pool release];
+	return 0;
+}

Framework/Contrib/ISO-8601-parser/unparse-ordinaldate.m

+#import "NSCalendarDate+ISO8601Parsing.h"
+#import "NSCalendarDate+ISO8601Unparsing.h"
+
+int main(int argc, const char **argv) {
+	NSAutoreleasePool *pool = [NSAutoreleasePool new];
+
+	while(--argc) {
+		NSString *arg = [NSString stringWithUTF8String:*++argv];
+		printf("%s\n", [[NSString stringWithFormat:@"%@:\t%@", arg, [[NSCalendarDate calendarDateWithString:arg] ISO8601OrdinalDateStringWithTime:YES]] UTF8String]);
+	}
+
+	[pool release];
+	return 0;
+}

Framework/Contrib/ISO-8601-parser/unparse-weekdate.m

+#import "NSCalendarDate+ISO8601Parsing.h"
+#import "NSCalendarDate+ISO8601Unparsing.h"
+
+int main(int argc, const char **argv) {
+	NSAutoreleasePool *pool = [NSAutoreleasePool new];
+
+	while(--argc) {
+		NSString *arg = [NSString stringWithUTF8String:*++argv];
+		printf("%s\n", [[NSString stringWithFormat:@"%@:\t%@", arg, [[NSCalendarDate calendarDateWithString:arg] ISO8601WeekDateStringWithTime:YES]] UTF8String]);
+	}
+
+	[pool release];
+	return 0;
+}
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.