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