Cappuccino  1.0.0
 All Classes Files Functions Variables Typedefs Macros Groups Pages
CPDateFormatter.j
Go to the documentation of this file.
1 /*
2  * CPDateFormatter.j
3  * Foundation
4  *
5  * Created by Alexander Ljungberg.
6  * Copyright 2012, SlevenBits Ltd.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
24 
26 @global CPLocaleCountryCode
27 
28 @typedef CPDateFormatterStyle
34 
35 @typedef CPDateFormatterBehavior
39 
43 
44 var _separatorsCharacterSet = nil;
45 
53 @implementation CPDateFormatter : CPFormatter
54 {
55  BOOL _allowNaturalLanguage;
56  BOOL _doesRelativeDateFormatting;
57  CPDate _defaultDate;
58  CPDate _twoDigitStartDate;
59  CPDateFormatterBehavior _formatterBehavior;
60  CPDateFormatterStyle _dateStyle;
61  CPDateFormatterStyle _timeStyle;
62  CPLocale _locale;
63  CPString _AMSymbol;
64  CPString _dateFormat;
65  CPString _PMSymbol;
66  CPTimeZone _timeZone;
67 
68  CPDictionary _symbols;
69 }
70 
71 
72 + (void)initialize
73 {
74  if (self !== [CPDateFormatter class])
75  return;
76 
78  @"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],
79  @"en" : [@"tomorrow", 1, @"yesterday", -1],
80  @"de" : [@"morgen", 1, @"gestern", -1, String.fromCharCode(129) + @"bermorgen", 2, @"vorgestern", -2],
81  @"es" : []
82  };
83 
84  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"];
85 }
86 
93 + (CPString)localizedStringFromDate:(CPDate)date dateStyle:(CPDateFormatterStyle)dateStyle timeStyle:(CPDateFormatterStyle)timeStyle
94 {
95  var formatter = [[CPDateFormatter alloc] init];
96 
97  [formatter setFormatterBehavior:CPDateFormatterBehavior10_4];
98  [formatter setDateStyle:dateStyle];
99  [formatter setTimeStyle:timeStyle];
100 
101  return [formatter stringForObjectValue:date];
102 }
103 
111 + (CPString)dateFormatFromTemplate:(CPString)template options:(CPUInteger)opts locale:(CPLocale)locale
112 {
113  // TODO : check every template from cocoa and return a good format (have fun ^^)
114 }
115 
119 + (CPDateFormatterBehavior)defaultFormatterBehavior
120 {
122 }
123 
127 + (void)setDefaultFormatterBehavior:(CPDateFormatterBehavior)behavior
128 {
129  defaultDateFormatterBehavior = behavior;
130 }
131 
132 + (CPCharacterSet)_separatorsCharacterSet
133 {
134  if (_separatorsCharacterSet == nil)
135  _separatorsCharacterSet = [CPCharacterSet characterSetWithCharactersInString:@" ,:/-."];
136 
137  return _separatorsCharacterSet;
138 }
139 
143 - (id)init
144 {
145  if (self = [super init])
146  {
147  _dateStyle = nil;
148  _timeStyle = nil;
149 
150  [self _init];
151  }
152 
153  return self;
154 }
155 
161 - (id)initWithDateFormat:(CPString)format allowNaturalLanguage:(BOOL)flag
162 {
163  if (self = [self init])
164  {
165  _dateFormat = format;
166  _allowNaturalLanguage = flag;
167  }
168 
169  return self
170 }
171 
174 - (void)_init
175 {
176  var AMSymbol = [CPString stringWithFormat:@"%s", @"AM"],
177  PMSymbol = [CPString stringWithFormat:@"%s", @"PM"],
178  weekdaySymbols = [CPArray arrayWithObjects:@"Sunday", @"Monday", @"Tuesday", @"Wednesday", @"Thursday", @"Friday", @"Saturday"],
179  shortWeekdaySymbols = [CPArray arrayWithObjects:@"Sun", @"Mon", @"Tue", @"Wed", @"Thu", @"Fri", @"Sat"],
180  veryShortWeekdaySymbols = [CPArray arrayWithObjects:@"S", @"M", @"T", @"W", @"T", @"F", @"S"],
181  standaloneWeekdaySymbols = [CPArray arrayWithObjects:@"Sunday", @"Monday", @"Tuesday", @"Wednesday", @"Thursday", @"Friday", @"Saturday"],
182  shortStandaloneWeekdaySymbols = [CPArray arrayWithObjects:@"Sun", @"Mon", @"Tue", @"Wed", @"Thu", @"Fri", @"Sat"],
183  veryShortStandaloneWeekdaySymbols = [CPArray arrayWithObjects:@"S", @"M", @"T", @"W", @"T", @"F", @"S"],
184  monthSymbols = [CPArray arrayWithObjects:@"January", @"February", @"March", @"April", @"May", @"June", @"July", @"August", @"September", @"October", @"November", @"December"],
185  shortMonthSymbols = [CPArray arrayWithObjects:@"Jan", @"Feb", @"Mar", @"Apr", @"May", @"Jun", @"Jul", @"Aug", @"Sep", @"Oct", @"Nov", @"Dec"],
186  veryShortMonthSymbols = [CPArray arrayWithObjects:@"J", @"F", @"M", @"A", @"M", @"J", @"J", @"A", @"S", @"O", @"N", @"D"],
187  standaloneMonthSymbols = [CPArray arrayWithObjects:@"January", @"February", @"March", @"April", @"May", @"June", @"July", @"August", @"September", @"October", @"November", @"December"],
188  shortStandaloneMonthSymbols = [CPArray arrayWithObjects:@"Jan", @"Feb", @"Mar", @"Apr", @"May", @"Jun", @"Jul", @"Aug", @"Sep", @"Oct", @"Nov", @"Dec"],
189  veryShortStandaloneMonthSymbols = [CPArray arrayWithObjects:@"J", @"F", @"M", @"A", @"M", @"J", @"J", @"A", @"S", @"O", @"N", @"D"],
190  quarterSymbols = [CPArray arrayWithObjects:@"1st quarter", @"2nd quarter", @"3rd quarter", @"4th quarter"],
191  shortQuarterSymbols = [CPArray arrayWithObjects:@"Q1", @"Q2", @"Q3", @"Q4"],
192  standaloneQuarterSymbols = [CPArray arrayWithObjects:@"1st quarter", @"2nd quarter", @"3rd quarter", @"4th quarter"],
193  shortStandaloneQuarterSymbols = [CPArray arrayWithObjects:@"Q1", @"Q2", @"Q3", @"Q4"];
194 
195  _symbols = @{
196  @"root" : @{
197  @"AMSymbol" : AMSymbol,
198  @"PMSymbol" : PMSymbol,
199  @"weekdaySymbols" : weekdaySymbols,
200  @"shortWeekdaySymbols" : shortWeekdaySymbols,
201  @"veryShortWeekdaySymbols" : veryShortWeekdaySymbols,
202  @"standaloneWeekdaySymbols" : standaloneWeekdaySymbols,
203  @"shortStandaloneWeekdaySymbols" : shortStandaloneWeekdaySymbols,
204  @"veryShortStandaloneWeekdaySymbols" : veryShortStandaloneWeekdaySymbols,
205  @"monthSymbols" : monthSymbols,
206  @"shortMonthSymbols" : shortMonthSymbols,
207  @"veryShortMonthSymbols" : veryShortMonthSymbols,
208  @"standaloneMonthSymbols" : standaloneMonthSymbols,
209  @"shortStandaloneMonthSymbols" : shortStandaloneMonthSymbols,
210  @"veryShortStandaloneMonthSymbols" : veryShortStandaloneMonthSymbols,
211  @"quarterSymbols" : quarterSymbols,
212  @"shortQuarterSymbols" : shortQuarterSymbols,
213  @"standaloneQuarterSymbols" : standaloneQuarterSymbols,
214  @"shortStandaloneQuarterSymbols" : shortStandaloneQuarterSymbols
215  }
216 
217  };
218 
219  _timeZone = [CPTimeZone systemTimeZone];
220  _twoDigitStartDate = [[CPDate alloc] initWithString:@"1950-01-01 00:00:00 +0000"];
221  _locale = [CPLocale currentLocale];
222 }
223 
224 
225 #pragma mark -
226 #pragma mark Setter Getter Helper
227 
230 - (CPDictionary)symbolsForLanguageCode:(CPString)languageCode
231 {
232  var languageSymbols = [_symbols valueForKey:languageCode];
233 
234  if (!languageSymbols)
235  {
236  languageSymbols = [self symbolsForLanguageCode:@"root"];
237  [self setSymbols:languageSymbols forLanguageCode:languageCode];
238  }
239 
240  return languageSymbols;
241 }
242 
245 - (void)setSymbols:(CPDictionary)symbols forLanguageCode:(CPString)languageCode
246 {
247  [_symbols setValue:symbols forKey:languageCode];
248 }
249 
252 - (id)symbolForKey:(CPString)aKey languageCode:(CPString)languageCode
253 {
254  var languageSymbols = [self symbolsForLanguageCode:languageCode],
255  symbol = [languageSymbols valueForKey:aKey];
256 
257  if (!symbol)
258  {
259  symbol = [self symbolForKey:aKey languageCode:@"root"];
260  [self setSymbol:symbol forKey:aKey languageCode:languageCode];
261  }
262 
263  return symbol;
264 }
265 
268 - (void)setSymbol:(CPString)aSymbol forKey:(CPString)aKey languageCode:(CPString)languageCode
269 {
270  var languageSymbols = [self symbolsForLanguageCode:languageCode];
271  [languageSymbols setValue:aSymbol forKey:aKey];
272 }
273 
274 #pragma mark -
275 #pragma mark Setter Getter
276 
279 - (CPString)AMSymbol
280 {
281  return [self symbolForKey:@"AMSymbol" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
282 }
283 
286 - (void)setAMSymbol:(CPString)aValue
287 {
288  [self setSymbol:aValue forKey:@"AMSymbol" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
289 }
290 
293 - (CPString)PMSymbol
294 {
295  return [self symbolForKey:@"PMSymbol" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
296 }
297 
300 - (void)setPMSymbol:(CPString)aValue
301 {
302  [self setSymbol:aValue forKey:@"PMSymbol" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
303 }
304 
307 - (CPArray)weekdaySymbols
308 {
309  return [self symbolForKey:@"weekdaySymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
310 }
311 
314 - (void)setWeekdaySymbols:(CPArray)aValue
315 {
316  [self setSymbol:aValue forKey:@"weekdaySymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
317 }
318 
321 - (CPArray)shortWeekdaySymbols
322 {
323  return [self symbolForKey:@"shortWeekdaySymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
324 }
325 
328 - (void)setShortWeekdaySymbols:(CPArray)aValue
329 {
330  [self setSymbol:aValue forKey:@"shortWeekdaySymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
331 }
332 
335 - (CPArray)veryShortWeekdaySymbols
336 {
337  return [self symbolForKey:@"veryShortWeekdaySymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
338 }
339 
342 - (void)setVeryShortWeekdaySymbols:(CPArray)aValue
343 {
344  [self setSymbol:aValue forKey:@"veryShortWeekdaySymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
345 }
346 
349 - (CPArray)standaloneWeekdaySymbols
350 {
351  return [self symbolForKey:@"standaloneWeekdaySymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
352 }
353 
356 - (void)setStandaloneWeekdaySymbols:(CPArray)aValue
357 {
358  [self setSymbol:aValue forKey:@"standaloneWeekdaySymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
359 }
360 
363 - (CPArray)shortStandaloneWeekdaySymbols
364 {
365  return [self symbolForKey:@"shortStandaloneWeekdaySymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
366 }
367 
370 - (void)setShortStandaloneWeekdaySymbols:(CPArray)aValue
371 {
372  [self setSymbol:aValue forKey:@"shortStandaloneWeekdaySymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
373 }
374 
377 - (CPArray)veryShortStandaloneWeekdaySymbols
378 {
379  return [self symbolForKey:@"veryShortStandaloneWeekdaySymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
380 }
381 
384 - (void)setVeryShortStandaloneWeekdaySymbols:(CPArray)aValue
385 {
386  [self setSymbol:aValue forKey:@"veryShortStandaloneWeekdaySymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
387 }
388 
391 - (CPArray)monthSymbols
392 {
393  return [self symbolForKey:@"monthSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
394 }
395 
398 - (void)setMonthSymbols:(CPArray)aValue
399 {
400  [self setSymbol:aValue forKey:@"monthSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
401 }
402 
405 - (CPArray)shortMonthSymbols
406 {
407  return [self symbolForKey:@"shortMonthSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
408 }
409 
412 - (void)setShortMonthSymbols:(CPArray)aValue
413 {
414  [self setSymbol:aValue forKey:@"shortMonthSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
415 }
416 
419 - (CPArray)veryShortMonthSymbols
420 {
421  return [self symbolForKey:@"veryShortMonthSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
422 }
423 
426 - (void)setVeryShortMonthSymbols:(CPArray)aValue
427 {
428  [self setSymbol:aValue forKey:@"veryShortMonthSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
429 }
430 
433 - (CPArray)standaloneMonthSymbols
434 {
435  return [self symbolForKey:@"standaloneMonthSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
436 }
437 
440 - (void)setStandaloneMonthSymbols:(CPArray)aValue
441 {
442  [self setSymbol:aValue forKey:@"standaloneMonthSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
443 }
444 
447 - (CPArray)shortStandaloneMonthSymbols
448 {
449  return [self symbolForKey:@"shortStandaloneMonthSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
450 }
451 
454 - (void)setShortStandaloneMonthSymbols:(CPArray)aValue
455 {
456  [self setSymbol:aValue forKey:@"shortStandaloneMonthSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
457 }
458 
461 - (CPArray)veryShortStandaloneMonthSymbols
462 {
463  return [self symbolForKey:@"veryShortStandaloneMonthSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
464 }
465 
468 - (void)setVeryShortStandaloneMonthSymbols:(CPArray)aValue
469 {
470  [self setSymbol:aValue forKey:@"veryShortStandaloneMonthSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
471 }
472 
475 - (CPArray)quarterSymbols
476 {
477  return [self symbolForKey:@"quarterSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
478 }
479 
482 - (void)setQuarterSymbols:(CPArray)aValue
483 {
484  [self setSymbol:aValue forKey:@"quarterSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
485 }
486 
489 - (CPArray)shortQuarterSymbols
490 {
491  return [self symbolForKey:@"shortQuarterSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
492 }
493 
496 - (void)setShortQuarterSymbols:(CPArray)aValue
497 {
498  [self setSymbol:aValue forKey:@"shortQuarterSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
499 }
500 
503 - (CPArray)standaloneQuarterSymbols
504 {
505  return [self symbolForKey:@"standaloneQuarterSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
506 }
507 
510 - (void)setStandaloneQuarterSymbols:(CPArray)aValue
511 {
512  [self setSymbol:aValue forKey:@"standaloneQuarterSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
513 }
514 
517 - (CPArray)shortStandaloneQuarterSymbols
518 {
519  return [self symbolForKey:@"shortStandaloneQuarterSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
520 }
521 
524 - (void)setShortStandaloneQuarterSymbols:(CPArray)aValue
525 {
526  [self setSymbol:aValue forKey:@"shortStandaloneQuarterSymbols" languageCode:[_locale objectForKey:CPLocaleLanguageCode]];
527 }
528 
529 
530 #pragma mark -
531 #pragma mark StringFromDate methods
532 
538 - (CPString)stringFromDate:(CPDate)aDate
539 {
540  var format,
541  relativeWord,
542  result;
543 
544  if (!aDate)
545  return;
546 
547  aDate = [aDate copy];
548  [aDate _dateWithTimeZone:_timeZone];
549 
550  if (_dateFormat)
551  return [self _stringFromDate:aDate format:_dateFormat];
552 
553  switch (_dateStyle)
554  {
556  format = @"";
557  break;
558 
560  if ([self _isAmericanFormat])
561  format = @"M/d/yy";
562  else
563  {
564  if ([_locale objectForKey:CPLocaleLanguageCode] === 'de')
565  format = @"dd.MM.yy";
566  else
567  format = @"dd/MM/yy";
568 
569  }
570 
571  break;
572 
574  if ([self _isAmericanFormat])
575  format = @"MMM d, Y";
576  else
577  format = @"d MMM Y";
578 
579  break;
580 
582  if ([self _isAmericanFormat])
583  format = @"MMMM d, Y";
584  else
585  format = @"d MMMM Y";
586 
587  break;
588 
590  if ([self _isAmericanFormat])
591  format = @"EEEE, MMMM d, Y";
592  else
593  format = @"EEEE d MMMM Y";
594 
595  break;
596 
597  default:
598  format = @"";
599  }
600 
601 
602  if ([self doesRelativeDateFormatting])
603  {
604  var language = [_locale objectForKey:CPLocaleLanguageCode],
605  relativeWords = [relativeDateFormating valueForKey:language];
606 
607  for (var i = 1; i < [relativeWords count]; i = i + 2)
608  {
609  var date = [CPDate date];
610  [date _dateWithTimeZone:_timeZone];
611 
612  date.setHours(12);
613  date.setMinutes(0);
614  date.setSeconds(0);
615 
616  date.setDate([relativeWords objectAtIndex:i] + date.getDate());
617 
618  if (date.getDate() == aDate.getDate() && date.getMonth() == aDate.getMonth() && date.getFullYear() == aDate.getFullYear())
619  {
620  relativeWord = [relativeWords objectAtIndex:(i - 1)];
621  format = @"";
622  break;
623  }
624  }
625  }
626 
627  if ((relativeWord || format.length) && _timeStyle != CPDateFormatterNoStyle)
628  format += @" ";
629 
630  switch (_timeStyle)
631  {
633  format += @"";
634  break;
635 
637  if ([self _isEnglishFormat])
638  format += @"h:mm a";
639  else
640  format += @"H:mm";
641 
642  break;
643 
645  if ([self _isEnglishFormat])
646  format += @"h:mm:ss a";
647  else
648  format += @"H:mm:ss";
649 
650  break;
651 
653  if ([self _isEnglishFormat])
654  format += @"h:mm:ss a z";
655  else
656  format += @"H:mm:ss z";
657 
658  break;
659 
661  if ([self _isEnglishFormat])
662  format += @"h:mm:ss a zzzz";
663  else
664  format += @"h:mm:ss zzzz";
665 
666  break;
667 
668  default:
669  format += @"";
670  }
671 
672  result = [self _stringFromDate:aDate format:format];
673 
674  if (relativeWord)
675  result = relativeWord + result;
676 
677  return result;
678 }
679 
685 - (CPString)stringForObjectValue:(id)anObject
686 {
687  if ([anObject isKindOfClass:[CPDate class]])
688  return [self stringFromDate:anObject];
689  else
690  return nil;
691 }
692 
698 - (CPString)editingStringForObjectValue:(id)anObject
699 {
700  return [self stringForObjectValue:anObject];
701 }
702 
708 - (CPString)_stringFromDate:(CPDate)aDate format:(CPString)aFormat
709 {
710  var length = [aFormat length],
711  currentToken = [CPString new],
712  isTextToken = NO,
713  result = [CPString new];
714 
715  for (var i = 0; i < length; i++)
716  {
717  var character = [aFormat characterAtIndex:i];
718 
719  if (isTextToken)
720  {
721  if ([character isEqualToString:@"'"])
722  {
723  isTextToken = NO;
724  result += currentToken;
725  currentToken = [CPString new];
726  }
727  else
728  {
729  currentToken += character;
730  }
731 
732  continue;
733  }
734 
735  if ([character isEqualToString:@"'"])
736  {
737  if (!isTextToken)
738  {
739  isTextToken = YES;
740  result += currentToken;
741  currentToken = [CPString new];
742  }
743 
744  continue;
745  }
746 
747  if ([[CPDateFormatter _separatorsCharacterSet] characterIsMember:character])
748  {
749  result += [self _stringFromToken:currentToken date:aDate];
750  result += character;
751  currentToken = [CPString new];
752  }
753  else
754  {
755  if ([currentToken length] && ![[currentToken characterAtIndex:0] isEqualToString:character])
756  {
757  result += [self _stringFromToken:currentToken date:aDate];
758  currentToken = [CPString new];
759  }
760 
761  currentToken += character;
762 
763  if (i == (length - 1))
764  result += [self _stringFromToken:currentToken date:aDate];
765  }
766  }
767 
768  return result;
769 }
770 
776 - (CPString)_stringFromToken:(CPString)aToken date:(CPDate)aDate
777 {
778  if (![aToken length])
779  return aToken;
780 
781  var character = [aToken characterAtIndex:0],
782  length = [aToken length],
783  timeZone = _timeZone;
784 
785  switch (character)
786  {
787  case @"G":
788  // TODO
789  CPLog.warn(@"Token not yet implemented " + aToken);
790  return [CPString new];
791 
792  case @"y":
793  var currentLength = [[CPString stringWithFormat:@"%i", aDate.getFullYear()] length];
794 
795  return [self _stringValueForValue:aDate.getFullYear() length:(length == 2)?length:currentLength];
796 
797  case @"Y":
798  var currentLength = [[CPString stringWithFormat:@"%i", aDate.getFullYear()] length];
799 
800  return [self _stringValueForValue:aDate.getFullYear() length:(length == 2)?length:currentLength];
801 
802  case @"u":
803  // TODO
804  CPLog.warn(@"Token not yet implemented " + aToken);
805  return [CPString new];
806 
807  case @"U":
808  // TODO
809  CPLog.warn(@"Token not yet implemented " + aToken);
810  return [CPString new];
811 
812  case @"Q":
813  var quarter = 1;
814 
815  if (aDate.getMonth() < 6 && aDate.getMonth() > 2)
816  quarter = 2;
817 
818  if (aDate.getMonth() > 5 && aDate.getMonth() < 9)
819  quarter = 3;
820 
821  if (aDate.getMonth() >= 9)
822  quarter = 4;
823 
824  if (length <= 2)
825  return [self _stringValueForValue:quarter length:MIN(2,length)];
826 
827  if (length == 3)
828  return [[self shortQuarterSymbols] objectAtIndex:(quarter - 1)];
829 
830  if (length >= 4)
831  return [[self quarterSymbols] objectAtIndex:(quarter - 1)];
832 
833  case @"q":
834  var quarter = 1;
835 
836  if (aDate.getMonth() < 6 && aDate.getMonth() > 2)
837  quarter = 2;
838 
839  if (aDate.getMonth() > 5 && aDate.getMonth() < 9)
840  quarter = 3;
841 
842  if (aDate.getMonth() >= 9)
843  quarter = 4;
844 
845  if (length <= 2)
846  return [self _stringValueForValue:quarter length:MIN(2,length)];
847 
848  if (length == 3)
849  return [[self shortStandaloneQuarterSymbols] objectAtIndex:(quarter - 1)];
850 
851  if (length >= 4)
852  return [[self standaloneQuarterSymbols] objectAtIndex:(quarter - 1)];
853 
854  case @"M":
855  var currentLength = [[CPString stringWithFormat:@"%i", aDate.getMonth() + 1] length];
856 
857  if (length <= 2)
858  return [self _stringValueForValue:(aDate.getMonth() + 1) length:MAX(currentLength,length)];
859 
860  if (length == 3)
861  return [[self shortMonthSymbols] objectAtIndex:aDate.getMonth()];
862 
863  if (length == 4)
864  return [[self monthSymbols] objectAtIndex:aDate.getMonth()];
865 
866  if (length >= 5)
867  return [[self veryShortMonthSymbols] objectAtIndex:aDate.getMonth()];
868 
869  case @"L":
870  var currentLength = [[CPString stringWithFormat:@"%i", aDate.getMonth() + 1] length];
871 
872  if (length <= 2)
873  return [self _stringValueForValue:(aDate.getMonth() + 1) length:MAX(currentLength,length)];
874 
875  if (length == 3)
876  return [[self shortStandaloneMonthSymbols] objectAtIndex:aDate.getMonth()];
877 
878  if (length == 4)
879  return [[self standaloneMonthSymbols] objectAtIndex:aDate.getMonth()];
880 
881  if (length >= 5)
882  return [[self veryShortStandaloneMonthSymbols] objectAtIndex:aDate.getMonth()];
883 
884  case @"I":
885  // Deprecated
886  CPLog.warn(@"Depreacted - Token not yet implemented " + aToken);
887  return [CPString new];
888 
889  case @"w":
890  var d = [aDate copy];
891 
892  d.setHours(0, 0, 0);
893  d.setDate(d.getDate() + 4 - (d.getDay() || 7));
894 
895  var yearStart = new Date(d.getFullYear(), 0, 1),
896  weekOfYear = Math.ceil((((d - yearStart) / 86400000) + 1) / 7);
897 
898  return [self _stringValueForValue:(weekOfYear + 1) length:MAX(2, length)];
899 
900  case @"W":
901  var firstDay = new Date(aDate.getFullYear(), aDate.getMonth(), 1).getDay(),
902  weekOfMonth = Math.ceil((aDate.getDate() + firstDay) / 7);
903 
904  return [self _stringValueForValue:weekOfMonth length:1];
905 
906  case @"d":
907  var currentLength = [[CPString stringWithFormat:@"%i", aDate.getDate()] length];
908 
909  return [self _stringValueForValue:aDate.getDate() length:MAX(length, currentLength)];
910 
911  case @"D":
912  var oneJan = new Date(aDate.getFullYear(), 0, 1),
913  dayOfYear = Math.ceil((aDate - oneJan) / 86400000),
914  currentLength = [[CPString stringWithFormat:@"%i", dayOfYear] length];
915 
916  return [self _stringValueForValue:dayOfYear length:MAX(currentLength, MIN(3, length))];
917 
918  case @"F":
919  var dayOfWeek = 1,
920  day = aDate.getDate();
921 
922  if (day > 7 && day < 15)
923  dayOfWeek = 2;
924 
925  if (day > 14 && day < 22)
926  dayOfWeek = 3;
927 
928  if (day > 21 && day < 29)
929  dayOfWeek = 4;
930 
931  if (day > 28)
932  dayOfWeek = 5;
933 
934  return [self _stringValueForValue:dayOfWeek length:1];
935 
936  case @"g":
937  CPLog.warn(@"Token not yet implemented " + aToken);
938  return [CPString new];
939 
940  case @"E":
941  var day = aDate.getDay();
942 
943  if (length <= 3)
944  return [[self shortWeekdaySymbols] objectAtIndex:day];
945 
946  if (length == 4)
947  return [[self weekdaySymbols] objectAtIndex:day];
948 
949  if (length >= 5)
950  return [[self veryShortWeekdaySymbols] objectAtIndex:day];
951 
952  case @"e":
953  var day = aDate.getDay();
954 
955  if (length <= 2)
956  return [self _stringValueForValue:(day + 1) length:MIN(2, length)];
957 
958  if (length == 3)
959  return [[self shortWeekdaySymbols] objectAtIndex:day];
960 
961  if (length == 4)
962  return [[self weekdaySymbols] objectAtIndex:day];
963 
964  if (length >= 5)
965  return [[self veryShortWeekdaySymbols] objectAtIndex:day];
966 
967  case @"c":
968  var day = aDate.getDay();
969 
970  if (length <= 2)
971  return [self _stringValueForValue:(day + 1) length:aDate.getDay().toString().length];
972 
973  if (length == 3)
974  return [[self shortStandaloneWeekdaySymbols] objectAtIndex:day];
975 
976  if (length == 4)
977  return [[self standaloneWeekdaySymbols] objectAtIndex:day];
978 
979  if (length >= 5)
980  return [[self veryShortStandaloneWeekdaySymbols] objectAtIndex:day];
981 
982  case @"a":
983 
984  if (aDate.getHours() > 11)
985  return [self PMSymbol];
986  else
987  return [self AMSymbol];
988 
989  case @"h":
990  var hours = aDate.getHours();
991 
992  if ([self _isAmericanFormat] || [self _isEnglishFormat])
993  {
994  if (hours == 0)
995  hours = 12;
996  else if (hours > 12)
997  hours = hours - 12;
998  }
999 
1000  var currentLength = [[CPString stringWithFormat:@"%i", hours] length];
1001 
1002  return [self _stringValueForValue:hours length:MAX(currentLength, MIN(2, length))];
1003 
1004  case @"H":
1005  var currentLength = [[CPString stringWithFormat:@"%i", aDate.getHours()] length];
1006 
1007  return [self _stringValueForValue:aDate.getHours() length:MAX(currentLength, MIN(2, length))];
1008 
1009  case @"K":
1010  var hours = aDate.getHours();
1011 
1012  if (hours > 12)
1013  hours -= 12;
1014 
1015  var currentLength = [[CPString stringWithFormat:@"%i", hours] length];
1016 
1017  return [self _stringValueForValue:hours length:MAX(currentLength, MIN(2, length))];
1018 
1019  case @"k":
1020  var hours = aDate.getHours();
1021 
1022  if (aDate.getHours() == 0)
1023  hours = 24;
1024 
1025  var currentLength = [[CPString stringWithFormat:@"%i", hours] length];
1026 
1027  return [self _stringValueForValue:hours length:MAX(currentLength, MIN(2, length))];
1028 
1029  case @"j":
1030  CPLog.warn(@"Token not yet implemented " + aToken);
1031  return [CPString new];
1032 
1033  case @"m":
1034  var currentLength = [[CPString stringWithFormat:@"%i", aDate.getMinutes()] length];
1035 
1036  return [self _stringValueForValue:aDate.getMinutes() length:MAX(currentLength, MIN(2, length))];
1037 
1038  case @"s":
1039  var currentLength = [[CPString stringWithFormat:@"%i", aDate.getMinutes()] length];
1040 
1041  return [self _stringValueForValue:aDate.getSeconds() length:MIN(2, length)];
1042 
1043  case @"S":
1044  return [self _stringValueForValue:aDate.getMilliseconds() length:length];
1045 
1046  case @"A":
1047  var value = aDate.getHours() * 60 * 60 * 1000 + aDate.getMinutes() * 60 * 1000 + aDate.getSeconds() * 1000 + aDate.getMilliseconds();
1048 
1049  return [self _stringValueForValue:value length:value.toString().length];
1050 
1051  case @"z":
1052  if (length <= 3)
1053  return [timeZone localizedName:CPTimeZoneNameStyleShortDaylightSaving locale:_locale];
1054  else
1055  return [timeZone localizedName:CPTimeZoneNameStyleDaylightSaving locale:_locale];
1056 
1057  case @"Z":
1058  var seconds = [timeZone secondsFromGMT],
1059  minutes = seconds / 60,
1060  hours = minutes / 60,
1061  result,
1062  diffMinutes = (hours - parseInt(hours)) * 100 * 60 / 100;
1063 
1064  if (length <= 3)
1065  {
1066  result = diffMinutes.toString();
1067 
1068  while ([result length] < 2)
1069  result = @"0" + result;
1070 
1071  result = ABS(parseInt(hours)) + result;
1072 
1073  while ([result length] < 4)
1074  result = @"0" + result;
1075 
1076  if (seconds > 0)
1077  result = @"+" + result;
1078  else
1079  result = @"-" + result;
1080 
1081  return result;
1082  }
1083  else if (length == 4)
1084  {
1085  result = diffMinutes.toString();
1086 
1087  while ([result length] < 2)
1088  result = @"0" + result;
1089 
1090  result = @":" + result;
1091  result = ABS(parseInt(hours)) + result;
1092 
1093  while ([result length] < 5)
1094  result = @"0" + result;
1095 
1096  if (seconds > 0)
1097  result = @"+" + result;
1098  else
1099  result = @"-" + result;
1100 
1101  return @"GMT" + result;
1102  }
1103  else
1104  {
1105  result = diffMinutes.toString();
1106 
1107  while ([result length] < 2)
1108  result = @"0" + result;
1109 
1110  result = @":" + result;
1111  result = ABS(parseInt(hours)) + result;
1112 
1113  while ([result length] < 5)
1114  result = @"0" + result;
1115 
1116  if (seconds > 0)
1117  result = @"+" + result;
1118  else
1119  result = @"-" + result;
1120 
1121  return result;
1122  }
1123 
1124  case @"v":
1125  if (length == 1)
1126  return [timeZone localizedName:CPTimeZoneNameStyleShortGeneric locale:_locale];
1127  else if (length == 4)
1128  return [timeZone localizedName:CPTimeZoneNameStyleGeneric locale:_locale];
1129 
1130  return @" ";
1131 
1132  case @"V":
1133  if (length == 1)
1134  {
1135  return [timeZone localizedName:CPTimeZoneNameStyleShortDaylightSaving locale:_locale];
1136  }
1137  else if (length == 4)
1138  {
1139  CPLog.warn(@"No pattern found for " + aToken);
1140  return @"";
1141  }
1142 
1143  return @" ";
1144 
1145  default:
1146  CPLog.warn(@"No pattern found for " + aToken);
1147  return aToken;
1148  }
1149 
1150  return [CPString new];
1151 }
1152 
1153 
1154 #pragma mark -
1155 #pragma mark datefromString
1156 
1162 - (CPDate)dateFromString:(CPString)aString
1163 {
1164  var format;
1165 
1166  if (_dateFormat != nil)
1167  return [self _dateFromString:aString format:_dateFormat];
1168 
1169  switch (_dateStyle)
1170  {
1172  format = @"";
1173  break;
1174 
1176  if ([self _isAmericanFormat])
1177  format = @"M/d/yy";
1178  else
1179  format = @"dd/MM/yy";
1180 
1181  break;
1182 
1184  if ([self _isAmericanFormat])
1185  format = @"MMM d, Y";
1186  else
1187  format = @"d MMM Y";
1188 
1189  break;
1190 
1192  if ([self _isAmericanFormat])
1193  format = @"MMMM d, Y";
1194  else
1195  format = @"d MMMM Y";
1196 
1197  break;
1198 
1200  if ([self _isAmericanFormat])
1201  format = @"EEEE, MMMM d, Y";
1202  else
1203  format = @"EEEE d MMMM Y";
1204 
1205  break;
1206 
1207  default:
1208  format = @"";
1209  }
1210 
1211  switch (_timeStyle)
1212  {
1214  format += @"";
1215  break;
1216 
1218  if ([self _isEnglishFormat])
1219  format += @" h:mm a";
1220  else
1221  format += @" H:mm";
1222  break;
1223 
1225  if ([self _isEnglishFormat])
1226  format += @" h:mm:ss a";
1227  else
1228  format += @" H:mm:ss";
1229  break;
1230 
1232  if ([self _isEnglishFormat])
1233  format += @" h:mm:ss a z";
1234  else
1235  format += @" H:mm:ss z";
1236  break;
1237 
1239  if ([self _isEnglishFormat])
1240  format += @" h:mm:ss a zzzz";
1241  else
1242  format += @" h:mm:ss zzzz";
1243  break;
1244 
1245  default:
1246  format += @"";
1247  }
1248 
1249  return [self _dateFromString:aString format:format];
1250 }
1251 
1258 - (BOOL)getObjectValue:(idRef)anObject forString:(CPString)aString errorDescription:(CPStringRef)anError
1259 {
1260  var value = [self dateFromString:aString];
1261  @deref(anObject) = value;
1262 
1263  if (!value)
1264  {
1265  if (anError)
1266  @deref(anError) = @"The value \"" + aString + "\" is invalid.";
1267 
1268  return NO;
1269  }
1270 
1271  return YES;
1272 }
1273 
1279 - (CPDate)_dateFromString:(CPString)aString format:(CPString)aFormat
1280 {
1281  // Interpret @"" as the date 2000-01-01 00:00:00 +0000, like in Cocoa. No idea why they picked this particular date.
1282  if (!aString)
1284 
1285  if (aFormat == nil)
1286  return nil;
1287 
1288  var currentToken = [CPString new],
1289  isTextToken = NO,
1290  tokens = [CPArray array],
1291  dateComponents = [CPArray array],
1292  patternTokens = [CPArray array];
1293 
1294  for (var i = 0; i < [aFormat length]; i++)
1295  {
1296  var character = [aFormat characterAtIndex:i];
1297 
1298  if (isTextToken)
1299  {
1300  if ([character isEqualToString:@"'"])
1301  currentToken = [CPString new];
1302 
1303  continue;
1304  }
1305 
1306  if ([character isEqualToString:@"'"])
1307  {
1308  if (!isTextToken)
1309  isTextToken = YES;
1310 
1311  continue;
1312  }
1313 
1314  if ([character isEqualToString:@","] || [character isEqualToString:@":"] || [character isEqualToString:@"/"] || [character isEqualToString:@"-"] || [character isEqualToString:@" "])
1315  {
1316  [tokens addObject:currentToken];
1317 
1318  if ([patternStringTokens containsObject:currentToken])
1319  [patternTokens addObject:[tokens count] - 1];
1320 
1321  currentToken = [CPString new];
1322  }
1323  else
1324  {
1325  if ([currentToken length] && ![[currentToken characterAtIndex:0] isEqualToString:character])
1326  {
1327  [tokens addObject:currentToken];
1328 
1329  if ([patternStringTokens containsObject:currentToken])
1330  [patternTokens addObject:[tokens count] - 1];
1331 
1332  currentToken = [CPString new];
1333  }
1334 
1335  currentToken += character;
1336 
1337  if (i == ([aFormat length] - 1))
1338  {
1339  [tokens addObject:currentToken];
1340 
1341  if ([patternStringTokens containsObject:currentToken])
1342  [patternTokens addObject:[tokens count] - 1];
1343  }
1344  }
1345  }
1346 
1347  isTextToken = NO;
1348  currentToken = [CPString new];
1349 
1350  var currentIndexSpecialPattern = 0;
1351 
1352  if ([patternTokens count] == 0)
1353  [patternTokens addObject:CPNotFound];
1354 
1355  for (var i = 0; i < [aString length]; i++)
1356  {
1357  var character = [aString characterAtIndex:i];
1358 
1359  if (isTextToken)
1360  {
1361  if ([character isEqualToString:@"'"])
1362  currentToken = [CPString new];
1363 
1364  continue;
1365  }
1366 
1367  if ([character isEqualToString:@"'"])
1368  {
1369  if (!isTextToken)
1370  isTextToken = YES;
1371 
1372  continue;
1373  }
1374 
1375  // Need to do this to check if the word match with the token. We can get some words with space...
1376  if ([dateComponents count] == [patternTokens objectAtIndex:currentIndexSpecialPattern])
1377  {
1378  var j = [self _lastIndexMatchedString:aString token:[tokens objectAtIndex:[dateComponents count]] index:i];
1379 
1380  if (j == CPNotFound)
1381  return nil;
1382 
1383  currentIndexSpecialPattern++;
1384  [dateComponents addObject:[aString substringWithRange:CPMakeRange(i, (j - i))]];
1385  i = j;
1386 
1387  continue;
1388  }
1389 
1390  if ([character isEqualToString:@","] || [character isEqualToString:@":"] || [character isEqualToString:@"/"] || [character isEqualToString:@"-"] || [character isEqualToString:@" "])
1391  {
1392  [dateComponents addObject:currentToken];
1393  currentToken = [CPString new];
1394  }
1395  else
1396  {
1397  currentToken += character;
1398 
1399  if (i == ([aString length] - 1))
1400  [dateComponents addObject:currentToken];
1401  }
1402  }
1403 
1404  if ([dateComponents count] != [tokens count])
1405  return nil;
1406 
1407  return [self _dateFromTokens:tokens dateComponents:dateComponents];
1408 }
1409 
1410 - (CPDate)_dateFromTokens:(CPArray)tokens dateComponents:(CPArray)dateComponents
1411 {
1412  var timeZoneseconds = [_timeZone secondsFromGMT],
1413  dateArray = [2000, 01, 01, 00, 00, 00, @"+0000"],
1414  isPM = NO,
1415  dayOfYear,
1416  dayIndexInWeek,
1417  weekOfYear,
1418  weekOfMonth;
1419 
1420  for (var i = 0; i < [tokens count]; i++)
1421  {
1422  var token = [tokens objectAtIndex:i],
1423  dateComponent = [dateComponents objectAtIndex:i],
1424  character = [token characterAtIndex:0],
1425  length = [token length];
1426 
1427  switch (character)
1428  {
1429  case @"G":
1430  // TODO
1431  CPLog.warn(@"Token not yet implemented " + token);
1432  break;
1433 
1434  case @"y":
1435  var u = _twoDigitStartDate.getFullYear() % 10,
1436  d = parseInt(_twoDigitStartDate.getFullYear() / 10) % 10,
1437  c = parseInt(_twoDigitStartDate.getFullYear() / 100) % 10,
1438  m = parseInt(_twoDigitStartDate.getFullYear() / 1000) % 10;
1439 
1440  if (length == 2 && dateComponent.length == 2)
1441  {
1442  if ((u + d * 10) >= parseInt(dateComponent))
1443  dateArray[0] = (c + 1) * 100 + m * 1000 + parseInt(dateComponent);
1444  else
1445  dateArray[0] = c * 100 + m * 1000 + parseInt(dateComponent);
1446  }
1447  else
1448  {
1449  dateArray[0] = parseInt(dateComponent);
1450  }
1451 
1452  break;
1453 
1454  case @"Y":
1455  var u = _twoDigitStartDate.getFullYear() % 10,
1456  d = parseInt(_twoDigitStartDate.getFullYear() / 10) % 10,
1457  c = parseInt(_twoDigitStartDate.getFullYear() / 100) % 10,
1458  m = parseInt(_twoDigitStartDate.getFullYear() / 1000) % 10;
1459 
1460  if (length == 2 && dateComponent.length == 2)
1461  {
1462  if ((u + d * 10) >= parseInt(dateComponent))
1463  dateArray[0] = (c + 1) * 100 + m * 1000 + parseInt(dateComponent);
1464  else
1465  dateArray[0] = c * 100 + m * 1000 + parseInt(dateComponent);
1466  }
1467  else
1468  {
1469  dateArray[0] = parseInt(dateComponent);
1470  }
1471 
1472  break;
1473 
1474  case @"u":
1475  // TODO
1476  CPLog.warn(@"Token not yet implemented " + token);
1477  break;
1478 
1479  case @"U":
1480  // TODO
1481  CPLog.warn(@"Token not yet implemented " + token);
1482  break;
1483 
1484  case @"Q":
1485  var month;
1486 
1487  if (length <= 2)
1488  month = (parseInt(dateComponent) - 1) * 3;
1489 
1490  if (length == 3)
1491  {
1492  if (![[self shortQuarterSymbols] containsObject:dateComponent])
1493  return nil;
1494 
1495  month = [[self shortQuarterSymbols] indexOfObject:dateComponent] * 3;
1496  }
1497 
1498  if (length >= 4)
1499  {
1500  if (![[self quarterSymbols] containsObject:dateComponent])
1501  return nil;
1502 
1503  month = [[self quarterSymbols] indexOfObject:dateComponent] * 3;
1504  }
1505 
1506  if (month > 11)
1507  return nil;
1508 
1509  dateArray[1] = month + 1;
1510  break;
1511 
1512  case @"q":
1513  var month;
1514 
1515  if (length <= 2)
1516  month = (parseInt(dateComponent) - 1) * 3;
1517 
1518  if (length == 3)
1519  {
1520  if (![[self shortQuarterSymbols] containsObject:dateComponent])
1521  return nil;
1522 
1523  month = [[self shortQuarterSymbols] indexOfObject:dateComponent] * 3;
1524  }
1525 
1526  if (length >= 4)
1527  {
1528  if (![[self quarterSymbols] containsObject:dateComponent])
1529  return nil;
1530 
1531  month = [[self quarterSymbols] indexOfObject:dateComponent] * 3;
1532  }
1533 
1534  if (month > 11)
1535  return nil;
1536 
1537  dateArray[1] = month + 1;
1538  break;
1539 
1540  case @"M":
1541  var month;
1542 
1543  if (length <= 2)
1544  month = parseInt(dateComponent);
1545 
1546  if (length == 3)
1547  {
1548  if (![[self shortMonthSymbols] containsObject:dateComponent])
1549  return nil;
1550 
1551  month = [[self shortMonthSymbols] indexOfObject:dateComponent] + 1;
1552  }
1553 
1554  if (length == 4)
1555  {
1556  if (![[self monthSymbols] containsObject:dateComponent])
1557  return nil;
1558 
1559  month = [[self monthSymbols] indexOfObject:dateComponent] + 1;
1560  }
1561 
1562  if (month > 12 || length >= 5)
1563  return nil;
1564 
1565  dateArray[1] = month;
1566  break;
1567 
1568  case @"L":
1569  var month;
1570 
1571  if (length <= 2)
1572  month = parseInt(dateComponent);
1573 
1574  if (length == 3)
1575  {
1576  if (![[self shortStandaloneMonthSymbols] containsObject:dateComponent])
1577  return nil;
1578 
1579  month = [[self shortStandaloneMonthSymbols] indexOfObject:dateComponent] + 1;
1580  }
1581 
1582  if (length == 4)
1583  {
1584  if (![[self standaloneMonthSymbols] containsObject:dateComponent])
1585  return nil;
1586 
1587  month = [[self standaloneMonthSymbols] indexOfObject:dateComponent] + 1;
1588  }
1589 
1590  if (month > 12 || length >= 5)
1591  return nil;
1592 
1593  dateArray[1] = month;
1594  break;
1595 
1596  case @"I":
1597  // Deprecated
1598  CPLog.warn(@"Depreacted - Token not yet implemented " + token);
1599  break;
1600 
1601  case @"w":
1602  if (dateComponent > 52)
1603  return nil;
1604 
1605  weekOfYear = dateComponent;
1606  break;
1607 
1608  case @"W":
1609  if (dateComponent > 52)
1610  return nil;
1611 
1612  weekOfMonth = dateComponent;
1613  break;
1614 
1615  case @"d":
1616  dateArray[2] = parseInt(dateComponent);
1617  break;
1618 
1619  case @"D":
1620  if (isNaN(parseInt(dateComponent)) || parseInt(dateComponent) > 345)
1621  return nil;
1622 
1623  dayOfYear = parseInt(dateComponent);
1624  break;
1625 
1626  case @"F":
1627  if (isNaN(parseInt(dateComponent)) || parseInt(dateComponent) > 5 || parseInt(dateComponent) == 0)
1628  return nil;
1629 
1630  if (parseInt(dateComponent) == 1)
1631  dateArray[2] = 1;
1632 
1633  if (parseInt(dateComponent) == 2)
1634  dateArray[2] = 8;
1635 
1636  if (parseInt(dateComponent) == 3)
1637  dateArray[2] = 15;
1638 
1639  if (parseInt(dateComponent) == 4)
1640  dateArray[2] = 22;
1641 
1642  if (parseInt(dateComponent) == 5)
1643  dateArray[2] = 29;
1644 
1645  break;
1646 
1647  case @"g":
1648  CPLog.warn(@"Token not yet implemented " + token);
1649  break;
1650 
1651  case @"E":
1652  if (length <= 3)
1653  dayIndexInWeek = [[self shortWeekdaySymbols] indexOfObject:dateComponent];
1654 
1655  if (length == 4)
1656  dayIndexInWeek = [[self weekdaySymbols] indexOfObject:dateComponent];
1657 
1658  if (dayIndexInWeek == CPNotFound || length >= 5)
1659  return nil;
1660 
1661  break;
1662 
1663  case @"e":
1664  if (length <= 2 && isNaN(parseInt(dateComponent)))
1665  return nil;
1666 
1667  if (length <= 2)
1668  dayIndexInWeek = parseInt(dateComponent);
1669 
1670  if (length == 3)
1671  dayIndexInWeek = [[self shortWeekdaySymbols] indexOfObject:dateComponent];
1672 
1673  if (length == 4)
1674  dayIndexInWeek = [[self weekdaySymbols] indexOfObject:dateComponent];
1675 
1676  if (dayIndexInWeek == CPNotFound || length >= 5)
1677  return nil;
1678 
1679  break;
1680 
1681  case @"c":
1682  if (length <= 2 && isNaN(parseInt(dateComponent)))
1683  return nil;
1684 
1685  if (length <= 2)
1686  dayIndexInWeek = dateComponent;
1687 
1688  if (length == 3)
1689  dayIndexInWeek = [[self shortStandaloneWeekdaySymbols] indexOfObject:dateComponent];
1690 
1691  if (length == 4)
1692  dayIndexInWeek = [[self standaloneWeekdaySymbols] indexOfObject:dateComponent];
1693 
1694  if (length == 5)
1695  dayIndexInWeek = [[self veryShortStandaloneWeekdaySymbols] indexOfObject:dateComponent];
1696 
1697  if (dayIndexInWeek == CPNotFound || length >= 5)
1698  return nil;
1699 
1700  break;
1701 
1702  case @"a":
1703  if (![dateComponent isEqualToString:[self PMSymbol]] && ![dateComponent isEqualToString:[self AMSymbol]])
1704  return nil;
1705 
1706  if ([dateComponent isEqualToString:[self PMSymbol]])
1707  isPM = YES;
1708 
1709  break;
1710 
1711  case @"h":
1712  if (parseInt(dateComponent) < 0 || parseInt(dateComponent) > 12)
1713  return nil;
1714 
1715  dateArray[3] = parseInt(dateComponent);
1716  break;
1717 
1718  case @"H":
1719  if (parseInt(dateComponent) < 0 || parseInt(dateComponent) > 23)
1720  return nil;
1721 
1722  dateArray[3] = parseInt(dateComponent);
1723  break;
1724 
1725  case @"K":
1726  if (parseInt(dateComponent) < 0 || parseInt(dateComponent) > 11)
1727  return nil;
1728 
1729  dateArray[3] = parseInt(dateComponent);
1730  break;
1731 
1732  case @"k":
1733  if (parseInt(dateComponent) < 0 || parseInt(dateComponent) > 12)
1734  return nil;
1735 
1736  dateArray[3] = parseInt(dateComponent);
1737  break;
1738 
1739  case @"j":
1740  CPLog.warn(@"Token not yet implemented " + token);
1741  break;
1742 
1743  case @"m":
1744  var minutes = parseInt(dateComponent);
1745 
1746  if (minutes > 59)
1747  return nil;
1748 
1749  dateArray[4] = minutes;
1750  break;
1751 
1752  case @"s":
1753  var seconds = parseInt(dateComponent);
1754 
1755  if (seconds > 59)
1756  return nil;
1757 
1758  dateArray[5] = seconds;
1759  break;
1760 
1761  case @"S":
1762  if (isNaN(parseInt(dateComponent)))
1763  return nil;
1764 
1765  break;
1766 
1767  case @"A":
1768  if (isNaN(parseInt(dateComponent)))
1769  return nil;
1770 
1771  var millisecondsInDay = parseInt(dateComponent),
1772  tmpDate = new Date();
1773 
1774  tmpDate.setHours(0);
1775  tmpDate.setMinutes(0);
1776  tmpDate.setSeconds(0);
1777  tmpDate.setMilliseconds(0);
1778 
1779  tmpDate.setMilliseconds(millisecondsInDay);
1780 
1781  dateArray[3] = tmpDate.getHours();
1782  dateArray[4] = tmpDate.getMinutes();
1783  dateArray[5] = tmpDate.getSeconds();
1784  break;
1785 
1786  case @"z":
1787  if (length < 4)
1788  timeZoneseconds = [self _secondsFromTimeZoneString:dateComponent style:CPTimeZoneNameStyleShortDaylightSaving];
1789  else
1790  timeZoneseconds = [self _secondsFromTimeZoneString:dateComponent style:CPTimeZoneNameStyleDaylightSaving];
1791 
1792  if (!timeZoneseconds)
1793  timeZoneseconds = [self _secondsFromTimeZoneDefaultFormatString:dateComponent];
1794 
1795  if (!timeZoneseconds)
1796  return nil;
1797 
1798  timeZoneseconds = timeZoneseconds + 60 * 60;
1799 
1800  break;
1801 
1802  case @"Z":
1803  timeZoneseconds = [self _secondsFromTimeZoneDefaultFormatString:dateComponent];
1804 
1805  if (!timeZoneseconds)
1806  return nil;
1807 
1808  timeZoneseconds = timeZoneseconds + 60 * 60;
1809 
1810  break;
1811 
1812  case @"v":
1813  if (length <= 3)
1814  timeZoneseconds = [self _secondsFromTimeZoneString:dateComponent style:CPTimeZoneNameStyleShortGeneric];
1815  else
1816  timeZoneseconds = [self _secondsFromTimeZoneString:dateComponent style:CPTimeZoneNameStyleGeneric];
1817 
1818  if (!timeZoneseconds && length == 4)
1819  timeZoneseconds = [self _secondsFromTimeZoneDefaultFormatString:dateComponent];
1820 
1821  if (!timeZoneseconds)
1822  return nil;
1823 
1824  timeZoneseconds = timeZoneseconds + 60 * 60;
1825 
1826  break;
1827 
1828  case @"V":
1829  if (length <= 3)
1830  timeZoneseconds = [self _secondsFromTimeZoneString:dateComponent style:CPTimeZoneNameStyleShortStandard];
1831  else
1832  timeZoneseconds = [self _secondsFromTimeZoneString:dateComponent style:CPTimeZoneNameStyleStandard];
1833 
1834  if (!timeZoneseconds)
1835  timeZoneseconds = [self _secondsFromTimeZoneDefaultFormatString:dateComponent];
1836 
1837  if (!timeZoneseconds)
1838  return nil;
1839 
1840  timeZoneseconds = timeZoneseconds + 60 * 60;
1841 
1842  break;
1843 
1844  default:
1845  CPLog.warn(@"No pattern found for " + token);
1846  return nil;
1847  }
1848  }
1849 
1850  // Make the calcul day of the year
1851  if (dayOfYear)
1852  {
1853  var tmpDate = new Date();
1854  tmpDate.setFullYear(dateArray[0]);
1855  tmpDate.setMonth(0);
1856 
1857  tmpDate.setDate(dayOfYear);
1858 
1859  dateArray[1] = tmpDate.getMonth() + 1;
1860  dateArray[2] = tmpDate.getDate();
1861  }
1862 
1863  if (weekOfMonth)
1864  dateArray[2] = (weekOfMonth - 1) * 7 + 1;
1865 
1866  if (weekOfYear)
1867  {
1868  var tmpDate = new Date();
1869  tmpDate.setFullYear(dateArray[0]);
1870  tmpDate.setMonth(0);
1871  tmpDate.setDate(1);
1872 
1873  while (tmpDate.getDay() != 0)
1874  tmpDate.setDate(tmpDate.getDate() + 1);
1875 
1876  tmpDate.setDate(tmpDate.getDate() + (weekOfYear - 1) * 7);
1877 
1878  dateArray[1] = tmpDate.getMonth() + 1;
1879  dateArray[2] = tmpDate.getDate() - 1;
1880  }
1881 
1882  // Check if the day is possible in the current month
1883  var tmpDate = new Date();
1884  tmpDate.setMonth(dateArray[1] - 1);
1885  tmpDate.setFullYear(dateArray[0]);
1886 
1887  if (dateArray[2] <= 0 || dateArray[2] > [tmpDate _daysInMonth])
1888  return nil;
1889 
1890  // PM hours
1891  if (isPM)
1892  dateArray[3] += 12;
1893 
1894  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])))
1895  return nil;
1896 
1897  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]]];
1898  dateResult.setSeconds(dateResult.getSeconds() - timeZoneseconds + 60 * 60);
1899 
1900  return dateResult;
1901 }
1902 
1903 
1904 #pragma mark -
1905 #pragma mark Utils
1906 
1907 - (CPString)_stringValueForValue:(id)aValue length:(int)length
1908 {
1909  var string = [CPString stringWithFormat:@"%i", aValue];
1910 
1911  if ([string length] == length)
1912  return string;
1913 
1914  if ([string length] > length)
1915  return [string substringFromIndex:([string length] - length)];
1916 
1917  while ([string length] < length)
1918  string = [CPString stringWithFormat:@"0%s", string];
1919 
1920  return string;
1921 }
1922 
1925 - (BOOL)_isAmericanFormat
1926 {
1927  return [[_locale objectForKey:CPLocaleCountryCode] isEqualToString:@"US"];
1928 }
1929 
1932 - (BOOL)_isEnglishFormat
1933 {
1934  return [[_locale objectForKey:CPLocaleLanguageCode] isEqualToString:@"en"];
1935 }
1936 
1939 - (int)_secondsFromTimeZoneDefaultFormatString:(CPString)aTimeZoneFormatString
1940 {
1941  var format = new RegExp("\\w*([HPG-GMT])?([+-])(\\d{1,2})([:])?(\\d{2})\\w*"),
1942  result = aTimeZoneFormatString.match(new RegExp(format)),
1943  seconds = 0;
1944 
1945  if (!result)
1946  return nil;
1947 
1948  seconds = result[3] * 60 * 60 + result[5] * 60;
1949 
1950  if ([result[2] isEqualToString:@"-"])
1951  seconds = -seconds;
1952 
1953  return seconds;
1954 }
1955 
1958 - (int)_secondsFromTimeZoneString:(CPString)aTimeZoneString style:(NSTimeZoneNameStyle)aStyle
1959 {
1960  var timeZone = [CPTimeZone _timeZoneFromString:aTimeZoneString style:aStyle locale:_locale];
1961 
1962  if (!timeZone)
1963  return nil;
1964 
1965  return [timeZone secondsFromGMT];
1966 }
1967 
1974 - (int)_lastIndexMatchedString:(CPString)aString token:(CPString)aToken index:anIndex
1975 {
1976  var character = [aToken characterAtIndex:0],
1977  length = [aToken length],
1978  targetedArray,
1979  format = new RegExp("\\w*([HPG-GMT])?([+-])(\\d{1,2})([:])?(\\d{2})\\w*"),
1980  result = aString.match(new RegExp(format));
1981 
1982  switch (character)
1983  {
1984  case @"Q":
1985  if (length == 3)
1986  targetedArray = [self shortQuarterSymbols];
1987 
1988  if (length >= 4)
1989  targetedArray = [self quarterSymbols];
1990 
1991  break;
1992 
1993  case @"q":
1994  if (length == 3)
1995  targetedArray = [self shortStandaloneQuarterSymbols];
1996 
1997  if (length >= 4)
1998  targetedArray = [self standaloneQuarterSymbols];
1999 
2000  break;
2001 
2002  case @"M":
2003  if (length == 3)
2004  targetedArray = [self shortMonthSymbols];
2005 
2006  if (length == 4)
2007  targetedArray = [self monthSymbols];
2008 
2009  if (length >= 5)
2010  targetedArray = [self veryShortMonthSymbols];
2011 
2012  break;
2013 
2014  case @"L":
2015  if (length == 3)
2016  targetedArray = [self shortStandaloneMonthSymbols];
2017 
2018  if (length == 4)
2019  targetedArray = [self standaloneMonthSymbols];
2020 
2021  if (length >= 5)
2022  targetedArray = [self veryShortStandaloneMonthSymbols];
2023 
2024  break;
2025 
2026  case @"E":
2027  if (length <= 3)
2028  targetedArray = [self shortWeekdaySymbols];
2029 
2030  if (length == 4)
2031  targetedArray = [self weekdaySymbols];
2032 
2033  if (length >= 5)
2034  targetedArray = [self veryShortWeekdaySymbols];
2035 
2036  break;
2037 
2038  case @"e":
2039  if (length == 3)
2040  targetedArray = [self shortWeekdaySymbols];
2041 
2042  if (length == 4)
2043  targetedArray = [self weekdaySymbols];
2044 
2045  if (length >= 5)
2046  targetedArray = [self veryShortWeekdaySymbols];
2047 
2048  break;
2049 
2050  case @"c":
2051  if (length == 3)
2052  targetedArray = [self shortStandaloneWeekdaySymbols];
2053 
2054  if (length == 4)
2055  targetedArray = [self standaloneWeekdaySymbols];
2056 
2057  if (length >= 5)
2058  targetedArray = [self veryShortStandaloneWeekdaySymbols];
2059 
2060  break;
2061 
2062  case @"a":
2063  targetedArray = [[self PMSymbol], [self AMSymbol]];
2064  break;
2065 
2066  case @"z":
2067  if (length <= 3)
2068  targetedArray = [CPTimeZone _namesForStyle:CPTimeZoneNameStyleShortDaylightSaving locale:_locale];
2069  else
2070  targetedArray = [CPTimeZone _namesForStyle:CPTimeZoneNameStyleDaylightSaving locale:_locale];
2071 
2072  if (result)
2073  return anIndex + [result objectAtIndex:0].length;
2074 
2075  break;
2076 
2077  case @"Z":
2078  if (result)
2079  return anIndex + [result objectAtIndex:0].length;
2080 
2081  return CPNotFound;
2082 
2083  case @"v":
2084  if (length == 1)
2085  targetedArray = [CPTimeZone _namesForStyle:CPTimeZoneNameStyleShortGeneric locale:_locale];
2086  else if (length == 4)
2087  targetedArray = [CPTimeZone _namesForStyle:CPTimeZoneNameStyleGeneric locale:_locale];
2088 
2089  if (result)
2090  return anIndex + [result objectAtIndex:0].length;
2091 
2092  break;
2093 
2094  case @"V":
2095  if (length == 1)
2096  targetedArray = [CPTimeZone _namesForStyle:CPTimeZoneNameStyleShortStandard locale:_locale];
2097 
2098  if (result)
2099  return anIndex + [result objectAtIndex:0].length;
2100 
2101  break;
2102 
2103  default:
2104  CPLog.warn(@"No pattern found for " + aToken);
2105  return CPNotFound;
2106  }
2107 
2108  for (var i = 0; i < [targetedArray count]; i++)
2109  {
2110  var currentObject = [targetedArray objectAtIndex:i],
2111  range = [aString rangeOfString:currentObject];
2112 
2113  if (range.length == 0)
2114  continue;
2115 
2116  character = [aString characterAtIndex:(anIndex + range.length)];
2117 
2118  if ([character isEqualToString:@"'"] || [character isEqualToString:@","] || [character isEqualToString:@":"] || [character isEqualToString:@"/"] || [character isEqualToString:@"-"] || [character isEqualToString:@" "] || [character isEqualToString:@""])
2119  return anIndex + range.length;
2120  }
2121 
2122  return CPNotFound;
2123 }
2124 
2125 @end
2126 
2127 var CPDateFormatterDateStyleKey = @"CPDateFormatterDateStyle",
2128  CPDateFormatterTimeStyleKey = @"CPDateFormatterTimeStyleKey",
2129  CPDateFormatterFormatterBehaviorKey = @"CPDateFormatterFormatterBehaviorKey",
2130  CPDateFormatterDoseRelativeDateFormattingKey = @"CPDateFormatterDoseRelativeDateFormattingKey",
2131  CPDateFormatterDateFormatKey = @"CPDateFormatterDateFormatKey",
2132  CPDateFormatterAllowNaturalLanguageKey = @"CPDateFormatterAllowNaturalLanguageKey",
2133  CPDateFormatterLocaleKey = @"CPDateFormatterLocaleKey";
2134 
2136 
2137 - (id)initWithCoder:(CPCoder)aCoder
2138 {
2139  self = [super initWithCoder:aCoder];
2140 
2141  if (self)
2142  {
2143  _allowNaturalLanguage = [aCoder decodeBoolForKey:CPDateFormatterAllowNaturalLanguageKey];
2144  _dateFormat = [aCoder decodeObjectForKey:CPDateFormatterDateFormatKey];
2145  _dateStyle = [aCoder decodeIntForKey:CPDateFormatterDateStyleKey];
2146  _doesRelativeDateFormatting = [aCoder decodeBoolForKey:CPDateFormatterDoseRelativeDateFormattingKey];
2147  _formatterBehavior = [aCoder decodeIntForKey:CPDateFormatterFormatterBehaviorKey];
2148  _locale = [aCoder decodeObjectForKey:CPDateFormatterLocaleKey];
2149  _timeStyle = [aCoder decodeIntForKey:CPDateFormatterTimeStyleKey];
2150  }
2151 
2152  [self _init];
2153 
2154  return self;
2155 }
2156 
2157 - (void)encodeWithCoder:(CPCoder)aCoder
2158 {
2159  [super encodeWithCoder:aCoder];
2160 
2161  [aCoder encodeBool:_allowNaturalLanguage forKey:CPDateFormatterAllowNaturalLanguageKey];
2162  [aCoder encodeInt:_dateStyle forKey:CPDateFormatterDateStyleKey];
2163  [aCoder encodeObject:_dateFormat forKey:CPDateFormatterDateFormatKey];
2164  [aCoder encodeBool:_doesRelativeDateFormatting forKey:CPDateFormatterDoseRelativeDateFormattingKey];
2165  [aCoder encodeInt:_formatterBehavior forKey:CPDateFormatterFormatterBehaviorKey];
2166  [aCoder encodeInt:_locale forKey:CPDateFormatterLocaleKey];
2167  [aCoder encodeInt:_timeStyle forKey:CPDateFormatterTimeStyleKey];
2168 }
2169 
2170 @end
2171 
2172 
2173 @implementation CPDate (CPTimeZone)
2174 
2177 - (void)_dateWithTimeZone:(CPTimeZone)aTimeZone
2178 {
2179  if (!aTimeZone)
2180  return;
2181 
2182  self.setSeconds(self.getSeconds() - [aTimeZone secondsFromGMTForDate:self]);
2183  self.setSeconds(self.getSeconds() + [aTimeZone secondsFromGMT]);
2184 }
2185 
2186 @end
2187 
2189 
2193 - (BOOL)allowNaturalLanguage
2194 {
2195  return _allowNaturalLanguage;
2196 }
2197 
2201 - (BOOL)doesRelativeDateFormatting
2202 {
2203  return _doesRelativeDateFormatting;
2204 }
2205 
2209 - (void)setDoesRelativeDateFormatting:(BOOL)aValue
2210 {
2211  _doesRelativeDateFormatting = aValue;
2212 }
2213 
2217 - (CPDate)defaultDate
2218 {
2219  return _defaultDate;
2220 }
2221 
2225 - (void)setDefaultDate:(CPDate)aValue
2226 {
2227  _defaultDate = aValue;
2228 }
2229 
2233 - (CPDate)twoDigitStartDate
2234 {
2235  return _twoDigitStartDate;
2236 }
2237 
2241 - (void)setTwoDigitStartDate:(CPDate)aValue
2242 {
2243  _twoDigitStartDate = aValue;
2244 }
2245 
2249 - (CPDateFormatterBehavior)formatterBehavior
2250 {
2251  return _formatterBehavior;
2252 }
2253 
2257 - (void)setFormatterBehavior:(CPDateFormatterBehavior)aValue
2258 {
2259  _formatterBehavior = aValue;
2260 }
2261 
2265 - (CPDateFormatterStyle)dateStyle
2266 {
2267  return _dateStyle;
2268 }
2269 
2273 - (void)setDateStyle:(CPDateFormatterStyle)aValue
2274 {
2275  _dateStyle = aValue;
2276 }
2277 
2281 - (CPDateFormatterStyle)timeStyle
2282 {
2283  return _timeStyle;
2284 }
2285 
2289 - (void)setTimeStyle:(CPDateFormatterStyle)aValue
2290 {
2291  _timeStyle = aValue;
2292 }
2293 
2297 - (CPLocale)locale
2298 {
2299  return _locale;
2300 }
2301 
2305 - (void)setLocale:(CPLocale)aValue
2306 {
2307  _locale = aValue;
2308 }
2309 
2313 - (CPString)AMSymbol
2314 {
2315  return _AMSymbol;
2316 }
2317 
2321 - (void)setAMSymbol:(CPString)aValue
2322 {
2323  _AMSymbol = aValue;
2324 }
2325 
2329 - (CPString)dateFormat
2330 {
2331  return _dateFormat;
2332 }
2333 
2337 - (void)setDateFormat:(CPString)aValue
2338 {
2339  _dateFormat = aValue;
2340 }
2341 
2345 - (CPString)PMSymbol
2346 {
2347  return _PMSymbol;
2348 }
2349 
2353 - (void)setPMSymbol:(CPString)aValue
2354 {
2355  _PMSymbol = aValue;
2356 }
2357 
2361 - (CPTimeZone)timeZone
2362 {
2363  return _timeZone;
2364 }
2365 
2369 - (void)setTimeZone:(CPTimeZone)aValue
2370 {
2371  _timeZone = aValue;
2372 }
2373 
2374 @end