Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -63,10 +63,11 @@ OFXMLElementBuilder.m \ OFXMLNode.m \ OFXMLParser.m \ base64.m \ of_asprintf.m \ + of_strptime.m \ unicode.m INCLUDES := ${SRCS:.m=.h} \ OFCollection.h \ OFSerialization.h \ Index: src/OFDate.h ================================================================== --- src/OFDate.h +++ src/OFDate.h @@ -58,11 +58,14 @@ * * The time zone used is UTC. If a time zone is specified anyway, an * OFInvalidFormatException is thrown. See +[dateWithLocalDateString:format:] * if you want to specify a time zone. * - * See the manpage for strptime for information on the format. + * See the manpage for strftime for information on the format. + * + * WARNING: The format is currently limited to the following format specifiers: + * %d, %e, %H, %m, %M, %S, %y, %Y, %%, %n and %t. * * \param string The string describing the date * \param format The format of the string describing the date * \return A new, autoreleased OFDate with the specified date and time */ @@ -73,11 +76,14 @@ * \brief Creates a new OFDate with the specified string in the specified * format. * * If no time zone is specified, local time is assumed. * - * See the manpage for strptime for information on the format. + * See the manpage for strftime for information on the format. + * + * WARNING: The format is currently limited to the following format specifiers: + * %d, %e, %H, %m, %M, %S, %y, %Y, %%, %n and %t. * * \param string The string describing the date * \param format The format of the string describing the date * \return A new, autoreleased OFDate with the specified date and time */ @@ -126,11 +132,14 @@ * * The time zone used is UTC. If a time zone is specified anyway, an * OFInvalidFormatException is thrown. See -[initWithLocalDateString:format:] * if you want to specify a time zone. * - * See the manpage for strptime for information on the format. + * See the manpage for strftime for information on the format. + * + * WARNING: The format is currently limited to the following format specifiers: + * %d, %e, %H, %m, %M, %S, %y, %Y, %%, %n and %t. * * \param string The string describing the date * \param format The format of the string describing the date * \return An initialized OFDate with the specified date and time */ @@ -141,11 +150,14 @@ * \brief Initializes an already allocated OFDate with the specified string in * the specified format. * * If no time zone is specified, local time is assumed. * - * See the manpage for strptime for information on the format. + * See the manpage for strftime for information on the format. + * + * WARNING: The format is currently limited to the following format specifiers: + * %d, %e, %H, %m, %M, %S, %y, %Y, %%, %n and %t. * * \param string The string describing the date * \param format The format of the string describing the date * \return An initialized OFDate with the specified date and time */ Index: src/OFDate.m ================================================================== --- src/OFDate.m +++ src/OFDate.m @@ -39,10 +39,11 @@ #import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" #import "OFOutOfRangeException.h" #import "macros.h" +#import "of_strptime.h" #if (!defined(HAVE_GMTIME_R) || !defined(HAVE_LOCALTIME_R)) && \ defined(OF_THREADS) static OFMutex *mutex; #endif @@ -242,11 +243,11 @@ @try { struct tm tm = {}; tm.tm_isdst = -1; - if (strptime([string UTF8String], [format UTF8String], + if (of_strptime([string UTF8String], [format UTF8String], &tm) == NULL) @throw [OFInvalidFormatException exceptionWithClass: isa]; #ifdef STRUCT_TM_HAS_TM_GMTOFF @@ -295,11 +296,11 @@ @try { struct tm tm = {}; tm.tm_isdst = -1; - if (strptime([string UTF8String], [format UTF8String], + if (of_strptime([string UTF8String], [format UTF8String], &tm) == NULL) @throw [OFInvalidFormatException exceptionWithClass: isa]; if ((seconds = mktime(&tm)) == -1) Index: src/ObjFW.h ================================================================== --- src/ObjFW.h +++ src/ObjFW.h @@ -141,5 +141,6 @@ #endif #import "asprintf.h" #import "base64.h" #import "of_asprintf.h" +#import "of_strptime.h" ADDED src/of_strptime.h Index: src/of_strptime.h ================================================================== --- src/of_strptime.h +++ src/of_strptime.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif +extern const char* of_strptime(const char*, const char*, struct tm *tm); +#ifdef __cplusplus +} +#endif ADDED src/of_strptime.m Index: src/of_strptime.m ================================================================== --- src/of_strptime.m +++ src/of_strptime.m @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include + +#include + +const char* +of_strptime(const char *buffer, const char *format, struct tm *tm) +{ + size_t i, j, bufferLength, formatLength; + enum { + SEARCH_CONVERSION_SPECIFIER, + IN_CONVERSION_SPECIFIER + } state = SEARCH_CONVERSION_SPECIFIER; + + bufferLength = strlen(buffer); + formatLength = strlen(format); + + for (i = j = 0; i < formatLength; i++) { + if (j >= bufferLength) + return NULL; + + switch (state) { + case SEARCH_CONVERSION_SPECIFIER: + if (format[i] == '%') + state = IN_CONVERSION_SPECIFIER; + else if (format[i] != buffer[j++]) + return NULL; + + break; + + case IN_CONVERSION_SPECIFIER:; + int k, maxLength, number = 0; + + switch (format[i]) { + case 'd': + case 'e': + case 'H': + case 'm': + case 'M': + case 'S': + case 'y': + maxLength = 2; + break; + case 'Y': + maxLength = 4; + break; + case '%': + case 'n': + case 't': + maxLength = 0; + break; + default: + return NULL; + } + + if (maxLength > 0 && + (buffer[j] < '0' || buffer[j] > '9')) + return NULL; + + for (k = 0; k < maxLength && j < bufferLength && + buffer[j] >= '0' && buffer[j] <= '9'; k++, j++) { + number *= 10; + number += buffer[j] - '0'; + } + + switch (format[i]) { + case 'd': + case 'e': + tm->tm_mday = number; + break; + case 'H': + tm->tm_hour = number; + break; + case 'm': + tm->tm_mon = number - 1; + break; + case 'M': + tm->tm_min = number; + break; + case 'S': + tm->tm_sec = number; + break; + case 'y': + if (number <= 68) + number += 100; + + tm->tm_year = number; + break; + case 'Y': + if (number < 1900) + return NULL; + + tm->tm_year = number - 1900; + break; + case '%': + if (buffer[j++] != '%') + return NULL; + break; + case 'n': + case 't': + if (buffer[j] != ' ' && buffer[j] != '\r' && + buffer[j] != '\n' && buffer[j] != '\t' && + buffer[j] != '\f') + return NULL; + + j++; + break; + } + + state = SEARCH_CONVERSION_SPECIFIER; + + break; + } + } + + return buffer + j; +} Index: tests/OFDateTests.m ================================================================== --- tests/OFDateTests.m +++ tests/OFDateTests.m @@ -31,17 +31,22 @@ OFDate *d1, *d2; TEST(@"+[dateWithTimeIntervalSince1970:]", (d1 = [OFDate dateWithTimeIntervalSince1970: 0])) - TEST(@"-[dateByADdingTimeInterval:]", + TEST(@"-[dateByAddingTimeInterval:]", (d2 = [d1 dateByAddingTimeInterval: 3600 * 25 + 5.000001])) TEST(@"-[description]", [[d1 description] isEqual: @"1970-01-01T00:00:00Z"] && [[d2 description] isEqual: @"1970-01-02T01:00:05Z"]) + TEST(@"+[dateWithDateString:format:]", + [[[OFDate dateWithDateString: @"2000-06-20T12:34:56Z" + format: @"%Y-%m-%dT%H:%M:%SZ"] description] + isEqual: @"2000-06-20T12:34:56Z"]); + TEST(@"-[isEqual:]", [d1 isEqual: [OFDate dateWithTimeIntervalSince1970: 0]] && ![d1 isEqual: [OFDate dateWithTimeIntervalSince1970: 0.0000001]]) TEST(@"-[compare:]", [d1 compare: d2] == OF_ORDERED_ASCENDING)