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