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