source: releases/Pester/1.1a2/Source/NJRDateFormatter.m@ 46

Last change on this file since 46 was 45, checked in by Nicholas Riley, 21 years ago

Pester 1.1a2.

English.lproj/Credits.html: Fixed some HTML formatting issues, added Ben Hines to credits (thanks for helping out with 1.1a1 testing!)

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

English.lproj/MainMenu.nib: Reconnected initialFirstResponder outlet on the window; somehow it became disconnected. Fixed keyboard navigation loop. Removed formatters from date/time fields which were causing crashes on launch on 10.2 (they're instantiated from code in any case). Removed text from date field because it didn't work without the formatter.

NJRDateFormatter: Workaround for 10.2 NSScanner bug [Ben Hines].

NJRQTMediaPopUpButton: Remove corrupt JPEG note, can no longer reproduce. Removed -validateRecentMedia invocation, debug code shouldn't have been left in.

PSAlarmSetController: Set alerts before setting alarm, otherwise alarm in bogus state remains. Set date to today in awakeFromNib, moved from the nib. Disconnect initial first responder to work around 10.1 bug so keyboard focus is set properly when the window opens.

Pester.pbproj: Added VERSION.

Read Me.rtfd: Updated for 1.1a2.

VERSION: Updated for 1.1a2.

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)] forKey: NSAMPMDesignation];
146 [paddedFormat release];
147 } else {
148 alteredLocale = [(NSDictionary *)locale retain];
149 }
150 }
151 return self;
152}
153
154- (void)dealloc;
155{
156 [alteredLocale release];
157 [super dealloc];
158}
159
160- (BOOL)getObjectValue:(id *)anObject forString:(NSString *)string errorDescription:(NSString **)error
161{
162 NSCalendarDate *date;
163 if (![self allowsNaturalLanguage])
164 return [super getObjectValue: anObject forString: string errorDescription: error];
165 if (string == nil) return nil;
166 NS_DURING // dateWithNaturalLanguageString: can throw an exception
167 date = [NSCalendarDate dateWithNaturalLanguageString: stringByRemovingSurroundingWhitespace(string) locale: alteredLocale];
168 // NSLog(@"%@: natural language date is %@", string, date);
169 NS_HANDLER
170 if (error != nil) *error = [localException reason];
171 NS_VALUERETURN(NO, BOOL);
172 NS_ENDHANDLER
173 // [super getObjectValue: anObject forString: string errorDescription: error];
174 // NSLog(@"%@: formatter date is %@", string, anObject == nil ? @"(null)" : *anObject);
175 if (date == nil) return [super getObjectValue: anObject forString: string errorDescription: error];
176 *anObject = date;
177 return YES;
178}
179
180@end
Note: See TracBrowser for help on using the repository browser.