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