Anonymous avatar Anonymous committed a17edcd

Completed the burnination of the old Parser folder. This is a combined parser/unparser project now.

Comments (0)

Files changed (57)

+   December 2005
+ M Tu  W Th  F  S  S 
+          1  2  3  4 
+ 5  6  7  8  9 10 11 
+12 13 14 15 16 17 18 
+19 20 21 22 23 24 25 
+26 27 28 29 30 31
+
+    January 2006
+ M Tu  W Th  F  S  S 
+                   1 <- NOT W01
+ 2  3  4  5  6  7  8 <- W01
+ 9 10 11 12 13 14 15 
+16 17 18 19 20 21 22 
+23 24 25 26 27 28 29 
+30 31
+
+	  2005
+
+      January              
+ CW Mo Tu We Th Fr Sa Su   
+ 53                 1  2   
+ 01  3  4  5  6  7  8  9   
+ 02 10 11 12 13 14 15 16   
+ 03 17 18 19 20 21 22 23   
+ 04 24 25 26 27 28 29 30   
+ 05 31                                             
+   
+         February       
+ CW Mo Tu We Th Fr Sa Su
+ 05     1  2  3  4  5  6
+ 06  7  8  9 10 11 12 13
+ 07 14 15 16 17 18 19 20
+ 08 21 22 23 24 25 26 27
+ 09 28                  
+   
+          March
+ CW Mo Tu We Th Fr Sa Su
+ 09     1  2  3  4  5  6
+ 10  7  8  9 10 11 12 13
+ 11 14 15 16 17 18 19 20
+ 12 21 22 23 24 25 26 27
+ 13 28 29 30 31         
+   
+          April            
+ CW Mo Tu We Th Fr Sa Su   
+ 13              1  2  3   
+ 14  4  5  6  7  8  9 10   
+ 15 11 12 13 14 15 16 17   
+ 16 18 19 20 21 22 23 24   
+ 17 25 26 27 28 29 30      
+                                                   
+           May          
+ CW Mo Tu We Th Fr Sa Su
+ 17                    1
+ 18  2  3  4  5  6  7  8
+ 19  9 10 11 12 13 14 15
+ 20 16 17 18 19 20 21 22
+ 21 23 24 25 26 27 28 29
+ 22 30 31               
+   
+           June
+ CW Mo Tu We Th Fr Sa Su
+ 22        1  2  3  4  5
+ 23  6  7  8  9 10 11 12
+ 24 13 14 15 16 17 18 19
+ 25 20 21 22 23 24 25 26
+ 26 27 28 29 30         
+   
+           July            
+ CW Mo Tu We Th Fr Sa Su   
+ 26              1  2  3   
+ 27  4  5  6  7  8  9 10   
+ 28 11 12 13 14 15 16 17   
+ 29 18 19 20 21 22 23 24   
+ 30 25 26 27 28 29 30 31   
+                                                                           
+          August        
+ CW Mo Tu We Th Fr Sa Su
+ 31  1  2  3  4  5  6  7
+ 32  8  9 10 11 12 13 14
+ 33 15 16 17 18 19 20 21
+ 34 22 23 24 25 26 27 28
+ 35 29 30 31            
+   
+        September
+ CW Mo Tu We Th Fr Sa Su
+ 35           1  2  3  4
+ 36  5  6  7  8  9 10 11
+ 37 12 13 14 15 16 17 18
+ 38 19 20 21 22 23 24 25
+ 39 26 27 28 29 30      
+   
+         October           
+ CW Mo Tu We Th Fr Sa Su   
+ 39                 1  2   
+ 40  3  4  5  6  7  8  9   
+ 41 10 11 12 13 14 15 16   
+ 42 17 18 19 20 21 22 23   
+ 43 24 25 26 27 28 29 30   
+ 44 31                                                                     
+   
+         November       
+ CW Mo Tu We Th Fr Sa Su
+ 44     1  2  3  4  5  6
+ 45  7  8  9 10 11 12 13
+ 46 14 15 16 17 18 19 20
+ 47 21 22 23 24 25 26 27
+ 48 28 29 30            
+   
+         December
+ CW Mo Tu We Th Fr Sa Su
+ 48           1  2  3  4
+ 49  5  6  7  8  9 10 11
+ 50 12 13 14 15 16 17 18
+ 51 19 20 21 22 23 24 25
+ 52 26 27 28 29 30 31   

