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