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