1 ///////////////////////////////////////////////////////////////////////////////
3 // Purpose: implementation of time/date related classes
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 license
17 ///////////////////////////////////////////////////////////////////////////////
20 * Implementation notes:
22 * 1. the time is stored as a 64bit integer containing the signed number of
23 * milliseconds since Jan 1. 1970 (the Unix Epoch) - so it is always
26 * 2. the range is thus something about 580 million years, but due to current
27 * algorithms limitations, only dates from Nov 24, 4714BC are handled
29 * 3. standard ANSI C functions are used to do time calculations whenever
30 * possible, i.e. when the date is in the range Jan 1, 1970 to 2038
32 * 4. otherwise, the calculations are done by converting the date to/from JDN
33 * first (the range limitation mentioned above comes from here: the
34 * algorithm used by Scott E. Lee's code only works for positive JDNs, more
37 * 5. the object constructed for the given DD-MM-YYYY HH:MM:SS corresponds to
38 * this moment in local time and may be converted to the object
39 * corresponding to the same date/time in another time zone by using
42 * 6. the conversions to the current (or any other) timezone are done when the
43 * internal time representation is converted to the broken-down one in
47 // ============================================================================
49 // ============================================================================
51 // ----------------------------------------------------------------------------
53 // ----------------------------------------------------------------------------
56 #pragma implementation "datetime.h"
59 // For compilers that support precompilation, includes "wx.h".
60 #include "wx/wxprec.h"
66 #if !defined(wxUSE_DATETIME) || wxUSE_DATETIME
69 #include "wx/string.h"
74 #include "wx/thread.h"
75 #include "wx/tokenzr.h"
76 #include "wx/module.h"
78 #define wxDEFINE_TIME_CONSTANTS // before including datetime.h
82 #include "wx/datetime.h"
83 #include "wx/timer.h" // for wxGetLocalTimeMillis()
85 // ----------------------------------------------------------------------------
86 // conditional compilation
87 // ----------------------------------------------------------------------------
89 #if defined(HAVE_STRPTIME) && defined(__LINUX__)
90 // glibc 2.0.7 strptime() is broken - the following snippet causes it to
91 // crash (instead of just failing):
93 // strncpy(buf, "Tue Dec 21 20:25:40 1999", 128);
94 // strptime(buf, "%x", &tm);
98 #endif // broken strptime()
100 #if !defined(WX_TIMEZONE) && !defined(WX_GMTOFF_IN_TM)
101 #if defined(__BORLANDC__) || defined(__MINGW32__) || defined(__VISAGECPP__)
102 #define WX_TIMEZONE _timezone
103 #elif defined(__MWERKS__)
104 long wxmw_timezone
= 28800;
105 #define WX_TIMEZONE wxmw_timezone
106 #elif defined(__DJGPP__)
107 #include <sys/timeb.h>
109 static long wxGetTimeZone()
111 static long timezone
= MAXLONG
; // invalid timezone
112 if (timezone
== MAXLONG
)
116 timezone
= tb
.timezone
;
120 #define WX_TIMEZONE wxGetTimeZone()
121 #elif defined(__DARWIN__)
122 #define WX_GMTOFF_IN_TM
123 #else // unknown platform - try timezone
124 #define WX_TIMEZONE timezone
126 #endif // !WX_TIMEZONE && !WX_GMTOFF_IN_TM
128 // ----------------------------------------------------------------------------
130 // ----------------------------------------------------------------------------
132 // debugging helper: just a convenient replacement of wxCHECK()
133 #define wxDATETIME_CHECK(expr, msg) \
137 *this = wxInvalidDateTime; \
141 // ----------------------------------------------------------------------------
143 // ----------------------------------------------------------------------------
145 class wxDateTimeHolidaysModule
: public wxModule
148 virtual bool OnInit()
150 wxDateTimeHolidayAuthority::AddAuthority(new wxDateTimeWorkDays
);
155 virtual void OnExit()
157 wxDateTimeHolidayAuthority::ClearAllAuthorities();
158 wxDateTimeHolidayAuthority::ms_authorities
.Clear();
162 DECLARE_DYNAMIC_CLASS(wxDateTimeHolidaysModule
)
165 IMPLEMENT_DYNAMIC_CLASS(wxDateTimeHolidaysModule
, wxModule
)
167 // ----------------------------------------------------------------------------
169 // ----------------------------------------------------------------------------
172 static const int MONTHS_IN_YEAR
= 12;
174 static const int SEC_PER_MIN
= 60;
176 static const int MIN_PER_HOUR
= 60;
178 static const int HOURS_PER_DAY
= 24;
180 static const long SECONDS_PER_DAY
= 86400l;
182 static const int DAYS_PER_WEEK
= 7;
184 static const long MILLISECONDS_PER_DAY
= 86400000l;
186 // this is the integral part of JDN of the midnight of Jan 1, 1970
187 // (i.e. JDN(Jan 1, 1970) = 2440587.5)
188 static const long EPOCH_JDN
= 2440587l;
190 // the date of JDN -0.5 (as we don't work with fractional parts, this is the
191 // reference date for us) is Nov 24, 4714BC
192 static const int JDN_0_YEAR
= -4713;
193 static const int JDN_0_MONTH
= wxDateTime::Nov
;
194 static const int JDN_0_DAY
= 24;
196 // the constants used for JDN calculations
197 static const long JDN_OFFSET
= 32046l;
198 static const long DAYS_PER_5_MONTHS
= 153l;
199 static const long DAYS_PER_4_YEARS
= 1461l;
200 static const long DAYS_PER_400_YEARS
= 146097l;
202 // this array contains the cumulated number of days in all previous months for
203 // normal and leap years
204 static const wxDateTime::wxDateTime_t gs_cumulatedDays
[2][MONTHS_IN_YEAR
] =
206 { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 },
207 { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 }
210 // ----------------------------------------------------------------------------
212 // ----------------------------------------------------------------------------
214 // in the fine tradition of ANSI C we use our equivalent of (time_t)-1 to
215 // indicate an invalid wxDateTime object
216 const wxDateTime wxDefaultDateTime
;
218 wxDateTime::Country
wxDateTime::ms_country
= wxDateTime::Country_Unknown
;
220 // ----------------------------------------------------------------------------
222 // ----------------------------------------------------------------------------
224 // a critical section is needed to protect GetTimeZone() static
225 // variable in MT case
227 static wxCriticalSection gs_critsectTimezone
;
228 #endif // wxUSE_THREADS
230 // ----------------------------------------------------------------------------
232 // ----------------------------------------------------------------------------
234 // debugger helper: shows what the date really is
236 extern const wxChar
*wxDumpDate(const wxDateTime
* dt
)
238 static wxChar buf
[128];
240 wxStrcpy(buf
, dt
->Format(_T("%Y-%m-%d (%a) %H:%M:%S")));
246 // get the number of days in the given month of the given year
248 wxDateTime::wxDateTime_t
GetNumOfDaysInMonth(int year
, wxDateTime::Month month
)
250 // the number of days in month in Julian/Gregorian calendar: the first line
251 // is for normal years, the second one is for the leap ones
252 static wxDateTime::wxDateTime_t daysInMonth
[2][MONTHS_IN_YEAR
] =
254 { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
255 { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
258 return daysInMonth
[wxDateTime::IsLeapYear(year
)][month
];
261 // ensure that the timezone variable is set by calling localtime
262 static int GetTimeZone()
264 // set to TRUE when the timezone is set
265 static bool s_timezoneSet
= FALSE
;
266 #ifdef WX_GMTOFF_IN_TM
267 static long gmtoffset
= LONG_MAX
; // invalid timezone
270 wxCRIT_SECT_LOCKER(lock
, gs_critsectTimezone
);
272 if ( !s_timezoneSet
)
274 // just call localtime() instead of figuring out whether this system
275 // supports tzset(), _tzset() or something else
280 s_timezoneSet
= TRUE
;
281 #ifdef WX_GMTOFF_IN_TM
282 gmtoffset
= tm
->tm_gmtoff
;
286 #ifdef WX_GMTOFF_IN_TM
287 return (int)gmtoffset
;
289 return (int)WX_TIMEZONE
;
293 // return the integral part of the JDN for the midnight of the given date (to
294 // get the real JDN you need to add 0.5, this is, in fact, JDN of the
295 // noon of the previous day)
296 static long GetTruncatedJDN(wxDateTime::wxDateTime_t day
,
297 wxDateTime::Month mon
,
300 // CREDIT: code below is by Scott E. Lee (but bugs are mine)
302 // check the date validity
304 (year
> JDN_0_YEAR
) ||
305 ((year
== JDN_0_YEAR
) && (mon
> JDN_0_MONTH
)) ||
306 ((year
== JDN_0_YEAR
) && (mon
== JDN_0_MONTH
) && (day
>= JDN_0_DAY
)),
307 _T("date out of range - can't convert to JDN")
310 // make the year positive to avoid problems with negative numbers division
313 // months are counted from March here
315 if ( mon
>= wxDateTime::Mar
)
325 // now we can simply add all the contributions together
326 return ((year
/ 100) * DAYS_PER_400_YEARS
) / 4
327 + ((year
% 100) * DAYS_PER_4_YEARS
) / 4
328 + (month
* DAYS_PER_5_MONTHS
+ 2) / 5
333 // this function is a wrapper around strftime(3)
334 static wxString
CallStrftime(const wxChar
*format
, const tm
* tm
)
337 if ( !wxStrftime(buf
, WXSIZEOF(buf
), format
, tm
) )
339 // buffer is too small?
340 wxFAIL_MSG(_T("strftime() failed"));
343 return wxString(buf
);
346 // if year and/or month have invalid values, replace them with the current ones
347 static void ReplaceDefaultYearMonthWithCurrent(int *year
,
348 wxDateTime::Month
*month
)
350 struct tm
*tmNow
= NULL
;
352 if ( *year
== wxDateTime::Inv_Year
)
354 tmNow
= wxDateTime::GetTmNow();
356 *year
= 1900 + tmNow
->tm_year
;
359 if ( *month
== wxDateTime::Inv_Month
)
362 tmNow
= wxDateTime::GetTmNow();
364 *month
= (wxDateTime::Month
)tmNow
->tm_mon
;
368 // fll the struct tm with default values
369 static void InitTm(struct tm
& tm
)
371 // struct tm may have etxra fields (undocumented and with unportable
372 // names) which, nevertheless, must be set to 0
373 memset(&tm
, 0, sizeof(struct tm
));
375 tm
.tm_mday
= 1; // mday 0 is invalid
376 tm
.tm_year
= 76; // any valid year
377 tm
.tm_isdst
= -1; // auto determine
383 // return the month if the string is a month name or Inv_Month otherwise
384 static wxDateTime::Month
GetMonthFromName(const wxString
& name
, int flags
)
386 wxDateTime::Month mon
;
387 for ( mon
= wxDateTime::Jan
; mon
< wxDateTime::Inv_Month
; wxNextMonth(mon
) )
389 // case-insensitive comparison either one of or with both abbreviated
391 if ( flags
& wxDateTime::Name_Full
)
393 if ( name
.CmpNoCase(wxDateTime::
394 GetMonthName(mon
, wxDateTime::Name_Full
)) == 0 )
400 if ( flags
& wxDateTime::Name_Abbr
)
402 if ( name
.CmpNoCase(wxDateTime::
403 GetMonthName(mon
, wxDateTime::Name_Abbr
)) == 0 )
413 // return the weekday if the string is a weekday name or Inv_WeekDay otherwise
414 static wxDateTime::WeekDay
GetWeekDayFromName(const wxString
& name
, int flags
)
416 wxDateTime::WeekDay wd
;
417 for ( wd
= wxDateTime::Sun
; wd
< wxDateTime::Inv_WeekDay
; wxNextWDay(wd
) )
419 // case-insensitive comparison either one of or with both abbreviated
421 if ( flags
& wxDateTime::Name_Full
)
423 if ( name
.CmpNoCase(wxDateTime::
424 GetWeekDayName(wd
, wxDateTime::Name_Full
)) == 0 )
430 if ( flags
& wxDateTime::Name_Abbr
)
432 if ( name
.CmpNoCase(wxDateTime::
433 GetWeekDayName(wd
, wxDateTime::Name_Abbr
)) == 0 )
443 // scans all digits (but no more than len) and returns the resulting number
444 static bool GetNumericToken(size_t len
, const wxChar
*& p
, unsigned long *number
)
448 while ( wxIsdigit(*p
) )
452 if ( len
&& ++n
> len
)
456 return !!s
&& s
.ToULong(number
);
459 // scans all alphabetic characters and returns the resulting string
460 static wxString
GetAlphaToken(const wxChar
*& p
)
463 while ( wxIsalpha(*p
) )
471 // ============================================================================
472 // implementation of wxDateTime
473 // ============================================================================
475 // ----------------------------------------------------------------------------
477 // ----------------------------------------------------------------------------
481 year
= (wxDateTime_t
)wxDateTime::Inv_Year
;
482 mon
= wxDateTime::Inv_Month
;
484 hour
= min
= sec
= msec
= 0;
485 wday
= wxDateTime::Inv_WeekDay
;
488 wxDateTime::Tm::Tm(const struct tm
& tm
, const TimeZone
& tz
)
496 mon
= (wxDateTime::Month
)tm
.tm_mon
;
497 year
= 1900 + tm
.tm_year
;
502 bool wxDateTime::Tm::IsValid() const
504 // we allow for the leap seconds, although we don't use them (yet)
505 return (year
!= wxDateTime::Inv_Year
) && (mon
!= wxDateTime::Inv_Month
) &&
506 (mday
<= GetNumOfDaysInMonth(year
, mon
)) &&
507 (hour
< 24) && (min
< 60) && (sec
< 62) && (msec
< 1000);
510 void wxDateTime::Tm::ComputeWeekDay()
512 // compute the week day from day/month/year: we use the dumbest algorithm
513 // possible: just compute our JDN and then use the (simple to derive)
514 // formula: weekday = (JDN + 1.5) % 7
515 wday
= (wxDateTime::WeekDay
)(GetTruncatedJDN(mday
, mon
, year
) + 2) % 7;
518 void wxDateTime::Tm::AddMonths(int monDiff
)
520 // normalize the months field
521 while ( monDiff
< -mon
)
525 monDiff
+= MONTHS_IN_YEAR
;
528 while ( monDiff
+ mon
>= MONTHS_IN_YEAR
)
532 monDiff
-= MONTHS_IN_YEAR
;
535 mon
= (wxDateTime::Month
)(mon
+ monDiff
);
537 wxASSERT_MSG( mon
>= 0 && mon
< MONTHS_IN_YEAR
, _T("logic error") );
539 // NB: we don't check here that the resulting date is valid, this function
540 // is private and the caller must check it if needed
543 void wxDateTime::Tm::AddDays(int dayDiff
)
545 // normalize the days field
546 while ( dayDiff
+ mday
< 1 )
550 dayDiff
+= GetNumOfDaysInMonth(year
, mon
);
554 while ( mday
> GetNumOfDaysInMonth(year
, mon
) )
556 mday
-= GetNumOfDaysInMonth(year
, mon
);
561 wxASSERT_MSG( mday
> 0 && mday
<= GetNumOfDaysInMonth(year
, mon
),
565 // ----------------------------------------------------------------------------
567 // ----------------------------------------------------------------------------
569 wxDateTime::TimeZone::TimeZone(wxDateTime::TZ tz
)
573 case wxDateTime::Local
:
574 // get the offset from C RTL: it returns the difference GMT-local
575 // while we want to have the offset _from_ GMT, hence the '-'
576 m_offset
= -GetTimeZone();
579 case wxDateTime::GMT_12
:
580 case wxDateTime::GMT_11
:
581 case wxDateTime::GMT_10
:
582 case wxDateTime::GMT_9
:
583 case wxDateTime::GMT_8
:
584 case wxDateTime::GMT_7
:
585 case wxDateTime::GMT_6
:
586 case wxDateTime::GMT_5
:
587 case wxDateTime::GMT_4
:
588 case wxDateTime::GMT_3
:
589 case wxDateTime::GMT_2
:
590 case wxDateTime::GMT_1
:
591 m_offset
= -3600*(wxDateTime::GMT0
- tz
);
594 case wxDateTime::GMT0
:
595 case wxDateTime::GMT1
:
596 case wxDateTime::GMT2
:
597 case wxDateTime::GMT3
:
598 case wxDateTime::GMT4
:
599 case wxDateTime::GMT5
:
600 case wxDateTime::GMT6
:
601 case wxDateTime::GMT7
:
602 case wxDateTime::GMT8
:
603 case wxDateTime::GMT9
:
604 case wxDateTime::GMT10
:
605 case wxDateTime::GMT11
:
606 case wxDateTime::GMT12
:
607 m_offset
= 3600*(tz
- wxDateTime::GMT0
);
610 case wxDateTime::A_CST
:
611 // Central Standard Time in use in Australia = UTC + 9.5
612 m_offset
= 60l*(9*60 + 30);
616 wxFAIL_MSG( _T("unknown time zone") );
620 // ----------------------------------------------------------------------------
622 // ----------------------------------------------------------------------------
625 bool wxDateTime::IsLeapYear(int year
, wxDateTime::Calendar cal
)
627 if ( year
== Inv_Year
)
628 year
= GetCurrentYear();
630 if ( cal
== Gregorian
)
632 // in Gregorian calendar leap years are those divisible by 4 except
633 // those divisible by 100 unless they're also divisible by 400
634 // (in some countries, like Russia and Greece, additional corrections
635 // exist, but they won't manifest themselves until 2700)
636 return (year
% 4 == 0) && ((year
% 100 != 0) || (year
% 400 == 0));
638 else if ( cal
== Julian
)
640 // in Julian calendar the rule is simpler
641 return year
% 4 == 0;
645 wxFAIL_MSG(_T("unknown calendar"));
652 int wxDateTime::GetCentury(int year
)
654 return year
> 0 ? year
/ 100 : year
/ 100 - 1;
658 int wxDateTime::ConvertYearToBC(int year
)
661 return year
> 0 ? year
: year
- 1;
665 int wxDateTime::GetCurrentYear(wxDateTime::Calendar cal
)
670 return Now().GetYear();
673 wxFAIL_MSG(_T("TODO"));
677 wxFAIL_MSG(_T("unsupported calendar"));
685 wxDateTime::Month
wxDateTime::GetCurrentMonth(wxDateTime::Calendar cal
)
690 return Now().GetMonth();
693 wxFAIL_MSG(_T("TODO"));
697 wxFAIL_MSG(_T("unsupported calendar"));
705 wxDateTime::wxDateTime_t
wxDateTime::GetNumberOfDays(int year
, Calendar cal
)
707 if ( year
== Inv_Year
)
709 // take the current year if none given
710 year
= GetCurrentYear();
717 return IsLeapYear(year
) ? 366 : 365;
720 wxFAIL_MSG(_T("unsupported calendar"));
728 wxDateTime::wxDateTime_t
wxDateTime::GetNumberOfDays(wxDateTime::Month month
,
730 wxDateTime::Calendar cal
)
732 wxCHECK_MSG( month
< MONTHS_IN_YEAR
, 0, _T("invalid month") );
734 if ( cal
== Gregorian
|| cal
== Julian
)
736 if ( year
== Inv_Year
)
738 // take the current year if none given
739 year
= GetCurrentYear();
742 return GetNumOfDaysInMonth(year
, month
);
746 wxFAIL_MSG(_T("unsupported calendar"));
753 wxString
wxDateTime::GetMonthName(wxDateTime::Month month
,
754 wxDateTime::NameFlags flags
)
756 wxCHECK_MSG( month
!= Inv_Month
, _T(""), _T("invalid month") );
758 // notice that we must set all the fields to avoid confusing libc (GNU one
759 // gets confused to a crash if we don't do this)
764 return CallStrftime(flags
== Name_Abbr
? _T("%b") : _T("%B"), &tm
);
768 wxString
wxDateTime::GetWeekDayName(wxDateTime::WeekDay wday
,
769 wxDateTime::NameFlags flags
)
771 wxCHECK_MSG( wday
!= Inv_WeekDay
, _T(""), _T("invalid weekday") );
773 // take some arbitrary Sunday
780 // and offset it by the number of days needed to get the correct wday
783 // call mktime() to normalize it...
786 // ... and call strftime()
787 return CallStrftime(flags
== Name_Abbr
? _T("%a") : _T("%A"), &tm
);
791 void wxDateTime::GetAmPmStrings(wxString
*am
, wxString
*pm
)
797 *am
= CallStrftime(_T("%p"), &tm
);
802 *pm
= CallStrftime(_T("%p"), &tm
);
806 // ----------------------------------------------------------------------------
807 // Country stuff: date calculations depend on the country (DST, work days,
808 // ...), so we need to know which rules to follow.
809 // ----------------------------------------------------------------------------
812 wxDateTime::Country
wxDateTime::GetCountry()
814 // TODO use LOCALE_ICOUNTRY setting under Win32
816 if ( ms_country
== Country_Unknown
)
818 // try to guess from the time zone name
819 time_t t
= time(NULL
);
820 struct tm
*tm
= localtime(&t
);
822 wxString tz
= CallStrftime(_T("%Z"), tm
);
823 if ( tz
== _T("WET") || tz
== _T("WEST") )
827 else if ( tz
== _T("CET") || tz
== _T("CEST") )
829 ms_country
= Country_EEC
;
831 else if ( tz
== _T("MSK") || tz
== _T("MSD") )
835 else if ( tz
== _T("AST") || tz
== _T("ADT") ||
836 tz
== _T("EST") || tz
== _T("EDT") ||
837 tz
== _T("CST") || tz
== _T("CDT") ||
838 tz
== _T("MST") || tz
== _T("MDT") ||
839 tz
== _T("PST") || tz
== _T("PDT") )
845 // well, choose a default one
854 void wxDateTime::SetCountry(wxDateTime::Country country
)
856 ms_country
= country
;
860 bool wxDateTime::IsWestEuropeanCountry(Country country
)
862 if ( country
== Country_Default
)
864 country
= GetCountry();
867 return (Country_WesternEurope_Start
<= country
) &&
868 (country
<= Country_WesternEurope_End
);
871 // ----------------------------------------------------------------------------
872 // DST calculations: we use 3 different rules for the West European countries,
873 // USA and for the rest of the world. This is undoubtedly false for many
874 // countries, but I lack the necessary info (and the time to gather it),
875 // please add the other rules here!
876 // ----------------------------------------------------------------------------
879 bool wxDateTime::IsDSTApplicable(int year
, Country country
)
881 if ( year
== Inv_Year
)
883 // take the current year if none given
884 year
= GetCurrentYear();
887 if ( country
== Country_Default
)
889 country
= GetCountry();
896 // DST was first observed in the US and UK during WWI, reused
897 // during WWII and used again since 1966
898 return year
>= 1966 ||
899 (year
>= 1942 && year
<= 1945) ||
900 (year
== 1918 || year
== 1919);
903 // assume that it started after WWII
909 wxDateTime
wxDateTime::GetBeginDST(int year
, Country country
)
911 if ( year
== Inv_Year
)
913 // take the current year if none given
914 year
= GetCurrentYear();
917 if ( country
== Country_Default
)
919 country
= GetCountry();
922 if ( !IsDSTApplicable(year
, country
) )
924 return wxInvalidDateTime
;
929 if ( IsWestEuropeanCountry(country
) || (country
== Russia
) )
931 // DST begins at 1 a.m. GMT on the last Sunday of March
932 if ( !dt
.SetToLastWeekDay(Sun
, Mar
, year
) )
935 wxFAIL_MSG( _T("no last Sunday in March?") );
938 dt
+= wxTimeSpan::Hours(1);
940 // disable DST tests because it could result in an infinite recursion!
943 else switch ( country
)
950 // don't know for sure - assume it was in effect all year
955 dt
.Set(1, Jan
, year
);
959 // DST was installed Feb 2, 1942 by the Congress
960 dt
.Set(2, Feb
, year
);
963 // Oil embargo changed the DST period in the US
965 dt
.Set(6, Jan
, 1974);
969 dt
.Set(23, Feb
, 1975);
973 // before 1986, DST begun on the last Sunday of April, but
974 // in 1986 Reagan changed it to begin at 2 a.m. of the
975 // first Sunday in April
978 if ( !dt
.SetToLastWeekDay(Sun
, Apr
, year
) )
981 wxFAIL_MSG( _T("no first Sunday in April?") );
986 if ( !dt
.SetToWeekDay(Sun
, 1, Apr
, year
) )
989 wxFAIL_MSG( _T("no first Sunday in April?") );
993 dt
+= wxTimeSpan::Hours(2);
995 // TODO what about timezone??
1001 // assume Mar 30 as the start of the DST for the rest of the world
1002 // - totally bogus, of course
1003 dt
.Set(30, Mar
, year
);
1010 wxDateTime
wxDateTime::GetEndDST(int year
, Country country
)
1012 if ( year
== Inv_Year
)
1014 // take the current year if none given
1015 year
= GetCurrentYear();
1018 if ( country
== Country_Default
)
1020 country
= GetCountry();
1023 if ( !IsDSTApplicable(year
, country
) )
1025 return wxInvalidDateTime
;
1030 if ( IsWestEuropeanCountry(country
) || (country
== Russia
) )
1032 // DST ends at 1 a.m. GMT on the last Sunday of October
1033 if ( !dt
.SetToLastWeekDay(Sun
, Oct
, year
) )
1035 // weirder and weirder...
1036 wxFAIL_MSG( _T("no last Sunday in October?") );
1039 dt
+= wxTimeSpan::Hours(1);
1041 // disable DST tests because it could result in an infinite recursion!
1044 else switch ( country
)
1051 // don't know for sure - assume it was in effect all year
1055 dt
.Set(31, Dec
, year
);
1059 // the time was reset after the end of the WWII
1060 dt
.Set(30, Sep
, year
);
1064 // DST ends at 2 a.m. on the last Sunday of October
1065 if ( !dt
.SetToLastWeekDay(Sun
, Oct
, year
) )
1067 // weirder and weirder...
1068 wxFAIL_MSG( _T("no last Sunday in October?") );
1071 dt
+= wxTimeSpan::Hours(2);
1073 // TODO what about timezone??
1078 // assume October 26th as the end of the DST - totally bogus too
1079 dt
.Set(26, Oct
, year
);
1085 // ----------------------------------------------------------------------------
1086 // constructors and assignment operators
1087 // ----------------------------------------------------------------------------
1089 // return the current time with ms precision
1090 /* static */ wxDateTime
wxDateTime::UNow()
1092 return wxDateTime(wxGetLocalTimeMillis());
1095 // the values in the tm structure contain the local time
1096 wxDateTime
& wxDateTime::Set(const struct tm
& tm
)
1099 time_t timet
= mktime(&tm2
);
1101 if ( timet
== (time_t)-1 )
1103 // mktime() rather unintuitively fails for Jan 1, 1970 if the hour is
1104 // less than timezone - try to make it work for this case
1105 if ( tm2
.tm_year
== 70 && tm2
.tm_mon
== 0 && tm2
.tm_mday
== 1 )
1107 // add timezone to make sure that date is in range
1108 tm2
.tm_sec
-= GetTimeZone();
1110 timet
= mktime(&tm2
);
1111 if ( timet
!= (time_t)-1 )
1113 timet
+= GetTimeZone();
1119 wxFAIL_MSG( _T("mktime() failed") );
1121 *this = wxInvalidDateTime
;
1131 wxDateTime
& wxDateTime::Set(wxDateTime_t hour
,
1132 wxDateTime_t minute
,
1133 wxDateTime_t second
,
1134 wxDateTime_t millisec
)
1136 // we allow seconds to be 61 to account for the leap seconds, even if we
1137 // don't use them really
1138 wxDATETIME_CHECK( hour
< 24 &&
1142 _T("Invalid time in wxDateTime::Set()") );
1144 // get the current date from system
1145 struct tm
*tm
= GetTmNow();
1147 wxDATETIME_CHECK( tm
, _T("localtime() failed") );
1151 tm
->tm_min
= minute
;
1152 tm
->tm_sec
= second
;
1156 // and finally adjust milliseconds
1157 return SetMillisecond(millisec
);
1160 wxDateTime
& wxDateTime::Set(wxDateTime_t day
,
1164 wxDateTime_t minute
,
1165 wxDateTime_t second
,
1166 wxDateTime_t millisec
)
1168 wxDATETIME_CHECK( hour
< 24 &&
1172 _T("Invalid time in wxDateTime::Set()") );
1174 ReplaceDefaultYearMonthWithCurrent(&year
, &month
);
1176 wxDATETIME_CHECK( (0 < day
) && (day
<= GetNumberOfDays(month
, year
)),
1177 _T("Invalid date in wxDateTime::Set()") );
1179 // the range of time_t type (inclusive)
1180 static const int yearMinInRange
= 1970;
1181 static const int yearMaxInRange
= 2037;
1183 // test only the year instead of testing for the exact end of the Unix
1184 // time_t range - it doesn't bring anything to do more precise checks
1185 if ( year
>= yearMinInRange
&& year
<= yearMaxInRange
)
1187 // use the standard library version if the date is in range - this is
1188 // probably more efficient than our code
1190 tm
.tm_year
= year
- 1900;
1196 tm
.tm_isdst
= -1; // mktime() will guess it
1200 // and finally adjust milliseconds
1201 return SetMillisecond(millisec
);
1205 // do time calculations ourselves: we want to calculate the number of
1206 // milliseconds between the given date and the epoch
1208 // get the JDN for the midnight of this day
1209 m_time
= GetTruncatedJDN(day
, month
, year
);
1210 m_time
-= EPOCH_JDN
;
1211 m_time
*= SECONDS_PER_DAY
* TIME_T_FACTOR
;
1213 // JDN corresponds to GMT, we take localtime
1214 Add(wxTimeSpan(hour
, minute
, second
+ GetTimeZone(), millisec
));
1220 wxDateTime
& wxDateTime::Set(double jdn
)
1222 // so that m_time will be 0 for the midnight of Jan 1, 1970 which is jdn
1224 jdn
-= EPOCH_JDN
+ 0.5;
1226 jdn
*= MILLISECONDS_PER_DAY
;
1233 wxDateTime
& wxDateTime::ResetTime()
1237 if ( tm
.hour
|| tm
.min
|| tm
.sec
|| tm
.msec
)
1250 // ----------------------------------------------------------------------------
1251 // DOS Date and Time Format functions
1252 // ----------------------------------------------------------------------------
1253 // the dos date and time value is an unsigned 32 bit value in the format:
1254 // YYYYYYYMMMMDDDDDhhhhhmmmmmmsssss
1256 // Y = year offset from 1980 (0-127)
1258 // D = day of month (1-31)
1260 // m = minute (0-59)
1261 // s = bisecond (0-29) each bisecond indicates two seconds
1262 // ----------------------------------------------------------------------------
1264 wxDateTime
& wxDateTime::SetFromDOS(unsigned long ddt
)
1268 long year
= ddt
& 0xFE000000;
1273 long month
= ddt
& 0x1E00000;
1278 long day
= ddt
& 0x1F0000;
1282 long hour
= ddt
& 0xF800;
1286 long minute
= ddt
& 0x7E0;
1290 long second
= ddt
& 0x1F;
1291 tm
.tm_sec
= second
* 2;
1293 return Set(mktime(&tm
));
1296 unsigned long wxDateTime::GetAsDOS() const
1299 time_t ticks
= GetTicks();
1300 struct tm
*tm
= localtime(&ticks
);
1302 long year
= tm
->tm_year
;
1306 long month
= tm
->tm_mon
;
1310 long day
= tm
->tm_mday
;
1313 long hour
= tm
->tm_hour
;
1316 long minute
= tm
->tm_min
;
1319 long second
= tm
->tm_sec
;
1322 ddt
= year
| month
| day
| hour
| minute
| second
;
1326 // ----------------------------------------------------------------------------
1327 // time_t <-> broken down time conversions
1328 // ----------------------------------------------------------------------------
1330 wxDateTime::Tm
wxDateTime::GetTm(const TimeZone
& tz
) const
1332 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1334 time_t time
= GetTicks();
1335 if ( time
!= (time_t)-1 )
1337 // use C RTL functions
1339 if ( tz
.GetOffset() == -GetTimeZone() )
1341 // we are working with local time
1342 tm
= localtime(&time
);
1344 // should never happen
1345 wxCHECK_MSG( tm
, Tm(), _T("localtime() failed") );
1349 time
+= (time_t)tz
.GetOffset();
1350 #if defined(__VMS__) || defined(__WATCOMC__) // time is unsigned so avoid warning
1351 int time2
= (int) time
;
1359 // should never happen
1360 wxCHECK_MSG( tm
, Tm(), _T("gmtime() failed") );
1364 tm
= (struct tm
*)NULL
;
1370 // adjust the milliseconds
1372 long timeOnly
= (m_time
% MILLISECONDS_PER_DAY
).ToLong();
1373 tm2
.msec
= (wxDateTime_t
)(timeOnly
% 1000);
1376 //else: use generic code below
1379 // remember the time and do the calculations with the date only - this
1380 // eliminates rounding errors of the floating point arithmetics
1382 wxLongLong timeMidnight
= m_time
+ tz
.GetOffset() * 1000;
1384 long timeOnly
= (timeMidnight
% MILLISECONDS_PER_DAY
).ToLong();
1386 // we want to always have positive time and timeMidnight to be really
1387 // the midnight before it
1390 timeOnly
= MILLISECONDS_PER_DAY
+ timeOnly
;
1393 timeMidnight
-= timeOnly
;
1395 // calculate the Gregorian date from JDN for the midnight of our date:
1396 // this will yield day, month (in 1..12 range) and year
1398 // actually, this is the JDN for the noon of the previous day
1399 long jdn
= (timeMidnight
/ MILLISECONDS_PER_DAY
).ToLong() + EPOCH_JDN
;
1401 // CREDIT: code below is by Scott E. Lee (but bugs are mine)
1403 wxASSERT_MSG( jdn
> -2, _T("JDN out of range") );
1405 // calculate the century
1406 long temp
= (jdn
+ JDN_OFFSET
) * 4 - 1;
1407 long century
= temp
/ DAYS_PER_400_YEARS
;
1409 // then the year and day of year (1 <= dayOfYear <= 366)
1410 temp
= ((temp
% DAYS_PER_400_YEARS
) / 4) * 4 + 3;
1411 long year
= (century
* 100) + (temp
/ DAYS_PER_4_YEARS
);
1412 long dayOfYear
= (temp
% DAYS_PER_4_YEARS
) / 4 + 1;
1414 // and finally the month and day of the month
1415 temp
= dayOfYear
* 5 - 3;
1416 long month
= temp
/ DAYS_PER_5_MONTHS
;
1417 long day
= (temp
% DAYS_PER_5_MONTHS
) / 5 + 1;
1419 // month is counted from March - convert to normal
1430 // year is offset by 4800
1433 // check that the algorithm gave us something reasonable
1434 wxASSERT_MSG( (0 < month
) && (month
<= 12), _T("invalid month") );
1435 wxASSERT_MSG( (1 <= day
) && (day
< 32), _T("invalid day") );
1437 // construct Tm from these values
1439 tm
.year
= (int)year
;
1440 tm
.mon
= (Month
)(month
- 1); // algorithm yields 1 for January, not 0
1441 tm
.mday
= (wxDateTime_t
)day
;
1442 tm
.msec
= (wxDateTime_t
)(timeOnly
% 1000);
1443 timeOnly
-= tm
.msec
;
1444 timeOnly
/= 1000; // now we have time in seconds
1446 tm
.sec
= (wxDateTime_t
)(timeOnly
% 60);
1448 timeOnly
/= 60; // now we have time in minutes
1450 tm
.min
= (wxDateTime_t
)(timeOnly
% 60);
1453 tm
.hour
= (wxDateTime_t
)(timeOnly
/ 60);
1458 wxDateTime
& wxDateTime::SetYear(int year
)
1460 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1469 wxDateTime
& wxDateTime::SetMonth(Month month
)
1471 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1480 wxDateTime
& wxDateTime::SetDay(wxDateTime_t mday
)
1482 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1491 wxDateTime
& wxDateTime::SetHour(wxDateTime_t hour
)
1493 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1502 wxDateTime
& wxDateTime::SetMinute(wxDateTime_t min
)
1504 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1513 wxDateTime
& wxDateTime::SetSecond(wxDateTime_t sec
)
1515 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1524 wxDateTime
& wxDateTime::SetMillisecond(wxDateTime_t millisecond
)
1526 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1528 // we don't need to use GetTm() for this one
1529 m_time
-= m_time
% 1000l;
1530 m_time
+= millisecond
;
1535 // ----------------------------------------------------------------------------
1536 // wxDateTime arithmetics
1537 // ----------------------------------------------------------------------------
1539 wxDateTime
& wxDateTime::Add(const wxDateSpan
& diff
)
1543 tm
.year
+= diff
.GetYears();
1544 tm
.AddMonths(diff
.GetMonths());
1546 // check that the resulting date is valid
1547 if ( tm
.mday
> GetNumOfDaysInMonth(tm
.year
, tm
.mon
) )
1549 // We suppose that when adding one month to Jan 31 we want to get Feb
1550 // 28 (or 29), i.e. adding a month to the last day of the month should
1551 // give the last day of the next month which is quite logical.
1553 // Unfortunately, there is no logic way to understand what should
1554 // Jan 30 + 1 month be - Feb 28 too or Feb 27 (assuming non leap year)?
1555 // We make it Feb 28 (last day too), but it is highly questionable.
1556 tm
.mday
= GetNumOfDaysInMonth(tm
.year
, tm
.mon
);
1559 tm
.AddDays(diff
.GetTotalDays());
1563 wxASSERT_MSG( IsSameTime(tm
),
1564 _T("Add(wxDateSpan) shouldn't modify time") );
1569 // ----------------------------------------------------------------------------
1570 // Weekday and monthday stuff
1571 // ----------------------------------------------------------------------------
1573 bool wxDateTime::SetToTheWeek(wxDateTime_t numWeek
,
1577 wxASSERT_MSG( numWeek
> 0,
1578 _T("invalid week number: weeks are counted from 1") );
1580 int year
= GetYear();
1582 // Jan 4 always lies in the 1st week of the year
1584 SetToWeekDayInSameWeek(weekday
, flags
) += wxDateSpan::Weeks(numWeek
- 1);
1586 if ( GetYear() != year
)
1588 // oops... numWeek was too big
1595 wxDateTime
& wxDateTime::SetToLastMonthDay(Month month
,
1598 // take the current month/year if none specified
1599 if ( year
== Inv_Year
)
1601 if ( month
== Inv_Month
)
1604 return Set(GetNumOfDaysInMonth(year
, month
), month
, year
);
1607 wxDateTime
& wxDateTime::SetToWeekDayInSameWeek(WeekDay weekday
, WeekFlags flags
)
1609 wxDATETIME_CHECK( weekday
!= Inv_WeekDay
, _T("invalid weekday") );
1611 int wdayThis
= GetWeekDay();
1612 if ( weekday
== wdayThis
)
1618 if ( flags
== Default_First
)
1620 flags
= GetCountry() == USA
? Sunday_First
: Monday_First
;
1623 // the logic below based on comparing weekday and wdayThis works if Sun (0)
1624 // is the first day in the week, but breaks down for Monday_First case so
1625 // we adjust the week days in this case
1626 if( flags
== Monday_First
)
1628 if ( wdayThis
== Sun
)
1631 //else: Sunday_First, nothing to do
1633 // go forward or back in time to the day we want
1634 if ( weekday
< wdayThis
)
1636 return Subtract(wxDateSpan::Days(wdayThis
- weekday
));
1638 else // weekday > wdayThis
1640 return Add(wxDateSpan::Days(weekday
- wdayThis
));
1644 wxDateTime
& wxDateTime::SetToNextWeekDay(WeekDay weekday
)
1646 wxDATETIME_CHECK( weekday
!= Inv_WeekDay
, _T("invalid weekday") );
1649 WeekDay wdayThis
= GetWeekDay();
1650 if ( weekday
== wdayThis
)
1655 else if ( weekday
< wdayThis
)
1657 // need to advance a week
1658 diff
= 7 - (wdayThis
- weekday
);
1660 else // weekday > wdayThis
1662 diff
= weekday
- wdayThis
;
1665 return Add(wxDateSpan::Days(diff
));
1668 wxDateTime
& wxDateTime::SetToPrevWeekDay(WeekDay weekday
)
1670 wxDATETIME_CHECK( weekday
!= Inv_WeekDay
, _T("invalid weekday") );
1673 WeekDay wdayThis
= GetWeekDay();
1674 if ( weekday
== wdayThis
)
1679 else if ( weekday
> wdayThis
)
1681 // need to go to previous week
1682 diff
= 7 - (weekday
- wdayThis
);
1684 else // weekday < wdayThis
1686 diff
= wdayThis
- weekday
;
1689 return Subtract(wxDateSpan::Days(diff
));
1692 bool wxDateTime::SetToWeekDay(WeekDay weekday
,
1697 wxCHECK_MSG( weekday
!= Inv_WeekDay
, FALSE
, _T("invalid weekday") );
1699 // we don't check explicitly that -5 <= n <= 5 because we will return FALSE
1700 // anyhow in such case - but may be should still give an assert for it?
1702 // take the current month/year if none specified
1703 ReplaceDefaultYearMonthWithCurrent(&year
, &month
);
1707 // TODO this probably could be optimised somehow...
1711 // get the first day of the month
1712 dt
.Set(1, month
, year
);
1715 WeekDay wdayFirst
= dt
.GetWeekDay();
1717 // go to the first weekday of the month
1718 int diff
= weekday
- wdayFirst
;
1722 // add advance n-1 weeks more
1725 dt
+= wxDateSpan::Days(diff
);
1727 else // count from the end of the month
1729 // get the last day of the month
1730 dt
.SetToLastMonthDay(month
, year
);
1733 WeekDay wdayLast
= dt
.GetWeekDay();
1735 // go to the last weekday of the month
1736 int diff
= wdayLast
- weekday
;
1740 // and rewind n-1 weeks from there
1743 dt
-= wxDateSpan::Days(diff
);
1746 // check that it is still in the same month
1747 if ( dt
.GetMonth() == month
)
1755 // no such day in this month
1760 wxDateTime::wxDateTime_t
wxDateTime::GetDayOfYear(const TimeZone
& tz
) const
1764 return gs_cumulatedDays
[IsLeapYear(tm
.year
)][tm
.mon
] + tm
.mday
;
1767 wxDateTime::wxDateTime_t
wxDateTime::GetWeekOfYear(wxDateTime::WeekFlags flags
,
1768 const TimeZone
& tz
) const
1770 if ( flags
== Default_First
)
1772 flags
= GetCountry() == USA
? Sunday_First
: Monday_First
;
1775 wxDateTime_t nDayInYear
= GetDayOfYear(tz
);
1778 WeekDay wd
= GetWeekDay(tz
);
1779 if ( flags
== Sunday_First
)
1781 week
= (nDayInYear
- wd
+ 7) / 7;
1785 // have to shift the week days values
1786 week
= (nDayInYear
- (wd
- 1 + 7) % 7 + 7) / 7;
1789 // FIXME some more elegant way??
1790 WeekDay wdYearStart
= wxDateTime(1, Jan
, GetYear()).GetWeekDay();
1791 if ( wdYearStart
== Wed
|| wdYearStart
== Thu
)
1799 wxDateTime::wxDateTime_t
wxDateTime::GetWeekOfMonth(wxDateTime::WeekFlags flags
,
1800 const TimeZone
& tz
) const
1803 wxDateTime dtMonthStart
= wxDateTime(1, tm
.mon
, tm
.year
);
1804 int nWeek
= GetWeekOfYear(flags
) - dtMonthStart
.GetWeekOfYear(flags
) + 1;
1807 // this may happen for January when Jan, 1 is the last week of the
1809 nWeek
+= IsLeapYear(tm
.year
- 1) ? 53 : 52;
1812 return (wxDateTime::wxDateTime_t
)nWeek
;
1815 wxDateTime
& wxDateTime::SetToYearDay(wxDateTime::wxDateTime_t yday
)
1817 int year
= GetYear();
1818 wxDATETIME_CHECK( (0 < yday
) && (yday
<= GetNumberOfDays(year
)),
1819 _T("invalid year day") );
1821 bool isLeap
= IsLeapYear(year
);
1822 for ( Month mon
= Jan
; mon
< Inv_Month
; wxNextMonth(mon
) )
1824 // for Dec, we can't compare with gs_cumulatedDays[mon + 1], but we
1825 // don't need it neither - because of the CHECK above we know that
1826 // yday lies in December then
1827 if ( (mon
== Dec
) || (yday
< gs_cumulatedDays
[isLeap
][mon
+ 1]) )
1829 Set(yday
- gs_cumulatedDays
[isLeap
][mon
], mon
, year
);
1838 // ----------------------------------------------------------------------------
1839 // Julian day number conversion and related stuff
1840 // ----------------------------------------------------------------------------
1842 double wxDateTime::GetJulianDayNumber() const
1844 // JDN are always expressed for the GMT dates
1845 Tm
tm(ToTimezone(GMT0
).GetTm(GMT0
));
1847 double result
= GetTruncatedJDN(tm
.mday
, tm
.mon
, tm
.year
);
1849 // add the part GetTruncatedJDN() neglected
1852 // and now add the time: 86400 sec = 1 JDN
1853 return result
+ ((double)(60*(60*tm
.hour
+ tm
.min
) + tm
.sec
)) / 86400;
1856 double wxDateTime::GetRataDie() const
1858 // March 1 of the year 0 is Rata Die day -306 and JDN 1721119.5
1859 return GetJulianDayNumber() - 1721119.5 - 306;
1862 // ----------------------------------------------------------------------------
1863 // timezone and DST stuff
1864 // ----------------------------------------------------------------------------
1866 int wxDateTime::IsDST(wxDateTime::Country country
) const
1868 wxCHECK_MSG( country
== Country_Default
, -1,
1869 _T("country support not implemented") );
1871 // use the C RTL for the dates in the standard range
1872 time_t timet
= GetTicks();
1873 if ( timet
!= (time_t)-1 )
1875 tm
*tm
= localtime(&timet
);
1877 wxCHECK_MSG( tm
, -1, _T("localtime() failed") );
1879 return tm
->tm_isdst
;
1883 int year
= GetYear();
1885 if ( !IsDSTApplicable(year
, country
) )
1887 // no DST time in this year in this country
1891 return IsBetween(GetBeginDST(year
, country
), GetEndDST(year
, country
));
1895 wxDateTime
& wxDateTime::MakeTimezone(const TimeZone
& tz
, bool noDST
)
1897 long secDiff
= GetTimeZone() + tz
.GetOffset();
1899 // we need to know whether DST is or not in effect for this date unless
1900 // the test disabled by the caller
1901 if ( !noDST
&& (IsDST() == 1) )
1903 // FIXME we assume that the DST is always shifted by 1 hour
1907 return Subtract(wxTimeSpan::Seconds(secDiff
));
1910 // ----------------------------------------------------------------------------
1911 // wxDateTime to/from text representations
1912 // ----------------------------------------------------------------------------
1914 wxString
wxDateTime::Format(const wxChar
*format
, const TimeZone
& tz
) const
1916 wxCHECK_MSG( format
, _T(""), _T("NULL format in wxDateTime::Format") );
1918 // we have to use our own implementation if the date is out of range of
1919 // strftime() or if we use non standard specificators
1920 time_t time
= GetTicks();
1921 if ( (time
!= (time_t)-1) && !wxStrstr(format
, _T("%l")) )
1925 if ( tz
.GetOffset() == -GetTimeZone() )
1927 // we are working with local time
1928 tm
= localtime(&time
);
1930 // should never happen
1931 wxCHECK_MSG( tm
, wxEmptyString
, _T("localtime() failed") );
1935 time
+= (int)tz
.GetOffset();
1937 #if defined(__VMS__) || defined(__WATCOMC__) // time is unsigned so avoid warning
1938 int time2
= (int) time
;
1946 // should never happen
1947 wxCHECK_MSG( tm
, wxEmptyString
, _T("gmtime() failed") );
1951 tm
= (struct tm
*)NULL
;
1957 return CallStrftime(format
, tm
);
1959 //else: use generic code below
1962 // we only parse ANSI C format specifications here, no POSIX 2
1963 // complications, no GNU extensions but we do add support for a "%l" format
1964 // specifier allowing to get the number of milliseconds
1967 // used for calls to strftime() when we only deal with time
1968 struct tm tmTimeOnly
;
1969 tmTimeOnly
.tm_hour
= tm
.hour
;
1970 tmTimeOnly
.tm_min
= tm
.min
;
1971 tmTimeOnly
.tm_sec
= tm
.sec
;
1972 tmTimeOnly
.tm_wday
= 0;
1973 tmTimeOnly
.tm_yday
= 0;
1974 tmTimeOnly
.tm_mday
= 1; // any date will do
1975 tmTimeOnly
.tm_mon
= 0;
1976 tmTimeOnly
.tm_year
= 76;
1977 tmTimeOnly
.tm_isdst
= 0; // no DST, we adjust for tz ourselves
1979 wxString tmp
, res
, fmt
;
1980 for ( const wxChar
*p
= format
; *p
; p
++ )
1982 if ( *p
!= _T('%') )
1990 // set the default format
1993 case _T('Y'): // year has 4 digits
1997 case _T('j'): // day of year has 3 digits
1998 case _T('l'): // milliseconds have 3 digits
2002 case _T('w'): // week day as number has only one
2007 // it's either another valid format specifier in which case
2008 // the format is "%02d" (for all the rest) or we have the
2009 // field width preceding the format in which case it will
2010 // override the default format anyhow
2014 bool restart
= TRUE
;
2019 // start of the format specification
2022 case _T('a'): // a weekday name
2024 // second parameter should be TRUE for abbreviated names
2025 res
+= GetWeekDayName(tm
.GetWeekDay(),
2026 *p
== _T('a') ? Name_Abbr
: Name_Full
);
2029 case _T('b'): // a month name
2031 res
+= GetMonthName(tm
.mon
,
2032 *p
== _T('b') ? Name_Abbr
: Name_Full
);
2035 case _T('c'): // locale default date and time representation
2036 case _T('x'): // locale default date representation
2038 // the problem: there is no way to know what do these format
2039 // specifications correspond to for the current locale.
2041 // the solution: use a hack and still use strftime(): first
2042 // find the YEAR which is a year in the strftime() range (1970
2043 // - 2038) whose Jan 1 falls on the same week day as the Jan 1
2044 // of the real year. Then make a copy of the format and
2045 // replace all occurences of YEAR in it with some unique
2046 // string not appearing anywhere else in it, then use
2047 // strftime() to format the date in year YEAR and then replace
2048 // YEAR back by the real year and the unique replacement
2049 // string back with YEAR. Notice that "all occurences of YEAR"
2050 // means all occurences of 4 digit as well as 2 digit form!
2052 // the bugs: we assume that neither of %c nor %x contains any
2053 // fields which may change between the YEAR and real year. For
2054 // example, the week number (%U, %W) and the day number (%j)
2055 // will change if one of these years is leap and the other one
2058 // find the YEAR: normally, for any year X, Jan 1 or the
2059 // year X + 28 is the same weekday as Jan 1 of X (because
2060 // the weekday advances by 1 for each normal X and by 2
2061 // for each leap X, hence by 5 every 4 years or by 35
2062 // which is 0 mod 7 every 28 years) but this rule breaks
2063 // down if there are years between X and Y which are
2064 // divisible by 4 but not leap (i.e. divisible by 100 but
2065 // not 400), hence the correction.
2067 int yearReal
= GetYear(tz
);
2068 int mod28
= yearReal
% 28;
2070 // be careful to not go too far - we risk to leave the
2075 year
= 1988 + mod28
; // 1988 == 0 (mod 28)
2079 year
= 1970 + mod28
- 10; // 1970 == 10 (mod 28)
2082 int nCentury
= year
/ 100,
2083 nCenturyReal
= yearReal
/ 100;
2085 // need to adjust for the years divisble by 400 which are
2086 // not leap but are counted like leap ones if we just take
2087 // the number of centuries in between for nLostWeekDays
2088 int nLostWeekDays
= (nCentury
- nCenturyReal
) -
2089 (nCentury
/ 4 - nCenturyReal
/ 4);
2091 // we have to gain back the "lost" weekdays: note that the
2092 // effect of this loop is to not do anything to
2093 // nLostWeekDays (which we won't use any more), but to
2094 // (indirectly) set the year correctly
2095 while ( (nLostWeekDays
% 7) != 0 )
2097 nLostWeekDays
+= year
++ % 4 ? 1 : 2;
2100 // at any rate, we couldn't go further than 1988 + 9 + 28!
2101 wxASSERT_MSG( year
< 2030,
2102 _T("logic error in wxDateTime::Format") );
2104 wxString strYear
, strYear2
;
2105 strYear
.Printf(_T("%d"), year
);
2106 strYear2
.Printf(_T("%d"), year
% 100);
2108 // find two strings not occuring in format (this is surely
2109 // not optimal way of doing it... improvements welcome!)
2110 wxString fmt
= format
;
2111 wxString replacement
= (wxChar
)-1;
2112 while ( fmt
.Find(replacement
) != wxNOT_FOUND
)
2114 replacement
<< (wxChar
)-1;
2117 wxString replacement2
= (wxChar
)-2;
2118 while ( fmt
.Find(replacement
) != wxNOT_FOUND
)
2120 replacement
<< (wxChar
)-2;
2123 // replace all occurences of year with it
2124 bool wasReplaced
= fmt
.Replace(strYear
, replacement
) > 0;
2126 wasReplaced
= fmt
.Replace(strYear2
, replacement2
) > 0;
2128 // use strftime() to format the same date but in supported
2131 // NB: we assume that strftime() doesn't check for the
2132 // date validity and will happily format the date
2133 // corresponding to Feb 29 of a non leap year (which
2134 // may happen if yearReal was leap and year is not)
2135 struct tm tmAdjusted
;
2137 tmAdjusted
.tm_hour
= tm
.hour
;
2138 tmAdjusted
.tm_min
= tm
.min
;
2139 tmAdjusted
.tm_sec
= tm
.sec
;
2140 tmAdjusted
.tm_wday
= tm
.GetWeekDay();
2141 tmAdjusted
.tm_yday
= GetDayOfYear();
2142 tmAdjusted
.tm_mday
= tm
.mday
;
2143 tmAdjusted
.tm_mon
= tm
.mon
;
2144 tmAdjusted
.tm_year
= year
- 1900;
2145 tmAdjusted
.tm_isdst
= 0; // no DST, already adjusted
2146 wxString str
= CallStrftime(*p
== _T('c') ? _T("%c")
2150 // now replace the occurence of 1999 with the real year
2151 wxString strYearReal
, strYearReal2
;
2152 strYearReal
.Printf(_T("%04d"), yearReal
);
2153 strYearReal2
.Printf(_T("%02d"), yearReal
% 100);
2154 str
.Replace(strYear
, strYearReal
);
2155 str
.Replace(strYear2
, strYearReal2
);
2157 // and replace back all occurences of replacement string
2160 str
.Replace(replacement2
, strYear2
);
2161 str
.Replace(replacement
, strYear
);
2168 case _T('d'): // day of a month (01-31)
2169 res
+= wxString::Format(fmt
, tm
.mday
);
2172 case _T('H'): // hour in 24h format (00-23)
2173 res
+= wxString::Format(fmt
, tm
.hour
);
2176 case _T('I'): // hour in 12h format (01-12)
2178 // 24h -> 12h, 0h -> 12h too
2179 int hour12
= tm
.hour
> 12 ? tm
.hour
- 12
2180 : tm
.hour
? tm
.hour
: 12;
2181 res
+= wxString::Format(fmt
, hour12
);
2185 case _T('j'): // day of the year
2186 res
+= wxString::Format(fmt
, GetDayOfYear(tz
));
2189 case _T('l'): // milliseconds (NOT STANDARD)
2190 res
+= wxString::Format(fmt
, GetMillisecond(tz
));
2193 case _T('m'): // month as a number (01-12)
2194 res
+= wxString::Format(fmt
, tm
.mon
+ 1);
2197 case _T('M'): // minute as a decimal number (00-59)
2198 res
+= wxString::Format(fmt
, tm
.min
);
2201 case _T('p'): // AM or PM string
2202 res
+= CallStrftime(_T("%p"), &tmTimeOnly
);
2205 case _T('S'): // second as a decimal number (00-61)
2206 res
+= wxString::Format(fmt
, tm
.sec
);
2209 case _T('U'): // week number in the year (Sunday 1st week day)
2210 res
+= wxString::Format(fmt
, GetWeekOfYear(Sunday_First
, tz
));
2213 case _T('W'): // week number in the year (Monday 1st week day)
2214 res
+= wxString::Format(fmt
, GetWeekOfYear(Monday_First
, tz
));
2217 case _T('w'): // weekday as a number (0-6), Sunday = 0
2218 res
+= wxString::Format(fmt
, tm
.GetWeekDay());
2221 // case _T('x'): -- handled with "%c"
2223 case _T('X'): // locale default time representation
2224 // just use strftime() to format the time for us
2225 res
+= CallStrftime(_T("%X"), &tmTimeOnly
);
2228 case _T('y'): // year without century (00-99)
2229 res
+= wxString::Format(fmt
, tm
.year
% 100);
2232 case _T('Y'): // year with century
2233 res
+= wxString::Format(fmt
, tm
.year
);
2236 case _T('Z'): // timezone name
2237 res
+= CallStrftime(_T("%Z"), &tmTimeOnly
);
2241 // is it the format width?
2243 while ( *p
== _T('-') || *p
== _T('+') ||
2244 *p
== _T(' ') || wxIsdigit(*p
) )
2249 if ( !fmt
.IsEmpty() )
2251 // we've only got the flags and width so far in fmt
2252 fmt
.Prepend(_T('%'));
2253 fmt
.Append(_T('d'));
2260 // no, it wasn't the width
2261 wxFAIL_MSG(_T("unknown format specificator"));
2263 // fall through and just copy it nevertheless
2265 case _T('%'): // a percent sign
2269 case 0: // the end of string
2270 wxFAIL_MSG(_T("missing format at the end of string"));
2272 // just put the '%' which was the last char in format
2282 // this function parses a string in (strict) RFC 822 format: see the section 5
2283 // of the RFC for the detailed description, but briefly it's something of the
2284 // form "Sat, 18 Dec 1999 00:48:30 +0100"
2286 // this function is "strict" by design - it must reject anything except true
2287 // RFC822 time specs.
2289 // TODO a great candidate for using reg exps
2290 const wxChar
*wxDateTime::ParseRfc822Date(const wxChar
* date
)
2292 wxCHECK_MSG( date
, (wxChar
*)NULL
, _T("NULL pointer in wxDateTime::Parse") );
2294 const wxChar
*p
= date
;
2295 const wxChar
*comma
= wxStrchr(p
, _T(','));
2298 // the part before comma is the weekday
2300 // skip it for now - we don't use but might check that it really
2301 // corresponds to the specfied date
2304 if ( *p
!= _T(' ') )
2306 wxLogDebug(_T("no space after weekday in RFC822 time spec"));
2308 return (wxChar
*)NULL
;
2314 // the following 1 or 2 digits are the day number
2315 if ( !wxIsdigit(*p
) )
2317 wxLogDebug(_T("day number expected in RFC822 time spec, none found"));
2319 return (wxChar
*)NULL
;
2322 wxDateTime_t day
= *p
++ - _T('0');
2323 if ( wxIsdigit(*p
) )
2326 day
+= *p
++ - _T('0');
2329 if ( *p
++ != _T(' ') )
2331 return (wxChar
*)NULL
;
2334 // the following 3 letters specify the month
2335 wxString
monName(p
, 3);
2337 if ( monName
== _T("Jan") )
2339 else if ( monName
== _T("Feb") )
2341 else if ( monName
== _T("Mar") )
2343 else if ( monName
== _T("Apr") )
2345 else if ( monName
== _T("May") )
2347 else if ( monName
== _T("Jun") )
2349 else if ( monName
== _T("Jul") )
2351 else if ( monName
== _T("Aug") )
2353 else if ( monName
== _T("Sep") )
2355 else if ( monName
== _T("Oct") )
2357 else if ( monName
== _T("Nov") )
2359 else if ( monName
== _T("Dec") )
2363 wxLogDebug(_T("Invalid RFC 822 month name '%s'"), monName
.c_str());
2365 return (wxChar
*)NULL
;
2370 if ( *p
++ != _T(' ') )
2372 return (wxChar
*)NULL
;
2376 if ( !wxIsdigit(*p
) )
2379 return (wxChar
*)NULL
;
2382 int year
= *p
++ - _T('0');
2384 if ( !wxIsdigit(*p
) )
2386 // should have at least 2 digits in the year
2387 return (wxChar
*)NULL
;
2391 year
+= *p
++ - _T('0');
2393 // is it a 2 digit year (as per original RFC 822) or a 4 digit one?
2394 if ( wxIsdigit(*p
) )
2397 year
+= *p
++ - _T('0');
2399 if ( !wxIsdigit(*p
) )
2401 // no 3 digit years please
2402 return (wxChar
*)NULL
;
2406 year
+= *p
++ - _T('0');
2409 if ( *p
++ != _T(' ') )
2411 return (wxChar
*)NULL
;
2414 // time is in the format hh:mm:ss and seconds are optional
2415 if ( !wxIsdigit(*p
) )
2417 return (wxChar
*)NULL
;
2420 wxDateTime_t hour
= *p
++ - _T('0');
2422 if ( !wxIsdigit(*p
) )
2424 return (wxChar
*)NULL
;
2428 hour
+= *p
++ - _T('0');
2430 if ( *p
++ != _T(':') )
2432 return (wxChar
*)NULL
;
2435 if ( !wxIsdigit(*p
) )
2437 return (wxChar
*)NULL
;
2440 wxDateTime_t min
= *p
++ - _T('0');
2442 if ( !wxIsdigit(*p
) )
2444 return (wxChar
*)NULL
;
2448 min
+= *p
++ - _T('0');
2450 wxDateTime_t sec
= 0;
2451 if ( *p
++ == _T(':') )
2453 if ( !wxIsdigit(*p
) )
2455 return (wxChar
*)NULL
;
2458 sec
= *p
++ - _T('0');
2460 if ( !wxIsdigit(*p
) )
2462 return (wxChar
*)NULL
;
2466 sec
+= *p
++ - _T('0');
2469 if ( *p
++ != _T(' ') )
2471 return (wxChar
*)NULL
;
2474 // and now the interesting part: the timezone
2476 if ( *p
== _T('-') || *p
== _T('+') )
2478 // the explicit offset given: it has the form of hhmm
2479 bool plus
= *p
++ == _T('+');
2481 if ( !wxIsdigit(*p
) || !wxIsdigit(*(p
+ 1)) )
2483 return (wxChar
*)NULL
;
2487 offset
= 60*(10*(*p
- _T('0')) + (*(p
+ 1) - _T('0')));
2491 if ( !wxIsdigit(*p
) || !wxIsdigit(*(p
+ 1)) )
2493 return (wxChar
*)NULL
;
2497 offset
+= 10*(*p
- _T('0')) + (*(p
+ 1) - _T('0'));
2508 // the symbolic timezone given: may be either military timezone or one
2509 // of standard abbreviations
2512 // military: Z = UTC, J unused, A = -1, ..., Y = +12
2513 static const int offsets
[26] =
2515 //A B C D E F G H I J K L M
2516 -1, -2, -3, -4, -5, -6, -7, -8, -9, 0, -10, -11, -12,
2517 //N O P R Q S T U V W Z Y Z
2518 +1, +2, +3, +4, +5, +6, +7, +8, +9, +10, +11, +12, 0
2521 if ( *p
< _T('A') || *p
> _T('Z') || *p
== _T('J') )
2523 wxLogDebug(_T("Invalid militaty timezone '%c'"), *p
);
2525 return (wxChar
*)NULL
;
2528 offset
= offsets
[*p
++ - _T('A')];
2534 if ( tz
== _T("UT") || tz
== _T("UTC") || tz
== _T("GMT") )
2536 else if ( tz
== _T("AST") )
2537 offset
= AST
- GMT0
;
2538 else if ( tz
== _T("ADT") )
2539 offset
= ADT
- GMT0
;
2540 else if ( tz
== _T("EST") )
2541 offset
= EST
- GMT0
;
2542 else if ( tz
== _T("EDT") )
2543 offset
= EDT
- GMT0
;
2544 else if ( tz
== _T("CST") )
2545 offset
= CST
- GMT0
;
2546 else if ( tz
== _T("CDT") )
2547 offset
= CDT
- GMT0
;
2548 else if ( tz
== _T("MST") )
2549 offset
= MST
- GMT0
;
2550 else if ( tz
== _T("MDT") )
2551 offset
= MDT
- GMT0
;
2552 else if ( tz
== _T("PST") )
2553 offset
= PST
- GMT0
;
2554 else if ( tz
== _T("PDT") )
2555 offset
= PDT
- GMT0
;
2558 wxLogDebug(_T("Unknown RFC 822 timezone '%s'"), p
);
2560 return (wxChar
*)NULL
;
2570 // the spec was correct
2571 Set(day
, mon
, year
, hour
, min
, sec
);
2572 MakeTimezone((wxDateTime_t
)(60*offset
));
2577 const wxChar
*wxDateTime::ParseFormat(const wxChar
*date
,
2578 const wxChar
*format
,
2579 const wxDateTime
& dateDef
)
2581 wxCHECK_MSG( date
&& format
, (wxChar
*)NULL
,
2582 _T("NULL pointer in wxDateTime::ParseFormat()") );
2587 // what fields have we found?
2588 bool haveWDay
= FALSE
,
2597 bool hourIsIn12hFormat
= FALSE
, // or in 24h one?
2598 isPM
= FALSE
; // AM by default
2600 // and the value of the items we have (init them to get rid of warnings)
2601 wxDateTime_t sec
= 0,
2604 WeekDay wday
= Inv_WeekDay
;
2605 wxDateTime_t yday
= 0,
2607 wxDateTime::Month mon
= Inv_Month
;
2610 const wxChar
*input
= date
;
2611 for ( const wxChar
*fmt
= format
; *fmt
; fmt
++ )
2613 if ( *fmt
!= _T('%') )
2615 if ( wxIsspace(*fmt
) )
2617 // a white space in the format string matches 0 or more white
2618 // spaces in the input
2619 while ( wxIsspace(*input
) )
2626 // any other character (not whitespace, not '%') must be
2627 // matched by itself in the input
2628 if ( *input
++ != *fmt
)
2631 return (wxChar
*)NULL
;
2635 // done with this format char
2639 // start of a format specification
2641 // parse the optional width
2643 while ( isdigit(*++fmt
) )
2646 width
+= *fmt
- _T('0');
2649 // the default widths for the various fields
2654 case _T('Y'): // year has 4 digits
2658 case _T('j'): // day of year has 3 digits
2659 case _T('l'): // milliseconds have 3 digits
2663 case _T('w'): // week day as number has only one
2668 // default for all other fields
2673 // then the format itself
2676 case _T('a'): // a weekday name
2679 int flag
= *fmt
== _T('a') ? Name_Abbr
: Name_Full
;
2680 wday
= GetWeekDayFromName(GetAlphaToken(input
), flag
);
2681 if ( wday
== Inv_WeekDay
)
2684 return (wxChar
*)NULL
;
2690 case _T('b'): // a month name
2693 int flag
= *fmt
== _T('b') ? Name_Abbr
: Name_Full
;
2694 mon
= GetMonthFromName(GetAlphaToken(input
), flag
);
2695 if ( mon
== Inv_Month
)
2698 return (wxChar
*)NULL
;
2704 case _T('c'): // locale default date and time representation
2708 // this is the format which corresponds to ctime() output
2709 // and strptime("%c") should parse it, so try it first
2710 static const wxChar
*fmtCtime
= _T("%a %b %d %H:%M:%S %Y");
2712 const wxChar
*result
= dt
.ParseFormat(input
, fmtCtime
);
2715 result
= dt
.ParseFormat(input
, _T("%x %X"));
2720 result
= dt
.ParseFormat(input
, _T("%X %x"));
2725 // we've tried everything and still no match
2726 return (wxChar
*)NULL
;
2731 haveDay
= haveMon
= haveYear
=
2732 haveHour
= haveMin
= haveSec
= TRUE
;
2746 case _T('d'): // day of a month (01-31)
2747 if ( !GetNumericToken(width
, input
, &num
) ||
2748 (num
> 31) || (num
< 1) )
2751 return (wxChar
*)NULL
;
2754 // we can't check whether the day range is correct yet, will
2755 // do it later - assume ok for now
2757 mday
= (wxDateTime_t
)num
;
2760 case _T('H'): // hour in 24h format (00-23)
2761 if ( !GetNumericToken(width
, input
, &num
) || (num
> 23) )
2764 return (wxChar
*)NULL
;
2768 hour
= (wxDateTime_t
)num
;
2771 case _T('I'): // hour in 12h format (01-12)
2772 if ( !GetNumericToken(width
, input
, &num
) || !num
|| (num
> 12) )
2775 return (wxChar
*)NULL
;
2779 hourIsIn12hFormat
= TRUE
;
2780 hour
= (wxDateTime_t
)(num
% 12); // 12 should be 0
2783 case _T('j'): // day of the year
2784 if ( !GetNumericToken(width
, input
, &num
) || !num
|| (num
> 366) )
2787 return (wxChar
*)NULL
;
2791 yday
= (wxDateTime_t
)num
;
2794 case _T('m'): // month as a number (01-12)
2795 if ( !GetNumericToken(width
, input
, &num
) || !num
|| (num
> 12) )
2798 return (wxChar
*)NULL
;
2802 mon
= (Month
)(num
- 1);
2805 case _T('M'): // minute as a decimal number (00-59)
2806 if ( !GetNumericToken(width
, input
, &num
) || (num
> 59) )
2809 return (wxChar
*)NULL
;
2813 min
= (wxDateTime_t
)num
;
2816 case _T('p'): // AM or PM string
2818 wxString am
, pm
, token
= GetAlphaToken(input
);
2820 GetAmPmStrings(&am
, &pm
);
2821 if ( token
.CmpNoCase(pm
) == 0 )
2825 else if ( token
.CmpNoCase(am
) != 0 )
2828 return (wxChar
*)NULL
;
2833 case _T('r'): // time as %I:%M:%S %p
2836 input
= dt
.ParseFormat(input
, _T("%I:%M:%S %p"));
2840 return (wxChar
*)NULL
;
2843 haveHour
= haveMin
= haveSec
= TRUE
;
2852 case _T('R'): // time as %H:%M
2855 input
= dt
.ParseFormat(input
, _T("%H:%M"));
2859 return (wxChar
*)NULL
;
2862 haveHour
= haveMin
= TRUE
;
2869 case _T('S'): // second as a decimal number (00-61)
2870 if ( !GetNumericToken(width
, input
, &num
) || (num
> 61) )
2873 return (wxChar
*)NULL
;
2877 sec
= (wxDateTime_t
)num
;
2880 case _T('T'): // time as %H:%M:%S
2883 input
= dt
.ParseFormat(input
, _T("%H:%M:%S"));
2887 return (wxChar
*)NULL
;
2890 haveHour
= haveMin
= haveSec
= TRUE
;
2899 case _T('w'): // weekday as a number (0-6), Sunday = 0
2900 if ( !GetNumericToken(width
, input
, &num
) || (wday
> 6) )
2903 return (wxChar
*)NULL
;
2907 wday
= (WeekDay
)num
;
2910 case _T('x'): // locale default date representation
2911 #ifdef HAVE_STRPTIME
2912 // try using strptime() - it may fail even if the input is
2913 // correct but the date is out of range, so we will fall back
2914 // to our generic code anyhow (FIXME !Unicode friendly)
2917 const wxChar
*result
= strptime(input
, "%x", &tm
);
2922 haveDay
= haveMon
= haveYear
= TRUE
;
2924 year
= 1900 + tm
.tm_year
;
2925 mon
= (Month
)tm
.tm_mon
;
2931 #endif // HAVE_STRPTIME
2933 // TODO query the LOCALE_IDATE setting under Win32
2937 wxString fmtDate
, fmtDateAlt
;
2938 if ( IsWestEuropeanCountry(GetCountry()) ||
2939 GetCountry() == Russia
)
2941 fmtDate
= _T("%d/%m/%y");
2942 fmtDateAlt
= _T("%m/%d/%y");
2946 fmtDate
= _T("%m/%d/%y");
2947 fmtDateAlt
= _T("%d/%m/%y");
2950 const wxChar
*result
= dt
.ParseFormat(input
, fmtDate
);
2954 // ok, be nice and try another one
2955 result
= dt
.ParseFormat(input
, fmtDateAlt
);
2961 return (wxChar
*)NULL
;
2966 haveDay
= haveMon
= haveYear
= TRUE
;
2977 case _T('X'): // locale default time representation
2978 #ifdef HAVE_STRPTIME
2980 // use strptime() to do it for us (FIXME !Unicode friendly)
2982 input
= strptime(input
, "%X", &tm
);
2985 return (wxChar
*)NULL
;
2988 haveHour
= haveMin
= haveSec
= TRUE
;
2994 #else // !HAVE_STRPTIME
2995 // TODO under Win32 we can query the LOCALE_ITIME system
2996 // setting which says whether the default time format is
2999 // try to parse what follows as "%H:%M:%S" and, if this
3000 // fails, as "%I:%M:%S %p" - this should catch the most
3004 const wxChar
*result
= dt
.ParseFormat(input
, _T("%T"));
3007 result
= dt
.ParseFormat(input
, _T("%r"));
3013 return (wxChar
*)NULL
;
3016 haveHour
= haveMin
= haveSec
= TRUE
;
3025 #endif // HAVE_STRPTIME/!HAVE_STRPTIME
3028 case _T('y'): // year without century (00-99)
3029 if ( !GetNumericToken(width
, input
, &num
) || (num
> 99) )
3032 return (wxChar
*)NULL
;
3037 // TODO should have an option for roll over date instead of
3038 // hard coding it here
3039 year
= (num
> 30 ? 1900 : 2000) + (wxDateTime_t
)num
;
3042 case _T('Y'): // year with century
3043 if ( !GetNumericToken(width
, input
, &num
) )
3046 return (wxChar
*)NULL
;
3050 year
= (wxDateTime_t
)num
;
3053 case _T('Z'): // timezone name
3054 wxFAIL_MSG(_T("TODO"));
3057 case _T('%'): // a percent sign
3058 if ( *input
++ != _T('%') )
3061 return (wxChar
*)NULL
;
3065 case 0: // the end of string
3066 wxFAIL_MSG(_T("unexpected format end"));
3070 default: // not a known format spec
3071 return (wxChar
*)NULL
;
3075 // format matched, try to construct a date from what we have now
3077 if ( dateDef
.IsValid() )
3079 // take this date as default
3080 tmDef
= dateDef
.GetTm();
3082 else if ( IsValid() )
3084 // if this date is valid, don't change it
3089 // no default and this date is invalid - fall back to Today()
3090 tmDef
= Today().GetTm();
3101 // TODO we don't check here that the values are consistent, if both year
3102 // day and month/day were found, we just ignore the year day and we
3103 // also always ignore the week day
3104 if ( haveMon
&& haveDay
)
3106 if ( mday
> GetNumOfDaysInMonth(tm
.year
, mon
) )
3108 wxLogDebug(_T("bad month day in wxDateTime::ParseFormat"));
3110 return (wxChar
*)NULL
;
3116 else if ( haveYDay
)
3118 if ( yday
> GetNumberOfDays(tm
.year
) )
3120 wxLogDebug(_T("bad year day in wxDateTime::ParseFormat"));
3122 return (wxChar
*)NULL
;
3125 Tm tm2
= wxDateTime(1, Jan
, tm
.year
).SetToYearDay(yday
).GetTm();
3132 if ( haveHour
&& hourIsIn12hFormat
&& isPM
)
3134 // translate to 24hour format
3137 //else: either already in 24h format or no translation needed
3160 const wxChar
*wxDateTime::ParseDateTime(const wxChar
*date
)
3162 wxCHECK_MSG( date
, (wxChar
*)NULL
, _T("NULL pointer in wxDateTime::Parse") );
3164 // there is a public domain version of getdate.y, but it only works for
3166 wxFAIL_MSG(_T("TODO"));
3168 return (wxChar
*)NULL
;
3171 const wxChar
*wxDateTime::ParseDate(const wxChar
*date
)
3173 // this is a simplified version of ParseDateTime() which understands only
3174 // "today" (for wxDate compatibility) and digits only otherwise (and not
3175 // all esoteric constructions ParseDateTime() knows about)
3177 wxCHECK_MSG( date
, (wxChar
*)NULL
, _T("NULL pointer in wxDateTime::Parse") );
3179 const wxChar
*p
= date
;
3180 while ( wxIsspace(*p
) )
3183 // some special cases
3187 int dayDiffFromToday
;
3190 { wxTRANSLATE("today"), 0 },
3191 { wxTRANSLATE("yesterday"), -1 },
3192 { wxTRANSLATE("tomorrow"), 1 },
3195 for ( size_t n
= 0; n
< WXSIZEOF(literalDates
); n
++ )
3197 wxString date
= wxGetTranslation(literalDates
[n
].str
);
3198 size_t len
= date
.length();
3199 if ( wxStrlen(p
) >= len
&& (wxString(p
, len
).CmpNoCase(date
) == 0) )
3201 // nothing can follow this, so stop here
3204 int dayDiffFromToday
= literalDates
[n
].dayDiffFromToday
;
3206 if ( dayDiffFromToday
)
3208 *this += wxDateSpan::Days(dayDiffFromToday
);
3215 // We try to guess what we have here: for each new (numeric) token, we
3216 // determine if it can be a month, day or a year. Of course, there is an
3217 // ambiguity as some numbers may be days as well as months, so we also
3218 // have the ability to back track.
3221 bool haveDay
= FALSE
, // the months day?
3222 haveWDay
= FALSE
, // the day of week?
3223 haveMon
= FALSE
, // the month?
3224 haveYear
= FALSE
; // the year?
3226 // and the value of the items we have (init them to get rid of warnings)
3227 WeekDay wday
= Inv_WeekDay
;
3228 wxDateTime_t day
= 0;
3229 wxDateTime::Month mon
= Inv_Month
;
3232 // tokenize the string
3234 static const wxChar
*dateDelimiters
= _T(".,/-\t\n ");
3235 wxStringTokenizer
tok(p
, dateDelimiters
);
3236 while ( tok
.HasMoreTokens() )
3238 wxString token
= tok
.GetNextToken();
3244 if ( token
.ToULong(&val
) )
3246 // guess what this number is
3252 if ( !haveMon
&& val
> 0 && val
<= 12 )
3254 // assume it is month
3257 else // not the month
3259 wxDateTime_t maxDays
= haveMon
3260 ? GetNumOfDaysInMonth(haveYear
? year
: Inv_Year
, mon
)
3264 if ( (val
== 0) || (val
> (unsigned long)maxDays
) ) // cast to shut up compiler warning in BCC
3281 year
= (wxDateTime_t
)val
;
3290 day
= (wxDateTime_t
)val
;
3296 mon
= (Month
)(val
- 1);
3299 else // not a number
3301 // be careful not to overwrite the current mon value
3302 Month mon2
= GetMonthFromName(token
, Name_Full
| Name_Abbr
);
3303 if ( mon2
!= Inv_Month
)
3308 // but we already have a month - maybe we guessed wrong?
3311 // no need to check in month range as always < 12, but
3312 // the days are counted from 1 unlike the months
3313 day
= (wxDateTime_t
)mon
+ 1;
3318 // could possible be the year (doesn't the year come
3319 // before the month in the japanese format?) (FIXME)
3328 else // not a valid month name
3330 wday
= GetWeekDayFromName(token
, Name_Full
| Name_Abbr
);
3331 if ( wday
!= Inv_WeekDay
)
3341 else // not a valid weekday name
3344 static const wxChar
*ordinals
[] =
3346 wxTRANSLATE("first"),
3347 wxTRANSLATE("second"),
3348 wxTRANSLATE("third"),
3349 wxTRANSLATE("fourth"),
3350 wxTRANSLATE("fifth"),
3351 wxTRANSLATE("sixth"),
3352 wxTRANSLATE("seventh"),
3353 wxTRANSLATE("eighth"),
3354 wxTRANSLATE("ninth"),
3355 wxTRANSLATE("tenth"),
3356 wxTRANSLATE("eleventh"),
3357 wxTRANSLATE("twelfth"),
3358 wxTRANSLATE("thirteenth"),
3359 wxTRANSLATE("fourteenth"),
3360 wxTRANSLATE("fifteenth"),
3361 wxTRANSLATE("sixteenth"),
3362 wxTRANSLATE("seventeenth"),
3363 wxTRANSLATE("eighteenth"),
3364 wxTRANSLATE("nineteenth"),
3365 wxTRANSLATE("twentieth"),
3366 // that's enough - otherwise we'd have problems with
3367 // composite (or not) ordinals
3371 for ( n
= 0; n
< WXSIZEOF(ordinals
); n
++ )
3373 if ( token
.CmpNoCase(ordinals
[n
]) == 0 )
3379 if ( n
== WXSIZEOF(ordinals
) )
3381 // stop here - something unknown
3388 // don't try anything here (as in case of numeric day
3389 // above) - the symbolic day spec should always
3390 // precede the month/year
3396 day
= (wxDateTime_t
)(n
+ 1);
3401 nPosCur
= tok
.GetPosition();
3404 // either no more tokens or the scan was stopped by something we couldn't
3405 // parse - in any case, see if we can construct a date from what we have
3406 if ( !haveDay
&& !haveWDay
)
3408 wxLogDebug(_T("ParseDate: no day, no weekday hence no date."));
3410 return (wxChar
*)NULL
;
3413 if ( haveWDay
&& (haveMon
|| haveYear
|| haveDay
) &&
3414 !(haveDay
&& haveMon
&& haveYear
) )
3416 // without adjectives (which we don't support here) the week day only
3417 // makes sense completely separately or with the full date
3418 // specification (what would "Wed 1999" mean?)
3419 return (wxChar
*)NULL
;
3422 if ( !haveWDay
&& haveYear
&& !(haveDay
&& haveMon
) )
3424 // may be we have month and day instead of day and year?
3425 if ( haveDay
&& !haveMon
)
3429 // exchange day and month
3430 mon
= (wxDateTime::Month
)(day
- 1);
3432 // we're in the current year then
3434 (unsigned)year
<= GetNumOfDaysInMonth(Inv_Year
, mon
) )
3441 //else: no, can't exchange, leave haveMon == FALSE
3447 // if we give the year, month and day must be given too
3448 wxLogDebug(_T("ParseDate: day and month should be specified if year is."));
3450 return (wxChar
*)NULL
;
3456 mon
= GetCurrentMonth();
3461 year
= GetCurrentYear();
3466 Set(day
, mon
, year
);
3470 // check that it is really the same
3471 if ( GetWeekDay() != wday
)
3473 // inconsistency detected
3474 wxLogDebug(_T("ParseDate: inconsistent day/weekday."));
3476 return (wxChar
*)NULL
;
3484 SetToWeekDayInSameWeek(wday
);
3487 // return the pointer to the first unparsed char
3489 if ( nPosCur
&& wxStrchr(dateDelimiters
, *(p
- 1)) )
3491 // if we couldn't parse the token after the delimiter, put back the
3492 // delimiter as well
3499 const wxChar
*wxDateTime::ParseTime(const wxChar
*time
)
3501 wxCHECK_MSG( time
, (wxChar
*)NULL
, _T("NULL pointer in wxDateTime::Parse") );
3503 // first try some extra things
3510 { wxTRANSLATE("noon"), 12 },
3511 { wxTRANSLATE("midnight"), 00 },
3515 for ( size_t n
= 0; n
< WXSIZEOF(stdTimes
); n
++ )
3517 wxString timeString
= wxGetTranslation(stdTimes
[n
].name
);
3518 size_t len
= timeString
.length();
3519 if ( timeString
.CmpNoCase(wxString(time
, len
)) == 0 )
3521 Set(stdTimes
[n
].hour
, 0, 0);
3527 // try all time formats we may think about in the order from longest to
3530 // 12hour with AM/PM?
3531 const wxChar
*result
= ParseFormat(time
, _T("%I:%M:%S %p"));
3535 // normally, it's the same, but why not try it?
3536 result
= ParseFormat(time
, _T("%H:%M:%S"));
3541 // 12hour with AM/PM but without seconds?
3542 result
= ParseFormat(time
, _T("%I:%M %p"));
3548 result
= ParseFormat(time
, _T("%H:%M"));
3553 // just the hour and AM/PM?
3554 result
= ParseFormat(time
, _T("%I %p"));
3560 result
= ParseFormat(time
, _T("%H"));
3565 // parse the standard format: normally it is one of the formats above
3566 // but it may be set to something completely different by the user
3567 result
= ParseFormat(time
, _T("%X"));
3570 // TODO: parse timezones
3575 // ----------------------------------------------------------------------------
3576 // Workdays and holidays support
3577 // ----------------------------------------------------------------------------
3579 bool wxDateTime::IsWorkDay(Country
WXUNUSED(country
)) const
3581 return !wxDateTimeHolidayAuthority::IsHoliday(*this);
3584 // ============================================================================
3586 // ============================================================================
3588 // this enum is only used in wxTimeSpan::Format() below but we can't declare
3589 // it locally to the method as it provokes an internal compiler error in egcs
3590 // 2.91.60 when building with -O2
3601 // not all strftime(3) format specifiers make sense here because, for example,
3602 // a time span doesn't have a year nor a timezone
3604 // Here are the ones which are supported (all of them are supported by strftime
3606 // %H hour in 24 hour format
3607 // %M minute (00 - 59)
3608 // %S second (00 - 59)
3611 // Also, for MFC CTimeSpan compatibility, we support
3612 // %D number of days
3614 // And, to be better than MFC :-), we also have
3615 // %E number of wEeks
3616 // %l milliseconds (000 - 999)
3617 wxString
wxTimeSpan::Format(const wxChar
*format
) const
3619 wxCHECK_MSG( format
, _T(""), _T("NULL format in wxTimeSpan::Format") );
3622 str
.Alloc(wxStrlen(format
));
3624 // Suppose we have wxTimeSpan ts(1 /* hour */, 2 /* min */, 3 /* sec */)
3626 // Then, of course, ts.Format("%H:%M:%S") must return "01:02:03", but the
3627 // question is what should ts.Format("%S") do? The code here returns "3273"
3628 // in this case (i.e. the total number of seconds, not just seconds % 60)
3629 // because, for me, this call means "give me entire time interval in
3630 // seconds" and not "give me the seconds part of the time interval"
3632 // If we agree that it should behave like this, it is clear that the
3633 // interpretation of each format specifier depends on the presence of the
3634 // other format specs in the string: if there was "%H" before "%M", we
3635 // should use GetMinutes() % 60, otherwise just GetMinutes() &c
3637 // we remember the most important unit found so far
3638 TimeSpanPart partBiggest
= Part_MSec
;
3640 for ( const wxChar
*pch
= format
; *pch
; pch
++ )
3644 if ( ch
== _T('%') )
3646 // the start of the format specification of the printf() below
3647 wxString fmtPrefix
= _T('%');
3652 ch
= *++pch
; // get the format spec char
3656 wxFAIL_MSG( _T("invalid format character") );
3662 // skip the part below switch
3667 if ( partBiggest
< Part_Day
)
3673 partBiggest
= Part_Day
;
3678 partBiggest
= Part_Week
;
3684 if ( partBiggest
< Part_Hour
)
3690 partBiggest
= Part_Hour
;
3693 fmtPrefix
+= _T("02");
3697 n
= GetMilliseconds().ToLong();
3698 if ( partBiggest
< Part_MSec
)
3702 //else: no need to reset partBiggest to Part_MSec, it is
3703 // the least significant one anyhow
3705 fmtPrefix
+= _T("03");
3710 if ( partBiggest
< Part_Min
)
3716 partBiggest
= Part_Min
;
3719 fmtPrefix
+= _T("02");
3723 n
= GetSeconds().ToLong();
3724 if ( partBiggest
< Part_Sec
)
3730 partBiggest
= Part_Sec
;
3733 fmtPrefix
+= _T("02");
3737 str
+= wxString::Format(fmtPrefix
+ _T("ld"), n
);
3741 // normal character, just copy
3749 // ============================================================================
3750 // wxDateTimeHolidayAuthority and related classes
3751 // ============================================================================
3753 #include "wx/arrimpl.cpp"
3755 WX_DEFINE_OBJARRAY(wxDateTimeArray
);
3757 static int wxCMPFUNC_CONV
3758 wxDateTimeCompareFunc(wxDateTime
**first
, wxDateTime
**second
)
3760 wxDateTime dt1
= **first
,
3763 return dt1
== dt2
? 0 : dt1
< dt2
? -1 : +1;
3766 // ----------------------------------------------------------------------------
3767 // wxDateTimeHolidayAuthority
3768 // ----------------------------------------------------------------------------
3770 wxHolidayAuthoritiesArray
wxDateTimeHolidayAuthority::ms_authorities
;
3773 bool wxDateTimeHolidayAuthority::IsHoliday(const wxDateTime
& dt
)
3775 size_t count
= ms_authorities
.GetCount();
3776 for ( size_t n
= 0; n
< count
; n
++ )
3778 if ( ms_authorities
[n
]->DoIsHoliday(dt
) )
3789 wxDateTimeHolidayAuthority::GetHolidaysInRange(const wxDateTime
& dtStart
,
3790 const wxDateTime
& dtEnd
,
3791 wxDateTimeArray
& holidays
)
3793 wxDateTimeArray hol
;
3797 size_t count
= ms_authorities
.GetCount();
3798 for ( size_t nAuth
= 0; nAuth
< count
; nAuth
++ )
3800 ms_authorities
[nAuth
]->DoGetHolidaysInRange(dtStart
, dtEnd
, hol
);
3802 WX_APPEND_ARRAY(holidays
, hol
);
3805 holidays
.Sort(wxDateTimeCompareFunc
);
3807 return holidays
.GetCount();
3811 void wxDateTimeHolidayAuthority::ClearAllAuthorities()
3813 WX_CLEAR_ARRAY(ms_authorities
);
3817 void wxDateTimeHolidayAuthority::AddAuthority(wxDateTimeHolidayAuthority
*auth
)
3819 ms_authorities
.Add(auth
);
3822 // ----------------------------------------------------------------------------
3823 // wxDateTimeWorkDays
3824 // ----------------------------------------------------------------------------
3826 bool wxDateTimeWorkDays::DoIsHoliday(const wxDateTime
& dt
) const
3828 wxDateTime::WeekDay wd
= dt
.GetWeekDay();
3830 return (wd
== wxDateTime::Sun
) || (wd
== wxDateTime::Sat
);
3833 size_t wxDateTimeWorkDays::DoGetHolidaysInRange(const wxDateTime
& dtStart
,
3834 const wxDateTime
& dtEnd
,
3835 wxDateTimeArray
& holidays
) const
3837 if ( dtStart
> dtEnd
)
3839 wxFAIL_MSG( _T("invalid date range in GetHolidaysInRange") );
3846 // instead of checking all days, start with the first Sat after dtStart and
3847 // end with the last Sun before dtEnd
3848 wxDateTime dtSatFirst
= dtStart
.GetNextWeekDay(wxDateTime::Sat
),
3849 dtSatLast
= dtEnd
.GetPrevWeekDay(wxDateTime::Sat
),
3850 dtSunFirst
= dtStart
.GetNextWeekDay(wxDateTime::Sun
),
3851 dtSunLast
= dtEnd
.GetPrevWeekDay(wxDateTime::Sun
),
3854 for ( dt
= dtSatFirst
; dt
<= dtSatLast
; dt
+= wxDateSpan::Week() )
3859 for ( dt
= dtSunFirst
; dt
<= dtSunLast
; dt
+= wxDateSpan::Week() )
3864 return holidays
.GetCount();
3867 #endif // wxUSE_DATETIME