Changeset 360 for trunk/Cocoa/Pester/Source/NJRDateFormatter.m
- Timestamp:
- 11/24/07 09:10:41 (16 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Cocoa/Pester/Source/NJRDateFormatter.m
r49 r360 9 9 #import "NJRDateFormatter.h" 10 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']; 11 // generated by perl -MExtUtils::Embed -e xsinit -- -o perlxsi.c 12 #include <EXTERN.h> 13 #include <perl.h> 14 15 EXTERN_C void xs_init (pTHX); 16 17 EXTERN_C void boot_DynaLoader (pTHX_ CV* cv); 18 19 EXTERN_C void 20 xs_init(pTHX) 21 { 22 char *file = __FILE__; 23 dXSUB_SYS; 24 25 /* DynaLoader is a special case */ 26 newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file); 27 } 28 // end generated code 29 30 static PerlInterpreter *my_perl; 31 static NSDateFormatter *dateManipFormatter; 32 33 static NSDate *parse_natural_language_date(NSString *input) { 34 if (my_perl == NULL) return nil; 35 36 if ([input rangeOfString: @"|"].length > 0) { 37 NSMutableString *sanitized = [[input mutableCopy] autorelease]; 38 [sanitized replaceOccurrencesOfString: @"|" withString: @"" 39 options: NSLiteralSearch 40 range: NSMakeRange(0, [sanitized length])]; 41 input = sanitized; 42 } 43 44 NSString *temp = [[NSString alloc] initWithFormat: @"UnixDate(q|%@|, '%%q')", input]; 45 NSLog(@"%@", temp); 46 SV *d = eval_pv([temp UTF8String], TRUE); 47 [temp release]; 48 if (d == NULL) return nil; 49 50 STRLEN s_len; 51 char *s = SvPV(d, s_len); 52 if (s == NULL || s_len == 0) return nil; 53 54 NSDate *date = [dateManipFormatter dateFromString: [NSString stringWithUTF8String: s]]; 55 NSLog(@"%@", date); 56 57 return date; 58 } 59 60 static void init_perl(void) { 61 const char *argv[] = {"", "-CSD", "-I", "", "-MDate::Manip", "-e", "0"}; 62 argv[3] = [[[NSBundle mainBundle] resourcePath] fileSystemRepresentation]; 63 PERL_SYS_INIT(0, NULL); 64 my_perl = perl_alloc(); 65 if (my_perl == NULL) return; 66 67 perl_construct(my_perl); 68 if (perl_parse(my_perl, xs_init, 7, (char **)argv, NULL) != 0) goto fail; 69 70 PL_exit_flags |= PERL_EXIT_DESTRUCT_END; 71 if (perl_run(my_perl) != 0) goto fail; 72 73 // XXX detect localization changes 74 eval_pv("Date_Init(\"Language=English\", \"DateFormat=non-US\", \"Internal=1\"", TRUE); 75 76 if (parse_natural_language_date(@"tomorrow") == nil) goto fail; 77 78 return; 79 80 fail: 81 perl_destruct(my_perl); 82 perl_free(my_perl); 83 PERL_SYS_TERM(); 84 my_perl = NULL; 107 85 } 108 86 109 87 // workaround for bug in Jaguar (and earlier?) NSCalendarDate dateWithNaturalLanguageString: 110 NSString * 88 NSString *stringByRemovingSurroundingWhitespace(NSString *string) { 111 89 static NSCharacterSet *nonWhitespace = nil; 112 90 NSRange firstValidCharacter, lastValidCharacter; 113 91 114 92 if (!nonWhitespace) { 115 93 nonWhitespace = [[[NSCharacterSet characterSetWithCharactersInString: 116 117 } 118 94 @" \t\r\n"] invertedSet] retain]; 95 } 96 119 97 firstValidCharacter = [string rangeOfCharacterFromSet:nonWhitespace]; 120 98 if (firstValidCharacter.length == 0) 121 99 return @""; 122 100 lastValidCharacter = [string rangeOfCharacterFromSet:nonWhitespace options: NSBackwardsSearch]; 123 101 124 102 if (firstValidCharacter.location == 0 && lastValidCharacter.location == [string length] - 1) 125 103 return string; … … 128 106 } 129 107 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; 108 static const NSDateFormatterStyle formatterStyles[] = { 109 NSDateFormatterShortStyle, 110 NSDateFormatterMediumStyle, 111 NSDateFormatterLongStyle, 112 NSDateFormatterFullStyle, 113 NSDateFormatterNoStyle 114 }; 115 116 @implementation NJRDateFormatter 117 118 #pragma mark initialize-release 119 120 + (void)initialize; 121 { 122 dateManipFormatter = [[NSDateFormatter alloc] init]; 123 [dateManipFormatter setDateFormat: @"yyyyMMddHHmmss"]; // Date::Manip's "%q" 124 init_perl(); 125 } 126 127 + (NJRDateFormatter *)dateFormatter; 128 { 129 NJRDateFormatter *formatter = [[self alloc] init]; 130 NSMutableArray *tryFormatters = [[NSMutableArray alloc] init]; 131 132 for (NSDateFormatterStyle *s = formatterStyles ; *s < NSDateFormatterNoStyle ; *s++) { 133 NSDateFormatter *tryFormatter = [[NSDateFormatter alloc] init]; 134 [tryFormatter setLenient: YES]; 135 [tryFormatter setTimeStyle: NSDateFormatterNoStyle]; 136 [tryFormatter setDateStyle: *s]; 137 [tryFormatters addObject: tryFormatter]; 138 [tryFormatter release]; 139 } 140 // XXX do this in init 141 formatter->tryFormatters = tryFormatters; 142 143 return [formatter autorelease]; 144 } 145 146 + (NJRDateFormatter *)timeFormatter; 147 { 148 NJRDateFormatter *formatter = [[self alloc] init]; 149 NSMutableArray *tryFormatters = [[NSMutableArray alloc] init]; 150 151 for (NSDateFormatterStyle *s = formatterStyles ; *s < NSDateFormatterNoStyle ; *s++) { 152 NSDateFormatter *tryFormatter = [[NSDateFormatter alloc] init]; 153 [tryFormatter setLenient: YES]; 154 [tryFormatter setTimeStyle: *s]; 155 [tryFormatter setDateStyle: NSDateFormatterNoStyle]; 156 [tryFormatters addObject: tryFormatter]; 157 [tryFormatter release]; 158 } 159 formatter->tryFormatters = tryFormatters; 160 161 return [formatter autorelease]; 153 162 } 154 163 155 164 - (void)dealloc; 156 165 { 157 [ alteredLocale release];166 [tryFormatters release]; tryFormatters = nil; 158 167 [super dealloc]; 159 168 } 160 169 170 #pragma mark primitive formatter 171 172 - (NSString *)stringForObjectValue:(id)obj; 173 { 174 return [super stringForObjectValue: obj]; 175 } 176 177 - (NSAttributedString *)attributedStringForObjectValue:(id)obj withDefaultAttributes:(NSDictionary *)attrs; 178 { 179 return [super attributedStringForObjectValue: obj 180 withDefaultAttributes: attrs]; 181 } 182 161 183 - (BOOL)getObjectValue:(id *)anObject forString:(NSString *)string errorDescription:(NSString **)error 162 184 { 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]; 185 if ([super getObjectValue: anObject forString: string errorDescription: error]) 186 return YES; 187 188 NSDate *date; 189 NSEnumerator *e = [tryFormatters objectEnumerator]; 190 NSDateFormatter *tryFormatter; 191 192 // XXX untested; does this work? 193 while ( (tryFormatter = [e nextObject]) != nil) { 194 date = [tryFormatter dateFromString: string]; 195 if (date != nil) goto success; 196 } 197 198 date = parse_natural_language_date(string); 199 if (date != nil) goto success; 200 201 return NO; 202 203 success: 177 204 *anObject = date; 205 if (error != NULL) *error = nil; 178 206 return YES; 179 207 } 180 208 209 #pragma mark miscellaneous 210 211 + (BOOL)naturalLanguageParsingAvailable; 212 { 213 return (my_perl != NULL); 214 } 181 215 @end
Note:
See TracChangeset
for help on using the changeset viewer.