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