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