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

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

NJRDateFormatter.m: Remove an unused function; actually get ICU-based date formatters working (previously, only Date::Manip was in use); add additional date formatters; resolve issue with "a" and "p" (or localized equivalents) not being interpreted as expected.

File size: 6.6 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 const 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 const 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 {
76        return;
77    }
78   
79    NSString *libPath = [[NSBundle mainBundle] pathForResource: libName ofType: @"dylib"];
80    if (libPath == nil)
81        return;
82   
83    void *lib = dlopen([libPath fileSystemRepresentation], RTLD_LAZY | RTLD_GLOBAL);
84    const char *libError;
85    if ( (libError = dlerror()) != NULL) {
86        NSLog(@"failed to dlopen %@: %s", libPath, libError);
87        return;
88    }
89   
90    parse_natural_language_date = dlsym(lib, "parse_natural_language_date");
91    if ( (libError = dlerror()) != NULL) {
92        NSLog(@"failed to look up parse_natural_language_date in %@: %s", libPath, libError);
93        parse_natural_language_date = NULL;
94        return;
95    }
96}
97
98+ (NJRDateFormatter *)dateFormatter;
99{
100    NJRDateFormatter *formatter = [[self alloc] init];
101    NSMutableArray *tryFormatters = [[NSMutableArray alloc] init];
102   
103    for (const NSDateFormatterStyle *s = formatterStyles ; *s != NSDateFormatterNoStyle ; *s++) {
104        NSDateFormatter *tryFormatter = dateFormatterWithStyle(*s);
105        [tryFormatters addObject: tryFormatter];
106        [tryFormatter release];
107    }
108    // XXX do this in init
109    formatter->tryFormatters = tryFormatters;
110
111    return [formatter autorelease];
112}
113
114+ (NJRDateFormatter *)timeFormatter;
115{
116    NJRDateFormatter *formatter = [[self alloc] init];
117    NSMutableArray *tryFormatters = [[NSMutableArray alloc] init];
118    NSDateFormatter *tryFormatter;
119   
120    for (const NSDateFormatterStyle *s = formatterStyles ; *s != NSDateFormatterNoStyle ; *s++) {
121        tryFormatter = timeFormatterWithStyle(*s);
122        [tryFormatters addObject: tryFormatter];
123        [tryFormatter release];
124    }
125    for (NSString **s = timeFormats ; *s != nil ; *s++) {
126        tryFormatter = timeFormatterWithFormat(*s);
127        [tryFormatters addObject: tryFormatter];
128        [tryFormatter release];
129    }
130    formatter->tryFormatters = tryFormatters;
131   
132    return [formatter autorelease];
133}
134
135- (void)dealloc;
136{
137    [tryFormatters release]; tryFormatters = nil;
138    [super dealloc];
139}
140
141#pragma mark primitive formatter
142
143- (NSString *)stringForObjectValue:(id)obj;
144{
145    return [super stringForObjectValue: obj];
146}
147
148- (NSAttributedString *)attributedStringForObjectValue:(id)obj withDefaultAttributes:(NSDictionary *)attrs;
149{
150    return [super attributedStringForObjectValue: obj
151                           withDefaultAttributes: attrs];
152}
153
154- (BOOL)getObjectValue:(id *)anObject forString:(NSString *)string errorDescription:(NSString **)error
155{
156    if ([super getObjectValue: anObject forString: string errorDescription: error])
157        return YES;
158       
159    NSDate *date;
160    NSEnumerator *e = [tryFormatters objectEnumerator];
161    NSDateFormatter *tryFormatter;
162    NSString *cleaned = nil;
163
164    // don't let time specifications ending in "a" or "p" trigger Date::Manip
165    if ([self timeStyle] != NSDateFormatterNoStyle &&
166        [string length] > 1 &&
167        ![[NSCharacterSet letterCharacterSet]
168           characterIsMember: [string characterAtIndex: [string length] - 2]]) {
169
170        NSString *am = [self AMSymbol], *pm = [self PMSymbol];
171        if (am != nil && [am length] > 1 && pm != nil && [pm length] > 1) {
172
173            NSString *a = [am substringToIndex: 1], *p = [pm substringToIndex: 1];
174            if (![a isCaseInsensitiveLike: p]) {
175
176                NSString *last = [string substringFromIndex: [string length] - 1];
177                if ([last isCaseInsensitiveLike: a])
178                    string = [string stringByAppendingString: [am substringFromIndex: 1]];
179                if ([last isCaseInsensitiveLike: p])
180                    string = [string stringByAppendingString: [pm substringFromIndex: 1]];
181            }
182        }
183    }
184   
185    while ( (tryFormatter = [e nextObject]) != nil) {
186        date = [tryFormatter dateFromString: string];
187
188        if (date == nil)
189            continue;
190
191        if (([tryFormatter dateStyle] != NSDateFormatterNoStyle) ||
192            ([tryFormatter timeStyle] != NSDateFormatterNoStyle))
193            goto success;
194
195        // XXX ICU-based "format" formatters return 0 instead of nil
196        if ([date timeIntervalSince1970] == 0)
197            continue;
198
199        // even non-lenient ICU-based "format" formatters are insufficiently strict,
200        // permitting arbitrary characters before and after the parsed string
201        NSString *formatted = [tryFormatter stringFromDate: date];
202        if (cleaned == nil)
203            cleaned = [[string componentsSeparatedByString: @" "] componentsJoinedByString: @""];
204        if ([cleaned characterAtIndex: 0] != '0' && [formatted characterAtIndex: 0] == '0')
205            formatted = [formatted substringFromIndex: 1];
206
207        if ([formatted isCaseInsensitiveLike: cleaned])
208            goto success;
209    }
210   
211    if (parse_natural_language_date == NULL) return NO;
212
213    date = parse_natural_language_date(string);
214    if (date != nil) goto success;
215   
216    return NO;
217
218success:
219    *anObject = date;
220    if (error != NULL) *error = nil;
221    return YES;
222}
223
224#pragma mark miscellaneous
225
226+ (BOOL)naturalLanguageParsingAvailable;
227{
228    return (parse_natural_language_date != NULL);
229}
230@end
Note: See TracBrowser for help on using the repository browser.