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