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