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