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