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