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