Empty file added.

+   December 2009
+ M Tu  W Th  F  S  S 
+    1  2  3  4  5  6 
+ 7  8  9 10 11 12 13 
+14 15 16 17 18 19 20 
+21 22 23 24 25 26 27 
+28 29 30 31      
+
+    January 2010
+ M Tu  W Th  F  S  S 
+             1  2  3 
+ 4  5  6  7  8  9 10 
+11 12 13 14 15 16 17 
+18 19 20 21 22 23 24 
+25 26 27 28 29 30 31 
+
+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.
+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

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.
+ */
+
+@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;
++ (NSCalendarDate *)calendarDateWithString:(NSString *)str getRange:(out NSRange *)outRange;
+
+@end

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"
+
+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 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;
+
+	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 == ':') {
+					++ch;
+					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 == ':') {
+						++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 == ':') ++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 getRange:(out NSRange *)outRange {
+	return [self calendarDateWithString:str strictly:NO getRange:outRange];
+}
+
+@end

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>
+ */
+
+@interface NSCalendarDate(ISO8601Unparsing)
+
+- (NSString *)ISO8601DateStringWithTime:(BOOL)includeTime;
+- (NSString *)ISO8601WeekDateStringWithTime:(BOOL)includeTime;
+- (NSString *)ISO8601OrdinalDateStringWithTime:(BOOL)includeTime;
+
+//includeTime: YES.
+- (NSString *)ISO8601DateString;
+- (NSString *)ISO8601WeekDateString;
+- (NSString *)ISO8601OrdinalDateString;
+
+@end
+

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>
+
+static BOOL is_leap_year(unsigned year) {
+	return \
+	    ((year %   4U) == 0U)
+	&& (((year % 100U) != 0U)
+	||  ((year % 400U) == 0U));
+}
+
+@implementation NSCalendarDate(ISO8601Unparsing)
+
+#pragma mark Public methods
+
+- (NSString *)ISO8601DateStringWithTime:(BOOL)includeTime {
+	NSDateFormatter *formatter = [[NSDateFormatter alloc] initWithDateFormat:(includeTime ? @"%Y-%m-%dT%H:%M:%S%z" : @"%Y-%m-%d") allowNaturalLanguage:NO];
+	NSString *str = [formatter stringFromDate:self];
+	[formatter release];
+	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 {
+	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" allowNaturalLanguage:NO];
+		timeString = [formatter stringFromDate:self];
+		[formatter release];
+	} else
+		timeString = @"";
+
+	return [NSString stringWithFormat:@"%u-W%02u-%02u%@", year, week, dayOfWeek + 1U, timeString];
+}
+- (NSString *)ISO8601OrdinalDateStringWithTime:(BOOL)includeTime {
+	NSString *timeString;
+	if(includeTime) {
+		NSDateFormatter *formatter = [[NSDateFormatter alloc] initWithDateFormat:@"T%H:%M:%S%z" allowNaturalLanguage:NO];
+		timeString = [formatter stringFromDate:self];
+		[formatter release];
+	} else
+		timeString = @"";
+
+	return [NSString stringWithFormat:@"%u-%03u%@", [self yearOfCommonEra], [self dayOfYear], timeString];
+}
+
+#pragma mark -
+
+- (NSString *)ISO8601DateString {
+	return [self ISO8601DateStringWithTime:YES];
+}
+- (NSString *)ISO8601WeekDateString {
+	return [self ISO8601WeekDateStringWithTime:YES];
+}
+- (NSString *)ISO8601OrdinalDateString {
+	return [self ISO8601OrdinalDateStringWithTime:YES];
+}
+
+@end

Parser/LICENSE.txt

-Copyright © 2006 Mac-arena the Bored Zo
- 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 Mac-arena the Bored Zo 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.

Parser/Makefile

-CFLAGS+=-std=c99 -Wall
-LDFLAGS+=-framework Foundation
-
-testprog: testprog.o NSCalendarDate+ISO8601Parsing.o
-
-test.sh: test.sh.in
-	python test.sh.py
-.PHONY: test
-test: testprog test.sh
-	./test.sh

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.
- */
-
-@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;
-+ (NSCalendarDate *)calendarDateWithString:(NSString *)str getRange:(out NSRange *)outRange;
-
-@end

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"
-
-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 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;
-
-	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 == ':') {
-					++ch;
-					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 == ':') {
-						++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 == ':') ++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 getRange:(out NSRange *)outRange {
-	return [self calendarDateWithString:str strictly:NO getRange:outRange];
-}
-
-@end

