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

Last change on this file since 306 was 49, checked in by Nicholas Riley, 22 years ago

Pester 1.1a3.

VERSION: Updated for 1.1a3.

Read Me.rtfd: Updated for 1.1a3, removed references to unimplemented
repeating alarms.

NJRDateFormatter: Fixed stupid bug with missing nil that caused
crashes on certain platforms.

English.lproj/InfoPlist.strings: Updated for 1.1a3.

File size: 8.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
11NSUserDefaults *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
67NSString *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:
110NSString * 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
Note: See TracBrowser for help on using the repository browser.