From: Vadim Zeitlin <vadim@wxwidgets.org> Date: Wed, 1 Dec 1999 22:15:53 +0000 (+0000) Subject: 1. more wxDateTime work X-Git-Url: https://git.saurik.com/wxWidgets.git/commitdiff_plain/fcc3d7cbadcd59d6a2cafafc32624e8a22af0e56 1. more wxDateTime work 2. some thread corrections (not fixing the deadlock with Delete() :-( ) git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@4791 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- diff --git a/include/wx/datetime.h b/include/wx/datetime.h index ffc3bf6c13..6b13723bd3 100644 --- a/include/wx/datetime.h +++ b/include/wx/datetime.h @@ -114,10 +114,45 @@ public: // Note that GMT12 and GMT_12 are not the same: there is a difference // of exactly one day between them - // Universal Coordinated Time + // some symbolic names for TZ + + // Europe + WET = GMT0, // Western Europe Time + WEST = GMT1, // Western Europe Summer Time + CET = GMT1, // Central Europe Time + CEST = GMT2, // Central Europe Summer Time + EET = GMT2, // Eastern Europe Time + EEST = GMT3, // Eastern Europe Summer Time + MSK = GMT3, // Moscow Time + MSD = GMT4, // Moscow Summer Time + + // US and Canada + AST = GMT_4, // Atlantic Standard Time + ADT = GMT_3, // Atlantic Daylight Time + EST = GMT_5, // Eastern Standard Time + EDT = GMT_4, // Eastern Daylight Saving Time + CST = GMT_6, // Central Standard Time + CDT = GMT_5, // Central Daylight Saving Time + MST = GMT_7, // Mountain Standard Time + MDT = GMT_6, // Mountain Daylight Saving Time + PST = GMT_8, // Pacific Standard Time + PDT = GMT_7, // Pacific Daylight Saving Time + HST = GMT_10, // Hawaiian Standard Time + AKST = GMT_9, // Alaska Standard Time + AKDT = GMT_8, // Alaska Daylight Saving Time + + // Australia + + A_WST = GMT8, // Western Standard Time + A_CST = GMT12 + 1, // Central Standard Time (+9.5) + A_EST = GMT10, // Eastern Standard Time + A_ESST = GMT11, // Eastern Summer Time + + // TODO add more symbolic timezone names here + + // Universal Coordinated Time = the new and politically correct name + // for GMT UTC = GMT0 - - // TODO add symbolic names for TZ (EST, MET, ...)? }; // the calendar systems we know about: notice that it's valid (for @@ -290,10 +325,15 @@ public: // standard struct tm is limited to the years from 1900 (because // tm_year field is the offset from 1900), so we use our own struct // instead to represent broken down time + // + // NB: this struct should always be kept normalized (i.e. mon should + // be < 12, 1 <= day <= 31 &c), so use AddMonths(), AddDays() + // instead of modifying the member fields directly! struct Tm { - wxDateTime_t sec, min, hour, - mday, mon, year; + wxDateTime_t sec, min, hour, mday; + Month mon; + int year; // default ctor inits the object to an invalid value Tm(); @@ -313,6 +353,12 @@ public: return (WeekDay)wday; } + // add the given number of months to the date keeping it normalized + void AddMonths(wxDateTime_t monDiff); + + // add the given number of months to the date keeping it normalized + void AddDays(wxDateTime_t dayDiff); + private: // compute the weekday from other fields void ComputeWeekDay(); @@ -330,11 +376,11 @@ public: TimeZone(TZ tz); TimeZone(wxDateTime_t offset) { m_offset = offset; } - wxDateTime_t GetOffset() const { return m_offset; } + int GetOffset() const { return m_offset; } private: // offset for this timezone from GMT in minutes - wxDateTime_t m_offset; + int m_offset; }; // static methods @@ -558,18 +604,24 @@ public: // anything else, it should be requested explicitly // ------------------------------------------------------------------------ - // get the time corresponding to this one in UTC/GMT - wxDateTime ToUTC() const; - wxDateTime ToGMT() const { return ToUTC(); } - // transform this object to UTC/GMT wxDateTime& MakeUTC(); wxDateTime& MakeGMT() { return MakeUTC(); } - // generic version: transform time to any given timezone - wxDateTime ToTimezone(const TimeZone& tz); + // get the time corresponding to this one in UTC/GMT + inline wxDateTime ToUTC() const; + wxDateTime ToGMT() const { return ToUTC(); } + + // generic versions of the above + + // transform from local time to any given timezone + inline wxDateTime ToTimezone(const TimeZone& tz) const; wxDateTime& MakeTimezone(const TimeZone& tz); + // transform time from any timezone to the local time + inline wxDateTime ToLocalTime(const TimeZone& tz) const; + wxDateTime& MakeLocalTime(const TimeZone& tz); + // accessors: many of them take the timezone parameter which indicates the // timezone for which to make the calculations and the default value means // to do it for the current timezone of this machine (even if the function @@ -741,7 +793,7 @@ private: // returns TRUE if we fall in range in which we can use standard ANSI C // functions - inline IsInStdRange() const; + inline bool IsInStdRange() const; // the internal representation of the time is the amount of milliseconds // elapsed since the origin which is set by convention to the UNIX/C epoch @@ -761,25 +813,25 @@ public: // constructors // ------------------------------------------------------------------------ + // return the timespan for the given number of seconds + static wxTimeSpan Seconds(int sec) { return wxTimeSpan(0, 0, sec); } + + // return the timespan for the given number of minutes + static wxTimeSpan Minutes(int min) { return wxTimeSpan(0, min, 0 ); } + + // return the timespan for the given number of hours + static wxTimeSpan Hours(int hours) { return wxTimeSpan(hours, 0, 0); } + // default ctor constructs the 0 time span wxTimeSpan() { } // from separate values for each component, date set to 0 (hours are // not restricted to 0..24 range, neither are minutes, seconds or // milliseconds) - wxTimeSpan(int hours, - int minutes = 0, - int seconds = 0, - int milliseconds = 0); - // from separate values for each component with explicit date (none of - // the parameters isn't restricted to any range) - wxTimeSpan(int years, - int months, - int days, - int hours = 0, - int minutes = 0, - int seconds = 0, - int milliseconds = 0); + inline wxTimeSpan(int hours, + int minutes = 0, + int seconds = 0, + int milliseconds = 0); // from internal representation wxTimeSpan(wxLongLong diff) : m_diff(diff) { } @@ -879,11 +931,11 @@ public: // resulting text representation. Notice that only some of format // specifiers valid for wxDateTime are valid for wxTimeSpan: hours, // minutes and seconds make sense, but not "PM/AM" string for example. - wxString Format(const char *format = "%c") const; + wxString Format(const wxChar *format = _T("%c")) const; // preferred date representation for the current locale - wxString FormatDate() const { return Format("%x"); } + wxString FormatDate() const { return Format(_T("%x")); } // preferred time representation for the current locale - wxString FormatTime() const { return Format("%X"); } + wxString FormatTime() const { return Format(_T("%X")); } // implementation // ------------------------------------------------------------------------ diff --git a/include/wx/datetime.inl b/include/wx/datetime.inl index 1ae112d5ad..57555e6355 100644 --- a/include/wx/datetime.inl +++ b/include/wx/datetime.inl @@ -35,7 +35,7 @@ wxDateTime::Country wxDateTime::GetCountry() const unsigned int wxDateTime::TIME_T_FACTOR = 1000; #endif // wxDEFINE_TIME_CONSTANTS -wxDateTime::IsInStdRange() const +bool wxDateTime::IsInStdRange() const { return m_time >= 0l && (m_time / (long)TIME_T_FACTOR) < LONG_MAX; } @@ -237,10 +237,41 @@ wxDateTime& wxDateTime::operator+=(const wxDateSpan& diff) return Add(diff); } +// ---------------------------------------------------------------------------- +// wxDateTime and timezones +// ---------------------------------------------------------------------------- + +wxDateTime wxDateTime::ToUTC() const +{ + return wxDateTime(*this).MakeUTC(); +} + +wxDateTime wxDateTime::ToTimezone(const wxDateTime::TimeZone& tz) const +{ + return wxDateTime(*this).MakeTimezone(tz); +} + +wxDateTime wxDateTime::ToLocalTime(const wxDateTime::TimeZone& tz) const +{ + return wxDateTime(*this).MakeLocalTime(tz); +} + // ---------------------------------------------------------------------------- // wxTimeSpan // ---------------------------------------------------------------------------- +wxTimeSpan::wxTimeSpan(int hours, int minutes, int seconds, int milliseconds) +{ + // assign first to avoid precision loss + m_diff = hours; + m_diff *= 60; + m_diff += minutes; + m_diff *= 60; + m_diff += seconds; + m_diff *= 1000; + m_diff += milliseconds; +} + wxTimeSpan& wxTimeSpan::Add(const wxTimeSpan& diff) { m_diff += diff.GetValue(); @@ -325,4 +356,3 @@ wxDateSpan& wxDateSpan::Neg() return *this; } - diff --git a/include/wx/longlong.h b/include/wx/longlong.h index c9b582b7a3..74114fc9a2 100644 --- a/include/wx/longlong.h +++ b/include/wx/longlong.h @@ -254,8 +254,10 @@ public: // conversion to byte array: returns a pointer to static buffer! void *asArray() const; +#if wxUSE_STD_IOSTREAM // input/output friend ostream& operator<<(ostream&, const wxLongLongNative&); +#endif private: wxLongLong_t m_ll; @@ -356,8 +358,10 @@ public: wxLongLongWx& quotient, wxLongLongWx& remainder) const; +#if wxUSE_STD_IOSTREAM // input/output friend ostream& operator<<(ostream&, const wxLongLongWx&); +#endif // wxUSE_STD_IOSTREAM private: // long is at least 32 bits, so represent our 64bit number as 2 longs diff --git a/samples/console/console.cpp b/samples/console/console.cpp index f83df94ab8..5b15489d9d 100644 --- a/samples/console/console.cpp +++ b/samples/console/console.cpp @@ -32,8 +32,8 @@ //#define TEST_ARRAYS //#define TEST_LOG //#define TEST_STRINGS -//#define TEST_THREADS -#define TEST_TIME +#define TEST_THREADS +//#define TEST_TIME //#define TEST_LONGLONG // ============================================================================ @@ -144,8 +144,8 @@ static void TestTimeStatic() wxDateTime::GetNumberOfDays(month)); // leap year logic - static const nYears = 5; - static const int years[2][nYears] = + static const size_t nYears = 5; + static const size_t years[2][nYears] = { // first line: the years to test { 1990, 1976, 2000, 2030, 1984, }, @@ -179,6 +179,20 @@ static void TestTimeSet() printf("May 29, 1976:\t%s\n", wxDateTime(29, wxDateTime::May, 1976).Format().c_str()); } +// test time zones stuff +static void TestTimeZones() +{ + puts("\n*** wxDateTime timezone test ***"); + + wxDateTime now = wxDateTime::Now(); + + printf("Current GMT time:\t%s\n", now.ToGMT().Format().c_str()); + //printf("Unix epoch (GMT):\t%s\n", wxDateTime((time_t)0).MakeGMT().Format().c_str()); + printf("Current time in Paris:\t%s\n", now.ToTimezone(wxDateTime::CET).Format().c_str()); + printf(" Moscow:\t%s\n", now.ToTimezone(wxDateTime::MSK).Format().c_str()); + printf(" New York:\t%s\n", now.ToTimezone(wxDateTime::EST).Format().c_str()); +} + #endif // TEST_TIME // ---------------------------------------------------------------------------- @@ -223,7 +237,14 @@ wxThread::ExitCode MyJoinableThread::Entry() class MyDetachedThread : public wxThread { public: - MyDetachedThread(size_t n, char ch) { m_n = n; m_ch = ch; Create(); } + MyDetachedThread(size_t n, char ch) + { + m_n = n; + m_ch = ch; + m_cancelled = FALSE; + + Create(); + } // thread execution starts here virtual ExitCode Entry(); @@ -234,6 +255,8 @@ public: private: size_t m_n; // number of characters to write char m_ch; // character to write + + bool m_cancelled; // FALSE if we exit normally }; wxThread::ExitCode MyDetachedThread::Entry() @@ -249,7 +272,11 @@ wxThread::ExitCode MyDetachedThread::Entry() for ( size_t n = 0; n < m_n; n++ ) { if ( TestDestroy() ) + { + m_cancelled = TRUE; + break; + } putchar(m_ch); fflush(stdout); @@ -265,7 +292,7 @@ void MyDetachedThread::OnExit() wxLogTrace("thread", "Thread %ld is in OnExit", GetId()); wxCriticalSectionLocker lock(gs_critsect); - if ( !--gs_counter ) + if ( !--gs_counter && !m_cancelled ) gs_cond.Signal(); } @@ -565,6 +592,7 @@ int main(int argc, char **argv) #ifdef TEST_TIME TestTimeStatic(); TestTimeSet(); + TestTimeZones(); #endif // TEST_TIME wxUninitialize(); diff --git a/src/common/datetime.cpp b/src/common/datetime.cpp index 46f65d001f..901156a569 100644 --- a/src/common/datetime.cpp +++ b/src/common/datetime.cpp @@ -34,6 +34,8 @@ #include "wx/log.h" #endif // WX_PRECOMP +#include "wx/thread.h" + #define wxDEFINE_TIME_CONSTANTS #include "wx/datetime.h" @@ -42,18 +44,59 @@ // constants // ---------------------------------------------------------------------------- +// note that all these constants should be signed or we'd get some big +// surprizes with C integer arithmetics +static const int MONTHS_IN_YEAR = 12; + +static const int SECONDS_IN_MINUTE = 60; + // the number of days in month in Julian/Gregorian calendar: the first line is // for normal years, the second one is for the leap ones -static wxDateTime::wxDateTime_t gs_daysInMonth[2][12] = +static wxDateTime::wxDateTime_t gs_daysInMonth[2][MONTHS_IN_YEAR] = { { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } }; +// ---------------------------------------------------------------------------- +// globals +// ---------------------------------------------------------------------------- + +// a critical section is needed to protect GetTimeZone() static +// variable in MT case +#ifdef wxUSE_THREADS + wxCriticalSection gs_critsectTimezone; +#endif // wxUSE_THREADS + // ---------------------------------------------------------------------------- // private functions // ---------------------------------------------------------------------------- +// get the number of days in the given month of the given year +static inline +wxDateTime::wxDateTime_t GetNumOfDaysInMonth(int year, wxDateTime::Month month) +{ + return gs_daysInMonth[wxDateTime::IsLeapYear(year)][month]; +} + +// ensure that the timezone variable is set by calling localtime +static int GetTimeZone() +{ + // set to TRUE when the timezone is set + static bool s_timezoneSet = FALSE; + + wxCRIT_SECT_LOCKER(lock, gs_critsectTimezone); + + if ( !s_timezoneSet ) + { + (void)localtime(0); + + s_timezoneSet = TRUE; + } + + return (int)timezone; +} + // this function is a wrapper around strftime(3) static wxString CallStrftime(const wxChar *format, const tm* tm) { @@ -119,7 +162,7 @@ wxDateTime::Tm::Tm(const struct tm& tm) min = tm.tm_min; hour = tm.tm_hour; mday = tm.tm_mday; - mon = tm.tm_mon; + mon = (wxDateTime::Month)tm.tm_mon; year = 1900 + tm.tm_year; wday = tm.tm_wday; yday = tm.tm_yday; @@ -128,8 +171,8 @@ wxDateTime::Tm::Tm(const struct tm& tm) bool wxDateTime::Tm::IsValid() const { // we allow for the leap seconds, although we don't use them (yet) - return (year != wxDateTime::Inv_Year) && (mon < 12) && - (mday < gs_daysInMonth[IsLeapYear(year)][mon]) && + return (year != wxDateTime::Inv_Year) && (mon != wxDateTime::Inv_Month) && + (mday < GetNumOfDaysInMonth(year, mon)) && (hour < 24) && (min < 60) && (sec < 62); } @@ -138,6 +181,101 @@ void wxDateTime::Tm::ComputeWeekDay() wxFAIL_MSG(_T("TODO")); } +void wxDateTime::Tm::AddMonths(wxDateTime::wxDateTime_t monDiff) +{ + // normalize the months field + while ( monDiff < -mon ) + { + year--; + + monDiff += MONTHS_IN_YEAR; + } + + while ( monDiff + mon > MONTHS_IN_YEAR ) + { + year++; + } + + mon = (wxDateTime::Month)(mon + monDiff); + + wxASSERT_MSG( mon >= 0 && mon < 12, _T("logic error") ); +} + +void wxDateTime::Tm::AddDays(wxDateTime::wxDateTime_t dayDiff) +{ + // normalize the days field + mday += dayDiff; + while ( mday < 1 ) + { + AddMonths(-1); + + mday += GetNumOfDaysInMonth(year, mon); + } + + while ( mday > GetNumOfDaysInMonth(year, mon) ) + { + mday -= GetNumOfDaysInMonth(year, mon); + + AddMonths(1); + } + + wxASSERT_MSG( mday > 0 && mday <= GetNumOfDaysInMonth(year, mon), + _T("logic error") ); +} + +// ---------------------------------------------------------------------------- +// class TimeZone +// ---------------------------------------------------------------------------- + +wxDateTime::TimeZone::TimeZone(wxDateTime::TZ tz) +{ + switch ( tz ) + { + case wxDateTime::Local: + // leave offset to be 0 + break; + + case wxDateTime::GMT_12: + case wxDateTime::GMT_11: + case wxDateTime::GMT_10: + case wxDateTime::GMT_9: + case wxDateTime::GMT_8: + case wxDateTime::GMT_7: + case wxDateTime::GMT_6: + case wxDateTime::GMT_5: + case wxDateTime::GMT_4: + case wxDateTime::GMT_3: + case wxDateTime::GMT_2: + case wxDateTime::GMT_1: + m_offset = -60*(wxDateTime::GMT0 - tz); + break; + + case wxDateTime::GMT0: + case wxDateTime::GMT1: + case wxDateTime::GMT2: + case wxDateTime::GMT3: + case wxDateTime::GMT4: + case wxDateTime::GMT5: + case wxDateTime::GMT6: + case wxDateTime::GMT7: + case wxDateTime::GMT8: + case wxDateTime::GMT9: + case wxDateTime::GMT10: + case wxDateTime::GMT11: + case wxDateTime::GMT12: + m_offset = 60*(tz - wxDateTime::GMT0); + break; + + case wxDateTime::A_CST: + // Central Standard Time in use in Australia = UTC + 9.5 + m_offset = 9*60 + 30; + break; + + default: + wxFAIL_MSG( _T("unknown time zone") ); + } +} + // ---------------------------------------------------------------------------- // static functions // ---------------------------------------------------------------------------- @@ -169,6 +307,12 @@ bool wxDateTime::IsLeapYear(int year, wxDateTime::Calendar cal) } } +/* static */ +int wxDateTime::GetCentury(int year) +{ + return year > 0 ? year / 100 : year / 100 - 1; +} + /* static */ void wxDateTime::SetCountry(wxDateTime::Country country) { @@ -252,7 +396,7 @@ wxDateTime::wxDateTime_t wxDateTime::GetNumberOfDays(wxDateTime::Month month, int year, wxDateTime::Calendar cal) { - wxCHECK_MSG( month < 12, 0, _T("invalid month") ); + wxCHECK_MSG( month < MONTHS_IN_YEAR, 0, _T("invalid month") ); if ( cal == Gregorian || cal == Julian ) { @@ -262,7 +406,7 @@ wxDateTime::wxDateTime_t wxDateTime::GetNumberOfDays(wxDateTime::Month month, year = GetCurrentYear(); } - return gs_daysInMonth[IsLeapYear(year)][month]; + return GetNumOfDaysInMonth(year, month); } else { @@ -391,8 +535,7 @@ wxDateTime& wxDateTime::Set(wxDateTime_t day, else { // do time calculations ourselves: we want to calculate the number of - // milliseconds between the given date and the epoch (necessarily - // negative) + // milliseconds between the given date and the epoch wxFAIL_MSG(_T("TODO")); } @@ -512,8 +655,8 @@ wxDateTime& wxDateTime::Add(const wxDateSpan& diff) Tm tm(GetTm()); tm.year += diff.GetYears(); - tm.mon += diff.GetMonths(); - tm.mday += diff.GetTotalDays(); + tm.AddMonths(diff.GetMonths()); + tm.AddDays(diff.GetTotalDays()); Set(tm); @@ -530,7 +673,7 @@ wxDateTime& wxDateTime::SetToLastMonthDay(Month month, // take the current month/year if none specified ReplaceDefaultYearMonthWithCurrent(&year, &month); - return Set(gs_daysInMonth[IsLeapYear(year)][month], month, year); + return Set(GetNumOfDaysInMonth(year, month), month, year); } bool wxDateTime::SetToWeekDay(WeekDay weekday, @@ -601,6 +744,27 @@ bool wxDateTime::SetToWeekDay(WeekDay weekday, } } +// ---------------------------------------------------------------------------- +// timezone stuff +// ---------------------------------------------------------------------------- + +wxDateTime& wxDateTime::MakeUTC() +{ + return Add(wxTimeSpan::Seconds(GetTimeZone())); +} + +wxDateTime& wxDateTime::MakeTimezone(const TimeZone& tz) +{ + int minDiff = GetTimeZone() / SECONDS_IN_MINUTE + tz.GetOffset(); + return Add(wxTimeSpan::Minutes(minDiff)); +} + +wxDateTime& wxDateTime::MakeLocalTime(const TimeZone& tz) +{ + int minDiff = GetTimeZone() / SECONDS_IN_MINUTE + tz.GetOffset(); + return Substract(wxTimeSpan::Minutes(minDiff)); +} + // ---------------------------------------------------------------------------- // wxDateTime to/from text representations // ---------------------------------------------------------------------------- @@ -625,3 +789,16 @@ wxString wxDateTime::Format(const wxChar *format) const return _T(""); } } + +// ============================================================================ +// wxTimeSpan +// ============================================================================ + +wxString wxTimeSpan::Format(const wxChar *format) const +{ + wxFAIL_MSG( _T("TODO") ); + + wxString str; + + return str; +} diff --git a/src/common/longlong.cpp b/src/common/longlong.cpp index fd0c992c3f..29a4f867b9 100644 --- a/src/common/longlong.cpp +++ b/src/common/longlong.cpp @@ -57,6 +57,8 @@ void *wxLongLongNative::asArray(void) const return temp; } +#if wxUSE_STD_IOSTREAM + // input/output ostream& operator<< (ostream& o, const wxLongLongNative& ll) { @@ -74,6 +76,8 @@ ostream& operator<< (ostream& o, const wxLongLongNative& ll) return o << result; } +#endif // wxUSE_STD_IOSTREAM + #endif // wxUSE_LONGLONG_NATIVE #if wxUSE_LONGLONG_WX @@ -449,8 +453,9 @@ void *wxLongLongWx::asArray(void) const return temp; } -// input/output +#if wxUSE_STD_IOSTREAM +// input/output ostream& operator<< (ostream& o, const wxLongLongWx& ll) { char result[65]; @@ -467,7 +472,8 @@ ostream& operator<< (ostream& o, const wxLongLongWx& ll) return o << result; } -#endif - // wxUSE_LONGLONG_NATIVE +#endif // wxUSE_STD_IOSTREAM + +#endif // wxUSE_LONGLONG_NATIVE #endif // wxUSE_LONGLONG diff --git a/src/unix/threadpsx.cpp b/src/unix/threadpsx.cpp index 1ab453633f..197d935d3d 100644 --- a/src/unix/threadpsx.cpp +++ b/src/unix/threadpsx.cpp @@ -24,9 +24,7 @@ #pragma implementation "thread.h" #endif -// With simple makefiles, we must ignore the file body if not using -// threads. -#include "wx/setup.h" +#include "wx/defs.h" #if wxUSE_THREADS @@ -586,6 +584,9 @@ void wxThreadInternal::Wait() if ( wxThread::IsMain() ) wxMutexGuiLeave(); + wxLogTrace(TRACE_THREADS, _T("Starting to wait for thread %ld to exit."), + GetId()); + // wait until the thread terminates (we're blocking in _another_ thread, // of course) m_condEnd.Wait(); @@ -622,6 +623,9 @@ void wxThreadInternal::SignalExit() // wake up all the threads waiting for our termination - if there are any if ( m_shouldBroadcast ) { + wxLogTrace(TRACE_THREADS, _T("Thread %ld signals end condition."), + GetId()); + m_condEnd.Broadcast(); } } @@ -1243,8 +1247,9 @@ static void ScheduleThreadForDeletion() gs_nThreadsBeingDeleted++; - wxLogTrace(TRACE_THREADS, _T("%u threads waiting to be deleted"), - gs_nThreadsBeingDeleted); + wxLogTrace(TRACE_THREADS, _T("%u thread%s waiting to be deleted"), + gs_nThreadsBeingDeleted, + gs_nThreadsBeingDeleted == 1 ? "" : "s"); } static void DeleteThread(wxThread *This)