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