diff --git a/docs/changes.txt b/docs/changes.txt index 914e6c9692..c8ded70912 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -15,6 +15,7 @@ wxBase: - wxCopyFile() respects the file permissions (Roland Scholz) - wxFTP::GetFileSize() added (Søren Erland Vestø) - wxDateTime::IsSameDate() bug fixed +- wxTimeSpan::Format() now behaves more as expected, see docs All (GUI): diff --git a/docs/latex/wx/datetime.tex b/docs/latex/wx/datetime.tex index ed7d98cc68..c2d02f6808 100644 --- a/docs/latex/wx/datetime.tex +++ b/docs/latex/wx/datetime.tex @@ -145,7 +145,7 @@ values as parameter: }; \end{verbatim} -Differnet parst of the world use different conventions for the week start. +Different parst of the world use different conventions for the week start. In some countries, the week starts on Sunday, while in others - on Monday. The ISO standard doesn't address this issue, so we support both conventions in the functions whose result depends on it (\helpref{GetWeekOfYear}{wxdatetimegetweekofyear} and @@ -174,8 +174,8 @@ No base class \wxheading{See also} \helpref{Date classes overview}{wxdatetimeoverview},\rtfsp -wxTimeSpan,\rtfsp -wxDateSpan,\rtfsp +\helpref{wxTimeSpan}{wxtimespan},\rtfsp +\helpref{wxDateSpan}{wxdatespan},\rtfsp \helpref{wxCalendarCtrl}{wxcalendarctrl} \latexignore{\rtfignore{\wxheading{Function groups}}} diff --git a/docs/latex/wx/timespan.tex b/docs/latex/wx/timespan.tex index b5909c76b6..1a884f1bd4 100644 --- a/docs/latex/wx/timespan.tex +++ b/docs/latex/wx/timespan.tex @@ -11,5 +11,76 @@ \section{\class{wxTimeSpan}}\label{wxtimespan} -TODO +wxTimeSpan class represents a time interval. + +\wxheading{Derived from} + +No base class + +\wxheading{Include files} + + + +\wxheading{See also} + +\helpref{Date classes overview}{wxdatetimeoverview},\rtfsp +\helpref{wxDateTime}{wxdatetime} + +\latexignore{\rtfignore{\wxheading{Function groups}}} + +\membersection{Static functions} + +\membersection{Constructors} + +\helpref{wxTimeSpan()}{wxtimespandef} +\helpref{wxTimeSpan(hours, min, sec, msec)}{wxtimespan} + +\membersection{Accessors} + +\membersection{Operations} + +\membersection{Tests} + +\membersection{Formatting time spans} + +\helpref{Format}{wxtimespanformat} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Start of member function part % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\helponly{\insertatlevel{2}{ + \wxheading{Members} +}} + +\membersection{wxTimeSpan::Format}\label{wxtimespanformat} + +\func{wxString}{Format}{\param{const wxChar * }{format = "\%H:\%M:\%S"}} + +Returns the string containing the formatted representation of the time span. +The following format specifiers are allowed after \%: + +\twocolwidtha{5cm}% +\begin{twocollist}\itemsep=0pt +\twocolitem{H}{number of {\bf H}ours} +\twocolitem{M}{number of {\bf M}inutes} +\twocolitem{S}{number of {\bf S}econds} +\twocolitem{l}{number of mi{\bf l}liseconds} +\twocolitem{D}{number of {\bf D}ays} +\twocolitem{E}{number of w{\bf E}eks} +\twocolitem{\%}{the percent character} +\end{twocollist} + +Note that, for example, the number of hours in the description above is not +well defined: it can be either the total number of hours (for example, for a +time span of $50$ hours this would be $50$) or just the hour part of the time +span, which would be $2$ in this case as $50$ hours is equal to $2$ days and +$2$ hours. + +wxTimeSpan resolves this ambiguity in the following way: if there had been, +indeed, the {\tt \%D} format specified preceding the {\tt \%H}, then it is +interpreted as $2$. Otherwise, it is $50$. + +The same applies to all other format specifiers: if they follow a specifier of +larger unit, only the rest part is taken, otherwise the full value is used. diff --git a/samples/console/console.cpp b/samples/console/console.cpp index 4caca091af..e25ccf588d 100644 --- a/samples/console/console.cpp +++ b/samples/console/console.cpp @@ -37,7 +37,7 @@ //#define TEST_ARRAYS //#define TEST_CMDLINE -//#define TEST_DATETIME +#define TEST_DATETIME //#define TEST_DIR //#define TEST_DLLLOADER //#define TEST_ENVIRON @@ -3383,6 +3383,32 @@ static void TestTimeZoneBug() puts(""); } +static void TestTimeSpanFormat() +{ + puts("\n*** wxTimeSpan tests ***"); + + static const char *formats[] = + { + _T("(default) %H:%M:%S"), + _T("%E weeks and %D days"), + _T("%l milliseconds"), + _T("(with ms) %H:%M:%S:%l"), + _T("100%% of minutes is %M"), // test "%%" + _T("%D days and %H hours"), + }; + + wxTimeSpan ts1(1, 2, 3, 4), + ts2(111, 222, 333); + for ( size_t n = 0; n < WXSIZEOF(formats); n++ ) + { + printf("ts1 = %s\tts2 = %s\n", + ts1.Format(formats[n]).c_str(), + ts2.Format(formats[n]).c_str()); + } + + puts(""); +} + #if 0 // test compatibility with the old wxDate/wxTime classes @@ -4402,7 +4428,7 @@ int main(int argc, char **argv) #endif // TEST_TIMER #ifdef TEST_DATETIME - if ( 1 ) + if ( 0 ) { TestTimeSet(); TestTimeStatic(); @@ -4421,6 +4447,7 @@ int main(int argc, char **argv) TestTimeZoneBug(); } + TestTimeSpanFormat(); if ( 0 ) TestDateTimeInteractive(); #endif // TEST_DATETIME diff --git a/src/common/datetime.cpp b/src/common/datetime.cpp index 6dab762700..5198a3773d 100644 --- a/src/common/datetime.cpp +++ b/src/common/datetime.cpp @@ -152,10 +152,16 @@ IMPLEMENT_DYNAMIC_CLASS(wxDateTimeHolidaysModule, wxModule) // some trivial ones static const int MONTHS_IN_YEAR = 12; -static const int SECONDS_IN_MINUTE = 60; +static const int SEC_PER_MIN = 60; + +static const int MIN_PER_HOUR = 60; + +static const int HOURS_PER_DAY = 24; static const long SECONDS_PER_DAY = 86400l; +static const int DAYS_PER_WEEK = 7; + static const long MILLISECONDS_PER_DAY = 86400000l; // this is the integral part of JDN of the midnight of Jan 1, 1970 @@ -3454,13 +3460,41 @@ wxString wxTimeSpan::Format(const wxChar *format) const wxString str; str.Alloc(wxStrlen(format)); + // Suppose we have wxTimeSpan ts(1 /* hour */, 2 /* min */, 3 /* sec */) + // + // Then, of course, ts.Format("%H:%M:%S") must return "01:02:03", but the + // question is what should ts.Format("%S") do? The code here returns "3273" + // in this case (i.e. the total number of seconds, not just seconds % 60) + // because, for me, this call means "give me entire time interval in + // seconds" and not "give me the seconds part of the time interval" + // + // If we agree that it should behave like this, it is clear that the + // interpretation of each format specifier depends on the presence of the + // other format specs in the string: if there was "%H" before "%M", we + // should use GetMinutes() % 60, otherwise just GetMinutes() &c + + // we remember the most important unit found so far + enum TimeSpanPart + { + Part_Week, + Part_Day, + Part_Hour, + Part_Min, + Part_Sec, + Part_MSec + } partBiggest = Part_MSec; + for ( const wxChar *pch = format; *pch; pch++ ) { wxChar ch = *pch; if ( ch == _T('%') ) { - wxString tmp; + // the start of the format specification of the printf() below + wxString fmtPrefix = _T('%'); + + // the number + long n; ch = *++pch; // get the format spec char switch ( ch ) @@ -3470,44 +3504,90 @@ wxString wxTimeSpan::Format(const wxChar *format) const // fall through case _T('%'): - // will get to str << ch below - break; + str += ch; + + // skip the part below switch + continue; case _T('D'): - tmp.Printf(_T("%d"), GetDays()); + n = GetDays(); + if ( partBiggest < Part_Day ) + { + n %= DAYS_PER_WEEK; + } + else + { + partBiggest = Part_Day; + } break; case _T('E'): - tmp.Printf(_T("%d"), GetWeeks()); + partBiggest = Part_Week; + n = GetWeeks(); break; case _T('H'): - tmp.Printf(_T("%02d"), GetHours()); + n = GetHours(); + if ( partBiggest < Part_Hour ) + { + n %= HOURS_PER_DAY; + } + else + { + partBiggest = Part_Hour; + } + + fmtPrefix += _T("02"); break; case _T('l'): - tmp.Printf(_T("%03ld"), GetMilliseconds().ToLong()); + n = GetMilliseconds().ToLong(); + if ( partBiggest < Part_MSec ) + { + n %= 1000; + } + //else: no need to reset partBiggest to Part_MSec, it is + // the least significant one anyhow + + fmtPrefix += _T("03"); break; case _T('M'): - tmp.Printf(_T("%02d"), GetMinutes()); + n = GetMinutes(); + if ( partBiggest < Part_Min ) + { + n %= MIN_PER_HOUR; + } + else + { + partBiggest = Part_Min; + } + + fmtPrefix += _T("02"); break; case _T('S'): - tmp.Printf(_T("%02ld"), GetSeconds().ToLong()); + n = GetSeconds().ToLong(); + if ( partBiggest < Part_Sec ) + { + n %= SEC_PER_MIN; + } + else + { + partBiggest = Part_Sec; + } + + fmtPrefix += _T("02"); break; } - if ( !!tmp ) - { - str += tmp; - - // skip str += ch below - continue; - } + str += wxString::Format(fmtPrefix + _T("ld"), n); + } + else + { + // normal character, just copy + str += ch; } - - str += ch; } return str;