Index: trunk/Cocoa/Pester/Source/NJRDateFormatter.m
===================================================================
--- trunk/Cocoa/Pester/Source/NJRDateFormatter.m	(revision 49)
+++ trunk/Cocoa/Pester/Source/NJRDateFormatter.m	(revision 360)
@@ -9,117 +9,95 @@
 #import "NJRDateFormatter.h"
 
-NSUserDefaults *locale;
-
-@implementation NJRDateFormatter
-
-+ (void)initialize;
-{
-    locale = [[NSUserDefaults standardUserDefaults] retain];
-}
-
-+ (NSString *)format:(NSString *)format withoutComponent:(unichar)component;
-{
-    NSScanner *scanner = [NSScanner scannerWithString: format];
-    int formatLength = [format length];
-    NSRange range;
-    [scanner setCharactersToBeSkipped: [NSCharacterSet characterSetWithCharactersInString: @""]];
-    // NSLog(@"format:withoutComponent: trying to excise %c from %@", component, format);
-    while ([scanner scanUpToString: @"%" intoString: nil] || ![scanner isAtEnd]) {
-        range.location = [scanner scanLocation];
-        // NSLog(@"location: %d/%d, remaining: %@%@", range.location, formatLength, [format substringFromIndex: range.location], [scanner isAtEnd] ? @", isAtEnd" : @"");
-        // 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*
-        if (range.location >= formatLength) break;
-        [scanner scanUpToCharactersFromSet: [NSCharacterSet letterCharacterSet] intoString: nil];
-        if ([format characterAtIndex: [scanner scanLocation]] == component) {
-            if ([scanner scanUpToString: @"%" intoString: nil] && ![scanner isAtEnd]) {
-                NSMutableString *mutableFormat = [format mutableCopy];
-                if (range.location != 0 && [[NSCharacterSet punctuationCharacterSet] characterIsMember: [format characterAtIndex: range.location - 1]]) {
-                    range.location--; // "%I:%M:%S%p" -> "%I:%M%p"
-                }
-                range.length = [scanner scanLocation] - range.location;
-                [mutableFormat deleteCharactersInRange: range];
-                format = [mutableFormat copy];
-                [mutableFormat release];
-                return [format autorelease];
-            } else {
-                range = [format rangeOfCharacterFromSet: [NSCharacterSet letterCharacterSet] options: NSBackwardsSearch range: NSMakeRange(0, range.location)];
-                return [format substringToIndex: NSMaxRange(range)];
-            }
-        }
-    }
-    return format;
-}
-
-+ (NSString *)localizedDateFormatIncludingWeekday:(BOOL)weekday;
-{
-    NSString *format = [locale stringForKey: NSDateFormatString];
-    if (weekday) return format;
-    return [self format: format withoutComponent: (unichar)'A'];
-}
-
-+ (NSString *)localizedShortDateFormatIncludingWeekday:(BOOL)weekday;
-{
-    NSString *format = [locale stringForKey: NSShortDateFormatString];
-    if (weekday) return format;
-    return [self format: format withoutComponent: (unichar)'A'];
-}
-
-NSString *stringByInsertingStringAtLocation(NSString *string, NSString *insert, int location) {
-    return [NSString stringWithFormat: @"%@%@%@", [string substringToIndex: location], insert,
-        [string substringFromIndex: location]];
-}
-
-+ (NSString *)localizedTimeFormatIncludingSeconds:(BOOL)seconds;
-{
-    NSString *format = [locale stringForKey: NSTimeFormatString];
-    NSArray *ampm = [locale arrayForKey: NSAMPMDesignation];
-    NSString *am = [ampm objectAtIndex: 0], *pm = [ampm objectAtIndex: 1];
-    // work around bug with inconsistent AM/PM and time format
-    if ([am isEqualToString: @""] && [pm isEqualToString: @""])
-        format = [self format: format withoutComponent: 'p'];
-    else {
-        NSRange ampmComponentRange = [format rangeOfString: @"%p"];
-        NSCharacterSet *whitespace = [NSCharacterSet whitespaceCharacterSet];
-        BOOL needSpaceInFormatString = ![whitespace characterIsMember: [am characterAtIndex: 0]];
-        if (ampmComponentRange.location == NSNotFound) // "%1I:%M:%S" -> "%1I:%M:%S%p", "%1I:%M:%S %p"
-            format = [format stringByAppendingString: (needSpaceInFormatString ? @" %p" : @"%p")];
-        else {
-            NSRange whitespaceRange = [format rangeOfCharacterFromSet: whitespace options: NSBackwardsSearch range: NSMakeRange(0, ampmComponentRange.location)];
-            if (whitespaceRange.location == NSNotFound) {
-                if (needSpaceInFormatString) // "%1I:%M:%S%p" -> "%1I:%M:%S %p"
-                    format = stringByInsertingStringAtLocation(format, @" ", ampmComponentRange.location);
-                // else "%1I:%M:%S%p" -> no change
-            } else {
-                if (NSMaxRange(whitespaceRange) == ampmComponentRange.location) { 
-                    if (!needSpaceInFormatString) // "%1I:%M:%S %p" -> "%1I:%M:%S%p"
-                        format = [[format substringToIndex: whitespaceRange.location] stringByAppendingString: [format substringFromIndex: ampmComponentRange.location]];
-                    // else "%1I:%M:%S %p" -> no change
-                } else {
-                    if (needSpaceInFormatString)
-                        format = stringByInsertingStringAtLocation(format, @" ", ampmComponentRange.location);
-                    // else "%1I %M:%S%p" -> no change
-                }
-            }
-        }
-    }
-    if (seconds) return format;
-    return [self format: format withoutComponent: 'S'];
+// generated by perl -MExtUtils::Embed -e xsinit -- -o perlxsi.c
+#include <EXTERN.h>
+#include <perl.h>
+
+EXTERN_C void xs_init (pTHX);
+
+EXTERN_C void boot_DynaLoader (pTHX_ CV* cv);
+
+EXTERN_C void
+xs_init(pTHX)
+{
+    char *file = __FILE__;
+    dXSUB_SYS;
+    
+    /* DynaLoader is a special case */
+    newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file);
+}
+// end generated code
+
+static PerlInterpreter *my_perl;
+static NSDateFormatter *dateManipFormatter;
+
+static NSDate *parse_natural_language_date(NSString *input) {
+    if (my_perl == NULL) return nil;
+    
+    if ([input rangeOfString: @"|"].length > 0) {
+	NSMutableString *sanitized = [[input mutableCopy] autorelease];
+	[sanitized replaceOccurrencesOfString: @"|" withString: @""
+				      options: NSLiteralSearch
+					range: NSMakeRange(0, [sanitized length])];
+	input = sanitized;
+    }
+    
+    NSString *temp = [[NSString alloc] initWithFormat: @"UnixDate(q|%@|, '%%q')", input];
+    NSLog(@"%@", temp);
+    SV *d = eval_pv([temp UTF8String], TRUE);
+    [temp release];
+    if (d == NULL) return nil;
+    
+    STRLEN s_len;
+    char *s = SvPV(d, s_len);
+    if (s == NULL || s_len == 0) return nil;
+    
+    NSDate *date = [dateManipFormatter dateFromString: [NSString stringWithUTF8String: s]];
+    NSLog(@"%@", date);
+    
+    return date;
+}
+
+static void init_perl(void) {
+    const char *argv[] = {"", "-CSD", "-I", "", "-MDate::Manip", "-e", "0"};
+    argv[3] = [[[NSBundle mainBundle] resourcePath] fileSystemRepresentation];
+    PERL_SYS_INIT(0, NULL);
+    my_perl = perl_alloc();
+    if (my_perl == NULL) return;
+    
+    perl_construct(my_perl);
+    if (perl_parse(my_perl, xs_init, 7, (char **)argv, NULL) != 0) goto fail;
+    
+    PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
+    if (perl_run(my_perl) != 0) goto fail;
+    
+    // XXX detect localization changes
+    eval_pv("Date_Init(\"Language=English\", \"DateFormat=non-US\", \"Internal=1\"", TRUE);
+
+    if (parse_natural_language_date(@"tomorrow") == nil) goto fail;
+    
+    return;
+    
+fail:
+    perl_destruct(my_perl);
+    perl_free(my_perl);
+    PERL_SYS_TERM();
+    my_perl = NULL;
 }
 
 // workaround for bug in Jaguar (and earlier?) NSCalendarDate dateWithNaturalLanguageString:
