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

Last change on this file since 43 was 43, checked in by Nicholas Riley, 20 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
RevLine 
[21]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
[43]11NSUserDefaults *locale;
[21]12
13@implementation NJRDateFormatter
14
[43]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
[21]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 @"";
[43]117    lastValidCharacter = [string rangeOfCharacterFromSet:nonWhitespace options: NSBackwardsSearch];
[21]118
119    if (firstValidCharacter.location == 0 && lastValidCharacter.location == [string length] - 1)
120        return string;
121    else
[43]122        return [string substringWithRange: NSUnionRange(firstValidCharacter, lastValidCharacter)];
[21]123}
124
[43]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}
[21]148
[43]149- (void)dealloc;
150{
151    [alteredLocale release];
152    [super dealloc];
153}
154
[21]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
[43]162        date = [NSCalendarDate dateWithNaturalLanguageString: stringByRemovingSurroundingWhitespace(string) locale: alteredLocale];
163        // NSLog(@"%@: natural language date is %@", string, date);
[21]164    NS_HANDLER
165        if (error != nil) *error = [localException reason];
166        NS_VALUERETURN(NO, BOOL);
167    NS_ENDHANDLER
[43]168    // [super getObjectValue: anObject forString: string errorDescription: error];
169    // NSLog(@"%@: formatter date is %@", string, anObject == nil ? @"(null)" : *anObject);
[21]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.