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