Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Support for Dates, Fix bug in offset computation for webviews

  • Loading branch information...
commit 2f00a23ff83cc801ad7bcfc49a1e7c4d59c65671 1 parent e067804
@krukow krukow authored
View
9 LICENSE_ISO8601.txt
@@ -0,0 +1,9 @@
+Copyright © 2006–2011 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.
2  calabash-js
@@ -1 +1 @@
-Subproject commit 4c353625943bdf6b22b27bb85ba2df778957bf1a
+Subproject commit bb8b9360651146483350f605cebe41f21f8e3d22
View
10 calabash.xcodeproj/project.pbxproj
@@ -24,6 +24,8 @@
/* Begin PBXBuildFile section */
B1296FC015DC1F4B005DDF5C /* LPReflectUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = B1296FBF15DC1F4B005DDF5C /* LPReflectUtils.h */; };
B1296FC315DC1F5B005DDF5C /* LPReflectUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = B1296FC215DC1F5B005DDF5C /* LPReflectUtils.m */; };
+ B13B29251627389500433F6C /* LPISO8601DateFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = B13B29231627389500433F6C /* LPISO8601DateFormatter.h */; };
+ B13B29261627389500433F6C /* LPISO8601DateFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = B13B29241627389500433F6C /* LPISO8601DateFormatter.m */; };
B13D5F8A15D43A3900504726 /* LPI.mm in Sources */ = {isa = PBXBuildFile; fileRef = B184FDBD14B6DB2B002A744C /* LPI.mm */; };
B17E756E15D433A40066550B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B121E79714B6D9FB0034C6A9 /* Foundation.framework */; };
B17E757B15D434D00066550B /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B17E757A15D434D00066550B /* CFNetwork.framework */; };
@@ -144,6 +146,8 @@
B1296FC215DC1F5B005DDF5C /* LPReflectUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LPReflectUtils.m; sourceTree = "<group>"; };
B12A04001511546F002FA032 /* LPInterpolateRoute.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LPInterpolateRoute.h; sourceTree = "<group>"; };
B12A04011511546F002FA032 /* LPInterpolateRoute.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LPInterpolateRoute.m; sourceTree = "<group>"; };
+ B13B29231627389500433F6C /* LPISO8601DateFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LPISO8601DateFormatter.h; sourceTree = "<group>"; };
+ B13B29241627389500433F6C /* LPISO8601DateFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LPISO8601DateFormatter.m; sourceTree = "<group>"; };
B153F55A15949F3D00867E12 /* LPVersionRoute.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LPVersionRoute.h; sourceTree = "<group>"; };
B153F55B15949F3D00867E12 /* LPVersionRoute.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LPVersionRoute.m; sourceTree = "<group>"; };
B15DA5E315C5D80A009E6CFE /* LPQueryAllOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LPQueryAllOperation.h; sourceTree = "<group>"; };
@@ -497,6 +501,8 @@
B184FDDD14B6DB2B002A744C /* JSON */ = {
isa = PBXGroup;
children = (
+ B13B29231627389500433F6C /* LPISO8601DateFormatter.h */,
+ B13B29241627389500433F6C /* LPISO8601DateFormatter.m */,
B184FDDE14B6DB2B002A744C /* CDataScanner_Extensions.h */,
B184FDDF14B6DB2B002A744C /* CDataScanner_Extensions.m */,
B184FDE014B6DB2B002A744C /* LPCJSONDeserializer.h */,
@@ -599,6 +605,7 @@
files = (
B17E75B615D436100066550B /* CalabashServer.h in Headers */,
B1296FC015DC1F4B005DDF5C /* LPReflectUtils.h in Headers */,
+ B13B29251627389500433F6C /* LPISO8601DateFormatter.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -678,7 +685,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "set -e\nset +u\n# Avoid recursively calling this script.\nif [[ $SF_MASTER_SCRIPT_RUNNING ]]\nthen\nexit 0\nfi\nset -u\nexport SF_MASTER_SCRIPT_RUNNING=1\n\nSF_TARGET_NAME=${PROJECT_NAME}\nSF_EXECUTABLE_PATH=\"lib${SF_TARGET_NAME}.a\"\nSF_WRAPPER_NAME=\"${SF_TARGET_NAME}.framework\"\n\n# The following conditionals come from\n# https://github.com/kstenerud/iOS-Universal-Framework\n\nif [[ \"$SDK_NAME\" =~ ([A-Za-z]+) ]]\nthen\nSF_SDK_PLATFORM=${BASH_REMATCH[1]}\nelse\necho \"Could not find platform name from SDK_NAME: $SDK_NAME\"\nexit 1\nfi\n\nif [[ \"$SDK_NAME\" =~ ([0-9]+.*$) ]]\nthen\nSF_SDK_VERSION=${BASH_REMATCH[1]}\nelse\necho \"Could not find sdk version from SDK_NAME: $SDK_NAME\"\nexit 1\nfi\n\nif [[ \"$SF_SDK_PLATFORM\" = \"iphoneos\" ]]\nthen\nSF_OTHER_PLATFORM=iphonesimulator\nelse\nSF_OTHER_PLATFORM=iphoneos\nfi\n\nif [[ \"$BUILT_PRODUCTS_DIR\" =~ (.*)$SF_SDK_PLATFORM$ ]]\nthen\nSF_OTHER_BUILT_PRODUCTS_DIR=\"${BASH_REMATCH[1]}${SF_OTHER_PLATFORM}\"\nelse\necho \"Could not find platform name from build products directory: $BUILT_PRODUCTS_DIR\"\nexit 1\nfi\n\n# Build the other platform.\nxcodebuild -project \"${PROJECT_FILE_PATH}\" -target \"${TARGET_NAME}\" -configuration \"${CONFIGURATION}\" -sdk ${SF_OTHER_PLATFORM}${SF_SDK_VERSION} BUILD_DIR=\"${BUILD_DIR}\" OBJROOT=\"${OBJROOT}\" BUILD_ROOT=\"${BUILD_ROOT}\" SYMROOT=\"${SYMROOT}\" $ACTION\n\n# Smash the two static libraries into one fat binary and store it in the .framework\nlipo -create \"${BUILT_PRODUCTS_DIR}/${SF_EXECUTABLE_PATH}\" \"${SF_OTHER_BUILT_PRODUCTS_DIR}/${SF_EXECUTABLE_PATH}\" -output \"${BUILT_PRODUCTS_DIR}/${SF_WRAPPER_NAME}/Versions/${FRAMEWORK_VERSION}/${SF_TARGET_NAME}\"\n\n# Copy the binary to the other architecture folder to have a complete framework in both.\ncp -a \"${BUILT_PRODUCTS_DIR}/${SF_WRAPPER_NAME}/Versions/${FRAMEWORK_VERSION}/${SF_TARGET_NAME}\" \"${SF_OTHER_BUILT_PRODUCTS_DIR}/${SF_WRAPPER_NAME}/Versions/${FRAMEWORK_VERSION}/${SF_TARGET_NAME}\"";
+ shellScript = "set -e\nset +u\n# Avoid recursively calling this script.\nif [[ $SF_MASTER_SCRIPT_RUNNING ]]\nthen\nexit 0\nfi\nset -u\nexport SF_MASTER_SCRIPT_RUNNING=1\n\nSF_TARGET_NAME=${PROJECT_NAME}\nSF_EXECUTABLE_PATH=\"lib${SF_TARGET_NAME}.a\"\nSF_WRAPPER_NAME=\"${SF_TARGET_NAME}.framework\"\n\n# The following conditionals come from\n# https://github.com/kstenerud/iOS-Universal-Framework\n\nif [[ \"$SDK_NAME\" =~ ([A-Za-z]+) ]]\nthen\nSF_SDK_PLATFORM=${BASH_REMATCH[1]}\nelse\necho \"Could not find platform name from SDK_NAME: $SDK_NAME\"\nexit 1\nfi\n\nif [[ \"$SDK_NAME\" =~ ([0-9]+.*$) ]]\nthen\nSF_SDK_VERSION=${BASH_REMATCH[1]}\nelse\necho \"Could not find sdk version from SDK_NAME: $SDK_NAME\"\nexit 1\nfi\n\nif [[ \"$SF_SDK_PLATFORM\" = \"iphoneos\" ]]\nthen\nSF_OTHER_PLATFORM=iphonesimulator\nelse\nSF_OTHER_PLATFORM=iphoneos\nfi\n\nif [[ \"$BUILT_PRODUCTS_DIR\" =~ (.*)$SF_SDK_PLATFORM$ ]]\nthen\nSF_OTHER_BUILT_PRODUCTS_DIR=\"${BASH_REMATCH[1]}${SF_OTHER_PLATFORM}\"\nelse\necho \"Could not find platform name from build products directory: $BUILT_PRODUCTS_DIR\"\nexit 1\nfi\n\n# Build the other platform.\nxcodebuild -project \"${PROJECT_FILE_PATH}\" -target \"${TARGET_NAME}\" -configuration \"${CONFIGURATION}\" -sdk ${SF_OTHER_PLATFORM}${SF_SDK_VERSION} BUILD_DIR=\"${BUILD_DIR}\" OBJROOT=\"${OBJROOT}\" BUILD_ROOT=\"${BUILD_ROOT}\" SYMROOT=\"${SYMROOT}\" $ACTION\n\n# Smash the two static libraries into one fat binary and store it in the .framework\nlipo -create \"${BUILT_PRODUCTS_DIR}/${SF_EXECUTABLE_PATH}\" \"${SF_OTHER_BUILT_PRODUCTS_DIR}/${SF_EXECUTABLE_PATH}\" -output \"${BUILT_PRODUCTS_DIR}/${SF_WRAPPER_NAME}/Versions/${FRAMEWORK_VERSION}/${SF_TARGET_NAME}\"\n\n# Copy the binary to the other architecture folder to have a complete framework in both.\ncp -a \"${BUILT_PRODUCTS_DIR}/${SF_WRAPPER_NAME}/Versions/${FRAMEWORK_VERSION}/${SF_TARGET_NAME}\" \"${SF_OTHER_BUILT_PRODUCTS_DIR}/${SF_WRAPPER_NAME}/Versions/${FRAMEWORK_VERSION}/${SF_TARGET_NAME}\"\n\n# Copy the binary as a libcalabashuni.a file\ncp -a \"${BUILT_PRODUCTS_DIR}/${SF_WRAPPER_NAME}/Versions/${FRAMEWORK_VERSION}/${SF_TARGET_NAME}\" \"${BUILT_PRODUCTS_DIR}/lib${SF_TARGET_NAME}uni.a\"\n\n# Copy the binary as a libcalabashuni.a file\ncp -a \"${BUILT_PRODUCTS_DIR}/${SF_WRAPPER_NAME}/Versions/${FRAMEWORK_VERSION}/${SF_TARGET_NAME}\" \"${SF_OTHER_BUILT_PRODUCTS_DIR}/lib${SF_TARGET_NAME}uni.a\"\n";
};
/* End PBXShellScriptBuildPhase section */
@@ -744,6 +751,7 @@
B17E75B215D4357F0066550B /* CalabashServer.m in Sources */,
B13D5F8A15D43A3900504726 /* LPI.mm in Sources */,
B1296FC315DC1F5B005DDF5C /* LPReflectUtils.m in Sources */,
+ B13B29261627389500433F6C /* LPISO8601DateFormatter.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
View
2  calabash/Classes/FranklyServer/Routes/LPVersionRoute.m
@@ -8,7 +8,7 @@
#import "LPVersionRoute.h"
-#define kLPCALABASHVERSION @"0.9.103"
+#define kLPCALABASHVERSION @"0.9.109"
@implementation LPVersionRoute
View
24 calabash/Classes/JSON/LPCJSONScanner.m
@@ -28,6 +28,7 @@
//
#import "LPCJSONScanner.h"
+#import "LPISO8601DateFormatter.h"
#import "CDataScanner_Extensions.h"
@@ -180,6 +181,10 @@ - (BOOL)scanJSONObject:(id *)outObject error:(NSError **)outError
case '\"':
case '\'':
theResult = [self scanJSONStringConstant:&theObject error:outError];
+ if (theResult && theObject && [theObject isKindOfClass:[NSString class]])
+ {
+ theObject = [self asDateOrString:theObject];
+ }
break;
case '0':
case '1':
@@ -213,6 +218,7 @@ - (BOOL)scanJSONObject:(id *)outObject error:(NSError **)outError
}
break;
}
+
if (outObject != NULL)
*outObject = theObject;
@@ -220,6 +226,24 @@ - (BOOL)scanJSONObject:(id *)outObject error:(NSError **)outError
return(theResult);
}
+-(id)asDateOrString:(NSString*) str
+{
+ static LPISO8601DateFormatter *dateFormat = nil;
+ if (dateFormat == nil)
+ {
+ dateFormat = [[LPISO8601DateFormatter alloc] init];
+ dateFormat.parsesStrictly = YES;
+ }
+ NSRange range;
+ NSDate *date = [dateFormat dateFromString:str timeZone:nil range:&range];
+ if (range.location != NSNotFound)
+ {
+ return date;
+ }
+ return str;
+
+}
+
- (BOOL)scanJSONDictionary:(NSDictionary **)outDictionary error:(NSError **)outError
{
NSUInteger theScanLocation = [self scanLocation];
View
21 calabash/Classes/JSON/LPCJSONSerializer.m
@@ -28,6 +28,7 @@
//
#import "LPCJSONSerializer.h"
+#import "LPISO8601DateFormatter.h"
#import "LPJSONRepresentation.h"
@@ -124,6 +125,26 @@ - (NSData *)serializeObject:(id)inObject error:(NSError **)outError
NSString *theString = [[[NSString alloc] initWithData:inObject encoding:NSUTF8StringEncoding] autorelease];
theResult = [self serializeString:theString error:outError];
}
+ else if ([inObject isKindOfClass:[NSDate class]])
+ {
+
+ static LPISO8601DateFormatter *dateFormat = nil;
+ if (dateFormat == nil)
+ {
+ dateFormat = [[LPISO8601DateFormatter alloc] init];
+ [dateFormat setIncludeTime:YES];
+ }
+
+ NSString *str = [dateFormat stringFromDate:(NSDate*)inObject];
+ NSArray *comps = [str componentsSeparatedByString:@"+"];
+ NSString *dt = [comps objectAtIndex:0];
+ NSString *tz = [comps objectAtIndex:1];
+ NSRange rStart = NSMakeRange(0, 2);
+ NSString *first = [tz substringWithRange:rStart];
+ NSString *rest = [tz substringFromIndex:2];
+ str = [NSString stringWithFormat:@"%@+%@:%@",dt,first,rest];
+ theResult = [self serializeString:str error:outError];
+ }
else if ([inObject respondsToSelector:@selector(JSONDataRepresentation)])
{
theResult = [inObject JSONDataRepresentation];
View
79 calabash/Classes/JSON/LPISO8601DateFormatter.h
@@ -0,0 +1,79 @@
+/*LPISO8601DateFormatter.h
+ *
+ *Created by Peter Hosey on 2009-04-11.
+ *Copyright 2009 Peter Hosey. All rights reserved.
+ */
+
+#import <Foundation/Foundation.h>
+
+/*This class converts dates to and from ISO 8601 strings. A good introduction to ISO 8601: <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 NSDate 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 format to either expect or produce.
+ *Calendar format is YYYY-MM-DD.
+ *Ordinal format is YYYY-DDD, where DDD ranges from 1 to 366; for example, 2009-32 is 2009-02-01.
+ *Week format is YYYY-Www-D, where ww ranges from 1 to 53 (the 'W' is literal) and D ranges from 1 to 7; for example, 2009-W05-07.
+ */
+enum {
+ LPISO8601DateFormatCalendar,
+ LPISO8601DateFormatOrdinal,
+ LPISO8601DateFormatWeek,
+};
+typedef NSUInteger LPISO8601DateFormat;
+
+//The default separator for time values. Currently, this is ':'.
+extern unichar ISO8601DefaultTimeSeparatorCharacter;
+
+@interface LPISO8601DateFormatter: NSFormatter
+{
+ NSString *lastUsedFormatString;
+ NSDateFormatter *unparsingFormatter;
+
+ NSCalendar *parsingCalendar, *unparsingCalendar;
+
+ NSTimeZone *defaultTimeZone;
+ LPISO8601DateFormat format;
+ unichar timeSeparator;
+ BOOL includeTime;
+ BOOL parsesStrictly;
+}
+
+//Call this if you get a memory warning.
++ (void) purgeGlobalCaches;
+
+@property(nonatomic, retain) NSTimeZone *defaultTimeZone;
+
+#pragma mark Parsing
+
+//As a formatter, this object converts strings to dates.
+
+@property BOOL parsesStrictly;
+
+- (NSDateComponents *) dateComponentsFromString:(NSString *)string;
+- (NSDateComponents *) dateComponentsFromString:(NSString *)string timeZone:(out NSTimeZone **)outTimeZone;
+- (NSDateComponents *) dateComponentsFromString:(NSString *)string timeZone:(out NSTimeZone **)outTimeZone range:(out NSRange *)outRange;
+
+- (NSDate *) dateFromString:(NSString *)string;
+- (NSDate *) dateFromString:(NSString *)string timeZone:(out NSTimeZone **)outTimeZone;
+- (NSDate *) dateFromString:(NSString *)string timeZone:(out NSTimeZone **)outTimeZone range:(out NSRange *)outRange;
+
+#pragma mark Unparsing
+
+@property LPISO8601DateFormat format;
+@property BOOL includeTime;
+@property unichar timeSeparator;
+
+- (NSString *) stringFromDate:(NSDate *)date;
+- (NSString *) stringFromDate:(NSDate *)date timeZone:(NSTimeZone *)timeZone;
+
+@end
View
895 calabash/Classes/JSON/LPISO8601DateFormatter.m
@@ -0,0 +1,895 @@
+/*LPISO8601DateFormatter.m
+ *
+ *Created by Peter Hosey on 2009-04-11.
+ *Copyright 2009 Peter Hosey. All rights reserved.
+ */
+
+#import <Foundation/Foundation.h>
+#import "LPISO8601DateFormatter.h"
+
+#ifndef DEFAULT_TIME_SEPARATOR
+# define DEFAULT_TIME_SEPARATOR ':'
+#endif
+unichar ISO8601DefaultTimeSeparatorCharacter = DEFAULT_TIME_SEPARATOR;
+
+//Unicode date formats.
+#define ISO_CALENDAR_DATE_FORMAT @"yyyy-MM-dd"
+//#define ISO_WEEK_DATE_FORMAT @"YYYY-'W'ww-ee" //Doesn't actually work because NSDateComponents counts the weekday starting at 1.
+#define ISO_ORDINAL_DATE_FORMAT @"yyyy-DDD"
+#define ISO_TIME_FORMAT @"HH:mm:ss"
+#define ISO_TIME_WITH_TIMEZONE_FORMAT ISO_TIME_FORMAT @"Z"
+//printf formats.
+#define ISO_TIMEZONE_UTC_FORMAT @"Z"
+#define ISO_TIMEZONE_OFFSET_FORMAT @"%+.2d%.2d"
+
+@interface LPISO8601DateFormatter(UnparsingPrivate)
+
+- (NSString *) replaceColonsInString:(NSString *)timeFormat withTimeSeparator:(unichar)timeSep;
+
+- (NSString *) stringFromDate:(NSDate *)date formatString:(NSString *)dateFormat timeZone:(NSTimeZone *)timeZone;
+- (NSString *) weekDateStringForDate:(NSDate *)date timeZone:(NSTimeZone *)timeZone;
+
+@end
+
+static NSMutableDictionary *timeZonesByOffset;
+
+@implementation LPISO8601DateFormatter
+
++ (void) initialize {
+ if (!timeZonesByOffset) {
+ timeZonesByOffset = [[NSMutableDictionary alloc] init];
+ }
+}
+
++ (void) purgeGlobalCaches {
+ NSMutableDictionary *oldCache = timeZonesByOffset;
+ timeZonesByOffset = nil;
+ [oldCache release];
+}
+
+- (NSCalendar *) makeCalendarWithDesiredConfiguration {
+ NSCalendar *calendar = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease];
+ calendar.firstWeekday = 2; //Monday
+ calendar.timeZone = [NSTimeZone defaultTimeZone];
+ return calendar;
+}
+
+- (id) init {
+ if ((self = [super init])) {
+ parsingCalendar = [[self makeCalendarWithDesiredConfiguration] retain];
+ unparsingCalendar = [[self makeCalendarWithDesiredConfiguration] retain];
+
+ format = LPISO8601DateFormatCalendar;
+ timeSeparator = ISO8601DefaultTimeSeparatorCharacter;
+ includeTime = NO;
+ parsesStrictly = NO;
+ }
+ return self;
+}
+- (void) dealloc {
+ [defaultTimeZone release];
+
+ [unparsingFormatter release];
+ [lastUsedFormatString release];
+ [parsingCalendar release];
+ [unparsingCalendar release];
+
+ [super dealloc];
+}
+
+@synthesize defaultTimeZone;
+- (void) setDefaultTimeZone:(NSTimeZone *)tz {
+ if (defaultTimeZone != tz) {
+ [defaultTimeZone release];
+ defaultTimeZone = [tz retain];
+
+ unparsingCalendar.timeZone = defaultTimeZone;
+ }
+}
+
+//The following properties are only here because GCC doesn't like @synthesize in category implementations.
+
+#pragma mark Parsing
+
+@synthesize parsesStrictly;
+
+static NSUInteger read_segment(const unsigned char *str, const unsigned char **next, NSUInteger *out_num_digits);
+static NSUInteger read_segment_4digits(const unsigned char *str, const unsigned char **next, NSUInteger *out_num_digits);
+static NSUInteger read_segment_2digits(const unsigned char *str, const unsigned char **next);
+static double read_double(const unsigned char *str, const unsigned char **next);
+static BOOL is_leap_year(NSUInteger year);
+
+/*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
+ */
+
+- (NSDateComponents *) dateComponentsFromString:(NSString *)string {
+ return [self dateComponentsFromString:string timeZone:NULL];
+}
+- (NSDateComponents *) dateComponentsFromString:(NSString *)string timeZone:(out NSTimeZone **)outTimeZone {
+ return [self dateComponentsFromString:string timeZone:outTimeZone range:NULL];
+}
+- (NSDateComponents *) dateComponentsFromString:(NSString *)string timeZone:(out NSTimeZone **)outTimeZone range:(out NSRange *)outRange {
+ NSDate *now = [NSDate date];
+
+ NSDateComponents *components = [[[NSDateComponents alloc] init] autorelease];
+ NSDateComponents *nowComponents = [parsingCalendar components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit) fromDate:now];
+
+ NSUInteger
+ //Date
+ year,
+ month_or_week = 0U,
+ day = 0U,
+ //Time
+ hour = 0U;
+ NSTimeInterval
+ minute = 0.0,
+ second = 0.0;
+ //Time zone
+ NSInteger tz_hour = 0;
+ NSInteger tz_minute = 0;
+
+ enum {
+ monthAndDate,
+ week,
+ dateOnly
+ } dateSpecification = monthAndDate;
+
+ BOOL strict = self.parsesStrictly;
+ unichar timeSep = self.timeSeparator;
+
+ if (strict) timeSep = ISO8601DefaultTimeSeparatorCharacter;
+ NSAssert(timeSep != '\0', @"Time separator must not be NUL.");
+
+ BOOL isValidDate = ([string length] > 0U);
+ NSTimeZone *timeZone = nil;
+
+ const unsigned char *ch = (const unsigned char *)[string UTF8String];
+
+ NSRange range = { 0U, 0U };
+ const unsigned char *start_of_date = NULL;
+ if (strict && isspace(*ch)) {
+ range.location = NSNotFound;
+ isValidDate = NO;
+ } else {
+ //Skip leading whitespace.
+ NSUInteger i = 0U;
+ for(NSUInteger len = strlen((const char *)ch); i < len; ++i) {
+ if (!isspace(ch[i]))
+ break;
+ }
+
+ range.location = i;
+ ch += i;
+ start_of_date = ch;
+
+ NSUInteger segment;
+ NSUInteger 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 = nowComponents.year;
+ month_or_week = nowComponents.month;
+ day = nowComponents.day;
+ } else {
+ 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 = nowComponents.year;
+ month_or_week = 1U;
+ ch += 2;
+ goto parseDayAfterWeek;
+ } else if (num_leading_hyphens == 1U) {
+ year = nowComponents.year;
+ 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 = nowComponents.year;
+ 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 = nowComponents.year;
+
+ 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 = nowComponents.year;
+ 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 = nowComponents.year;
+ 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 %lu and ch is %s", string, (unsigned long)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 = nowComponents.year;
+ 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 + nowComponents.year % 100U;
+ month_or_week = day = 1U;
+ }
+ break;
+
+ case 1:; //-YY; -YY-MM (implicit century)
+ NSLog(@"(%@) found %lu digits and one hyphen, so this is either -YY or -YY-MM; segment (year) is %lu", string, (unsigned long)num_digits, (unsigned long)segment);
+ NSUInteger current_year = nowComponents.year;
+ NSUInteger current_century = (current_year % 100U);
+ year = segment + (current_year - current_century);
+ if (num_digits == 1U) //implied decade
+ year += current_century - (current_year % 10U);
+
+ if (*ch == '-') {
+ ++ch;
+ month_or_week = read_segment_2digits(ch, &ch);
+ NSLog(@"(%@) month is %lu", string, (unsigned long)month_or_week);
+ }
+
+ day = 1U;
+ break;
+
+ case 2: //--MM; --MM-DD
+ year = nowComponents.year;
+ month_or_week = segment;
+ if (*ch == '-') {
+ ++ch;
+ day = read_segment_2digits(ch, &ch);
+ }
+ break;
+
+ case 3: //---DD
+ year = nowComponents.year;
+ month_or_week = nowComponents.month;
+ 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 = nowComponents.year;
+ 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);
+ }
+ }
+ }
+
+ if (!strict) {
+ if (isspace(*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 = (NSInteger)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;
+ }
+
+ NSTimeInterval timeZoneOffset = (tz_hour * 3600) + (tz_minute * 60);
+ NSNumber *offsetNum = [NSNumber numberWithDouble:timeZoneOffset];
+ timeZone = [timeZonesByOffset objectForKey:offsetNum];
+ if (!timeZone) {
+ timeZone = [NSTimeZone timeZoneForSecondsFromGMT:timeZoneOffset];
+ if (timeZone)
+ [timeZonesByOffset setObject:timeZone forKey:offsetNum];
+ }
+ }
+ }
+ }
+ }
+
+ if (isValidDate) {
+ components.year = year;
+ components.day = day;
+ components.hour = hour;
+ components.minute = (NSInteger)minute;
+ components.second = (NSInteger)second;
+
+ switch(dateSpecification) {
+ case monthAndDate:
+ components.month = month_or_week;
+ 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.
+ NSUInteger prevYear = year - 1U;
+ NSUInteger YY = prevYear % 100U;
+ NSUInteger C = prevYear - YY;
+ NSUInteger G = YY + YY / 4U;
+ NSUInteger isLeapYear = (((C / 100U) % 4U) * 5U);
+ NSUInteger Jan1Weekday = (isLeapYear + G) % 7U;
+ enum { monday, tuesday, wednesday, thursday/*, friday, saturday, sunday*/ };
+ components.day = ((8U - Jan1Weekday) + (7U * (Jan1Weekday > thursday))) + (day - 1U) + (7U * (month_or_week - 2));
+
+ case dateOnly: //An "ordinal date".
+ break;
+ }
+ }
+ } //if (!(strict && isdigit(ch[0])))
+
+ if (outRange) {
+ if (isValidDate)
+ range.length = ch - start_of_date;
+ else
+ range.location = NSNotFound;
+
+ *outRange = range;
+ }
+ if (outTimeZone) {
+ *outTimeZone = timeZone;
+ }
+
+ return components;
+}
+
+- (NSDate *) dateFromString:(NSString *)string {
+ return [self dateFromString:string timeZone:NULL];
+}
+- (NSDate *) dateFromString:(NSString *)string timeZone:(out NSTimeZone **)outTimeZone {
+ return [self dateFromString:string timeZone:outTimeZone range:NULL];
+}
+- (NSDate *) dateFromString:(NSString *)string timeZone:(out NSTimeZone **)outTimeZone range:(out NSRange *)outRange {
+ NSTimeZone *timeZone = nil;
+ NSDateComponents *components = [self dateComponentsFromString:string timeZone:&timeZone range:outRange];
+ if (outTimeZone)
+ *outTimeZone = timeZone;
+ parsingCalendar.timeZone = timeZone;
+
+ return [parsingCalendar dateFromComponents:components];
+}
+
+- (BOOL)getObjectValue:(id *)outValue forString:(NSString *)string errorDescription:(NSString **)error {
+ NSDate *date = [self dateFromString:string];
+ if (outValue)
+ *outValue = date;
+ return (date != nil);
+}
+
+#pragma mark Unparsing
+
+@synthesize format;
+@synthesize includeTime;
+@synthesize timeSeparator;
+
+- (NSString *) replaceColonsInString:(NSString *)timeFormat withTimeSeparator:(unichar)timeSep {
+ if (timeSep != ':') {
+ NSMutableString *timeFormatMutable = [[timeFormat mutableCopy] autorelease];
+ [timeFormatMutable replaceOccurrencesOfString:@":"
+ withString:[NSString stringWithCharacters:&timeSep length:1U]
+ options:NSBackwardsSearch | NSLiteralSearch
+ range:(NSRange){ 0UL, [timeFormat length] }];
+ timeFormat = timeFormatMutable;
+ }
+ return timeFormat;
+}
+
+- (NSString *) stringFromDate:(NSDate *)date {
+ NSTimeZone *timeZone = self.defaultTimeZone;
+ if (!timeZone) timeZone = [NSTimeZone defaultTimeZone];
+ return [self stringFromDate:date timeZone:timeZone];
+}
+
+- (NSString *) stringFromDate:(NSDate *)date timeZone:(NSTimeZone *)timeZone {
+ switch (self.format) {
+ case LPISO8601DateFormatCalendar:
+ return [self stringFromDate:date formatString:ISO_CALENDAR_DATE_FORMAT timeZone:timeZone];
+ case LPISO8601DateFormatWeek:
+ return [self weekDateStringForDate:date timeZone:timeZone];
+ case LPISO8601DateFormatOrdinal:
+ return [self stringFromDate:date formatString:ISO_ORDINAL_DATE_FORMAT timeZone:timeZone];
+ default:
+ [NSException raise:NSInternalInconsistencyException format:@"self.format was %d, not calendar (%d), week (%d), or ordinal (%d)", self.format, LPISO8601DateFormatCalendar, LPISO8601DateFormatWeek, LPISO8601DateFormatOrdinal];
+ return nil;
+ }
+}
+
+- (NSString *) stringFromDate:(NSDate *)date formatString:(NSString *)dateFormat timeZone:(NSTimeZone *)timeZone {
+ if (includeTime)
+ dateFormat = [dateFormat stringByAppendingFormat:@"'T'%@", [self replaceColonsInString:ISO_TIME_FORMAT withTimeSeparator:self.timeSeparator]];
+
+ unparsingCalendar.timeZone = timeZone;
+
+ if (dateFormat != lastUsedFormatString) {
+ [unparsingFormatter release];
+ unparsingFormatter = nil;
+
+ [lastUsedFormatString release];
+ lastUsedFormatString = [dateFormat retain];
+ }
+
+ if (!unparsingFormatter) {
+ unparsingFormatter = [[NSDateFormatter alloc] init];
+ unparsingFormatter.formatterBehavior = NSDateFormatterBehavior10_4;
+ unparsingFormatter.dateFormat = dateFormat;
+ unparsingFormatter.calendar = unparsingCalendar;
+ }
+
+ NSString *str = [unparsingFormatter stringForObjectValue:date];
+
+ if (includeTime) {
+ NSInteger offset = [timeZone secondsFromGMT];
+ offset /= 60; //bring down to minutes
+ if (offset == 0)
+ str = [str stringByAppendingString:ISO_TIMEZONE_UTC_FORMAT];
+ else
+ str = [str stringByAppendingFormat:ISO_TIMEZONE_OFFSET_FORMAT, offset / 60, offset % 60];
+ }
+
+ //Undo the change we made earlier
+ unparsingCalendar.timeZone = self.defaultTimeZone;
+
+ return str;
+}
+
+- (NSString *) stringForObjectValue:(id)value {
+ NSParameterAssert([value isKindOfClass:[NSDate class]]);
+
+ return [self stringFromDate:(NSDate *)value];
+}
+
+/*Adapted from:
+ * Algorithm for Converting Gregorian Dates to ISO 8601 Week Date
+ * Rick McCarty, 1999
+ * http://personal.ecu.edu/mccartyr/ISOwdALG.txt
+ */
+- (NSString *) weekDateStringForDate:(NSDate *)date timeZone:(NSTimeZone *)timeZone {
+ unparsingCalendar.timeZone = timeZone;
+ NSDateComponents *components = [unparsingCalendar components:NSYearCalendarUnit | NSWeekdayCalendarUnit | NSDayCalendarUnit fromDate:date];
+
+ //Determine the ordinal date.
+ NSDateComponents *startOfYearComponents = [unparsingCalendar components:NSYearCalendarUnit fromDate:date];
+ startOfYearComponents.month = 1;
+ startOfYearComponents.day = 1;
+ NSDateComponents *ordinalComponents = [unparsingCalendar components:NSDayCalendarUnit fromDate:[unparsingCalendar dateFromComponents:startOfYearComponents] toDate:date options:0];
+ ordinalComponents.day += 1;
+
+ enum {
+ monday, tuesday, wednesday, thursday, friday, saturday, sunday
+ };
+ enum {
+ january = 1, february, march,
+ april, may, june,
+ july, august, september,
+ october, november, december
+ };
+
+ NSInteger year = components.year;
+ NSInteger week = 0;
+ //The old unparser added 6 to [calendarDate dayOfWeek], which was zero-based; components.weekday is one-based, so we now add only 5.
+ NSInteger dayOfWeek = (components.weekday + 5) % 7;
+ NSInteger dayOfYear = ordinalComponents.day;
+
+ NSInteger prevYear = year - 1;
+
+ BOOL yearIsLeapYear = is_leap_year(year);
+ BOOL prevYearIsLeapYear = is_leap_year(prevYear);
+
+ NSInteger YY = prevYear % 100;
+ NSInteger C = prevYear - YY;
+ NSInteger G = YY + YY / 4;
+ NSInteger Jan1Weekday = (((((C / 100) % 4) * 5) + G) % 7);
+
+ NSInteger weekday = ((dayOfYear + Jan1Weekday) - 1) % 7;
+
+ if((dayOfYear <= (7 - Jan1Weekday)) && (Jan1Weekday > thursday)) {
+ week = 52 + ((Jan1Weekday == friday) || ((Jan1Weekday == saturday) && prevYearIsLeapYear));
+ --year;
+ } else {
+ NSInteger lengthOfYear = 365 + yearIsLeapYear;
+ if((lengthOfYear - dayOfYear) < (thursday - weekday)) {
+ ++year;
+ week = 1;
+ } else {
+ NSInteger J = dayOfYear + (sunday - weekday) + Jan1Weekday;
+ week = J / 7 - (Jan1Weekday > thursday);
+ }
+ }
+
+ NSString *timeString;
+ if(includeTime) {
+ NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
+ unichar timeSep = self.timeSeparator;
+ if (!timeSep) timeSep = ISO8601DefaultTimeSeparatorCharacter;
+ formatter.dateFormat = [self replaceColonsInString:ISO_TIME_WITH_TIMEZONE_FORMAT withTimeSeparator:timeSep];
+
+ timeString = [formatter stringForObjectValue:date];
+
+ [formatter release];
+ } else
+ timeString = @"";
+
+ return [NSString stringWithFormat:@"%lu-W%02lu-%02lu%@", (unsigned long)year, (unsigned long)week, ((unsigned long)dayOfWeek) + 1U, timeString];
+}
+
+@end
+
+static NSUInteger read_segment(const unsigned char *str, const unsigned char **next, NSUInteger *out_num_digits) {
+ NSUInteger num_digits = 0U;
+ NSUInteger 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 NSUInteger read_segment_4digits(const unsigned char *str, const unsigned char **next, NSUInteger *out_num_digits) {
+ NSUInteger num_digits = 0U;
+ NSUInteger 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 NSUInteger read_segment_2digits(const unsigned char *str, const unsigned char **next) {
+ NSUInteger 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) {
+ NSUInteger 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(NSUInteger year) {
+ return \
+ ((year % 4U) == 0U)
+ && (((year % 100U) != 0U)
+ || ((year % 400U) == 0U));
+}
View
2  calabash/Classes/Utils/LPTouchUtils.h
@@ -15,6 +15,8 @@
withSuperView:(UIView *)superView
inWindow:(id)window;
++(UIWindow*)windowForView:(UIView*)view;
+
+(BOOL)isViewVisible:(UIView *)view;
@end
View
2  calabash/Classes/Utils/LPTouchUtils.m
@@ -30,7 +30,7 @@ +(CGPoint) translateToScreenCoords:(CGPoint) point {
return point;
}
}
-+(UIWindow*)windowForView:(UIView*)view;
++(UIWindow*)windowForView:(UIView*)view
{
id v = view;
while (v && ![v isKindOfClass:[UIWindow class]])
Please sign in to comment.
Something went wrong with that request. Please try again.