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