source: trunk/Cocoa/Pester/Source/NJRDateFormatter.m @ 581

Last change on this file since 581 was 581, checked in by Nicholas Riley, 10 years ago

Basic 10.6 compatibility fixes for Perl embedding. Apparently DynaLoader? is no longer necessary (was it ever?).

File size: 6.7 KB
Line 
1//
2//  NJRDateFormatter.m
3//  Pester
4//
5//  Created by Nicholas Riley on Wed Oct 09 2002.
6//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
7//
8
9#import "NJRDateFormatter.h"
10#import "ParseDate.h"
11#include <dlfcn.h>
12
13static NSDateFormatter *protoFormatter() {
14    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
15    [formatter setLenient: YES];
16    return formatter;
17}
18
19static NSDateFormatter *dateFormatterWithStyle(NSDateFormatterStyle style) {
20    NSDateFormatter *formatter = protoFormatter();
21    [formatter setTimeStyle: NSDateFormatterNoStyle];
22    [formatter setDateStyle: style];
23    return formatter;
24}
25
26static NSDateFormatter *timeFormatterWithStyle(NSDateFormatterStyle style) {
27    NSDateFormatter *formatter = protoFormatter();
28    [formatter setTimeStyle: style];
29    [formatter setDateStyle: NSDateFormatterNoStyle];
30    return formatter;
31}
32
33static NSDateFormatter *timeFormatterWithFormat(NSString *format) {
34    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
35    [formatter setDateFormat: format];
36    [formatter setLenient: NO];
37    return formatter;
38}
39
40static NSDateFormatterStyle formatterStyles[] = {
41    NSDateFormatterShortStyle,
42    NSDateFormatterMediumStyle,
43    NSDateFormatterLongStyle,
44    NSDateFormatterFullStyle,
45    NSDateFormatterNoStyle
46};
47
48// note: these formats must be 0-padded where appropriate and contain no spaces
49// or attempts to force them into strict interpretation will fail
50static NSString *timeFormats[] = {
51    @"hha",
52    @"HHmmss",
53    @"HHmm",
54    @"HH",
55    nil
56};
57
58@implementation NJRDateFormatter
59
60#pragma mark initialize-release
61
62+ (void)initialize;
63{
64    long minorVersion, majorVersion;
65    Gestalt(gestaltSystemVersionMajor, &majorVersion);
66    Gestalt(gestaltSystemVersionMinor, &minorVersion);
67    if (majorVersion != 10)
68        return;
69   
70    NSString *libName;
71    if (minorVersion == 4) {
72        libName = @"libParseDate-10.4";
73    } else if (minorVersion == 5) {
74        libName = @"libParseDate-10.5";
75    } else if (minorVersion == 6) {
76        libName = @"libParseDate-10.6";
77    } else {
78        return;
79    }
80   
81    NSString *libPath = [[NSBundle mainBundle] pathForResource: libName ofType: @"dylib"];
82    if (libPath == nil)
83        return;
84   
85    void *lib = dlopen([libPath fileSystemRepresentation], RTLD_LAZY | RTLD_GLOBAL);
86    const char *libError;
87    if ( (libError = dlerror()) != NULL) {
88        NSLog(@"failed to dlopen %@: %s", libPath, libError);
89        return;
90    }
91   
92    parse_natural_language_date = dlsym(lib, "parse_natural_language_date");
93    if ( (libError = dlerror()) != NULL) {
94        NSLog(@"failed to look up parse_natural_language_date in %@: %s", libPath, libError);
95        parse_natural_language_date = NULL;
96        return;
97    }
98}
99
100+ (NJRDateFormatter *)dateFormatter;
101{
102    NJRDateFormatter *formatter = [[self alloc] init];
103    NSMutableArray *tryFormatters = [[NSMutableArray alloc] init];
104   
105    for (const NSDateFormatterStyle *s = formatterStyles ; *s != NSDateFormatterNoStyle ; *s++) {
106        NSDateFormatter *tryFormatter = dateFormatterWithStyle(*s);
107        [tryFormatters addObject: tryFormatter];
108        [tryFormatter release];
109    }
110    // XXX do this in init
111    formatter->tryFormatters = tryFormatters;
112
113    return [formatter autorelease];
114}
115
116+ (NJRDateFormatter *)timeFormatter;
117{
118    NJRDateFormatter *formatter = [[self alloc] init];
119    NSMutableArray *tryFormatters = [[NSMutableArray alloc] init];
120    NSDateFormatter *tryFormatter;
121   
122    for (const NSDateFormatterStyle *s = formatterStyles ; *s != NSDateFormatterNoStyle ; *s++) {
123        tryFormatter = timeFormatterWithStyle(*s);
124        [tryFormatters addObject: tryFormatter];
125        [tryFormatter release];
126    }
127    for (NSString **s = timeFormats ; *s != nil ; *s++) {
128        tryFormatter = timeFormatterWithFormat(*s);
129        [tryFormatters addObject: tryFormatter];
130        [tryFormatter release];
131    }
132    formatter->tryFormatters = tryFormatters;
133   
134    return [formatter autorelease];
135}
136
137- (void)dealloc;
138{
139    [tryFormatters release]; tryFormatters = nil;
140    [super dealloc];
141}
142
143#pragma mark primitive formatter
144
145- (NSString *)stringForObjectValue:(id)obj;
146{
147    return [super stringForObjectValue: obj];
148}
149
150- (NSAttributedString *)attributedStringForObjectValue:(id)obj withDefaultAttributes:(NSDictionary *)attrs;
151{
152    return [super attributedStringForObjectValue: obj
153                           withDefaultAttributes: attrs];
154}
155
156- (BOOL)getObjectValue:(id *)anObject forString:(NSString *)string errorDescription:(NSString **)error
157{
158    if ([super getObjectValue: anObject forString: string errorDescription: error])
159        return YES;
160       
161    NSDate *date;
162    NSEnumerator *e = [tryFormatters objectEnumerator];
163    NSDateFormatter *tryFormatter;
164    NSString *cleaned = nil;
165
166    // don't let time specifications ending in "a" or "p" trigger Date::Manip
167    if ([self timeStyle] != NSDateFormatterNoStyle &&
168        [string length] > 1 &&
169        ![[NSCharacterSet letterCharacterSet]
170           characterIsMember: [string characterAtIndex: [string length] - 2]]) {
171
172        NSString *am = [self AMSymbol], *pm = [self PMSymbol];
173        if (am != nil && [am length] > 1 && pm != nil && [pm length] > 1) {
174
175            NSString *a = [am substringToIndex: 1], *p = [pm substringToIndex: 1];
176            if (![a isCaseInsensitiveLike: p]) {
177
178                NSString *last = [string substringFromIndex: [string length] - 1];
179                if ([last isCaseInsensitiveLike: a])
180                    string = [string stringByAppendingString: [am substringFromIndex: 1]];
181                if ([last isCaseInsensitiveLike: p])
182                    string = [string stringByAppendingString: [pm substringFromIndex: 1]];
183            }
184        }
185    }
186   
187    while ( (tryFormatter = [e nextObject]) != nil) {
188        date = [tryFormatter dateFromString: string];
189
190        if (date == nil)
191            continue;
192
193        if (([tryFormatter dateStyle] != NSDateFormatterNoStyle) ||
194            ([tryFormatter timeStyle] != NSDateFormatterNoStyle))
195            goto success;
196
197        // XXX ICU-based "format" formatters return 0 instead of nil
198        if ([date timeIntervalSince1970] == 0)
199            continue;
200
201        // even non-lenient ICU-based "format" formatters are insufficiently strict,
202        // permitting arbitrary characters before and after the parsed string
203        NSString *formatted = [tryFormatter stringFromDate: date];
204        if (cleaned == nil)
205            cleaned = [[string componentsSeparatedByString: @" "] componentsJoinedByString: @""];
206        if ([cleaned characterAtIndex: 0] != '0' && [formatted characterAtIndex: 0] == '0')
207            formatted = [formatted substringFromIndex: 1];
208
209        if ([formatted isCaseInsensitiveLike: cleaned])
210            goto success;
211    }
212   
213    if (parse_natural_language_date == NULL) return NO;
214
215    date = parse_natural_language_date(string);
216    if (date != nil) goto success;
217   
218    return NO;
219
220success:
221    *anObject = date;
222    if (error != NULL) *error = nil;
223    return YES;
224}
225
226#pragma mark miscellaneous
227
228+ (BOOL)naturalLanguageParsingAvailable;
229{
230    return (parse_natural_language_date != NULL);
231}
232@end
Note: See TracBrowser for help on using the repository browser.