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()
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 #else // unknown platform - try timezone
122 #define WX_TIMEZONE timezone
124 #endif // !WX_TIMEZONE
126 // ----------------------------------------------------------------------------
128 // ----------------------------------------------------------------------------
130 // debugging helper: just a convenient replacement of wxCHECK()
131 #define wxDATETIME_CHECK(expr, msg) \
135 *this = wxInvalidDateTime; \
139 // ----------------------------------------------------------------------------
141 // ----------------------------------------------------------------------------
143 class wxDateTimeHolidaysModule
: public wxModule
146 virtual bool OnInit()
148 wxDateTimeHolidayAuthority::AddAuthority(new wxDateTimeWorkDays
);
153 virtual void OnExit()
155 wxDateTimeHolidayAuthority::ClearAllAuthorities();
156 wxDateTimeHolidayAuthority::ms_authorities
.Clear();
160 DECLARE_DYNAMIC_CLASS(wxDateTimeHolidaysModule
)
163 IMPLEMENT_DYNAMIC_CLASS(wxDateTimeHolidaysModule
, wxModule
)
165 // ----------------------------------------------------------------------------
167 // ----------------------------------------------------------------------------
170 static const int MONTHS_IN_YEAR
= 12;
172 static const int SEC_PER_MIN
= 60;
174 static const int MIN_PER_HOUR
= 60;
176 static const int HOURS_PER_DAY
= 24;
178 static const long SECONDS_PER_DAY
= 86400l;
180 static const int DAYS_PER_WEEK
= 7;
182 static const long MILLISECONDS_PER_DAY
= 86400000l;
184 // this is the integral part of JDN of the midnight of Jan 1, 1970
185 // (i.e. JDN(Jan 1, 1970) = 2440587.5)
186 static const long EPOCH_JDN
= 2440587l;
188 // the date of JDN -0.5 (as we don't work with fractional parts, this is the
189 // reference date for us) is Nov 24, 4714BC
190 static const int JDN_0_YEAR
= -4713;
191 static const int JDN_0_MONTH
= wxDateTime::Nov
;
192 static const int JDN_0_DAY
= 24;
194 // the constants used for JDN calculations
195 static const long JDN_OFFSET
= 32046l;
196 static const long DAYS_PER_5_MONTHS
= 153l;
197 static const long DAYS_PER_4_YEARS
= 1461l;
198 static const long DAYS_PER_400_YEARS
= 146097l;
200 // this array contains the cumulated number of days in all previous months for
201 // normal and leap years
202 static const wxDateTime::wxDateTime_t gs_cumulatedDays
[2][MONTHS_IN_YEAR
] =
204 { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 },
205 { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 }
208 // ----------------------------------------------------------------------------
210 // ----------------------------------------------------------------------------
212 // in the fine tradition of ANSI C we use our equivalent of (time_t)-1 to
213 // indicate an invalid wxDateTime object
214 const wxDateTime wxDefaultDateTime
;
216 wxDateTime::Country
wxDateTime::ms_country
= wxDateTime::Country_Unknown
;
218 // ----------------------------------------------------------------------------
220 // ----------------------------------------------------------------------------
222 // a critical section is needed to protect GetTimeZone() static
223 // variable in MT case
225 static wxCriticalSection gs_critsectTimezone
;
226 #endif // wxUSE_THREADS
228 // ----------------------------------------------------------------------------
230 // ----------------------------------------------------------------------------
232 // debugger helper: shows what the date really is
234 extern const wxChar
*wxDumpDate(const wxDateTime
* dt
)
236 static wxChar buf
[128];
238 wxStrcpy(buf
, dt
->Format(_T("%Y-%m-%d (%a) %H:%M:%S")));
244 // get the number of days in the given month of the given year
246 wxDateTime::wxDateTime_t
GetNumOfDaysInMonth(int year
, wxDateTime::Month month
)
248 // the number of days in month in Julian/Gregorian calendar: the first line
249 // is for normal years, the second one is for the leap ones
250 static wxDateTime::wxDateTime_t daysInMonth
[2][MONTHS_IN_YEAR
] =
252 { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
253 { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
256 return daysInMonth
[wxDateTime::IsLeapYear(year
)][month
];
259 // ensure that the timezone variable is set by calling localtime
260 static int GetTimeZone()
262 // set to TRUE when the timezone is set
263 static bool s_timezoneSet
= FALSE
;
265 wxCRIT_SECT_LOCKER(lock
, gs_critsectTimezone
);
267 if ( !s_timezoneSet
)
269 // just call localtime() instead of figuring out whether this system
270 // supports tzset(), _tzset() or something else
274 s_timezoneSet
= TRUE
;
277 return (int)WX_TIMEZONE
;
280 // return the integral part of the JDN for the midnight of the given date (to
281 // get the real JDN you need to add 0.5, this is, in fact, JDN of the
282 // noon of the previous day)
283 static long GetTruncatedJDN(wxDateTime::wxDateTime_t day
,
284 wxDateTime::Month mon
,
287 // CREDIT: code below is by Scott E. Lee (but bugs are mine)
289 // check the date validity
291 (year
> JDN_0_YEAR
) ||
292 ((year
== JDN_0_YEAR
) && (mon
> JDN_0_MONTH
)) ||
293 ((year
== JDN_0_YEAR
) && (mon
== JDN_0_MONTH
) && (day
>= JDN_0_DAY
)),
294 _T("date out of range - can't convert to JDN")
297 // make the year positive to avoid problems with negative numbers division
300 // months are counted from March here
302 if ( mon
>= wxDateTime::Mar
)
312 // now we can simply add all the contributions together
313 return ((year
/ 100) * DAYS_PER_400_YEARS
) / 4
314 + ((year
% 100) * DAYS_PER_4_YEARS
) / 4
315 + (month
* DAYS_PER_5_MONTHS
+ 2) / 5
320 // this function is a wrapper around strftime(3)
321 static wxString
CallStrftime(const wxChar
*format
, const tm
* tm
)
324 if ( !wxStrftime(buf
, WXSIZEOF(buf
), format
, tm
) )
326 // buffer is too small?
327 wxFAIL_MSG(_T("strftime() failed"));
330 return wxString(buf
);
333 // if year and/or month have invalid values, replace them with the current ones
334 static void ReplaceDefaultYearMonthWithCurrent(int *year
,
335 wxDateTime::Month
*month
)
337 struct tm
*tmNow
= NULL
;
339 if ( *year
== wxDateTime::Inv_Year
)
341 tmNow
= wxDateTime::GetTmNow();
343 *year
= 1900 + tmNow
->tm_year
;
346 if ( *month
== wxDateTime::Inv_Month
)
349 tmNow
= wxDateTime::GetTmNow();
351 *month
= (wxDateTime::Month
)tmNow
->tm_mon
;
355 // fll the struct tm with default values
356 static void InitTm(struct tm
& tm
)
358 // struct tm may have etxra fields (undocumented and with unportable
359 // names) which, nevertheless, must be set to 0
360 memset(&tm
, 0, sizeof(struct tm
));
362 tm
.tm_mday
= 1; // mday 0 is invalid
363 tm
.tm_year
= 76; // any valid year
364 tm
.tm_isdst
= -1; // auto determine
370 // return the month if the string is a month name or Inv_Month otherwise
371 static wxDateTime::Month
GetMonthFromName(const wxString
& name
, int flags
)
373 wxDateTime::Month mon
;
374 for ( mon
= wxDateTime::Jan
; mon
< wxDateTime::Inv_Month
; wxNextMonth(mon
) )
376 // case-insensitive comparison either one of or with both abbreviated
378 if ( flags
& wxDateTime::Name_Full
)
380 if ( name
.CmpNoCase(wxDateTime::
381 GetMonthName(mon
, wxDateTime::Name_Full
)) == 0 )
387 if ( flags
& wxDateTime::Name_Abbr
)
389 if ( name
.CmpNoCase(wxDateTime::
390 GetMonthName(mon
, wxDateTime::Name_Abbr
)) == 0 )
400 // return the weekday if the string is a weekday name or Inv_WeekDay otherwise
401 static wxDateTime::WeekDay
GetWeekDayFromName(const wxString
& name
, int flags
)
403 wxDateTime::WeekDay wd
;
404 for ( wd
= wxDateTime::Sun
; wd
< wxDateTime::Inv_WeekDay
; wxNextWDay(wd
) )
406 // case-insensitive comparison either one of or with both abbreviated
408 if ( flags
& wxDateTime::Name_Full
)
410 if ( name
.CmpNoCase(wxDateTime::
411 GetWeekDayName(wd
, wxDateTime::Name_Full
)) == 0 )
417 if ( flags
& wxDateTime::Name_Abbr
)
419 if ( name
.CmpNoCase(wxDateTime::
420 GetWeekDayName(wd
, wxDateTime::Name_Abbr
)) == 0 )
430 // scans all digits (but no more than len) and returns the resulting number
431 static bool GetNumericToken(size_t len
, const wxChar
*& p
, unsigned long *number
)
435 while ( wxIsdigit(*p
) )
439 if ( len
&& ++n
> len
)
443 return !!s
&& s
.ToULong(number
);
446 // scans all alphabetic characters and returns the resulting string
447 static wxString
GetAlphaToken(const wxChar
*& p
)
450 while ( wxIsalpha(*p
) )
458 // ============================================================================
459 // implementation of wxDateTime
460 // ============================================================================
462 // ----------------------------------------------------------------------------
464 // ----------------------------------------------------------------------------
468 year
= (wxDateTime_t
)wxDateTime::Inv_Year
;
469 mon
= wxDateTime::Inv_Month
;
471 hour
= min
= sec
= msec
= 0;
472 wday
= wxDateTime::Inv_WeekDay
;
475 wxDateTime::Tm::Tm(const struct tm
& tm
, const TimeZone
& tz
)
483 mon
= (wxDateTime::Month
)tm
.tm_mon
;
484 year
= 1900 + tm
.tm_year
;
489 bool wxDateTime::Tm::IsValid() const
491 // we allow for the leap seconds, although we don't use them (yet)
492 return (year
!= wxDateTime::Inv_Year
) && (mon
!= wxDateTime::Inv_Month
) &&
493 (mday
<= GetNumOfDaysInMonth(year
, mon
)) &&
494 (hour
< 24) && (min
< 60) && (sec
< 62) && (msec
< 1000);
497 void wxDateTime::Tm::ComputeWeekDay()
499 // compute the week day from day/month/year: we use the dumbest algorithm
500 // possible: just compute our JDN and then use the (simple to derive)
501 // formula: weekday = (JDN + 1.5) % 7
502 wday
= (wxDateTime::WeekDay
)(GetTruncatedJDN(mday
, mon
, year
) + 2) % 7;
505 void wxDateTime::Tm::AddMonths(int monDiff
)
507 // normalize the months field
508 while ( monDiff
< -mon
)
512 monDiff
+= MONTHS_IN_YEAR
;
515 while ( monDiff
+ mon
>= MONTHS_IN_YEAR
)
519 monDiff
-= MONTHS_IN_YEAR
;
522 mon
= (wxDateTime::Month
)(mon
+ monDiff
);
524 wxASSERT_MSG( mon
>= 0 && mon
< MONTHS_IN_YEAR
, _T("logic error") );
526 // NB: we don't check here that the resulting date is valid, this function
527 // is private and the caller must check it if needed
530 void wxDateTime::Tm::AddDays(int dayDiff
)
532 // normalize the days field
533 while ( dayDiff
+ mday
< 1 )
537 dayDiff
+= GetNumOfDaysInMonth(year
, mon
);
541 while ( mday
> GetNumOfDaysInMonth(year
, mon
) )
543 mday
-= GetNumOfDaysInMonth(year
, mon
);
548 wxASSERT_MSG( mday
> 0 && mday
<= GetNumOfDaysInMonth(year
, mon
),
552 // ----------------------------------------------------------------------------
554 // ----------------------------------------------------------------------------
556 wxDateTime::TimeZone::TimeZone(wxDateTime::TZ tz
)
560 case wxDateTime::Local
:
561 // get the offset from C RTL: it returns the difference GMT-local
562 // while we want to have the offset _from_ GMT, hence the '-'
563 m_offset
= -GetTimeZone();
566 case wxDateTime::GMT_12
:
567 case wxDateTime::GMT_11
:
568 case wxDateTime::GMT_10
:
569 case wxDateTime::GMT_9
:
570 case wxDateTime::GMT_8
:
571 case wxDateTime::GMT_7
:
572 case wxDateTime::GMT_6
:
573 case wxDateTime::GMT_5
:
574 case wxDateTime::GMT_4
:
575 case wxDateTime::GMT_3
:
576 case wxDateTime::GMT_2
:
577 case wxDateTime::GMT_1
:
578 m_offset
= -3600*(wxDateTime::GMT0
- tz
);
581 case wxDateTime::GMT0
:
582 case wxDateTime::GMT1
:
583 case wxDateTime::GMT2
:
584 case wxDateTime::GMT3
:
585 case wxDateTime::GMT4
:
586 case wxDateTime::GMT5
:
587 case wxDateTime::GMT6
:
588 case wxDateTime::GMT7
:
589 case wxDateTime::GMT8
:
590 case wxDateTime::GMT9
:
591 case wxDateTime::GMT10
:
592 case wxDateTime::GMT11
:
593 case wxDateTime::GMT12
:
594 m_offset
= 3600*(tz
- wxDateTime::GMT0
);
597 case wxDateTime::A_CST
:
598 // Central Standard Time in use in Australia = UTC + 9.5
599 m_offset
= 60l*(9*60 + 30);
603 wxFAIL_MSG( _T("unknown time zone") );
607 // ----------------------------------------------------------------------------
609 // ----------------------------------------------------------------------------
612 bool wxDateTime::IsLeapYear(int year
, wxDateTime::Calendar cal
)
614 if ( year
== Inv_Year
)
615 year
= GetCurrentYear();
617 if ( cal
== Gregorian
)
619 // in Gregorian calendar leap years are those divisible by 4 except
620 // those divisible by 100 unless they're also divisible by 400
621 // (in some countries, like Russia and Greece, additional corrections
622 // exist, but they won't manifest themselves until 2700)
623 return (year
% 4 == 0) && ((year
% 100 != 0) || (year
% 400 == 0));
625 else if ( cal
== Julian
)
627 // in Julian calendar the rule is simpler
628 return year
% 4 == 0;
632 wxFAIL_MSG(_T("unknown calendar"));
639 int wxDateTime::GetCentury(int year
)
641 return year
> 0 ? year
/ 100 : year
/ 100 - 1;
645 int wxDateTime::ConvertYearToBC(int year
)
648 return year
> 0 ? year
: year
- 1;
652 int wxDateTime::GetCurrentYear(wxDateTime::Calendar cal
)
657 return Now().GetYear();
660 wxFAIL_MSG(_T("TODO"));
664 wxFAIL_MSG(_T("unsupported calendar"));
672 wxDateTime::Month
wxDateTime::GetCurrentMonth(wxDateTime::Calendar cal
)
677 return Now().GetMonth();
680 wxFAIL_MSG(_T("TODO"));
684 wxFAIL_MSG(_T("unsupported calendar"));
692 wxDateTime::wxDateTime_t
wxDateTime::GetNumberOfDays(int year
, Calendar cal
)
694 if ( year
== Inv_Year
)
696 // take the current year if none given
697 year
= GetCurrentYear();
704 return IsLeapYear(year
) ? 366 : 365;
707 wxFAIL_MSG(_T("unsupported calendar"));
715 wxDateTime::wxDateTime_t
wxDateTime::GetNumberOfDays(wxDateTime::Month month
,
717 wxDateTime::Calendar cal
)
719 wxCHECK_MSG( month
< MONTHS_IN_YEAR
, 0, _T("invalid month") );
721 if ( cal
== Gregorian
|| cal
== Julian
)
723 if ( year
== Inv_Year
)
725 // take the current year if none given
726 year
= GetCurrentYear();
729 return GetNumOfDaysInMonth(year
, month
);
733 wxFAIL_MSG(_T("unsupported calendar"));
740 wxString
wxDateTime::GetMonthName(wxDateTime::Month month
,
741 wxDateTime::NameFlags flags
)
743 wxCHECK_MSG( month
!= Inv_Month
, _T(""), _T("invalid month") );
745 // notice that we must set all the fields to avoid confusing libc (GNU one
746 // gets confused to a crash if we don't do this)
751 return CallStrftime(flags
== Name_Abbr
? _T("%b") : _T("%B"), &tm
);
755 wxString
wxDateTime::GetWeekDayName(wxDateTime::WeekDay wday
,
756 wxDateTime::NameFlags flags
)
758 wxCHECK_MSG( wday
!= Inv_WeekDay
, _T(""), _T("invalid weekday") );
760 // take some arbitrary Sunday
767 // and offset it by the number of days needed to get the correct wday
770 // call mktime() to normalize it...
773 // ... and call strftime()
774 return CallStrftime(flags
== Name_Abbr
? _T("%a") : _T("%A"), &tm
);
778 void wxDateTime::GetAmPmStrings(wxString
*am
, wxString
*pm
)
784 *am
= CallStrftime(_T("%p"), &tm
);
789 *pm
= CallStrftime(_T("%p"), &tm
);
793 // ----------------------------------------------------------------------------
794 // Country stuff: date calculations depend on the country (DST, work days,
795 // ...), so we need to know which rules to follow.
796 // ----------------------------------------------------------------------------
799 wxDateTime::Country
wxDateTime::GetCountry()
801 // TODO use LOCALE_ICOUNTRY setting under Win32
803 if ( ms_country
== Country_Unknown
)
805 // try to guess from the time zone name
806 time_t t
= time(NULL
);
807 struct tm
*tm
= localtime(&t
);
809 wxString tz
= CallStrftime(_T("%Z"), tm
);
810 if ( tz
== _T("WET") || tz
== _T("WEST") )
814 else if ( tz
== _T("CET") || tz
== _T("CEST") )
816 ms_country
= Country_EEC
;
818 else if ( tz
== _T("MSK") || tz
== _T("MSD") )
822 else if ( tz
== _T("AST") || tz
== _T("ADT") ||
823 tz
== _T("EST") || tz
== _T("EDT") ||
824 tz
== _T("CST") || tz
== _T("CDT") ||
825 tz
== _T("MST") || tz
== _T("MDT") ||
826 tz
== _T("PST") || tz
== _T("PDT") )
832 // well, choose a default one
841 void wxDateTime::SetCountry(wxDateTime::Country country
)
843 ms_country
= country
;
847 bool wxDateTime::IsWestEuropeanCountry(Country country
)
849 if ( country
== Country_Default
)
851 country
= GetCountry();
854 return (Country_WesternEurope_Start
<= country
) &&
855 (country
<= Country_WesternEurope_End
);
858 // ----------------------------------------------------------------------------
859 // DST calculations: we use 3 different rules for the West European countries,
860 // USA and for the rest of the world. This is undoubtedly false for many
861 // countries, but I lack the necessary info (and the time to gather it),
862 // please add the other rules here!
863 // ----------------------------------------------------------------------------
866 bool wxDateTime::IsDSTApplicable(int year
, Country country
)
868 if ( year
== Inv_Year
)
870 // take the current year if none given
871 year
= GetCurrentYear();
874 if ( country
== Country_Default
)
876 country
= GetCountry();
883 // DST was first observed in the US and UK during WWI, reused
884 // during WWII and used again since 1966
885 return year
>= 1966 ||
886 (year
>= 1942 && year
<= 1945) ||
887 (year
== 1918 || year
== 1919);
890 // assume that it started after WWII
896 wxDateTime
wxDateTime::GetBeginDST(int year
, Country country
)
898 if ( year
== Inv_Year
)
900 // take the current year if none given
901 year
= GetCurrentYear();
904 if ( country
== Country_Default
)
906 country
= GetCountry();
909 if ( !IsDSTApplicable(year
, country
) )
911 return wxInvalidDateTime
;
916 if ( IsWestEuropeanCountry(country
) || (country
== Russia
) )
918 // DST begins at 1 a.m. GMT on the last Sunday of March
919 if ( !dt
.SetToLastWeekDay(Sun
, Mar
, year
) )
922 wxFAIL_MSG( _T("no last Sunday in March?") );
925 dt
+= wxTimeSpan::Hours(1);
927 // disable DST tests because it could result in an infinite recursion!
930 else switch ( country
)
937 // don't know for sure - assume it was in effect all year
942 dt
.Set(1, Jan
, year
);
946 // DST was installed Feb 2, 1942 by the Congress
947 dt
.Set(2, Feb
, year
);
950 // Oil embargo changed the DST period in the US
952 dt
.Set(6, Jan
, 1974);
956 dt
.Set(23, Feb
, 1975);
960 // before 1986, DST begun on the last Sunday of April, but
961 // in 1986 Reagan changed it to begin at 2 a.m. of the
962 // first Sunday in April
965 if ( !dt
.SetToLastWeekDay(Sun
, Apr
, year
) )
968 wxFAIL_MSG( _T("no first Sunday in April?") );
973 if ( !dt
.SetToWeekDay(Sun
, 1, Apr
, year
) )
976 wxFAIL_MSG( _T("no first Sunday in April?") );
980 dt
+= wxTimeSpan::Hours(2);
982 // TODO what about timezone??
988 // assume Mar 30 as the start of the DST for the rest of the world
989 // - totally bogus, of course
990 dt
.Set(30, Mar
, year
);
997 wxDateTime
wxDateTime::GetEndDST(int year
, Country country
)
999 if ( year
== Inv_Year
)
1001 // take the current year if none given
1002 year
= GetCurrentYear();
1005 if ( country
== Country_Default
)
1007 country
= GetCountry();
1010 if ( !IsDSTApplicable(year
, country
) )
1012 return wxInvalidDateTime
;
1017 if ( IsWestEuropeanCountry(country
) || (country
== Russia
) )
1019 // DST ends at 1 a.m. GMT on the last Sunday of October
1020 if ( !dt
.SetToLastWeekDay(Sun
, Oct
, year
) )
1022 // weirder and weirder...
1023 wxFAIL_MSG( _T("no last Sunday in October?") );
1026 dt
+= wxTimeSpan::Hours(1);
1028 // disable DST tests because it could result in an infinite recursion!
1031 else switch ( country
)
1038 // don't know for sure - assume it was in effect all year
1042 dt
.Set(31, Dec
, year
);
1046 // the time was reset after the end of the WWII
1047 dt
.Set(30, Sep
, year
);
1051 // DST ends at 2 a.m. on the last Sunday of October
1052 if ( !dt
.SetToLastWeekDay(Sun
, Oct
, year
) )
1054 // weirder and weirder...
1055 wxFAIL_MSG( _T("no last Sunday in October?") );
1058 dt
+= wxTimeSpan::Hours(2);
1060 // TODO what about timezone??
1065 // assume October 26th as the end of the DST - totally bogus too
1066 dt
.Set(26, Oct
, year
);
1072 // ----------------------------------------------------------------------------
1073 // constructors and assignment operators
1074 // ----------------------------------------------------------------------------
1076 // return the current time with ms precision
1077 /* static */ wxDateTime
wxDateTime::UNow()
1079 return wxDateTime(wxGetLocalTimeMillis());
1082 // the values in the tm structure contain the local time
1083 wxDateTime
& wxDateTime::Set(const struct tm
& tm
)
1086 time_t timet
= mktime(&tm2
);
1088 if ( timet
== (time_t)-1 )
1090 // mktime() rather unintuitively fails for Jan 1, 1970 if the hour is
1091 // less than timezone - try to make it work for this case
1092 if ( tm2
.tm_year
== 70 && tm2
.tm_mon
== 0 && tm2
.tm_mday
== 1 )
1094 // add timezone to make sure that date is in range
1095 tm2
.tm_sec
-= GetTimeZone();
1097 timet
= mktime(&tm2
);
1098 if ( timet
!= (time_t)-1 )
1100 timet
+= GetTimeZone();
1106 wxFAIL_MSG( _T("mktime() failed") );
1108 *this = wxInvalidDateTime
;
1118 wxDateTime
& wxDateTime::Set(wxDateTime_t hour
,
1119 wxDateTime_t minute
,
1120 wxDateTime_t second
,
1121 wxDateTime_t millisec
)
1123 // we allow seconds to be 61 to account for the leap seconds, even if we
1124 // don't use them really
1125 wxDATETIME_CHECK( hour
< 24 &&
1129 _T("Invalid time in wxDateTime::Set()") );
1131 // get the current date from system
1132 struct tm
*tm
= GetTmNow();
1134 wxDATETIME_CHECK( tm
, _T("localtime() failed") );
1138 tm
->tm_min
= minute
;
1139 tm
->tm_sec
= second
;
1143 // and finally adjust milliseconds
1144 return SetMillisecond(millisec
);
1147 wxDateTime
& wxDateTime::Set(wxDateTime_t day
,
1151 wxDateTime_t minute
,
1152 wxDateTime_t second
,
1153 wxDateTime_t millisec
)
1155 wxDATETIME_CHECK( hour
< 24 &&
1159 _T("Invalid time in wxDateTime::Set()") );
1161 ReplaceDefaultYearMonthWithCurrent(&year
, &month
);
1163 wxDATETIME_CHECK( (0 < day
) && (day
<= GetNumberOfDays(month
, year
)),
1164 _T("Invalid date in wxDateTime::Set()") );
1166 // the range of time_t type (inclusive)
1167 static const int yearMinInRange
= 1970;
1168 static const int yearMaxInRange
= 2037;
1170 // test only the year instead of testing for the exact end of the Unix
1171 // time_t range - it doesn't bring anything to do more precise checks
1172 if ( year
>= yearMinInRange
&& year
<= yearMaxInRange
)
1174 // use the standard library version if the date is in range - this is
1175 // probably more efficient than our code
1177 tm
.tm_year
= year
- 1900;
1183 tm
.tm_isdst
= -1; // mktime() will guess it
1187 // and finally adjust milliseconds
1188 return SetMillisecond(millisec
);
1192 // do time calculations ourselves: we want to calculate the number of
1193 // milliseconds between the given date and the epoch
1195 // get the JDN for the midnight of this day
1196 m_time
= GetTruncatedJDN(day
, month
, year
);
1197 m_time
-= EPOCH_JDN
;
1198 m_time
*= SECONDS_PER_DAY
* TIME_T_FACTOR
;
1200 // JDN corresponds to GMT, we take localtime
1201 Add(wxTimeSpan(hour
, minute
, second
+ GetTimeZone(), millisec
));
1207 wxDateTime
& wxDateTime::Set(double jdn
)
1209 // so that m_time will be 0 for the midnight of Jan 1, 1970 which is jdn
1211 jdn
-= EPOCH_JDN
+ 0.5;
1213 jdn
*= MILLISECONDS_PER_DAY
;
1220 wxDateTime
& wxDateTime::ResetTime()
1224 if ( tm
.hour
|| tm
.min
|| tm
.sec
|| tm
.msec
)
1237 // ----------------------------------------------------------------------------
1238 // time_t <-> broken down time conversions
1239 // ----------------------------------------------------------------------------
1241 wxDateTime::Tm
wxDateTime::GetTm(const TimeZone
& tz
) const
1243 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1245 time_t time
= GetTicks();
1246 if ( time
!= (time_t)-1 )
1248 // use C RTL functions
1250 if ( tz
.GetOffset() == -GetTimeZone() )
1252 // we are working with local time
1253 tm
= localtime(&time
);
1255 // should never happen
1256 wxCHECK_MSG( tm
, Tm(), _T("localtime() failed") );
1260 time
+= (time_t)tz
.GetOffset();
1261 #if defined(__VMS__) || defined(__WATCOMC__) // time is unsigned so avoid warning
1262 int time2
= (int) time
;
1270 // should never happen
1271 wxCHECK_MSG( tm
, Tm(), _T("gmtime() failed") );
1275 tm
= (struct tm
*)NULL
;
1281 // adjust the milliseconds
1283 long timeOnly
= (m_time
% MILLISECONDS_PER_DAY
).ToLong();
1284 tm2
.msec
= (wxDateTime_t
)(timeOnly
% 1000);
1287 //else: use generic code below
1290 // remember the time and do the calculations with the date only - this
1291 // eliminates rounding errors of the floating point arithmetics
1293 wxLongLong timeMidnight
= m_time
+ tz
.GetOffset() * 1000;
1295 long timeOnly
= (timeMidnight
% MILLISECONDS_PER_DAY
).ToLong();
1297 // we want to always have positive time and timeMidnight to be really
1298 // the midnight before it
1301 timeOnly
= MILLISECONDS_PER_DAY
+ timeOnly
;
1304 timeMidnight
-= timeOnly
;
1306 // calculate the Gregorian date from JDN for the midnight of our date:
1307 // this will yield day, month (in 1..12 range) and year
1309 // actually, this is the JDN for the noon of the previous day
1310 long jdn
= (timeMidnight
/ MILLISECONDS_PER_DAY
).ToLong() + EPOCH_JDN
;
1312 // CREDIT: code below is by Scott E. Lee (but bugs are mine)
1314 wxASSERT_MSG( jdn
> -2, _T("JDN out of range") );
1316 // calculate the century
1317 long temp
= (jdn
+ JDN_OFFSET
) * 4 - 1;
1318 long century
= temp
/ DAYS_PER_400_YEARS
;
1320 // then the year and day of year (1 <= dayOfYear <= 366)
1321 temp
= ((temp
% DAYS_PER_400_YEARS
) / 4) * 4 + 3;
1322 long year
= (century
* 100) + (temp
/ DAYS_PER_4_YEARS
);
1323 long dayOfYear
= (temp
% DAYS_PER_4_YEARS
) / 4 + 1;
1325 // and finally the month and day of the month
1326 temp
= dayOfYear
* 5 - 3;
1327 long month
= temp
/ DAYS_PER_5_MONTHS
;
1328 long day
= (temp
% DAYS_PER_5_MONTHS
) / 5 + 1;
1330 // month is counted from March - convert to normal
1341 // year is offset by 4800
1344 // check that the algorithm gave us something reasonable
1345 wxASSERT_MSG( (0 < month
) && (month
<= 12), _T("invalid month") );
1346 wxASSERT_MSG( (1 <= day
) && (day
< 32), _T("invalid day") );
1348 // construct Tm from these values
1350 tm
.year
= (int)year
;
1351 tm
.mon
= (Month
)(month
- 1); // algorithm yields 1 for January, not 0
1352 tm
.mday
= (wxDateTime_t
)day
;
1353 tm
.msec
= (wxDateTime_t
)(timeOnly
% 1000);
1354 timeOnly
-= tm
.msec
;
1355 timeOnly
/= 1000; // now we have time in seconds
1357 tm
.sec
= (wxDateTime_t
)(timeOnly
% 60);
1359 timeOnly
/= 60; // now we have time in minutes
1361 tm
.min
= (wxDateTime_t
)(timeOnly
% 60);
1364 tm
.hour
= (wxDateTime_t
)(timeOnly
/ 60);
1369 wxDateTime
& wxDateTime::SetYear(int year
)
1371 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1380 wxDateTime
& wxDateTime::SetMonth(Month month
)
1382 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1391 wxDateTime
& wxDateTime::SetDay(wxDateTime_t mday
)
1393 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1402 wxDateTime
& wxDateTime::SetHour(wxDateTime_t hour
)
1404 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1413 wxDateTime
& wxDateTime::SetMinute(wxDateTime_t min
)
1415 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1424 wxDateTime
& wxDateTime::SetSecond(wxDateTime_t sec
)
1426 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1435 wxDateTime
& wxDateTime::SetMillisecond(wxDateTime_t millisecond
)
1437 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1439 // we don't need to use GetTm() for this one
1440 m_time
-= m_time
% 1000l;
1441 m_time
+= millisecond
;
1446 // ----------------------------------------------------------------------------
1447 // wxDateTime arithmetics
1448 // ----------------------------------------------------------------------------
1450 wxDateTime
& wxDateTime::Add(const wxDateSpan
& diff
)
1454 tm
.year
+= diff
.GetYears();
1455 tm
.AddMonths(diff
.GetMonths());
1457 // check that the resulting date is valid
1458 if ( tm
.mday
> GetNumOfDaysInMonth(tm
.year
, tm
.mon
) )
1460 // We suppose that when adding one month to Jan 31 we want to get Feb
1461 // 28 (or 29), i.e. adding a month to the last day of the month should
1462 // give the last day of the next month which is quite logical.
1464 // Unfortunately, there is no logic way to understand what should
1465 // Jan 30 + 1 month be - Feb 28 too or Feb 27 (assuming non leap year)?
1466 // We make it Feb 28 (last day too), but it is highly questionable.
1467 tm
.mday
= GetNumOfDaysInMonth(tm
.year
, tm
.mon
);
1470 tm
.AddDays(diff
.GetTotalDays());
1474 wxASSERT_MSG( IsSameTime(tm
),
1475 _T("Add(wxDateSpan) shouldn't modify time") );
1480 // ----------------------------------------------------------------------------
1481 // Weekday and monthday stuff
1482 // ----------------------------------------------------------------------------
1484 bool wxDateTime::SetToTheWeek(wxDateTime_t numWeek
, WeekDay weekday
)
1486 int year
= GetYear();
1488 // Jan 4 always lies in the 1st week of the year
1490 SetToWeekDayInSameWeek(weekday
) += wxDateSpan::Weeks(numWeek
);
1492 if ( GetYear() != year
)
1494 // oops... numWeek was too big
1501 wxDateTime
& wxDateTime::SetToLastMonthDay(Month month
,
1504 // take the current month/year if none specified
1505 if ( year
== Inv_Year
)
1507 if ( month
== Inv_Month
)
1510 return Set(GetNumOfDaysInMonth(year
, month
), month
, year
);
1513 wxDateTime
& wxDateTime::SetToWeekDayInSameWeek(WeekDay weekday
)
1515 wxDATETIME_CHECK( weekday
!= Inv_WeekDay
, _T("invalid weekday") );
1517 WeekDay wdayThis
= GetWeekDay();
1518 if ( weekday
== wdayThis
)
1523 else if ( weekday
< wdayThis
)
1525 return Subtract(wxDateSpan::Days(wdayThis
- weekday
));
1527 else // weekday > wdayThis
1529 return Add(wxDateSpan::Days(weekday
- wdayThis
));
1533 wxDateTime
& wxDateTime::SetToNextWeekDay(WeekDay weekday
)
1535 wxDATETIME_CHECK( weekday
!= Inv_WeekDay
, _T("invalid weekday") );
1538 WeekDay wdayThis
= GetWeekDay();
1539 if ( weekday
== wdayThis
)
1544 else if ( weekday
< wdayThis
)
1546 // need to advance a week
1547 diff
= 7 - (wdayThis
- weekday
);
1549 else // weekday > wdayThis
1551 diff
= weekday
- wdayThis
;
1554 return Add(wxDateSpan::Days(diff
));
1557 wxDateTime
& wxDateTime::SetToPrevWeekDay(WeekDay weekday
)
1559 wxDATETIME_CHECK( weekday
!= Inv_WeekDay
, _T("invalid weekday") );
1562 WeekDay wdayThis
= GetWeekDay();
1563 if ( weekday
== wdayThis
)
1568 else if ( weekday
> wdayThis
)
1570 // need to go to previous week
1571 diff
= 7 - (weekday
- wdayThis
);
1573 else // weekday < wdayThis
1575 diff
= wdayThis
- weekday
;
1578 return Subtract(wxDateSpan::Days(diff
));
1581 bool wxDateTime::SetToWeekDay(WeekDay weekday
,
1586 wxCHECK_MSG( weekday
!= Inv_WeekDay
, FALSE
, _T("invalid weekday") );
1588 // we don't check explicitly that -5 <= n <= 5 because we will return FALSE
1589 // anyhow in such case - but may be should still give an assert for it?
1591 // take the current month/year if none specified
1592 ReplaceDefaultYearMonthWithCurrent(&year
, &month
);
1596 // TODO this probably could be optimised somehow...
1600 // get the first day of the month
1601 dt
.Set(1, month
, year
);
1604 WeekDay wdayFirst
= dt
.GetWeekDay();
1606 // go to the first weekday of the month
1607 int diff
= weekday
- wdayFirst
;
1611 // add advance n-1 weeks more
1614 dt
+= wxDateSpan::Days(diff
);
1616 else // count from the end of the month
1618 // get the last day of the month
1619 dt
.SetToLastMonthDay(month
, year
);
1622 WeekDay wdayLast
= dt
.GetWeekDay();
1624 // go to the last weekday of the month
1625 int diff
= wdayLast
- weekday
;
1629 // and rewind n-1 weeks from there
1632 dt
-= wxDateSpan::Days(diff
);
1635 // check that it is still in the same month
1636 if ( dt
.GetMonth() == month
)
1644 // no such day in this month
1649 wxDateTime::wxDateTime_t
wxDateTime::GetDayOfYear(const TimeZone
& tz
) const
1653 return gs_cumulatedDays
[IsLeapYear(tm
.year
)][tm
.mon
] + tm
.mday
;
1656 wxDateTime::wxDateTime_t
wxDateTime::GetWeekOfYear(wxDateTime::WeekFlags flags
,
1657 const TimeZone
& tz
) const
1659 if ( flags
== Default_First
)
1661 flags
= GetCountry() == USA
? Sunday_First
: Monday_First
;
1664 wxDateTime_t nDayInYear
= GetDayOfYear(tz
);
1667 WeekDay wd
= GetWeekDay(tz
);
1668 if ( flags
== Sunday_First
)
1670 week
= (nDayInYear
- wd
+ 7) / 7;
1674 // have to shift the week days values
1675 week
= (nDayInYear
- (wd
- 1 + 7) % 7 + 7) / 7;
1678 // FIXME some more elegant way??
1679 WeekDay wdYearStart
= wxDateTime(1, Jan
, GetYear()).GetWeekDay();
1680 if ( wdYearStart
== Wed
|| wdYearStart
== Thu
)
1688 wxDateTime::wxDateTime_t
wxDateTime::GetWeekOfMonth(wxDateTime::WeekFlags flags
,
1689 const TimeZone
& tz
) const
1692 wxDateTime dtMonthStart
= wxDateTime(1, tm
.mon
, tm
.year
);
1693 int nWeek
= GetWeekOfYear(flags
) - dtMonthStart
.GetWeekOfYear(flags
) + 1;
1696 // this may happen for January when Jan, 1 is the last week of the
1698 nWeek
+= IsLeapYear(tm
.year
- 1) ? 53 : 52;
1701 return (wxDateTime::wxDateTime_t
)nWeek
;
1704 wxDateTime
& wxDateTime::SetToYearDay(wxDateTime::wxDateTime_t yday
)
1706 int year
= GetYear();
1707 wxDATETIME_CHECK( (0 < yday
) && (yday
<= GetNumberOfDays(year
)),
1708 _T("invalid year day") );
1710 bool isLeap
= IsLeapYear(year
);
1711 for ( Month mon
= Jan
; mon
< Inv_Month
; wxNextMonth(mon
) )
1713 // for Dec, we can't compare with gs_cumulatedDays[mon + 1], but we
1714 // don't need it neither - because of the CHECK above we know that
1715 // yday lies in December then
1716 if ( (mon
== Dec
) || (yday
< gs_cumulatedDays
[isLeap
][mon
+ 1]) )
1718 Set(yday
- gs_cumulatedDays
[isLeap
][mon
], mon
, year
);
1727 // ----------------------------------------------------------------------------
1728 // Julian day number conversion and related stuff
1729 // ----------------------------------------------------------------------------
1731 double wxDateTime::GetJulianDayNumber() const
1733 // JDN are always expressed for the GMT dates
1734 Tm
tm(ToTimezone(GMT0
).GetTm(GMT0
));
1736 double result
= GetTruncatedJDN(tm
.mday
, tm
.mon
, tm
.year
);
1738 // add the part GetTruncatedJDN() neglected
1741 // and now add the time: 86400 sec = 1 JDN
1742 return result
+ ((double)(60*(60*tm
.hour
+ tm
.min
) + tm
.sec
)) / 86400;
1745 double wxDateTime::GetRataDie() const
1747 // March 1 of the year 0 is Rata Die day -306 and JDN 1721119.5
1748 return GetJulianDayNumber() - 1721119.5 - 306;
1751 // ----------------------------------------------------------------------------
1752 // timezone and DST stuff
1753 // ----------------------------------------------------------------------------
1755 int wxDateTime::IsDST(wxDateTime::Country country
) const
1757 wxCHECK_MSG( country
== Country_Default
, -1,
1758 _T("country support not implemented") );
1760 // use the C RTL for the dates in the standard range
1761 time_t timet
= GetTicks();
1762 if ( timet
!= (time_t)-1 )
1764 tm
*tm
= localtime(&timet
);
1766 wxCHECK_MSG( tm
, -1, _T("localtime() failed") );
1768 return tm
->tm_isdst
;
1772 int year
= GetYear();
1774 if ( !IsDSTApplicable(year
, country
) )
1776 // no DST time in this year in this country
1780 return IsBetween(GetBeginDST(year
, country
), GetEndDST(year
, country
));
1784 wxDateTime
& wxDateTime::MakeTimezone(const TimeZone
& tz
, bool noDST
)
1786 long secDiff
= GetTimeZone() + tz
.GetOffset();
1788 // we need to know whether DST is or not in effect for this date unless
1789 // the test disabled by the caller
1790 if ( !noDST
&& (IsDST() == 1) )
1792 // FIXME we assume that the DST is always shifted by 1 hour
1796 return Subtract(wxTimeSpan::Seconds(secDiff
));
1799 // ----------------------------------------------------------------------------
1800 // wxDateTime to/from text representations
1801 // ----------------------------------------------------------------------------
1803 wxString
wxDateTime::Format(const wxChar
*format
, const TimeZone
& tz
) const
1805 wxCHECK_MSG( format
, _T(""), _T("NULL format in wxDateTime::Format") );
1807 // we have to use our own implementation if the date is out of range of
1808 // strftime() or if we use non standard specificators
1809 time_t time
= GetTicks();
1810 if ( (time
!= (time_t)-1) && !wxStrstr(format
, _T("%l")) )
1814 if ( tz
.GetOffset() == -GetTimeZone() )
1816 // we are working with local time
1817 tm
= localtime(&time
);
1819 // should never happen
1820 wxCHECK_MSG( tm
, wxEmptyString
, _T("localtime() failed") );
1824 time
+= (int)tz
.GetOffset();
1826 #if defined(__VMS__) || defined(__WATCOMC__) // time is unsigned so avoid warning
1827 int time2
= (int) time
;
1835 // should never happen
1836 wxCHECK_MSG( tm
, wxEmptyString
, _T("gmtime() failed") );
1840 tm
= (struct tm
*)NULL
;
1846 return CallStrftime(format
, tm
);
1848 //else: use generic code below
1851 // we only parse ANSI C format specifications here, no POSIX 2
1852 // complications, no GNU extensions but we do add support for a "%l" format
1853 // specifier allowing to get the number of milliseconds
1856 // used for calls to strftime() when we only deal with time
1857 struct tm tmTimeOnly
;
1858 tmTimeOnly
.tm_hour
= tm
.hour
;
1859 tmTimeOnly
.tm_min
= tm
.min
;
1860 tmTimeOnly
.tm_sec
= tm
.sec
;
1861 tmTimeOnly
.tm_wday
= 0;
1862 tmTimeOnly
.tm_yday
= 0;
1863 tmTimeOnly
.tm_mday
= 1; // any date will do
1864 tmTimeOnly
.tm_mon
= 0;
1865 tmTimeOnly
.tm_year
= 76;
1866 tmTimeOnly
.tm_isdst
= 0; // no DST, we adjust for tz ourselves
1868 wxString tmp
, res
, fmt
;
1869 for ( const wxChar
*p
= format
; *p
; p
++ )
1871 if ( *p
!= _T('%') )
1879 // set the default format
1882 case _T('Y'): // year has 4 digits
1886 case _T('j'): // day of year has 3 digits
1887 case _T('l'): // milliseconds have 3 digits
1892 // it's either another valid format specifier in which case
1893 // the format is "%02d" (for all the rest) or we have the
1894 // field width preceding the format in which case it will
1895 // override the default format anyhow
1899 bool restart
= TRUE
;
1904 // start of the format specification
1907 case _T('a'): // a weekday name
1909 // second parameter should be TRUE for abbreviated names
1910 res
+= GetWeekDayName(tm
.GetWeekDay(),
1911 *p
== _T('a') ? Name_Abbr
: Name_Full
);
1914 case _T('b'): // a month name
1916 res
+= GetMonthName(tm
.mon
,
1917 *p
== _T('b') ? Name_Abbr
: Name_Full
);
1920 case _T('c'): // locale default date and time representation
1921 case _T('x'): // locale default date representation
1923 // the problem: there is no way to know what do these format
1924 // specifications correspond to for the current locale.
1926 // the solution: use a hack and still use strftime(): first
1927 // find the YEAR which is a year in the strftime() range (1970
1928 // - 2038) whose Jan 1 falls on the same week day as the Jan 1
1929 // of the real year. Then make a copy of the format and
1930 // replace all occurences of YEAR in it with some unique
1931 // string not appearing anywhere else in it, then use
1932 // strftime() to format the date in year YEAR and then replace
1933 // YEAR back by the real year and the unique replacement
1934 // string back with YEAR. Notice that "all occurences of YEAR"
1935 // means all occurences of 4 digit as well as 2 digit form!
1937 // the bugs: we assume that neither of %c nor %x contains any
1938 // fields which may change between the YEAR and real year. For
1939 // example, the week number (%U, %W) and the day number (%j)
1940 // will change if one of these years is leap and the other one
1943 // find the YEAR: normally, for any year X, Jan 1 or the
1944 // year X + 28 is the same weekday as Jan 1 of X (because
1945 // the weekday advances by 1 for each normal X and by 2
1946 // for each leap X, hence by 5 every 4 years or by 35
1947 // which is 0 mod 7 every 28 years) but this rule breaks
1948 // down if there are years between X and Y which are
1949 // divisible by 4 but not leap (i.e. divisible by 100 but
1950 // not 400), hence the correction.
1952 int yearReal
= GetYear(tz
);
1953 int mod28
= yearReal
% 28;
1955 // be careful to not go too far - we risk to leave the
1960 year
= 1988 + mod28
; // 1988 == 0 (mod 28)
1964 year
= 1970 + mod28
- 10; // 1970 == 10 (mod 28)
1967 int nCentury
= year
/ 100,
1968 nCenturyReal
= yearReal
/ 100;
1970 // need to adjust for the years divisble by 400 which are
1971 // not leap but are counted like leap ones if we just take
1972 // the number of centuries in between for nLostWeekDays
1973 int nLostWeekDays
= (nCentury
- nCenturyReal
) -
1974 (nCentury
/ 4 - nCenturyReal
/ 4);
1976 // we have to gain back the "lost" weekdays: note that the
1977 // effect of this loop is to not do anything to
1978 // nLostWeekDays (which we won't use any more), but to
1979 // (indirectly) set the year correctly
1980 while ( (nLostWeekDays
% 7) != 0 )
1982 nLostWeekDays
+= year
++ % 4 ? 1 : 2;
1985 // at any rate, we couldn't go further than 1988 + 9 + 28!
1986 wxASSERT_MSG( year
< 2030,
1987 _T("logic error in wxDateTime::Format") );
1989 wxString strYear
, strYear2
;
1990 strYear
.Printf(_T("%d"), year
);
1991 strYear2
.Printf(_T("%d"), year
% 100);
1993 // find two strings not occuring in format (this is surely
1994 // not optimal way of doing it... improvements welcome!)
1995 wxString fmt
= format
;
1996 wxString replacement
= (wxChar
)-1;
1997 while ( fmt
.Find(replacement
) != wxNOT_FOUND
)
1999 replacement
<< (wxChar
)-1;
2002 wxString replacement2
= (wxChar
)-2;
2003 while ( fmt
.Find(replacement
) != wxNOT_FOUND
)
2005 replacement
<< (wxChar
)-2;
2008 // replace all occurences of year with it
2009 bool wasReplaced
= fmt
.Replace(strYear
, replacement
) > 0;
2011 wasReplaced
= fmt
.Replace(strYear2
, replacement2
) > 0;
2013 // use strftime() to format the same date but in supported
2016 // NB: we assume that strftime() doesn't check for the
2017 // date validity and will happily format the date
2018 // corresponding to Feb 29 of a non leap year (which
2019 // may happen if yearReal was leap and year is not)
2020 struct tm tmAdjusted
;
2022 tmAdjusted
.tm_hour
= tm
.hour
;
2023 tmAdjusted
.tm_min
= tm
.min
;
2024 tmAdjusted
.tm_sec
= tm
.sec
;
2025 tmAdjusted
.tm_wday
= tm
.GetWeekDay();
2026 tmAdjusted
.tm_yday
= GetDayOfYear();
2027 tmAdjusted
.tm_mday
= tm
.mday
;
2028 tmAdjusted
.tm_mon
= tm
.mon
;
2029 tmAdjusted
.tm_year
= year
- 1900;
2030 tmAdjusted
.tm_isdst
= 0; // no DST, already adjusted
2031 wxString str
= CallStrftime(*p
== _T('c') ? _T("%c")
2035 // now replace the occurence of 1999 with the real year
2036 wxString strYearReal
, strYearReal2
;
2037 strYearReal
.Printf(_T("%04d"), yearReal
);
2038 strYearReal2
.Printf(_T("%02d"), yearReal
% 100);
2039 str
.Replace(strYear
, strYearReal
);
2040 str
.Replace(strYear2
, strYearReal2
);
2042 // and replace back all occurences of replacement string
2045 str
.Replace(replacement2
, strYear2
);
2046 str
.Replace(replacement
, strYear
);
2053 case _T('d'): // day of a month (01-31)
2054 res
+= wxString::Format(fmt
, tm
.mday
);
2057 case _T('H'): // hour in 24h format (00-23)
2058 res
+= wxString::Format(fmt
, tm
.hour
);
2061 case _T('I'): // hour in 12h format (01-12)
2063 // 24h -> 12h, 0h -> 12h too
2064 int hour12
= tm
.hour
> 12 ? tm
.hour
- 12
2065 : tm
.hour
? tm
.hour
: 12;
2066 res
+= wxString::Format(fmt
, hour12
);
2070 case _T('j'): // day of the year
2071 res
+= wxString::Format(fmt
, GetDayOfYear(tz
));
2074 case _T('l'): // milliseconds (NOT STANDARD)
2075 res
+= wxString::Format(fmt
, GetMillisecond(tz
));
2078 case _T('m'): // month as a number (01-12)
2079 res
+= wxString::Format(fmt
, tm
.mon
+ 1);
2082 case _T('M'): // minute as a decimal number (00-59)
2083 res
+= wxString::Format(fmt
, tm
.min
);
2086 case _T('p'): // AM or PM string
2087 res
+= CallStrftime(_T("%p"), &tmTimeOnly
);
2090 case _T('S'): // second as a decimal number (00-61)
2091 res
+= wxString::Format(fmt
, tm
.sec
);
2094 case _T('U'): // week number in the year (Sunday 1st week day)
2095 res
+= wxString::Format(fmt
, GetWeekOfYear(Sunday_First
, tz
));
2098 case _T('W'): // week number in the year (Monday 1st week day)
2099 res
+= wxString::Format(fmt
, GetWeekOfYear(Monday_First
, tz
));
2102 case _T('w'): // weekday as a number (0-6), Sunday = 0
2103 res
+= wxString::Format(fmt
, tm
.GetWeekDay());
2106 // case _T('x'): -- handled with "%c"
2108 case _T('X'): // locale default time representation
2109 // just use strftime() to format the time for us
2110 res
+= CallStrftime(_T("%X"), &tmTimeOnly
);
2113 case _T('y'): // year without century (00-99)
2114 res
+= wxString::Format(fmt
, tm
.year
% 100);
2117 case _T('Y'): // year with century
2118 res
+= wxString::Format(fmt
, tm
.year
);
2121 case _T('Z'): // timezone name
2122 res
+= CallStrftime(_T("%Z"), &tmTimeOnly
);
2126 // is it the format width?
2128 while ( *p
== _T('-') || *p
== _T('+') ||
2129 *p
== _T(' ') || wxIsdigit(*p
) )
2134 if ( !fmt
.IsEmpty() )
2136 // we've only got the flags and width so far in fmt
2137 fmt
.Prepend(_T('%'));
2138 fmt
.Append(_T('d'));
2145 // no, it wasn't the width
2146 wxFAIL_MSG(_T("unknown format specificator"));
2148 // fall through and just copy it nevertheless
2150 case _T('%'): // a percent sign
2154 case 0: // the end of string
2155 wxFAIL_MSG(_T("missing format at the end of string"));
2157 // just put the '%' which was the last char in format
2167 // this function parses a string in (strict) RFC 822 format: see the section 5
2168 // of the RFC for the detailed description, but briefly it's something of the
2169 // form "Sat, 18 Dec 1999 00:48:30 +0100"
2171 // this function is "strict" by design - it must reject anything except true
2172 // RFC822 time specs.
2174 // TODO a great candidate for using reg exps
2175 const wxChar
*wxDateTime::ParseRfc822Date(const wxChar
* date
)
2177 wxCHECK_MSG( date
, (wxChar
*)NULL
, _T("NULL pointer in wxDateTime::Parse") );
2179 const wxChar
*p
= date
;
2180 const wxChar
*comma
= wxStrchr(p
, _T(','));
2183 // the part before comma is the weekday
2185 // skip it for now - we don't use but might check that it really
2186 // corresponds to the specfied date
2189 if ( *p
!= _T(' ') )
2191 wxLogDebug(_T("no space after weekday in RFC822 time spec"));
2193 return (wxChar
*)NULL
;
2199 // the following 1 or 2 digits are the day number
2200 if ( !wxIsdigit(*p
) )
2202 wxLogDebug(_T("day number expected in RFC822 time spec, none found"));
2204 return (wxChar
*)NULL
;
2207 wxDateTime_t day
= *p
++ - _T('0');
2208 if ( wxIsdigit(*p
) )
2211 day
+= *p
++ - _T('0');
2214 if ( *p
++ != _T(' ') )
2216 return (wxChar
*)NULL
;
2219 // the following 3 letters specify the month
2220 wxString
monName(p
, 3);
2222 if ( monName
== _T("Jan") )
2224 else if ( monName
== _T("Feb") )
2226 else if ( monName
== _T("Mar") )
2228 else if ( monName
== _T("Apr") )
2230 else if ( monName
== _T("May") )
2232 else if ( monName
== _T("Jun") )
2234 else if ( monName
== _T("Jul") )
2236 else if ( monName
== _T("Aug") )
2238 else if ( monName
== _T("Sep") )
2240 else if ( monName
== _T("Oct") )
2242 else if ( monName
== _T("Nov") )
2244 else if ( monName
== _T("Dec") )
2248 wxLogDebug(_T("Invalid RFC 822 month name '%s'"), monName
.c_str());
2250 return (wxChar
*)NULL
;
2255 if ( *p
++ != _T(' ') )
2257 return (wxChar
*)NULL
;
2261 if ( !wxIsdigit(*p
) )
2264 return (wxChar
*)NULL
;
2267 int year
= *p
++ - _T('0');
2269 if ( !wxIsdigit(*p
) )
2271 // should have at least 2 digits in the year
2272 return (wxChar
*)NULL
;
2276 year
+= *p
++ - _T('0');
2278 // is it a 2 digit year (as per original RFC 822) or a 4 digit one?
2279 if ( wxIsdigit(*p
) )
2282 year
+= *p
++ - _T('0');
2284 if ( !wxIsdigit(*p
) )
2286 // no 3 digit years please
2287 return (wxChar
*)NULL
;
2291 year
+= *p
++ - _T('0');
2294 if ( *p
++ != _T(' ') )
2296 return (wxChar
*)NULL
;
2299 // time is in the format hh:mm:ss and seconds are optional
2300 if ( !wxIsdigit(*p
) )
2302 return (wxChar
*)NULL
;
2305 wxDateTime_t hour
= *p
++ - _T('0');
2307 if ( !wxIsdigit(*p
) )
2309 return (wxChar
*)NULL
;
2313 hour
+= *p
++ - _T('0');
2315 if ( *p
++ != _T(':') )
2317 return (wxChar
*)NULL
;
2320 if ( !wxIsdigit(*p
) )
2322 return (wxChar
*)NULL
;
2325 wxDateTime_t min
= *p
++ - _T('0');
2327 if ( !wxIsdigit(*p
) )
2329 return (wxChar
*)NULL
;
2333 min
+= *p
++ - _T('0');
2335 wxDateTime_t sec
= 0;
2336 if ( *p
++ == _T(':') )
2338 if ( !wxIsdigit(*p
) )
2340 return (wxChar
*)NULL
;
2343 sec
= *p
++ - _T('0');
2345 if ( !wxIsdigit(*p
) )
2347 return (wxChar
*)NULL
;
2351 sec
+= *p
++ - _T('0');
2354 if ( *p
++ != _T(' ') )
2356 return (wxChar
*)NULL
;
2359 // and now the interesting part: the timezone
2361 if ( *p
== _T('-') || *p
== _T('+') )
2363 // the explicit offset given: it has the form of hhmm
2364 bool plus
= *p
++ == _T('+');
2366 if ( !wxIsdigit(*p
) || !wxIsdigit(*(p
+ 1)) )
2368 return (wxChar
*)NULL
;
2372 offset
= 60*(10*(*p
- _T('0')) + (*(p
+ 1) - _T('0')));
2376 if ( !wxIsdigit(*p
) || !wxIsdigit(*(p
+ 1)) )
2378 return (wxChar
*)NULL
;
2382 offset
+= 10*(*p
- _T('0')) + (*(p
+ 1) - _T('0'));
2393 // the symbolic timezone given: may be either military timezone or one
2394 // of standard abbreviations
2397 // military: Z = UTC, J unused, A = -1, ..., Y = +12
2398 static const int offsets
[26] =
2400 //A B C D E F G H I J K L M
2401 -1, -2, -3, -4, -5, -6, -7, -8, -9, 0, -10, -11, -12,
2402 //N O P R Q S T U V W Z Y Z
2403 +1, +2, +3, +4, +5, +6, +7, +8, +9, +10, +11, +12, 0
2406 if ( *p
< _T('A') || *p
> _T('Z') || *p
== _T('J') )
2408 wxLogDebug(_T("Invalid militaty timezone '%c'"), *p
);
2410 return (wxChar
*)NULL
;
2413 offset
= offsets
[*p
++ - _T('A')];
2419 if ( tz
== _T("UT") || tz
== _T("UTC") || tz
== _T("GMT") )
2421 else if ( tz
== _T("AST") )
2422 offset
= AST
- GMT0
;
2423 else if ( tz
== _T("ADT") )
2424 offset
= ADT
- GMT0
;
2425 else if ( tz
== _T("EST") )
2426 offset
= EST
- GMT0
;
2427 else if ( tz
== _T("EDT") )
2428 offset
= EDT
- GMT0
;
2429 else if ( tz
== _T("CST") )
2430 offset
= CST
- GMT0
;
2431 else if ( tz
== _T("CDT") )
2432 offset
= CDT
- GMT0
;
2433 else if ( tz
== _T("MST") )
2434 offset
= MST
- GMT0
;
2435 else if ( tz
== _T("MDT") )
2436 offset
= MDT
- GMT0
;
2437 else if ( tz
== _T("PST") )
2438 offset
= PST
- GMT0
;
2439 else if ( tz
== _T("PDT") )
2440 offset
= PDT
- GMT0
;
2443 wxLogDebug(_T("Unknown RFC 822 timezone '%s'"), p
);
2445 return (wxChar
*)NULL
;
2455 // the spec was correct
2456 Set(day
, mon
, year
, hour
, min
, sec
);
2457 MakeTimezone((wxDateTime_t
)(60*offset
));
2462 const wxChar
*wxDateTime::ParseFormat(const wxChar
*date
,
2463 const wxChar
*format
,
2464 const wxDateTime
& dateDef
)
2466 wxCHECK_MSG( date
&& format
, (wxChar
*)NULL
,
2467 _T("NULL pointer in wxDateTime::ParseFormat()") );
2472 // what fields have we found?
2473 bool haveWDay
= FALSE
,
2482 bool hourIsIn12hFormat
= FALSE
, // or in 24h one?
2483 isPM
= FALSE
; // AM by default
2485 // and the value of the items we have (init them to get rid of warnings)
2486 wxDateTime_t sec
= 0,
2489 WeekDay wday
= Inv_WeekDay
;
2490 wxDateTime_t yday
= 0,
2492 wxDateTime::Month mon
= Inv_Month
;
2495 const wxChar
*input
= date
;
2496 for ( const wxChar
*fmt
= format
; *fmt
; fmt
++ )
2498 if ( *fmt
!= _T('%') )
2500 if ( wxIsspace(*fmt
) )
2502 // a white space in the format string matches 0 or more white
2503 // spaces in the input
2504 while ( wxIsspace(*input
) )
2511 // any other character (not whitespace, not '%') must be
2512 // matched by itself in the input
2513 if ( *input
++ != *fmt
)
2516 return (wxChar
*)NULL
;
2520 // done with this format char
2524 // start of a format specification
2526 // parse the optional width
2528 while ( isdigit(*++fmt
) )
2531 width
+= *fmt
- _T('0');
2534 // then the format itself
2537 case _T('a'): // a weekday name
2540 int flag
= *fmt
== _T('a') ? Name_Abbr
: Name_Full
;
2541 wday
= GetWeekDayFromName(GetAlphaToken(input
), flag
);
2542 if ( wday
== Inv_WeekDay
)
2545 return (wxChar
*)NULL
;
2551 case _T('b'): // a month name
2554 int flag
= *fmt
== _T('b') ? Name_Abbr
: Name_Full
;
2555 mon
= GetMonthFromName(GetAlphaToken(input
), flag
);
2556 if ( mon
== Inv_Month
)
2559 return (wxChar
*)NULL
;
2565 case _T('c'): // locale default date and time representation
2569 // this is the format which corresponds to ctime() output
2570 // and strptime("%c") should parse it, so try it first
2571 static const wxChar
*fmtCtime
= _T("%a %b %d %H:%M:%S %Y");
2573 const wxChar
*result
= dt
.ParseFormat(input
, fmtCtime
);
2576 result
= dt
.ParseFormat(input
, _T("%x %X"));
2581 result
= dt
.ParseFormat(input
, _T("%X %x"));
2586 // we've tried everything and still no match
2587 return (wxChar
*)NULL
;
2592 haveDay
= haveMon
= haveYear
=
2593 haveHour
= haveMin
= haveSec
= TRUE
;
2607 case _T('d'): // day of a month (01-31)
2608 if ( !GetNumericToken(width
, input
, &num
) ||
2609 (num
> 31) || (num
< 1) )
2612 return (wxChar
*)NULL
;
2615 // we can't check whether the day range is correct yet, will
2616 // do it later - assume ok for now
2618 mday
= (wxDateTime_t
)num
;
2621 case _T('H'): // hour in 24h format (00-23)
2622 if ( !GetNumericToken(width
, input
, &num
) || (num
> 23) )
2625 return (wxChar
*)NULL
;
2629 hour
= (wxDateTime_t
)num
;
2632 case _T('I'): // hour in 12h format (01-12)
2633 if ( !GetNumericToken(width
, input
, &num
) || !num
|| (num
> 12) )
2636 return (wxChar
*)NULL
;
2640 hourIsIn12hFormat
= TRUE
;
2641 hour
= (wxDateTime_t
)(num
% 12); // 12 should be 0
2644 case _T('j'): // day of the year
2645 if ( !GetNumericToken(width
, input
, &num
) || !num
|| (num
> 366) )
2648 return (wxChar
*)NULL
;
2652 yday
= (wxDateTime_t
)num
;
2655 case _T('m'): // month as a number (01-12)
2656 if ( !GetNumericToken(width
, input
, &num
) || !num
|| (num
> 12) )
2659 return (wxChar
*)NULL
;
2663 mon
= (Month
)(num
- 1);
2666 case _T('M'): // minute as a decimal number (00-59)
2667 if ( !GetNumericToken(width
, input
, &num
) || (num
> 59) )
2670 return (wxChar
*)NULL
;
2674 min
= (wxDateTime_t
)num
;
2677 case _T('p'): // AM or PM string
2679 wxString am
, pm
, token
= GetAlphaToken(input
);
2681 GetAmPmStrings(&am
, &pm
);
2682 if ( token
.CmpNoCase(pm
) == 0 )
2686 else if ( token
.CmpNoCase(am
) != 0 )
2689 return (wxChar
*)NULL
;
2694 case _T('r'): // time as %I:%M:%S %p
2697 input
= dt
.ParseFormat(input
, _T("%I:%M:%S %p"));
2701 return (wxChar
*)NULL
;
2704 haveHour
= haveMin
= haveSec
= TRUE
;
2713 case _T('R'): // time as %H:%M
2716 input
= dt
.ParseFormat(input
, _T("%H:%M"));
2720 return (wxChar
*)NULL
;
2723 haveHour
= haveMin
= TRUE
;
2730 case _T('S'): // second as a decimal number (00-61)
2731 if ( !GetNumericToken(width
, input
, &num
) || (num
> 61) )
2734 return (wxChar
*)NULL
;
2738 sec
= (wxDateTime_t
)num
;
2741 case _T('T'): // time as %H:%M:%S
2744 input
= dt
.ParseFormat(input
, _T("%H:%M:%S"));
2748 return (wxChar
*)NULL
;
2751 haveHour
= haveMin
= haveSec
= TRUE
;
2760 case _T('w'): // weekday as a number (0-6), Sunday = 0
2761 if ( !GetNumericToken(width
, input
, &num
) || (wday
> 6) )
2764 return (wxChar
*)NULL
;
2768 wday
= (WeekDay
)num
;
2771 case _T('x'): // locale default date representation
2772 #ifdef HAVE_STRPTIME
2773 // try using strptime() - it may fail even if the input is
2774 // correct but the date is out of range, so we will fall back
2775 // to our generic code anyhow (FIXME !Unicode friendly)
2778 const wxChar
*result
= strptime(input
, "%x", &tm
);
2783 haveDay
= haveMon
= haveYear
= TRUE
;
2785 year
= 1900 + tm
.tm_year
;
2786 mon
= (Month
)tm
.tm_mon
;
2792 #endif // HAVE_STRPTIME
2794 // TODO query the LOCALE_IDATE setting under Win32
2798 wxString fmtDate
, fmtDateAlt
;
2799 if ( IsWestEuropeanCountry(GetCountry()) ||
2800 GetCountry() == Russia
)
2802 fmtDate
= _T("%d/%m/%y");
2803 fmtDateAlt
= _T("%m/%d/%y");
2807 fmtDate
= _T("%m/%d/%y");
2808 fmtDateAlt
= _T("%d/%m/%y");
2811 const wxChar
*result
= dt
.ParseFormat(input
, fmtDate
);
2815 // ok, be nice and try another one
2816 result
= dt
.ParseFormat(input
, fmtDateAlt
);
2822 return (wxChar
*)NULL
;
2827 haveDay
= haveMon
= haveYear
= TRUE
;
2838 case _T('X'): // locale default time representation
2839 #ifdef HAVE_STRPTIME
2841 // use strptime() to do it for us (FIXME !Unicode friendly)
2843 input
= strptime(input
, "%X", &tm
);
2846 return (wxChar
*)NULL
;
2849 haveHour
= haveMin
= haveSec
= TRUE
;
2855 #else // !HAVE_STRPTIME
2856 // TODO under Win32 we can query the LOCALE_ITIME system
2857 // setting which says whether the default time format is
2860 // try to parse what follows as "%H:%M:%S" and, if this
2861 // fails, as "%I:%M:%S %p" - this should catch the most
2865 const wxChar
*result
= dt
.ParseFormat(input
, _T("%T"));
2868 result
= dt
.ParseFormat(input
, _T("%r"));
2874 return (wxChar
*)NULL
;
2877 haveHour
= haveMin
= haveSec
= TRUE
;
2886 #endif // HAVE_STRPTIME/!HAVE_STRPTIME
2889 case _T('y'): // year without century (00-99)
2890 if ( !GetNumericToken(width
, input
, &num
) || (num
> 99) )
2893 return (wxChar
*)NULL
;
2898 // TODO should have an option for roll over date instead of
2899 // hard coding it here
2900 year
= (num
> 30 ? 1900 : 2000) + (wxDateTime_t
)num
;
2903 case _T('Y'): // year with century
2904 if ( !GetNumericToken(width
, input
, &num
) )
2907 return (wxChar
*)NULL
;
2911 year
= (wxDateTime_t
)num
;
2914 case _T('Z'): // timezone name
2915 wxFAIL_MSG(_T("TODO"));
2918 case _T('%'): // a percent sign
2919 if ( *input
++ != _T('%') )
2922 return (wxChar
*)NULL
;
2926 case 0: // the end of string
2927 wxFAIL_MSG(_T("unexpected format end"));
2931 default: // not a known format spec
2932 return (wxChar
*)NULL
;
2936 // format matched, try to construct a date from what we have now
2938 if ( dateDef
.IsValid() )
2940 // take this date as default
2941 tmDef
= dateDef
.GetTm();
2943 else if ( IsValid() )
2945 // if this date is valid, don't change it
2950 // no default and this date is invalid - fall back to Today()
2951 tmDef
= Today().GetTm();
2962 // TODO we don't check here that the values are consistent, if both year
2963 // day and month/day were found, we just ignore the year day and we
2964 // also always ignore the week day
2965 if ( haveMon
&& haveDay
)
2967 if ( mday
> GetNumOfDaysInMonth(tm
.year
, mon
) )
2969 wxLogDebug(_T("bad month day in wxDateTime::ParseFormat"));
2971 return (wxChar
*)NULL
;
2977 else if ( haveYDay
)
2979 if ( yday
> GetNumberOfDays(tm
.year
) )
2981 wxLogDebug(_T("bad year day in wxDateTime::ParseFormat"));
2983 return (wxChar
*)NULL
;
2986 Tm tm2
= wxDateTime(1, Jan
, tm
.year
).SetToYearDay(yday
).GetTm();
2993 if ( haveHour
&& hourIsIn12hFormat
&& isPM
)
2995 // translate to 24hour format
2998 //else: either already in 24h format or no translation needed
3021 const wxChar
*wxDateTime::ParseDateTime(const wxChar
*date
)
3023 wxCHECK_MSG( date
, (wxChar
*)NULL
, _T("NULL pointer in wxDateTime::Parse") );
3025 // there is a public domain version of getdate.y, but it only works for
3027 wxFAIL_MSG(_T("TODO"));
3029 return (wxChar
*)NULL
;
3032 const wxChar
*wxDateTime::ParseDate(const wxChar
*date
)
3034 // this is a simplified version of ParseDateTime() which understands only
3035 // "today" (for wxDate compatibility) and digits only otherwise (and not
3036 // all esoteric constructions ParseDateTime() knows about)
3038 wxCHECK_MSG( date
, (wxChar
*)NULL
, _T("NULL pointer in wxDateTime::Parse") );
3040 const wxChar
*p
= date
;
3041 while ( wxIsspace(*p
) )
3044 // some special cases
3048 int dayDiffFromToday
;
3051 { wxTRANSLATE("today"), 0 },
3052 { wxTRANSLATE("yesterday"), -1 },
3053 { wxTRANSLATE("tomorrow"), 1 },
3056 for ( size_t n
= 0; n
< WXSIZEOF(literalDates
); n
++ )
3058 wxString date
= wxGetTranslation(literalDates
[n
].str
);
3059 size_t len
= date
.length();
3060 if ( wxStrlen(p
) >= len
&& (wxString(p
, len
).CmpNoCase(date
) == 0) )
3062 // nothing can follow this, so stop here
3065 int dayDiffFromToday
= literalDates
[n
].dayDiffFromToday
;
3067 if ( dayDiffFromToday
)
3069 *this += wxDateSpan::Days(dayDiffFromToday
);
3076 // We try to guess what we have here: for each new (numeric) token, we
3077 // determine if it can be a month, day or a year. Of course, there is an
3078 // ambiguity as some numbers may be days as well as months, so we also
3079 // have the ability to back track.
3082 bool haveDay
= FALSE
, // the months day?
3083 haveWDay
= FALSE
, // the day of week?
3084 haveMon
= FALSE
, // the month?
3085 haveYear
= FALSE
; // the year?
3087 // and the value of the items we have (init them to get rid of warnings)
3088 WeekDay wday
= Inv_WeekDay
;
3089 wxDateTime_t day
= 0;
3090 wxDateTime::Month mon
= Inv_Month
;
3093 // tokenize the string
3095 static const wxChar
*dateDelimiters
= _T(".,/-\t\n ");
3096 wxStringTokenizer
tok(p
, dateDelimiters
);
3097 while ( tok
.HasMoreTokens() )
3099 wxString token
= tok
.GetNextToken();
3105 if ( token
.ToULong(&val
) )
3107 // guess what this number is
3113 if ( !haveMon
&& val
> 0 && val
<= 12 )
3115 // assume it is month
3118 else // not the month
3120 wxDateTime_t maxDays
= haveMon
3121 ? GetNumOfDaysInMonth(haveYear
? year
: Inv_Year
, mon
)
3125 if ( (val
== 0) || (val
> (unsigned long)maxDays
) ) // cast to shut up compiler warning in BCC
3142 year
= (wxDateTime_t
)val
;
3151 day
= (wxDateTime_t
)val
;
3157 mon
= (Month
)(val
- 1);
3160 else // not a number
3162 // be careful not to overwrite the current mon value
3163 Month mon2
= GetMonthFromName(token
, Name_Full
| Name_Abbr
);
3164 if ( mon2
!= Inv_Month
)
3169 // but we already have a month - maybe we guessed wrong?
3172 // no need to check in month range as always < 12, but
3173 // the days are counted from 1 unlike the months
3174 day
= (wxDateTime_t
)mon
+ 1;
3179 // could possible be the year (doesn't the year come
3180 // before the month in the japanese format?) (FIXME)
3189 else // not a valid month name
3191 wday
= GetWeekDayFromName(token
, Name_Full
| Name_Abbr
);
3192 if ( wday
!= Inv_WeekDay
)
3202 else // not a valid weekday name
3205 static const wxChar
*ordinals
[] =
3207 wxTRANSLATE("first"),
3208 wxTRANSLATE("second"),
3209 wxTRANSLATE("third"),
3210 wxTRANSLATE("fourth"),
3211 wxTRANSLATE("fifth"),
3212 wxTRANSLATE("sixth"),
3213 wxTRANSLATE("seventh"),
3214 wxTRANSLATE("eighth"),
3215 wxTRANSLATE("ninth"),
3216 wxTRANSLATE("tenth"),
3217 wxTRANSLATE("eleventh"),
3218 wxTRANSLATE("twelfth"),
3219 wxTRANSLATE("thirteenth"),
3220 wxTRANSLATE("fourteenth"),
3221 wxTRANSLATE("fifteenth"),
3222 wxTRANSLATE("sixteenth"),
3223 wxTRANSLATE("seventeenth"),
3224 wxTRANSLATE("eighteenth"),
3225 wxTRANSLATE("nineteenth"),
3226 wxTRANSLATE("twentieth"),
3227 // that's enough - otherwise we'd have problems with
3228 // composite (or not) ordinals
3232 for ( n
= 0; n
< WXSIZEOF(ordinals
); n
++ )
3234 if ( token
.CmpNoCase(ordinals
[n
]) == 0 )
3240 if ( n
== WXSIZEOF(ordinals
) )
3242 // stop here - something unknown
3249 // don't try anything here (as in case of numeric day
3250 // above) - the symbolic day spec should always
3251 // precede the month/year
3257 day
= (wxDateTime_t
)(n
+ 1);
3262 nPosCur
= tok
.GetPosition();
3265 // either no more tokens or the scan was stopped by something we couldn't
3266 // parse - in any case, see if we can construct a date from what we have
3267 if ( !haveDay
&& !haveWDay
)
3269 wxLogDebug(_T("ParseDate: no day, no weekday hence no date."));
3271 return (wxChar
*)NULL
;
3274 if ( haveWDay
&& (haveMon
|| haveYear
|| haveDay
) &&
3275 !(haveDay
&& haveMon
&& haveYear
) )
3277 // without adjectives (which we don't support here) the week day only
3278 // makes sense completely separately or with the full date
3279 // specification (what would "Wed 1999" mean?)
3280 return (wxChar
*)NULL
;
3283 if ( !haveWDay
&& haveYear
&& !(haveDay
&& haveMon
) )
3285 // may be we have month and day instead of day and year?
3286 if ( haveDay
&& !haveMon
)
3290 // exchange day and month
3291 mon
= (wxDateTime::Month
)(day
- 1);
3293 // we're in the current year then
3295 (unsigned)year
<= GetNumOfDaysInMonth(Inv_Year
, mon
) )
3302 //else: no, can't exchange, leave haveMon == FALSE
3308 // if we give the year, month and day must be given too
3309 wxLogDebug(_T("ParseDate: day and month should be specified if year is."));
3311 return (wxChar
*)NULL
;
3317 mon
= GetCurrentMonth();
3322 year
= GetCurrentYear();
3327 Set(day
, mon
, year
);
3331 // check that it is really the same
3332 if ( GetWeekDay() != wday
)
3334 // inconsistency detected
3335 wxLogDebug(_T("ParseDate: inconsistent day/weekday."));
3337 return (wxChar
*)NULL
;
3345 SetToWeekDayInSameWeek(wday
);
3348 // return the pointer to the first unparsed char
3350 if ( nPosCur
&& wxStrchr(dateDelimiters
, *(p
- 1)) )
3352 // if we couldn't parse the token after the delimiter, put back the
3353 // delimiter as well
3360 const wxChar
*wxDateTime::ParseTime(const wxChar
*time
)
3362 wxCHECK_MSG( time
, (wxChar
*)NULL
, _T("NULL pointer in wxDateTime::Parse") );
3364 // first try some extra things
3371 { wxTRANSLATE("noon"), 12 },
3372 { wxTRANSLATE("midnight"), 00 },
3376 for ( size_t n
= 0; n
< WXSIZEOF(stdTimes
); n
++ )
3378 wxString timeString
= wxGetTranslation(stdTimes
[n
].name
);
3379 size_t len
= timeString
.length();
3380 if ( timeString
.CmpNoCase(wxString(time
, len
)) == 0 )
3382 Set(stdTimes
[n
].hour
, 0, 0);
3388 // try all time formats we may think about in the order from longest to
3391 // 12hour with AM/PM?
3392 const wxChar
*result
= ParseFormat(time
, _T("%I:%M:%S %p"));
3396 // normally, it's the same, but why not try it?
3397 result
= ParseFormat(time
, _T("%H:%M:%S"));
3402 // 12hour with AM/PM but without seconds?
3403 result
= ParseFormat(time
, _T("%I:%M %p"));
3409 result
= ParseFormat(time
, _T("%H:%M"));
3414 // just the hour and AM/PM?
3415 result
= ParseFormat(time
, _T("%I %p"));
3421 result
= ParseFormat(time
, _T("%H"));
3426 // parse the standard format: normally it is one of the formats above
3427 // but it may be set to something completely different by the user
3428 result
= ParseFormat(time
, _T("%X"));
3431 // TODO: parse timezones
3436 // ----------------------------------------------------------------------------
3437 // Workdays and holidays support
3438 // ----------------------------------------------------------------------------
3440 bool wxDateTime::IsWorkDay(Country
WXUNUSED(country
)) const
3442 return !wxDateTimeHolidayAuthority::IsHoliday(*this);
3445 // ============================================================================
3447 // ============================================================================
3449 // this enum is only used in wxTimeSpan::Format() below but we can't declare
3450 // it locally to the method as it provokes an internal compiler error in egcs
3451 // 2.91.60 when building with -O2
3462 // not all strftime(3) format specifiers make sense here because, for example,
3463 // a time span doesn't have a year nor a timezone
3465 // Here are the ones which are supported (all of them are supported by strftime
3467 // %H hour in 24 hour format
3468 // %M minute (00 - 59)
3469 // %S second (00 - 59)
3472 // Also, for MFC CTimeSpan compatibility, we support
3473 // %D number of days
3475 // And, to be better than MFC :-), we also have
3476 // %E number of wEeks
3477 // %l milliseconds (000 - 999)
3478 wxString
wxTimeSpan::Format(const wxChar
*format
) const
3480 wxCHECK_MSG( format
, _T(""), _T("NULL format in wxTimeSpan::Format") );
3483 str
.Alloc(wxStrlen(format
));
3485 // Suppose we have wxTimeSpan ts(1 /* hour */, 2 /* min */, 3 /* sec */)
3487 // Then, of course, ts.Format("%H:%M:%S") must return "01:02:03", but the
3488 // question is what should ts.Format("%S") do? The code here returns "3273"
3489 // in this case (i.e. the total number of seconds, not just seconds % 60)
3490 // because, for me, this call means "give me entire time interval in
3491 // seconds" and not "give me the seconds part of the time interval"
3493 // If we agree that it should behave like this, it is clear that the
3494 // interpretation of each format specifier depends on the presence of the
3495 // other format specs in the string: if there was "%H" before "%M", we
3496 // should use GetMinutes() % 60, otherwise just GetMinutes() &c
3498 // we remember the most important unit found so far
3499 TimeSpanPart partBiggest
= Part_MSec
;
3501 for ( const wxChar
*pch
= format
; *pch
; pch
++ )
3505 if ( ch
== _T('%') )
3507 // the start of the format specification of the printf() below
3508 wxString fmtPrefix
= _T('%');
3513 ch
= *++pch
; // get the format spec char
3517 wxFAIL_MSG( _T("invalid format character") );
3523 // skip the part below switch
3528 if ( partBiggest
< Part_Day
)
3534 partBiggest
= Part_Day
;
3539 partBiggest
= Part_Week
;
3545 if ( partBiggest
< Part_Hour
)
3551 partBiggest
= Part_Hour
;
3554 fmtPrefix
+= _T("02");
3558 n
= GetMilliseconds().ToLong();
3559 if ( partBiggest
< Part_MSec
)
3563 //else: no need to reset partBiggest to Part_MSec, it is
3564 // the least significant one anyhow
3566 fmtPrefix
+= _T("03");
3571 if ( partBiggest
< Part_Min
)
3577 partBiggest
= Part_Min
;
3580 fmtPrefix
+= _T("02");
3584 n
= GetSeconds().ToLong();
3585 if ( partBiggest
< Part_Sec
)
3591 partBiggest
= Part_Sec
;
3594 fmtPrefix
+= _T("02");
3598 str
+= wxString::Format(fmtPrefix
+ _T("ld"), n
);
3602 // normal character, just copy
3610 // ============================================================================
3611 // wxDateTimeHolidayAuthority and related classes
3612 // ============================================================================
3614 #include "wx/arrimpl.cpp"
3616 WX_DEFINE_OBJARRAY(wxDateTimeArray
);
3618 static int wxCMPFUNC_CONV
3619 wxDateTimeCompareFunc(wxDateTime
**first
, wxDateTime
**second
)
3621 wxDateTime dt1
= **first
,
3624 return dt1
== dt2
? 0 : dt1
< dt2
? -1 : +1;
3627 // ----------------------------------------------------------------------------
3628 // wxDateTimeHolidayAuthority
3629 // ----------------------------------------------------------------------------
3631 wxHolidayAuthoritiesArray
wxDateTimeHolidayAuthority::ms_authorities
;
3634 bool wxDateTimeHolidayAuthority::IsHoliday(const wxDateTime
& dt
)
3636 size_t count
= ms_authorities
.GetCount();
3637 for ( size_t n
= 0; n
< count
; n
++ )
3639 if ( ms_authorities
[n
]->DoIsHoliday(dt
) )
3650 wxDateTimeHolidayAuthority::GetHolidaysInRange(const wxDateTime
& dtStart
,
3651 const wxDateTime
& dtEnd
,
3652 wxDateTimeArray
& holidays
)
3654 wxDateTimeArray hol
;
3658 size_t count
= ms_authorities
.GetCount();
3659 for ( size_t nAuth
= 0; nAuth
< count
; nAuth
++ )
3661 ms_authorities
[nAuth
]->DoGetHolidaysInRange(dtStart
, dtEnd
, hol
);
3663 WX_APPEND_ARRAY(holidays
, hol
);
3666 holidays
.Sort(wxDateTimeCompareFunc
);
3668 return holidays
.GetCount();
3672 void wxDateTimeHolidayAuthority::ClearAllAuthorities()
3674 WX_CLEAR_ARRAY(ms_authorities
);
3678 void wxDateTimeHolidayAuthority::AddAuthority(wxDateTimeHolidayAuthority
*auth
)
3680 ms_authorities
.Add(auth
);
3683 // ----------------------------------------------------------------------------
3684 // wxDateTimeWorkDays
3685 // ----------------------------------------------------------------------------
3687 bool wxDateTimeWorkDays::DoIsHoliday(const wxDateTime
& dt
) const
3689 wxDateTime::WeekDay wd
= dt
.GetWeekDay();
3691 return (wd
== wxDateTime::Sun
) || (wd
== wxDateTime::Sat
);
3694 size_t wxDateTimeWorkDays::DoGetHolidaysInRange(const wxDateTime
& dtStart
,
3695 const wxDateTime
& dtEnd
,
3696 wxDateTimeArray
& holidays
) const
3698 if ( dtStart
> dtEnd
)
3700 wxFAIL_MSG( _T("invalid date range in GetHolidaysInRange") );
3707 // instead of checking all days, start with the first Sat after dtStart and
3708 // end with the last Sun before dtEnd
3709 wxDateTime dtSatFirst
= dtStart
.GetNextWeekDay(wxDateTime::Sat
),
3710 dtSatLast
= dtEnd
.GetPrevWeekDay(wxDateTime::Sat
),
3711 dtSunFirst
= dtStart
.GetNextWeekDay(wxDateTime::Sun
),
3712 dtSunLast
= dtEnd
.GetPrevWeekDay(wxDateTime::Sun
),
3715 for ( dt
= dtSatFirst
; dt
<= dtSatLast
; dt
+= wxDateSpan::Week() )
3720 for ( dt
= dtSunFirst
; dt
<= dtSunLast
; dt
+= wxDateSpan::Week() )
3725 return holidays
.GetCount();
3728 #endif // wxUSE_DATETIME