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

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

Pester 1.1a1.

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

English.lproj/MainMenu.nib: Placeholder for day names in popup menu, fixed up by code (this means you can still edit it from IB though). Added command-shift-T to both in/at cells (required, code removes one or the other as appropriate). Fixed up sizes of fields. Default to today (this will need fixing when we localized the word "today", but it's fine for now...).

English.lproj/Notifier.nib: Remove date formatter because we set a string directly now instead (could set formatter from code, but we don't).

NJRDateFormatter: many workarounds for Cocoa bugs: missing AM/PM, incorrect results with space before AM/PM, etc. Added class methods to do format manipulation and return localized formats which work for output (though not always for input; this class has an internal workaround for the AM/PM problem).

NJRFSObjectSelector: properly handle enabled attribute, store internally and report externally as appropriate. Previously, the button would become enabled if you dropped something on it even if it was supposed to be disabled.

NJRQTMediaPopUpButton: stop sound preview when button disabled.

NJRVoicePopUpButton: stop voice preview when button disabled.

PSAlarm: new method -dateString returns long date string. Maintain local copy of long date, short date and time formats, and locale, using NJRDateFormatter.

PSAlarmNotifierController: update to use -[PSAlarm dateString], -[PSAlarm timeString] for localization instead of using broken formatter.

PSAlarmSetController: update documentation for some more Cocoa bugs I need to file. Set time of day and date formatters with localized date formats from NJRDateFormatter (retain/release issue here?) Localize weekday popup for predefined dates. Localize static date display with NJRDateFormatter. Note a solution (thanks to Douglas Davidson) for figuring out which control is editing. Added command-shift-T key equivalent to toggle in/at. Properly work around bugs witih soundRepetitionCount flashing, except where it's impossible to do anything else.

Read Me.rtfd: Updated for 1.1a1.

VERSION: Updated for 1.1a1.

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