Parser/README.txt

-HOW TO USE IN YOUR PROGRAM
-
-Add the source files to your project. When you want to parse a string that you believe is in ISO 8601 date format, call [NSCalendarDate calendarDateWithString:myString]. The method will return either an NSCalendarDate or nil.
-
-HOW TO TEST THAT THIS CODE WORKS
-
-Type 'make test'. make will build the test program (testprog), then invoke test.sh.py to generate test.sh. Then make will invoke test.sh, which will invoke the test program with various dates.
-
-Alternatively, type 'make testprog', then invoke testprog 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.
-
-NOTES
-
-This version is 0.1.
-
-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.
-
-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.
-
-BUGS
-
-• 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.

Parser/test.out

-cc -std=c99   -c -o NSCalendarDate+ISO8601Parsing.o NSCalendarDate+ISO8601Parsing.m
-cc -framework Foundation  testprog.o NSCalendarDate+ISO8601Parsing.o   -o testprog
-./test.sh
-
-Tests with a date only:
-% ./testprog 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
-2006-02-24 → 2006-02-24 00:00:00 -0800
-2006-12 → 2006-12-01 00:00:00 -0800
-2006 → 2006-01-01 00:00:00 -0800
-2006- → 2006-01-01 00:00:00 -0800
-2006-02- → 2006-02-01 00:00:00 -0800
-2006-0224 → 2006-02-24 00:00:00 -0800
-200602-24 → 2020-06-02 00:00:00 -0700
-20060224 → 2006-02-24 00:00:00 -0800
-2001-W1-1 → 2001-01-01 00:00:00 -0800
-2002-W1-1 → 2001-12-31 00:00:00 -0800
-2003-W1-1 → 2002-12-30 00:00:00 -0800
-2004-W1-1 → 2003-12-29 00:00:00 -0800
-2010-W1-1 → 2010-01-04 00:00:00 -0800
-2000-W1-1 → 2000-01-03 00:00:00 -0800
-2006-W1-1 → 2006-01-02 00:00:00 -0800
-2006-W1-2 → 2006-01-03 00:00:00 -0800
-2006-W02 → 2006-01-09 00:00:00 -0800
-2006-W2 → 2006-01-09 00:00:00 -0800
-2006-W02-01 → 2006-01-09 00:00:00 -0800
-2006-W02-1 → 2006-01-09 00:00:00 -0800
-2006-W2-01 → 2006-01-09 00:00:00 -0800
-2006-W2-1 → 2006-01-09 00:00:00 -0800
-2006-W2-2 → 2006-01-10 00:00:00 -0800
-2006-W2-8 → 2006-01-16 00:00:00 -0800
-2006-W3-1 → 2006-01-16 00:00:00 -0800
-2006-W22 → 2006-05-29 00:00:00 -0700
-2006-W22-11 → 2006-06-08 00:00:00 -0700
-06-02-24 → 2006-02-24 00:00:00 -0800
-06-12 → 2006-12-01 00:00:00 -0800
-06-02- → 2006-02-01 00:00:00 -0800
-06-0224 → 2006-02-24 00:00:00 -0800
-0602-24 → 602-05-03 00:00:00 -0800
-060224 → 2006-02-24 00:00:00 -0800
-06-W22 → 2006-05-29 00:00:00 -0700
-06-W2 → 2006-01-09 00:00:00 -0800
-06-W22-11 → 2006-06-08 00:00:00 -0700
-06-W2-1 → 2006-01-09 00:00:00 -0800
--6-02-24 → 2006-02-01 00:00:00 -0800
--6-12 → 2006-12-01 00:00:00 -0800
--6 → 2006-01-01 00:00:00 -0800
--6- → 2006-01-01 00:00:00 -0800
--6-02- → 2006-02-01 00:00:00 -0800
--6-0224 → 2006-02-02 00:00:00 -0800
--602-24 → 2007-08-25 00:00:00 -0700
--6-W22 → 2006-02-01 02:00:00 -0800
--6-W2 → 2006-02-01 00:00:00 -0800
--6-W22-11 → 2006-02-01 02:00:00 -1100
--6-W2-1 → 2006-02-01 00:00:00 -0800
---0224 → 2006-02-24 00:00:00 -0800
---02-24 → 2006-02-24 00:00:00 -0800
---2-24 → 2006-02-24 00:00:00 -0800
---02-2 → 2006-02-02 00:00:00 -0800
---02 → 2006-01-31 00:00:00 -0800
----24 → 2006-05-24 00:00:00 -0700
--W2 → 2006-01-09 00:00:00 -0800
--W2-11 → 2006-01-19 00:00:00 -0800
--W-3 → 2006-01-04 00:00:00 -0800
---W-3 → 2006-01-04 00:00:00 -0800
-2006-001 → 2006-01-01 00:00:00 -0800
-2006-002 → 2006-01-02 00:00:00 -0800
-2006-055 → 2006-02-24 00:00:00 -0800
-2006-365 → 2006-12-31 00:00:00 -0800
-2004-001 → 2004-01-01 00:00:00 -0800
-2004-366 → 2004-12-31 00:00:00 -0800
--055 → 2006-02-24 00:00:00 -0800
---055 → 2006-02-24 00:00:00 -0800
-2006T → 2006-01-01 00:00:00 -0800
-
-Current year in x century:
-% ./testprog 20 1
-20 → 2006-01-01 00:00:00 -0800
-1 → 106-01-01 00:00:00 -0800
-
-x year in current century:
-% ./testprog -06
--06 → 2006-01-02 00:00:00 -0800
-
-x year and month in current century:
-% ./testprog -06-02
--06-02 → 2006-02-01 00:00:00 -0800
-
-x year, month, and date in current century:
-% ./testprog 06-02-24
-06-02-24 → 2006-02-24 00:00:00 -0800
-
-x month and date in current year:
-% ./testprog --02-24
---02-24 → 2006-02-24 00:00:00 -0800
-
-x date in current year and month:
-% ./testprog ---24
----24 → 2006-05-24 00:00:00 -0700
-
-Tests with a time only:
-% ./testprog T22:63:24-11:21 T22:63:24+50:70 T22:1:2 T22:1Z T22: T22 T2 T2:2:2
-T22:63:24-11:21 → 2006-05-29 23:03:24 -1121
-T22:63:24+50:70 → 2006-05-29 23:03:24 -0700
-T22:1:2 → 2006-05-29 22:01:02 -0700
-T22:1Z → 2006-05-29 22:01:00 +0000
-T22: → 2006-05-29 22:00:00 -0700
-T22 → 2006-05-29 22:00:00 -0700
-T2 → 2006-05-29 02:00:00 -0700
-T2:2:2 → 2006-05-29 02:02:02 -0700
-
-Tests with both a date and a time:
-% ./testprog 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-24 02:43:24 -0800
-2006-02-24T22:43:24 → 2006-02-24 22:43:24 -0800
-2006-02-24T22:63:24 → 2006-02-24 23:03:24 -0800
-2006-12T12:34 → 2006-12-01 12:34:00 -0800
-2006T22 → 2006-01-01 22:00:00 -0800
-
-Tests with a date, a time, and a time zone:
-% ./testprog 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
-2006-02-24T22:63:24-01:00 → 2006-02-24 23:03:24 -0100
-2006-02-24T22:63:24Z → 2006-02-24 23:03:24 +0000
-2006-02-24T22:63:24-1 → 2006-02-24 23:03:24 -0100
-2006-02-24T22:63:24-01 → 2006-02-24 23:03:24 -0100
-2006-02-24T22:63:24-01:32 → 2006-02-24 23:03:24 -0132
-2006-02-24T22:63:24-01:0 → 2006-02-24 23:03:24 -0100
-2006-02-24T22:63:24-01:00 → 2006-02-24 23:03:24 -0100
-2006-02-24T22:63:24-01:01 → 2006-02-24 23:03:24 -0101
-2006-02-24T22:63:24-01:11 → 2006-02-24 23:03:24 -0111
-2006-02-24T22:63:24-11:21 → 2006-02-24 23:03:24 -1121
-
-Invalid dates:
-% ./testprog  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
- → (null)
-T → (null)
-2006-W → 2006-01-01 00:00:00 -0800
-2006-366 → 2007-01-01 00:00:00 -0800
-2006-400 → 2007-02-04 00:00:00 -0800
-2004-367 → 2005-01-01 00:00:00 -0800
--2006-02-24T02:43:24 → 20-06-02 00:00:00 -0800
--2006-02-24T22:43:24 → 20-06-02 00:00:00 -0800
--2006-02-24T22:63:24 → 20-06-02 00:00:00 -0800
--2006-12T12:34 → 20-06-12 12:34:00 -0800
--2006T22 → 20-06-01 22:00:00 -0800
--60224 → (null)
---2006-02-24T02:43:24 → 2006-09-08 00:00:00 -0700
---2006-02-24T22:43:24 → 2006-09-08 00:00:00 -0700
---2006-02-24T22:63:24 → 2006-09-08 00:00:00 -0700
---2006-12T12:34 → 2006-09-08 00:00:00 -0700
---2006T22 → 2006-09-08 22:00:00 -0700
----2006-02-24T02:43:24 → (null)
----2006-02-24T22:43:24 → (null)
----2006-02-24T22:63:24 → (null)
----2006-12T12:34 → (null)
----2006T22 → (null)

