/* * CPDateFormatter.j * Foundation * * Created by Alexander Ljungberg. * Copyright 2012, SlevenBits Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @import "CPArray.j" @import "CPDate.j" @import "CPString.j" @import "CPFormatter.j" @import "CPTimeZone.j" @import "CPLocale.j" @class CPNull @class CPDictionary @global CPLocaleLanguageCode @global CPLocaleCountryCode @typedef CPDateFormatterStyle CPDateFormatterNoStyle = 0; CPDateFormatterShortStyle = 1; CPDateFormatterMediumStyle = 2; CPDateFormatterLongStyle = 3; CPDateFormatterFullStyle = 4; @typedef CPDateFormatterBehavior CPDateFormatterBehaviorDefault = 0; CPDateFormatterBehavior10_0 = 1000; CPDateFormatterBehavior10_4 = 1040; var defaultDateFormatterBehavior = CPDateFormatterBehavior10_4, relativeDateFormating, patternStringTokens; var _separatorsCharacterSet = nil; /*! @ingroup foundation @class CPDateFormatter CPDateFormatter takes a CPDate value and formats it as text for display. It also supports the converse, taking text and interpreting it as a CPDate by configurable formatting rules. */ @implementation CPDateFormatter : CPFormatter { BOOL _allowNaturalLanguage @accessors(property=allowNaturalLanguage, readonly); BOOL _doesRelativeDateFormatting @accessors(property=doesRelativeDateFormatting); CPDate _defaultDate @accessors(property=defaultDate); CPDate _twoDigitStartDate @accessors(property=twoDigitStartDate); CPDateFormatterBehavior _formatterBehavior @accessors(property=formatterBehavior); CPDateFormatterStyle _dateStyle @accessors(property=dateStyle); CPDateFormatterStyle _timeStyle @accessors(property=timeStyle); CPLocale _locale @accessors(property=locale); CPString _AMSymbol @accessors(property=AMSymbol); CPString _dateFormat @accessors(property=dateFormat); CPString _PMSymbol @accessors(property=PMSymbol); CPTimeZone _timeZone @accessors(property=timeZone); CPDictionary _symbols; } + (void)initialize { if (self !== [CPDateFormatter class]) return; relativeDateFormating = @{ @"fr" : [@"demain", 1, @"apr" + String.fromCharCode(233) + @"s-demain", 2, @"apr" + String.fromCharCode(233) + @"s-apr" + String.fromCharCode(233) + @"s-demain", 3, @"hier", -1, @"avant-hier", -2, @"avant-avant-hier", -3], @"en" : [@"tomorrow", 1, @"yesterday", -1], @"de" : [@"morgen", 1, @"gestern", -1, String.fromCharCode(129) + @"bermorgen", 2, @"vorgestern", -2], @"es" : [] }; patternStringTokens = [@"QQQ", @"qqq", @"QQQQ", @"qqqq", @"MMM", @"MMMM", @"LLL", @"LLLL", @"E", @"EE", @"EEE", @"eee", @"eeee", @"eeeee", @"a", @"z", @"zz", @"zzz", @"zzzz", @"Z", @"ZZ", @"ZZZ", @"ZZZZ", @"ZZZZZ", @"v", @"vv", @"vvv", @"vvvv", @"V", @"VV", @"VVV", @"VVVV"]; } /*! Return a string representation of the given date, dateStyle and timeStyle @param date the given date @param dateStyle the dateStyle @param timeStyle the timeStyle @return a CPString reprensenting the given date */ + (CPString)localizedStringFromDate:(CPDate)date dateStyle:(CPDateFormatterStyle)dateStyle timeStyle:(CPDateFormatterStyle)timeStyle { var formatter = [[CPDateFormatter alloc] init]; [formatter setFormatterBehavior:CPDateFormatterBehavior10_4]; [formatter setDateStyle:dateStyle]; [formatter setTimeStyle:timeStyle]; return [formatter stringForObjectValue:date]; } /*! Not yet implemented Return a string representation of the given template, opts and locale @param template the template @param opts, pass 0 @param locale the locale @return a CPString representing the givent template */ + (CPString)dateFormatFromTemplate:(CPString)template options:(CPUInteger)opts locale:(CPLocale)locale { // TODO : check every template from cocoa and return a good format (have fun ^^) } /*! Return the defaultFormatterBehavior @return a CPDateFormatterBehavior */ + (CPDateFormatterBehavior)defaultFormatterBehavior { return defaultDateFormatterBehavior; } /*! Set the defaultFormatterBehavior @param behavior */ + (void)setDefaultFormatterBehavior:(CPDateFormatterBehavior)behavior { defaultDateFormatterBehavior = behavior; } + (CPCharacterSet)_separatorsCharacterSet { if (_separatorsCharacterSet == nil) _separatorsCharacterSet = [CPCharacterSet characterSetWithCharactersInString:@" ,:/-."]; return _separatorsCharacterSet; } /*! Init a dateFormatter @return a new CPDateFormatter */ - (id)init { if (self = [super init]) { _dateStyle = nil; _timeStyle = nil; [self _init]; } return self; } /*! Init a dateFormatter with a format and the naturalLanguage @param format the format @param flag flag representation of allowNaturalLanguage @return a new CPDateFormatter */ - (id)initWithDateFormat:(CPString)format allowNaturalLanguage:(BOOL)flag { if (self = [self init]) { _dateFormat = format; _allowNaturalLanguage = flag; } return self } /*! Private init */ - (void)_init { var AMSymbol = [CPString stringWithFormat:@"%s", @"AM"], PMSymbol = [CPString stringWithFormat:@"%s", @"PM"], weekdaySymbols = [CPArray arrayWithObjects:@"Sunday", @"Monday", @"Tuesday", @"Wednesday", @"Thursday", @"Friday", @"Saturday"], shortWeekdaySymbols = [CPArray arrayWithObjects:@"Sun", @"Mon", @"Tue", @"Wed", @"Thu", @"Fri", @"Sat"], veryShortWeekdaySymbols = [CPArray arrayWithObjects:@"S", @"M", @"T", @"W", @"T", @"F", @"S"], standaloneWeekdaySymbols = [CPArray arrayWithObjects:@"Sunday", @"Monday", @"Tuesday", @"Wednesday", @"Thursday", @"Friday", @"Saturday"], shortStandaloneWeekdaySymbols = [CPArray arrayWithObjects:@"Sun", @"Mon", @"Tue", @"Wed", @"Thu", @"Fri", @"Sat"], veryShortStandaloneWeekdaySymbols = [CPArray arrayWithObjects:@"S", @"M", @"T", @"W", @"T", @"F", @"S"], monthSymbols = [CPArray arrayWithObjects:@"January", @"February", @"March", @"April", @"May", @"June", @"July", @"August", @"September", @"October", @"November", @"December"], shortMonthSymbols = [CPArray arrayWithObjects:@"Jan", @"Feb", @"Mar", @"Apr", @"May", @"Jun", @"Jul", @"Aug", @"Sep", @"Oct", @"Nov", @"Dec"], veryShortMonthSymbols = [CPArray arrayWithObjects:@"J", @"F", @"M", @"A", @"M", @"J", @"J", @"A", @"S", @"O", @"N", @"D"], standaloneMonthSymbols = [CPArray arrayWithObjects:@"January", @"February", @"March", @"April", @"May", @"June", @"July", @"August", @"September", @"October", @"November", @"December"], shortStandaloneMonthSymbols = [CPArray arrayWithObjects:@"Jan", @"Feb", @"Mar", @"Apr", @"May", @"Jun", @"Jul", @"Aug", @"Sep", @"Oct", @"Nov", @"Dec"], veryShortStandaloneMonthSymbols = [CPArray arrayWithObjects:@"J", @"F", @"M", @"A", @"M", @"J", @"J", @"A", @"S", @"O", @"N", @"D"], quarterSymbols = [CPArray arrayWithObjects:@"1st quarter", @"2nd quarter", @"3rd quarter", @"4th quarter"], shortQuarterSymbols = [CPArray arrayWithObjects:@"Q1", @"Q2", @"Q3", @"Q4"], standaloneQuarterSymbols = [CPArray arrayWithObjects:@"1st quarter", @"2nd quarter", @"3rd quarter", @"4th quarter"], shortStandaloneQuarterSymbols = [CPArray arrayWithObjects:@"Q1", @"Q2", @"Q3", @"Q4"]; _symbols = @{ @"root" : @{ @"AMSymbol" : AMSymbol, @"PMSymbol" : PMSymbol, @"weekdaySymbols" : weekdaySymbols, @"shortWeekdaySymbols" : shortWeekdaySymbols, @"veryShortWeekdaySymbols" : veryShortWeekdaySymbols, @"standaloneWeekdaySymbols" : standaloneWeekdaySymbols, @"shortStandaloneWeekdaySymbols" : shortStandaloneWeekdaySymbols, @"veryShortStandaloneWeekdaySymbols" : veryShortStandaloneWeekdaySymbols, @"monthSymbols" : monthSymbols, @"shortMonthSymbols" : shortMonthSymbols, @"veryShortMonthSymbols" : veryShortMonthSymbols, @"standaloneMonthSymbols" : standaloneMonthSymbols, @"shortStandaloneMonthSymbols" : shortStandaloneMonthSymbols, @"veryShortStandaloneMonthSymbols" : veryShortStandaloneMonthSymbols, @"quarterSymbols" : quarterSymbols, @"shortQuarterSymbols" : shortQuarterSymbols, @"standaloneQuarterSymbols" : standaloneQuarterSymbols, @"shortStandaloneQuarterSymbols" : shortStandaloneQuarterSymbols } }; _timeZone = [CPTimeZone systemTimeZone]; _twoDigitStartDate = [[CPDate alloc] initWithString:@"1950-01-01 00:00:00 +0000"]; _locale = [CPLocale currentLocale]; } #pragma mark - #pragma mark Setter Getter Helper /*! Return symbols for the given language code */ - (CPDictionary)symbolsForLanguageCode:(CPString)languageCode { var languageSymbols = [_symbols valueForKey:languageCode]; if (!languageSymbols) { languageSymbols = [self symbolsForLanguageCode:@"root"]; [self setSymbols:languageSymbols forLanguageCode:languageCode]; } return languageSymbols; } /*! Set the symbols for the given language code */ - (void)setSymbols:(CPDictionary)symbols forLanguageCode:(CPString)languageCode { [_symbols setValue:symbols forKey:languageCode]; } /*! Return a symbol specified by the given key for the given language code */ - (id)symbolForKey:(CPString)aKey languageCode:(CPString)languageCode { var languageSymbols = [self symbolsForLanguageCode:languageCode], symbol = [languageSymbols valueForKey:aKey]; if (!symbol) { symbol = [self symbolForKey:aKey languageCode:@"root"]; [self setSymbol:symbol forKey:aKey languageCode:languageCode]; } return symbol; } /*! Set the symbol specified by the given key for the given language code */ - (void)setSymbol:(CPString)aSymbol forKey:(CPString)aKey languageCode:(CPString)languageCode { var languageSymbols = [self symbolsForLanguageCode:languageCode]; [languageSymbols setValue:aSymbol forKey:aKey]; } #pragma mark - #pragma mark Setter Getter /*! Return AMSymbol */ - (CPString)AMSymbol { return [self symbolForKey:@"AMSymbol" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Set the AMSymbol */ - (void)setAMSymbol:(CPString)aValue { [self setSymbol:aValue forKey:@"AMSymbol" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Return a PMSymbol */ - (CPString)PMSymbol { return [self symbolForKey:@"PMSymbol" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Set the PMSymbol */ - (void)setPMSymbol:(CPString)aValue { [self setSymbol:aValue forKey:@"PMSymbol" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Return the weekdaySymbols */ - (CPArray)weekdaySymbols { return [self symbolForKey:@"weekdaySymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Set the weekdaySymbols */ - (void)setWeekdaySymbols:(CPArray)aValue { [self setSymbol:aValue forKey:@"weekdaySymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Return a shortWeekdaySymbols */ - (CPArray)shortWeekdaySymbols { return [self symbolForKey:@"shortWeekdaySymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Set the shortWeekdaySymbols */ - (void)setShortWeekdaySymbols:(CPArray)aValue { [self setSymbol:aValue forKey:@"shortWeekdaySymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Return veryShortWeekdaySymbols */ - (CPArray)veryShortWeekdaySymbols { return [self symbolForKey:@"veryShortWeekdaySymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Set the veryShortWeekdaySymbols */ - (void)setVeryShortWeekdaySymbols:(CPArray)aValue { [self setSymbol:aValue forKey:@"veryShortWeekdaySymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Return the standaloneWeekdaySymbols */ - (CPArray)standaloneWeekdaySymbols { return [self symbolForKey:@"standaloneWeekdaySymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Set the standaloneWeekdaySymbols */ - (void)setStandaloneWeekdaySymbols:(CPArray)aValue { [self setSymbol:aValue forKey:@"standaloneWeekdaySymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Return the shortStandaloneWeekdaySymbols */ - (CPArray)shortStandaloneWeekdaySymbols { return [self symbolForKey:@"shortStandaloneWeekdaySymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Set the shortStandaloneWeekdaySymbols */ - (void)setShortStandaloneWeekdaySymbols:(CPArray)aValue { [self setSymbol:aValue forKey:@"shortStandaloneWeekdaySymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Return the veryShortStandaloneWeekdaySymbols */ - (CPArray)veryShortStandaloneWeekdaySymbols { return [self symbolForKey:@"veryShortStandaloneWeekdaySymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Set the veryShortStandaloneWeekdaySymbols */ - (void)setVeryShortStandaloneWeekdaySymbols:(CPArray)aValue { [self setSymbol:aValue forKey:@"veryShortStandaloneWeekdaySymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Return the monthSymbols */ - (CPArray)monthSymbols { return [self symbolForKey:@"monthSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Set the monthSymbols */ - (void)setMonthSymbols:(CPArray)aValue { [self setSymbol:aValue forKey:@"monthSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Return a shortMonthSymbols */ - (CPArray)shortMonthSymbols { return [self symbolForKey:@"shortMonthSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Set the shortMonthSymbols */ - (void)setShortMonthSymbols:(CPArray)aValue { [self setSymbol:aValue forKey:@"shortMonthSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Return veryShortMonthSymbols */ - (CPArray)veryShortMonthSymbols { return [self symbolForKey:@"veryShortMonthSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Set the veryShortMonthSymbols */ - (void)setVeryShortMonthSymbols:(CPArray)aValue { [self setSymbol:aValue forKey:@"veryShortMonthSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Return standaloneMonthSymbols */ - (CPArray)standaloneMonthSymbols { return [self symbolForKey:@"standaloneMonthSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Set the standaloneMonthSymbols */ - (void)setStandaloneMonthSymbols:(CPArray)aValue { [self setSymbol:aValue forKey:@"standaloneMonthSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Return the shortStandaloneMonthSymbols */ - (CPArray)shortStandaloneMonthSymbols { return [self symbolForKey:@"shortStandaloneMonthSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Set the shortStandaloneMonthSymbols */ - (void)setShortStandaloneMonthSymbols:(CPArray)aValue { [self setSymbol:aValue forKey:@"shortStandaloneMonthSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Return the veryShortStandaloneMonthSymbols */ - (CPArray)veryShortStandaloneMonthSymbols { return [self symbolForKey:@"veryShortStandaloneMonthSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Set the veryShortStandaloneMonthSymbols */ - (void)setVeryShortStandaloneMonthSymbols:(CPArray)aValue { [self setSymbol:aValue forKey:@"veryShortStandaloneMonthSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Return the quarterSymbols */ - (CPArray)quarterSymbols { return [self symbolForKey:@"quarterSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Set the quarterSymbols */ - (void)setQuarterSymbols:(CPArray)aValue { [self setSymbol:aValue forKey:@"quarterSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Return the shortQuarterSymbols */ - (CPArray)shortQuarterSymbols { return [self symbolForKey:@"shortQuarterSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Set the shortQuarterSymbols */ - (void)setShortQuarterSymbols:(CPArray)aValue { [self setSymbol:aValue forKey:@"shortQuarterSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Return the standaloneQuarterSymbols */ - (CPArray)standaloneQuarterSymbols { return [self symbolForKey:@"standaloneQuarterSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Set the standaloneQuarterSymbols */ - (void)setStandaloneQuarterSymbols:(CPArray)aValue { [self setSymbol:aValue forKey:@"standaloneQuarterSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Return the shortStandaloneQuarterSymbols */ - (CPArray)shortStandaloneQuarterSymbols { return [self symbolForKey:@"shortStandaloneQuarterSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } /*! Set the shortStandaloneQuarterSymbols */ - (void)setShortStandaloneQuarterSymbols:(CPArray)aValue { [self setSymbol:aValue forKey:@"shortStandaloneQuarterSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]]; } #pragma mark - #pragma mark StringFromDate methods /*! Return a string representation of a given date. This method returns (if possible) a representation of the given date with the dateFormat of the CPDateFormatter, otherwise it takes the dateStyle and timeStyle @param aDate the given date @return CPString the string representation */ - (CPString)stringFromDate:(CPDate)aDate { var format, relativeWord, result; if (!aDate) return; aDate = [aDate copy]; [aDate _dateWithTimeZone:_timeZone]; if (_dateFormat) return [self _stringFromDate:aDate format:_dateFormat]; switch (_dateStyle) { case CPDateFormatterNoStyle: format = @""; break; case CPDateFormatterShortStyle: if ([self _isAmericanFormat]) format = @"M/d/yy"; else { if ([_locale objectForKey:CPLocaleLanguageCode] === 'de') format = @"dd.MM.yy"; else format = @"dd/MM/yy"; } break; case CPDateFormatterMediumStyle: if ([self _isAmericanFormat]) format = @"MMM d, Y"; else format = @"d MMM Y"; break; case CPDateFormatterLongStyle: if ([self _isAmericanFormat]) format = @"MMMM d, Y"; else format = @"d MMMM Y"; break; case CPDateFormatterFullStyle: if ([self _isAmericanFormat]) format = @"EEEE, MMMM d, Y"; else format = @"EEEE d MMMM Y"; break; default: format = @""; } if ([self doesRelativeDateFormatting]) { var language = [_locale objectForKey:CPLocaleLanguageCode], relativeWords = [relativeDateFormating valueForKey:language]; for (var i = 1; i < [relativeWords count]; i = i + 2) { var date = [CPDate date]; [date _dateWithTimeZone:_timeZone]; date.setHours(12); date.setMinutes(0); date.setSeconds(0); date.setDate([relativeWords objectAtIndex:i] + date.getDate()); if (date.getDate() == aDate.getDate() && date.getMonth() == aDate.getMonth() && date.getFullYear() == aDate.getFullYear()) { relativeWord = [relativeWords objectAtIndex:(i - 1)]; format = @""; break; } } } if ((relativeWord || format.length) && _timeStyle != CPDateFormatterNoStyle) format += @" "; switch (_timeStyle) { case CPDateFormatterNoStyle: format += @""; break; case CPDateFormatterShortStyle: if ([self _isEnglishFormat]) format += @"h:mm a"; else format += @"H:mm"; break; case CPDateFormatterMediumStyle: if ([self _isEnglishFormat]) format += @"h:mm:ss a"; else format += @"H:mm:ss"; break; case CPDateFormatterLongStyle: if ([self _isEnglishFormat]) format += @"h:mm:ss a z"; else format += @"H:mm:ss z"; break; case CPDateFormatterFullStyle: if ([self _isEnglishFormat]) format += @"h:mm:ss a zzzz"; else format += @"h:mm:ss zzzz"; break; default: format += @""; } result = [self _stringFromDate:aDate format:format]; if (relativeWord) result = relativeWord + result; return result; } /*! Return a string representation of the given objectValue. This method call the method stringFromDate if possible, otherwise it returns the description of the object @param anObject @return a string */ - (CPString)stringForObjectValue:(id)anObject { if ([anObject isKindOfClass:[CPDate class]]) return [self stringFromDate:anObject]; else return nil; } /*! Return a string This method call the method stringForObjectValue @param anObject @return a string */ - (CPString)editingStringForObjectValue:(id)anObject { return [self stringForObjectValue:anObject]; } /*! Return a string representation of the given date and format @patam aDate @param aFormat @return a string */ - (CPString)_stringFromDate:(CPDate)aDate format:(CPString)aFormat { var length = [aFormat length], currentToken = [CPString new], isTextToken = NO, result = [CPString new]; for (var i = 0; i < length; i++) { var character = [aFormat characterAtIndex:i]; if (isTextToken) { if ([character isEqualToString:@"'"]) { isTextToken = NO; result += currentToken; currentToken = [CPString new]; } else { currentToken += character; } continue; } if ([character isEqualToString:@"'"]) { if (!isTextToken) { isTextToken = YES; result += currentToken; currentToken = [CPString new]; } continue; } if ([[CPDateFormatter _separatorsCharacterSet] characterIsMember:character]) { result += [self _stringFromToken:currentToken date:aDate]; result += character; currentToken = [CPString new]; } else { if ([currentToken length] && ![[currentToken characterAtIndex:0] isEqualToString:character]) { result += [self _stringFromToken:currentToken date:aDate]; currentToken = [CPString new]; } currentToken += character; if (i == (length - 1)) result += [self _stringFromToken:currentToken date:aDate]; } } return result; } /*! Return a string representation of the given token and date @param aToken @param aDate @return a string */ - (CPString)_stringFromToken:(CPString)aToken date:(CPDate)aDate { if (![aToken length]) return aToken; var character = [aToken characterAtIndex:0], length = [aToken length], timeZone = _timeZone; switch (character) { case @"G": // TODO CPLog.warn(@"Token not yet implemented " + aToken); return [CPString new]; case @"y": var currentLength = [[CPString stringWithFormat:@"%i", aDate.getFullYear()] length]; return [self _stringValueForValue:aDate.getFullYear() length:(length == 2)?length:currentLength]; case @"Y": var currentLength = [[CPString stringWithFormat:@"%i", aDate.getFullYear()] length]; return [self _stringValueForValue:aDate.getFullYear() length:(length == 2)?length:currentLength]; case @"u": // TODO CPLog.warn(@"Token not yet implemented " + aToken); return [CPString new]; case @"U": // TODO CPLog.warn(@"Token not yet implemented " + aToken); return [CPString new]; case @"Q": var quarter = 1; if (aDate.getMonth() < 6 && aDate.getMonth() > 2) quarter = 2; if (aDate.getMonth() > 5 && aDate.getMonth() < 9) quarter = 3; if (aDate.getMonth() >= 9) quarter = 4; if (length <= 2) return [self _stringValueForValue:quarter length:MIN(2,length)]; if (length == 3) return [[self shortQuarterSymbols] objectAtIndex:(quarter - 1)]; if (length >= 4) return [[self quarterSymbols] objectAtIndex:(quarter - 1)]; case @"q": var quarter = 1; if (aDate.getMonth() < 6 && aDate.getMonth() > 2) quarter = 2; if (aDate.getMonth() > 5 && aDate.getMonth() < 9) quarter = 3; if (aDate.getMonth() >= 9) quarter = 4; if (length <= 2) return [self _stringValueForValue:quarter length:MIN(2,length)]; if (length == 3) return [[self shortStandaloneQuarterSymbols] objectAtIndex:(quarter - 1)]; if (length >= 4) return [[self standaloneQuarterSymbols] objectAtIndex:(quarter - 1)]; case @"M": var currentLength = [[CPString stringWithFormat:@"%i", aDate.getMonth() + 1] length]; if (length <= 2) return [self _stringValueForValue:(aDate.getMonth() + 1) length:MAX(currentLength,length)]; if (length == 3) return [[self shortMonthSymbols] objectAtIndex:aDate.getMonth()]; if (length == 4) return [[self monthSymbols] objectAtIndex:aDate.getMonth()]; if (length >= 5) return [[self veryShortMonthSymbols] objectAtIndex:aDate.getMonth()]; case @"L": var currentLength = [[CPString stringWithFormat:@"%i", aDate.getMonth() + 1] length]; if (length <= 2) return [self _stringValueForValue:(aDate.getMonth() + 1) length:MAX(currentLength,length)]; if (length == 3) return [[self shortStandaloneMonthSymbols] objectAtIndex:aDate.getMonth()]; if (length == 4) return [[self standaloneMonthSymbols] objectAtIndex:aDate.getMonth()]; if (length >= 5) return [[self veryShortStandaloneMonthSymbols] objectAtIndex:aDate.getMonth()]; case @"I": // Deprecated CPLog.warn(@"Depreacted - Token not yet implemented " + aToken); return [CPString new]; case @"w": var d = [aDate copy]; d.setHours(0, 0, 0); d.setDate(d.getDate() + 4 - (d.getDay() || 7)); var yearStart = new Date(d.getFullYear(), 0, 1), weekOfYear = Math.ceil((((d - yearStart) / 86400000) + 1) / 7); return [self _stringValueForValue:(weekOfYear + 1) length:MAX(2, length)]; case @"W": var firstDay = new Date(aDate.getFullYear(), aDate.getMonth(), 1).getDay(), weekOfMonth = Math.ceil((aDate.getDate() + firstDay) / 7); return [self _stringValueForValue:weekOfMonth length:1]; case @"d": var currentLength = [[CPString stringWithFormat:@"%i", aDate.getDate()] length]; return [self _stringValueForValue:aDate.getDate() length:MAX(length, currentLength)]; case @"D": var oneJan = new Date(aDate.getFullYear(), 0, 1), dayOfYear = Math.ceil((aDate - oneJan) / 86400000), currentLength = [[CPString stringWithFormat:@"%i", dayOfYear] length]; return [self _stringValueForValue:dayOfYear length:MAX(currentLength, MIN(3, length))]; case @"F": var dayOfWeek = 1, day = aDate.getDate(); if (day > 7 && day < 15) dayOfWeek = 2; if (day > 14 && day < 22) dayOfWeek = 3; if (day > 21 && day < 29) dayOfWeek = 4; if (day > 28) dayOfWeek = 5; return [self _stringValueForValue:dayOfWeek length:1]; case @"g": CPLog.warn(@"Token not yet implemented " + aToken); return [CPString new]; case @"E": var day = aDate.getDay(); if (length <= 3) return [[self shortWeekdaySymbols] objectAtIndex:day]; if (length == 4) return [[self weekdaySymbols] objectAtIndex:day]; if (length >= 5) return [[self veryShortWeekdaySymbols] objectAtIndex:day]; case @"e": var day = aDate.getDay(); if (length <= 2) return [self _stringValueForValue:(day + 1) length:MIN(2, length)]; if (length == 3) return [[self shortWeekdaySymbols] objectAtIndex:day]; if (length == 4) return [[self weekdaySymbols] objectAtIndex:day]; if (length >= 5) return [[self veryShortWeekdaySymbols] objectAtIndex:day]; case @"c": var day = aDate.getDay(); if (length <= 2) return [self _stringValueForValue:(day + 1) length:aDate.getDay().toString().length]; if (length == 3) return [[self shortStandaloneWeekdaySymbols] objectAtIndex:day]; if (length == 4) return [[self standaloneWeekdaySymbols] objectAtIndex:day]; if (length >= 5) return [[self veryShortStandaloneWeekdaySymbols] objectAtIndex:day]; case @"a": if (aDate.getHours() > 11) return [self PMSymbol]; else return [self AMSymbol]; case @"h": var hours = aDate.getHours(); if ([self _isAmericanFormat] || [self _isEnglishFormat]) { if (hours == 0) hours = 12; else if (hours > 12) hours = hours - 12; } var currentLength = [[CPString stringWithFormat:@"%i", hours] length]; return [self _stringValueForValue:hours length:MAX(currentLength, MIN(2, length))]; case @"H": var currentLength = [[CPString stringWithFormat:@"%i", aDate.getHours()] length]; return [self _stringValueForValue:aDate.getHours() length:MAX(currentLength, MIN(2, length))]; case @"K": var hours = aDate.getHours(); if (hours > 12) hours -= 12; var currentLength = [[CPString stringWithFormat:@"%i", hours] length]; return [self _stringValueForValue:hours length:MAX(currentLength, MIN(2, length))]; case @"k": var hours = aDate.getHours(); if (aDate.getHours() == 0) hours = 24; var currentLength = [[CPString stringWithFormat:@"%i", hours] length]; return [self _stringValueForValue:hours length:MAX(currentLength, MIN(2, length))]; case @"j": CPLog.warn(@"Token not yet implemented " + aToken); return [CPString new]; case @"m": var currentLength = [[CPString stringWithFormat:@"%i", aDate.getMinutes()] length]; return [self _stringValueForValue:aDate.getMinutes() length:MAX(currentLength, MIN(2, length))]; case @"s": var currentLength = [[CPString stringWithFormat:@"%i", aDate.getMinutes()] length]; return [self _stringValueForValue:aDate.getSeconds() length:MIN(2, length)]; case @"S": return [self _stringValueForValue:aDate.getMilliseconds() length:length]; case @"A": var value = aDate.getHours() * 60 * 60 * 1000 + aDate.getMinutes() * 60 * 1000 + aDate.getSeconds() * 1000 + aDate.getMilliseconds(); return [self _stringValueForValue:value length:value.toString().length]; case @"z": if (length <= 3) return [timeZone localizedName:CPTimeZoneNameStyleShortDaylightSaving locale:_locale]; else return [timeZone localizedName:CPTimeZoneNameStyleDaylightSaving locale:_locale]; case @"Z": var seconds = [timeZone secondsFromGMT], minutes = seconds / 60, hours = minutes / 60, result, diffMinutes = (hours - parseInt(hours)) * 100 * 60 / 100; if (length <= 3) { result = diffMinutes.toString(); while ([result length] < 2) result = @"0" + result; result = ABS(parseInt(hours)) + result; while ([result length] < 4) result = @"0" + result; if (seconds > 0) result = @"+" + result; else result = @"-" + result; return result; } else if (length == 4) { result = diffMinutes.toString(); while ([result length] < 2) result = @"0" + result; result = @":" + result; result = ABS(parseInt(hours)) + result; while ([result length] < 5) result = @"0" + result; if (seconds > 0) result = @"+" + result; else result = @"-" + result; return @"GMT" + result; } else { result = diffMinutes.toString(); while ([result length] < 2) result = @"0" + result; result = @":" + result; result = ABS(parseInt(hours)) + result; while ([result length] < 5) result = @"0" + result; if (seconds > 0) result = @"+" + result; else result = @"-" + result; return result; } case @"v": if (length == 1) return [timeZone localizedName:CPTimeZoneNameStyleShortGeneric locale:_locale]; else if (length == 4) return [timeZone localizedName:CPTimeZoneNameStyleGeneric locale:_locale]; return @" "; case @"V": if (length == 1) { return [timeZone localizedName:CPTimeZoneNameStyleShortDaylightSaving locale:_locale]; } else if (length == 4) { CPLog.warn(@"No pattern found for " + aToken); return @""; } return @" "; default: CPLog.warn(@"No pattern found for " + aToken); return aToken; } return [CPString new]; } #pragma mark - #pragma mark datefromString /*! Return a date of the given string This method returns (if possible) a representation of the given string with the dateFormat of the CPDateFormatter, otherwise it takes the dateStyle and timeStyle @param aString @return CPDate the date */ - (CPDate)dateFromString:(CPString)aString { var format; if (_dateFormat != nil) return [self _dateFromString:aString format:_dateFormat]; switch (_dateStyle) { case CPDateFormatterNoStyle: format = @""; break; case CPDateFormatterShortStyle: if ([self _isAmericanFormat]) format = @"M/d/yy"; else format = @"dd/MM/yy"; break; case CPDateFormatterMediumStyle: if ([self _isAmericanFormat]) format = @"MMM d, Y"; else format = @"d MMM Y"; break; case CPDateFormatterLongStyle: if ([self _isAmericanFormat]) format = @"MMMM d, Y"; else format = @"d MMMM Y"; break; case CPDateFormatterFullStyle: if ([self _isAmericanFormat]) format = @"EEEE, MMMM d, Y"; else format = @"EEEE d MMMM Y"; break; default: format = @""; } switch (_timeStyle) { case CPDateFormatterNoStyle: format += @""; break; case CPDateFormatterShortStyle: if ([self _isEnglishFormat]) format += @" h:mm a"; else format += @" H:mm"; break; case CPDateFormatterMediumStyle: if ([self _isEnglishFormat]) format += @" h:mm:ss a"; else format += @" H:mm:ss"; break; case CPDateFormatterLongStyle: if ([self _isEnglishFormat]) format += @" h:mm:ss a z"; else format += @" H:mm:ss z"; break; case CPDateFormatterFullStyle: if ([self _isEnglishFormat]) format += @" h:mm:ss a zzzz"; else format += @" h:mm:ss zzzz"; break; default: format += @""; } return [self _dateFromString:aString format:format]; } /*! Returns a boolean if the given object has been changed or not depending of the given string (use of ref) @param anObject the given object @param aString @param anError, if it returns NO the describe error will be in anError (use of ref) @return aBoolean for the success or fail of the method */ - (BOOL)getObjectValue:(idRef)anObject forString:(CPString)aString errorDescription:(CPStringRef)anError { var value = [self dateFromString:aString]; @deref(anObject) = value; if (!value) { if (anError) @deref(anError) = @"The value \"" + aString + "\" is invalid."; return NO; } return YES; } /*! Return a date representation of the given string and format @patam aDate @param aFormat @return a string */ - (CPDate)_dateFromString:(CPString)aString format:(CPString)aFormat { // Interpret @"" as the date 2000-01-01 00:00:00 +0000, like in Cocoa. No idea why they picked this particular date. if (!aString) return [[CPDate alloc] initWithTimeIntervalSinceReferenceDate:-31622400]; if (aFormat == nil) return nil; var currentToken = [CPString new], isTextToken = NO, tokens = [CPArray array], dateComponents = [CPArray array], patternTokens = [CPArray array]; for (var i = 0; i < [aFormat length]; i++) { var character = [aFormat characterAtIndex:i]; if (isTextToken) { if ([character isEqualToString:@"'"]) currentToken = [CPString new]; continue; } if ([character isEqualToString:@"'"]) { if (!isTextToken) isTextToken = YES; continue; } if ([character isEqualToString:@","] || [character isEqualToString:@":"] || [character isEqualToString:@"/"] || [character isEqualToString:@"-"] || [character isEqualToString:@" "]) { [tokens addObject:currentToken]; if ([patternStringTokens containsObject:currentToken]) [patternTokens addObject:[tokens count] - 1]; currentToken = [CPString new]; } else { if ([currentToken length] && ![[currentToken characterAtIndex:0] isEqualToString:character]) { [tokens addObject:currentToken]; if ([patternStringTokens containsObject:currentToken]) [patternTokens addObject:[tokens count] - 1]; currentToken = [CPString new]; } currentToken += character; if (i == ([aFormat length] - 1)) { [tokens addObject:currentToken]; if ([patternStringTokens containsObject:currentToken]) [patternTokens addObject:[tokens count] - 1]; } } } isTextToken = NO; currentToken = [CPString new]; var currentIndexSpecialPattern = 0; if ([patternTokens count] == 0) [patternTokens addObject:CPNotFound]; for (var i = 0; i < [aString length]; i++) { var character = [aString characterAtIndex:i]; if (isTextToken) { if ([character isEqualToString:@"'"]) currentToken = [CPString new]; continue; } if ([character isEqualToString:@"'"]) { if (!isTextToken) isTextToken = YES; continue; } // Need to do this to check if the word match with the token. We can get some words with space... if ([dateComponents count] == [patternTokens objectAtIndex:currentIndexSpecialPattern]) { var j = [self _lastIndexMatchedString:aString token:[tokens objectAtIndex:[dateComponents count]] index:i]; if (j == CPNotFound) return nil; currentIndexSpecialPattern++; [dateComponents addObject:[aString substringWithRange:CPMakeRange(i, (j - i))]]; i = j; continue; } if ([character isEqualToString:@","] || [character isEqualToString:@":"] || [character isEqualToString:@"/"] || [character isEqualToString:@"-"] || [character isEqualToString:@" "]) { [dateComponents addObject:currentToken]; currentToken = [CPString new]; } else { currentToken += character; if (i == ([aString length] - 1)) [dateComponents addObject:currentToken]; } } if ([dateComponents count] != [tokens count]) return nil; return [self _dateFromTokens:tokens dateComponents:dateComponents]; } - (CPDate)_dateFromTokens:(CPArray)tokens dateComponents:(CPArray)dateComponents { var timeZoneseconds = [_timeZone secondsFromGMT], dateArray = [2000, 01, 01, 00, 00, 00, @"+0000"], isPM = NO, dayOfYear, dayIndexInWeek, weekOfYear, weekOfMonth; for (var i = 0; i < [tokens count]; i++) { var token = [tokens objectAtIndex:i], dateComponent = [dateComponents objectAtIndex:i], character = [token characterAtIndex:0], length = [token length]; switch (character) { case @"G": // TODO CPLog.warn(@"Token not yet implemented " + token); break; case @"y": var u = _twoDigitStartDate.getFullYear() % 10, d = parseInt(_twoDigitStartDate.getFullYear() / 10) % 10, c = parseInt(_twoDigitStartDate.getFullYear() / 100) % 10, m = parseInt(_twoDigitStartDate.getFullYear() / 1000) % 10; if (length == 2 && dateComponent.length == 2) { if ((u + d * 10) >= parseInt(dateComponent)) dateArray[0] = (c + 1) * 100 + m * 1000 + parseInt(dateComponent); else dateArray[0] = c * 100 + m * 1000 + parseInt(dateComponent); } else { dateArray[0] = parseInt(dateComponent); } break; case @"Y": var u = _twoDigitStartDate.getFullYear() % 10, d = parseInt(_twoDigitStartDate.getFullYear() / 10) % 10, c = parseInt(_twoDigitStartDate.getFullYear() / 100) % 10, m = parseInt(_twoDigitStartDate.getFullYear() / 1000) % 10; if (length == 2 && dateComponent.length == 2) { if ((u + d * 10) >= parseInt(dateComponent)) dateArray[0] = (c + 1) * 100 + m * 1000 + parseInt(dateComponent); else dateArray[0] = c * 100 + m * 1000 + parseInt(dateComponent); } else { dateArray[0] = parseInt(dateComponent); } break; case @"u": // TODO CPLog.warn(@"Token not yet implemented " + token); break; case @"U": // TODO CPLog.warn(@"Token not yet implemented " + token); break; case @"Q": var month; if (length <= 2) month = (parseInt(dateComponent) - 1) * 3; if (length == 3) { if (![[self shortQuarterSymbols] containsObject:dateComponent]) return nil; month = [[self shortQuarterSymbols] indexOfObject:dateComponent] * 3; } if (length >= 4) { if (![[self quarterSymbols] containsObject:dateComponent]) return nil; month = [[self quarterSymbols] indexOfObject:dateComponent] * 3; } if (month > 11) return nil; dateArray[1] = month + 1; break; case @"q": var month; if (length <= 2) month = (parseInt(dateComponent) - 1) * 3; if (length == 3) { if (![[self shortQuarterSymbols] containsObject:dateComponent]) return nil; month = [[self shortQuarterSymbols] indexOfObject:dateComponent] * 3; } if (length >= 4) { if (![[self quarterSymbols] containsObject:dateComponent]) return nil; month = [[self quarterSymbols] indexOfObject:dateComponent] * 3; } if (month > 11) return nil; dateArray[1] = month + 1; break; case @"M": var month; if (length <= 2) month = parseInt(dateComponent); if (length == 3) { if (![[self shortMonthSymbols] containsObject:dateComponent]) return nil; month = [[self shortMonthSymbols] indexOfObject:dateComponent] + 1; } if (length == 4) { if (![[self monthSymbols] containsObject:dateComponent]) return nil; month = [[self monthSymbols] indexOfObject:dateComponent] + 1; } if (month > 12 || length >= 5) return nil; dateArray[1] = month; break; case @"L": var month; if (length <= 2) month = parseInt(dateComponent); if (length == 3) { if (![[self shortStandaloneMonthSymbols] containsObject:dateComponent]) return nil; month = [[self shortStandaloneMonthSymbols] indexOfObject:dateComponent] + 1; } if (length == 4) { if (![[self standaloneMonthSymbols] containsObject:dateComponent]) return nil; month = [[self standaloneMonthSymbols] indexOfObject:dateComponent] + 1; } if (month > 12 || length >= 5) return nil; dateArray[1] = month; break; case @"I": // Deprecated CPLog.warn(@"Depreacted - Token not yet implemented " + token); break; case @"w": if (dateComponent > 52) return nil; weekOfYear = dateComponent; break; case @"W": if (dateComponent > 52) return nil; weekOfMonth = dateComponent; break; case @"d": dateArray[2] = parseInt(dateComponent); break; case @"D": if (isNaN(parseInt(dateComponent)) || parseInt(dateComponent) > 345) return nil; dayOfYear = parseInt(dateComponent); break; case @"F": if (isNaN(parseInt(dateComponent)) || parseInt(dateComponent) > 5 || parseInt(dateComponent) == 0) return nil; if (parseInt(dateComponent) == 1) dateArray[2] = 1; if (parseInt(dateComponent) == 2) dateArray[2] = 8; if (parseInt(dateComponent) == 3) dateArray[2] = 15; if (parseInt(dateComponent) == 4) dateArray[2] = 22; if (parseInt(dateComponent) == 5) dateArray[2] = 29; break; case @"g": CPLog.warn(@"Token not yet implemented " + token); break; case @"E": if (length <= 3) dayIndexInWeek = [[self shortWeekdaySymbols] indexOfObject:dateComponent]; if (length == 4) dayIndexInWeek = [[self weekdaySymbols] indexOfObject:dateComponent]; if (dayIndexInWeek == CPNotFound || length >= 5) return nil; break; case @"e": if (length <= 2 && isNaN(parseInt(dateComponent))) return nil; if (length <= 2) dayIndexInWeek = parseInt(dateComponent); if (length == 3) dayIndexInWeek = [[self shortWeekdaySymbols] indexOfObject:dateComponent]; if (length == 4) dayIndexInWeek = [[self weekdaySymbols] indexOfObject:dateComponent]; if (dayIndexInWeek == CPNotFound || length >= 5) return nil; break; case @"c": if (length <= 2 && isNaN(parseInt(dateComponent))) return nil; if (length <= 2) dayIndexInWeek = dateComponent; if (length == 3) dayIndexInWeek = [[self shortStandaloneWeekdaySymbols] indexOfObject:dateComponent]; if (length == 4) dayIndexInWeek = [[self standaloneWeekdaySymbols] indexOfObject:dateComponent]; if (length == 5) dayIndexInWeek = [[self veryShortStandaloneWeekdaySymbols] indexOfObject:dateComponent]; if (dayIndexInWeek == CPNotFound || length >= 5) return nil; break; case @"a": if (![dateComponent isEqualToString:[self PMSymbol]] && ![dateComponent isEqualToString:[self AMSymbol]]) return nil; if ([dateComponent isEqualToString:[self PMSymbol]]) isPM = YES; break; case @"h": if (parseInt(dateComponent) < 0 || parseInt(dateComponent) > 12) return nil; dateArray[3] = parseInt(dateComponent); break; case @"H": if (parseInt(dateComponent) < 0 || parseInt(dateComponent) > 23) return nil; dateArray[3] = parseInt(dateComponent); break; case @"K": if (parseInt(dateComponent) < 0 || parseInt(dateComponent) > 11) return nil; dateArray[3] = parseInt(dateComponent); break; case @"k": if (parseInt(dateComponent) < 0 || parseInt(dateComponent) > 12) return nil; dateArray[3] = parseInt(dateComponent); break; case @"j": CPLog.warn(@"Token not yet implemented " + token); break; case @"m": var minutes = parseInt(dateComponent); if (minutes > 59) return nil; dateArray[4] = minutes; break; case @"s": var seconds = parseInt(dateComponent); if (seconds > 59) return nil; dateArray[5] = seconds; break; case @"S": if (isNaN(parseInt(dateComponent))) return nil; break; case @"A": if (isNaN(parseInt(dateComponent))) return nil; var millisecondsInDay = parseInt(dateComponent), tmpDate = new Date(); tmpDate.setHours(0); tmpDate.setMinutes(0); tmpDate.setSeconds(0); tmpDate.setMilliseconds(0); tmpDate.setMilliseconds(millisecondsInDay); dateArray[3] = tmpDate.getHours(); dateArray[4] = tmpDate.getMinutes(); dateArray[5] = tmpDate.getSeconds(); break; case @"z": if (length < 4) timeZoneseconds = [self _secondsFromTimeZoneString:dateComponent style:CPTimeZoneNameStyleShortDaylightSaving]; else timeZoneseconds = [self _secondsFromTimeZoneString:dateComponent style:CPTimeZoneNameStyleDaylightSaving]; if (!timeZoneseconds) timeZoneseconds = [self _secondsFromTimeZoneDefaultFormatString:dateComponent]; if (!timeZoneseconds) return nil; timeZoneseconds = timeZoneseconds + 60 * 60; break; case @"Z": timeZoneseconds = [self _secondsFromTimeZoneDefaultFormatString:dateComponent]; if (!timeZoneseconds) return nil; timeZoneseconds = timeZoneseconds + 60 * 60; break; case @"v": if (length <= 3) timeZoneseconds = [self _secondsFromTimeZoneString:dateComponent style:CPTimeZoneNameStyleShortGeneric]; else timeZoneseconds = [self _secondsFromTimeZoneString:dateComponent style:CPTimeZoneNameStyleGeneric]; if (!timeZoneseconds && length == 4) timeZoneseconds = [self _secondsFromTimeZoneDefaultFormatString:dateComponent]; if (!timeZoneseconds) return nil; timeZoneseconds = timeZoneseconds + 60 * 60; break; case @"V": if (length <= 3) timeZoneseconds = [self _secondsFromTimeZoneString:dateComponent style:CPTimeZoneNameStyleShortStandard]; else timeZoneseconds = [self _secondsFromTimeZoneString:dateComponent style:CPTimeZoneNameStyleStandard]; if (!timeZoneseconds) timeZoneseconds = [self _secondsFromTimeZoneDefaultFormatString:dateComponent]; if (!timeZoneseconds) return nil; timeZoneseconds = timeZoneseconds + 60 * 60; break; default: CPLog.warn(@"No pattern found for " + token); return nil; } } // Make the calcul day of the year if (dayOfYear) { var tmpDate = new Date(); tmpDate.setFullYear(dateArray[0]); tmpDate.setMonth(0); tmpDate.setDate(dayOfYear); dateArray[1] = tmpDate.getMonth() + 1; dateArray[2] = tmpDate.getDate(); } if (weekOfMonth) dateArray[2] = (weekOfMonth - 1) * 7 + 1; if (weekOfYear) { var tmpDate = new Date(); tmpDate.setFullYear(dateArray[0]); tmpDate.setMonth(0); tmpDate.setDate(1); while (tmpDate.getDay() != 0) tmpDate.setDate(tmpDate.getDate() + 1); tmpDate.setDate(tmpDate.getDate() + (weekOfYear - 1) * 7); dateArray[1] = tmpDate.getMonth() + 1; dateArray[2] = tmpDate.getDate() - 1; } // Check if the day is possible in the current month var tmpDate = new Date(); tmpDate.setMonth(dateArray[1] - 1); tmpDate.setFullYear(dateArray[0]); if (dateArray[2] <= 0 || dateArray[2] > [tmpDate _daysInMonth]) return nil; // PM hours if (isPM) dateArray[3] += 12; if (isNaN(parseInt(dateArray[0])) || isNaN(parseInt(dateArray[1])) || isNaN(parseInt(dateArray[2])) || isNaN(parseInt(dateArray[3])) || isNaN(parseInt(dateArray[4])) || isNaN(parseInt(dateArray[5])) || isNaN(parseInt(dateArray[6]))) return nil; var dateResult = [[CPDate alloc] initWithString:[CPString stringWithFormat:@"%04d-%02d-%02d %02d:%02d:%02d %s", dateArray[0], dateArray[1], dateArray[2], dateArray[3], dateArray[4], dateArray[5], dateArray[6]]]; dateResult.setSeconds(dateResult.getSeconds() - timeZoneseconds + 60 * 60); return dateResult; } #pragma mark - #pragma mark Utils - (CPString)_stringValueForValue:(id)aValue length:(int)length { var string = [CPString stringWithFormat:@"%i", aValue]; if ([string length] == length) return string; if ([string length] > length) return [string substringFromIndex:([string length] - length)]; while ([string length] < length) string = [CPString stringWithFormat:@"0%s", string]; return string; } /*! Check if we are in the american format or not. Depending on the locale */ - (BOOL)_isAmericanFormat { return [[_locale objectForKey:CPLocaleCountryCode] isEqualToString:@"US"]; } /*! Check if we are in the english format or not. Depending on the locale */ - (BOOL)_isEnglishFormat { return [[_locale objectForKey:CPLocaleLanguageCode] isEqualToString:@"en"]; } /*! Returns the number of second from a time zone (-8000 or HGP-8:35 or GMT-08:00) */ - (int)_secondsFromTimeZoneDefaultFormatString:(CPString)aTimeZoneFormatString { var format = new RegExp("\\w*([HPG-GMT])?([+-])(\\d{1,2})([:])?(\\d{2})\\w*"), result = aTimeZoneFormatString.match(new RegExp(format)), seconds = 0; if (!result) return nil; seconds = result[3] * 60 * 60 + result[5] * 60; if ([result[2] isEqualToString:@"-"]) seconds = -seconds; return seconds; } /*! Return the number of seconds from a timeZoneString */ - (int)_secondsFromTimeZoneString:(CPString)aTimeZoneString style:(NSTimeZoneNameStyle)aStyle { var timeZone = [CPTimeZone _timeZoneFromString:aTimeZoneString style:aStyle locale:_locale]; if (!timeZone) return nil; return [timeZone secondsFromGMT]; } /*! This method is used to know if the given string match with the token. @param aString @param aToken @param anIndex the current index in the string @return an index who describes the position of the end of the word for the token */ - (int)_lastIndexMatchedString:(CPString)aString token:(CPString)aToken index:anIndex { var character = [aToken characterAtIndex:0], length = [aToken length], targetedArray, format = new RegExp("\\w*([HPG-GMT])?([+-])(\\d{1,2})([:])?(\\d{2})\\w*"), result = aString.match(new RegExp(format)); switch (character) { case @"Q": if (length == 3) targetedArray = [self shortQuarterSymbols]; if (length >= 4) targetedArray = [self quarterSymbols]; break; case @"q": if (length == 3) targetedArray = [self shortStandaloneQuarterSymbols]; if (length >= 4) targetedArray = [self standaloneQuarterSymbols]; break; case @"M": if (length == 3) targetedArray = [self shortMonthSymbols]; if (length == 4) targetedArray = [self monthSymbols]; if (length >= 5) targetedArray = [self veryShortMonthSymbols]; break; case @"L": if (length == 3) targetedArray = [self shortStandaloneMonthSymbols]; if (length == 4) targetedArray = [self standaloneMonthSymbols]; if (length >= 5) targetedArray = [self veryShortStandaloneMonthSymbols]; break; case @"E": if (length <= 3) targetedArray = [self shortWeekdaySymbols]; if (length == 4) targetedArray = [self weekdaySymbols]; if (length >= 5) targetedArray = [self veryShortWeekdaySymbols]; break; case @"e": if (length == 3) targetedArray = [self shortWeekdaySymbols]; if (length == 4) targetedArray = [self weekdaySymbols]; if (length >= 5) targetedArray = [self veryShortWeekdaySymbols]; break; case @"c": if (length == 3) targetedArray = [self shortStandaloneWeekdaySymbols]; if (length == 4) targetedArray = [self standaloneWeekdaySymbols]; if (length >= 5) targetedArray = [self veryShortStandaloneWeekdaySymbols]; break; case @"a": targetedArray = [[self PMSymbol], [self AMSymbol]]; break; case @"z": if (length <= 3) targetedArray = [CPTimeZone _namesForStyle:CPTimeZoneNameStyleShortDaylightSaving locale:_locale]; else targetedArray = [CPTimeZone _namesForStyle:CPTimeZoneNameStyleDaylightSaving locale:_locale]; if (result) return anIndex + [result objectAtIndex:0].length; break; case @"Z": if (result) return anIndex + [result objectAtIndex:0].length; return CPNotFound; case @"v": if (length == 1) targetedArray = [CPTimeZone _namesForStyle:CPTimeZoneNameStyleShortGeneric locale:_locale]; else if (length == 4) targetedArray = [CPTimeZone _namesForStyle:CPTimeZoneNameStyleGeneric locale:_locale]; if (result) return anIndex + [result objectAtIndex:0].length; break; case @"V": if (length == 1) targetedArray = [CPTimeZone _namesForStyle:CPTimeZoneNameStyleShortStandard locale:_locale]; if (result) return anIndex + [result objectAtIndex:0].length; break; default: CPLog.warn(@"No pattern found for " + aToken); return CPNotFound; } for (var i = 0; i < [targetedArray count]; i++) { var currentObject = [targetedArray objectAtIndex:i], range = [aString rangeOfString:currentObject]; if (range.length == 0) continue; character = [aString characterAtIndex:(anIndex + range.length)]; if ([character isEqualToString:@"'"] || [character isEqualToString:@","] || [character isEqualToString:@":"] || [character isEqualToString:@"/"] || [character isEqualToString:@"-"] || [character isEqualToString:@" "] || [character isEqualToString:@""]) return anIndex + range.length; } return CPNotFound; } @end var CPDateFormatterDateStyleKey = @"CPDateFormatterDateStyle", CPDateFormatterTimeStyleKey = @"CPDateFormatterTimeStyleKey", CPDateFormatterFormatterBehaviorKey = @"CPDateFormatterFormatterBehaviorKey", CPDateFormatterDoseRelativeDateFormattingKey = @"CPDateFormatterDoseRelativeDateFormattingKey", CPDateFormatterDateFormatKey = @"CPDateFormatterDateFormatKey", CPDateFormatterAllowNaturalLanguageKey = @"CPDateFormatterAllowNaturalLanguageKey", CPDateFormatterLocaleKey = @"CPDateFormatterLocaleKey"; @implementation CPDateFormatter (CPCoding) - (id)initWithCoder:(CPCoder)aCoder { self = [super initWithCoder:aCoder]; if (self) { _allowNaturalLanguage = [aCoder decodeBoolForKey:CPDateFormatterAllowNaturalLanguageKey]; _dateFormat = [aCoder decodeObjectForKey:CPDateFormatterDateFormatKey]; _dateStyle = [aCoder decodeIntForKey:CPDateFormatterDateStyleKey]; _doesRelativeDateFormatting = [aCoder decodeBoolForKey:CPDateFormatterDoseRelativeDateFormattingKey]; _formatterBehavior = [aCoder decodeIntForKey:CPDateFormatterFormatterBehaviorKey]; _locale = [aCoder decodeObjectForKey:CPDateFormatterLocaleKey]; _timeStyle = [aCoder decodeIntForKey:CPDateFormatterTimeStyleKey]; } [self _init]; return self; } - (void)encodeWithCoder:(CPCoder)aCoder { [super encodeWithCoder:aCoder]; [aCoder encodeBool:_allowNaturalLanguage forKey:CPDateFormatterAllowNaturalLanguageKey]; [aCoder encodeInt:_dateStyle forKey:CPDateFormatterDateStyleKey]; [aCoder encodeObject:_dateFormat forKey:CPDateFormatterDateFormatKey]; [aCoder encodeBool:_doesRelativeDateFormatting forKey:CPDateFormatterDoseRelativeDateFormattingKey]; [aCoder encodeInt:_formatterBehavior forKey:CPDateFormatterFormatterBehaviorKey]; [aCoder encodeInt:_locale forKey:CPDateFormatterLocaleKey]; [aCoder encodeInt:_timeStyle forKey:CPDateFormatterTimeStyleKey]; } @end @implementation CPDate (CPTimeZone) /*! Convert a date from a timeZone */ - (void)_dateWithTimeZone:(CPTimeZone)aTimeZone { if (!aTimeZone) return; self.setSeconds(self.getSeconds() - [aTimeZone secondsFromGMTForDate:self]); self.setSeconds(self.getSeconds() + [aTimeZone secondsFromGMT]); } @end