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