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