Parser/test.sh

-#!/bin/sh
-
-echo
-echo 'Tests with a date only:'
-
-echo '% ./testprog 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'
-./testprog 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 '% ./testprog 20 1'
-./testprog 20 1
-
-echo
-echo 'x year in current century:'
-
-echo '% ./testprog -06'
-./testprog -06
-
-echo
-echo 'x year and month in current century:'
-
-echo '% ./testprog -06-02'
-./testprog -06-02
-
-echo
-echo 'x year, month, and date in current century:'
-
-echo '% ./testprog 06-02-24'
-./testprog 06-02-24
-
-echo
-echo 'x month and date in current year:'
-
-echo '% ./testprog --02-24'
-./testprog --02-24
-
-echo
-echo 'x date in current year and month:'
-
-echo '% ./testprog ---24'
-./testprog ---24
-
-echo
-echo 'Tests with a time only:'
-
-echo '% ./testprog T22:63:24-11:21 T22:63:24+50:70 T22:1:2 T22:1Z T22: T22 T2 T2:2:2'
-./testprog 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 '% ./testprog 2006-02-24T02:43:24 2006-02-24T22:43:24 2006-02-24T22:63:24 2006-12T12:34 2006T22'
-./testprog 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 '% ./testprog 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'
-./testprog 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 '% ./testprog '' 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'
-./testprog '' 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