-NSString * stringByRemovingSurroundingWhitespace(NSString *string) {
+NSString *stringByRemovingSurroundingWhitespace(NSString *string) {
     static NSCharacterSet *nonWhitespace = nil;
     NSRange firstValidCharacter, lastValidCharacter;
-
+    
     if (!nonWhitespace) {
         nonWhitespace = [[[NSCharacterSet characterSetWithCharactersInString:
-            @" \t\r\n"] invertedSet] retain];
-    }
-
+			   @" \t\r\n"] invertedSet] retain];
+    }
+    
     firstValidCharacter = [string rangeOfCharacterFromSet:nonWhitespace];
     if (firstValidCharacter.length == 0)
         return @"";
     lastValidCharacter = [string rangeOfCharacterFromSet:nonWhitespace options: NSBackwardsSearch];
-
+    
     if (firstValidCharacter.location == 0 && lastValidCharacter.location == [string length] - 1)
         return string;
@@ -128,54 +106,110 @@
 }
 
-- (id)initWithDateFormat:(NSString *)format allowNaturalLanguage:(BOOL)flag;
-{
-    if ( (self = [super initWithDateFormat: format allowNaturalLanguage: flag]) != nil) {
-        NSRange ampmRange = [format rangeOfString: @"%p"];
-        NSArray *ampm = [locale arrayForKey: NSAMPMDesignation];
-        NSString *am = [ampm objectAtIndex: 0], *pm = [ampm objectAtIndex: 1];
-        if (flag && ampmRange.location != NSNotFound &&
-            [[locale stringForKey: NSTimeFormatString] rangeOfString: @"%p"].location == NSNotFound && ![am isEqualToString: pm]) {
-            // 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)
-            NSMutableString *paddedFormat = [format mutableCopy];
-            [paddedFormat replaceCharactersInRange: ampmRange withString: @" %p"];
-            alteredLocale = [[locale dictionaryRepresentation] mutableCopy];
-            [(NSMutableDictionary *)alteredLocale setObject: paddedFormat forKey: NSTimeFormatString];
-            [(NSMutableDictionary *)alteredLocale setObject:
-                [NSArray arrayWithObjects: stringByRemovingSurroundingWhitespace(am),
-                    stringByRemovingSurroundingWhitespace(pm), nil]
-                                                     forKey: NSAMPMDesignation];
-            [paddedFormat release];
-        } else {
-            alteredLocale = [(NSDictionary *)locale retain];
-        }
-    }
-    return self;
+static const NSDateFormatterStyle formatterStyles[] = {
+    NSDateFormatterShortStyle,
+    NSDateFormatterMediumStyle,
+    NSDateFormatterLongStyle,
+    NSDateFormatterFullStyle,
+    NSDateFormatterNoStyle
+};
+
+@implementation NJRDateFormatter
+
+#pragma mark initialize-release
+
++ (void)initialize;
+{
+    dateManipFormatter = [[NSDateFormatter alloc] init];
+    [dateManipFormatter setDateFormat: @"yyyyMMddHHmmss"]; // Date::Manip's "%q"
+    init_perl();
+}
+
++ (NJRDateFormatter *)dateFormatter;
+{
+    NJRDateFormatter *formatter = [[self alloc] init];
+    NSMutableArray *tryFormatters = [[NSMutableArray alloc] init];
+    
+    for (NSDateFormatterStyle *s = formatterStyles ; *s < NSDateFormatterNoStyle ; *s++) {
+	NSDateFormatter *tryFormatter = [[NSDateFormatter alloc] init];
+	[tryFormatter setLenient: YES];
+	[tryFormatter setTimeStyle: NSDateFormatterNoStyle];
+	[tryFormatter setDateStyle: *s];
+	[tryFormatters addObject: tryFormatter];
+	[tryFormatter release];
+    }
+    // XXX do this in init
+    formatter->tryFormatters = tryFormatters;
+
+    return [formatter autorelease];
+}
+
++ (NJRDateFormatter *)timeFormatter;
+{
+    NJRDateFormatter *formatter = [[self alloc] init];
+    NSMutableArray *tryFormatters = [[NSMutableArray alloc] init];
+    
+    for (NSDateFormatterStyle *s = formatterStyles ; *s < NSDateFormatterNoStyle ; *s++) {
+	NSDateFormatter *tryFormatter = [[NSDateFormatter alloc] init];
+	[tryFormatter setLenient: YES];
+	[tryFormatter setTimeStyle: *s];
+	[tryFormatter setDateStyle: NSDateFormatterNoStyle];
+	[tryFormatters addObject: tryFormatter];
+	[tryFormatter release];
+    }
+    formatter->tryFormatters = tryFormatters;
+    
+    return [formatter autorelease];
 }
 
 - (void)dealloc;
 {
-    [alteredLocale release];
+    [tryFormatters release]; tryFormatters = nil;
     [super dealloc];
 }
 
+#pragma mark primitive formatter 
+
+- (NSString *)stringForObjectValue:(id)obj;
+{
+    return [super stringForObjectValue: obj];
+}
+
+- (NSAttributedString *)attributedStringForObjectValue:(id)obj withDefaultAttributes:(NSDictionary *)attrs;
+{
+    return [super attributedStringForObjectValue: obj
+			   withDefaultAttributes: attrs];
+}
+
 - (BOOL)getObjectValue:(id *)anObject forString:(NSString *)string errorDescription:(NSString **)error
 {
-    NSCalendarDate *date;
-    if (![self allowsNaturalLanguage])
-        return [super getObjectValue: anObject forString: string errorDescription: error];
-    if (string == nil) return nil;
-    NS_DURING // dateWithNaturalLanguageString: can throw an exception
-        date = [NSCalendarDate dateWithNaturalLanguageString: stringByRemovingSurroundingWhitespace(string) locale: alteredLocale];
-        // NSLog(@"%@: natural language date is %@", string, date);
-    NS_HANDLER
-        if (error != nil) *error = [localException reason];
-        NS_VALUERETURN(NO, BOOL);
-    NS_ENDHANDLER
-    // [super getObjectValue: anObject forString: string errorDescription: error];
-    // NSLog(@"%@: formatter date is %@", string, anObject == nil ? @"(null)" : *anObject);
-    if (date == nil) return [super getObjectValue: anObject forString: string errorDescription: error];
+    if ([super getObjectValue: anObject forString: string errorDescription: error])
+	return YES;
+	
+    NSDate *date;
+    NSEnumerator *e = [tryFormatters objectEnumerator];
+    NSDateFormatter *tryFormatter;
+    
+    // XXX untested; does this work?
+    while ( (tryFormatter = [e nextObject]) != nil) {
+	date = [tryFormatter dateFromString: string];
+	if (date != nil) goto success;
+    }
+    
+    date = parse_natural_language_date(string);
+    if (date != nil) goto success;
+    
+    return NO;
+
+success:
     *anObject = date;
+    if (error != NULL) *error = nil;
     return YES;
 }
 
+#pragma mark miscellaneous
+
++ (BOOL)naturalLanguageParsingAvailable;
+{
+    return (my_perl != NULL);
+}
 @end
