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"
67 #include "wx/string.h"
72 #include "wx/thread.h"
73 #include "wx/tokenzr.h"
74 #include "wx/module.h"
76 #define wxDEFINE_TIME_CONSTANTS // before including datetime.h
80 #include "wx/datetime.h"
81 #include "wx/timer.h" // for wxGetLocalTimeMillis()
83 // ----------------------------------------------------------------------------
84 // conditional compilation
85 // ----------------------------------------------------------------------------
87 #if defined(HAVE_STRPTIME) && defined(__LINUX__)
88 // glibc 2.0.7 strptime() is broken - the following snippet causes it to
89 // crash (instead of just failing):
91 // strncpy(buf, "Tue Dec 21 20:25:40 1999", 128);
92 // strptime(buf, "%x", &tm);
96 #endif // broken strptime()
99 #if defined(__BORLANDC__) || defined(__MINGW32__) || defined(__VISAGECPP__)
100 #define WX_TIMEZONE _timezone
101 #elif defined(__MWERKS__)
102 long wxmw_timezone
= 28800;
103 #define WX_TIMEZONE wxmw_timezone;
104 #else // unknown platform - try timezone
105 #define WX_TIMEZONE timezone
107 #endif // !WX_TIMEZONE
109 // ----------------------------------------------------------------------------
111 // ----------------------------------------------------------------------------
113 // debugging helper: just a convenient replacement of wxCHECK()
114 #define wxDATETIME_CHECK(expr, msg) \
118 *this = wxInvalidDateTime; \
122 // ----------------------------------------------------------------------------
124 // ----------------------------------------------------------------------------
126 class wxDateTimeHolidaysModule
: public wxModule
129 virtual bool OnInit()
131 wxDateTimeHolidayAuthority::AddAuthority(new wxDateTimeWorkDays
);
136 virtual void OnExit()
138 wxDateTimeHolidayAuthority::ClearAllAuthorities();
139 wxDateTimeHolidayAuthority::ms_authorities
.Clear();
143 DECLARE_DYNAMIC_CLASS(wxDateTimeHolidaysModule
)
146 IMPLEMENT_DYNAMIC_CLASS(wxDateTimeHolidaysModule
, wxModule
)
148 // ----------------------------------------------------------------------------
150 // ----------------------------------------------------------------------------
153 static const int MONTHS_IN_YEAR
= 12;
155 static const int SEC_PER_MIN
= 60;
157 static const int MIN_PER_HOUR
= 60;
159 static const int HOURS_PER_DAY
= 24;
161 static const long SECONDS_PER_DAY
= 86400l;
163 static const int DAYS_PER_WEEK
= 7;
165 static const long MILLISECONDS_PER_DAY
= 86400000l;
167 // this is the integral part of JDN of the midnight of Jan 1, 1970
168 // (i.e. JDN(Jan 1, 1970) = 2440587.5)
169 static const long EPOCH_JDN
= 2440587l;
171 // the date of JDN -0.5 (as we don't work with fractional parts, this is the
172 // reference date for us) is Nov 24, 4714BC
173 static const int JDN_0_YEAR
= -4713;
174 static const int JDN_0_MONTH
= wxDateTime::Nov
;
175 static const int JDN_0_DAY
= 24;
177 // the constants used for JDN calculations
178 static const long JDN_OFFSET
= 32046l;
179 static const long DAYS_PER_5_MONTHS
= 153l;
180 static const long DAYS_PER_4_YEARS
= 1461l;
181 static const long DAYS_PER_400_YEARS
= 146097l;
183 // this array contains the cumulated number of days in all previous months for
184 // normal and leap years
185 static const wxDateTime::wxDateTime_t gs_cumulatedDays
[2][MONTHS_IN_YEAR
] =
187 { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 },
188 { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 }
191 // ----------------------------------------------------------------------------
193 // ----------------------------------------------------------------------------
195 // in the fine tradition of ANSI C we use our equivalent of (time_t)-1 to
196 // indicate an invalid wxDateTime object
197 static const wxDateTime gs_dtDefault
= wxLongLong((long)ULONG_MAX
, ULONG_MAX
);
199 const wxDateTime
& wxDefaultDateTime
= gs_dtDefault
;
201 wxDateTime::Country
wxDateTime::ms_country
= wxDateTime::Country_Unknown
;
203 // ----------------------------------------------------------------------------
205 // ----------------------------------------------------------------------------
207 // a critical section is needed to protect GetTimeZone() static
208 // variable in MT case
210 static wxCriticalSection gs_critsectTimezone
;
211 #endif // wxUSE_THREADS
213 // ----------------------------------------------------------------------------
215 // ----------------------------------------------------------------------------
217 // debugger helper: shows what the date really is
219 extern const wxChar
*wxDumpDate(const wxDateTime
* dt
)
221 static wxChar buf
[128];
223 wxStrcpy(buf
, dt
->Format(_T("%Y-%m-%d (%a) %H:%M:%S")));
229 // get the number of days in the given month of the given year
231 wxDateTime::wxDateTime_t
GetNumOfDaysInMonth(int year
, wxDateTime::Month month
)
233 // the number of days in month in Julian/Gregorian calendar: the first line
234 // is for normal years, the second one is for the leap ones
235 static wxDateTime::wxDateTime_t daysInMonth
[2][MONTHS_IN_YEAR
] =
237 { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
238 { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
241 return daysInMonth
[wxDateTime::IsLeapYear(year
)][month
];
244 // ensure that the timezone variable is set by calling localtime
245 static int GetTimeZone()
247 // set to TRUE when the timezone is set
248 static bool s_timezoneSet
= FALSE
;
250 wxCRIT_SECT_LOCKER(lock
, gs_critsectTimezone
);
252 if ( !s_timezoneSet
)
254 // just call localtime() instead of figuring out whether this system
255 // supports tzset(), _tzset() or something else
259 s_timezoneSet
= TRUE
;
262 return (int)WX_TIMEZONE
;
265 // return the integral part of the JDN for the midnight of the given date (to
266 // get the real JDN you need to add 0.5, this is, in fact, JDN of the
267 // noon of the previous day)
268 static long GetTruncatedJDN(wxDateTime::wxDateTime_t day
,
269 wxDateTime::Month mon
,
272 // CREDIT: code below is by Scott E. Lee (but bugs are mine)
274 // check the date validity
276 (year
> JDN_0_YEAR
) ||
277 ((year
== JDN_0_YEAR
) && (mon
> JDN_0_MONTH
)) ||
278 ((year
== JDN_0_YEAR
) && (mon
== JDN_0_MONTH
) && (day
>= JDN_0_DAY
)),
279 _T("date out of range - can't convert to JDN")
282 // make the year positive to avoid problems with negative numbers division
285 // months are counted from March here
287 if ( mon
>= wxDateTime::Mar
)
297 // now we can simply add all the contributions together
298 return ((year
/ 100) * DAYS_PER_400_YEARS
) / 4
299 + ((year
% 100) * DAYS_PER_4_YEARS
) / 4
300 + (month
* DAYS_PER_5_MONTHS
+ 2) / 5
305 // this function is a wrapper around strftime(3)
306 static wxString
CallStrftime(const wxChar
*format
, const tm
* tm
)
309 if ( !wxStrftime(buf
, WXSIZEOF(buf
), format
, tm
) )
311 // buffer is too small?
312 wxFAIL_MSG(_T("strftime() failed"));
315 return wxString(buf
);
318 // if year and/or month have invalid values, replace them with the current ones
319 static void ReplaceDefaultYearMonthWithCurrent(int *year
,
320 wxDateTime::Month
*month
)
322 struct tm
*tmNow
= NULL
;
324 if ( *year
== wxDateTime::Inv_Year
)
326 tmNow
= wxDateTime::GetTmNow();
328 *year
= 1900 + tmNow
->tm_year
;
331 if ( *month
== wxDateTime::Inv_Month
)
334 tmNow
= wxDateTime::GetTmNow();
336 *month
= (wxDateTime::Month
)tmNow
->tm_mon
;
340 // fll the struct tm with default values
341 static void InitTm(struct tm
& tm
)
343 // struct tm may have etxra fields (undocumented and with unportable
344 // names) which, nevertheless, must be set to 0
345 memset(&tm
, 0, sizeof(struct tm
));
347 tm
.tm_mday
= 1; // mday 0 is invalid
348 tm
.tm_year
= 76; // any valid year
349 tm
.tm_isdst
= -1; // auto determine
355 // return the month if the string is a month name or Inv_Month otherwise
356 static wxDateTime::Month
GetMonthFromName(const wxString
& name
, int flags
)
358 wxDateTime::Month mon
;
359 for ( mon
= wxDateTime::Jan
; mon
< wxDateTime::Inv_Month
; wxNextMonth(mon
) )
361 // case-insensitive comparison either one of or with both abbreviated
363 if ( flags
& wxDateTime::Name_Full
)
365 if ( name
.CmpNoCase(wxDateTime::
366 GetMonthName(mon
, wxDateTime::Name_Full
)) == 0 )
372 if ( flags
& wxDateTime::Name_Abbr
)
374 if ( name
.CmpNoCase(wxDateTime::
375 GetMonthName(mon
, wxDateTime::Name_Abbr
)) == 0 )
385 // return the weekday if the string is a weekday name or Inv_WeekDay otherwise
386 static wxDateTime::WeekDay
GetWeekDayFromName(const wxString
& name
, int flags
)
388 wxDateTime::WeekDay wd
;
389 for ( wd
= wxDateTime::Sun
; wd
< wxDateTime::Inv_WeekDay
; wxNextWDay(wd
) )
391 // case-insensitive comparison either one of or with both abbreviated
393 if ( flags
& wxDateTime::Name_Full
)
395 if ( name
.CmpNoCase(wxDateTime::
396 GetWeekDayName(wd
, wxDateTime::Name_Full
)) == 0 )
402 if ( flags
& wxDateTime::Name_Abbr
)
404 if ( name
.CmpNoCase(wxDateTime::
405 GetWeekDayName(wd
, wxDateTime::Name_Abbr
)) == 0 )
415 // scans all digits (but no more than len) and returns the resulting number
416 static bool GetNumericToken(size_t len
, const wxChar
*& p
, unsigned long *number
)
420 while ( wxIsdigit(*p
) )
424 if ( len
&& ++n
> len
)
428 return !!s
&& s
.ToULong(number
);
431 // scans all alphabetic characters and returns the resulting string
432 static wxString
GetAlphaToken(const wxChar
*& p
)
435 while ( wxIsalpha(*p
) )
443 // ============================================================================
444 // implementation of wxDateTime
445 // ============================================================================
447 // ----------------------------------------------------------------------------
449 // ----------------------------------------------------------------------------
453 year
= (wxDateTime_t
)wxDateTime::Inv_Year
;
454 mon
= wxDateTime::Inv_Month
;
456 hour
= min
= sec
= msec
= 0;
457 wday
= wxDateTime::Inv_WeekDay
;
460 wxDateTime::Tm::Tm(const struct tm
& tm
, const TimeZone
& tz
)
468 mon
= (wxDateTime::Month
)tm
.tm_mon
;
469 year
= 1900 + tm
.tm_year
;
474 bool wxDateTime::Tm::IsValid() const
476 // we allow for the leap seconds, although we don't use them (yet)
477 return (year
!= wxDateTime::Inv_Year
) && (mon
!= wxDateTime::Inv_Month
) &&
478 (mday
<= GetNumOfDaysInMonth(year
, mon
)) &&
479 (hour
< 24) && (min
< 60) && (sec
< 62) && (msec
< 1000);
482 void wxDateTime::Tm::ComputeWeekDay()
484 // compute the week day from day/month/year: we use the dumbest algorithm
485 // possible: just compute our JDN and then use the (simple to derive)
486 // formula: weekday = (JDN + 1.5) % 7
487 wday
= (wxDateTime::WeekDay
)(GetTruncatedJDN(mday
, mon
, year
) + 2) % 7;
490 void wxDateTime::Tm::AddMonths(int monDiff
)
492 // normalize the months field
493 while ( monDiff
< -mon
)
497 monDiff
+= MONTHS_IN_YEAR
;
500 while ( monDiff
+ mon
>= MONTHS_IN_YEAR
)
504 monDiff
-= MONTHS_IN_YEAR
;
507 mon
= (wxDateTime::Month
)(mon
+ monDiff
);
509 wxASSERT_MSG( mon
>= 0 && mon
< MONTHS_IN_YEAR
, _T("logic error") );
511 // NB: we don't check here that the resulting date is valid, this function
512 // is private and the caller must check it if needed
515 void wxDateTime::Tm::AddDays(int dayDiff
)
517 // normalize the days field
518 while ( dayDiff
+ mday
< 1 )
522 dayDiff
+= GetNumOfDaysInMonth(year
, mon
);
526 while ( mday
> GetNumOfDaysInMonth(year
, mon
) )
528 mday
-= GetNumOfDaysInMonth(year
, mon
);
533 wxASSERT_MSG( mday
> 0 && mday
<= GetNumOfDaysInMonth(year
, mon
),
537 // ----------------------------------------------------------------------------
539 // ----------------------------------------------------------------------------
541 wxDateTime::TimeZone::TimeZone(wxDateTime::TZ tz
)
545 case wxDateTime::Local
:
546 // get the offset from C RTL: it returns the difference GMT-local
547 // while we want to have the offset _from_ GMT, hence the '-'
548 m_offset
= -GetTimeZone();
551 case wxDateTime::GMT_12
:
552 case wxDateTime::GMT_11
:
553 case wxDateTime::GMT_10
:
554 case wxDateTime::GMT_9
:
555 case wxDateTime::GMT_8
:
556 case wxDateTime::GMT_7
:
557 case wxDateTime::GMT_6
:
558 case wxDateTime::GMT_5
:
559 case wxDateTime::GMT_4
:
560 case wxDateTime::GMT_3
:
561 case wxDateTime::GMT_2
:
562 case wxDateTime::GMT_1
:
563 m_offset
= -3600*(wxDateTime::GMT0
- tz
);
566 case wxDateTime::GMT0
:
567 case wxDateTime::GMT1
:
568 case wxDateTime::GMT2
:
569 case wxDateTime::GMT3
:
570 case wxDateTime::GMT4
:
571 case wxDateTime::GMT5
:
572 case wxDateTime::GMT6
:
573 case wxDateTime::GMT7
:
574 case wxDateTime::GMT8
:
575 case wxDateTime::GMT9
:
576 case wxDateTime::GMT10
:
577 case wxDateTime::GMT11
:
578 case wxDateTime::GMT12
:
579 m_offset
= 3600*(tz
- wxDateTime::GMT0
);
582 case wxDateTime::A_CST
:
583 // Central Standard Time in use in Australia = UTC + 9.5
584 m_offset
= 60l*(9*60 + 30);
588 wxFAIL_MSG( _T("unknown time zone") );
592 // ----------------------------------------------------------------------------
594 // ----------------------------------------------------------------------------
597 bool wxDateTime::IsLeapYear(int year
, wxDateTime::Calendar cal
)
599 if ( year
== Inv_Year
)
600 year
= GetCurrentYear();
602 if ( cal
== Gregorian
)
604 // in Gregorian calendar leap years are those divisible by 4 except
605 // those divisible by 100 unless they're also divisible by 400
606 // (in some countries, like Russia and Greece, additional corrections
607 // exist, but they won't manifest themselves until 2700)
608 return (year
% 4 == 0) && ((year
% 100 != 0) || (year
% 400 == 0));
610 else if ( cal
== Julian
)
612 // in Julian calendar the rule is simpler
613 return year
% 4 == 0;
617 wxFAIL_MSG(_T("unknown calendar"));
624 int wxDateTime::GetCentury(int year
)
626 return year
> 0 ? year
/ 100 : year
/ 100 - 1;
630 int wxDateTime::ConvertYearToBC(int year
)
633 return year
> 0 ? year
: year
- 1;
637 int wxDateTime::GetCurrentYear(wxDateTime::Calendar cal
)
642 return Now().GetYear();
645 wxFAIL_MSG(_T("TODO"));
649 wxFAIL_MSG(_T("unsupported calendar"));
657 wxDateTime::Month
wxDateTime::GetCurrentMonth(wxDateTime::Calendar cal
)
662 return Now().GetMonth();
665 wxFAIL_MSG(_T("TODO"));
669 wxFAIL_MSG(_T("unsupported calendar"));
677 wxDateTime::wxDateTime_t
wxDateTime::GetNumberOfDays(int year
, Calendar cal
)
679 if ( year
== Inv_Year
)
681 // take the current year if none given
682 year
= GetCurrentYear();
689 return IsLeapYear(year
) ? 366 : 365;
692 wxFAIL_MSG(_T("unsupported calendar"));
700 wxDateTime::wxDateTime_t
wxDateTime::GetNumberOfDays(wxDateTime::Month month
,
702 wxDateTime::Calendar cal
)
704 wxCHECK_MSG( month
< MONTHS_IN_YEAR
, 0, _T("invalid month") );
706 if ( cal
== Gregorian
|| cal
== Julian
)
708 if ( year
== Inv_Year
)
710 // take the current year if none given
711 year
= GetCurrentYear();
714 return GetNumOfDaysInMonth(year
, month
);
718 wxFAIL_MSG(_T("unsupported calendar"));
725 wxString
wxDateTime::GetMonthName(wxDateTime::Month month
,
726 wxDateTime::NameFlags flags
)
728 wxCHECK_MSG( month
!= Inv_Month
, _T(""), _T("invalid month") );
730 // notice that we must set all the fields to avoid confusing libc (GNU one
731 // gets confused to a crash if we don't do this)
736 return CallStrftime(flags
== Name_Abbr
? _T("%b") : _T("%B"), &tm
);
740 wxString
wxDateTime::GetWeekDayName(wxDateTime::WeekDay wday
,
741 wxDateTime::NameFlags flags
)
743 wxCHECK_MSG( wday
!= Inv_WeekDay
, _T(""), _T("invalid weekday") );
745 // take some arbitrary Sunday
752 // and offset it by the number of days needed to get the correct wday
755 // call mktime() to normalize it...
758 // ... and call strftime()
759 return CallStrftime(flags
== Name_Abbr
? _T("%a") : _T("%A"), &tm
);
763 void wxDateTime::GetAmPmStrings(wxString
*am
, wxString
*pm
)
769 *am
= CallStrftime(_T("%p"), &tm
);
774 *pm
= CallStrftime(_T("%p"), &tm
);
778 // ----------------------------------------------------------------------------
779 // Country stuff: date calculations depend on the country (DST, work days,
780 // ...), so we need to know which rules to follow.
781 // ----------------------------------------------------------------------------
784 wxDateTime::Country
wxDateTime::GetCountry()
786 // TODO use LOCALE_ICOUNTRY setting under Win32
788 if ( ms_country
== Country_Unknown
)
790 // try to guess from the time zone name
791 time_t t
= time(NULL
);
792 struct tm
*tm
= localtime(&t
);
794 wxString tz
= CallStrftime(_T("%Z"), tm
);
795 if ( tz
== _T("WET") || tz
== _T("WEST") )
799 else if ( tz
== _T("CET") || tz
== _T("CEST") )
801 ms_country
= Country_EEC
;
803 else if ( tz
== _T("MSK") || tz
== _T("MSD") )
807 else if ( tz
== _T("AST") || tz
== _T("ADT") ||
808 tz
== _T("EST") || tz
== _T("EDT") ||
809 tz
== _T("CST") || tz
== _T("CDT") ||
810 tz
== _T("MST") || tz
== _T("MDT") ||
811 tz
== _T("PST") || tz
== _T("PDT") )
817 // well, choose a default one
826 void wxDateTime::SetCountry(wxDateTime::Country country
)
828 ms_country
= country
;
832 bool wxDateTime::IsWestEuropeanCountry(Country country
)
834 if ( country
== Country_Default
)
836 country
= GetCountry();
839 return (Country_WesternEurope_Start
<= country
) &&
840 (country
<= Country_WesternEurope_End
);
843 // ----------------------------------------------------------------------------
844 // DST calculations: we use 3 different rules for the West European countries,
845 // USA and for the rest of the world. This is undoubtedly false for many
846 // countries, but I lack the necessary info (and the time to gather it),
847 // please add the other rules here!
848 // ----------------------------------------------------------------------------
851 bool wxDateTime::IsDSTApplicable(int year
, Country country
)
853 if ( year
== Inv_Year
)
855 // take the current year if none given
856 year
= GetCurrentYear();
859 if ( country
== Country_Default
)
861 country
= GetCountry();
868 // DST was first observed in the US and UK during WWI, reused
869 // during WWII and used again since 1966
870 return year
>= 1966 ||
871 (year
>= 1942 && year
<= 1945) ||
872 (year
== 1918 || year
== 1919);
875 // assume that it started after WWII
881 wxDateTime
wxDateTime::GetBeginDST(int year
, Country country
)
883 if ( year
== Inv_Year
)
885 // take the current year if none given
886 year
= GetCurrentYear();
889 if ( country
== Country_Default
)
891 country
= GetCountry();
894 if ( !IsDSTApplicable(year
, country
) )
896 return wxInvalidDateTime
;
901 if ( IsWestEuropeanCountry(country
) || (country
== Russia
) )
903 // DST begins at 1 a.m. GMT on the last Sunday of March
904 if ( !dt
.SetToLastWeekDay(Sun
, Mar
, year
) )
907 wxFAIL_MSG( _T("no last Sunday in March?") );
910 dt
+= wxTimeSpan::Hours(1);
912 // disable DST tests because it could result in an infinite recursion!
915 else switch ( country
)
922 // don't know for sure - assume it was in effect all year
927 dt
.Set(1, Jan
, year
);
931 // DST was installed Feb 2, 1942 by the Congress
932 dt
.Set(2, Feb
, year
);
935 // Oil embargo changed the DST period in the US
937 dt
.Set(6, Jan
, 1974);
941 dt
.Set(23, Feb
, 1975);
945 // before 1986, DST begun on the last Sunday of April, but
946 // in 1986 Reagan changed it to begin at 2 a.m. of the
947 // first Sunday in April
950 if ( !dt
.SetToLastWeekDay(Sun
, Apr
, year
) )
953 wxFAIL_MSG( _T("no first Sunday in April?") );
958 if ( !dt
.SetToWeekDay(Sun
, 1, Apr
, year
) )
961 wxFAIL_MSG( _T("no first Sunday in April?") );
965 dt
+= wxTimeSpan::Hours(2);
967 // TODO what about timezone??
973 // assume Mar 30 as the start of the DST for the rest of the world
974 // - totally bogus, of course
975 dt
.Set(30, Mar
, year
);
982 wxDateTime
wxDateTime::GetEndDST(int year
, Country country
)
984 if ( year
== Inv_Year
)
986 // take the current year if none given
987 year
= GetCurrentYear();
990 if ( country
== Country_Default
)
992 country
= GetCountry();
995 if ( !IsDSTApplicable(year
, country
) )
997 return wxInvalidDateTime
;
1002 if ( IsWestEuropeanCountry(country
) || (country
== Russia
) )
1004 // DST ends at 1 a.m. GMT on the last Sunday of October
1005 if ( !dt
.SetToLastWeekDay(Sun
, Oct
, year
) )
1007 // weirder and weirder...
1008 wxFAIL_MSG( _T("no last Sunday in October?") );
1011 dt
+= wxTimeSpan::Hours(1);
1013 // disable DST tests because it could result in an infinite recursion!
1016 else switch ( country
)
1023 // don't know for sure - assume it was in effect all year
1027 dt
.Set(31, Dec
, year
);
1031 // the time was reset after the end of the WWII
1032 dt
.Set(30, Sep
, year
);
1036 // DST ends at 2 a.m. on the last Sunday of October
1037 if ( !dt
.SetToLastWeekDay(Sun
, Oct
, year
) )
1039 // weirder and weirder...
1040 wxFAIL_MSG( _T("no last Sunday in October?") );
1043 dt
+= wxTimeSpan::Hours(2);
1045 // TODO what about timezone??
1050 // assume October 26th as the end of the DST - totally bogus too
1051 dt
.Set(26, Oct
, year
);
1057 // ----------------------------------------------------------------------------
1058 // constructors and assignment operators
1059 // ----------------------------------------------------------------------------
1061 // return the current time with ms precision
1062 /* static */ wxDateTime
wxDateTime::UNow()
1064 return wxDateTime(wxGetLocalTimeMillis());
1067 // the values in the tm structure contain the local time
1068 wxDateTime
& wxDateTime::Set(const struct tm
& tm
)
1070 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1073 time_t timet
= mktime(&tm2
);
1075 if ( timet
== (time_t)-1 )
1077 // mktime() rather unintuitively fails for Jan 1, 1970 if the hour is
1078 // less than timezone - try to make it work for this case
1079 if ( tm2
.tm_year
== 70 && tm2
.tm_mon
== 0 && tm2
.tm_mday
== 1 )
1081 // add timezone to make sure that date is in range
1082 tm2
.tm_sec
-= GetTimeZone();
1084 timet
= mktime(&tm2
);
1085 if ( timet
!= (time_t)-1 )
1087 timet
+= GetTimeZone();
1093 wxFAIL_MSG( _T("mktime() failed") );
1095 *this = wxInvalidDateTime
;
1105 wxDateTime
& wxDateTime::Set(wxDateTime_t hour
,
1106 wxDateTime_t minute
,
1107 wxDateTime_t second
,
1108 wxDateTime_t millisec
)
1110 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1112 // we allow seconds to be 61 to account for the leap seconds, even if we
1113 // don't use them really
1114 wxDATETIME_CHECK( hour
< 24 &&
1118 _T("Invalid time in wxDateTime::Set()") );
1120 // get the current date from system
1121 struct tm
*tm
= GetTmNow();
1123 wxDATETIME_CHECK( tm
, _T("localtime() failed") );
1127 tm
->tm_min
= minute
;
1128 tm
->tm_sec
= second
;
1132 // and finally adjust milliseconds
1133 return SetMillisecond(millisec
);
1136 wxDateTime
& wxDateTime::Set(wxDateTime_t day
,
1140 wxDateTime_t minute
,
1141 wxDateTime_t second
,
1142 wxDateTime_t millisec
)
1144 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1146 wxDATETIME_CHECK( hour
< 24 &&
1150 _T("Invalid time in wxDateTime::Set()") );
1152 ReplaceDefaultYearMonthWithCurrent(&year
, &month
);
1154 wxDATETIME_CHECK( (0 < day
) && (day
<= GetNumberOfDays(month
, year
)),
1155 _T("Invalid date in wxDateTime::Set()") );
1157 // the range of time_t type (inclusive)
1158 static const int yearMinInRange
= 1970;
1159 static const int yearMaxInRange
= 2037;
1161 // test only the year instead of testing for the exact end of the Unix
1162 // time_t range - it doesn't bring anything to do more precise checks
1163 if ( year
>= yearMinInRange
&& year
<= yearMaxInRange
)
1165 // use the standard library version if the date is in range - this is
1166 // probably more efficient than our code
1168 tm
.tm_year
= year
- 1900;
1174 tm
.tm_isdst
= -1; // mktime() will guess it
1178 // and finally adjust milliseconds
1179 return SetMillisecond(millisec
);
1183 // do time calculations ourselves: we want to calculate the number of
1184 // milliseconds between the given date and the epoch
1186 // get the JDN for the midnight of this day
1187 m_time
= GetTruncatedJDN(day
, month
, year
);
1188 m_time
-= EPOCH_JDN
;
1189 m_time
*= SECONDS_PER_DAY
* TIME_T_FACTOR
;
1191 // JDN corresponds to GMT, we take localtime
1192 Add(wxTimeSpan(hour
, minute
, second
+ GetTimeZone(), millisec
));
1198 wxDateTime
& wxDateTime::Set(double jdn
)
1200 // so that m_time will be 0 for the midnight of Jan 1, 1970 which is jdn
1202 jdn
-= EPOCH_JDN
+ 0.5;
1204 jdn
*= MILLISECONDS_PER_DAY
;
1211 wxDateTime
& wxDateTime::ResetTime()
1215 if ( tm
.hour
|| tm
.min
|| tm
.sec
|| tm
.msec
)
1228 // ----------------------------------------------------------------------------
1229 // time_t <-> broken down time conversions
1230 // ----------------------------------------------------------------------------
1232 wxDateTime::Tm
wxDateTime::GetTm(const TimeZone
& tz
) const
1234 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1236 time_t time
= GetTicks();
1237 if ( time
!= (time_t)-1 )
1239 // use C RTL functions
1241 if ( tz
.GetOffset() == -GetTimeZone() )
1243 // we are working with local time
1244 tm
= localtime(&time
);
1246 // should never happen
1247 wxCHECK_MSG( tm
, Tm(), _T("localtime() failed") );
1251 time
+= (time_t)tz
.GetOffset();
1252 #ifdef __VMS__ // time is unsigned so avoid warning
1253 int time2
= (int) time
;
1261 // should never happen
1262 wxCHECK_MSG( tm
, Tm(), _T("gmtime() failed") );
1266 tm
= (struct tm
*)NULL
;
1272 // adjust the milliseconds
1274 long timeOnly
= (m_time
% MILLISECONDS_PER_DAY
).ToLong();
1275 tm2
.msec
= (wxDateTime_t
)(timeOnly
% 1000);
1278 //else: use generic code below
1281 // remember the time and do the calculations with the date only - this
1282 // eliminates rounding errors of the floating point arithmetics
1284 wxLongLong timeMidnight
= m_time
+ tz
.GetOffset() * 1000;
1286 long timeOnly
= (timeMidnight
% MILLISECONDS_PER_DAY
).ToLong();
1288 // we want to always have positive time and timeMidnight to be really
1289 // the midnight before it
1292 timeOnly
= MILLISECONDS_PER_DAY
+ timeOnly
;
1295 timeMidnight
-= timeOnly
;
1297 // calculate the Gregorian date from JDN for the midnight of our date:
1298 // this will yield day, month (in 1..12 range) and year
1300 // actually, this is the JDN for the noon of the previous day
1301 long jdn
= (timeMidnight
/ MILLISECONDS_PER_DAY
).ToLong() + EPOCH_JDN
;
1303 // CREDIT: code below is by Scott E. Lee (but bugs are mine)
1305 wxASSERT_MSG( jdn
> -2, _T("JDN out of range") );
1307 // calculate the century
1308 long temp
= (jdn
+ JDN_OFFSET
) * 4 - 1;
1309 long century
= temp
/ DAYS_PER_400_YEARS
;
1311 // then the year and day of year (1 <= dayOfYear <= 366)
1312 temp
= ((temp
% DAYS_PER_400_YEARS
) / 4) * 4 + 3;
1313 long year
= (century
* 100) + (temp
/ DAYS_PER_4_YEARS
);
1314 long dayOfYear
= (temp
% DAYS_PER_4_YEARS
) / 4 + 1;
1316 // and finally the month and day of the month
1317 temp
= dayOfYear
* 5 - 3;
1318 long month
= temp
/ DAYS_PER_5_MONTHS
;
1319 long day
= (temp
% DAYS_PER_5_MONTHS
) / 5 + 1;
1321 // month is counted from March - convert to normal
1332 // year is offset by 4800
1335 // check that the algorithm gave us something reasonable
1336 wxASSERT_MSG( (0 < month
) && (month
<= 12), _T("invalid month") );
1337 wxASSERT_MSG( (1 <= day
) && (day
< 32), _T("invalid day") );
1338 wxASSERT_MSG( (INT_MIN
<= year
) && (year
<= INT_MAX
),
1339 _T("year range overflow") );
1341 // construct Tm from these values
1343 tm
.year
= (int)year
;
1344 tm
.mon
= (Month
)(month
- 1); // algorithm yields 1 for January, not 0
1345 tm
.mday
= (wxDateTime_t
)day
;
1346 tm
.msec
= (wxDateTime_t
)(timeOnly
% 1000);
1347 timeOnly
-= tm
.msec
;
1348 timeOnly
/= 1000; // now we have time in seconds
1350 tm
.sec
= (wxDateTime_t
)(timeOnly
% 60);
1352 timeOnly
/= 60; // now we have time in minutes
1354 tm
.min
= (wxDateTime_t
)(timeOnly
% 60);
1357 tm
.hour
= (wxDateTime_t
)(timeOnly
/ 60);
1362 wxDateTime
& wxDateTime::SetYear(int year
)
1364 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1373 wxDateTime
& wxDateTime::SetMonth(Month month
)
1375 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1384 wxDateTime
& wxDateTime::SetDay(wxDateTime_t mday
)
1386 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1395 wxDateTime
& wxDateTime::SetHour(wxDateTime_t hour
)
1397 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1406 wxDateTime
& wxDateTime::SetMinute(wxDateTime_t min
)
1408 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1417 wxDateTime
& wxDateTime::SetSecond(wxDateTime_t sec
)
1419 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1428 wxDateTime
& wxDateTime::SetMillisecond(wxDateTime_t millisecond
)
1430 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1432 // we don't need to use GetTm() for this one
1433 m_time
-= m_time
% 1000l;
1434 m_time
+= millisecond
;
1439 // ----------------------------------------------------------------------------
1440 // wxDateTime arithmetics
1441 // ----------------------------------------------------------------------------
1443 wxDateTime
& wxDateTime::Add(const wxDateSpan
& diff
)
1447 tm
.year
+= diff
.GetYears();
1448 tm
.AddMonths(diff
.GetMonths());
1450 // check that the resulting date is valid
1451 if ( tm
.mday
> GetNumOfDaysInMonth(tm
.year
, tm
.mon
) )
1453 // We suppose that when adding one month to Jan 31 we want to get Feb
1454 // 28 (or 29), i.e. adding a month to the last day of the month should
1455 // give the last day of the next month which is quite logical.
1457 // Unfortunately, there is no logic way to understand what should
1458 // Jan 30 + 1 month be - Feb 28 too or Feb 27 (assuming non leap year)?
1459 // We make it Feb 28 (last day too), but it is highly questionable.
1460 tm
.mday
= GetNumOfDaysInMonth(tm
.year
, tm
.mon
);
1463 tm
.AddDays(diff
.GetTotalDays());
1467 wxASSERT_MSG( IsSameTime(tm
),
1468 _T("Add(wxDateSpan) shouldn't modify time") );
1473 // ----------------------------------------------------------------------------
1474 // Weekday and monthday stuff
1475 // ----------------------------------------------------------------------------
1477 bool wxDateTime::SetToTheWeek(wxDateTime_t numWeek
, WeekDay weekday
)
1479 int year
= GetYear();
1481 // Jan 4 always lies in the 1st week of the year
1483 SetToWeekDayInSameWeek(weekday
) += wxDateSpan::Weeks(numWeek
);
1485 if ( GetYear() != year
)
1487 // oops... numWeek was too big
1494 wxDateTime
& wxDateTime::SetToLastMonthDay(Month month
,
1497 // take the current month/year if none specified
1498 if ( year
== Inv_Year
)
1500 if ( month
== Inv_Month
)
1503 return Set(GetNumOfDaysInMonth(year
, month
), month
, year
);
1506 wxDateTime
& wxDateTime::SetToWeekDayInSameWeek(WeekDay weekday
)
1508 wxDATETIME_CHECK( weekday
!= Inv_WeekDay
, _T("invalid weekday") );
1510 WeekDay wdayThis
= GetWeekDay();
1511 if ( weekday
== wdayThis
)
1516 else if ( weekday
< wdayThis
)
1518 return Subtract(wxDateSpan::Days(wdayThis
- weekday
));
1520 else // weekday > wdayThis
1522 return Add(wxDateSpan::Days(weekday
- wdayThis
));
1526 wxDateTime
& wxDateTime::SetToNextWeekDay(WeekDay weekday
)
1528 wxDATETIME_CHECK( weekday
!= Inv_WeekDay
, _T("invalid weekday") );
1531 WeekDay wdayThis
= GetWeekDay();
1532 if ( weekday
== wdayThis
)
1537 else if ( weekday
< wdayThis
)
1539 // need to advance a week
1540 diff
= 7 - (wdayThis
- weekday
);
1542 else // weekday > wdayThis
1544 diff
= weekday
- wdayThis
;
1547 return Add(wxDateSpan::Days(diff
));
1550 wxDateTime
& wxDateTime::SetToPrevWeekDay(WeekDay weekday
)
1552 wxDATETIME_CHECK( weekday
!= Inv_WeekDay
, _T("invalid weekday") );
1555 WeekDay wdayThis
= GetWeekDay();
1556 if ( weekday
== wdayThis
)
1561 else if ( weekday
> wdayThis
)
1563 // need to go to previous week
1564 diff
= 7 - (weekday
- wdayThis
);
1566 else // weekday < wdayThis
1568 diff
= wdayThis
- weekday
;
1571 return Subtract(wxDateSpan::Days(diff
));
1574 bool wxDateTime::SetToWeekDay(WeekDay weekday
,
1579 wxCHECK_MSG( weekday
!= Inv_WeekDay
, FALSE
, _T("invalid weekday") );
1581 // we don't check explicitly that -5 <= n <= 5 because we will return FALSE
1582 // anyhow in such case - but may be should still give an assert for it?
1584 // take the current month/year if none specified
1585 ReplaceDefaultYearMonthWithCurrent(&year
, &month
);
1589 // TODO this probably could be optimised somehow...
1593 // get the first day of the month
1594 dt
.Set(1, month
, year
);
1597 WeekDay wdayFirst
= dt
.GetWeekDay();
1599 // go to the first weekday of the month
1600 int diff
= weekday
- wdayFirst
;
1604 // add advance n-1 weeks more
1607 dt
+= wxDateSpan::Days(diff
);
1609 else // count from the end of the month
1611 // get the last day of the month
1612 dt
.SetToLastMonthDay(month
, year
);
1615 WeekDay wdayLast
= dt
.GetWeekDay();
1617 // go to the last weekday of the month
1618 int diff
= wdayLast
- weekday
;
1622 // and rewind n-1 weeks from there
1625 dt
-= wxDateSpan::Days(diff
);
1628 // check that it is still in the same month
1629 if ( dt
.GetMonth() == month
)
1637 // no such day in this month
1642 wxDateTime::wxDateTime_t
wxDateTime::GetDayOfYear(const TimeZone
& tz
) const
1646 return gs_cumulatedDays
[IsLeapYear(tm
.year
)][tm
.mon
] + tm
.mday
;
1649 wxDateTime::wxDateTime_t
wxDateTime::GetWeekOfYear(wxDateTime::WeekFlags flags
,
1650 const TimeZone
& tz
) const
1652 if ( flags
== Default_First
)
1654 flags
= GetCountry() == USA
? Sunday_First
: Monday_First
;
1657 wxDateTime_t nDayInYear
= GetDayOfYear(tz
);
1660 WeekDay wd
= GetWeekDay(tz
);
1661 if ( flags
== Sunday_First
)
1663 week
= (nDayInYear
- wd
+ 7) / 7;
1667 // have to shift the week days values
1668 week
= (nDayInYear
- (wd
- 1 + 7) % 7 + 7) / 7;
1671 // FIXME some more elegant way??
1672 WeekDay wdYearStart
= wxDateTime(1, Jan
, GetYear()).GetWeekDay();
1673 if ( wdYearStart
== Wed
|| wdYearStart
== Thu
)
1681 wxDateTime::wxDateTime_t
wxDateTime::GetWeekOfMonth(wxDateTime::WeekFlags flags
,
1682 const TimeZone
& tz
) const
1685 wxDateTime dtMonthStart
= wxDateTime(1, tm
.mon
, tm
.year
);
1686 int nWeek
= GetWeekOfYear(flags
) - dtMonthStart
.GetWeekOfYear(flags
) + 1;
1689 // this may happen for January when Jan, 1 is the last week of the
1691 nWeek
+= IsLeapYear(tm
.year
- 1) ? 53 : 52;
1694 return (wxDateTime::wxDateTime_t
)nWeek
;
1697 wxDateTime
& wxDateTime::SetToYearDay(wxDateTime::wxDateTime_t yday
)
1699 int year
= GetYear();
1700 wxDATETIME_CHECK( (0 < yday
) && (yday
<= GetNumberOfDays(year
)),
1701 _T("invalid year day") );
1703 bool isLeap
= IsLeapYear(year
);
1704 for ( Month mon
= Jan
; mon
< Inv_Month
; wxNextMonth(mon
) )
1706 // for Dec, we can't compare with gs_cumulatedDays[mon + 1], but we
1707 // don't need it neither - because of the CHECK above we know that
1708 // yday lies in December then
1709 if ( (mon
== Dec
) || (yday
< gs_cumulatedDays
[isLeap
][mon
+ 1]) )
1711 Set(yday
- gs_cumulatedDays
[isLeap
][mon
], mon
, year
);
1720 // ----------------------------------------------------------------------------
1721 // Julian day number conversion and related stuff
1722 // ----------------------------------------------------------------------------
1724 double wxDateTime::GetJulianDayNumber() const
1726 // JDN are always expressed for the GMT dates
1727 Tm
tm(ToTimezone(GMT0
).GetTm(GMT0
));
1729 double result
= GetTruncatedJDN(tm
.mday
, tm
.mon
, tm
.year
);
1731 // add the part GetTruncatedJDN() neglected
1734 // and now add the time: 86400 sec = 1 JDN
1735 return result
+ ((double)(60*(60*tm
.hour
+ tm
.min
) + tm
.sec
)) / 86400;
1738 double wxDateTime::GetRataDie() const
1740 // March 1 of the year 0 is Rata Die day -306 and JDN 1721119.5
1741 return GetJulianDayNumber() - 1721119.5 - 306;
1744 // ----------------------------------------------------------------------------
1745 // timezone and DST stuff
1746 // ----------------------------------------------------------------------------
1748 int wxDateTime::IsDST(wxDateTime::Country country
) const
1750 wxCHECK_MSG( country
== Country_Default
, -1,
1751 _T("country support not implemented") );
1753 // use the C RTL for the dates in the standard range
1754 time_t timet
= GetTicks();
1755 if ( timet
!= (time_t)-1 )
1757 tm
*tm
= localtime(&timet
);
1759 wxCHECK_MSG( tm
, -1, _T("localtime() failed") );
1761 return tm
->tm_isdst
;
1765 int year
= GetYear();
1767 if ( !IsDSTApplicable(year
, country
) )
1769 // no DST time in this year in this country
1773 return IsBetween(GetBeginDST(year
, country
), GetEndDST(year
, country
));
1777 wxDateTime
& wxDateTime::MakeTimezone(const TimeZone
& tz
, bool noDST
)
1779 long secDiff
= GetTimeZone() + tz
.GetOffset();
1781 // we need to know whether DST is or not in effect for this date unless
1782 // the test disabled by the caller
1783 if ( !noDST
&& (IsDST() == 1) )
1785 // FIXME we assume that the DST is always shifted by 1 hour
1789 return Subtract(wxTimeSpan::Seconds(secDiff
));
1792 // ----------------------------------------------------------------------------
1793 // wxDateTime to/from text representations
1794 // ----------------------------------------------------------------------------
1796 wxString
wxDateTime::Format(const wxChar
*format
, const TimeZone
& tz
) const
1798 wxCHECK_MSG( format
, _T(""), _T("NULL format in wxDateTime::Format") );
1800 // we have to use our own implementation if the date is out of range of
1801 // strftime() or if we use non standard specificators
1802 time_t time
= GetTicks();
1803 if ( (time
!= (time_t)-1) && !wxStrstr(format
, _T("%l")) )
1807 if ( tz
.GetOffset() == -GetTimeZone() )
1809 // we are working with local time
1810 tm
= localtime(&time
);
1812 // should never happen
1813 wxCHECK_MSG( tm
, wxEmptyString
, _T("localtime() failed") );
1817 time
+= (int)tz
.GetOffset();
1819 #ifdef __VMS__ // time is unsigned so avoid the warning
1820 int time2
= (int) time
;
1828 // should never happen
1829 wxCHECK_MSG( tm
, wxEmptyString
, _T("gmtime() failed") );
1833 tm
= (struct tm
*)NULL
;
1839 return CallStrftime(format
, tm
);
1841 //else: use generic code below
1844 // we only parse ANSI C format specifications here, no POSIX 2
1845 // complications, no GNU extensions but we do add support for a "%l" format
1846 // specifier allowing to get the number of milliseconds
1849 // used for calls to strftime() when we only deal with time
1850 struct tm tmTimeOnly
;
1851 tmTimeOnly
.tm_hour
= tm
.hour
;
1852 tmTimeOnly
.tm_min
= tm
.min
;
1853 tmTimeOnly
.tm_sec
= tm
.sec
;
1854 tmTimeOnly
.tm_wday
= 0;
1855 tmTimeOnly
.tm_yday
= 0;
1856 tmTimeOnly
.tm_mday
= 1; // any date will do
1857 tmTimeOnly
.tm_mon
= 0;
1858 tmTimeOnly
.tm_year
= 76;
1859 tmTimeOnly
.tm_isdst
= 0; // no DST, we adjust for tz ourselves
1861 wxString tmp
, res
, fmt
;
1862 for ( const wxChar
*p
= format
; *p
; p
++ )
1864 if ( *p
!= _T('%') )
1872 // set the default format
1875 case _T('Y'): // year has 4 digits
1879 case _T('j'): // day of year has 3 digits
1880 case _T('l'): // milliseconds have 3 digits
1885 // it's either another valid format specifier in which case
1886 // the format is "%02d" (for all the rest) or we have the
1887 // field width preceding the format in which case it will
1888 // override the default format anyhow
1892 bool restart
= TRUE
;
1897 // start of the format specification
1900 case _T('a'): // a weekday name
1902 // second parameter should be TRUE for abbreviated names
1903 res
+= GetWeekDayName(tm
.GetWeekDay(),
1904 *p
== _T('a') ? Name_Abbr
: Name_Full
);
1907 case _T('b'): // a month name
1909 res
+= GetMonthName(tm
.mon
,
1910 *p
== _T('b') ? Name_Abbr
: Name_Full
);
1913 case _T('c'): // locale default date and time representation
1914 case _T('x'): // locale default date representation
1916 // the problem: there is no way to know what do these format
1917 // specifications correspond to for the current locale.
1919 // the solution: use a hack and still use strftime(): first
1920 // find the YEAR which is a year in the strftime() range (1970
1921 // - 2038) whose Jan 1 falls on the same week day as the Jan 1
1922 // of the real year. Then make a copy of the format and
1923 // replace all occurences of YEAR in it with some unique
1924 // string not appearing anywhere else in it, then use
1925 // strftime() to format the date in year YEAR and then replace
1926 // YEAR back by the real year and the unique replacement
1927 // string back with YEAR. Notice that "all occurences of YEAR"
1928 // means all occurences of 4 digit as well as 2 digit form!
1930 // the bugs: we assume that neither of %c nor %x contains any
1931 // fields which may change between the YEAR and real year. For
1932 // example, the week number (%U, %W) and the day number (%j)
1933 // will change if one of these years is leap and the other one
1936 // find the YEAR: normally, for any year X, Jan 1 or the
1937 // year X + 28 is the same weekday as Jan 1 of X (because
1938 // the weekday advances by 1 for each normal X and by 2
1939 // for each leap X, hence by 5 every 4 years or by 35
1940 // which is 0 mod 7 every 28 years) but this rule breaks
1941 // down if there are years between X and Y which are
1942 // divisible by 4 but not leap (i.e. divisible by 100 but
1943 // not 400), hence the correction.
1945 int yearReal
= GetYear(tz
);
1946 int mod28
= yearReal
% 28;
1948 // be careful to not go too far - we risk to leave the
1953 year
= 1988 + mod28
; // 1988 == 0 (mod 28)
1957 year
= 1970 + mod28
- 10; // 1970 == 10 (mod 28)
1960 int nCentury
= year
/ 100,
1961 nCenturyReal
= yearReal
/ 100;
1963 // need to adjust for the years divisble by 400 which are
1964 // not leap but are counted like leap ones if we just take
1965 // the number of centuries in between for nLostWeekDays
1966 int nLostWeekDays
= (nCentury
- nCenturyReal
) -
1967 (nCentury
/ 4 - nCenturyReal
/ 4);
1969 // we have to gain back the "lost" weekdays: note that the
1970 // effect of this loop is to not do anything to
1971 // nLostWeekDays (which we won't use any more), but to
1972 // (indirectly) set the year correctly
1973 while ( (nLostWeekDays
% 7) != 0 )
1975 nLostWeekDays
+= year
++ % 4 ? 1 : 2;
1978 // at any rate, we couldn't go further than 1988 + 9 + 28!
1979 wxASSERT_MSG( year
< 2030,
1980 _T("logic error in wxDateTime::Format") );
1982 wxString strYear
, strYear2
;
1983 strYear
.Printf(_T("%d"), year
);
1984 strYear2
.Printf(_T("%d"), year
% 100);
1986 // find two strings not occuring in format (this is surely
1987 // not optimal way of doing it... improvements welcome!)
1988 wxString fmt
= format
;
1989 wxString replacement
= (wxChar
)-1;
1990 while ( fmt
.Find(replacement
) != wxNOT_FOUND
)
1992 replacement
<< (wxChar
)-1;
1995 wxString replacement2
= (wxChar
)-2;
1996 while ( fmt
.Find(replacement
) != wxNOT_FOUND
)
1998 replacement
<< (wxChar
)-2;
2001 // replace all occurences of year with it
2002 bool wasReplaced
= fmt
.Replace(strYear
, replacement
) > 0;
2004 wasReplaced
= fmt
.Replace(strYear2
, replacement2
) > 0;
2006 // use strftime() to format the same date but in supported
2009 // NB: we assume that strftime() doesn't check for the
2010 // date validity and will happily format the date
2011 // corresponding to Feb 29 of a non leap year (which
2012 // may happen if yearReal was leap and year is not)
2013 struct tm tmAdjusted
;
2015 tmAdjusted
.tm_hour
= tm
.hour
;
2016 tmAdjusted
.tm_min
= tm
.min
;
2017 tmAdjusted
.tm_sec
= tm
.sec
;
2018 tmAdjusted
.tm_wday
= tm
.GetWeekDay();
2019 tmAdjusted
.tm_yday
= GetDayOfYear();
2020 tmAdjusted
.tm_mday
= tm
.mday
;
2021 tmAdjusted
.tm_mon
= tm
.mon
;
2022 tmAdjusted
.tm_year
= year
- 1900;
2023 tmAdjusted
.tm_isdst
= 0; // no DST, already adjusted
2024 wxString str
= CallStrftime(*p
== _T('c') ? _T("%c")
2028 // now replace the occurence of 1999 with the real year
2029 wxString strYearReal
, strYearReal2
;
2030 strYearReal
.Printf(_T("%04d"), yearReal
);
2031 strYearReal2
.Printf(_T("%02d"), yearReal
% 100);
2032 str
.Replace(strYear
, strYearReal
);
2033 str
.Replace(strYear2
, strYearReal2
);
2035 // and replace back all occurences of replacement string
2038 str
.Replace(replacement2
, strYear2
);
2039 str
.Replace(replacement
, strYear
);
2046 case _T('d'): // day of a month (01-31)
2047 res
+= wxString::Format(fmt
, tm
.mday
);
2050 case _T('H'): // hour in 24h format (00-23)
2051 res
+= wxString::Format(fmt
, tm
.hour
);
2054 case _T('I'): // hour in 12h format (01-12)
2056 // 24h -> 12h, 0h -> 12h too
2057 int hour12
= tm
.hour
> 12 ? tm
.hour
- 12
2058 : tm
.hour
? tm
.hour
: 12;
2059 res
+= wxString::Format(fmt
, hour12
);
2063 case _T('j'): // day of the year
2064 res
+= wxString::Format(fmt
, GetDayOfYear(tz
));
2067 case _T('l'): // milliseconds (NOT STANDARD)
2068 res
+= wxString::Format(fmt
, GetMillisecond(tz
));
2071 case _T('m'): // month as a number (01-12)
2072 res
+= wxString::Format(fmt
, tm
.mon
+ 1);
2075 case _T('M'): // minute as a decimal number (00-59)
2076 res
+= wxString::Format(fmt
, tm
.min
);
2079 case _T('p'): // AM or PM string
2080 res
+= CallStrftime(_T("%p"), &tmTimeOnly
);
2083 case _T('S'): // second as a decimal number (00-61)
2084 res
+= wxString::Format(fmt
, tm
.sec
);
2087 case _T('U'): // week number in the year (Sunday 1st week day)
2088 res
+= wxString::Format(fmt
, GetWeekOfYear(Sunday_First
, tz
));
2091 case _T('W'): // week number in the year (Monday 1st week day)
2092 res
+= wxString::Format(fmt
, GetWeekOfYear(Monday_First
, tz
));
2095 case _T('w'): // weekday as a number (0-6), Sunday = 0
2096 res
+= wxString::Format(fmt
, tm
.GetWeekDay());
2099 // case _T('x'): -- handled with "%c"
2101 case _T('X'): // locale default time representation
2102 // just use strftime() to format the time for us
2103 res
+= CallStrftime(_T("%X"), &tmTimeOnly
);
2106 case _T('y'): // year without century (00-99)
2107 res
+= wxString::Format(fmt
, tm
.year
% 100);
2110 case _T('Y'): // year with century
2111 res
+= wxString::Format(fmt
, tm
.year
);
2114 case _T('Z'): // timezone name
2115 res
+= CallStrftime(_T("%Z"), &tmTimeOnly
);
2119 // is it the format width?
2121 while ( *p
== _T('-') || *p
== _T('+') ||
2122 *p
== _T(' ') || wxIsdigit(*p
) )
2127 if ( !fmt
.IsEmpty() )
2129 // we've only got the flags and width so far in fmt
2130 fmt
.Prepend(_T('%'));
2131 fmt
.Append(_T('d'));
2138 // no, it wasn't the width
2139 wxFAIL_MSG(_T("unknown format specificator"));
2141 // fall through and just copy it nevertheless
2143 case _T('%'): // a percent sign
2147 case 0: // the end of string
2148 wxFAIL_MSG(_T("missing format at the end of string"));
2150 // just put the '%' which was the last char in format
2160 // this function parses a string in (strict) RFC 822 format: see the section 5
2161 // of the RFC for the detailed description, but briefly it's something of the
2162 // form "Sat, 18 Dec 1999 00:48:30 +0100"
2164 // this function is "strict" by design - it must reject anything except true
2165 // RFC822 time specs.
2167 // TODO a great candidate for using reg exps
2168 const wxChar
*wxDateTime::ParseRfc822Date(const wxChar
* date
)
2170 wxCHECK_MSG( date
, (wxChar
*)NULL
, _T("NULL pointer in wxDateTime::Parse") );
2172 const wxChar
*p
= date
;
2173 const wxChar
*comma
= wxStrchr(p
, _T(','));
2176 // the part before comma is the weekday
2178 // skip it for now - we don't use but might check that it really
2179 // corresponds to the specfied date
2182 if ( *p
!= _T(' ') )
2184 wxLogDebug(_T("no space after weekday in RFC822 time spec"));
2186 return (wxChar
*)NULL
;
2192 // the following 1 or 2 digits are the day number
2193 if ( !wxIsdigit(*p
) )
2195 wxLogDebug(_T("day number expected in RFC822 time spec, none found"));
2197 return (wxChar
*)NULL
;
2200 wxDateTime_t day
= *p
++ - _T('0');
2201 if ( wxIsdigit(*p
) )
2204 day
+= *p
++ - _T('0');
2207 if ( *p
++ != _T(' ') )
2209 return (wxChar
*)NULL
;
2212 // the following 3 letters specify the month
2213 wxString
monName(p
, 3);
2215 if ( monName
== _T("Jan") )
2217 else if ( monName
== _T("Feb") )
2219 else if ( monName
== _T("Mar") )
2221 else if ( monName
== _T("Apr") )
2223 else if ( monName
== _T("May") )
2225 else if ( monName
== _T("Jun") )
2227 else if ( monName
== _T("Jul") )
2229 else if ( monName
== _T("Aug") )
2231 else if ( monName
== _T("Sep") )
2233 else if ( monName
== _T("Oct") )
2235 else if ( monName
== _T("Nov") )
2237 else if ( monName
== _T("Dec") )
2241 wxLogDebug(_T("Invalid RFC 822 month name '%s'"), monName
.c_str());
2243 return (wxChar
*)NULL
;
2248 if ( *p
++ != _T(' ') )
2250 return (wxChar
*)NULL
;
2254 if ( !wxIsdigit(*p
) )
2257 return (wxChar
*)NULL
;
2260 int year
= *p
++ - _T('0');
2262 if ( !wxIsdigit(*p
) )
2264 // should have at least 2 digits in the year
2265 return (wxChar
*)NULL
;
2269 year
+= *p
++ - _T('0');
2271 // is it a 2 digit year (as per original RFC 822) or a 4 digit one?
2272 if ( wxIsdigit(*p
) )
2275 year
+= *p
++ - _T('0');
2277 if ( !wxIsdigit(*p
) )
2279 // no 3 digit years please
2280 return (wxChar
*)NULL
;
2284 year
+= *p
++ - _T('0');
2287 if ( *p
++ != _T(' ') )
2289 return (wxChar
*)NULL
;
2292 // time is in the format hh:mm:ss and seconds are optional
2293 if ( !wxIsdigit(*p
) )
2295 return (wxChar
*)NULL
;
2298 wxDateTime_t hour
= *p
++ - _T('0');
2300 if ( !wxIsdigit(*p
) )
2302 return (wxChar
*)NULL
;
2306 hour
+= *p
++ - _T('0');
2308 if ( *p
++ != _T(':') )
2310 return (wxChar
*)NULL
;
2313 if ( !wxIsdigit(*p
) )
2315 return (wxChar
*)NULL
;
2318 wxDateTime_t min
= *p
++ - _T('0');
2320 if ( !wxIsdigit(*p
) )
2322 return (wxChar
*)NULL
;
2326 min
+= *p
++ - _T('0');
2328 wxDateTime_t sec
= 0;
2329 if ( *p
++ == _T(':') )
2331 if ( !wxIsdigit(*p
) )
2333 return (wxChar
*)NULL
;
2336 sec
= *p
++ - _T('0');
2338 if ( !wxIsdigit(*p
) )
2340 return (wxChar
*)NULL
;
2344 sec
+= *p
++ - _T('0');
2347 if ( *p
++ != _T(' ') )
2349 return (wxChar
*)NULL
;
2352 // and now the interesting part: the timezone
2354 if ( *p
== _T('-') || *p
== _T('+') )
2356 // the explicit offset given: it has the form of hhmm
2357 bool plus
= *p
++ == _T('+');
2359 if ( !wxIsdigit(*p
) || !wxIsdigit(*(p
+ 1)) )
2361 return (wxChar
*)NULL
;
2365 offset
= 60*(10*(*p
- _T('0')) + (*(p
+ 1) - _T('0')));
2369 if ( !wxIsdigit(*p
) || !wxIsdigit(*(p
+ 1)) )
2371 return (wxChar
*)NULL
;
2375 offset
+= 10*(*p
- _T('0')) + (*(p
+ 1) - _T('0'));
2386 // the symbolic timezone given: may be either military timezone or one
2387 // of standard abbreviations
2390 // military: Z = UTC, J unused, A = -1, ..., Y = +12
2391 static const int offsets
[26] =
2393 //A B C D E F G H I J K L M
2394 -1, -2, -3, -4, -5, -6, -7, -8, -9, 0, -10, -11, -12,
2395 //N O P R Q S T U V W Z Y Z
2396 +1, +2, +3, +4, +5, +6, +7, +8, +9, +10, +11, +12, 0
2399 if ( *p
< _T('A') || *p
> _T('Z') || *p
== _T('J') )
2401 wxLogDebug(_T("Invalid militaty timezone '%c'"), *p
);
2403 return (wxChar
*)NULL
;
2406 offset
= offsets
[*p
++ - _T('A')];
2412 if ( tz
== _T("UT") || tz
== _T("UTC") || tz
== _T("GMT") )
2414 else if ( tz
== _T("AST") )
2415 offset
= AST
- GMT0
;
2416 else if ( tz
== _T("ADT") )
2417 offset
= ADT
- GMT0
;
2418 else if ( tz
== _T("EST") )
2419 offset
= EST
- GMT0
;
2420 else if ( tz
== _T("EDT") )
2421 offset
= EDT
- GMT0
;
2422 else if ( tz
== _T("CST") )
2423 offset
= CST
- GMT0
;
2424 else if ( tz
== _T("CDT") )
2425 offset
= CDT
- GMT0
;
2426 else if ( tz
== _T("MST") )
2427 offset
= MST
- GMT0
;
2428 else if ( tz
== _T("MDT") )
2429 offset
= MDT
- GMT0
;
2430 else if ( tz
== _T("PST") )
2431 offset
= PST
- GMT0
;
2432 else if ( tz
== _T("PDT") )
2433 offset
= PDT
- GMT0
;
2436 wxLogDebug(_T("Unknown RFC 822 timezone '%s'"), p
);
2438 return (wxChar
*)NULL
;
2448 // the spec was correct
2449 Set(day
, mon
, year
, hour
, min
, sec
);
2450 MakeTimezone((wxDateTime_t
)(60*offset
));
2455 const wxChar
*wxDateTime::ParseFormat(const wxChar
*date
,
2456 const wxChar
*format
,
2457 const wxDateTime
& dateDef
)
2459 wxCHECK_MSG( date
&& format
, (wxChar
*)NULL
,
2460 _T("NULL pointer in wxDateTime::ParseFormat()") );
2465 // what fields have we found?
2466 bool haveWDay
= FALSE
,
2475 bool hourIsIn12hFormat
= FALSE
, // or in 24h one?
2476 isPM
= FALSE
; // AM by default
2478 // and the value of the items we have (init them to get rid of warnings)
2479 wxDateTime_t sec
= 0,
2482 WeekDay wday
= Inv_WeekDay
;
2483 wxDateTime_t yday
= 0,
2485 wxDateTime::Month mon
= Inv_Month
;
2488 const wxChar
*input
= date
;
2489 for ( const wxChar
*fmt
= format
; *fmt
; fmt
++ )
2491 if ( *fmt
!= _T('%') )
2493 if ( wxIsspace(*fmt
) )
2495 // a white space in the format string matches 0 or more white
2496 // spaces in the input
2497 while ( wxIsspace(*input
) )
2504 // any other character (not whitespace, not '%') must be
2505 // matched by itself in the input
2506 if ( *input
++ != *fmt
)
2509 return (wxChar
*)NULL
;
2513 // done with this format char
2517 // start of a format specification
2519 // parse the optional width
2521 while ( isdigit(*++fmt
) )
2524 width
+= *fmt
- _T('0');
2527 // then the format itself
2530 case _T('a'): // a weekday name
2533 int flag
= *fmt
== _T('a') ? Name_Abbr
: Name_Full
;
2534 wday
= GetWeekDayFromName(GetAlphaToken(input
), flag
);
2535 if ( wday
== Inv_WeekDay
)
2538 return (wxChar
*)NULL
;
2544 case _T('b'): // a month name
2547 int flag
= *fmt
== _T('b') ? Name_Abbr
: Name_Full
;
2548 mon
= GetMonthFromName(GetAlphaToken(input
), flag
);
2549 if ( mon
== Inv_Month
)
2552 return (wxChar
*)NULL
;
2558 case _T('c'): // locale default date and time representation
2562 // this is the format which corresponds to ctime() output
2563 // and strptime("%c") should parse it, so try it first
2564 static const wxChar
*fmtCtime
= _T("%a %b %d %H:%M:%S %Y");
2566 const wxChar
*result
= dt
.ParseFormat(input
, fmtCtime
);
2569 result
= dt
.ParseFormat(input
, _T("%x %X"));
2574 result
= dt
.ParseFormat(input
, _T("%X %x"));
2579 // we've tried everything and still no match
2580 return (wxChar
*)NULL
;
2585 haveDay
= haveMon
= haveYear
=
2586 haveHour
= haveMin
= haveSec
= TRUE
;
2600 case _T('d'): // day of a month (01-31)
2601 if ( !GetNumericToken(width
, input
, &num
) ||
2602 (num
> 31) || (num
< 1) )
2605 return (wxChar
*)NULL
;
2608 // we can't check whether the day range is correct yet, will
2609 // do it later - assume ok for now
2611 mday
= (wxDateTime_t
)num
;
2614 case _T('H'): // hour in 24h format (00-23)
2615 if ( !GetNumericToken(width
, input
, &num
) || (num
> 23) )
2618 return (wxChar
*)NULL
;
2622 hour
= (wxDateTime_t
)num
;
2625 case _T('I'): // hour in 12h format (01-12)
2626 if ( !GetNumericToken(width
, input
, &num
) || !num
|| (num
> 12) )
2629 return (wxChar
*)NULL
;
2633 hourIsIn12hFormat
= TRUE
;
2634 hour
= (wxDateTime_t
)(num
% 12); // 12 should be 0
2637 case _T('j'): // day of the year
2638 if ( !GetNumericToken(width
, input
, &num
) || !num
|| (num
> 366) )
2641 return (wxChar
*)NULL
;
2645 yday
= (wxDateTime_t
)num
;
2648 case _T('m'): // month as a number (01-12)
2649 if ( !GetNumericToken(width
, input
, &num
) || !num
|| (num
> 12) )
2652 return (wxChar
*)NULL
;
2656 mon
= (Month
)(num
- 1);
2659 case _T('M'): // minute as a decimal number (00-59)
2660 if ( !GetNumericToken(width
, input
, &num
) || (num
> 59) )
2663 return (wxChar
*)NULL
;
2667 min
= (wxDateTime_t
)num
;
2670 case _T('p'): // AM or PM string
2672 wxString am
, pm
, token
= GetAlphaToken(input
);
2674 GetAmPmStrings(&am
, &pm
);
2675 if ( token
.CmpNoCase(pm
) == 0 )
2679 else if ( token
.CmpNoCase(am
) != 0 )
2682 return (wxChar
*)NULL
;
2687 case _T('r'): // time as %I:%M:%S %p
2690 input
= dt
.ParseFormat(input
, _T("%I:%M:%S %p"));
2694 return (wxChar
*)NULL
;
2697 haveHour
= haveMin
= haveSec
= TRUE
;
2706 case _T('R'): // time as %H:%M
2709 input
= dt
.ParseFormat(input
, _T("%H:%M"));
2713 return (wxChar
*)NULL
;
2716 haveHour
= haveMin
= TRUE
;
2723 case _T('S'): // second as a decimal number (00-61)
2724 if ( !GetNumericToken(width
, input
, &num
) || (num
> 61) )
2727 return (wxChar
*)NULL
;
2731 sec
= (wxDateTime_t
)num
;
2734 case _T('T'): // time as %H:%M:%S
2737 input
= dt
.ParseFormat(input
, _T("%H:%M:%S"));
2741 return (wxChar
*)NULL
;
2744 haveHour
= haveMin
= haveSec
= TRUE
;
2753 case _T('w'): // weekday as a number (0-6), Sunday = 0
2754 if ( !GetNumericToken(width
, input
, &num
) || (wday
> 6) )
2757 return (wxChar
*)NULL
;
2761 wday
= (WeekDay
)num
;
2764 case _T('x'): // locale default date representation
2765 #ifdef HAVE_STRPTIME
2766 // try using strptime() - it may fail even if the input is
2767 // correct but the date is out of range, so we will fall back
2768 // to our generic code anyhow (FIXME !Unicode friendly)
2771 const wxChar
*result
= strptime(input
, "%x", &tm
);
2776 haveDay
= haveMon
= haveYear
= TRUE
;
2778 year
= 1900 + tm
.tm_year
;
2779 mon
= (Month
)tm
.tm_mon
;
2785 #endif // HAVE_STRPTIME
2787 // TODO query the LOCALE_IDATE setting under Win32
2791 wxString fmtDate
, fmtDateAlt
;
2792 if ( IsWestEuropeanCountry(GetCountry()) ||
2793 GetCountry() == Russia
)
2795 fmtDate
= _T("%d/%m/%y");
2796 fmtDateAlt
= _T("%m/%d/%y");
2800 fmtDate
= _T("%m/%d/%y");
2801 fmtDateAlt
= _T("%d/%m/%y");
2804 const wxChar
*result
= dt
.ParseFormat(input
, fmtDate
);
2808 // ok, be nice and try another one
2809 result
= dt
.ParseFormat(input
, fmtDateAlt
);
2815 return (wxChar
*)NULL
;
2820 haveDay
= haveMon
= haveYear
= TRUE
;
2831 case _T('X'): // locale default time representation
2832 #ifdef HAVE_STRPTIME
2834 // use strptime() to do it for us (FIXME !Unicode friendly)
2836 input
= strptime(input
, "%X", &tm
);
2839 return (wxChar
*)NULL
;
2842 haveHour
= haveMin
= haveSec
= TRUE
;
2848 #else // !HAVE_STRPTIME
2849 // TODO under Win32 we can query the LOCALE_ITIME system
2850 // setting which says whether the default time format is
2853 // try to parse what follows as "%H:%M:%S" and, if this
2854 // fails, as "%I:%M:%S %p" - this should catch the most
2858 const wxChar
*result
= dt
.ParseFormat(input
, _T("%T"));
2861 result
= dt
.ParseFormat(input
, _T("%r"));
2867 return (wxChar
*)NULL
;
2870 haveHour
= haveMin
= haveSec
= TRUE
;
2879 #endif // HAVE_STRPTIME/!HAVE_STRPTIME
2882 case _T('y'): // year without century (00-99)
2883 if ( !GetNumericToken(width
, input
, &num
) || (num
> 99) )
2886 return (wxChar
*)NULL
;
2891 // TODO should have an option for roll over date instead of
2892 // hard coding it here
2893 year
= (num
> 30 ? 1900 : 2000) + (wxDateTime_t
)num
;
2896 case _T('Y'): // year with century
2897 if ( !GetNumericToken(width
, input
, &num
) )
2900 return (wxChar
*)NULL
;
2904 year
= (wxDateTime_t
)num
;
2907 case _T('Z'): // timezone name
2908 wxFAIL_MSG(_T("TODO"));
2911 case _T('%'): // a percent sign
2912 if ( *input
++ != _T('%') )
2915 return (wxChar
*)NULL
;
2919 case 0: // the end of string
2920 wxFAIL_MSG(_T("unexpected format end"));
2924 default: // not a known format spec
2925 return (wxChar
*)NULL
;
2929 // format matched, try to construct a date from what we have now
2931 if ( dateDef
.IsValid() )
2933 // take this date as default
2934 tmDef
= dateDef
.GetTm();
2937 else if ( m_time
!= 0 )
2939 else if ( m_time
!= wxLongLong(0) )
2942 // if this date is valid, don't change it
2947 // no default and this date is invalid - fall back to Today()
2948 tmDef
= Today().GetTm();
2959 // TODO we don't check here that the values are consistent, if both year
2960 // day and month/day were found, we just ignore the year day and we
2961 // also always ignore the week day
2962 if ( haveMon
&& haveDay
)
2964 if ( mday
> GetNumOfDaysInMonth(tm
.year
, mon
) )
2966 wxLogDebug(_T("bad month day in wxDateTime::ParseFormat"));
2968 return (wxChar
*)NULL
;
2974 else if ( haveYDay
)
2976 if ( yday
> GetNumberOfDays(tm
.year
) )
2978 wxLogDebug(_T("bad year day in wxDateTime::ParseFormat"));
2980 return (wxChar
*)NULL
;
2983 Tm tm2
= wxDateTime(1, Jan
, tm
.year
).SetToYearDay(yday
).GetTm();
2990 if ( haveHour
&& hourIsIn12hFormat
&& isPM
)
2992 // translate to 24hour format
2995 //else: either already in 24h format or no translation needed
3018 const wxChar
*wxDateTime::ParseDateTime(const wxChar
*date
)
3020 wxCHECK_MSG( date
, (wxChar
*)NULL
, _T("NULL pointer in wxDateTime::Parse") );
3022 // there is a public domain version of getdate.y, but it only works for
3024 wxFAIL_MSG(_T("TODO"));
3026 return (wxChar
*)NULL
;
3029 const wxChar
*wxDateTime::ParseDate(const wxChar
*date
)
3031 // this is a simplified version of ParseDateTime() which understands only
3032 // "today" (for wxDate compatibility) and digits only otherwise (and not
3033 // all esoteric constructions ParseDateTime() knows about)
3035 wxCHECK_MSG( date
, (wxChar
*)NULL
, _T("NULL pointer in wxDateTime::Parse") );
3037 const wxChar
*p
= date
;
3038 while ( wxIsspace(*p
) )
3041 // some special cases
3045 int dayDiffFromToday
;
3048 { wxTRANSLATE("today"), 0 },
3049 { wxTRANSLATE("yesterday"), -1 },
3050 { wxTRANSLATE("tomorrow"), 1 },
3053 for ( size_t n
= 0; n
< WXSIZEOF(literalDates
); n
++ )
3055 wxString date
= wxGetTranslation(literalDates
[n
].str
);
3056 size_t len
= date
.length();
3057 if ( wxStrlen(p
) >= len
&& (wxString(p
, len
).CmpNoCase(date
) == 0) )
3059 // nothing can follow this, so stop here
3062 int dayDiffFromToday
= literalDates
[n
].dayDiffFromToday
;
3064 if ( dayDiffFromToday
)
3066 *this += wxDateSpan::Days(dayDiffFromToday
);
3073 // We try to guess what we have here: for each new (numeric) token, we
3074 // determine if it can be a month, day or a year. Of course, there is an
3075 // ambiguity as some numbers may be days as well as months, so we also
3076 // have the ability to back track.
3079 bool haveDay
= FALSE
, // the months day?
3080 haveWDay
= FALSE
, // the day of week?
3081 haveMon
= FALSE
, // the month?
3082 haveYear
= FALSE
; // the year?
3084 // and the value of the items we have (init them to get rid of warnings)
3085 WeekDay wday
= Inv_WeekDay
;
3086 wxDateTime_t day
= 0;
3087 wxDateTime::Month mon
= Inv_Month
;
3090 // tokenize the string
3092 static const wxChar
*dateDelimiters
= _T(".,/-\t\n ");
3093 wxStringTokenizer
tok(p
, dateDelimiters
);
3094 while ( tok
.HasMoreTokens() )
3096 wxString token
= tok
.GetNextToken();
3102 if ( token
.ToULong(&val
) )
3104 // guess what this number is
3110 if ( !haveMon
&& val
> 0 && val
<= 12 )
3112 // assume it is month
3115 else // not the month
3117 wxDateTime_t maxDays
= haveMon
3118 ? GetNumOfDaysInMonth(haveYear
? year
: Inv_Year
, mon
)
3122 if ( (val
== 0) || (val
> maxDays
) )
3139 year
= (wxDateTime_t
)val
;
3148 day
= (wxDateTime_t
)val
;
3154 mon
= (Month
)(val
- 1);
3157 else // not a number
3159 // be careful not to overwrite the current mon value
3160 Month mon2
= GetMonthFromName(token
, Name_Full
| Name_Abbr
);
3161 if ( mon2
!= Inv_Month
)
3166 // but we already have a month - maybe we guessed wrong?
3169 // no need to check in month range as always < 12, but
3170 // the days are counted from 1 unlike the months
3171 day
= (wxDateTime_t
)mon
+ 1;
3176 // could possible be the year (doesn't the year come
3177 // before the month in the japanese format?) (FIXME)
3186 else // not a valid month name
3188 wday
= GetWeekDayFromName(token
, Name_Full
| Name_Abbr
);
3189 if ( wday
!= Inv_WeekDay
)
3199 else // not a valid weekday name
3202 static const wxChar
*ordinals
[] =
3204 wxTRANSLATE("first"),
3205 wxTRANSLATE("second"),
3206 wxTRANSLATE("third"),
3207 wxTRANSLATE("fourth"),
3208 wxTRANSLATE("fifth"),
3209 wxTRANSLATE("sixth"),
3210 wxTRANSLATE("seventh"),
3211 wxTRANSLATE("eighth"),
3212 wxTRANSLATE("ninth"),
3213 wxTRANSLATE("tenth"),
3214 wxTRANSLATE("eleventh"),
3215 wxTRANSLATE("twelfth"),
3216 wxTRANSLATE("thirteenth"),
3217 wxTRANSLATE("fourteenth"),
3218 wxTRANSLATE("fifteenth"),
3219 wxTRANSLATE("sixteenth"),
3220 wxTRANSLATE("seventeenth"),
3221 wxTRANSLATE("eighteenth"),
3222 wxTRANSLATE("nineteenth"),
3223 wxTRANSLATE("twentieth"),
3224 // that's enough - otherwise we'd have problems with
3225 // composite (or not) ordinals
3229 for ( n
= 0; n
< WXSIZEOF(ordinals
); n
++ )
3231 if ( token
.CmpNoCase(ordinals
[n
]) == 0 )
3237 if ( n
== WXSIZEOF(ordinals
) )
3239 // stop here - something unknown
3246 // don't try anything here (as in case of numeric day
3247 // above) - the symbolic day spec should always
3248 // precede the month/year
3259 nPosCur
= tok
.GetPosition();
3262 // either no more tokens or the scan was stopped by something we couldn't
3263 // parse - in any case, see if we can construct a date from what we have
3264 if ( !haveDay
&& !haveWDay
)
3266 wxLogDebug(_T("ParseDate: no day, no weekday hence no date."));
3268 return (wxChar
*)NULL
;
3271 if ( haveWDay
&& (haveMon
|| haveYear
|| haveDay
) &&
3272 !(haveDay
&& haveMon
&& haveYear
) )
3274 // without adjectives (which we don't support here) the week day only
3275 // makes sense completely separately or with the full date
3276 // specification (what would "Wed 1999" mean?)
3277 return (wxChar
*)NULL
;
3280 if ( !haveWDay
&& haveYear
&& !(haveDay
&& haveMon
) )
3282 // may be we have month and day instead of day and year?
3283 if ( haveDay
&& !haveMon
)
3287 // exchange day and month
3288 mon
= (wxDateTime::Month
)(day
- 1);
3290 // we're in the current year then
3291 if ( year
<= GetNumOfDaysInMonth(Inv_Year
, mon
) )
3298 //else: no, can't exchange, leave haveMon == FALSE
3304 // if we give the year, month and day must be given too
3305 wxLogDebug(_T("ParseDate: day and month should be specified if year is."));
3307 return (wxChar
*)NULL
;
3313 mon
= GetCurrentMonth();
3318 year
= GetCurrentYear();
3323 Set(day
, mon
, year
);
3327 // check that it is really the same
3328 if ( GetWeekDay() != wday
)
3330 // inconsistency detected
3331 wxLogDebug(_T("ParseDate: inconsistent day/weekday."));
3333 return (wxChar
*)NULL
;
3341 SetToWeekDayInSameWeek(wday
);
3344 // return the pointer to the first unparsed char
3346 if ( nPosCur
&& wxStrchr(dateDelimiters
, *(p
- 1)) )
3348 // if we couldn't parse the token after the delimiter, put back the
3349 // delimiter as well
3356 const wxChar
*wxDateTime::ParseTime(const wxChar
*time
)
3358 wxCHECK_MSG( time
, (wxChar
*)NULL
, _T("NULL pointer in wxDateTime::Parse") );
3360 // first try some extra things
3367 { wxTRANSLATE("noon"), 12 },
3368 { wxTRANSLATE("midnight"), 00 },
3372 for ( size_t n
= 0; n
< WXSIZEOF(stdTimes
); n
++ )
3374 wxString timeString
= wxGetTranslation(stdTimes
[n
].name
);
3375 size_t len
= timeString
.length();
3376 if ( timeString
.CmpNoCase(wxString(time
, len
)) == 0 )
3378 Set(stdTimes
[n
].hour
, 0, 0);
3384 // try all time formats we may think about starting with the standard one
3385 const wxChar
*result
= ParseFormat(time
, _T("%X"));
3388 // normally, it's the same, but why not try it?
3389 result
= ParseFormat(time
, _T("%H:%M:%S"));
3394 // 12hour with AM/PM?
3395 result
= ParseFormat(time
, _T("%I:%M:%S %p"));
3401 result
= ParseFormat(time
, _T("%H:%M"));
3406 // 12hour with AM/PM but without seconds?
3407 result
= ParseFormat(time
, _T("%I:%M %p"));
3413 result
= ParseFormat(time
, _T("%H"));
3418 // just the hour and AM/PM?
3419 result
= ParseFormat(time
, _T("%I %p"));
3422 // TODO: parse timezones
3427 // ----------------------------------------------------------------------------
3428 // Workdays and holidays support
3429 // ----------------------------------------------------------------------------
3431 bool wxDateTime::IsWorkDay(Country
WXUNUSED(country
)) const
3433 return !wxDateTimeHolidayAuthority::IsHoliday(*this);
3436 // ============================================================================
3438 // ============================================================================
3440 // not all strftime(3) format specifiers make sense here because, for example,
3441 // a time span doesn't have a year nor a timezone
3443 // Here are the ones which are supported (all of them are supported by strftime
3445 // %H hour in 24 hour format
3446 // %M minute (00 - 59)
3447 // %S second (00 - 59)
3450 // Also, for MFC CTimeSpan compatibility, we support
3451 // %D number of days
3453 // And, to be better than MFC :-), we also have
3454 // %E number of wEeks
3455 // %l milliseconds (000 - 999)
3456 wxString
wxTimeSpan::Format(const wxChar
*format
) const
3458 wxCHECK_MSG( format
, _T(""), _T("NULL format in wxTimeSpan::Format") );
3461 str
.Alloc(wxStrlen(format
));
3463 // Suppose we have wxTimeSpan ts(1 /* hour */, 2 /* min */, 3 /* sec */)
3465 // Then, of course, ts.Format("%H:%M:%S") must return "01:02:03", but the
3466 // question is what should ts.Format("%S") do? The code here returns "3273"
3467 // in this case (i.e. the total number of seconds, not just seconds % 60)
3468 // because, for me, this call means "give me entire time interval in
3469 // seconds" and not "give me the seconds part of the time interval"
3471 // If we agree that it should behave like this, it is clear that the
3472 // interpretation of each format specifier depends on the presence of the
3473 // other format specs in the string: if there was "%H" before "%M", we
3474 // should use GetMinutes() % 60, otherwise just GetMinutes() &c
3476 // we remember the most important unit found so far
3485 } partBiggest
= Part_MSec
;
3487 for ( const wxChar
*pch
= format
; *pch
; pch
++ )
3491 if ( ch
== _T('%') )
3493 // the start of the format specification of the printf() below
3494 wxString fmtPrefix
= _T('%');
3499 ch
= *++pch
; // get the format spec char
3503 wxFAIL_MSG( _T("invalid format character") );
3509 // skip the part below switch
3514 if ( partBiggest
< Part_Day
)
3520 partBiggest
= Part_Day
;
3525 partBiggest
= Part_Week
;
3531 if ( partBiggest
< Part_Hour
)
3537 partBiggest
= Part_Hour
;
3540 fmtPrefix
+= _T("02");
3544 n
= GetMilliseconds().ToLong();
3545 if ( partBiggest
< Part_MSec
)
3549 //else: no need to reset partBiggest to Part_MSec, it is
3550 // the least significant one anyhow
3552 fmtPrefix
+= _T("03");
3557 if ( partBiggest
< Part_Min
)
3563 partBiggest
= Part_Min
;
3566 fmtPrefix
+= _T("02");
3570 n
= GetSeconds().ToLong();
3571 if ( partBiggest
< Part_Sec
)
3577 partBiggest
= Part_Sec
;
3580 fmtPrefix
+= _T("02");
3584 str
+= wxString::Format(fmtPrefix
+ _T("ld"), n
);
3588 // normal character, just copy
3596 // ============================================================================
3597 // wxDateTimeHolidayAuthority and related classes
3598 // ============================================================================
3600 #include "wx/arrimpl.cpp"
3602 WX_DEFINE_OBJARRAY(wxDateTimeArray
);
3604 static int wxCMPFUNC_CONV
3605 wxDateTimeCompareFunc(wxDateTime
**first
, wxDateTime
**second
)
3607 wxDateTime dt1
= **first
,
3610 return dt1
== dt2
? 0 : dt1
< dt2
? -1 : +1;
3613 // ----------------------------------------------------------------------------
3614 // wxDateTimeHolidayAuthority
3615 // ----------------------------------------------------------------------------
3617 wxHolidayAuthoritiesArray
wxDateTimeHolidayAuthority::ms_authorities
;
3620 bool wxDateTimeHolidayAuthority::IsHoliday(const wxDateTime
& dt
)
3622 size_t count
= ms_authorities
.GetCount();
3623 for ( size_t n
= 0; n
< count
; n
++ )
3625 if ( ms_authorities
[n
]->DoIsHoliday(dt
) )
3636 wxDateTimeHolidayAuthority::GetHolidaysInRange(const wxDateTime
& dtStart
,
3637 const wxDateTime
& dtEnd
,
3638 wxDateTimeArray
& holidays
)
3640 wxDateTimeArray hol
;
3644 size_t count
= ms_authorities
.GetCount();
3645 for ( size_t nAuth
= 0; nAuth
< count
; nAuth
++ )
3647 ms_authorities
[nAuth
]->DoGetHolidaysInRange(dtStart
, dtEnd
, hol
);
3649 WX_APPEND_ARRAY(holidays
, hol
);
3652 holidays
.Sort(wxDateTimeCompareFunc
);
3654 return holidays
.GetCount();
3658 void wxDateTimeHolidayAuthority::ClearAllAuthorities()
3660 WX_CLEAR_ARRAY(ms_authorities
);
3664 void wxDateTimeHolidayAuthority::AddAuthority(wxDateTimeHolidayAuthority
*auth
)
3666 ms_authorities
.Add(auth
);
3669 // ----------------------------------------------------------------------------
3670 // wxDateTimeWorkDays
3671 // ----------------------------------------------------------------------------
3673 bool wxDateTimeWorkDays::DoIsHoliday(const wxDateTime
& dt
) const
3675 wxDateTime::WeekDay wd
= dt
.GetWeekDay();
3677 return (wd
== wxDateTime::Sun
) || (wd
== wxDateTime::Sat
);
3680 size_t wxDateTimeWorkDays::DoGetHolidaysInRange(const wxDateTime
& dtStart
,
3681 const wxDateTime
& dtEnd
,
3682 wxDateTimeArray
& holidays
) const
3684 if ( dtStart
> dtEnd
)
3686 wxFAIL_MSG( _T("invalid date range in GetHolidaysInRange") );
3693 // instead of checking all days, start with the first Sat after dtStart and
3694 // end with the last Sun before dtEnd
3695 wxDateTime dtSatFirst
= dtStart
.GetNextWeekDay(wxDateTime::Sat
),
3696 dtSatLast
= dtEnd
.GetPrevWeekDay(wxDateTime::Sat
),
3697 dtSunFirst
= dtStart
.GetNextWeekDay(wxDateTime::Sun
),
3698 dtSunLast
= dtEnd
.GetPrevWeekDay(wxDateTime::Sun
),
3701 for ( dt
= dtSatFirst
; dt
<= dtSatLast
; dt
+= wxDateSpan::Week() )
3706 for ( dt
= dtSunFirst
; dt
<= dtSunLast
; dt
+= wxDateSpan::Week() )
3711 return holidays
.GetCount();