Parser/test.sh.in

-
-#Tests with a date only
-./testprog 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
-./testprog 20 \
-1
-#x year in current century
-./testprog -06
-#x year and month in current century
-./testprog -06-02
-#x year, month, and date in current century
-./testprog 06-02-24
-#x month and date in current year
-./testprog --02-24
-#x date in current year and month
-./testprog ---24
-
-#Tests with a time only
-./testprog 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
-./testprog 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
-./testprog 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
-./testprog '' \
-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

Parser/test.sh.py

-#!/usr/bin/env python
-
-outfile = file('test.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(['test.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()
-
-import os
-os.chmod('test.sh', 0755)

Parser/testprog.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];
-		NSDate *date = [NSCalendarDate calendarDateWithString:str strictly:parseStrictly];
-		fputs([[NSString stringWithFormat:@"%@ %C %@\n", str, 0x2192, date] UTF8String], stdout);
-	}
-
-	[pool release];
-	return 0;
-}

Parser/weekone.txt

-#This is W1 in each year. Output is from gcal -s Mon $YEAR. Manually re-sorted by weekday of W1-1.
-#A comment after each W1 shows the ordinal and calendar (--MM-DD) dates for W1-1.
-
-#In some cases, W1-1 is one of the last days the previous year. In this case, the ordinal date will be > 300 and the calendar date's month will be 12 rather than 1. Mentally fill in the fact that this applies not to the year shown on the gcal output but to the year before it.
-
-     January 2001
- Mo Tu We Th Fr Sa Su
-  1  2  3  4  5  6  7 #W1-1: 001: --01-01
-
-     January 2002
- Mo Tu We Th Fr Sa Su
-     1  2  3  4  5  6 #W1-1: 365: --12-31
-
-     January 2003
- Mo Tu We Th Fr Sa Su
-        1  2  3  4  5 #W1-1: 364: --12-30
-
-     January 2004
- Mo Tu We Th Fr Sa Su
-           1  2  3  4 #W1-1: 363: --12-29
-
-#W1 is the first week containing Thursday. So in the years after this line, W1 does not contain --01-01, because --01-01 was > Thursday, not <= it.
-