1 /////////////////////////////////////////////////////////////////////////////
3 // Purpose: implementation of time/date related classes
4 // Author: Vadim Zeitlin
8 // Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // Licence: wxWindows license
10 /////////////////////////////////////////////////////////////////////////////
12 // ============================================================================
14 // ============================================================================
16 // ----------------------------------------------------------------------------
18 // ----------------------------------------------------------------------------
21 #pragma implementation "datetime.h"
24 // For compilers that support precompilation, includes "wx.h".
25 #include "wx/wxprec.h"
32 #include "wx/string.h"
37 #include "wx/thread.h"
39 #define wxDEFINE_TIME_CONSTANTS
41 #include "wx/datetime.h"
43 // ----------------------------------------------------------------------------
45 // ----------------------------------------------------------------------------
48 static const int MONTHS_IN_YEAR
= 12;
50 static const int SECONDS_IN_MINUTE
= 60;
52 static const long SECONDS_PER_DAY
= 86400l;
54 static const long MILLISECONDS_PER_DAY
= 86400000l;
56 // this is the integral part of JDN of the midnight of Jan 1, 1970
57 // (i.e. JDN(Jan 1, 1970) = 2440587.5)
58 static const int EPOCH_JDN
= 2440587;
60 // ----------------------------------------------------------------------------
62 // ----------------------------------------------------------------------------
64 // a critical section is needed to protect GetTimeZone() static
65 // variable in MT case
67 wxCriticalSection gs_critsectTimezone
;
68 #endif // wxUSE_THREADS
70 // ----------------------------------------------------------------------------
72 // ----------------------------------------------------------------------------
74 // get the number of days in the given month of the given year
76 wxDateTime::wxDateTime_t
GetNumOfDaysInMonth(int year
, wxDateTime::Month month
)
78 // the number of days in month in Julian/Gregorian calendar: the first line
79 // is for normal years, the second one is for the leap ones
80 static wxDateTime::wxDateTime_t daysInMonth
[2][MONTHS_IN_YEAR
] =
82 { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
83 { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
86 return daysInMonth
[wxDateTime::IsLeapYear(year
)][month
];
89 // ensure that the timezone variable is set by calling localtime
90 static int GetTimeZone()
92 // set to TRUE when the timezone is set
93 static bool s_timezoneSet
= FALSE
;
95 wxCRIT_SECT_LOCKER(lock
, gs_critsectTimezone
);
99 // just call localtime() instead of figurin out whether this system
100 // supports tzset(), _tzset() or something else
104 s_timezoneSet
= TRUE
;
107 return (int)timezone
;
110 // return the integral part of the JDN for the midnight of the given date (to
111 // get the real JDN you need to add 0.5, this is, in fact, JDN of the noon of
113 static long GetTruncatedJDN(wxDateTime::wxDateTime_t day
,
114 wxDateTime::Month mon
,
117 // CREDIT: the algorithm was taken from Peter Baum's home page
119 // the algorithm assumes Jan == 1
122 // we want the leap day (Feb 29) be at the end of the year, so we count
123 // March as the first month
124 if ( month
< wxDateTime::Mar
+ 1 )
126 month
+= MONTHS_IN_YEAR
;
130 // this table contains the number of the days before the 1st of the each
131 // month (in a non leap year) with the third value corresponding to March
132 // (and the last one to February)
133 static const int monthOffsets
[14] =
135 0, 31, 61, 92, 122, 153, 184, 214, 245, 275, 306
138 // and now add contributions of all terms together to get the result (you'd
139 // better see the Web page for the description if you want to understand
140 // why it works (if it does :-))
142 // linear approximation for months
143 monthOffsets
[month
- (wxDateTime::Mar
+ 1)] +
144 // the year contribution
145 365*year
+ year
/4 - year
/100 + year
/400 +
146 // 1721119.5 is the JDN of the midnight of Mar 1, year 0
150 // this function is a wrapper around strftime(3)
151 static wxString
CallStrftime(const wxChar
*format
, const tm
* tm
)
154 if ( !wxStrftime(buf
, WXSIZEOF(buf
), format
, tm
) )
156 // is ti really possible that 1024 is too short?
157 wxFAIL_MSG(_T("strftime() failed"));
160 return wxString(buf
);
163 // if year and/or month have invalid values, replace them with the current ones
164 static void ReplaceDefaultYearMonthWithCurrent(int *year
,
165 wxDateTime::Month
*month
)
167 struct tm
*tmNow
= NULL
;
169 if ( *year
== wxDateTime::Inv_Year
)
171 tmNow
= wxDateTime::GetTmNow();
173 *year
= 1900 + tmNow
->tm_year
;
176 if ( *month
== wxDateTime::Inv_Month
)
179 tmNow
= wxDateTime::GetTmNow();
181 *month
= (wxDateTime::Month
)tmNow
->tm_mon
;
185 // ============================================================================
186 // implementation of wxDateTime
187 // ============================================================================
189 // ----------------------------------------------------------------------------
191 // ----------------------------------------------------------------------------
193 wxDateTime::Country
wxDateTime::ms_country
= wxDateTime::Country_Unknown
;
194 wxDateTime
wxDateTime::ms_InvDateTime
;
196 // ----------------------------------------------------------------------------
198 // ----------------------------------------------------------------------------
202 year
= (wxDateTime_t
)wxDateTime::Inv_Year
;
203 mon
= wxDateTime::Inv_Month
;
205 hour
= min
= sec
= msec
= 0;
206 wday
= wxDateTime::Inv_WeekDay
;
209 wxDateTime::Tm::Tm(const struct tm
& tm
)
216 mon
= (wxDateTime::Month
)tm
.tm_mon
;
217 year
= 1900 + tm
.tm_year
;
222 bool wxDateTime::Tm::IsValid() const
224 // we allow for the leap seconds, although we don't use them (yet)
225 return (year
!= wxDateTime::Inv_Year
) && (mon
!= wxDateTime::Inv_Month
) &&
226 (mday
< GetNumOfDaysInMonth(year
, mon
)) &&
227 (hour
< 24) && (min
< 60) && (sec
< 62) && (msec
< 1000);
230 void wxDateTime::Tm::ComputeWeekDay()
232 wxFAIL_MSG(_T("TODO"));
235 void wxDateTime::Tm::AddMonths(int monDiff
)
237 // normalize the months field
238 while ( monDiff
< -mon
)
242 monDiff
+= MONTHS_IN_YEAR
;
245 while ( monDiff
+ mon
> MONTHS_IN_YEAR
)
250 mon
= (wxDateTime::Month
)(mon
+ monDiff
);
252 wxASSERT_MSG( mon
>= 0 && mon
< MONTHS_IN_YEAR
, _T("logic error") );
255 void wxDateTime::Tm::AddDays(int dayDiff
)
257 // normalize the days field
263 mday
+= GetNumOfDaysInMonth(year
, mon
);
266 while ( mday
> GetNumOfDaysInMonth(year
, mon
) )
268 mday
-= GetNumOfDaysInMonth(year
, mon
);
273 wxASSERT_MSG( mday
> 0 && mday
<= GetNumOfDaysInMonth(year
, mon
),
277 // ----------------------------------------------------------------------------
279 // ----------------------------------------------------------------------------
281 wxDateTime::TimeZone::TimeZone(wxDateTime::TZ tz
)
285 case wxDateTime::Local
:
286 // leave offset to be 0
289 case wxDateTime::GMT_12
:
290 case wxDateTime::GMT_11
:
291 case wxDateTime::GMT_10
:
292 case wxDateTime::GMT_9
:
293 case wxDateTime::GMT_8
:
294 case wxDateTime::GMT_7
:
295 case wxDateTime::GMT_6
:
296 case wxDateTime::GMT_5
:
297 case wxDateTime::GMT_4
:
298 case wxDateTime::GMT_3
:
299 case wxDateTime::GMT_2
:
300 case wxDateTime::GMT_1
:
301 m_offset
= -60*(wxDateTime::GMT0
- tz
);
304 case wxDateTime::GMT0
:
305 case wxDateTime::GMT1
:
306 case wxDateTime::GMT2
:
307 case wxDateTime::GMT3
:
308 case wxDateTime::GMT4
:
309 case wxDateTime::GMT5
:
310 case wxDateTime::GMT6
:
311 case wxDateTime::GMT7
:
312 case wxDateTime::GMT8
:
313 case wxDateTime::GMT9
:
314 case wxDateTime::GMT10
:
315 case wxDateTime::GMT11
:
316 case wxDateTime::GMT12
:
317 m_offset
= 60*(tz
- wxDateTime::GMT0
);
320 case wxDateTime::A_CST
:
321 // Central Standard Time in use in Australia = UTC + 9.5
322 m_offset
= 9*60 + 30;
326 wxFAIL_MSG( _T("unknown time zone") );
330 // ----------------------------------------------------------------------------
332 // ----------------------------------------------------------------------------
335 bool wxDateTime::IsLeapYear(int year
, wxDateTime::Calendar cal
)
337 if ( year
== Inv_Year
)
338 year
= GetCurrentYear();
340 if ( cal
== Gregorian
)
342 // in Gregorian calendar leap years are those divisible by 4 except
343 // those divisible by 100 unless they're also divisible by 400
344 // (in some countries, like Russia and Greece, additional corrections
345 // exist, but they won't manifest themselves until 2700)
346 return (year
% 4 == 0) && ((year
% 100 != 0) || (year
% 400 == 0));
348 else if ( cal
== Julian
)
350 // in Julian calendar the rule is simpler
351 return year
% 4 == 0;
355 wxFAIL_MSG(_T("unknown calendar"));
362 int wxDateTime::GetCentury(int year
)
364 return year
> 0 ? year
/ 100 : year
/ 100 - 1;
368 void wxDateTime::SetCountry(wxDateTime::Country country
)
370 ms_country
= country
;
374 int wxDateTime::ConvertYearToBC(int year
)
377 return year
> 0 ? year
: year
- 1;
381 int wxDateTime::GetCurrentYear(wxDateTime::Calendar cal
)
386 return Now().GetYear();
389 wxFAIL_MSG(_T("TODO"));
393 wxFAIL_MSG(_T("unsupported calendar"));
401 wxDateTime::Month
wxDateTime::GetCurrentMonth(wxDateTime::Calendar cal
)
406 return Now().GetMonth();
410 wxFAIL_MSG(_T("TODO"));
414 wxFAIL_MSG(_T("unsupported calendar"));
422 wxDateTime::wxDateTime_t
wxDateTime::GetNumberOfDays(int year
, Calendar cal
)
424 if ( year
== Inv_Year
)
426 // take the current year if none given
427 year
= GetCurrentYear();
434 return IsLeapYear(year
) ? 366 : 365;
438 wxFAIL_MSG(_T("unsupported calendar"));
446 wxDateTime::wxDateTime_t
wxDateTime::GetNumberOfDays(wxDateTime::Month month
,
448 wxDateTime::Calendar cal
)
450 wxCHECK_MSG( month
< MONTHS_IN_YEAR
, 0, _T("invalid month") );
452 if ( cal
== Gregorian
|| cal
== Julian
)
454 if ( year
== Inv_Year
)
456 // take the current year if none given
457 year
= GetCurrentYear();
460 return GetNumOfDaysInMonth(year
, month
);
464 wxFAIL_MSG(_T("unsupported calendar"));
471 wxString
wxDateTime::GetMonthName(wxDateTime::Month month
, bool abbr
)
473 wxCHECK_MSG( month
!= Inv_Month
, _T(""), _T("invalid month") );
475 tm tm
= { 0, 0, 0, 1, month
, 76 }; // any year will do
477 return CallStrftime(abbr
? _T("%b") : _T("%B"), &tm
);
481 wxString
wxDateTime::GetWeekDayName(wxDateTime::WeekDay wday
, bool abbr
)
483 wxCHECK_MSG( wday
!= Inv_WeekDay
, _T(""), _T("invalid weekday") );
485 // take some arbitrary Sunday
486 tm tm
= { 0, 0, 0, 28, Nov
, 99 };
488 // and offset it by the number of days needed to get
491 return CallStrftime(abbr
? _T("%a") : _T("%A"), &tm
);
494 // ----------------------------------------------------------------------------
495 // constructors and assignment operators
496 // ----------------------------------------------------------------------------
498 wxDateTime
& wxDateTime::Set(const struct tm
& tm1
)
500 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
503 time_t timet
= mktime(&tm2
);
504 if ( timet
== (time_t)(-1) )
506 wxFAIL_MSG(_T("Invalid time"));
508 return ms_InvDateTime
;
516 wxDateTime
& wxDateTime::Set(wxDateTime_t hour
,
519 wxDateTime_t millisec
)
521 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
523 // we allow seconds to be 61 to account for the leap seconds, even if we
524 // don't use them really
525 wxCHECK_MSG( hour
< 24 && second
< 62 && minute
< 60 && millisec
< 1000,
527 _T("Invalid time in wxDateTime::Set()") );
529 // get the current date from system
530 time_t timet
= GetTimeNow();
531 struct tm
*tm
= localtime(&timet
);
540 // and finally adjust milliseconds
541 return SetMillisecond(millisec
);
544 wxDateTime
& wxDateTime::Set(wxDateTime_t day
,
550 wxDateTime_t millisec
)
552 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
554 wxCHECK_MSG( hour
< 24 && second
< 62 && minute
< 60 && millisec
< 1000,
556 _T("Invalid time in wxDateTime::Set()") );
558 ReplaceDefaultYearMonthWithCurrent(&year
, &month
);
560 wxCHECK_MSG( day
<= GetNumberOfDays(month
, year
), ms_InvDateTime
,
561 _T("Invalid date in wxDateTime::Set()") );
563 // the range of time_t type (inclusive)
564 static const int yearMinInRange
= 1970;
565 static const int yearMaxInRange
= 2037;
567 // test only the year instead of testing for the exact end of the Unix
568 // time_t range - it doesn't bring anything to do more precise checks
569 if ( year
>= yearMinInRange
&& year
<= yearMaxInRange
)
571 // use the standard library version if the date is in range - this is
572 // probably more efficient than our code
574 tm
.tm_year
= year
- 1900;
583 // and finally adjust milliseconds
584 return SetMillisecond(millisec
);
588 // do time calculations ourselves: we want to calculate the number of
589 // milliseconds between the given date and the epoch
591 // get the JDN for the midnight of this day
592 m_time
= GetTruncatedJDN(day
, month
, year
);
594 m_time
*= SECONDS_PER_DAY
* TIME_T_FACTOR
;
596 Add(wxTimeSpan(hour
, minute
, second
, millisec
));
602 wxDateTime
& wxDateTime::Set(double jdn
)
604 m_time
= (jdn
- 0.5 - EPOCH_JDN
) * TIME_T_FACTOR
;
609 // ----------------------------------------------------------------------------
610 // time_t <-> broken down time conversions
611 // ----------------------------------------------------------------------------
613 wxDateTime::Tm
wxDateTime::GetTm() const
615 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
617 time_t time
= GetTicks();
618 if ( time
!= (time_t)-1 )
620 // use C RTL functions
621 tm
*tm
= localtime(&time
);
623 // should never happen
624 wxCHECK_MSG( tm
, Tm(), _T("localtime() failed") );
630 // CREDIT: the algorithm was taken from Peter Baum's home page
632 // calculate the Gregorian date from JDN for the midnight of our date
633 wxLongLong timeMidnight
= m_time
;
634 long timeOnly
= (m_time
% MILLISECONDS_PER_DAY
).GetLo();
635 timeMidnight
-= timeOnly
;
637 // TODO this probably could be optimised somehow...
639 double jdn
= (timeMidnight
/ MILLISECONDS_PER_DAY
).GetLo();
640 jdn
+= EPOCH_JDN
+ 0.5;
641 long z
= jdn
- 1721118.5;
642 double r
= jdn
- 1721118.5 - z
;
644 long a
= g
/36524.25; // number of days per year
646 int year
= (b
+ g
) / 365.25;
647 long c
= b
+ z
- 365.25*year
;
648 int month
= (5*c
+ 456)/153;
649 int day
= c
- (153*month
- 457)/5 + (r
< 0.5 ? 0 : 1);
658 tm
.mon
= (Month
)(month
- 1); // algorithm yields 1 for January, not 0
660 tm
.msec
= timeOnly
% 1000;
662 timeOnly
/= 1000; // now we have time in seconds
664 tm
.sec
= timeOnly
% 60;
666 timeOnly
/= 60; // now we have time in minutes
668 tm
.min
= timeOnly
% 60;
671 tm
.hour
= timeOnly
/ 60;
677 wxDateTime
& wxDateTime::SetYear(int year
)
679 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
688 wxDateTime
& wxDateTime::SetMonth(Month month
)
690 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
699 wxDateTime
& wxDateTime::SetDay(wxDateTime_t mday
)
701 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
710 wxDateTime
& wxDateTime::SetHour(wxDateTime_t hour
)
712 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
721 wxDateTime
& wxDateTime::SetMinute(wxDateTime_t min
)
723 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
732 wxDateTime
& wxDateTime::SetSecond(wxDateTime_t sec
)
734 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
743 wxDateTime
& wxDateTime::SetMillisecond(wxDateTime_t millisecond
)
745 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
747 // we don't need to use GetTm() for this one
748 m_time
-= m_time
% 1000l;
749 m_time
+= millisecond
;
754 // ----------------------------------------------------------------------------
755 // wxDateTime arithmetics
756 // ----------------------------------------------------------------------------
758 wxDateTime
& wxDateTime::Add(const wxDateSpan
& diff
)
762 tm
.year
+= diff
.GetYears();
763 tm
.AddMonths(diff
.GetMonths());
764 tm
.AddDays(diff
.GetTotalDays());
771 // ----------------------------------------------------------------------------
772 // Weekday and monthday stuff
773 // ----------------------------------------------------------------------------
775 wxDateTime
& wxDateTime::SetToLastMonthDay(Month month
,
778 // take the current month/year if none specified
779 ReplaceDefaultYearMonthWithCurrent(&year
, &month
);
781 return Set(GetNumOfDaysInMonth(year
, month
), month
, year
);
784 bool wxDateTime::SetToWeekDay(WeekDay weekday
,
789 wxCHECK_MSG( weekday
!= Inv_WeekDay
, FALSE
, _T("invalid weekday") );
791 // we don't check explicitly that -5 <= n <= 5 because we will return FALSE
792 // anyhow in such case - but may be should still give an assert for it?
794 // take the current month/year if none specified
795 ReplaceDefaultYearMonthWithCurrent(&year
, &month
);
799 // TODO this probably could be optimised somehow...
803 // get the first day of the month
804 dt
.Set(1, month
, year
);
807 WeekDay wdayFirst
= dt
.GetWeekDay();
809 // go to the first weekday of the month
810 int diff
= weekday
- wdayFirst
;
814 // add advance n-1 weeks more
817 dt
-= wxDateSpan::Days(diff
);
821 // get the last day of the month
822 dt
.SetToLastMonthDay(month
, year
);
825 WeekDay wdayLast
= dt
.GetWeekDay();
827 // go to the last weekday of the month
828 int diff
= wdayLast
- weekday
;
832 // and rewind n-1 weeks from there
835 dt
-= wxDateSpan::Days(diff
);
838 // check that it is still in the same month
839 if ( dt
.GetMonth() == month
)
847 // no such day in this month
852 // ----------------------------------------------------------------------------
853 // Julian day number conversion and related stuff
854 // ----------------------------------------------------------------------------
856 double wxDateTime::GetJulianDayNumber() const
860 double result
= GetTruncatedJDN(tm
.mday
, tm
.mon
, tm
.year
);
862 // add the part GetTruncatedJDN() neglected
865 // and now add the time: 86400 sec = 1 JDN
866 return result
+ ((double)(60*(60*tm
.hour
+ tm
.min
) + tm
.sec
)) / 86400;
869 double wxDateTime::GetRataDie() const
871 // March 1 of the year 0 is Rata Die day -306 and JDN 1721119.5
872 return GetJulianDayNumber() - 1721119.5 - 306;
875 // ----------------------------------------------------------------------------
877 // ----------------------------------------------------------------------------
879 wxDateTime
& wxDateTime::MakeUTC()
881 return Add(wxTimeSpan::Seconds(GetTimeZone()));
884 wxDateTime
& wxDateTime::MakeTimezone(const TimeZone
& tz
)
886 int minDiff
= GetTimeZone() / SECONDS_IN_MINUTE
+ tz
.GetOffset();
887 return Add(wxTimeSpan::Minutes(minDiff
));
890 wxDateTime
& wxDateTime::MakeLocalTime(const TimeZone
& tz
)
892 int minDiff
= GetTimeZone() / SECONDS_IN_MINUTE
+ tz
.GetOffset();
893 return Substract(wxTimeSpan::Minutes(minDiff
));
896 // ----------------------------------------------------------------------------
897 // wxDateTime to/from text representations
898 // ----------------------------------------------------------------------------
900 wxString
wxDateTime::Format(const wxChar
*format
) const
902 wxCHECK_MSG( format
, _T(""), _T("NULL format in wxDateTime::Format") );
904 time_t time
= GetTicks();
905 if ( time
!= (time_t)-1 )
908 tm
*tm
= localtime(&time
);
910 // should never happen
911 wxCHECK_MSG( tm
, _T(""), _T("localtime() failed") );
913 return CallStrftime(format
, tm
);
917 // use a hack and still use strftime(): make a copy of the format and
918 // replace all occurences of YEAR in it with some unique string not
919 // appearing anywhere else in it, then use strftime() to format the
920 // date in year YEAR and then replace YEAR back by the real year and
921 // the unique replacement string back with YEAR where YEAR is any year
922 // in the range supported by strftime() (1970 - 2037) which is equal to
923 // the real year modulo 28 (so the week days coincide for them)
926 int yearReal
= GetYear();
927 int year
= 1970 + yearReal
% 28;
930 strYear
.Printf(_T("%d"), year
);
932 // find a string not occuring in format (this is surely not optimal way
933 // of doing it... improvements welcome!)
934 wxString fmt
= format
;
935 wxString replacement
= (wxChar
)-1;
936 while ( fmt
.Find(replacement
) != wxNOT_FOUND
)
938 replacement
<< (wxChar
)-1;
941 // replace all occurences of year with it
942 bool wasReplaced
= fmt
.Replace(strYear
, replacement
) > 0;
944 // use strftime() to format the same date but in supported year
945 wxDateTime
dt(*this);
947 wxString str
= dt
.Format(format
);
949 // now replace the occurence of 1999 with the real year
950 wxString strYearReal
;
951 strYearReal
.Printf(_T("%d"), yearReal
);
952 str
.Replace(strYear
, strYearReal
);
954 // and replace back all occurences of replacement string
956 str
.Replace(replacement
, strYear
);
962 // ============================================================================
964 // ============================================================================
966 // not all strftime(3) format specifiers make sense here because, for example,
967 // a time span doesn't have a year nor a timezone
969 // Here are the ones which are supported (all of them are supported by strftime
971 // %H hour in 24 hour format
972 // %M minute (00 - 59)
973 // %S second (00 - 59)
976 // Also, for MFC CTimeSpan compatibility, we support
979 // And, to be better than MFC :-), we also have
980 // %E number of wEeks
981 // %l milliseconds (000 - 999)
982 wxString
wxTimeSpan::Format(const wxChar
*format
) const
984 wxCHECK_MSG( format
, _T(""), _T("NULL format in wxTimeSpan::Format") );
987 str
.Alloc(strlen(format
));
989 for ( const wxChar
*pch
= format
; pch
; pch
++ )
1001 wxFAIL_MSG( _T("invalid format character") );
1005 // will get to str << ch below
1009 tmp
.Printf(_T("%d"), GetDays());
1013 tmp
.Printf(_T("%d"), GetWeeks());
1017 tmp
.Printf(_T("%02d"), GetHours());
1021 tmp
.Printf(_T("%03d"), GetMilliseconds());
1025 tmp
.Printf(_T("%02d"), GetMinutes());
1029 tmp
.Printf(_T("%02d"), GetSeconds());
1037 // skip str += ch below