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