don't dereference end iterator (closes #10656)
[wxWidgets.git] / src / common / datetimefmt.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/common/datetimefmt.cpp
3 // Purpose: wxDateTime formatting & parsing code
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 11.05.99
7 // RCS-ID: $Id$
8 // Copyright: (c) 1999 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // parts of code taken from sndcal library by Scott E. Lee:
10 //
11 // Copyright 1993-1995, Scott E. Lee, all rights reserved.
12 // Permission granted to use, copy, modify, distribute and sell
13 // so long as the above copyright and this permission statement
14 // are retained in all copies.
15 //
16 // Licence: wxWindows licence
17 ///////////////////////////////////////////////////////////////////////////////
18
19 // ============================================================================
20 // declarations
21 // ============================================================================
22
23 // ----------------------------------------------------------------------------
24 // headers
25 // ----------------------------------------------------------------------------
26
27 // For compilers that support precompilation, includes "wx.h".
28 #include "wx/wxprec.h"
29
30 #ifdef __BORLANDC__
31 #pragma hdrstop
32 #endif
33
34 #if !defined(wxUSE_DATETIME) || wxUSE_DATETIME
35
36 #ifndef WX_PRECOMP
37 #ifdef __WXMSW__
38 #include "wx/msw/wrapwin.h"
39 #endif
40 #include "wx/string.h"
41 #include "wx/log.h"
42 #include "wx/intl.h"
43 #include "wx/stopwatch.h" // for wxGetLocalTimeMillis()
44 #include "wx/module.h"
45 #include "wx/crt.h"
46 #endif // WX_PRECOMP
47
48 #include "wx/thread.h"
49 #include "wx/tokenzr.h"
50
51 #include <ctype.h>
52
53 #ifdef __WINDOWS__
54 #include <winnls.h>
55 #ifndef __WXWINCE__
56 #include <locale.h>
57 #endif
58 #endif
59
60 #include "wx/datetime.h"
61
62 // ============================================================================
63 // implementation of wxDateTime
64 // ============================================================================
65
66 // ----------------------------------------------------------------------------
67 // helpers shared between datetime.cpp and datetimefmt.cpp
68 // ----------------------------------------------------------------------------
69
70 extern void InitTm(struct tm& tm);
71
72 extern int GetTimeZone();
73
74 extern wxString CallStrftime(const wxString& format, const tm* tm);
75
76 // ----------------------------------------------------------------------------
77 // constants (see also datetime.cpp)
78 // ----------------------------------------------------------------------------
79
80 static const int DAYS_PER_WEEK = 7;
81
82 static const int HOURS_PER_DAY = 24;
83
84 static const int SEC_PER_MIN = 60;
85
86 static const int MIN_PER_HOUR = 60;
87
88 // ----------------------------------------------------------------------------
89 // parsing helpers
90 // ----------------------------------------------------------------------------
91
92 namespace
93 {
94
95 enum
96 {
97 DateLang_English = 1,
98 DateLang_Local = 2
99 };
100
101 // return the month if the string is a month name or Inv_Month otherwise
102 //
103 // flags can contain wxDateTime::Name_Abbr/Name_Full or both of them and lang
104 // can be either DateLang_Local (default) to interpret string as a localized
105 // month name or DateLang_English to parse it as a standard English name or
106 // their combination to interpret it in any way
107 wxDateTime::Month
108 GetMonthFromName(const wxString& name, int flags, int lang)
109 {
110 wxDateTime::Month mon;
111 for ( mon = wxDateTime::Jan; mon < wxDateTime::Inv_Month; wxNextMonth(mon) )
112 {
113 // case-insensitive comparison either one of or with both abbreviated
114 // and not versions
115 if ( flags & wxDateTime::Name_Full )
116 {
117 if ( lang & DateLang_English )
118 {
119 if ( name.CmpNoCase(wxDateTime::GetEnglishMonthName(mon,
120 wxDateTime::Name_Full)) == 0 )
121 break;
122 }
123
124 if ( lang & DateLang_Local )
125 {
126 if ( name.CmpNoCase(wxDateTime::GetMonthName(mon,
127 wxDateTime::Name_Full)) == 0 )
128 break;
129 }
130 }
131
132 if ( flags & wxDateTime::Name_Abbr )
133 {
134 if ( lang & DateLang_English )
135 {
136 if ( name.CmpNoCase(wxDateTime::GetEnglishMonthName(mon,
137 wxDateTime::Name_Abbr)) == 0 )
138 break;
139 }
140
141 if ( lang & DateLang_Local )
142 {
143 if ( name.CmpNoCase(wxDateTime::GetMonthName(mon,
144 wxDateTime::Name_Abbr)) == 0 )
145 break;
146 }
147 }
148 }
149
150 return mon;
151 }
152
153 // return the weekday if the string is a weekday name or Inv_WeekDay otherwise
154 //
155 // flags and lang parameters have the same meaning as for GetMonthFromName()
156 // above
157 wxDateTime::WeekDay
158 GetWeekDayFromName(const wxString& name, int flags, int lang)
159 {
160 wxDateTime::WeekDay wd;
161 for ( wd = wxDateTime::Sun; wd < wxDateTime::Inv_WeekDay; wxNextWDay(wd) )
162 {
163 if ( flags & wxDateTime::Name_Full )
164 {
165 if ( lang & DateLang_English )
166 {
167 if ( name.CmpNoCase(wxDateTime::GetEnglishWeekDayName(wd,
168 wxDateTime::Name_Full)) == 0 )
169 break;
170 }
171
172 if ( lang & DateLang_Local )
173 {
174 if ( name.CmpNoCase(wxDateTime::GetWeekDayName(wd,
175 wxDateTime::Name_Full)) == 0 )
176 break;
177 }
178 }
179
180 if ( flags & wxDateTime::Name_Abbr )
181 {
182 if ( lang & DateLang_English )
183 {
184 if ( name.CmpNoCase(wxDateTime::GetEnglishWeekDayName(wd,
185 wxDateTime::Name_Abbr)) == 0 )
186 break;
187 }
188
189 if ( lang & DateLang_Local )
190 {
191 if ( name.CmpNoCase(wxDateTime::GetWeekDayName(wd,
192 wxDateTime::Name_Abbr)) == 0 )
193 break;
194 }
195 }
196 }
197
198 return wd;
199 }
200
201 // scans all digits (but no more than len) and returns the resulting number
202 bool GetNumericToken(size_t len,
203 wxString::const_iterator& p,
204 const wxString::const_iterator& end,
205 unsigned long *number)
206 {
207 size_t n = 1;
208 wxString s;
209 while ( p != end && wxIsdigit(*p) )
210 {
211 s += *p++;
212
213 if ( len && ++n > len )
214 break;
215 }
216
217 return !s.empty() && s.ToULong(number);
218 }
219
220 // scans all alphabetic characters and returns the resulting string
221 wxString
222 GetAlphaToken(wxString::const_iterator& p,
223 const wxString::const_iterator& end)
224 {
225 wxString s;
226 while ( p != end && wxIsalpha(*p) )
227 {
228 s += *p++;
229 }
230
231 return s;
232 }
233
234 // parses string starting at given iterator using the specified format and,
235 // optionally, a fall back format (and optionally another one... but it stops
236 // there, really)
237 //
238 // if unsuccessful, returns invalid wxDateTime without changing p; otherwise
239 // advance p to the end of the match and returns wxDateTime containing the
240 // results of the parsing
241 wxDateTime
242 ParseFormatAt(wxString::const_iterator& p,
243 const wxString::const_iterator& end,
244 const wxString& fmt,
245 // FIXME-VC6: using wxString() instead of wxEmptyString in the
246 // line below results in error C2062: type 'class
247 // wxString (__cdecl *)(void)' unexpected
248 const wxString& fmtAlt = wxEmptyString)
249 {
250 const wxString str(p, end);
251 wxString::const_iterator endParse;
252 wxDateTime dt;
253 if ( dt.ParseFormat(str, fmt, &endParse) ||
254 (!fmtAlt.empty() && dt.ParseFormat(str, fmtAlt, &endParse)) )
255 {
256 p += endParse - str.begin();
257 }
258 //else: all formats failed
259
260 return dt;
261 }
262
263 } // anonymous namespace
264
265 // ----------------------------------------------------------------------------
266 // wxDateTime to/from text representations
267 // ----------------------------------------------------------------------------
268
269 wxString wxDateTime::Format(const wxString& format, const TimeZone& tz) const
270 {
271 wxCHECK_MSG( !format.empty(), wxEmptyString,
272 _T("NULL format in wxDateTime::Format") );
273
274 // we have to use our own implementation if the date is out of range of
275 // strftime() or if we use non standard specificators
276 #ifdef HAVE_STRFTIME
277 time_t time = GetTicks();
278
279 if ( (time != (time_t)-1) && !wxStrstr(format, _T("%l")) )
280 {
281 // use strftime()
282 struct tm tmstruct;
283 struct tm *tm;
284 if ( tz.GetOffset() == -GetTimeZone() )
285 {
286 // we are working with local time
287 tm = wxLocaltime_r(&time, &tmstruct);
288
289 // should never happen
290 wxCHECK_MSG( tm, wxEmptyString, _T("wxLocaltime_r() failed") );
291 }
292 else
293 {
294 time += (int)tz.GetOffset();
295
296 #if defined(__VMS__) || defined(__WATCOMC__) // time is unsigned so avoid warning
297 int time2 = (int) time;
298 if ( time2 >= 0 )
299 #else
300 if ( time >= 0 )
301 #endif
302 {
303 tm = wxGmtime_r(&time, &tmstruct);
304
305 // should never happen
306 wxCHECK_MSG( tm, wxEmptyString, _T("wxGmtime_r() failed") );
307 }
308 else
309 {
310 tm = (struct tm *)NULL;
311 }
312 }
313
314 if ( tm )
315 {
316 return CallStrftime(format, tm);
317 }
318 }
319 //else: use generic code below
320 #endif // HAVE_STRFTIME
321
322 // we only parse ANSI C format specifications here, no POSIX 2
323 // complications, no GNU extensions but we do add support for a "%l" format
324 // specifier allowing to get the number of milliseconds
325 Tm tm = GetTm(tz);
326
327 // used for calls to strftime() when we only deal with time
328 struct tm tmTimeOnly;
329 tmTimeOnly.tm_hour = tm.hour;
330 tmTimeOnly.tm_min = tm.min;
331 tmTimeOnly.tm_sec = tm.sec;
332 tmTimeOnly.tm_wday = 0;
333 tmTimeOnly.tm_yday = 0;
334 tmTimeOnly.tm_mday = 1; // any date will do
335 tmTimeOnly.tm_mon = 0;
336 tmTimeOnly.tm_year = 76;
337 tmTimeOnly.tm_isdst = 0; // no DST, we adjust for tz ourselves
338
339 wxString tmp, res, fmt;
340 for ( wxString::const_iterator p = format.begin(); p != format.end(); ++p )
341 {
342 if ( *p != _T('%') )
343 {
344 // copy as is
345 res += *p;
346
347 continue;
348 }
349
350 // set the default format
351 switch ( (*++p).GetValue() )
352 {
353 case _T('Y'): // year has 4 digits
354 fmt = _T("%04d");
355 break;
356
357 case _T('j'): // day of year has 3 digits
358 case _T('l'): // milliseconds have 3 digits
359 fmt = _T("%03d");
360 break;
361
362 case _T('w'): // week day as number has only one
363 fmt = _T("%d");
364 break;
365
366 default:
367 // it's either another valid format specifier in which case
368 // the format is "%02d" (for all the rest) or we have the
369 // field width preceding the format in which case it will
370 // override the default format anyhow
371 fmt = _T("%02d");
372 }
373
374 bool restart = true;
375 while ( restart )
376 {
377 restart = false;
378
379 // start of the format specification
380 switch ( (*p).GetValue() )
381 {
382 case _T('a'): // a weekday name
383 case _T('A'):
384 // second parameter should be true for abbreviated names
385 res += GetWeekDayName(tm.GetWeekDay(),
386 *p == _T('a') ? Name_Abbr : Name_Full);
387 break;
388
389 case _T('b'): // a month name
390 case _T('B'):
391 res += GetMonthName(tm.mon,
392 *p == _T('b') ? Name_Abbr : Name_Full);
393 break;
394
395 case _T('c'): // locale default date and time representation
396 case _T('x'): // locale default date representation
397 #ifdef HAVE_STRFTIME
398 //
399 // the problem: there is no way to know what do these format
400 // specifications correspond to for the current locale.
401 //
402 // the solution: use a hack and still use strftime(): first
403 // find the YEAR which is a year in the strftime() range (1970
404 // - 2038) whose Jan 1 falls on the same week day as the Jan 1
405 // of the real year. Then make a copy of the format and
406 // replace all occurrences of YEAR in it with some unique
407 // string not appearing anywhere else in it, then use
408 // strftime() to format the date in year YEAR and then replace
409 // YEAR back by the real year and the unique replacement
410 // string back with YEAR. Notice that "all occurrences of YEAR"
411 // means all occurrences of 4 digit as well as 2 digit form!
412 //
413 // the bugs: we assume that neither of %c nor %x contains any
414 // fields which may change between the YEAR and real year. For
415 // example, the week number (%U, %W) and the day number (%j)
416 // will change if one of these years is leap and the other one
417 // is not!
418 {
419 // find the YEAR: normally, for any year X, Jan 1 of the
420 // year X + 28 is the same weekday as Jan 1 of X (because
421 // the weekday advances by 1 for each normal X and by 2
422 // for each leap X, hence by 5 every 4 years or by 35
423 // which is 0 mod 7 every 28 years) but this rule breaks
424 // down if there are years between X and Y which are
425 // divisible by 4 but not leap (i.e. divisible by 100 but
426 // not 400), hence the correction.
427
428 int yearReal = GetYear(tz);
429 int mod28 = yearReal % 28;
430
431 // be careful to not go too far - we risk to leave the
432 // supported range
433 int year;
434 if ( mod28 < 10 )
435 {
436 year = 1988 + mod28; // 1988 == 0 (mod 28)
437 }
438 else
439 {
440 year = 1970 + mod28 - 10; // 1970 == 10 (mod 28)
441 }
442
443 int nCentury = year / 100,
444 nCenturyReal = yearReal / 100;
445
446 // need to adjust for the years divisble by 400 which are
447 // not leap but are counted like leap ones if we just take
448 // the number of centuries in between for nLostWeekDays
449 int nLostWeekDays = (nCentury - nCenturyReal) -
450 (nCentury / 4 - nCenturyReal / 4);
451
452 // we have to gain back the "lost" weekdays: note that the
453 // effect of this loop is to not do anything to
454 // nLostWeekDays (which we won't use any more), but to
455 // (indirectly) set the year correctly
456 while ( (nLostWeekDays % 7) != 0 )
457 {
458 nLostWeekDays += year++ % 4 ? 1 : 2;
459 }
460
461 // finally move the year below 2000 so that the 2-digit
462 // year number can never match the month or day of the
463 // month when we do the replacements below
464 if ( year >= 2000 )
465 year -= 28;
466
467 wxASSERT_MSG( year >= 1970 && year < 2000,
468 _T("logic error in wxDateTime::Format") );
469
470
471 // use strftime() to format the same date but in supported
472 // year
473 //
474 // NB: we assume that strftime() doesn't check for the
475 // date validity and will happily format the date
476 // corresponding to Feb 29 of a non leap year (which
477 // may happen if yearReal was leap and year is not)
478 struct tm tmAdjusted;
479 InitTm(tmAdjusted);
480 tmAdjusted.tm_hour = tm.hour;
481 tmAdjusted.tm_min = tm.min;
482 tmAdjusted.tm_sec = tm.sec;
483 tmAdjusted.tm_wday = tm.GetWeekDay();
484 tmAdjusted.tm_yday = GetDayOfYear();
485 tmAdjusted.tm_mday = tm.mday;
486 tmAdjusted.tm_mon = tm.mon;
487 tmAdjusted.tm_year = year - 1900;
488 tmAdjusted.tm_isdst = 0; // no DST, already adjusted
489 wxString str = CallStrftime(*p == _T('c') ? _T("%c")
490 : _T("%x"),
491 &tmAdjusted);
492
493 // now replace the replacement year with the real year:
494 // notice that we have to replace the 4 digit year with
495 // a unique string not appearing in strftime() output
496 // first to prevent the 2 digit year from matching any
497 // substring of the 4 digit year (but any day, month,
498 // hours or minutes components should be safe because
499 // they are never in 70-99 range)
500 wxString replacement("|");
501 while ( str.find(replacement) != wxString::npos )
502 replacement += '|';
503
504 str.Replace(wxString::Format("%d", year),
505 replacement);
506 str.Replace(wxString::Format("%d", year % 100),
507 wxString::Format("%d", yearReal % 100));
508 str.Replace(replacement,
509 wxString::Format("%d", yearReal));
510
511 res += str;
512 }
513 #else // !HAVE_STRFTIME
514 // Use "%m/%d/%y %H:%M:%S" format instead
515 res += wxString::Format(wxT("%02d/%02d/%04d %02d:%02d:%02d"),
516 tm.mon+1,tm.mday, tm.year, tm.hour, tm.min, tm.sec);
517 #endif // HAVE_STRFTIME/!HAVE_STRFTIME
518 break;
519
520 case _T('d'): // day of a month (01-31)
521 res += wxString::Format(fmt, tm.mday);
522 break;
523
524 case _T('H'): // hour in 24h format (00-23)
525 res += wxString::Format(fmt, tm.hour);
526 break;
527
528 case _T('I'): // hour in 12h format (01-12)
529 {
530 // 24h -> 12h, 0h -> 12h too
531 int hour12 = tm.hour > 12 ? tm.hour - 12
532 : tm.hour ? tm.hour : 12;
533 res += wxString::Format(fmt, hour12);
534 }
535 break;
536
537 case _T('j'): // day of the year
538 res += wxString::Format(fmt, GetDayOfYear(tz));
539 break;
540
541 case _T('l'): // milliseconds (NOT STANDARD)
542 res += wxString::Format(fmt, GetMillisecond(tz));
543 break;
544
545 case _T('m'): // month as a number (01-12)
546 res += wxString::Format(fmt, tm.mon + 1);
547 break;
548
549 case _T('M'): // minute as a decimal number (00-59)
550 res += wxString::Format(fmt, tm.min);
551 break;
552
553 case _T('p'): // AM or PM string
554 #ifdef HAVE_STRFTIME
555 res += CallStrftime(_T("%p"), &tmTimeOnly);
556 #else // !HAVE_STRFTIME
557 res += (tmTimeOnly.tm_hour > 12) ? wxT("pm") : wxT("am");
558 #endif // HAVE_STRFTIME/!HAVE_STRFTIME
559 break;
560
561 case _T('S'): // second as a decimal number (00-61)
562 res += wxString::Format(fmt, tm.sec);
563 break;
564
565 case _T('U'): // week number in the year (Sunday 1st week day)
566 res += wxString::Format(fmt, GetWeekOfYear(Sunday_First, tz));
567 break;
568
569 case _T('W'): // week number in the year (Monday 1st week day)
570 res += wxString::Format(fmt, GetWeekOfYear(Monday_First, tz));
571 break;
572
573 case _T('w'): // weekday as a number (0-6), Sunday = 0
574 res += wxString::Format(fmt, tm.GetWeekDay());
575 break;
576
577 // case _T('x'): -- handled with "%c"
578
579 case _T('X'): // locale default time representation
580 // just use strftime() to format the time for us
581 #ifdef HAVE_STRFTIME
582 res += CallStrftime(_T("%X"), &tmTimeOnly);
583 #else // !HAVE_STRFTIME
584 res += wxString::Format(wxT("%02d:%02d:%02d"),tm.hour, tm.min, tm.sec);
585 #endif // HAVE_STRFTIME/!HAVE_STRFTIME
586 break;
587
588 case _T('y'): // year without century (00-99)
589 res += wxString::Format(fmt, tm.year % 100);
590 break;
591
592 case _T('Y'): // year with century
593 res += wxString::Format(fmt, tm.year);
594 break;
595
596 case _T('Z'): // timezone name
597 #ifdef HAVE_STRFTIME
598 res += CallStrftime(_T("%Z"), &tmTimeOnly);
599 #endif
600 break;
601
602 default:
603 // is it the format width?
604 fmt.Empty();
605 while ( *p == _T('-') || *p == _T('+') ||
606 *p == _T(' ') || wxIsdigit(*p) )
607 {
608 fmt += *p;
609 }
610
611 if ( !fmt.empty() )
612 {
613 // we've only got the flags and width so far in fmt
614 fmt.Prepend(_T('%'));
615 fmt.Append(_T('d'));
616
617 restart = true;
618
619 break;
620 }
621
622 // no, it wasn't the width
623 wxFAIL_MSG(_T("unknown format specificator"));
624
625 // fall through and just copy it nevertheless
626
627 case _T('%'): // a percent sign
628 res += *p;
629 break;
630
631 case 0: // the end of string
632 wxFAIL_MSG(_T("missing format at the end of string"));
633
634 // just put the '%' which was the last char in format
635 res += _T('%');
636 break;
637 }
638 }
639 }
640
641 return res;
642 }
643
644 // this function parses a string in (strict) RFC 822 format: see the section 5
645 // of the RFC for the detailed description, but briefly it's something of the
646 // form "Sat, 18 Dec 1999 00:48:30 +0100"
647 //
648 // this function is "strict" by design - it must reject anything except true
649 // RFC822 time specs.
650 bool
651 wxDateTime::ParseRfc822Date(const wxString& date, wxString::const_iterator *end)
652 {
653 wxString::const_iterator p = date.begin();
654
655 // 1. week day
656 static const int WDAY_LEN = 3;
657 const wxString::const_iterator endWday = p + WDAY_LEN;
658 const wxString wday(p, endWday);
659 if ( GetWeekDayFromName(wday, Name_Abbr, DateLang_English) == Inv_WeekDay )
660 return false;
661 //else: ignore week day for now, we could also check that it really
662 // corresponds to the specified date
663
664 p = endWday;
665
666 // 2. separating comma
667 if ( *p++ != ',' || *p++ != ' ' )
668 return false;
669
670 // 3. day number
671 if ( !wxIsdigit(*p) )
672 return false;
673
674 wxDateTime_t day = (wxDateTime_t)(*p++ - '0');
675 if ( wxIsdigit(*p) )
676 {
677 day *= 10;
678 day = (wxDateTime_t)(day + (*p++ - '0'));
679 }
680
681 if ( *p++ != ' ' )
682 return false;
683
684 // 4. month name
685 static const int MONTH_LEN = 3;
686 const wxString::const_iterator endMonth = p + MONTH_LEN;
687 const wxString monName(p, endMonth);
688 Month mon = GetMonthFromName(monName, Name_Abbr, DateLang_English);
689 if ( mon == Inv_Month )
690 return false;
691
692 p = endMonth;
693
694 if ( *p++ != ' ' )
695 return false;
696
697 // 5. year
698 if ( !wxIsdigit(*p) )
699 return false;
700
701 int year = *p++ - '0';
702 if ( !wxIsdigit(*p) ) // should have at least 2 digits in the year
703 return false;
704
705 year *= 10;
706 year += *p++ - '0';
707
708 // is it a 2 digit year (as per original RFC 822) or a 4 digit one?
709 if ( wxIsdigit(*p) )
710 {
711 year *= 10;
712 year += *p++ - '0';
713
714 if ( !wxIsdigit(*p) )
715 {
716 // no 3 digit years please
717 return false;
718 }
719
720 year *= 10;
721 year += *p++ - '0';
722 }
723
724 if ( *p++ != ' ' )
725 return false;
726
727 // 6. time in hh:mm:ss format with seconds being optional
728 if ( !wxIsdigit(*p) )
729 return false;
730
731 wxDateTime_t hour = (wxDateTime_t)(*p++ - '0');
732
733 if ( !wxIsdigit(*p) )
734 return false;
735
736 hour *= 10;
737 hour = (wxDateTime_t)(hour + (*p++ - '0'));
738
739 if ( *p++ != ':' )
740 return false;
741
742 if ( !wxIsdigit(*p) )
743 return false;
744
745 wxDateTime_t min = (wxDateTime_t)(*p++ - '0');
746
747 if ( !wxIsdigit(*p) )
748 return false;
749
750 min *= 10;
751 min += (wxDateTime_t)(*p++ - '0');
752
753 wxDateTime_t sec = 0;
754 if ( *p == ':' )
755 {
756 p++;
757 if ( !wxIsdigit(*p) )
758 return false;
759
760 sec = (wxDateTime_t)(*p++ - '0');
761
762 if ( !wxIsdigit(*p) )
763 return false;
764
765 sec *= 10;
766 sec += (wxDateTime_t)(*p++ - '0');
767 }
768
769 if ( *p++ != ' ' )
770 return false;
771
772 // 7. now the interesting part: the timezone
773 int offset wxDUMMY_INITIALIZE(0);
774 if ( *p == '-' || *p == '+' )
775 {
776 // the explicit offset given: it has the form of hhmm
777 bool plus = *p++ == '+';
778
779 if ( !wxIsdigit(*p) || !wxIsdigit(*(p + 1)) )
780 return false;
781
782
783 // hours
784 offset = MIN_PER_HOUR*(10*(*p - '0') + (*(p + 1) - '0'));
785
786 p += 2;
787
788 if ( !wxIsdigit(*p) || !wxIsdigit(*(p + 1)) )
789 return false;
790
791 // minutes
792 offset += 10*(*p - '0') + (*(p + 1) - '0');
793
794 if ( !plus )
795 offset = -offset;
796
797 p += 2;
798 }
799 else // not numeric
800 {
801 // the symbolic timezone given: may be either military timezone or one
802 // of standard abbreviations
803 if ( !*(p + 1) )
804 {
805 // military: Z = UTC, J unused, A = -1, ..., Y = +12
806 static const int offsets[26] =
807 {
808 //A B C D E F G H I J K L M
809 -1, -2, -3, -4, -5, -6, -7, -8, -9, 0, -10, -11, -12,
810 //N O P R Q S T U V W Z Y Z
811 +1, +2, +3, +4, +5, +6, +7, +8, +9, +10, +11, +12, 0
812 };
813
814 if ( *p < _T('A') || *p > _T('Z') || *p == _T('J') )
815 return false;
816
817 offset = offsets[*p++ - 'A'];
818 }
819 else
820 {
821 // abbreviation
822 const wxString tz(p, date.end());
823 if ( tz == _T("UT") || tz == _T("UTC") || tz == _T("GMT") )
824 offset = 0;
825 else if ( tz == _T("AST") )
826 offset = AST - GMT0;
827 else if ( tz == _T("ADT") )
828 offset = ADT - GMT0;
829 else if ( tz == _T("EST") )
830 offset = EST - GMT0;
831 else if ( tz == _T("EDT") )
832 offset = EDT - GMT0;
833 else if ( tz == _T("CST") )
834 offset = CST - GMT0;
835 else if ( tz == _T("CDT") )
836 offset = CDT - GMT0;
837 else if ( tz == _T("MST") )
838 offset = MST - GMT0;
839 else if ( tz == _T("MDT") )
840 offset = MDT - GMT0;
841 else if ( tz == _T("PST") )
842 offset = PST - GMT0;
843 else if ( tz == _T("PDT") )
844 offset = PDT - GMT0;
845 else
846 return false;
847
848 p += tz.length();
849 }
850
851 // make it minutes
852 offset *= MIN_PER_HOUR;
853 }
854
855
856 // the spec was correct, construct the date from the values we found
857 Set(day, mon, year, hour, min, sec);
858 MakeFromTimezone(TimeZone::Make(offset*SEC_PER_MIN));
859
860 if ( end )
861 *end = p;
862
863 return true;
864 }
865
866 bool
867 wxDateTime::ParseFormat(const wxString& date,
868 const wxString& format,
869 const wxDateTime& dateDef,
870 wxString::const_iterator *endParse)
871 {
872 wxCHECK_MSG( !format.empty(), false, "format can't be empty" );
873 wxCHECK_MSG( endParse, false, "end iterator pointer must be specified" );
874
875 wxString str;
876 unsigned long num;
877
878 // what fields have we found?
879 bool haveWDay = false,
880 haveYDay = false,
881 haveDay = false,
882 haveMon = false,
883 haveYear = false,
884 haveHour = false,
885 haveMin = false,
886 haveSec = false,
887 haveMsec = false;
888
889 bool hourIsIn12hFormat = false, // or in 24h one?
890 isPM = false; // AM by default
891
892 // and the value of the items we have (init them to get rid of warnings)
893 wxDateTime_t msec = 0,
894 sec = 0,
895 min = 0,
896 hour = 0;
897 WeekDay wday = Inv_WeekDay;
898 wxDateTime_t yday = 0,
899 mday = 0;
900 wxDateTime::Month mon = Inv_Month;
901 int year = 0;
902
903 wxString::const_iterator input = date.begin();
904 const wxString::const_iterator end = date.end();
905 for ( wxString::const_iterator fmt = format.begin(); fmt != format.end(); ++fmt )
906 {
907 if ( *fmt != _T('%') )
908 {
909 if ( wxIsspace(*fmt) )
910 {
911 // a white space in the format string matches 0 or more white
912 // spaces in the input
913 while ( input != end && wxIsspace(*input) )
914 {
915 input++;
916 }
917 }
918 else // !space
919 {
920 // any other character (not whitespace, not '%') must be
921 // matched by itself in the input
922 if ( input == end || *input++ != *fmt )
923 {
924 // no match
925 return false;
926 }
927 }
928
929 // done with this format char
930 continue;
931 }
932
933 // start of a format specification
934
935 // parse the optional width
936 size_t width = 0;
937 while ( wxIsdigit(*++fmt) )
938 {
939 width *= 10;
940 width += *fmt - '0';
941 }
942
943 // the default widths for the various fields
944 if ( !width )
945 {
946 switch ( (*fmt).GetValue() )
947 {
948 case _T('Y'): // year has 4 digits
949 width = 4;
950 break;
951
952 case _T('j'): // day of year has 3 digits
953 case _T('l'): // milliseconds have 3 digits
954 width = 3;
955 break;
956
957 case _T('w'): // week day as number has only one
958 width = 1;
959 break;
960
961 default:
962 // default for all other fields
963 width = 2;
964 }
965 }
966
967 // then the format itself
968 switch ( (*fmt).GetValue() )
969 {
970 case _T('a'): // a weekday name
971 case _T('A'):
972 {
973 wday = GetWeekDayFromName
974 (
975 GetAlphaToken(input, end),
976 *fmt == 'a' ? Name_Abbr : Name_Full,
977 DateLang_Local
978 );
979 if ( wday == Inv_WeekDay )
980 {
981 // no match
982 return false;
983 }
984 }
985 haveWDay = true;
986 break;
987
988 case _T('b'): // a month name
989 case _T('B'):
990 {
991 mon = GetMonthFromName
992 (
993 GetAlphaToken(input, end),
994 *fmt == 'b' ? Name_Abbr : Name_Full,
995 DateLang_Local
996 );
997 if ( mon == Inv_Month )
998 {
999 // no match
1000 return false;
1001 }
1002 }
1003 haveMon = true;
1004 break;
1005
1006 case _T('c'): // locale default date and time representation
1007 {
1008 wxDateTime dt;
1009
1010 const wxString
1011 fmtDateTime = wxLocale::GetInfo(wxLOCALE_DATE_TIME_FMT);
1012 if ( !fmtDateTime.empty() )
1013 dt = ParseFormatAt(input, end, fmtDateTime);
1014
1015 if ( !dt.IsValid() )
1016 {
1017 // also try the format which corresponds to ctime()
1018 // output (i.e. the "C" locale default)
1019 dt = ParseFormatAt(input, end, wxS("%a %b %d %H:%M:%S %Y"));
1020 }
1021
1022 if ( !dt.IsValid() )
1023 {
1024 // and finally also the two generic date/time formats
1025 dt = ParseFormatAt(input, end, wxS("%x %X"), wxS("%X %x"));
1026 }
1027
1028 if ( !dt.IsValid() )
1029 return false;
1030
1031 const Tm tm = dt.GetTm();
1032
1033 hour = tm.hour;
1034 min = tm.min;
1035 sec = tm.sec;
1036
1037 year = tm.year;
1038 mon = tm.mon;
1039 mday = tm.mday;
1040
1041 haveDay = haveMon = haveYear =
1042 haveHour = haveMin = haveSec = true;
1043 }
1044 break;
1045
1046 case _T('d'): // day of a month (01-31)
1047 if ( !GetNumericToken(width, input, end, &num) ||
1048 (num > 31) || (num < 1) )
1049 {
1050 // no match
1051 return false;
1052 }
1053
1054 // we can't check whether the day range is correct yet, will
1055 // do it later - assume ok for now
1056 haveDay = true;
1057 mday = (wxDateTime_t)num;
1058 break;
1059
1060 case _T('H'): // hour in 24h format (00-23)
1061 if ( !GetNumericToken(width, input, end, &num) || (num > 23) )
1062 {
1063 // no match
1064 return false;
1065 }
1066
1067 haveHour = true;
1068 hour = (wxDateTime_t)num;
1069 break;
1070
1071 case _T('I'): // hour in 12h format (01-12)
1072 if ( !GetNumericToken(width, input, end, &num) ||
1073 !num || (num > 12) )
1074 {
1075 // no match
1076 return false;
1077 }
1078
1079 haveHour = true;
1080 hourIsIn12hFormat = true;
1081 hour = (wxDateTime_t)(num % 12); // 12 should be 0
1082 break;
1083
1084 case _T('j'): // day of the year
1085 if ( !GetNumericToken(width, input, end, &num) ||
1086 !num || (num > 366) )
1087 {
1088 // no match
1089 return false;
1090 }
1091
1092 haveYDay = true;
1093 yday = (wxDateTime_t)num;
1094 break;
1095
1096 case _T('l'): // milliseconds (0-999)
1097 if ( !GetNumericToken(width, input, end, &num) )
1098 return false;
1099
1100 haveMsec = true;
1101 msec = (wxDateTime_t)num;
1102 break;
1103
1104 case _T('m'): // month as a number (01-12)
1105 if ( !GetNumericToken(width, input, end, &num) ||
1106 !num || (num > 12) )
1107 {
1108 // no match
1109 return false;
1110 }
1111
1112 haveMon = true;
1113 mon = (Month)(num - 1);
1114 break;
1115
1116 case _T('M'): // minute as a decimal number (00-59)
1117 if ( !GetNumericToken(width, input, end, &num) ||
1118 (num > 59) )
1119 {
1120 // no match
1121 return false;
1122 }
1123
1124 haveMin = true;
1125 min = (wxDateTime_t)num;
1126 break;
1127
1128 case _T('p'): // AM or PM string
1129 {
1130 wxString am, pm, token = GetAlphaToken(input, end);
1131
1132 // some locales have empty AM/PM tokens and thus when formatting
1133 // dates with the %p specifier nothing is generated; when trying to
1134 // parse them back, we get an empty token here... but that's not
1135 // an error.
1136 if (token.empty())
1137 break;
1138
1139 GetAmPmStrings(&am, &pm);
1140 if (am.empty() && pm.empty())
1141 return false; // no am/pm strings defined
1142 if ( token.CmpNoCase(pm) == 0 )
1143 {
1144 isPM = true;
1145 }
1146 else if ( token.CmpNoCase(am) != 0 )
1147 {
1148 // no match
1149 return false;
1150 }
1151 }
1152 break;
1153
1154 case _T('r'): // time as %I:%M:%S %p
1155 {
1156 wxDateTime dt;
1157 if ( !dt.ParseFormat(wxString(input, end),
1158 wxS("%I:%M:%S %p"), &input) )
1159 return false;
1160
1161 haveHour = haveMin = haveSec = true;
1162
1163 const Tm tm = dt.GetTm();
1164 hour = tm.hour;
1165 min = tm.min;
1166 sec = tm.sec;
1167 }
1168 break;
1169
1170 case _T('R'): // time as %H:%M
1171 {
1172 const wxDateTime
1173 dt = ParseFormatAt(input, end, wxS("%H:%M"));
1174 if ( !dt.IsValid() )
1175 return false;
1176
1177 haveHour =
1178 haveMin = true;
1179
1180 const Tm tm = dt.GetTm();
1181 hour = tm.hour;
1182 min = tm.min;
1183 }
1184 break;
1185
1186 case _T('S'): // second as a decimal number (00-61)
1187 if ( !GetNumericToken(width, input, end, &num) ||
1188 (num > 61) )
1189 {
1190 // no match
1191 return false;
1192 }
1193
1194 haveSec = true;
1195 sec = (wxDateTime_t)num;
1196 break;
1197
1198 case _T('T'): // time as %H:%M:%S
1199 {
1200 const wxDateTime
1201 dt = ParseFormatAt(input, end, wxS("%H:%M:%S"));
1202 if ( !dt.IsValid() )
1203 return false;
1204
1205 haveHour =
1206 haveMin =
1207 haveSec = true;
1208
1209 const Tm tm = dt.GetTm();
1210 hour = tm.hour;
1211 min = tm.min;
1212 sec = tm.sec;
1213 }
1214 break;
1215
1216 case _T('w'): // weekday as a number (0-6), Sunday = 0
1217 if ( !GetNumericToken(width, input, end, &num) ||
1218 (wday > 6) )
1219 {
1220 // no match
1221 return false;
1222 }
1223
1224 haveWDay = true;
1225 wday = (WeekDay)num;
1226 break;
1227
1228 case _T('x'): // locale default date representation
1229 {
1230 wxString
1231 fmtDate = wxLocale::GetInfo(wxLOCALE_SHORT_DATE_FMT),
1232 fmtDateAlt = wxLocale::GetInfo(wxLOCALE_LONG_DATE_FMT);
1233
1234 if ( fmtDate.empty() )
1235 {
1236 if ( IsWestEuropeanCountry(GetCountry()) ||
1237 GetCountry() == Russia )
1238 {
1239 fmtDate = wxS("%d/%m/%Y");
1240 fmtDateAlt = wxS("%m/%d/%Y");
1241 }
1242 else // assume USA
1243 {
1244 fmtDate = wxS("%m/%d/%Y");
1245 fmtDateAlt = wxS("%d/%m/%Y");
1246 }
1247 }
1248
1249 wxDateTime
1250 dt = ParseFormatAt(input, end, fmtDate, fmtDateAlt);
1251
1252 if ( !dt.IsValid() )
1253 {
1254 // try with short years too
1255 fmtDate.Replace("%Y","%y");
1256 fmtDateAlt.Replace("%Y","%y");
1257 dt = ParseFormatAt(input, end, fmtDate, fmtDateAlt);
1258
1259 if ( !dt.IsValid() )
1260 return false;
1261 }
1262
1263 const Tm tm = dt.GetTm();
1264
1265 haveDay =
1266 haveMon =
1267 haveYear = true;
1268
1269 year = tm.year;
1270 mon = tm.mon;
1271 mday = tm.mday;
1272 }
1273
1274 break;
1275
1276 case _T('X'): // locale default time representation
1277 {
1278 wxString fmtTime = wxLocale::GetInfo(wxLOCALE_TIME_FMT),
1279 fmtTimeAlt;
1280
1281 if ( fmtTime.empty() )
1282 {
1283 // try to parse what follows as "%H:%M:%S" and, if this
1284 // fails, as "%I:%M:%S %p" - this should catch the most
1285 // common cases
1286 fmtTime = "%T";
1287 fmtTimeAlt = "%r";
1288 }
1289
1290 const wxDateTime
1291 dt = ParseFormatAt(input, end, fmtTime, fmtTimeAlt);
1292 if ( !dt.IsValid() )
1293 return false;
1294
1295 haveHour =
1296 haveMin =
1297 haveSec = true;
1298
1299 const Tm tm = dt.GetTm();
1300 hour = tm.hour;
1301 min = tm.min;
1302 sec = tm.sec;
1303 }
1304 break;
1305
1306 case _T('y'): // year without century (00-99)
1307 if ( !GetNumericToken(width, input, end, &num) ||
1308 (num > 99) )
1309 {
1310 // no match
1311 return false;
1312 }
1313
1314 haveYear = true;
1315
1316 // TODO should have an option for roll over date instead of
1317 // hard coding it here
1318 year = (num > 30 ? 1900 : 2000) + (wxDateTime_t)num;
1319 break;
1320
1321 case _T('Y'): // year with century
1322 if ( !GetNumericToken(width, input, end, &num) )
1323 {
1324 // no match
1325 return false;
1326 }
1327
1328 haveYear = true;
1329 year = (wxDateTime_t)num;
1330 break;
1331
1332 case _T('Z'): // timezone name
1333 // FIXME: currently we just ignore everything that looks like a
1334 // time zone here
1335 GetAlphaToken(input, end);
1336 break;
1337
1338 case _T('%'): // a percent sign
1339 if ( *input++ != _T('%') )
1340 {
1341 // no match
1342 return false;
1343 }
1344 break;
1345
1346 case 0: // the end of string
1347 wxFAIL_MSG(_T("unexpected format end"));
1348
1349 // fall through
1350
1351 default: // not a known format spec
1352 return false;
1353 }
1354 }
1355
1356 // format matched, try to construct a date from what we have now
1357 Tm tmDef;
1358 if ( dateDef.IsValid() )
1359 {
1360 // take this date as default
1361 tmDef = dateDef.GetTm();
1362 }
1363 else if ( IsValid() )
1364 {
1365 // if this date is valid, don't change it
1366 tmDef = GetTm();
1367 }
1368 else
1369 {
1370 // no default and this date is invalid - fall back to Today()
1371 tmDef = Today().GetTm();
1372 }
1373
1374 Tm tm = tmDef;
1375
1376 // set the date
1377 if ( haveMon )
1378 {
1379 tm.mon = mon;
1380 }
1381
1382 if ( haveYear )
1383 {
1384 tm.year = year;
1385 }
1386
1387 // TODO we don't check here that the values are consistent, if both year
1388 // day and month/day were found, we just ignore the year day and we
1389 // also always ignore the week day
1390 if ( haveDay )
1391 {
1392 if ( mday > GetNumberOfDays(tm.mon, tm.year) )
1393 return false;
1394
1395 tm.mday = mday;
1396 }
1397 else if ( haveYDay )
1398 {
1399 if ( yday > GetNumberOfDays(tm.year) )
1400 return false;
1401
1402 Tm tm2 = wxDateTime(1, Jan, tm.year).SetToYearDay(yday).GetTm();
1403
1404 tm.mon = tm2.mon;
1405 tm.mday = tm2.mday;
1406 }
1407
1408 // deal with AM/PM
1409 if ( haveHour && hourIsIn12hFormat && isPM )
1410 {
1411 // translate to 24hour format
1412 hour += 12;
1413 }
1414 //else: either already in 24h format or no translation needed
1415
1416 // set the time
1417 if ( haveHour )
1418 {
1419 tm.hour = hour;
1420 }
1421
1422 if ( haveMin )
1423 {
1424 tm.min = min;
1425 }
1426
1427 if ( haveSec )
1428 {
1429 tm.sec = sec;
1430 }
1431
1432 if ( haveMsec )
1433 tm.msec = msec;
1434
1435 Set(tm);
1436
1437 // finally check that the week day is consistent -- if we had it
1438 if ( haveWDay && GetWeekDay() != wday )
1439 return false;
1440
1441 *endParse = input;
1442
1443 return true;
1444 }
1445
1446 bool
1447 wxDateTime::ParseDateTime(const wxString& date, wxString::const_iterator *end)
1448 {
1449 wxCHECK_MSG( end, false, "end iterator pointer must be specified" );
1450
1451 // Set to current day and hour, so strings like '14:00' becomes today at
1452 // 14, not some other random date
1453 wxDateTime dtDate = wxDateTime::Today();
1454 wxDateTime dtTime = wxDateTime::Today();
1455
1456 wxString::const_iterator
1457 endTime,
1458 endDate,
1459 endBoth;
1460
1461 // If we got a date in the beginning, see if there is a time specified
1462 // after the date
1463 if ( dtDate.ParseDate(date, &endDate) )
1464 {
1465 // Skip spaces, as the ParseTime() function fails on spaces
1466 while ( endDate != date.end() && wxIsspace(*endDate) )
1467 ++endDate;
1468
1469 const wxString timestr(endDate, date.end());
1470 if ( !dtTime.ParseTime(timestr, &endTime) )
1471 return false;
1472
1473 endBoth = endDate + (endTime - timestr.begin());
1474 }
1475 else // no date in the beginning
1476 {
1477 // check if we have a time followed by a date
1478 if ( !dtTime.ParseTime(date, &endTime) )
1479 return false;
1480
1481 while ( endTime != date.end() && wxIsspace(*endTime) )
1482 ++endTime;
1483
1484 const wxString datestr(endTime, date.end());
1485 if ( !dtDate.ParseDate(datestr, &endDate) )
1486 return false;
1487
1488 endBoth = endTime + (endDate - datestr.begin());
1489 }
1490
1491 Set(dtDate.GetDay(), dtDate.GetMonth(), dtDate.GetYear(),
1492 dtTime.GetHour(), dtTime.GetMinute(), dtTime.GetSecond(),
1493 dtTime.GetMillisecond());
1494
1495 *end = endBoth;
1496
1497 return true;
1498 }
1499
1500 bool
1501 wxDateTime::ParseDate(const wxString& date, wxString::const_iterator *end)
1502 {
1503 wxCHECK_MSG( end, false, "end iterator pointer must be specified" );
1504
1505 // this is a simplified version of ParseDateTime() which understands only
1506 // "today" (for wxDate compatibility) and digits only otherwise (and not
1507 // all esoteric constructions ParseDateTime() knows about)
1508
1509 const wxString::const_iterator pBegin = date.begin();
1510
1511 wxString::const_iterator p = pBegin;
1512 while ( wxIsspace(*p) )
1513 p++;
1514
1515 // some special cases
1516 static struct
1517 {
1518 const char *str;
1519 int dayDiffFromToday;
1520 } literalDates[] =
1521 {
1522 { wxTRANSLATE("today"), 0 },
1523 { wxTRANSLATE("yesterday"), -1 },
1524 { wxTRANSLATE("tomorrow"), 1 },
1525 };
1526
1527 const size_t lenRest = date.end() - p;
1528 for ( size_t n = 0; n < WXSIZEOF(literalDates); n++ )
1529 {
1530 const wxString dateStr = wxGetTranslation(literalDates[n].str);
1531 size_t len = dateStr.length();
1532
1533 if ( len > lenRest )
1534 continue;
1535
1536 const wxString::const_iterator pEnd = p + len;
1537 if ( wxString(p, pEnd).CmpNoCase(dateStr) == 0 )
1538 {
1539 // nothing can follow this, so stop here
1540
1541 p = pEnd;
1542
1543 int dayDiffFromToday = literalDates[n].dayDiffFromToday;
1544 *this = Today();
1545 if ( dayDiffFromToday )
1546 {
1547 *this += wxDateSpan::Days(dayDiffFromToday);
1548 }
1549
1550 *end = pEnd;
1551
1552 return true;
1553 }
1554 }
1555
1556 // We try to guess what we have here: for each new (numeric) token, we
1557 // determine if it can be a month, day or a year. Of course, there is an
1558 // ambiguity as some numbers may be days as well as months, so we also
1559 // have the ability to back track.
1560
1561 // what do we have?
1562 bool haveDay = false, // the months day?
1563 haveWDay = false, // the day of week?
1564 haveMon = false, // the month?
1565 haveYear = false; // the year?
1566
1567 // and the value of the items we have (init them to get rid of warnings)
1568 WeekDay wday = Inv_WeekDay;
1569 wxDateTime_t day = 0;
1570 wxDateTime::Month mon = Inv_Month;
1571 int year = 0;
1572
1573 // tokenize the string
1574 size_t nPosCur = 0;
1575 static const wxStringCharType *dateDelimiters = wxS(".,/-\t\r\n ");
1576 wxStringTokenizer tok(wxString(p, date.end()), dateDelimiters);
1577 while ( tok.HasMoreTokens() )
1578 {
1579 wxString token = tok.GetNextToken();
1580 if ( !token )
1581 continue;
1582
1583 // is it a number?
1584 unsigned long val;
1585 if ( token.ToULong(&val) )
1586 {
1587 // guess what this number is
1588
1589 bool isDay = false,
1590 isMonth = false,
1591 isYear = false;
1592
1593 if ( !haveMon && val > 0 && val <= 12 )
1594 {
1595 // assume it is month
1596 isMonth = true;
1597 }
1598 else // not the month
1599 {
1600 if ( haveDay )
1601 {
1602 // this can only be the year
1603 isYear = true;
1604 }
1605 else // may be either day or year
1606 {
1607 // use a leap year if we don't have the year yet to allow
1608 // dates like 2/29/1976 which would be rejected otherwise
1609 wxDateTime_t max_days = (wxDateTime_t)(
1610 haveMon
1611 ? GetNumberOfDays(mon, haveYear ? year : 1976)
1612 : 31
1613 );
1614
1615 // can it be day?
1616 if ( (val == 0) || (val > (unsigned long)max_days) )
1617 {
1618 // no
1619 isYear = true;
1620 }
1621 else // yes, suppose it's the day
1622 {
1623 isDay = true;
1624 }
1625 }
1626 }
1627
1628 if ( isYear )
1629 {
1630 if ( haveYear )
1631 break;
1632
1633 haveYear = true;
1634
1635 year = (wxDateTime_t)val;
1636 }
1637 else if ( isDay )
1638 {
1639 if ( haveDay )
1640 break;
1641
1642 haveDay = true;
1643
1644 day = (wxDateTime_t)val;
1645 }
1646 else if ( isMonth )
1647 {
1648 haveMon = true;
1649
1650 mon = (Month)(val - 1);
1651 }
1652 }
1653 else // not a number
1654 {
1655 // be careful not to overwrite the current mon value
1656 Month mon2 = GetMonthFromName
1657 (
1658 token,
1659 Name_Full | Name_Abbr,
1660 DateLang_Local | DateLang_English
1661 );
1662 if ( mon2 != Inv_Month )
1663 {
1664 // it's a month
1665 if ( haveMon )
1666 {
1667 // but we already have a month - maybe we guessed wrong?
1668 if ( !haveDay )
1669 {
1670 // no need to check in month range as always < 12, but
1671 // the days are counted from 1 unlike the months
1672 day = (wxDateTime_t)(mon + 1);
1673 haveDay = true;
1674 }
1675 else
1676 {
1677 // could possible be the year (doesn't the year come
1678 // before the month in the japanese format?) (FIXME)
1679 break;
1680 }
1681 }
1682
1683 mon = mon2;
1684
1685 haveMon = true;
1686 }
1687 else // not a valid month name
1688 {
1689 WeekDay wday2 = GetWeekDayFromName
1690 (
1691 token,
1692 Name_Full | Name_Abbr,
1693 DateLang_Local | DateLang_English
1694 );
1695 if ( wday2 != Inv_WeekDay )
1696 {
1697 // a week day
1698 if ( haveWDay )
1699 {
1700 break;
1701 }
1702
1703 wday = wday2;
1704
1705 haveWDay = true;
1706 }
1707 else // not a valid weekday name
1708 {
1709 // try the ordinals
1710 static const char *ordinals[] =
1711 {
1712 wxTRANSLATE("first"),
1713 wxTRANSLATE("second"),
1714 wxTRANSLATE("third"),
1715 wxTRANSLATE("fourth"),
1716 wxTRANSLATE("fifth"),
1717 wxTRANSLATE("sixth"),
1718 wxTRANSLATE("seventh"),
1719 wxTRANSLATE("eighth"),
1720 wxTRANSLATE("ninth"),
1721 wxTRANSLATE("tenth"),
1722 wxTRANSLATE("eleventh"),
1723 wxTRANSLATE("twelfth"),
1724 wxTRANSLATE("thirteenth"),
1725 wxTRANSLATE("fourteenth"),
1726 wxTRANSLATE("fifteenth"),
1727 wxTRANSLATE("sixteenth"),
1728 wxTRANSLATE("seventeenth"),
1729 wxTRANSLATE("eighteenth"),
1730 wxTRANSLATE("nineteenth"),
1731 wxTRANSLATE("twentieth"),
1732 // that's enough - otherwise we'd have problems with
1733 // composite (or not) ordinals
1734 };
1735
1736 size_t n;
1737 for ( n = 0; n < WXSIZEOF(ordinals); n++ )
1738 {
1739 if ( token.CmpNoCase(ordinals[n]) == 0 )
1740 {
1741 break;
1742 }
1743 }
1744
1745 if ( n == WXSIZEOF(ordinals) )
1746 {
1747 // stop here - something unknown
1748 break;
1749 }
1750
1751 // it's a day
1752 if ( haveDay )
1753 {
1754 // don't try anything here (as in case of numeric day
1755 // above) - the symbolic day spec should always
1756 // precede the month/year
1757 break;
1758 }
1759
1760 haveDay = true;
1761
1762 day = (wxDateTime_t)(n + 1);
1763 }
1764 }
1765 }
1766
1767 nPosCur = tok.GetPosition();
1768 }
1769
1770 // either no more tokens or the scan was stopped by something we couldn't
1771 // parse - in any case, see if we can construct a date from what we have
1772 if ( !haveDay && !haveWDay )
1773 return false;
1774
1775 if ( haveWDay && (haveMon || haveYear || haveDay) &&
1776 !(haveDay && haveMon && haveYear) )
1777 {
1778 // without adjectives (which we don't support here) the week day only
1779 // makes sense completely separately or with the full date
1780 // specification (what would "Wed 1999" mean?)
1781 return false;
1782 }
1783
1784 if ( !haveWDay && haveYear && !(haveDay && haveMon) )
1785 {
1786 // may be we have month and day instead of day and year?
1787 if ( haveDay && !haveMon )
1788 {
1789 if ( day <= 12 )
1790 {
1791 // exchange day and month
1792 mon = (wxDateTime::Month)(day - 1);
1793
1794 // we're in the current year then
1795 if ( (year > 0) && (year <= (int)GetNumberOfDays(mon, Inv_Year)) )
1796 {
1797 day = (wxDateTime_t)year;
1798
1799 haveMon = true;
1800 haveYear = false;
1801 }
1802 //else: no, can't exchange, leave haveMon == false
1803 }
1804 }
1805
1806 if ( !haveMon )
1807 return false;
1808 }
1809
1810 if ( !haveMon )
1811 {
1812 mon = GetCurrentMonth();
1813 }
1814
1815 if ( !haveYear )
1816 {
1817 year = GetCurrentYear();
1818 }
1819
1820 if ( haveDay )
1821 {
1822 // normally we check the day above but the check is optimistic in case
1823 // we find the day before its month/year so we have to redo it now
1824 if ( day > GetNumberOfDays(mon, year) )
1825 return false;
1826
1827 Set(day, mon, year);
1828
1829 if ( haveWDay )
1830 {
1831 // check that it is really the same
1832 if ( GetWeekDay() != wday )
1833 return false;
1834 }
1835 }
1836 else // haveWDay
1837 {
1838 *this = Today();
1839
1840 SetToWeekDayInSameWeek(wday);
1841 }
1842
1843 // return the pointer to the first unparsed char
1844 p += nPosCur;
1845 if ( nPosCur && wxStrchr(dateDelimiters, *(p - 1)) )
1846 {
1847 // if we couldn't parse the token after the delimiter, put back the
1848 // delimiter as well
1849 p--;
1850 }
1851
1852 *end = p;
1853
1854 return true;
1855 }
1856
1857 bool
1858 wxDateTime::ParseTime(const wxString& time, wxString::const_iterator *end)
1859 {
1860 wxCHECK_MSG( end, false, "end iterator pointer must be specified" );
1861
1862 // first try some extra things
1863 static const struct
1864 {
1865 const char *name;
1866 wxDateTime_t hour;
1867 } stdTimes[] =
1868 {
1869 { wxTRANSLATE("noon"), 12 },
1870 { wxTRANSLATE("midnight"), 00 },
1871 // anything else?
1872 };
1873
1874 for ( size_t n = 0; n < WXSIZEOF(stdTimes); n++ )
1875 {
1876 const wxString timeString = wxGetTranslation(stdTimes[n].name);
1877 const wxString::const_iterator p = time.begin() + timeString.length();
1878 if ( timeString.CmpNoCase(wxString(time.begin(), p)) == 0 )
1879 {
1880 // casts required by DigitalMars
1881 Set(stdTimes[n].hour, wxDateTime_t(0), wxDateTime_t(0));
1882
1883 if ( end )
1884 *end = p;
1885
1886 return true;
1887 }
1888 }
1889
1890 // try all time formats we may think about in the order from longest to
1891 // shortest
1892 static const char *timeFormats[] =
1893 {
1894 "%I:%M:%S %p", // 12hour with AM/PM
1895 "%H:%M:%S", // could be the same or 24 hour one so try it too
1896 "%I:%M %p", // 12hour with AM/PM but without seconds
1897 "%H:%M:%S", // and a possibly 24 hour version without seconds
1898 "%X", // possibly something from above or maybe something
1899 // completely different -- try it last
1900
1901 // TODO: parse timezones
1902 };
1903
1904 for ( size_t nFmt = 0; nFmt < WXSIZEOF(timeFormats); nFmt++ )
1905 {
1906 if ( ParseFormat(time, timeFormats[nFmt], end) )
1907 return true;
1908 }
1909
1910 return false;
1911 }
1912
1913 // ----------------------------------------------------------------------------
1914 // Workdays and holidays support
1915 // ----------------------------------------------------------------------------
1916
1917 bool wxDateTime::IsWorkDay(Country WXUNUSED(country)) const
1918 {
1919 return !wxDateTimeHolidayAuthority::IsHoliday(*this);
1920 }
1921
1922 // ============================================================================
1923 // wxDateSpan
1924 // ============================================================================
1925
1926 wxDateSpan WXDLLIMPEXP_BASE operator*(int n, const wxDateSpan& ds)
1927 {
1928 wxDateSpan ds1(ds);
1929 return ds1.Multiply(n);
1930 }
1931
1932 // ============================================================================
1933 // wxTimeSpan
1934 // ============================================================================
1935
1936 wxTimeSpan WXDLLIMPEXP_BASE operator*(int n, const wxTimeSpan& ts)
1937 {
1938 return wxTimeSpan(ts).Multiply(n);
1939 }
1940
1941 // this enum is only used in wxTimeSpan::Format() below but we can't declare
1942 // it locally to the method as it provokes an internal compiler error in egcs
1943 // 2.91.60 when building with -O2
1944 enum TimeSpanPart
1945 {
1946 Part_Week,
1947 Part_Day,
1948 Part_Hour,
1949 Part_Min,
1950 Part_Sec,
1951 Part_MSec
1952 };
1953
1954 // not all strftime(3) format specifiers make sense here because, for example,
1955 // a time span doesn't have a year nor a timezone
1956 //
1957 // Here are the ones which are supported (all of them are supported by strftime
1958 // as well):
1959 // %H hour in 24 hour format
1960 // %M minute (00 - 59)
1961 // %S second (00 - 59)
1962 // %% percent sign
1963 //
1964 // Also, for MFC CTimeSpan compatibility, we support
1965 // %D number of days
1966 //
1967 // And, to be better than MFC :-), we also have
1968 // %E number of wEeks
1969 // %l milliseconds (000 - 999)
1970 wxString wxTimeSpan::Format(const wxString& format) const
1971 {
1972 // we deal with only positive time spans here and just add the sign in
1973 // front for the negative ones
1974 if ( IsNegative() )
1975 {
1976 wxString str(Negate().Format(format));
1977 return "-" + str;
1978 }
1979
1980 wxCHECK_MSG( !format.empty(), wxEmptyString,
1981 _T("NULL format in wxTimeSpan::Format") );
1982
1983 wxString str;
1984 str.Alloc(format.length());
1985
1986 // Suppose we have wxTimeSpan ts(1 /* hour */, 2 /* min */, 3 /* sec */)
1987 //
1988 // Then, of course, ts.Format("%H:%M:%S") must return "01:02:03", but the
1989 // question is what should ts.Format("%S") do? The code here returns "3273"
1990 // in this case (i.e. the total number of seconds, not just seconds % 60)
1991 // because, for me, this call means "give me entire time interval in
1992 // seconds" and not "give me the seconds part of the time interval"
1993 //
1994 // If we agree that it should behave like this, it is clear that the
1995 // interpretation of each format specifier depends on the presence of the
1996 // other format specs in the string: if there was "%H" before "%M", we
1997 // should use GetMinutes() % 60, otherwise just GetMinutes() &c
1998
1999 // we remember the most important unit found so far
2000 TimeSpanPart partBiggest = Part_MSec;
2001
2002 for ( wxString::const_iterator pch = format.begin(); pch != format.end(); ++pch )
2003 {
2004 wxChar ch = *pch;
2005
2006 if ( ch == _T('%') )
2007 {
2008 // the start of the format specification of the printf() below
2009 wxString fmtPrefix(_T('%'));
2010
2011 // the number
2012 long n;
2013
2014 // the number of digits for the format string, 0 if unused
2015 unsigned digits = 0;
2016
2017 ch = *++pch; // get the format spec char
2018 switch ( ch )
2019 {
2020 default:
2021 wxFAIL_MSG( _T("invalid format character") );
2022 // fall through
2023
2024 case _T('%'):
2025 str += ch;
2026
2027 // skip the part below switch
2028 continue;
2029
2030 case _T('D'):
2031 n = GetDays();
2032 if ( partBiggest < Part_Day )
2033 {
2034 n %= DAYS_PER_WEEK;
2035 }
2036 else
2037 {
2038 partBiggest = Part_Day;
2039 }
2040 break;
2041
2042 case _T('E'):
2043 partBiggest = Part_Week;
2044 n = GetWeeks();
2045 break;
2046
2047 case _T('H'):
2048 n = GetHours();
2049 if ( partBiggest < Part_Hour )
2050 {
2051 n %= HOURS_PER_DAY;
2052 }
2053 else
2054 {
2055 partBiggest = Part_Hour;
2056 }
2057
2058 digits = 2;
2059 break;
2060
2061 case _T('l'):
2062 n = GetMilliseconds().ToLong();
2063 if ( partBiggest < Part_MSec )
2064 {
2065 n %= 1000;
2066 }
2067 //else: no need to reset partBiggest to Part_MSec, it is
2068 // the least significant one anyhow
2069
2070 digits = 3;
2071 break;
2072
2073 case _T('M'):
2074 n = GetMinutes();
2075 if ( partBiggest < Part_Min )
2076 {
2077 n %= MIN_PER_HOUR;
2078 }
2079 else
2080 {
2081 partBiggest = Part_Min;
2082 }
2083
2084 digits = 2;
2085 break;
2086
2087 case _T('S'):
2088 n = GetSeconds().ToLong();
2089 if ( partBiggest < Part_Sec )
2090 {
2091 n %= SEC_PER_MIN;
2092 }
2093 else
2094 {
2095 partBiggest = Part_Sec;
2096 }
2097
2098 digits = 2;
2099 break;
2100 }
2101
2102 if ( digits )
2103 {
2104 fmtPrefix << _T("0") << digits;
2105 }
2106
2107 str += wxString::Format(fmtPrefix + _T("ld"), n);
2108 }
2109 else
2110 {
2111 // normal character, just copy
2112 str += ch;
2113 }
2114 }
2115
2116 return str;
2117 }
2118
2119 #endif // wxUSE_DATETIME