1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/common/datetimefmt.cpp
3 // Purpose: wxDateTime formatting & parsing code
4 // Author: Vadim Zeitlin
8 // Copyright: (c) 1999 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // parts of code taken from sndcal library by Scott E. Lee:
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.
16 // Licence: wxWindows licence
17 ///////////////////////////////////////////////////////////////////////////////
19 // ============================================================================
21 // ============================================================================
23 // ----------------------------------------------------------------------------
25 // ----------------------------------------------------------------------------
27 // For compilers that support precompilation, includes "wx.h".
28 #include "wx/wxprec.h"
34 #if !defined(wxUSE_DATETIME) || wxUSE_DATETIME
38 #include "wx/msw/wrapwin.h"
40 #include "wx/string.h"
43 #include "wx/stopwatch.h" // for wxGetLocalTimeMillis()
44 #include "wx/module.h"
48 #include "wx/thread.h"
49 #include "wx/tokenzr.h"
60 #include "wx/datetime.h"
62 // ============================================================================
63 // implementation of wxDateTime
64 // ============================================================================
66 // ----------------------------------------------------------------------------
67 // helpers shared between datetime.cpp and datetimefmt.cpp
68 // ----------------------------------------------------------------------------
70 extern void InitTm(struct tm
& tm
);
72 extern int GetTimeZone();
74 extern wxString
CallStrftime(const wxString
& format
, const tm
* tm
);
76 // ----------------------------------------------------------------------------
77 // constants (see also datetime.cpp)
78 // ----------------------------------------------------------------------------
80 static const int DAYS_PER_WEEK
= 7;
82 static const int HOURS_PER_DAY
= 24;
84 static const int SEC_PER_MIN
= 60;
86 static const int MIN_PER_HOUR
= 60;
88 // ----------------------------------------------------------------------------
90 // ----------------------------------------------------------------------------
97 #if wxUSE_UNIX && !defined(HAVE_STRPTIME_DECL)
98 // configure detected that we had strptime() but not its declaration,
99 // provide it ourselves
100 extern "C" char *strptime(const char *, const char *, struct tm
*);
103 // strptime() wrapper: call strptime() for the string starting at the given
104 // iterator and fill output tm struct with the results and modify input to
105 // point to the end of the string consumed by strptime() if successful,
106 // otherwise return false and don't modify anything
108 CallStrptime(const wxString
& str
,
109 wxString::const_iterator
& p
,
113 // convert from iterator to char pointer: this is simple as wxCStrData
114 // already supports this
115 const char * const start
= str
.c_str() + (p
- str
.begin());
117 const char * const end
= strptime(start
, fmt
, tm
);
121 // convert back from char pointer to iterator: unfortunately we have no way
122 // to do it efficiently currently so create a temporary string just to
123 // compute the number of characters between start and end
124 p
+= wxString(start
, end
- start
).length();
129 #endif // HAVE_STRPTIME
131 // return the month if the string is a month name or Inv_Month otherwise
132 wxDateTime::Month
GetMonthFromName(const wxString
& name
, int flags
)
134 wxDateTime::Month mon
;
135 for ( mon
= wxDateTime::Jan
; mon
< wxDateTime::Inv_Month
; wxNextMonth(mon
) )
137 // case-insensitive comparison either one of or with both abbreviated
139 if ( flags
& wxDateTime::Name_Full
)
141 if ( name
.CmpNoCase(wxDateTime::
142 GetMonthName(mon
, wxDateTime::Name_Full
)) == 0 )
148 if ( flags
& wxDateTime::Name_Abbr
)
150 if ( name
.CmpNoCase(wxDateTime::
151 GetMonthName(mon
, wxDateTime::Name_Abbr
)) == 0 )
161 // return the weekday if the string is a weekday name or Inv_WeekDay otherwise
162 wxDateTime::WeekDay
GetWeekDayFromName(const wxString
& name
, int flags
)
164 wxDateTime::WeekDay wd
;
165 for ( wd
= wxDateTime::Sun
; wd
< wxDateTime::Inv_WeekDay
; wxNextWDay(wd
) )
167 // case-insensitive comparison either one of or with both abbreviated
169 if ( flags
& wxDateTime::Name_Full
)
171 if ( name
.CmpNoCase(wxDateTime::
172 GetWeekDayName(wd
, wxDateTime::Name_Full
)) == 0 )
178 if ( flags
& wxDateTime::Name_Abbr
)
180 if ( name
.CmpNoCase(wxDateTime::
181 GetWeekDayName(wd
, wxDateTime::Name_Abbr
)) == 0 )
191 // scans all digits (but no more than len) and returns the resulting number
192 bool GetNumericToken(size_t len
,
193 wxString::const_iterator
& p
,
194 unsigned long *number
)
198 while ( wxIsdigit(*p
) )
202 if ( len
&& ++n
> len
)
206 return !s
.empty() && s
.ToULong(number
);
209 // scans all alphabetic characters and returns the resulting string
210 wxString
GetAlphaToken(wxString::const_iterator
& p
)
213 while ( wxIsalpha(*p
) )
221 // parses string starting at given iterator using the specified format and,
222 // optionally, a fall back format (and optionally another one... but it stops
225 // if unsuccessful, returns invalid wxDateTime without changing p; otherwise
226 // advance p to the end of the match and returns wxDateTime containing the
227 // results of the parsing
229 ParseFormatAt(wxString::const_iterator
& p
,
230 const wxString::const_iterator
& end
,
232 // FIXME-VC6: using wxString() instead of wxEmptyString in the
233 // line below results in error C2062: type 'class
234 // wxString (__cdecl *)(void)' unexpected
235 const wxString
& fmtAlt
= wxEmptyString
,
236 const wxString
& fmtAlt2
= wxString())
238 const wxString
str(p
, end
);
239 wxString::const_iterator endParse
;
241 if ( dt
.ParseFormat(str
, fmt
, &endParse
) ||
242 (!fmtAlt
.empty() && dt
.ParseFormat(str
, fmtAlt
, &endParse
)) ||
243 (!fmtAlt2
.empty() && dt
.ParseFormat(str
, fmtAlt2
, &endParse
)) )
245 p
+= endParse
- str
.begin();
247 //else: all formats failed
252 } // anonymous namespace
254 // ----------------------------------------------------------------------------
255 // wxDateTime to/from text representations
256 // ----------------------------------------------------------------------------
258 wxString
wxDateTime::Format(const wxString
& format
, const TimeZone
& tz
) const
260 wxCHECK_MSG( !format
.empty(), wxEmptyString
,
261 _T("NULL format in wxDateTime::Format") );
263 // we have to use our own implementation if the date is out of range of
264 // strftime() or if we use non standard specificators
266 time_t time
= GetTicks();
268 if ( (time
!= (time_t)-1) && !wxStrstr(format
, _T("%l")) )
273 if ( tz
.GetOffset() == -GetTimeZone() )
275 // we are working with local time
276 tm
= wxLocaltime_r(&time
, &tmstruct
);
278 // should never happen
279 wxCHECK_MSG( tm
, wxEmptyString
, _T("wxLocaltime_r() failed") );
283 time
+= (int)tz
.GetOffset();
285 #if defined(__VMS__) || defined(__WATCOMC__) // time is unsigned so avoid warning
286 int time2
= (int) time
;
292 tm
= wxGmtime_r(&time
, &tmstruct
);
294 // should never happen
295 wxCHECK_MSG( tm
, wxEmptyString
, _T("wxGmtime_r() failed") );
299 tm
= (struct tm
*)NULL
;
305 return CallStrftime(format
, tm
);
308 //else: use generic code below
309 #endif // HAVE_STRFTIME
311 // we only parse ANSI C format specifications here, no POSIX 2
312 // complications, no GNU extensions but we do add support for a "%l" format
313 // specifier allowing to get the number of milliseconds
316 // used for calls to strftime() when we only deal with time
317 struct tm tmTimeOnly
;
318 tmTimeOnly
.tm_hour
= tm
.hour
;
319 tmTimeOnly
.tm_min
= tm
.min
;
320 tmTimeOnly
.tm_sec
= tm
.sec
;
321 tmTimeOnly
.tm_wday
= 0;
322 tmTimeOnly
.tm_yday
= 0;
323 tmTimeOnly
.tm_mday
= 1; // any date will do
324 tmTimeOnly
.tm_mon
= 0;
325 tmTimeOnly
.tm_year
= 76;
326 tmTimeOnly
.tm_isdst
= 0; // no DST, we adjust for tz ourselves
328 wxString tmp
, res
, fmt
;
329 for ( wxString::const_iterator p
= format
.begin(); p
!= format
.end(); ++p
)
339 // set the default format
340 switch ( (*++p
).GetValue() )
342 case _T('Y'): // year has 4 digits
346 case _T('j'): // day of year has 3 digits
347 case _T('l'): // milliseconds have 3 digits
351 case _T('w'): // week day as number has only one
356 // it's either another valid format specifier in which case
357 // the format is "%02d" (for all the rest) or we have the
358 // field width preceding the format in which case it will
359 // override the default format anyhow
368 // start of the format specification
369 switch ( (*p
).GetValue() )
371 case _T('a'): // a weekday name
373 // second parameter should be true for abbreviated names
374 res
+= GetWeekDayName(tm
.GetWeekDay(),
375 *p
== _T('a') ? Name_Abbr
: Name_Full
);
378 case _T('b'): // a month name
380 res
+= GetMonthName(tm
.mon
,
381 *p
== _T('b') ? Name_Abbr
: Name_Full
);
384 case _T('c'): // locale default date and time representation
385 case _T('x'): // locale default date representation
388 // the problem: there is no way to know what do these format
389 // specifications correspond to for the current locale.
391 // the solution: use a hack and still use strftime(): first
392 // find the YEAR which is a year in the strftime() range (1970
393 // - 2038) whose Jan 1 falls on the same week day as the Jan 1
394 // of the real year. Then make a copy of the format and
395 // replace all occurrences of YEAR in it with some unique
396 // string not appearing anywhere else in it, then use
397 // strftime() to format the date in year YEAR and then replace
398 // YEAR back by the real year and the unique replacement
399 // string back with YEAR. Notice that "all occurrences of YEAR"
400 // means all occurrences of 4 digit as well as 2 digit form!
402 // the bugs: we assume that neither of %c nor %x contains any
403 // fields which may change between the YEAR and real year. For
404 // example, the week number (%U, %W) and the day number (%j)
405 // will change if one of these years is leap and the other one
408 // find the YEAR: normally, for any year X, Jan 1 of the
409 // year X + 28 is the same weekday as Jan 1 of X (because
410 // the weekday advances by 1 for each normal X and by 2
411 // for each leap X, hence by 5 every 4 years or by 35
412 // which is 0 mod 7 every 28 years) but this rule breaks
413 // down if there are years between X and Y which are
414 // divisible by 4 but not leap (i.e. divisible by 100 but
415 // not 400), hence the correction.
417 int yearReal
= GetYear(tz
);
418 int mod28
= yearReal
% 28;
420 // be careful to not go too far - we risk to leave the
425 year
= 1988 + mod28
; // 1988 == 0 (mod 28)
429 year
= 1970 + mod28
- 10; // 1970 == 10 (mod 28)
432 int nCentury
= year
/ 100,
433 nCenturyReal
= yearReal
/ 100;
435 // need to adjust for the years divisble by 400 which are
436 // not leap but are counted like leap ones if we just take
437 // the number of centuries in between for nLostWeekDays
438 int nLostWeekDays
= (nCentury
- nCenturyReal
) -
439 (nCentury
/ 4 - nCenturyReal
/ 4);
441 // we have to gain back the "lost" weekdays: note that the
442 // effect of this loop is to not do anything to
443 // nLostWeekDays (which we won't use any more), but to
444 // (indirectly) set the year correctly
445 while ( (nLostWeekDays
% 7) != 0 )
447 nLostWeekDays
+= year
++ % 4 ? 1 : 2;
450 // finally move the year below 2000 so that the 2-digit
451 // year number can never match the month or day of the
452 // month when we do the replacements below
456 wxASSERT_MSG( year
>= 1970 && year
< 2000,
457 _T("logic error in wxDateTime::Format") );
460 // use strftime() to format the same date but in supported
463 // NB: we assume that strftime() doesn't check for the
464 // date validity and will happily format the date
465 // corresponding to Feb 29 of a non leap year (which
466 // may happen if yearReal was leap and year is not)
467 struct tm tmAdjusted
;
469 tmAdjusted
.tm_hour
= tm
.hour
;
470 tmAdjusted
.tm_min
= tm
.min
;
471 tmAdjusted
.tm_sec
= tm
.sec
;
472 tmAdjusted
.tm_wday
= tm
.GetWeekDay();
473 tmAdjusted
.tm_yday
= GetDayOfYear();
474 tmAdjusted
.tm_mday
= tm
.mday
;
475 tmAdjusted
.tm_mon
= tm
.mon
;
476 tmAdjusted
.tm_year
= year
- 1900;
477 tmAdjusted
.tm_isdst
= 0; // no DST, already adjusted
478 wxString str
= CallStrftime(*p
== _T('c') ? _T("%c")
482 // now replace the replacement year with the real year:
483 // notice that we have to replace the 4 digit year with
484 // a unique string not appearing in strftime() output
485 // first to prevent the 2 digit year from matching any
486 // substring of the 4 digit year (but any day, month,
487 // hours or minutes components should be safe because
488 // they are never in 70-99 range)
489 wxString
replacement("|");
490 while ( str
.find(replacement
) != wxString::npos
)
493 str
.Replace(wxString::Format("%d", year
),
495 str
.Replace(wxString::Format("%d", year
% 100),
496 wxString::Format("%d", yearReal
% 100));
497 str
.Replace(replacement
,
498 wxString::Format("%d", yearReal
));
502 #else // !HAVE_STRFTIME
503 // Use "%m/%d/%y %H:%M:%S" format instead
504 res
+= wxString::Format(wxT("%02d/%02d/%04d %02d:%02d:%02d"),
505 tm
.mon
+1,tm
.mday
, tm
.year
, tm
.hour
, tm
.min
, tm
.sec
);
506 #endif // HAVE_STRFTIME/!HAVE_STRFTIME
509 case _T('d'): // day of a month (01-31)
510 res
+= wxString::Format(fmt
, tm
.mday
);
513 case _T('H'): // hour in 24h format (00-23)
514 res
+= wxString::Format(fmt
, tm
.hour
);
517 case _T('I'): // hour in 12h format (01-12)
519 // 24h -> 12h, 0h -> 12h too
520 int hour12
= tm
.hour
> 12 ? tm
.hour
- 12
521 : tm
.hour
? tm
.hour
: 12;
522 res
+= wxString::Format(fmt
, hour12
);
526 case _T('j'): // day of the year
527 res
+= wxString::Format(fmt
, GetDayOfYear(tz
));
530 case _T('l'): // milliseconds (NOT STANDARD)
531 res
+= wxString::Format(fmt
, GetMillisecond(tz
));
534 case _T('m'): // month as a number (01-12)
535 res
+= wxString::Format(fmt
, tm
.mon
+ 1);
538 case _T('M'): // minute as a decimal number (00-59)
539 res
+= wxString::Format(fmt
, tm
.min
);
542 case _T('p'): // AM or PM string
544 res
+= CallStrftime(_T("%p"), &tmTimeOnly
);
545 #else // !HAVE_STRFTIME
546 res
+= (tmTimeOnly
.tm_hour
> 12) ? wxT("pm") : wxT("am");
547 #endif // HAVE_STRFTIME/!HAVE_STRFTIME
550 case _T('S'): // second as a decimal number (00-61)
551 res
+= wxString::Format(fmt
, tm
.sec
);
554 case _T('U'): // week number in the year (Sunday 1st week day)
555 res
+= wxString::Format(fmt
, GetWeekOfYear(Sunday_First
, tz
));
558 case _T('W'): // week number in the year (Monday 1st week day)
559 res
+= wxString::Format(fmt
, GetWeekOfYear(Monday_First
, tz
));
562 case _T('w'): // weekday as a number (0-6), Sunday = 0
563 res
+= wxString::Format(fmt
, tm
.GetWeekDay());
566 // case _T('x'): -- handled with "%c"
568 case _T('X'): // locale default time representation
569 // just use strftime() to format the time for us
571 res
+= CallStrftime(_T("%X"), &tmTimeOnly
);
572 #else // !HAVE_STRFTIME
573 res
+= wxString::Format(wxT("%02d:%02d:%02d"),tm
.hour
, tm
.min
, tm
.sec
);
574 #endif // HAVE_STRFTIME/!HAVE_STRFTIME
577 case _T('y'): // year without century (00-99)
578 res
+= wxString::Format(fmt
, tm
.year
% 100);
581 case _T('Y'): // year with century
582 res
+= wxString::Format(fmt
, tm
.year
);
585 case _T('Z'): // timezone name
587 res
+= CallStrftime(_T("%Z"), &tmTimeOnly
);
592 // is it the format width?
594 while ( *p
== _T('-') || *p
== _T('+') ||
595 *p
== _T(' ') || wxIsdigit(*p
) )
602 // we've only got the flags and width so far in fmt
603 fmt
.Prepend(_T('%'));
611 // no, it wasn't the width
612 wxFAIL_MSG(_T("unknown format specificator"));
614 // fall through and just copy it nevertheless
616 case _T('%'): // a percent sign
620 case 0: // the end of string
621 wxFAIL_MSG(_T("missing format at the end of string"));
623 // just put the '%' which was the last char in format
633 // this function parses a string in (strict) RFC 822 format: see the section 5
634 // of the RFC for the detailed description, but briefly it's something of the
635 // form "Sat, 18 Dec 1999 00:48:30 +0100"
637 // this function is "strict" by design - it must reject anything except true
638 // RFC822 time specs.
640 // TODO a great candidate for using reg exps
642 wxDateTime::ParseRfc822Date(const wxString
& date
, wxString::const_iterator
*end
)
644 // TODO: rewrite using iterators instead of wxChar pointers
645 const wxStringCharType
*p
= date
.wx_str();
646 const wxStringCharType
*comma
= wxStrchr(p
, wxS(','));
649 // the part before comma is the weekday
651 // skip it for now - we don't use but might check that it really
652 // corresponds to the specfied date
657 wxLogDebug(_T("no space after weekday in RFC822 time spec"));
665 // the following 1 or 2 digits are the day number
666 if ( !wxIsdigit(*p
) )
668 wxLogDebug(_T("day number expected in RFC822 time spec, none found"));
673 wxDateTime_t day
= (wxDateTime_t
)(*p
++ - _T('0'));
677 day
= (wxDateTime_t
)(day
+ (*p
++ - _T('0')));
680 if ( *p
++ != _T(' ') )
685 // the following 3 letters specify the month
686 wxString
monName(p
, 3);
688 if ( monName
== _T("Jan") )
690 else if ( monName
== _T("Feb") )
692 else if ( monName
== _T("Mar") )
694 else if ( monName
== _T("Apr") )
696 else if ( monName
== _T("May") )
698 else if ( monName
== _T("Jun") )
700 else if ( monName
== _T("Jul") )
702 else if ( monName
== _T("Aug") )
704 else if ( monName
== _T("Sep") )
706 else if ( monName
== _T("Oct") )
708 else if ( monName
== _T("Nov") )
710 else if ( monName
== _T("Dec") )
714 wxLogDebug(_T("Invalid RFC 822 month name '%s'"), monName
.c_str());
721 if ( *p
++ != _T(' ') )
727 if ( !wxIsdigit(*p
) )
733 int year
= *p
++ - _T('0');
735 if ( !wxIsdigit(*p
) )
737 // should have at least 2 digits in the year
742 year
+= *p
++ - _T('0');
744 // is it a 2 digit year (as per original RFC 822) or a 4 digit one?
748 year
+= *p
++ - _T('0');
750 if ( !wxIsdigit(*p
) )
752 // no 3 digit years please
757 year
+= *p
++ - _T('0');
760 if ( *p
++ != _T(' ') )
765 // time is in the format hh:mm:ss and seconds are optional
766 if ( !wxIsdigit(*p
) )
771 wxDateTime_t hour
= (wxDateTime_t
)(*p
++ - _T('0'));
773 if ( !wxIsdigit(*p
) )
779 hour
= (wxDateTime_t
)(hour
+ (*p
++ - _T('0')));
781 if ( *p
++ != _T(':') )
786 if ( !wxIsdigit(*p
) )
791 wxDateTime_t min
= (wxDateTime_t
)(*p
++ - _T('0'));
793 if ( !wxIsdigit(*p
) )
799 min
= (wxDateTime_t
)(min
+ *p
++ - _T('0'));
801 wxDateTime_t sec
= 0;
805 if ( !wxIsdigit(*p
) )
810 sec
= (wxDateTime_t
)(*p
++ - _T('0'));
812 if ( !wxIsdigit(*p
) )
818 sec
= (wxDateTime_t
)(sec
+ *p
++ - _T('0'));
821 if ( *p
++ != _T(' ') )
826 // and now the interesting part: the timezone
827 int offset
wxDUMMY_INITIALIZE(0);
828 if ( *p
== _T('-') || *p
== _T('+') )
830 // the explicit offset given: it has the form of hhmm
831 bool plus
= *p
++ == _T('+');
833 if ( !wxIsdigit(*p
) || !wxIsdigit(*(p
+ 1)) )
839 offset
= MIN_PER_HOUR
*(10*(*p
- _T('0')) + (*(p
+ 1) - _T('0')));
843 if ( !wxIsdigit(*p
) || !wxIsdigit(*(p
+ 1)) )
849 offset
+= 10*(*p
- _T('0')) + (*(p
+ 1) - _T('0'));
860 // the symbolic timezone given: may be either military timezone or one
861 // of standard abbreviations
864 // military: Z = UTC, J unused, A = -1, ..., Y = +12
865 static const int offsets
[26] =
867 //A B C D E F G H I J K L M
868 -1, -2, -3, -4, -5, -6, -7, -8, -9, 0, -10, -11, -12,
869 //N O P R Q S T U V W Z Y Z
870 +1, +2, +3, +4, +5, +6, +7, +8, +9, +10, +11, +12, 0
873 if ( *p
< _T('A') || *p
> _T('Z') || *p
== _T('J') )
875 wxLogDebug(_T("Invalid militaty timezone '%c'"), *p
);
880 offset
= offsets
[*p
++ - _T('A')];
886 if ( tz
== _T("UT") || tz
== _T("UTC") || tz
== _T("GMT") )
888 else if ( tz
== _T("AST") )
890 else if ( tz
== _T("ADT") )
892 else if ( tz
== _T("EST") )
894 else if ( tz
== _T("EDT") )
896 else if ( tz
== _T("CST") )
898 else if ( tz
== _T("CDT") )
900 else if ( tz
== _T("MST") )
902 else if ( tz
== _T("MDT") )
904 else if ( tz
== _T("PST") )
906 else if ( tz
== _T("PDT") )
910 wxLogDebug(_T("Unknown RFC 822 timezone '%s'"), p
);
919 offset
*= MIN_PER_HOUR
;
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
));
926 const size_t endpos
= p
- date
.wx_str();
928 *end
= date
.begin() + endpos
;
930 return date
.c_str() + endpos
;
935 // returns the string containing strftime() format used for short dates in the
936 // current locale or an empty string
937 static wxString
GetLocaleDateFormat()
941 // there is no setlocale() under Windows CE, so just always query the
944 if ( strcmp(setlocale(LC_ALL
, NULL
), "C") != 0 )
947 // The locale was programatically set to non-C. We assume that this was
948 // done using wxLocale, in which case thread's current locale is also
949 // set to correct LCID value and we can use GetLocaleInfo to determine
950 // the correct formatting string:
952 LCID lcid
= LOCALE_USER_DEFAULT
;
954 LCID lcid
= GetThreadLocale();
956 // according to MSDN 80 chars is max allowed for short date format
958 if ( ::GetLocaleInfo(lcid
, LOCALE_SSHORTDATE
, fmt
, WXSIZEOF(fmt
)) )
960 wxChar chLast
= _T('\0');
961 size_t lastCount
= 0;
962 for ( const wxChar
*p
= fmt
; /* NUL handled inside */; p
++ )
972 // these characters come in groups, start counting them
982 // first deal with any special characters we have had
992 // these two are the same as we
993 // don't distinguish between 1 and
1007 wxFAIL_MSG( _T("too many 'd's") );
1012 switch ( lastCount
)
1016 // as for 'd' and 'dd' above
1029 wxFAIL_MSG( _T("too many 'M's") );
1034 switch ( lastCount
)
1046 wxFAIL_MSG( _T("wrong number of 'y's") );
1051 // strftime() doesn't have era string,
1052 // ignore this format
1053 wxASSERT_MSG( lastCount
<= 2,
1054 _T("too many 'g's") );
1058 wxFAIL_MSG( _T("unreachable") );
1065 // not a special character so must be just a separator,
1067 if ( *p
!= _T('\0') )
1069 if ( *p
== _T('%') )
1071 // this one needs to be escaped
1079 if ( *p
== _T('\0') )
1083 //else: GetLocaleInfo() failed, leave fmtDate value unchanged and
1084 // try our luck with the default formats
1086 //else: default C locale, default formats should work
1091 #endif // __WINDOWS__
1094 wxDateTime::ParseFormat(const wxString
& date
,
1095 const wxString
& format
,
1096 const wxDateTime
& dateDef
,
1097 wxString::const_iterator
*end
)
1099 wxCHECK_MSG( !format
.empty(), NULL
, "format can't be empty" );
1104 // what fields have we found?
1105 bool haveWDay
= false,
1115 bool hourIsIn12hFormat
= false, // or in 24h one?
1116 isPM
= false; // AM by default
1118 // and the value of the items we have (init them to get rid of warnings)
1119 wxDateTime_t msec
= 0,
1123 WeekDay wday
= Inv_WeekDay
;
1124 wxDateTime_t yday
= 0,
1126 wxDateTime::Month mon
= Inv_Month
;
1129 wxString::const_iterator input
= date
.begin();
1130 for ( wxString::const_iterator fmt
= format
.begin(); fmt
!= format
.end(); ++fmt
)
1132 if ( *fmt
!= _T('%') )
1134 if ( wxIsspace(*fmt
) )
1136 // a white space in the format string matches 0 or more white
1137 // spaces in the input
1138 while ( wxIsspace(*input
) )
1145 // any other character (not whitespace, not '%') must be
1146 // matched by itself in the input
1147 if ( *input
++ != *fmt
)
1154 // done with this format char
1158 // start of a format specification
1160 // parse the optional width
1162 while ( wxIsdigit(*++fmt
) )
1165 width
+= *fmt
- _T('0');
1168 // the default widths for the various fields
1171 switch ( (*fmt
).GetValue() )
1173 case _T('Y'): // year has 4 digits
1177 case _T('j'): // day of year has 3 digits
1178 case _T('l'): // milliseconds have 3 digits
1182 case _T('w'): // week day as number has only one
1187 // default for all other fields
1192 // then the format itself
1193 switch ( (*fmt
).GetValue() )
1195 case _T('a'): // a weekday name
1198 int flag
= *fmt
== _T('a') ? Name_Abbr
: Name_Full
;
1199 wday
= GetWeekDayFromName(GetAlphaToken(input
), flag
);
1200 if ( wday
== Inv_WeekDay
)
1209 case _T('b'): // a month name
1212 int flag
= *fmt
== _T('b') ? Name_Abbr
: Name_Full
;
1213 mon
= GetMonthFromName(GetAlphaToken(input
), flag
);
1214 if ( mon
== Inv_Month
)
1223 case _T('c'): // locale default date and time representation
1225 #ifdef HAVE_STRPTIME
1228 // try using strptime() -- it may fail even if the input is
1229 // correct but the date is out of range, so we will fall back
1230 // to our generic code anyhow
1231 if ( CallStrptime(date
, input
, "%c", &tm
) )
1237 year
= 1900 + tm
.tm_year
;
1238 mon
= (Month
)tm
.tm_mon
;
1241 else // strptime() failed; try generic heuristic code
1242 #endif // HAVE_STRPTIME
1245 // try the format which corresponds to ctime() output
1246 // first, then the generic date/time formats
1247 const wxDateTime dt
= ParseFormatAt
1251 wxS("%a %b %d %H:%M:%S %Y"),
1255 if ( !dt
.IsValid() )
1269 haveDay
= haveMon
= haveYear
=
1270 haveHour
= haveMin
= haveSec
= true;
1274 case _T('d'): // day of a month (01-31)
1275 if ( !GetNumericToken(width
, input
, &num
) ||
1276 (num
> 31) || (num
< 1) )
1282 // we can't check whether the day range is correct yet, will
1283 // do it later - assume ok for now
1285 mday
= (wxDateTime_t
)num
;
1288 case _T('H'): // hour in 24h format (00-23)
1289 if ( !GetNumericToken(width
, input
, &num
) || (num
> 23) )
1296 hour
= (wxDateTime_t
)num
;
1299 case _T('I'): // hour in 12h format (01-12)
1300 if ( !GetNumericToken(width
, input
, &num
) || !num
|| (num
> 12) )
1307 hourIsIn12hFormat
= true;
1308 hour
= (wxDateTime_t
)(num
% 12); // 12 should be 0
1311 case _T('j'): // day of the year
1312 if ( !GetNumericToken(width
, input
, &num
) || !num
|| (num
> 366) )
1319 yday
= (wxDateTime_t
)num
;
1322 case _T('l'): // milliseconds (0-999)
1323 if ( !GetNumericToken(width
, input
, &num
) )
1327 msec
= (wxDateTime_t
)num
;
1330 case _T('m'): // month as a number (01-12)
1331 if ( !GetNumericToken(width
, input
, &num
) || !num
|| (num
> 12) )
1338 mon
= (Month
)(num
- 1);
1341 case _T('M'): // minute as a decimal number (00-59)
1342 if ( !GetNumericToken(width
, input
, &num
) || (num
> 59) )
1349 min
= (wxDateTime_t
)num
;
1352 case _T('p'): // AM or PM string
1354 wxString am
, pm
, token
= GetAlphaToken(input
);
1356 // some locales have empty AM/PM tokens and thus when formatting
1357 // dates with the %p specifier nothing is generated; when trying to
1358 // parse them back, we get an empty token here... but that's not
1363 GetAmPmStrings(&am
, &pm
);
1364 if (am
.empty() && pm
.empty())
1365 return NULL
; // no am/pm strings defined
1366 if ( token
.CmpNoCase(pm
) == 0 )
1370 else if ( token
.CmpNoCase(am
) != 0 )
1378 case _T('r'): // time as %I:%M:%S %p
1381 if ( !dt
.ParseFormat(wxString(input
, date
.end()),
1382 wxS("%I:%M:%S %p"), &input
) )
1385 haveHour
= haveMin
= haveSec
= true;
1394 case _T('R'): // time as %H:%M
1397 dt
= ParseFormatAt(input
, date
.end(), wxS("%H:%M"));
1398 if ( !dt
.IsValid() )
1410 case _T('S'): // second as a decimal number (00-61)
1411 if ( !GetNumericToken(width
, input
, &num
) || (num
> 61) )
1418 sec
= (wxDateTime_t
)num
;
1421 case _T('T'): // time as %H:%M:%S
1424 dt
= ParseFormatAt(input
, date
.end(), wxS("%H:%M:%S"));
1425 if ( !dt
.IsValid() )
1439 case _T('w'): // weekday as a number (0-6), Sunday = 0
1440 if ( !GetNumericToken(width
, input
, &num
) || (wday
> 6) )
1447 wday
= (WeekDay
)num
;
1450 case _T('x'): // locale default date representation
1451 #ifdef HAVE_STRPTIME
1452 // try using strptime() -- it may fail even if the input is
1453 // correct but the date is out of range, so we will fall back
1454 // to our generic code anyhow
1458 if ( CallStrptime(date
, input
, "%x", &tm
) )
1460 haveDay
= haveMon
= haveYear
= true;
1462 year
= 1900 + tm
.tm_year
;
1463 mon
= (Month
)tm
.tm_mon
;
1469 #endif // HAVE_STRPTIME
1476 // The above doesn't work for all locales, try to query
1477 // Windows for the right way of formatting the date:
1478 fmtDate
= GetLocaleDateFormat();
1479 if ( fmtDate
.empty() )
1480 #endif // __WINDOWS__
1482 if ( IsWestEuropeanCountry(GetCountry()) ||
1483 GetCountry() == Russia
)
1485 fmtDate
= _T("%d/%m/%y");
1486 fmtDateAlt
= _T("%m/%d/%y");
1490 fmtDate
= _T("%m/%d/%y");
1491 fmtDateAlt
= _T("%d/%m/%y");
1496 dt
= ParseFormatAt(input
, date
.end(),
1497 fmtDate
, fmtDateAlt
);
1498 if ( !dt
.IsValid() )
1514 case _T('X'): // locale default time representation
1515 #ifdef HAVE_STRPTIME
1517 // use strptime() to do it for us (FIXME !Unicode friendly)
1519 if ( !CallStrptime(date
, input
, "%X", &tm
) )
1522 haveHour
= haveMin
= haveSec
= true;
1528 #else // !HAVE_STRPTIME
1529 // TODO under Win32 we can query the LOCALE_ITIME system
1530 // setting which says whether the default time format is
1533 // try to parse what follows as "%H:%M:%S" and, if this
1534 // fails, as "%I:%M:%S %p" - this should catch the most
1537 dt
= ParseFormatAt(input
, date
.end(), "%T", "%r");
1538 if ( !dt
.IsValid() )
1550 #endif // HAVE_STRPTIME/!HAVE_STRPTIME
1553 case _T('y'): // year without century (00-99)
1554 if ( !GetNumericToken(width
, input
, &num
) || (num
> 99) )
1562 // TODO should have an option for roll over date instead of
1563 // hard coding it here
1564 year
= (num
> 30 ? 1900 : 2000) + (wxDateTime_t
)num
;
1567 case _T('Y'): // year with century
1568 if ( !GetNumericToken(width
, input
, &num
) )
1575 year
= (wxDateTime_t
)num
;
1578 case _T('Z'): // timezone name
1579 wxFAIL_MSG(_T("TODO"));
1582 case _T('%'): // a percent sign
1583 if ( *input
++ != _T('%') )
1590 case 0: // the end of string
1591 wxFAIL_MSG(_T("unexpected format end"));
1595 default: // not a known format spec
1600 // format matched, try to construct a date from what we have now
1602 if ( dateDef
.IsValid() )
1604 // take this date as default
1605 tmDef
= dateDef
.GetTm();
1607 else if ( IsValid() )
1609 // if this date is valid, don't change it
1614 // no default and this date is invalid - fall back to Today()
1615 tmDef
= Today().GetTm();
1631 // TODO we don't check here that the values are consistent, if both year
1632 // day and month/day were found, we just ignore the year day and we
1633 // also always ignore the week day
1636 if ( mday
> GetNumberOfDays(tm
.mon
, tm
.year
) )
1638 wxLogDebug(_T("bad month day in wxDateTime::ParseFormat"));
1645 else if ( haveYDay
)
1647 if ( yday
> GetNumberOfDays(tm
.year
) )
1649 wxLogDebug(_T("bad year day in wxDateTime::ParseFormat"));
1654 Tm tm2
= wxDateTime(1, Jan
, tm
.year
).SetToYearDay(yday
).GetTm();
1661 if ( haveHour
&& hourIsIn12hFormat
&& isPM
)
1663 // translate to 24hour format
1666 //else: either already in 24h format or no translation needed
1689 // finally check that the week day is consistent -- if we had it
1690 if ( haveWDay
&& GetWeekDay() != wday
)
1692 wxLogDebug(_T("inconsistsnet week day in wxDateTime::ParseFormat()"));
1700 return date
.c_str() + (input
- date
.begin());
1704 wxDateTime::ParseDateTime(const wxString
& date
, wxString::const_iterator
*end
)
1706 // Set to current day and hour, so strings like '14:00' becomes today at
1707 // 14, not some other random date
1708 wxDateTime dtDate
= wxDateTime::Today();
1709 wxDateTime dtTime
= wxDateTime::Today();
1711 wxString::const_iterator
1716 // If we got a date in the beginning, see if there is a time specified
1718 if ( dtDate
.ParseDate(date
, &endDate
) )
1720 // Skip spaces, as the ParseTime() function fails on spaces
1721 while ( endDate
!= date
.end() && wxIsspace(*endDate
) )
1724 const wxString
timestr(endDate
, date
.end());
1725 if ( !dtTime
.ParseTime(timestr
, &endTime
) )
1728 endBoth
= endDate
+ (endTime
- timestr
.begin());
1730 else // no date in the beginning
1732 // check if we have a time followed by a date
1733 if ( !dtTime
.ParseTime(date
, &endTime
) )
1736 while ( endTime
!= date
.end() && wxIsspace(*endTime
) )
1739 const wxString
datestr(endTime
, date
.end());
1740 if ( !dtDate
.ParseDate(datestr
, &endDate
) )
1743 endBoth
= endTime
+ (endDate
- datestr
.begin());
1746 Set(dtDate
.GetDay(), dtDate
.GetMonth(), dtDate
.GetYear(),
1747 dtTime
.GetHour(), dtTime
.GetMinute(), dtTime
.GetSecond(),
1748 dtTime
.GetMillisecond());
1750 // Return endpoint of scan
1754 return date
.c_str() + (endBoth
- date
.begin());
1758 wxDateTime::ParseDate(const wxString
& date
, wxString::const_iterator
*end
)
1760 // this is a simplified version of ParseDateTime() which understands only
1761 // "today" (for wxDate compatibility) and digits only otherwise (and not
1762 // all esoteric constructions ParseDateTime() knows about)
1764 const wxString::const_iterator pBegin
= date
.begin();
1766 wxString::const_iterator p
= pBegin
;
1767 while ( wxIsspace(*p
) )
1770 // some special cases
1774 int dayDiffFromToday
;
1777 { wxTRANSLATE("today"), 0 },
1778 { wxTRANSLATE("yesterday"), -1 },
1779 { wxTRANSLATE("tomorrow"), 1 },
1782 for ( size_t n
= 0; n
< WXSIZEOF(literalDates
); n
++ )
1784 const wxString dateStr
= wxGetTranslation(literalDates
[n
].str
);
1785 size_t len
= dateStr
.length();
1787 const wxString::const_iterator pEnd
= p
+ len
;
1788 if ( wxString(p
, pEnd
).CmpNoCase(dateStr
) == 0 )
1790 // nothing can follow this, so stop here
1794 int dayDiffFromToday
= literalDates
[n
].dayDiffFromToday
;
1796 if ( dayDiffFromToday
)
1798 *this += wxDateSpan::Days(dayDiffFromToday
);
1804 return wxStringOperations::AddToIter(date
.c_str().AsChar(),
1809 // We try to guess what we have here: for each new (numeric) token, we
1810 // determine if it can be a month, day or a year. Of course, there is an
1811 // ambiguity as some numbers may be days as well as months, so we also
1812 // have the ability to back track.
1815 bool haveDay
= false, // the months day?
1816 haveWDay
= false, // the day of week?
1817 haveMon
= false, // the month?
1818 haveYear
= false; // the year?
1820 // and the value of the items we have (init them to get rid of warnings)
1821 WeekDay wday
= Inv_WeekDay
;
1822 wxDateTime_t day
= 0;
1823 wxDateTime::Month mon
= Inv_Month
;
1826 // tokenize the string
1828 static const wxStringCharType
*dateDelimiters
= wxS(".,/-\t\r\n ");
1829 wxStringTokenizer
tok(wxString(p
, date
.end()), dateDelimiters
);
1830 while ( tok
.HasMoreTokens() )
1832 wxString token
= tok
.GetNextToken();
1838 if ( token
.ToULong(&val
) )
1840 // guess what this number is
1846 if ( !haveMon
&& val
> 0 && val
<= 12 )
1848 // assume it is month
1851 else // not the month
1855 // this can only be the year
1858 else // may be either day or year
1860 // use a leap year if we don't have the year yet to allow
1861 // dates like 2/29/1976 which would be rejected otherwise
1862 wxDateTime_t max_days
= (wxDateTime_t
)(
1864 ? GetNumberOfDays(mon
, haveYear
? year
: 1976)
1869 if ( (val
== 0) || (val
> (unsigned long)max_days
) )
1874 else // yes, suppose it's the day
1888 year
= (wxDateTime_t
)val
;
1897 day
= (wxDateTime_t
)val
;
1903 mon
= (Month
)(val
- 1);
1906 else // not a number
1908 // be careful not to overwrite the current mon value
1909 Month mon2
= GetMonthFromName(token
, Name_Full
| Name_Abbr
);
1910 if ( mon2
!= Inv_Month
)
1915 // but we already have a month - maybe we guessed wrong?
1918 // no need to check in month range as always < 12, but
1919 // the days are counted from 1 unlike the months
1920 day
= (wxDateTime_t
)(mon
+ 1);
1925 // could possible be the year (doesn't the year come
1926 // before the month in the japanese format?) (FIXME)
1935 else // not a valid month name
1937 WeekDay wday2
= GetWeekDayFromName(token
, Name_Full
| Name_Abbr
);
1938 if ( wday2
!= Inv_WeekDay
)
1950 else // not a valid weekday name
1953 static const char *ordinals
[] =
1955 wxTRANSLATE("first"),
1956 wxTRANSLATE("second"),
1957 wxTRANSLATE("third"),
1958 wxTRANSLATE("fourth"),
1959 wxTRANSLATE("fifth"),
1960 wxTRANSLATE("sixth"),
1961 wxTRANSLATE("seventh"),
1962 wxTRANSLATE("eighth"),
1963 wxTRANSLATE("ninth"),
1964 wxTRANSLATE("tenth"),
1965 wxTRANSLATE("eleventh"),
1966 wxTRANSLATE("twelfth"),
1967 wxTRANSLATE("thirteenth"),
1968 wxTRANSLATE("fourteenth"),
1969 wxTRANSLATE("fifteenth"),
1970 wxTRANSLATE("sixteenth"),
1971 wxTRANSLATE("seventeenth"),
1972 wxTRANSLATE("eighteenth"),
1973 wxTRANSLATE("nineteenth"),
1974 wxTRANSLATE("twentieth"),
1975 // that's enough - otherwise we'd have problems with
1976 // composite (or not) ordinals
1980 for ( n
= 0; n
< WXSIZEOF(ordinals
); n
++ )
1982 if ( token
.CmpNoCase(ordinals
[n
]) == 0 )
1988 if ( n
== WXSIZEOF(ordinals
) )
1990 // stop here - something unknown
1997 // don't try anything here (as in case of numeric day
1998 // above) - the symbolic day spec should always
1999 // precede the month/year
2005 day
= (wxDateTime_t
)(n
+ 1);
2010 nPosCur
= tok
.GetPosition();
2013 // either no more tokens or the scan was stopped by something we couldn't
2014 // parse - in any case, see if we can construct a date from what we have
2015 if ( !haveDay
&& !haveWDay
)
2017 wxLogDebug(_T("ParseDate: no day, no weekday hence no date."));
2022 if ( haveWDay
&& (haveMon
|| haveYear
|| haveDay
) &&
2023 !(haveDay
&& haveMon
&& haveYear
) )
2025 // without adjectives (which we don't support here) the week day only
2026 // makes sense completely separately or with the full date
2027 // specification (what would "Wed 1999" mean?)
2031 if ( !haveWDay
&& haveYear
&& !(haveDay
&& haveMon
) )
2033 // may be we have month and day instead of day and year?
2034 if ( haveDay
&& !haveMon
)
2038 // exchange day and month
2039 mon
= (wxDateTime::Month
)(day
- 1);
2041 // we're in the current year then
2042 if ( (year
> 0) && (year
<= (int)GetNumberOfDays(mon
, Inv_Year
)) )
2044 day
= (wxDateTime_t
)year
;
2049 //else: no, can't exchange, leave haveMon == false
2055 // if we give the year, month and day must be given too
2056 wxLogDebug(_T("ParseDate: day and month should be specified if year is."));
2064 mon
= GetCurrentMonth();
2069 year
= GetCurrentYear();
2074 // normally we check the day above but the check is optimistic in case
2075 // we find the day before its month/year so we have to redo it now
2076 if ( day
> GetNumberOfDays(mon
, year
) )
2079 Set(day
, mon
, year
);
2083 // check that it is really the same
2084 if ( GetWeekDay() != wday
)
2086 // inconsistency detected
2087 wxLogDebug(_T("ParseDate: inconsistent day/weekday."));
2097 SetToWeekDayInSameWeek(wday
);
2100 // return the pointer to the first unparsed char
2102 if ( nPosCur
&& wxStrchr(dateDelimiters
, *(p
- 1)) )
2104 // if we couldn't parse the token after the delimiter, put back the
2105 // delimiter as well
2112 return wxStringOperations::AddToIter(date
.c_str().AsChar(), p
- pBegin
);
2116 wxDateTime::ParseTime(const wxString
& time
, wxString::const_iterator
*end
)
2118 // first try some extra things
2125 { wxTRANSLATE("noon"), 12 },
2126 { wxTRANSLATE("midnight"), 00 },
2130 for ( size_t n
= 0; n
< WXSIZEOF(stdTimes
); n
++ )
2132 wxString timeString
= wxGetTranslation(stdTimes
[n
].name
);
2133 size_t len
= timeString
.length();
2134 if ( timeString
.CmpNoCase(wxString(time
, len
)) == 0 )
2136 // casts required by DigitalMars
2137 Set(stdTimes
[n
].hour
, wxDateTime_t(0), wxDateTime_t(0));
2140 *end
= time
.begin() + len
;
2142 return time
.c_str() + len
;
2146 // try all time formats we may think about in the order from longest to
2148 static const char *timeFormats
[] =
2150 "%I:%M:%S %p", // 12hour with AM/PM
2151 "%H:%M:%S", // could be the same or 24 hour one so try it too
2152 "%I:%M %p", // 12hour with AM/PM but without seconds
2153 "%H:%M:%S", // and a possibly 24 hour version without seconds
2154 "%X", // possibly something from above or maybe something
2155 // completely different -- try it last
2157 // TODO: parse timezones
2160 for ( size_t nFmt
= 0; nFmt
< WXSIZEOF(timeFormats
); nFmt
++ )
2162 const char *result
= ParseFormat(time
, timeFormats
[nFmt
], end
);
2170 // ----------------------------------------------------------------------------
2171 // Workdays and holidays support
2172 // ----------------------------------------------------------------------------
2174 bool wxDateTime::IsWorkDay(Country
WXUNUSED(country
)) const
2176 return !wxDateTimeHolidayAuthority::IsHoliday(*this);
2179 // ============================================================================
2181 // ============================================================================
2183 wxDateSpan WXDLLIMPEXP_BASE
operator*(int n
, const wxDateSpan
& ds
)
2186 return ds1
.Multiply(n
);
2189 // ============================================================================
2191 // ============================================================================
2193 wxTimeSpan WXDLLIMPEXP_BASE
operator*(int n
, const wxTimeSpan
& ts
)
2195 return wxTimeSpan(ts
).Multiply(n
);
2198 // this enum is only used in wxTimeSpan::Format() below but we can't declare
2199 // it locally to the method as it provokes an internal compiler error in egcs
2200 // 2.91.60 when building with -O2
2211 // not all strftime(3) format specifiers make sense here because, for example,
2212 // a time span doesn't have a year nor a timezone
2214 // Here are the ones which are supported (all of them are supported by strftime
2216 // %H hour in 24 hour format
2217 // %M minute (00 - 59)
2218 // %S second (00 - 59)
2221 // Also, for MFC CTimeSpan compatibility, we support
2222 // %D number of days
2224 // And, to be better than MFC :-), we also have
2225 // %E number of wEeks
2226 // %l milliseconds (000 - 999)
2227 wxString
wxTimeSpan::Format(const wxString
& format
) const
2229 // we deal with only positive time spans here and just add the sign in
2230 // front for the negative ones
2233 wxString
str(Negate().Format(format
));
2237 wxCHECK_MSG( !format
.empty(), wxEmptyString
,
2238 _T("NULL format in wxTimeSpan::Format") );
2241 str
.Alloc(format
.length());
2243 // Suppose we have wxTimeSpan ts(1 /* hour */, 2 /* min */, 3 /* sec */)
2245 // Then, of course, ts.Format("%H:%M:%S") must return "01:02:03", but the
2246 // question is what should ts.Format("%S") do? The code here returns "3273"
2247 // in this case (i.e. the total number of seconds, not just seconds % 60)
2248 // because, for me, this call means "give me entire time interval in
2249 // seconds" and not "give me the seconds part of the time interval"
2251 // If we agree that it should behave like this, it is clear that the
2252 // interpretation of each format specifier depends on the presence of the
2253 // other format specs in the string: if there was "%H" before "%M", we
2254 // should use GetMinutes() % 60, otherwise just GetMinutes() &c
2256 // we remember the most important unit found so far
2257 TimeSpanPart partBiggest
= Part_MSec
;
2259 for ( wxString::const_iterator pch
= format
.begin(); pch
!= format
.end(); ++pch
)
2263 if ( ch
== _T('%') )
2265 // the start of the format specification of the printf() below
2266 wxString
fmtPrefix(_T('%'));
2271 // the number of digits for the format string, 0 if unused
2272 unsigned digits
= 0;
2274 ch
= *++pch
; // get the format spec char
2278 wxFAIL_MSG( _T("invalid format character") );
2284 // skip the part below switch
2289 if ( partBiggest
< Part_Day
)
2295 partBiggest
= Part_Day
;
2300 partBiggest
= Part_Week
;
2306 if ( partBiggest
< Part_Hour
)
2312 partBiggest
= Part_Hour
;
2319 n
= GetMilliseconds().ToLong();
2320 if ( partBiggest
< Part_MSec
)
2324 //else: no need to reset partBiggest to Part_MSec, it is
2325 // the least significant one anyhow
2332 if ( partBiggest
< Part_Min
)
2338 partBiggest
= Part_Min
;
2345 n
= GetSeconds().ToLong();
2346 if ( partBiggest
< Part_Sec
)
2352 partBiggest
= Part_Sec
;
2361 fmtPrefix
<< _T("0") << digits
;
2364 str
+= wxString::Format(fmtPrefix
+ _T("ld"), n
);
2368 // normal character, just copy
2376 #endif // wxUSE_DATETIME