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 |
|
---|
11 | NSUserDefaults *locale;
|
---|
12 |
|
---|
13 | @implementation NJRDateFormatter
|
---|
14 |
|
---|
15 | + (void)initialize;
|
---|
16 | {
|
---|
17 | locale = [[NSUserDefaults standardUserDefaults] retain];
|
---|
18 | }
|
---|
19 |
|
---|
20 | + (NSString *)format:(NSString *)format withoutComponent:(unichar)component;
|
---|
21 | {
|
---|
22 | NSScanner *scanner = [NSScanner scannerWithString: format];
|
---|
23 | int formatLength = [format length];
|
---|
24 | NSRange range;
|
---|
25 | [scanner setCharactersToBeSkipped: [NSCharacterSet characterSetWithCharactersInString: @""]];
|
---|
26 | // NSLog(@"format:withoutComponent: trying to excise %c from %@", component, format);
|
---|
27 | while ([scanner scanUpToString: @"%" intoString: nil] || ![scanner isAtEnd]) {
|
---|
28 | range.location = [scanner scanLocation];
|
---|
29 | // NSLog(@"location: %d/%d, remaining: %@%@", range.location, formatLength, [format substringFromIndex: range.location], [scanner isAtEnd] ? @", isAtEnd" : @"");
|
---|
30 | // XXX works fine without keeping track of length in 10.1.5; in 10.2, [scanner scanUptoString:intoString:] still returns YES even when scanner is at end and thereÕs nothing left to scan, and if you start accessing the string past the end... *boom*
|
---|
31 | if (range.location >= formatLength) break;
|
---|
32 | [scanner scanUpToCharactersFromSet: [NSCharacterSet letterCharacterSet] intoString: nil];
|
---|
33 | if ([format characterAtIndex: [scanner scanLocation]] == component) {
|
---|
34 | if ([scanner scanUpToString: @"%" intoString: nil] && ![scanner isAtEnd]) {
|
---|
35 | NSMutableString *mutableFormat = [format mutableCopy];
|
---|
36 | if (range.location != 0 && [[NSCharacterSet punctuationCharacterSet] characterIsMember: [format characterAtIndex: range.location - 1]]) {
|
---|
37 | range.location--; // "%I:%M:%S%p" -> "%I:%M%p"
|
---|
38 | }
|
---|
39 | range.length = [scanner scanLocation] - range.location;
|
---|
40 | [mutableFormat deleteCharactersInRange: range];
|
---|
41 | format = [mutableFormat copy];
|
---|
42 | [mutableFormat release];
|
---|
43 | return [format autorelease];
|
---|
44 | } else {
|
---|
45 | range = [format rangeOfCharacterFromSet: [NSCharacterSet letterCharacterSet] options: NSBackwardsSearch range: NSMakeRange(0, range.location)];
|
---|
46 | return [format substringToIndex: NSMaxRange(range)];
|
---|
47 | }
|
---|
48 | }
|
---|
49 | }
|
---|
50 | return format;
|
---|
51 | }
|
---|
52 |
|
---|
53 | + (NSString *)localizedDateFormatIncludingWeekday:(BOOL)weekday;
|
---|
54 | {
|
---|
55 | NSString *format = [locale stringForKey: NSDateFormatString];
|
---|
56 | if (weekday) return format;
|
---|
57 | return [self format: format withoutComponent: (unichar)'A'];
|
---|
58 | }
|
---|
59 |
|
---|
60 | + (NSString *)localizedShortDateFormatIncludingWeekday:(BOOL)weekday;
|
---|
61 | {
|
---|
62 | NSString *format = [locale stringForKey: NSShortDateFormatString];
|
---|
63 | if (weekday) return format;
|
---|
64 | return [self format: format withoutComponent: (unichar)'A'];
|
---|
65 | }
|
---|
66 |
|
---|
67 | NSString *stringByInsertingStringAtLocation(NSString *string, NSString *insert, int location) {
|
---|
68 | return [NSString stringWithFormat: @"%@%@%@", [string substringToIndex: location], insert,
|
---|
69 | [string substringFromIndex: location]];
|
---|
70 | }
|
---|
71 |
|
---|
72 | + (NSString *)localizedTimeFormatIncludingSeconds:(BOOL)seconds;
|
---|
73 | {
|
---|
74 | NSString *format = [locale stringForKey: NSTimeFormatString];
|
---|
75 | NSArray *ampm = [locale arrayForKey: NSAMPMDesignation];
|
---|
76 | NSString *am = [ampm objectAtIndex: 0], *pm = [ampm objectAtIndex: 1];
|
---|
77 | // work around bug with inconsistent AM/PM and time format
|
---|
78 | if ([am isEqualToString: @""] && [pm isEqualToString: @""])
|
---|
79 | format = [self format: format withoutComponent: 'p'];
|
---|
80 | else {
|
---|
81 | NSRange ampmComponentRange = [format rangeOfString: @"%p"];
|
---|
82 | NSCharacterSet *whitespace = [NSCharacterSet whitespaceCharacterSet];
|
---|
83 | BOOL needSpaceInFormatString = ![whitespace characterIsMember: [am characterAtIndex: 0]];
|
---|
84 | if (ampmComponentRange.location == NSNotFound) // "%1I:%M:%S" -> "%1I:%M:%S%p", "%1I:%M:%S %p"
|
---|
85 | format = [format stringByAppendingString: (needSpaceInFormatString ? @" %p" : @"%p")];
|
---|
86 | else {
|
---|
87 | NSRange whitespaceRange = [format rangeOfCharacterFromSet: whitespace options: NSBackwardsSearch range: NSMakeRange(0, ampmComponentRange.location)];
|
---|
88 | if (whitespaceRange.location == NSNotFound) {
|
---|
89 | if (needSpaceInFormatString) // "%1I:%M:%S%p" -> "%1I:%M:%S %p"
|
---|
90 | format = stringByInsertingStringAtLocation(format, @" ", ampmComponentRange.location);
|
---|
91 | // else "%1I:%M:%S%p" -> no change
|
---|
92 | } else {
|
---|
93 | if (NSMaxRange(whitespaceRange) == ampmComponentRange.location) {
|
---|
94 | if (!needSpaceInFormatString) // "%1I:%M:%S %p" -> "%1I:%M:%S%p"
|
---|
95 | format = [[format substringToIndex: whitespaceRange.location] stringByAppendingString: [format substringFromIndex: ampmComponentRange.location]];
|
---|
96 | // else "%1I:%M:%S %p" -> no change
|
---|
97 | } else {
|
---|
98 | if (needSpaceInFormatString)
|
---|
99 | format = stringByInsertingStringAtLocation(format, @" ", ampmComponentRange.location);
|
---|
100 | // else "%1I %M:%S%p" -> no change
|
---|
101 | }
|
---|
102 | }
|
---|
103 | }
|
---|
104 | }
|
---|
105 | if (seconds) return format;
|
---|
106 | return [self format: format withoutComponent: 'S'];
|
---|
107 | }
|
---|
108 |
|
---|
109 | // workaround for bug in Jaguar (and earlier?) NSCalendarDate dateWithNaturalLanguageString:
|
---|
110 | NSString * stringByRemovingSurroundingWhitespace(NSString *string) {
|
---|
111 | static NSCharacterSet *nonWhitespace = nil;
|
---|
112 | NSRange firstValidCharacter, lastValidCharacter;
|
---|
113 |
|
---|
114 | if (!nonWhitespace) {
|
---|
115 | nonWhitespace = [[[NSCharacterSet characterSetWithCharactersInString:
|
---|
116 | @" \t\r\n"] invertedSet] retain];
|
---|
117 | }
|
---|
118 |
|
---|
119 | firstValidCharacter = [string rangeOfCharacterFromSet:nonWhitespace];
|
---|
120 | if (firstValidCharacter.length == 0)
|
---|
121 | return @"";
|
---|
122 | lastValidCharacter = [string rangeOfCharacterFromSet:nonWhitespace options: NSBackwardsSearch];
|
---|
123 |
|
---|
124 | if (firstValidCharacter.location == 0 && lastValidCharacter.location == [string length] - 1)
|
---|
125 | return string;
|
---|
126 | else
|
---|
127 | return [string substringWithRange: NSUnionRange(firstValidCharacter, lastValidCharacter)];
|
---|
128 | }
|
---|
129 |
|
---|
130 | - (id)initWithDateFormat:(NSString *)format allowNaturalLanguage:(BOOL)flag;
|
---|
131 | {
|
---|
132 | if ( (self = [super initWithDateFormat: format allowNaturalLanguage: flag]) != nil) {
|
---|
133 | NSRange ampmRange = [format rangeOfString: @"%p"];
|
---|
134 | NSArray *ampm = [locale arrayForKey: NSAMPMDesignation];
|
---|
135 | NSString *am = [ampm objectAtIndex: 0], *pm = [ampm objectAtIndex: 1];
|
---|
136 | if (flag && ampmRange.location != NSNotFound &&
|
---|
137 | [[locale stringForKey: NSTimeFormatString] rangeOfString: @"%p"].location == NSNotFound && ![am isEqualToString: pm]) {
|
---|
138 | // workaround for bug in NSCalendarDate dateWithNaturalLanguageString: discarding AM/PM value when AM/PM designations have spaces in them (of which the use thereof is a workaround for NSDateFormatter discarding the AM/PM value)
|
---|
139 | NSMutableString *paddedFormat = [format mutableCopy];
|
---|
140 | [paddedFormat replaceCharactersInRange: ampmRange withString: @" %p"];
|
---|
141 | alteredLocale = [[locale dictionaryRepresentation] mutableCopy];
|
---|
142 | [(NSMutableDictionary *)alteredLocale setObject: paddedFormat forKey: NSTimeFormatString];
|
---|
143 | [(NSMutableDictionary *)alteredLocale setObject:
|
---|
144 | [NSArray arrayWithObjects: stringByRemovingSurroundingWhitespace(am),
|
---|
145 | stringByRemovingSurroundingWhitespace(pm), nil]
|
---|
146 | forKey: NSAMPMDesignation];
|
---|
147 | [paddedFormat release];
|
---|
148 | } else {
|
---|
149 | alteredLocale = [(NSDictionary *)locale retain];
|
---|
150 | }
|
---|
151 | }
|
---|
152 | return self;
|
---|
153 | }
|
---|
154 |
|
---|
155 | - (void)dealloc;
|
---|
156 | {
|
---|
157 | [alteredLocale release];
|
---|
158 | [super dealloc];
|
---|
159 | }
|
---|
160 |
|
---|
161 | - (BOOL)getObjectValue:(id *)anObject forString:(NSString *)string errorDescription:(NSString **)error
|
---|
162 | {
|
---|
163 | NSCalendarDate *date;
|
---|
164 | if (![self allowsNaturalLanguage])
|
---|
165 | return [super getObjectValue: anObject forString: string errorDescription: error];
|
---|
166 | if (string == nil) return nil;
|
---|
167 | NS_DURING // dateWithNaturalLanguageString: can throw an exception
|
---|
168 | date = [NSCalendarDate dateWithNaturalLanguageString: stringByRemovingSurroundingWhitespace(string) locale: alteredLocale];
|
---|
169 | // NSLog(@"%@: natural language date is %@", string, date);
|
---|
170 | NS_HANDLER
|
---|
171 | if (error != nil) *error = [localException reason];
|
---|
172 | NS_VALUERETURN(NO, BOOL);
|
---|
173 | NS_ENDHANDLER
|
---|
174 | // [super getObjectValue: anObject forString: string errorDescription: error];
|
---|
175 | // NSLog(@"%@: formatter date is %@", string, anObject == nil ? @"(null)" : *anObject);
|
---|
176 | if (date == nil) return [super getObjectValue: anObject forString: string errorDescription: error];
|
---|
177 | *anObject = date;
|
---|
178 | return YES;
|
---|
179 | }
|
---|
180 |
|
---|
181 | @end
|
---|