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