A simple class that converts dates to/from strings using strftime_l(3) and strptime_l(3).
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
STRFTimeFormatter.xcodeproj
STRFTimeFormatter
STRFTimeFormatterTests
.gitignore
LICENSE
README.md
STRFTimeFormatter.podspec

README.md

Overview

STRFTimeFormatter is a simple class that encapsulates the strftime_l(3) and strptime_l(3) functions in an interface that replicates that of NSDateFormatter. This class is to be used to convert machine generated dates in an efficient manner as mentioned in issue 9 of objc.io (see the section on Parsing Machine Input).

Installation

Installation is best done via cocoapods.

Podfile

pod 'STRFTimeFormatter'

Usage

STRFTimeFormatter replicates the convenience functions of the NSDateFormatter class:

- (NSDate *)dateFromString:(NSString *)string;
- (NSString *)stringFromDate:(NSDate *)date;

The only optional setup is to change the formatString used by the strftime_l(3) and strptime_l(3) functions. See the strftime(3) man page for possible format specifiers. The default is %Y-%m-%dT%H:%M:%S%z, which corresponds to a string format such as 2014-02-18T12:42:07+0000.

STRFTimeFormatter is not a subclass of NSFormatter, for two reasons:

  • It should be cheap to allocate an instance.
  • Laziness.

The Point

strptime_l(3) is a lot faster than -[NSDateFormatter dateFromString:], but it's a pain to use. STRFTimeFormatter gives you the huge performance bump when dealing fixed format date strings, but without the hassle of dropping into C libraries all the time (or having to put #import <xlocale.h> everywhere - how ugly).

Here's the proof:

#import <Foundation/Foundation.h>
#import <xlocale.h>

#define LOG_SAMPLES

void startTimer(NSDate **timer);
void finishTimer(NSDate *timer, NSString *message);

int main(int argc, char *argv[])
{
    @autoreleasepool {
        
        NSUInteger numberOfDatesToCreate = 604800;
        NSLog(@"Creating %llu date strings.", (unsigned long long)numberOfDatesToCreate);
        
        NSDate *timer;
        
        // string format to be used by strftime_l and strptime_l
        const char *formatString = "%Y-%m-%dT%H:%M:%S%z";
        
        // date formatter to be used
        NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
        [dateFormatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
        [dateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"];
        [dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"GMT"]];
        
        NSMutableArray *strfStrings = [[NSMutableArray alloc] init];
        NSMutableArray *formatterStrings = [[NSMutableArray alloc] init];
        
        startTimer(&timer);
        
        for (NSUInteger i = 0; i < numberOfDatesToCreate; i++) {
            
            @autoreleasepool {
                
                // create the date anyway, lets keep the comparison fair :)
                NSDate *date = [NSDate dateWithTimeIntervalSince1970:(1392595200 + i)];
                
                time_t timeInterval = [date timeIntervalSince1970];
                struct tm time = *localtime(&timeInterval);
                char buffer[80];
                
                strftime_l(buffer, sizeof(buffer), formatString, &time, NULL);
                NSString *dateString = [NSString stringWithCString:buffer encoding:NSASCIIStringEncoding];
                
#ifdef LOG_SAMPLES
                if (i % 86400 == 0) {
                    NSLog(@"Sample: %@", dateString);
                }
#endif
                
                [strfStrings addObject:dateString];
            }
        }
        
        finishTimer(timer, @"Created date strings using strftime_l(3)");
        startTimer(&timer);
        
        for (NSUInteger i = 0; i < numberOfDatesToCreate; i++) {
            
            @autoreleasepool {
                
                NSDate *date = [NSDate dateWithTimeIntervalSince1970:(1392595200 + i)];
                NSString *dateString = [dateFormatter stringFromDate:date];
                
#ifdef LOG_SAMPLES
                if (i % 86400 == 0) {
                    NSLog(@"Sample: %@", dateString);
                }
#endif
                
                [formatterStrings addObject:dateString];
            }
        }
        
        finishTimer(timer, @"Created date strings using NSDateFormatter");
        
        NSLog(@"Parsing %llu date strings.", (unsigned long long)numberOfDatesToCreate);
        
        startTimer(&timer);
        
        for (NSUInteger i = 0; i < numberOfDatesToCreate; i++) {
            
            NSString *dateString = strfStrings[i];
            
            struct tm time;
            strptime_l([dateString cStringUsingEncoding:NSASCIIStringEncoding], formatString, &time, NULL);
            time_t timeInterval = mktime(&time);
            
            // create the date anyway, lets keep the comparison fair :)
            __unused NSDate *date = [NSDate dateWithTimeIntervalSince1970:timeInterval];
            
#ifdef LOG_SAMPLES
            if (i % 86400 == 0) {
                NSLog(@"Sample: %@", date);
            }
#endif
        }
        
        finishTimer(timer, @"Parsed date strings using strptime_l(3)");
        startTimer(&timer);
        
        for (NSUInteger i = 0; i < numberOfDatesToCreate; i++) {
            
            NSString *dateString = formatterStrings[i];
            __unused NSDate *date = [dateFormatter dateFromString:dateString];
            
#ifdef LOG_SAMPLES
            if (i % 86400 == 0) {
                NSLog(@"Sample: %@", date);
            }
#endif
        }
        
        finishTimer(timer, @"Parsed date strings using NSDateFormatter");
    }
}

void startTimer(NSDate **timer)
{
    *timer = [NSDate date];
}

void finishTimer(NSDate *timer, NSString *message)
{
    NSTimeInterval interval = -[timer timeIntervalSinceNow];
    NSLog(@"%@ -- %g seconds", message, interval);
}