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
215 static const wxDateTime gs_dtDefault
;
217 const wxDateTime
& wxDefaultDateTime
= gs_dtDefault
;
219 wxDateTime::Country
wxDateTime::ms_country
= wxDateTime::Country_Unknown
;
221 // ----------------------------------------------------------------------------
223 // ----------------------------------------------------------------------------
225 // a critical section is needed to protect GetTimeZone() static
226 // variable in MT case
228 static wxCriticalSection gs_critsectTimezone
;
229 #endif // wxUSE_THREADS
231 // ----------------------------------------------------------------------------
233 // ----------------------------------------------------------------------------
235 // debugger helper: shows what the date really is
237 extern const wxChar
*wxDumpDate(const wxDateTime
* dt
)
239 static wxChar buf
[128];
241 wxStrcpy(buf
, dt
->Format(_T("%Y-%m-%d (%a) %H:%M:%S")));
247 // get the number of days in the given month of the given year
249 wxDateTime::wxDateTime_t
GetNumOfDaysInMonth(int year
, wxDateTime::Month month
)
251 // the number of days in month in Julian/Gregorian calendar: the first line
252 // is for normal years, the second one is for the leap ones
253 static wxDateTime::wxDateTime_t daysInMonth
[2][MONTHS_IN_YEAR
] =
255 { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
256 { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
259 return daysInMonth
[wxDateTime::IsLeapYear(year
)][month
];
262 // ensure that the timezone variable is set by calling localtime
263 static int GetTimeZone()
265 // set to TRUE when the timezone is set
266 static bool s_timezoneSet
= FALSE
;
268 wxCRIT_SECT_LOCKER(lock
, gs_critsectTimezone
);
270 if ( !s_timezoneSet
)
272 // just call localtime() instead of figuring out whether this system
273 // supports tzset(), _tzset() or something else
277 s_timezoneSet
= TRUE
;
280 return (int)WX_TIMEZONE
;
283 // return the integral part of the JDN for the midnight of the given date (to
284 // get the real JDN you need to add 0.5, this is, in fact, JDN of the
285 // noon of the previous day)
286 static long GetTruncatedJDN(wxDateTime::wxDateTime_t day
,
287 wxDateTime::Month mon
,
290 // CREDIT: code below is by Scott E. Lee (but bugs are mine)
292 // check the date validity
294 (year
> JDN_0_YEAR
) ||
295 ((year
== JDN_0_YEAR
) && (mon
> JDN_0_MONTH
)) ||
296 ((year
== JDN_0_YEAR
) && (mon
== JDN_0_MONTH
) && (day
>= JDN_0_DAY
)),
297 _T("date out of range - can't convert to JDN")
300 // make the year positive to avoid problems with negative numbers division
303 // months are counted from March here
305 if ( mon
>= wxDateTime::Mar
)
315 // now we can simply add all the contributions together
316 return ((year
/ 100) * DAYS_PER_400_YEARS
) / 4
317 + ((year
% 100) * DAYS_PER_4_YEARS
) / 4
318 + (month
* DAYS_PER_5_MONTHS
+ 2) / 5
323 // this function is a wrapper around strftime(3)
324 static wxString
CallStrftime(const wxChar
*format
, const tm
* tm
)
327 if ( !wxStrftime(buf
, WXSIZEOF(buf
), format
, tm
) )
329 // buffer is too small?
330 wxFAIL_MSG(_T("strftime() failed"));
333 return wxString(buf
);
336 // if year and/or month have invalid values, replace them with the current ones
337 static void ReplaceDefaultYearMonthWithCurrent(int *year
,
338 wxDateTime::Month
*month
)
340 struct tm
*tmNow
= NULL
;
342 if ( *year
== wxDateTime::Inv_Year
)
344 tmNow
= wxDateTime::GetTmNow();
346 *year
= 1900 + tmNow
->tm_year
;
349 if ( *month
== wxDateTime::Inv_Month
)
352 tmNow
= wxDateTime::GetTmNow();
354 *month
= (wxDateTime::Month
)tmNow
->tm_mon
;
358 // fll the struct tm with default values
359 static void InitTm(struct tm
& tm
)
361 // struct tm may have etxra fields (undocumented and with unportable
362 // names) which, nevertheless, must be set to 0
363 memset(&tm
, 0, sizeof(struct tm
));
365 tm
.tm_mday
= 1; // mday 0 is invalid
366 tm
.tm_year
= 76; // any valid year
367 tm
.tm_isdst
= -1; // auto determine
373 // return the month if the string is a month name or Inv_Month otherwise
374 static wxDateTime::Month
GetMonthFromName(const wxString
& name
, int flags
)
376 wxDateTime::Month mon
;
377 for ( mon
= wxDateTime::Jan
; mon
< wxDateTime::Inv_Month
; wxNextMonth(mon
) )
379 // case-insensitive comparison either one of or with both abbreviated
381 if ( flags
& wxDateTime::Name_Full
)
383 if ( name
.CmpNoCase(wxDateTime::
384 GetMonthName(mon
, wxDateTime::Name_Full
)) == 0 )
390 if ( flags
& wxDateTime::Name_Abbr
)
392 if ( name
.CmpNoCase(wxDateTime::
393 GetMonthName(mon
, wxDateTime::Name_Abbr
)) == 0 )
403 // return the weekday if the string is a weekday name or Inv_WeekDay otherwise
404 static wxDateTime::WeekDay
GetWeekDayFromName(const wxString
& name
, int flags
)
406 wxDateTime::WeekDay wd
;
407 for ( wd
= wxDateTime::Sun
; wd
< wxDateTime::Inv_WeekDay
; wxNextWDay(wd
) )
409 // case-insensitive comparison either one of or with both abbreviated
411 if ( flags
& wxDateTime::Name_Full
)
413 if ( name
.CmpNoCase(wxDateTime::
414 GetWeekDayName(wd
, wxDateTime::Name_Full
)) == 0 )
420 if ( flags
& wxDateTime::Name_Abbr
)
422 if ( name
.CmpNoCase(wxDateTime::
423 GetWeekDayName(wd
, wxDateTime::Name_Abbr
)) == 0 )
433 // scans all digits (but no more than len) and returns the resulting number
434 static bool GetNumericToken(size_t len
, const wxChar
*& p
, unsigned long *number
)
438 while ( wxIsdigit(*p
) )
442 if ( len
&& ++n
> len
)
446 return !!s
&& s
.ToULong(number
);
449 // scans all alphabetic characters and returns the resulting string
450 static wxString
GetAlphaToken(const wxChar
*& p
)
453 while ( wxIsalpha(*p
) )
461 // ============================================================================
462 // implementation of wxDateTime
463 // ============================================================================
465 // ----------------------------------------------------------------------------
467 // ----------------------------------------------------------------------------
471 year
= (wxDateTime_t
)wxDateTime::Inv_Year
;
472 mon
= wxDateTime::Inv_Month
;
474 hour
= min
= sec
= msec
= 0;
475 wday
= wxDateTime::Inv_WeekDay
;
478 wxDateTime::Tm::Tm(const struct tm
& tm
, const TimeZone
& tz
)
486 mon
= (wxDateTime::Month
)tm
.tm_mon
;
487 year
= 1900 + tm
.tm_year
;
492 bool wxDateTime::Tm::IsValid() const
494 // we allow for the leap seconds, although we don't use them (yet)
495 return (year
!= wxDateTime::Inv_Year
) && (mon
!= wxDateTime::Inv_Month
) &&
496 (mday
<= GetNumOfDaysInMonth(year
, mon
)) &&
497 (hour
< 24) && (min
< 60) && (sec
< 62) && (msec
< 1000);
500 void wxDateTime::Tm::ComputeWeekDay()
502 // compute the week day from day/month/year: we use the dumbest algorithm
503 // possible: just compute our JDN and then use the (simple to derive)
504 // formula: weekday = (JDN + 1.5) % 7
505 wday
= (wxDateTime::WeekDay
)(GetTruncatedJDN(mday
, mon
, year
) + 2) % 7;
508 void wxDateTime::Tm::AddMonths(int monDiff
)
510 // normalize the months field
511 while ( monDiff
< -mon
)
515 monDiff
+= MONTHS_IN_YEAR
;
518 while ( monDiff
+ mon
>= MONTHS_IN_YEAR
)
522 monDiff
-= MONTHS_IN_YEAR
;
525 mon
= (wxDateTime::Month
)(mon
+ monDiff
);
527 wxASSERT_MSG( mon
>= 0 && mon
< MONTHS_IN_YEAR
, _T("logic error") );
529 // NB: we don't check here that the resulting date is valid, this function
530 // is private and the caller must check it if needed
533 void wxDateTime::Tm::AddDays(int dayDiff
)
535 // normalize the days field
536 while ( dayDiff
+ mday
< 1 )
540 dayDiff
+= GetNumOfDaysInMonth(year
, mon
);
544 while ( mday
> GetNumOfDaysInMonth(year
, mon
) )
546 mday
-= GetNumOfDaysInMonth(year
, mon
);
551 wxASSERT_MSG( mday
> 0 && mday
<= GetNumOfDaysInMonth(year
, mon
),
555 // ----------------------------------------------------------------------------
557 // ----------------------------------------------------------------------------
559 wxDateTime::TimeZone::TimeZone(wxDateTime::TZ tz
)
563 case wxDateTime::Local
:
564 // get the offset from C RTL: it returns the difference GMT-local
565 // while we want to have the offset _from_ GMT, hence the '-'
566 m_offset
= -GetTimeZone();
569 case wxDateTime::GMT_12
:
570 case wxDateTime::GMT_11
:
571 case wxDateTime::GMT_10
:
572 case wxDateTime::GMT_9
:
573 case wxDateTime::GMT_8
:
574 case wxDateTime::GMT_7
:
575 case wxDateTime::GMT_6
:
576 case wxDateTime::GMT_5
:
577 case wxDateTime::GMT_4
:
578 case wxDateTime::GMT_3
:
579 case wxDateTime::GMT_2
:
580 case wxDateTime::GMT_1
:
581 m_offset
= -3600*(wxDateTime::GMT0
- tz
);
584 case wxDateTime::GMT0
:
585 case wxDateTime::GMT1
:
586 case wxDateTime::GMT2
:
587 case wxDateTime::GMT3
:
588 case wxDateTime::GMT4
:
589 case wxDateTime::GMT5
:
590 case wxDateTime::GMT6
:
591 case wxDateTime::GMT7
:
592 case wxDateTime::GMT8
:
593 case wxDateTime::GMT9
:
594 case wxDateTime::GMT10
:
595 case wxDateTime::GMT11
:
596 case wxDateTime::GMT12
:
597 m_offset
= 3600*(tz
- wxDateTime::GMT0
);
600 case wxDateTime::A_CST
:
601 // Central Standard Time in use in Australia = UTC + 9.5
602 m_offset
= 60l*(9*60 + 30);
606 wxFAIL_MSG( _T("unknown time zone") );
610 // ----------------------------------------------------------------------------
612 // ----------------------------------------------------------------------------
615 bool wxDateTime::IsLeapYear(int year
, wxDateTime::Calendar cal
)
617 if ( year
== Inv_Year
)
618 year
= GetCurrentYear();
620 if ( cal
== Gregorian
)
622 // in Gregorian calendar leap years are those divisible by 4 except
623 // those divisible by 100 unless they're also divisible by 400
624 // (in some countries, like Russia and Greece, additional corrections
625 // exist, but they won't manifest themselves until 2700)
626 return (year
% 4 == 0) && ((year
% 100 != 0) || (year
% 400 == 0));
628 else if ( cal
== Julian
)
630 // in Julian calendar the rule is simpler
631 return year
% 4 == 0;
635 wxFAIL_MSG(_T("unknown calendar"));
642 int wxDateTime::GetCentury(int year
)
644 return year
> 0 ? year
/ 100 : year
/ 100 - 1;
648 int wxDateTime::ConvertYearToBC(int year
)
651 return year
> 0 ? year
: year
- 1;
655 int wxDateTime::GetCurrentYear(wxDateTime::Calendar cal
)
660 return Now().GetYear();
663 wxFAIL_MSG(_T("TODO"));
667 wxFAIL_MSG(_T("unsupported calendar"));
675 wxDateTime::Month
wxDateTime::GetCurrentMonth(wxDateTime::Calendar cal
)
680 return Now().GetMonth();
683 wxFAIL_MSG(_T("TODO"));
687 wxFAIL_MSG(_T("unsupported calendar"));
695 wxDateTime::wxDateTime_t
wxDateTime::GetNumberOfDays(int year
, Calendar cal
)
697 if ( year
== Inv_Year
)
699 // take the current year if none given
700 year
= GetCurrentYear();
707 return IsLeapYear(year
) ? 366 : 365;
710 wxFAIL_MSG(_T("unsupported calendar"));
718 wxDateTime::wxDateTime_t
wxDateTime::GetNumberOfDays(wxDateTime::Month month
,
720 wxDateTime::Calendar cal
)
722 wxCHECK_MSG( month
< MONTHS_IN_YEAR
, 0, _T("invalid month") );
724 if ( cal
== Gregorian
|| cal
== Julian
)
726 if ( year
== Inv_Year
)
728 // take the current year if none given
729 year
= GetCurrentYear();
732 return GetNumOfDaysInMonth(year
, month
);
736 wxFAIL_MSG(_T("unsupported calendar"));
743 wxString
wxDateTime::GetMonthName(wxDateTime::Month month
,
744 wxDateTime::NameFlags flags
)
746 wxCHECK_MSG( month
!= Inv_Month
, _T(""), _T("invalid month") );
748 // notice that we must set all the fields to avoid confusing libc (GNU one
749 // gets confused to a crash if we don't do this)
754 return CallStrftime(flags
== Name_Abbr
? _T("%b") : _T("%B"), &tm
);
758 wxString
wxDateTime::GetWeekDayName(wxDateTime::WeekDay wday
,
759 wxDateTime::NameFlags flags
)
761 wxCHECK_MSG( wday
!= Inv_WeekDay
, _T(""), _T("invalid weekday") );
763 // take some arbitrary Sunday
770 // and offset it by the number of days needed to get the correct wday
773 // call mktime() to normalize it...
776 // ... and call strftime()
777 return CallStrftime(flags
== Name_Abbr
? _T("%a") : _T("%A"), &tm
);
781 void wxDateTime::GetAmPmStrings(wxString
*am
, wxString
*pm
)
787 *am
= CallStrftime(_T("%p"), &tm
);
792 *pm
= CallStrftime(_T("%p"), &tm
);
796 // ----------------------------------------------------------------------------
797 // Country stuff: date calculations depend on the country (DST, work days,
798 // ...), so we need to know which rules to follow.
799 // ----------------------------------------------------------------------------
802 wxDateTime::Country
wxDateTime::GetCountry()
804 // TODO use LOCALE_ICOUNTRY setting under Win32
806 if ( ms_country
== Country_Unknown
)
808 // try to guess from the time zone name
809 time_t t
= time(NULL
);
810 struct tm
*tm
= localtime(&t
);
812 wxString tz
= CallStrftime(_T("%Z"), tm
);
813 if ( tz
== _T("WET") || tz
== _T("WEST") )
817 else if ( tz
== _T("CET") || tz
== _T("CEST") )
819 ms_country
= Country_EEC
;
821 else if ( tz
== _T("MSK") || tz
== _T("MSD") )
825 else if ( tz
== _T("AST") || tz
== _T("ADT") ||
826 tz
== _T("EST") || tz
== _T("EDT") ||
827 tz
== _T("CST") || tz
== _T("CDT") ||
828 tz
== _T("MST") || tz
== _T("MDT") ||
829 tz
== _T("PST") || tz
== _T("PDT") )
835 // well, choose a default one
844 void wxDateTime::SetCountry(wxDateTime::Country country
)
846 ms_country
= country
;
850 bool wxDateTime::IsWestEuropeanCountry(Country country
)
852 if ( country
== Country_Default
)
854 country
= GetCountry();
857 return (Country_WesternEurope_Start
<= country
) &&
858 (country
<= Country_WesternEurope_End
);
861 // ----------------------------------------------------------------------------
862 // DST calculations: we use 3 different rules for the West European countries,
863 // USA and for the rest of the world. This is undoubtedly false for many
864 // countries, but I lack the necessary info (and the time to gather it),
865 // please add the other rules here!
866 // ----------------------------------------------------------------------------
869 bool wxDateTime::IsDSTApplicable(int year
, Country country
)
871 if ( year
== Inv_Year
)
873 // take the current year if none given
874 year
= GetCurrentYear();
877 if ( country
== Country_Default
)
879 country
= GetCountry();
886 // DST was first observed in the US and UK during WWI, reused
887 // during WWII and used again since 1966
888 return year
>= 1966 ||
889 (year
>= 1942 && year
<= 1945) ||
890 (year
== 1918 || year
== 1919);
893 // assume that it started after WWII
899 wxDateTime
wxDateTime::GetBeginDST(int year
, Country country
)
901 if ( year
== Inv_Year
)
903 // take the current year if none given
904 year
= GetCurrentYear();
907 if ( country
== Country_Default
)
909 country
= GetCountry();
912 if ( !IsDSTApplicable(year
, country
) )
914 return wxInvalidDateTime
;
919 if ( IsWestEuropeanCountry(country
) || (country
== Russia
) )
921 // DST begins at 1 a.m. GMT on the last Sunday of March
922 if ( !dt
.SetToLastWeekDay(Sun
, Mar
, year
) )
925 wxFAIL_MSG( _T("no last Sunday in March?") );
928 dt
+= wxTimeSpan::Hours(1);
930 // disable DST tests because it could result in an infinite recursion!
933 else switch ( country
)
940 // don't know for sure - assume it was in effect all year
945 dt
.Set(1, Jan
, year
);
949 // DST was installed Feb 2, 1942 by the Congress
950 dt
.Set(2, Feb
, year
);
953 // Oil embargo changed the DST period in the US
955 dt
.Set(6, Jan
, 1974);
959 dt
.Set(23, Feb
, 1975);
963 // before 1986, DST begun on the last Sunday of April, but
964 // in 1986 Reagan changed it to begin at 2 a.m. of the
965 // first Sunday in April
968 if ( !dt
.SetToLastWeekDay(Sun
, Apr
, year
) )
971 wxFAIL_MSG( _T("no first Sunday in April?") );
976 if ( !dt
.SetToWeekDay(Sun
, 1, Apr
, year
) )
979 wxFAIL_MSG( _T("no first Sunday in April?") );
983 dt
+= wxTimeSpan::Hours(2);
985 // TODO what about timezone??
991 // assume Mar 30 as the start of the DST for the rest of the world
992 // - totally bogus, of course
993 dt
.Set(30, Mar
, year
);
1000 wxDateTime
wxDateTime::GetEndDST(int year
, Country country
)
1002 if ( year
== Inv_Year
)
1004 // take the current year if none given
1005 year
= GetCurrentYear();
1008 if ( country
== Country_Default
)
1010 country
= GetCountry();
1013 if ( !IsDSTApplicable(year
, country
) )
1015 return wxInvalidDateTime
;
1020 if ( IsWestEuropeanCountry(country
) || (country
== Russia
) )
1022 // DST ends at 1 a.m. GMT on the last Sunday of October
1023 if ( !dt
.SetToLastWeekDay(Sun
, Oct
, year
) )
1025 // weirder and weirder...
1026 wxFAIL_MSG( _T("no last Sunday in October?") );
1029 dt
+= wxTimeSpan::Hours(1);
1031 // disable DST tests because it could result in an infinite recursion!
1034 else switch ( country
)
1041 // don't know for sure - assume it was in effect all year
1045 dt
.Set(31, Dec
, year
);
1049 // the time was reset after the end of the WWII
1050 dt
.Set(30, Sep
, year
);
1054 // DST ends at 2 a.m. on the last Sunday of October
1055 if ( !dt
.SetToLastWeekDay(Sun
, Oct
, year
) )
1057 // weirder and weirder...
1058 wxFAIL_MSG( _T("no last Sunday in October?") );
1061 dt
+= wxTimeSpan::Hours(2);
1063 // TODO what about timezone??
1068 // assume October 26th as the end of the DST - totally bogus too
1069 dt
.Set(26, Oct
, year
);
1075 // ----------------------------------------------------------------------------
1076 // constructors and assignment operators
1077 // ----------------------------------------------------------------------------
1079 // return the current time with ms precision
1080 /* static */ wxDateTime
wxDateTime::UNow()
1082 return wxDateTime(wxGetLocalTimeMillis());
1085 // the values in the tm structure contain the local time
1086 wxDateTime
& wxDateTime::Set(const struct tm
& tm
)
1089 time_t timet
= mktime(&tm2
);
1091 if ( timet
== (time_t)-1 )
1093 // mktime() rather unintuitively fails for Jan 1, 1970 if the hour is
1094 // less than timezone - try to make it work for this case
1095 if ( tm2
.tm_year
== 70 && tm2
.tm_mon
== 0 && tm2
.tm_mday
== 1 )
1097 // add timezone to make sure that date is in range
1098 tm2
.tm_sec
-= GetTimeZone();
1100 timet
= mktime(&tm2
);
1101 if ( timet
!= (time_t)-1 )
1103 timet
+= GetTimeZone();
1109 wxFAIL_MSG( _T("mktime() failed") );
1111 *this = wxInvalidDateTime
;
1121 wxDateTime
& wxDateTime::Set(wxDateTime_t hour
,
1122 wxDateTime_t minute
,
1123 wxDateTime_t second
,
1124 wxDateTime_t millisec
)
1126 // we allow seconds to be 61 to account for the leap seconds, even if we
1127 // don't use them really
1128 wxDATETIME_CHECK( hour
< 24 &&
1132 _T("Invalid time in wxDateTime::Set()") );
1134 // get the current date from system
1135 struct tm
*tm
= GetTmNow();
1137 wxDATETIME_CHECK( tm
, _T("localtime() failed") );
1141 tm
->tm_min
= minute
;
1142 tm
->tm_sec
= second
;
1146 // and finally adjust milliseconds
1147 return SetMillisecond(millisec
);
1150 wxDateTime
& wxDateTime::Set(wxDateTime_t day
,
1154 wxDateTime_t minute
,
1155 wxDateTime_t second
,
1156 wxDateTime_t millisec
)
1158 wxDATETIME_CHECK( hour
< 24 &&
1162 _T("Invalid time in wxDateTime::Set()") );
1164 ReplaceDefaultYearMonthWithCurrent(&year
, &month
);
1166 wxDATETIME_CHECK( (0 < day
) && (day
<= GetNumberOfDays(month
, year
)),
1167 _T("Invalid date in wxDateTime::Set()") );
1169 // the range of time_t type (inclusive)
1170 static const int yearMinInRange
= 1970;
1171 static const int yearMaxInRange
= 2037;
1173 // test only the year instead of testing for the exact end of the Unix
1174 // time_t range - it doesn't bring anything to do more precise checks
1175 if ( year
>= yearMinInRange
&& year
<= yearMaxInRange
)
1177 // use the standard library version if the date is in range - this is
1178 // probably more efficient than our code
1180 tm
.tm_year
= year
- 1900;
1186 tm
.tm_isdst
= -1; // mktime() will guess it
1190 // and finally adjust milliseconds
1191 return SetMillisecond(millisec
);
1195 // do time calculations ourselves: we want to calculate the number of
1196 // milliseconds between the given date and the epoch
1198 // get the JDN for the midnight of this day
1199 m_time
= GetTruncatedJDN(day
, month
, year
);
1200 m_time
-= EPOCH_JDN
;
1201 m_time
*= SECONDS_PER_DAY
* TIME_T_FACTOR
;
1203 // JDN corresponds to GMT, we take localtime
1204 Add(wxTimeSpan(hour
, minute
, second
+ GetTimeZone(), millisec
));
1210 wxDateTime
& wxDateTime::Set(double jdn
)
1212 // so that m_time will be 0 for the midnight of Jan 1, 1970 which is jdn
1214 jdn
-= EPOCH_JDN
+ 0.5;
1216 jdn
*= MILLISECONDS_PER_DAY
;
1223 wxDateTime
& wxDateTime::ResetTime()
1227 if ( tm
.hour
|| tm
.min
|| tm
.sec
|| tm
.msec
)
1240 // ----------------------------------------------------------------------------
1241 // time_t <-> broken down time conversions
1242 // ----------------------------------------------------------------------------
1244 wxDateTime::Tm
wxDateTime::GetTm(const TimeZone
& tz
) const
1246 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1248 time_t time
= GetTicks();
1249 if ( time
!= (time_t)-1 )
1251 // use C RTL functions
1253 if ( tz
.GetOffset() == -GetTimeZone() )
1255 // we are working with local time
1256 tm
= localtime(&time
);
1258 // should never happen
1259 wxCHECK_MSG( tm
, Tm(), _T("localtime() failed") );
1263 time
+= (time_t)tz
.GetOffset();
1264 #if defined(__VMS__) || defined(__WATCOMC__) // time is unsigned so avoid warning
1265 int time2
= (int) time
;
1273 // should never happen
1274 wxCHECK_MSG( tm
, Tm(), _T("gmtime() failed") );
1278 tm
= (struct tm
*)NULL
;
1284 // adjust the milliseconds
1286 long timeOnly
= (m_time
% MILLISECONDS_PER_DAY
).ToLong();
1287 tm2
.msec
= (wxDateTime_t
)(timeOnly
% 1000);
1290 //else: use generic code below
1293 // remember the time and do the calculations with the date only - this
1294 // eliminates rounding errors of the floating point arithmetics
1296 wxLongLong timeMidnight
= m_time
+ tz
.GetOffset() * 1000;
1298 long timeOnly
= (timeMidnight
% MILLISECONDS_PER_DAY
).ToLong();
1300 // we want to always have positive time and timeMidnight to be really
1301 // the midnight before it
1304 timeOnly
= MILLISECONDS_PER_DAY
+ timeOnly
;
1307 timeMidnight
-= timeOnly
;
1309 // calculate the Gregorian date from JDN for the midnight of our date:
1310 // this will yield day, month (in 1..12 range) and year
1312 // actually, this is the JDN for the noon of the previous day
1313 long jdn
= (timeMidnight
/ MILLISECONDS_PER_DAY
).ToLong() + EPOCH_JDN
;
1315 // CREDIT: code below is by Scott E. Lee (but bugs are mine)
1317 wxASSERT_MSG( jdn
> -2, _T("JDN out of range") );
1319 // calculate the century
1320 long temp
= (jdn
+ JDN_OFFSET
) * 4 - 1;
1321 long century
= temp
/ DAYS_PER_400_YEARS
;
1323 // then the year and day of year (1 <= dayOfYear <= 366)
1324 temp
= ((temp
% DAYS_PER_400_YEARS
) / 4) * 4 + 3;
1325 long year
= (century
* 100) + (temp
/ DAYS_PER_4_YEARS
);
1326 long dayOfYear
= (temp
% DAYS_PER_4_YEARS
) / 4 + 1;
1328 // and finally the month and day of the month
1329 temp
= dayOfYear
* 5 - 3;
1330 long month
= temp
/ DAYS_PER_5_MONTHS
;
1331 long day
= (temp
% DAYS_PER_5_MONTHS
) / 5 + 1;
1333 // month is counted from March - convert to normal
1344 // year is offset by 4800
1347 // check that the algorithm gave us something reasonable
1348 wxASSERT_MSG( (0 < month
) && (month
<= 12), _T("invalid month") );
1349 wxASSERT_MSG( (1 <= day
) && (day
< 32), _T("invalid day") );
1351 // construct Tm from these values
1353 tm
.year
= (int)year
;
1354 tm
.mon
= (Month
)(month
- 1); // algorithm yields 1 for January, not 0
1355 tm
.mday
= (wxDateTime_t
)day
;
1356 tm
.msec
= (wxDateTime_t
)(timeOnly
% 1000);
1357 timeOnly
-= tm
.msec
;
1358 timeOnly
/= 1000; // now we have time in seconds
1360 tm
.sec
= (wxDateTime_t
)(timeOnly
% 60);
1362 timeOnly
/= 60; // now we have time in minutes
1364 tm
.min
= (wxDateTime_t
)(timeOnly
% 60);
1367 tm
.hour
= (wxDateTime_t
)(timeOnly
/ 60);
1372 wxDateTime
& wxDateTime::SetYear(int year
)
1374 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1383 wxDateTime
& wxDateTime::SetMonth(Month month
)
1385 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1394 wxDateTime
& wxDateTime::SetDay(wxDateTime_t mday
)
1396 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1405 wxDateTime
& wxDateTime::SetHour(wxDateTime_t hour
)
1407 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1416 wxDateTime
& wxDateTime::SetMinute(wxDateTime_t min
)
1418 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1427 wxDateTime
& wxDateTime::SetSecond(wxDateTime_t sec
)
1429 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1438 wxDateTime
& wxDateTime::SetMillisecond(wxDateTime_t millisecond
)
1440 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1442 // we don't need to use GetTm() for this one
1443 m_time
-= m_time
% 1000l;
1444 m_time
+= millisecond
;
1449 // ----------------------------------------------------------------------------
1450 // wxDateTime arithmetics
1451 // ----------------------------------------------------------------------------
1453 wxDateTime
& wxDateTime::Add(const wxDateSpan
& diff
)
1457 tm
.year
+= diff
.GetYears();
1458 tm
.AddMonths(diff
.GetMonths());
1460 // check that the resulting date is valid
1461 if ( tm
.mday
> GetNumOfDaysInMonth(tm
.year
, tm
.mon
) )
1463 // We suppose that when adding one month to Jan 31 we want to get Feb
1464 // 28 (or 29), i.e. adding a month to the last day of the month should
1465 // give the last day of the next month which is quite logical.
1467 // Unfortunately, there is no logic way to understand what should
1468 // Jan 30 + 1 month be - Feb 28 too or Feb 27 (assuming non leap year)?
1469 // We make it Feb 28 (last day too), but it is highly questionable.
1470 tm
.mday
= GetNumOfDaysInMonth(tm
.year
, tm
.mon
);
1473 tm
.AddDays(diff
.GetTotalDays());
1477 wxASSERT_MSG( IsSameTime(tm
),
1478 _T("Add(wxDateSpan) shouldn't modify time") );
1483 // ----------------------------------------------------------------------------
1484 // Weekday and monthday stuff
1485 // ----------------------------------------------------------------------------
1487 bool wxDateTime::SetToTheWeek(wxDateTime_t numWeek
, WeekDay weekday
)
1489 int year
= GetYear();
1491 // Jan 4 always lies in the 1st week of the year
1493 SetToWeekDayInSameWeek(weekday
) += wxDateSpan::Weeks(numWeek
);
1495 if ( GetYear() != year
)
1497 // oops... numWeek was too big
1504 wxDateTime
& wxDateTime::SetToLastMonthDay(Month month
,
1507 // take the current month/year if none specified
1508 if ( year
== Inv_Year
)
1510 if ( month
== Inv_Month
)
1513 return Set(GetNumOfDaysInMonth(year
, month
), month
, year
);
1516 wxDateTime
& wxDateTime::SetToWeekDayInSameWeek(WeekDay weekday
)
1518 wxDATETIME_CHECK( weekday
!= Inv_WeekDay
, _T("invalid weekday") );
1520 WeekDay wdayThis
= GetWeekDay();
1521 if ( weekday
== wdayThis
)
1526 else if ( weekday
< wdayThis
)
1528 return Subtract(wxDateSpan::Days(wdayThis
- weekday
));
1530 else // weekday > wdayThis
1532 return Add(wxDateSpan::Days(weekday
- wdayThis
));
1536 wxDateTime
& wxDateTime::SetToNextWeekDay(WeekDay weekday
)
1538 wxDATETIME_CHECK( weekday
!= Inv_WeekDay
, _T("invalid weekday") );
1541 WeekDay wdayThis
= GetWeekDay();
1542 if ( weekday
== wdayThis
)
1547 else if ( weekday
< wdayThis
)
1549 // need to advance a week
1550 diff
= 7 - (wdayThis
- weekday
);
1552 else // weekday > wdayThis
1554 diff
= weekday
- wdayThis
;
1557 return Add(wxDateSpan::Days(diff
));
1560 wxDateTime
& wxDateTime::SetToPrevWeekDay(WeekDay weekday
)
1562 wxDATETIME_CHECK( weekday
!= Inv_WeekDay
, _T("invalid weekday") );
1565 WeekDay wdayThis
= GetWeekDay();
1566 if ( weekday
== wdayThis
)
1571 else if ( weekday
> wdayThis
)
1573 // need to go to previous week
1574 diff
= 7 - (weekday
- wdayThis
);
1576 else // weekday < wdayThis
1578 diff
= wdayThis
- weekday
;
1581 return Subtract(wxDateSpan::Days(diff
));
1584 bool wxDateTime::SetToWeekDay(WeekDay weekday
,
1589 wxCHECK_MSG( weekday
!= Inv_WeekDay
, FALSE
, _T("invalid weekday") );
1591 // we don't check explicitly that -5 <= n <= 5 because we will return FALSE
1592 // anyhow in such case - but may be should still give an assert for it?
1594 // take the current month/year if none specified
1595 ReplaceDefaultYearMonthWithCurrent(&year
, &month
);
1599 // TODO this probably could be optimised somehow...
1603 // get the first day of the month
1604 dt
.Set(1, month
, year
);
1607 WeekDay wdayFirst
= dt
.GetWeekDay();
1609 // go to the first weekday of the month
1610 int diff
= weekday
- wdayFirst
;
1614 // add advance n-1 weeks more
1617 dt
+= wxDateSpan::Days(diff
);
1619 else // count from the end of the month
1621 // get the last day of the month
1622 dt
.SetToLastMonthDay(month
, year
);
1625 WeekDay wdayLast
= dt
.GetWeekDay();
1627 // go to the last weekday of the month
1628 int diff
= wdayLast
- weekday
;
1632 // and rewind n-1 weeks from there
1635 dt
-= wxDateSpan::Days(diff
);
1638 // check that it is still in the same month
1639 if ( dt
.GetMonth() == month
)
1647 // no such day in this month
1652 wxDateTime::wxDateTime_t
wxDateTime::GetDayOfYear(const TimeZone
& tz
) const
1656 return gs_cumulatedDays
[IsLeapYear(tm
.year
)][tm
.mon
] + tm
.mday
;
1659 wxDateTime::wxDateTime_t
wxDateTime::GetWeekOfYear(wxDateTime::WeekFlags flags
,
1660 const TimeZone
& tz
) const
1662 if ( flags
== Default_First
)
1664 flags
= GetCountry() == USA
? Sunday_First
: Monday_First
;
1667 wxDateTime_t nDayInYear
= GetDayOfYear(tz
);
1670 WeekDay wd
= GetWeekDay(tz
);
1671 if ( flags
== Sunday_First
)
1673 week
= (nDayInYear
- wd
+ 7) / 7;
1677 // have to shift the week days values
1678 week
= (nDayInYear
- (wd
- 1 + 7) % 7 + 7) / 7;
1681 // FIXME some more elegant way??
1682 WeekDay wdYearStart
= wxDateTime(1, Jan
, GetYear()).GetWeekDay();
1683 if ( wdYearStart
== Wed
|| wdYearStart
== Thu
)
1691 wxDateTime::wxDateTime_t
wxDateTime::GetWeekOfMonth(wxDateTime::WeekFlags flags
,
1692 const TimeZone
& tz
) const
1695 wxDateTime dtMonthStart
= wxDateTime(1, tm
.mon
, tm
.year
);
1696 int nWeek
= GetWeekOfYear(flags
) - dtMonthStart
.GetWeekOfYear(flags
) + 1;
1699 // this may happen for January when Jan, 1 is the last week of the
1701 nWeek
+= IsLeapYear(tm
.year
- 1) ? 53 : 52;
1704 return (wxDateTime::wxDateTime_t
)nWeek
;
1707 wxDateTime
& wxDateTime::SetToYearDay(wxDateTime::wxDateTime_t yday
)
1709 int year
= GetYear();
1710 wxDATETIME_CHECK( (0 < yday
) && (yday
<= GetNumberOfDays(year
)),
1711 _T("invalid year day") );
1713 bool isLeap
= IsLeapYear(year
);
1714 for ( Month mon
= Jan
; mon
< Inv_Month
; wxNextMonth(mon
) )
1716 // for Dec, we can't compare with gs_cumulatedDays[mon + 1], but we
1717 // don't need it neither - because of the CHECK above we know that
1718 // yday lies in December then
1719 if ( (mon
== Dec
) || (yday
< gs_cumulatedDays
[isLeap
][mon
+ 1]) )
1721 Set(yday
- gs_cumulatedDays
[isLeap
][mon
], mon
, year
);
1730 // ----------------------------------------------------------------------------
1731 // Julian day number conversion and related stuff
1732 // ----------------------------------------------------------------------------
1734 double wxDateTime::GetJulianDayNumber() const
1736 // JDN are always expressed for the GMT dates
1737 Tm
tm(ToTimezone(GMT0
).GetTm(GMT0
));
1739 double result
= GetTruncatedJDN(tm
.mday
, tm
.mon
, tm
.year
);
1741 // add the part GetTruncatedJDN() neglected
1744 // and now add the time: 86400 sec = 1 JDN
1745 return result
+ ((double)(60*(60*tm
.hour
+ tm
.min
) + tm
.sec
)) / 86400;
1748 double wxDateTime::GetRataDie() const
1750 // March 1 of the year 0 is Rata Die day -306 and JDN 1721119.5
1751 return GetJulianDayNumber() - 1721119.5 - 306;
1754 // ----------------------------------------------------------------------------
1755 // timezone and DST stuff
1756 // ----------------------------------------------------------------------------
1758 int wxDateTime::IsDST(wxDateTime::Country country
) const
1760 wxCHECK_MSG( country
== Country_Default
, -1,
1761 _T("country support not implemented") );
1763 // use the C RTL for the dates in the standard range
1764 time_t timet
= GetTicks();
1765 if ( timet
!= (time_t)-1 )
1767 tm
*tm
= localtime(&timet
);
1769 wxCHECK_MSG( tm
, -1, _T("localtime() failed") );
1771 return tm
->tm_isdst
;
1775 int year
= GetYear();
1777 if ( !IsDSTApplicable(year
, country
) )
1779 // no DST time in this year in this country
1783 return IsBetween(GetBeginDST(year
, country
), GetEndDST(year
, country
));
1787 wxDateTime
& wxDateTime::MakeTimezone(const TimeZone
& tz
, bool noDST
)
1789 long secDiff
= GetTimeZone() + tz
.GetOffset();
1791 // we need to know whether DST is or not in effect for this date unless
1792 // the test disabled by the caller
1793 if ( !noDST
&& (IsDST() == 1) )
1795 // FIXME we assume that the DST is always shifted by 1 hour
1799 return Subtract(wxTimeSpan::Seconds(secDiff
));
1802 // ----------------------------------------------------------------------------
1803 // wxDateTime to/from text representations
1804 // ----------------------------------------------------------------------------
1806 wxString
wxDateTime::Format(const wxChar
*format
, const TimeZone
& tz
) const
1808 wxCHECK_MSG( format
, _T(""), _T("NULL format in wxDateTime::Format") );
1810 // we have to use our own implementation if the date is out of range of
1811 // strftime() or if we use non standard specificators
1812 time_t time
= GetTicks();
1813 if ( (time
!= (time_t)-1) && !wxStrstr(format
, _T("%l")) )
1817 if ( tz
.GetOffset() == -GetTimeZone() )
1819 // we are working with local time
1820 tm
= localtime(&time
);
1822 // should never happen
1823 wxCHECK_MSG( tm
, wxEmptyString
, _T("localtime() failed") );
1827 time
+= (int)tz
.GetOffset();
1829 #if defined(__VMS__) || defined(__WATCOMC__) // time is unsigned so avoid warning
1830 int time2
= (int) time
;
1838 // should never happen
1839 wxCHECK_MSG( tm
, wxEmptyString
, _T("gmtime() failed") );
1843 tm
= (struct tm
*)NULL
;
1849 return CallStrftime(format
, tm
);
1851 //else: use generic code below
1854 // we only parse ANSI C format specifications here, no POSIX 2
1855 // complications, no GNU extensions but we do add support for a "%l" format
1856 // specifier allowing to get the number of milliseconds
1859 // used for calls to strftime() when we only deal with time
1860 struct tm tmTimeOnly
;
1861 tmTimeOnly
.tm_hour
= tm
.hour
;
1862 tmTimeOnly
.tm_min
= tm
.min
;
1863 tmTimeOnly
.tm_sec
= tm
.sec
;
1864 tmTimeOnly
.tm_wday
= 0;
1865 tmTimeOnly
.tm_yday
= 0;
1866 tmTimeOnly
.tm_mday
= 1; // any date will do
1867 tmTimeOnly
.tm_mon
= 0;
1868 tmTimeOnly
.tm_year
= 76;
1869 tmTimeOnly
.tm_isdst
= 0; // no DST, we adjust for tz ourselves
1871 wxString tmp
, res
, fmt
;
1872 for ( const wxChar
*p
= format
; *p
; p
++ )
1874 if ( *p
!= _T('%') )
1882 // set the default format
1885 case _T('Y'): // year has 4 digits
1889 case _T('j'): // day of year has 3 digits
1890 case _T('l'): // milliseconds have 3 digits
1895 // it's either another valid format specifier in which case
1896 // the format is "%02d" (for all the rest) or we have the
1897 // field width preceding the format in which case it will
1898 // override the default format anyhow
1902 bool restart
= TRUE
;
1907 // start of the format specification
1910 case _T('a'): // a weekday name
1912 // second parameter should be TRUE for abbreviated names
1913 res
+= GetWeekDayName(tm
.GetWeekDay(),
1914 *p
== _T('a') ? Name_Abbr
: Name_Full
);
1917 case _T('b'): // a month name
1919 res
+= GetMonthName(tm
.mon
,
1920 *p
== _T('b') ? Name_Abbr
: Name_Full
);
1923 case _T('c'): // locale default date and time representation
1924 case _T('x'): // locale default date representation
1926 // the problem: there is no way to know what do these format
1927 // specifications correspond to for the current locale.
1929 // the solution: use a hack and still use strftime(): first
1930 // find the YEAR which is a year in the strftime() range (1970
1931 // - 2038) whose Jan 1 falls on the same week day as the Jan 1
1932 // of the real year. Then make a copy of the format and
1933 // replace all occurences of YEAR in it with some unique
1934 // string not appearing anywhere else in it, then use
1935 // strftime() to format the date in year YEAR and then replace
1936 // YEAR back by the real year and the unique replacement
1937 // string back with YEAR. Notice that "all occurences of YEAR"
1938 // means all occurences of 4 digit as well as 2 digit form!
1940 // the bugs: we assume that neither of %c nor %x contains any
1941 // fields which may change between the YEAR and real year. For
1942 // example, the week number (%U, %W) and the day number (%j)
1943 // will change if one of these years is leap and the other one
1946 // find the YEAR: normally, for any year X, Jan 1 or the
1947 // year X + 28 is the same weekday as Jan 1 of X (because
1948 // the weekday advances by 1 for each normal X and by 2
1949 // for each leap X, hence by 5 every 4 years or by 35
1950 // which is 0 mod 7 every 28 years) but this rule breaks
1951 // down if there are years between X and Y which are
1952 // divisible by 4 but not leap (i.e. divisible by 100 but
1953 // not 400), hence the correction.
1955 int yearReal
= GetYear(tz
);
1956 int mod28
= yearReal
% 28;
1958 // be careful to not go too far - we risk to leave the
1963 year
= 1988 + mod28
; // 1988 == 0 (mod 28)
1967 year
= 1970 + mod28
- 10; // 1970 == 10 (mod 28)
1970 int nCentury
= year
/ 100,
1971 nCenturyReal
= yearReal
/ 100;
1973 // need to adjust for the years divisble by 400 which are
1974 // not leap but are counted like leap ones if we just take
1975 // the number of centuries in between for nLostWeekDays
1976 int nLostWeekDays
= (nCentury
- nCenturyReal
) -
1977 (nCentury
/ 4 - nCenturyReal
/ 4);
1979 // we have to gain back the "lost" weekdays: note that the
1980 // effect of this loop is to not do anything to
1981 // nLostWeekDays (which we won't use any more), but to
1982 // (indirectly) set the year correctly
1983 while ( (nLostWeekDays
% 7) != 0 )
1985 nLostWeekDays
+= year
++ % 4 ? 1 : 2;
1988 // at any rate, we couldn't go further than 1988 + 9 + 28!
1989 wxASSERT_MSG( year
< 2030,
1990 _T("logic error in wxDateTime::Format") );
1992 wxString strYear
, strYear2
;
1993 strYear
.Printf(_T("%d"), year
);
1994 strYear2
.Printf(_T("%d"), year
% 100);
1996 // find two strings not occuring in format (this is surely
1997 // not optimal way of doing it... improvements welcome!)
1998 wxString fmt
= format
;
1999 wxString replacement
= (wxChar
)-1;
2000 while ( fmt
.Find(replacement
) != wxNOT_FOUND
)
2002 replacement
<< (wxChar
)-1;
2005 wxString replacement2
= (wxChar
)-2;
2006 while ( fmt
.Find(replacement
) != wxNOT_FOUND
)
2008 replacement
<< (wxChar
)-2;
2011 // replace all occurences of year with it
2012 bool wasReplaced
= fmt
.Replace(strYear
, replacement
) > 0;
2014 wasReplaced
= fmt
.Replace(strYear2
, replacement2
) > 0;
2016 // use strftime() to format the same date but in supported
2019 // NB: we assume that strftime() doesn't check for the
2020 // date validity and will happily format the date
2021 // corresponding to Feb 29 of a non leap year (which
2022 // may happen if yearReal was leap and year is not)
2023 struct tm tmAdjusted
;
2025 tmAdjusted
.tm_hour
= tm
.hour
;
2026 tmAdjusted
.tm_min
= tm
.min
;
2027 tmAdjusted
.tm_sec
= tm
.sec
;
2028 tmAdjusted
.tm_wday
= tm
.GetWeekDay();
2029 tmAdjusted
.tm_yday
= GetDayOfYear();
2030 tmAdjusted
.tm_mday
= tm
.mday
;
2031 tmAdjusted
.tm_mon
= tm
.mon
;
2032 tmAdjusted
.tm_year
= year
- 1900;
2033 tmAdjusted
.tm_isdst
= 0; // no DST, already adjusted
2034 wxString str
= CallStrftime(*p
== _T('c') ? _T("%c")
2038 // now replace the occurence of 1999 with the real year
2039 wxString strYearReal
, strYearReal2
;
2040 strYearReal
.Printf(_T("%04d"), yearReal
);
2041 strYearReal2
.Printf(_T("%02d"), yearReal
% 100);
2042 str
.Replace(strYear
, strYearReal
);
2043 str
.Replace(strYear2
, strYearReal2
);
2045 // and replace back all occurences of replacement string
2048 str
.Replace(replacement2
, strYear2
);
2049 str
.Replace(replacement
, strYear
);
2056 case _T('d'): // day of a month (01-31)
2057 res
+= wxString::Format(fmt
, tm
.mday
);
2060 case _T('H'): // hour in 24h format (00-23)
2061 res
+= wxString::Format(fmt
, tm
.hour
);
2064 case _T('I'): // hour in 12h format (01-12)
2066 // 24h -> 12h, 0h -> 12h too
2067 int hour12
= tm
.hour
> 12 ? tm
.hour
- 12
2068 : tm
.hour
? tm
.hour
: 12;
2069 res
+= wxString::Format(fmt
, hour12
);
2073 case _T('j'): // day of the year
2074 res
+= wxString::Format(fmt
, GetDayOfYear(tz
));
2077 case _T('l'): // milliseconds (NOT STANDARD)
2078 res
+= wxString::Format(fmt
, GetMillisecond(tz
));
2081 case _T('m'): // month as a number (01-12)
2082 res
+= wxString::Format(fmt
, tm
.mon
+ 1);
2085 case _T('M'): // minute as a decimal number (00-59)
2086 res
+= wxString::Format(fmt
, tm
.min
);
2089 case _T('p'): // AM or PM string
2090 res
+= CallStrftime(_T("%p"), &tmTimeOnly
);
2093 case _T('S'): // second as a decimal number (00-61)
2094 res
+= wxString::Format(fmt
, tm
.sec
);
2097 case _T('U'): // week number in the year (Sunday 1st week day)
2098 res
+= wxString::Format(fmt
, GetWeekOfYear(Sunday_First
, tz
));
2101 case _T('W'): // week number in the year (Monday 1st week day)
2102 res
+= wxString::Format(fmt
, GetWeekOfYear(Monday_First
, tz
));
2105 case _T('w'): // weekday as a number (0-6), Sunday = 0
2106 res
+= wxString::Format(fmt
, tm
.GetWeekDay());
2109 // case _T('x'): -- handled with "%c"
2111 case _T('X'): // locale default time representation
2112 // just use strftime() to format the time for us
2113 res
+= CallStrftime(_T("%X"), &tmTimeOnly
);
2116 case _T('y'): // year without century (00-99)
2117 res
+= wxString::Format(fmt
, tm
.year
% 100);
2120 case _T('Y'): // year with century
2121 res
+= wxString::Format(fmt
, tm
.year
);
2124 case _T('Z'): // timezone name
2125 res
+= CallStrftime(_T("%Z"), &tmTimeOnly
);
2129 // is it the format width?
2131 while ( *p
== _T('-') || *p
== _T('+') ||
2132 *p
== _T(' ') || wxIsdigit(*p
) )
2137 if ( !fmt
.IsEmpty() )
2139 // we've only got the flags and width so far in fmt
2140 fmt
.Prepend(_T('%'));
2141 fmt
.Append(_T('d'));
2148 // no, it wasn't the width
2149 wxFAIL_MSG(_T("unknown format specificator"));
2151 // fall through and just copy it nevertheless
2153 case _T('%'): // a percent sign
2157 case 0: // the end of string
2158 wxFAIL_MSG(_T("missing format at the end of string"));
2160 // just put the '%' which was the last char in format
2170 // this function parses a string in (strict) RFC 822 format: see the section 5
2171 // of the RFC for the detailed description, but briefly it's something of the
2172 // form "Sat, 18 Dec 1999 00:48:30 +0100"
2174 // this function is "strict" by design - it must reject anything except true
2175 // RFC822 time specs.
2177 // TODO a great candidate for using reg exps
2178 const wxChar
*wxDateTime::ParseRfc822Date(const wxChar
* date
)
2180 wxCHECK_MSG( date
, (wxChar
*)NULL
, _T("NULL pointer in wxDateTime::Parse") );
2182 const wxChar
*p
= date
;
2183 const wxChar
*comma
= wxStrchr(p
, _T(','));
2186 // the part before comma is the weekday
2188 // skip it for now - we don't use but might check that it really
2189 // corresponds to the specfied date
2192 if ( *p
!= _T(' ') )
2194 wxLogDebug(_T("no space after weekday in RFC822 time spec"));
2196 return (wxChar
*)NULL
;
2202 // the following 1 or 2 digits are the day number
2203 if ( !wxIsdigit(*p
) )
2205 wxLogDebug(_T("day number expected in RFC822 time spec, none found"));
2207 return (wxChar
*)NULL
;
2210 wxDateTime_t day
= *p
++ - _T('0');
2211 if ( wxIsdigit(*p
) )
2214 day
+= *p
++ - _T('0');
2217 if ( *p
++ != _T(' ') )
2219 return (wxChar
*)NULL
;
2222 // the following 3 letters specify the month
2223 wxString
monName(p
, 3);
2225 if ( monName
== _T("Jan") )
2227 else if ( monName
== _T("Feb") )
2229 else if ( monName
== _T("Mar") )
2231 else if ( monName
== _T("Apr") )
2233 else if ( monName
== _T("May") )
2235 else if ( monName
== _T("Jun") )
2237 else if ( monName
== _T("Jul") )
2239 else if ( monName
== _T("Aug") )
2241 else if ( monName
== _T("Sep") )
2243 else if ( monName
== _T("Oct") )
2245 else if ( monName
== _T("Nov") )
2247 else if ( monName
== _T("Dec") )
2251 wxLogDebug(_T("Invalid RFC 822 month name '%s'"), monName
.c_str());
2253 return (wxChar
*)NULL
;
2258 if ( *p
++ != _T(' ') )
2260 return (wxChar
*)NULL
;
2264 if ( !wxIsdigit(*p
) )
2267 return (wxChar
*)NULL
;
2270 int year
= *p
++ - _T('0');
2272 if ( !wxIsdigit(*p
) )
2274 // should have at least 2 digits in the year
2275 return (wxChar
*)NULL
;
2279 year
+= *p
++ - _T('0');
2281 // is it a 2 digit year (as per original RFC 822) or a 4 digit one?
2282 if ( wxIsdigit(*p
) )
2285 year
+= *p
++ - _T('0');
2287 if ( !wxIsdigit(*p
) )
2289 // no 3 digit years please
2290 return (wxChar
*)NULL
;
2294 year
+= *p
++ - _T('0');
2297 if ( *p
++ != _T(' ') )
2299 return (wxChar
*)NULL
;
2302 // time is in the format hh:mm:ss and seconds are optional
2303 if ( !wxIsdigit(*p
) )
2305 return (wxChar
*)NULL
;
2308 wxDateTime_t hour
= *p
++ - _T('0');
2310 if ( !wxIsdigit(*p
) )
2312 return (wxChar
*)NULL
;
2316 hour
+= *p
++ - _T('0');
2318 if ( *p
++ != _T(':') )
2320 return (wxChar
*)NULL
;
2323 if ( !wxIsdigit(*p
) )
2325 return (wxChar
*)NULL
;
2328 wxDateTime_t min
= *p
++ - _T('0');
2330 if ( !wxIsdigit(*p
) )
2332 return (wxChar
*)NULL
;
2336 min
+= *p
++ - _T('0');
2338 wxDateTime_t sec
= 0;
2339 if ( *p
++ == _T(':') )
2341 if ( !wxIsdigit(*p
) )
2343 return (wxChar
*)NULL
;
2346 sec
= *p
++ - _T('0');
2348 if ( !wxIsdigit(*p
) )
2350 return (wxChar
*)NULL
;
2354 sec
+= *p
++ - _T('0');
2357 if ( *p
++ != _T(' ') )
2359 return (wxChar
*)NULL
;
2362 // and now the interesting part: the timezone
2364 if ( *p
== _T('-') || *p
== _T('+') )
2366 // the explicit offset given: it has the form of hhmm
2367 bool plus
= *p
++ == _T('+');
2369 if ( !wxIsdigit(*p
) || !wxIsdigit(*(p
+ 1)) )
2371 return (wxChar
*)NULL
;
2375 offset
= 60*(10*(*p
- _T('0')) + (*(p
+ 1) - _T('0')));
2379 if ( !wxIsdigit(*p
) || !wxIsdigit(*(p
+ 1)) )
2381 return (wxChar
*)NULL
;
2385 offset
+= 10*(*p
- _T('0')) + (*(p
+ 1) - _T('0'));
2396 // the symbolic timezone given: may be either military timezone or one
2397 // of standard abbreviations
2400 // military: Z = UTC, J unused, A = -1, ..., Y = +12
2401 static const int offsets
[26] =
2403 //A B C D E F G H I J K L M
2404 -1, -2, -3, -4, -5, -6, -7, -8, -9, 0, -10, -11, -12,
2405 //N O P R Q S T U V W Z Y Z
2406 +1, +2, +3, +4, +5, +6, +7, +8, +9, +10, +11, +12, 0
2409 if ( *p
< _T('A') || *p
> _T('Z') || *p
== _T('J') )
2411 wxLogDebug(_T("Invalid militaty timezone '%c'"), *p
);
2413 return (wxChar
*)NULL
;
2416 offset
= offsets
[*p
++ - _T('A')];
2422 if ( tz
== _T("UT") || tz
== _T("UTC") || tz
== _T("GMT") )
2424 else if ( tz
== _T("AST") )
2425 offset
= AST
- GMT0
;
2426 else if ( tz
== _T("ADT") )
2427 offset
= ADT
- GMT0
;
2428 else if ( tz
== _T("EST") )
2429 offset
= EST
- GMT0
;
2430 else if ( tz
== _T("EDT") )
2431 offset
= EDT
- GMT0
;
2432 else if ( tz
== _T("CST") )
2433 offset
= CST
- GMT0
;
2434 else if ( tz
== _T("CDT") )
2435 offset
= CDT
- GMT0
;
2436 else if ( tz
== _T("MST") )
2437 offset
= MST
- GMT0
;
2438 else if ( tz
== _T("MDT") )
2439 offset
= MDT
- GMT0
;
2440 else if ( tz
== _T("PST") )
2441 offset
= PST
- GMT0
;
2442 else if ( tz
== _T("PDT") )
2443 offset
= PDT
- GMT0
;
2446 wxLogDebug(_T("Unknown RFC 822 timezone '%s'"), p
);
2448 return (wxChar
*)NULL
;
2458 // the spec was correct
2459 Set(day
, mon
, year
, hour
, min
, sec
);
2460 MakeTimezone((wxDateTime_t
)(60*offset
));
2465 const wxChar
*wxDateTime::ParseFormat(const wxChar
*date
,
2466 const wxChar
*format
,
2467 const wxDateTime
& dateDef
)
2469 wxCHECK_MSG( date
&& format
, (wxChar
*)NULL
,
2470 _T("NULL pointer in wxDateTime::ParseFormat()") );
2475 // what fields have we found?
2476 bool haveWDay
= FALSE
,
2485 bool hourIsIn12hFormat
= FALSE
, // or in 24h one?
2486 isPM
= FALSE
; // AM by default
2488 // and the value of the items we have (init them to get rid of warnings)
2489 wxDateTime_t sec
= 0,
2492 WeekDay wday
= Inv_WeekDay
;
2493 wxDateTime_t yday
= 0,
2495 wxDateTime::Month mon
= Inv_Month
;
2498 const wxChar
*input
= date
;
2499 for ( const wxChar
*fmt
= format
; *fmt
; fmt
++ )
2501 if ( *fmt
!= _T('%') )
2503 if ( wxIsspace(*fmt
) )
2505 // a white space in the format string matches 0 or more white
2506 // spaces in the input
2507 while ( wxIsspace(*input
) )
2514 // any other character (not whitespace, not '%') must be
2515 // matched by itself in the input
2516 if ( *input
++ != *fmt
)
2519 return (wxChar
*)NULL
;
2523 // done with this format char
2527 // start of a format specification
2529 // parse the optional width
2531 while ( isdigit(*++fmt
) )
2534 width
+= *fmt
- _T('0');
2537 // then the format itself
2540 case _T('a'): // a weekday name
2543 int flag
= *fmt
== _T('a') ? Name_Abbr
: Name_Full
;
2544 wday
= GetWeekDayFromName(GetAlphaToken(input
), flag
);
2545 if ( wday
== Inv_WeekDay
)
2548 return (wxChar
*)NULL
;
2554 case _T('b'): // a month name
2557 int flag
= *fmt
== _T('b') ? Name_Abbr
: Name_Full
;
2558 mon
= GetMonthFromName(GetAlphaToken(input
), flag
);
2559 if ( mon
== Inv_Month
)
2562 return (wxChar
*)NULL
;
2568 case _T('c'): // locale default date and time representation
2572 // this is the format which corresponds to ctime() output
2573 // and strptime("%c") should parse it, so try it first
2574 static const wxChar
*fmtCtime
= _T("%a %b %d %H:%M:%S %Y");
2576 const wxChar
*result
= dt
.ParseFormat(input
, fmtCtime
);
2579 result
= dt
.ParseFormat(input
, _T("%x %X"));
2584 result
= dt
.ParseFormat(input
, _T("%X %x"));
2589 // we've tried everything and still no match
2590 return (wxChar
*)NULL
;
2595 haveDay
= haveMon
= haveYear
=
2596 haveHour
= haveMin
= haveSec
= TRUE
;
2610 case _T('d'): // day of a month (01-31)
2611 if ( !GetNumericToken(width
, input
, &num
) ||
2612 (num
> 31) || (num
< 1) )
2615 return (wxChar
*)NULL
;
2618 // we can't check whether the day range is correct yet, will
2619 // do it later - assume ok for now
2621 mday
= (wxDateTime_t
)num
;
2624 case _T('H'): // hour in 24h format (00-23)
2625 if ( !GetNumericToken(width
, input
, &num
) || (num
> 23) )
2628 return (wxChar
*)NULL
;
2632 hour
= (wxDateTime_t
)num
;
2635 case _T('I'): // hour in 12h format (01-12)
2636 if ( !GetNumericToken(width
, input
, &num
) || !num
|| (num
> 12) )
2639 return (wxChar
*)NULL
;
2643 hourIsIn12hFormat
= TRUE
;
2644 hour
= (wxDateTime_t
)(num
% 12); // 12 should be 0
2647 case _T('j'): // day of the year
2648 if ( !GetNumericToken(width
, input
, &num
) || !num
|| (num
> 366) )
2651 return (wxChar
*)NULL
;
2655 yday
= (wxDateTime_t
)num
;
2658 case _T('m'): // month as a number (01-12)
2659 if ( !GetNumericToken(width
, input
, &num
) || !num
|| (num
> 12) )
2662 return (wxChar
*)NULL
;
2666 mon
= (Month
)(num
- 1);
2669 case _T('M'): // minute as a decimal number (00-59)
2670 if ( !GetNumericToken(width
, input
, &num
) || (num
> 59) )
2673 return (wxChar
*)NULL
;
2677 min
= (wxDateTime_t
)num
;
2680 case _T('p'): // AM or PM string
2682 wxString am
, pm
, token
= GetAlphaToken(input
);
2684 GetAmPmStrings(&am
, &pm
);
2685 if ( token
.CmpNoCase(pm
) == 0 )
2689 else if ( token
.CmpNoCase(am
) != 0 )
2692 return (wxChar
*)NULL
;
2697 case _T('r'): // time as %I:%M:%S %p
2700 input
= dt
.ParseFormat(input
, _T("%I:%M:%S %p"));
2704 return (wxChar
*)NULL
;
2707 haveHour
= haveMin
= haveSec
= TRUE
;
2716 case _T('R'): // time as %H:%M
2719 input
= dt
.ParseFormat(input
, _T("%H:%M"));
2723 return (wxChar
*)NULL
;
2726 haveHour
= haveMin
= TRUE
;
2733 case _T('S'): // second as a decimal number (00-61)
2734 if ( !GetNumericToken(width
, input
, &num
) || (num
> 61) )
2737 return (wxChar
*)NULL
;
2741 sec
= (wxDateTime_t
)num
;
2744 case _T('T'): // time as %H:%M:%S
2747 input
= dt
.ParseFormat(input
, _T("%H:%M:%S"));
2751 return (wxChar
*)NULL
;
2754 haveHour
= haveMin
= haveSec
= TRUE
;
2763 case _T('w'): // weekday as a number (0-6), Sunday = 0
2764 if ( !GetNumericToken(width
, input
, &num
) || (wday
> 6) )
2767 return (wxChar
*)NULL
;
2771 wday
= (WeekDay
)num
;
2774 case _T('x'): // locale default date representation
2775 #ifdef HAVE_STRPTIME
2776 // try using strptime() - it may fail even if the input is
2777 // correct but the date is out of range, so we will fall back
2778 // to our generic code anyhow (FIXME !Unicode friendly)
2781 const wxChar
*result
= strptime(input
, "%x", &tm
);
2786 haveDay
= haveMon
= haveYear
= TRUE
;
2788 year
= 1900 + tm
.tm_year
;
2789 mon
= (Month
)tm
.tm_mon
;
2795 #endif // HAVE_STRPTIME
2797 // TODO query the LOCALE_IDATE setting under Win32
2801 wxString fmtDate
, fmtDateAlt
;
2802 if ( IsWestEuropeanCountry(GetCountry()) ||
2803 GetCountry() == Russia
)
2805 fmtDate
= _T("%d/%m/%y");
2806 fmtDateAlt
= _T("%m/%d/%y");
2810 fmtDate
= _T("%m/%d/%y");
2811 fmtDateAlt
= _T("%d/%m/%y");
2814 const wxChar
*result
= dt
.ParseFormat(input
, fmtDate
);
2818 // ok, be nice and try another one
2819 result
= dt
.ParseFormat(input
, fmtDateAlt
);
2825 return (wxChar
*)NULL
;
2830 haveDay
= haveMon
= haveYear
= TRUE
;
2841 case _T('X'): // locale default time representation
2842 #ifdef HAVE_STRPTIME
2844 // use strptime() to do it for us (FIXME !Unicode friendly)
2846 input
= strptime(input
, "%X", &tm
);
2849 return (wxChar
*)NULL
;
2852 haveHour
= haveMin
= haveSec
= TRUE
;
2858 #else // !HAVE_STRPTIME
2859 // TODO under Win32 we can query the LOCALE_ITIME system
2860 // setting which says whether the default time format is
2863 // try to parse what follows as "%H:%M:%S" and, if this
2864 // fails, as "%I:%M:%S %p" - this should catch the most
2868 const wxChar
*result
= dt
.ParseFormat(input
, _T("%T"));
2871 result
= dt
.ParseFormat(input
, _T("%r"));
2877 return (wxChar
*)NULL
;
2880 haveHour
= haveMin
= haveSec
= TRUE
;
2889 #endif // HAVE_STRPTIME/!HAVE_STRPTIME
2892 case _T('y'): // year without century (00-99)
2893 if ( !GetNumericToken(width
, input
, &num
) || (num
> 99) )
2896 return (wxChar
*)NULL
;
2901 // TODO should have an option for roll over date instead of
2902 // hard coding it here
2903 year
= (num
> 30 ? 1900 : 2000) + (wxDateTime_t
)num
;
2906 case _T('Y'): // year with century
2907 if ( !GetNumericToken(width
, input
, &num
) )
2910 return (wxChar
*)NULL
;
2914 year
= (wxDateTime_t
)num
;
2917 case _T('Z'): // timezone name
2918 wxFAIL_MSG(_T("TODO"));
2921 case _T('%'): // a percent sign
2922 if ( *input
++ != _T('%') )
2925 return (wxChar
*)NULL
;
2929 case 0: // the end of string
2930 wxFAIL_MSG(_T("unexpected format end"));
2934 default: // not a known format spec
2935 return (wxChar
*)NULL
;
2939 // format matched, try to construct a date from what we have now
2941 if ( dateDef
.IsValid() )
2943 // take this date as default
2944 tmDef
= dateDef
.GetTm();
2946 else if ( IsValid() )
2948 // if this date is valid, don't change it
2953 // no default and this date is invalid - fall back to Today()
2954 tmDef
= Today().GetTm();
2965 // TODO we don't check here that the values are consistent, if both year
2966 // day and month/day were found, we just ignore the year day and we
2967 // also always ignore the week day
2968 if ( haveMon
&& haveDay
)
2970 if ( mday
> GetNumOfDaysInMonth(tm
.year
, mon
) )
2972 wxLogDebug(_T("bad month day in wxDateTime::ParseFormat"));
2974 return (wxChar
*)NULL
;
2980 else if ( haveYDay
)
2982 if ( yday
> GetNumberOfDays(tm
.year
) )
2984 wxLogDebug(_T("bad year day in wxDateTime::ParseFormat"));
2986 return (wxChar
*)NULL
;
2989 Tm tm2
= wxDateTime(1, Jan
, tm
.year
).SetToYearDay(yday
).GetTm();
2996 if ( haveHour
&& hourIsIn12hFormat
&& isPM
)
2998 // translate to 24hour format
3001 //else: either already in 24h format or no translation needed
3024 const wxChar
*wxDateTime::ParseDateTime(const wxChar
*date
)
3026 wxCHECK_MSG( date
, (wxChar
*)NULL
, _T("NULL pointer in wxDateTime::Parse") );
3028 // there is a public domain version of getdate.y, but it only works for
3030 wxFAIL_MSG(_T("TODO"));
3032 return (wxChar
*)NULL
;
3035 const wxChar
*wxDateTime::ParseDate(const wxChar
*date
)
3037 // this is a simplified version of ParseDateTime() which understands only
3038 // "today" (for wxDate compatibility) and digits only otherwise (and not
3039 // all esoteric constructions ParseDateTime() knows about)
3041 wxCHECK_MSG( date
, (wxChar
*)NULL
, _T("NULL pointer in wxDateTime::Parse") );
3043 const wxChar
*p
= date
;
3044 while ( wxIsspace(*p
) )
3047 // some special cases
3051 int dayDiffFromToday
;
3054 { wxTRANSLATE("today"), 0 },
3055 { wxTRANSLATE("yesterday"), -1 },
3056 { wxTRANSLATE("tomorrow"), 1 },
3059 for ( size_t n
= 0; n
< WXSIZEOF(literalDates
); n
++ )
3061 wxString date
= wxGetTranslation(literalDates
[n
].str
);
3062 size_t len
= date
.length();
3063 if ( wxStrlen(p
) >= len
&& (wxString(p
, len
).CmpNoCase(date
) == 0) )
3065 // nothing can follow this, so stop here
3068 int dayDiffFromToday
= literalDates
[n
].dayDiffFromToday
;
3070 if ( dayDiffFromToday
)
3072 *this += wxDateSpan::Days(dayDiffFromToday
);
3079 // We try to guess what we have here: for each new (numeric) token, we
3080 // determine if it can be a month, day or a year. Of course, there is an
3081 // ambiguity as some numbers may be days as well as months, so we also
3082 // have the ability to back track.
3085 bool haveDay
= FALSE
, // the months day?
3086 haveWDay
= FALSE
, // the day of week?
3087 haveMon
= FALSE
, // the month?
3088 haveYear
= FALSE
; // the year?
3090 // and the value of the items we have (init them to get rid of warnings)
3091 WeekDay wday
= Inv_WeekDay
;
3092 wxDateTime_t day
= 0;
3093 wxDateTime::Month mon
= Inv_Month
;
3096 // tokenize the string
3098 static const wxChar
*dateDelimiters
= _T(".,/-\t\n ");
3099 wxStringTokenizer
tok(p
, dateDelimiters
);
3100 while ( tok
.HasMoreTokens() )
3102 wxString token
= tok
.GetNextToken();
3108 if ( token
.ToULong(&val
) )
3110 // guess what this number is
3116 if ( !haveMon
&& val
> 0 && val
<= 12 )
3118 // assume it is month
3121 else // not the month
3123 wxDateTime_t maxDays
= haveMon
3124 ? GetNumOfDaysInMonth(haveYear
? year
: Inv_Year
, mon
)
3128 if ( (val
== 0) || (val
> (unsigned long)maxDays
) ) // cast to shut up compiler warning in BCC
3145 year
= (wxDateTime_t
)val
;
3154 day
= (wxDateTime_t
)val
;
3160 mon
= (Month
)(val
- 1);
3163 else // not a number
3165 // be careful not to overwrite the current mon value
3166 Month mon2
= GetMonthFromName(token
, Name_Full
| Name_Abbr
);
3167 if ( mon2
!= Inv_Month
)
3172 // but we already have a month - maybe we guessed wrong?
3175 // no need to check in month range as always < 12, but
3176 // the days are counted from 1 unlike the months
3177 day
= (wxDateTime_t
)mon
+ 1;
3182 // could possible be the year (doesn't the year come
3183 // before the month in the japanese format?) (FIXME)
3192 else // not a valid month name
3194 wday
= GetWeekDayFromName(token
, Name_Full
| Name_Abbr
);
3195 if ( wday
!= Inv_WeekDay
)
3205 else // not a valid weekday name
3208 static const wxChar
*ordinals
[] =
3210 wxTRANSLATE("first"),
3211 wxTRANSLATE("second"),
3212 wxTRANSLATE("third"),
3213 wxTRANSLATE("fourth"),
3214 wxTRANSLATE("fifth"),
3215 wxTRANSLATE("sixth"),
3216 wxTRANSLATE("seventh"),
3217 wxTRANSLATE("eighth"),
3218 wxTRANSLATE("ninth"),
3219 wxTRANSLATE("tenth"),
3220 wxTRANSLATE("eleventh"),
3221 wxTRANSLATE("twelfth"),
3222 wxTRANSLATE("thirteenth"),
3223 wxTRANSLATE("fourteenth"),
3224 wxTRANSLATE("fifteenth"),
3225 wxTRANSLATE("sixteenth"),
3226 wxTRANSLATE("seventeenth"),
3227 wxTRANSLATE("eighteenth"),
3228 wxTRANSLATE("nineteenth"),
3229 wxTRANSLATE("twentieth"),
3230 // that's enough - otherwise we'd have problems with
3231 // composite (or not) ordinals
3235 for ( n
= 0; n
< WXSIZEOF(ordinals
); n
++ )
3237 if ( token
.CmpNoCase(ordinals
[n
]) == 0 )
3243 if ( n
== WXSIZEOF(ordinals
) )
3245 // stop here - something unknown
3252 // don't try anything here (as in case of numeric day
3253 // above) - the symbolic day spec should always
3254 // precede the month/year
3260 day
= (wxDateTime_t
)(n
+ 1);
3265 nPosCur
= tok
.GetPosition();
3268 // either no more tokens or the scan was stopped by something we couldn't
3269 // parse - in any case, see if we can construct a date from what we have
3270 if ( !haveDay
&& !haveWDay
)
3272 wxLogDebug(_T("ParseDate: no day, no weekday hence no date."));
3274 return (wxChar
*)NULL
;
3277 if ( haveWDay
&& (haveMon
|| haveYear
|| haveDay
) &&
3278 !(haveDay
&& haveMon
&& haveYear
) )
3280 // without adjectives (which we don't support here) the week day only
3281 // makes sense completely separately or with the full date
3282 // specification (what would "Wed 1999" mean?)
3283 return (wxChar
*)NULL
;
3286 if ( !haveWDay
&& haveYear
&& !(haveDay
&& haveMon
) )
3288 // may be we have month and day instead of day and year?
3289 if ( haveDay
&& !haveMon
)
3293 // exchange day and month
3294 mon
= (wxDateTime::Month
)(day
- 1);
3296 // we're in the current year then
3298 (unsigned)year
<= GetNumOfDaysInMonth(Inv_Year
, mon
) )
3305 //else: no, can't exchange, leave haveMon == FALSE
3311 // if we give the year, month and day must be given too
3312 wxLogDebug(_T("ParseDate: day and month should be specified if year is."));
3314 return (wxChar
*)NULL
;
3320 mon
= GetCurrentMonth();
3325 year
= GetCurrentYear();
3330 Set(day
, mon
, year
);
3334 // check that it is really the same
3335 if ( GetWeekDay() != wday
)
3337 // inconsistency detected
3338 wxLogDebug(_T("ParseDate: inconsistent day/weekday."));
3340 return (wxChar
*)NULL
;
3348 SetToWeekDayInSameWeek(wday
);
3351 // return the pointer to the first unparsed char
3353 if ( nPosCur
&& wxStrchr(dateDelimiters
, *(p
- 1)) )
3355 // if we couldn't parse the token after the delimiter, put back the
3356 // delimiter as well
3363 const wxChar
*wxDateTime::ParseTime(const wxChar
*time
)
3365 wxCHECK_MSG( time
, (wxChar
*)NULL
, _T("NULL pointer in wxDateTime::Parse") );
3367 // first try some extra things
3374 { wxTRANSLATE("noon"), 12 },
3375 { wxTRANSLATE("midnight"), 00 },
3379 for ( size_t n
= 0; n
< WXSIZEOF(stdTimes
); n
++ )
3381 wxString timeString
= wxGetTranslation(stdTimes
[n
].name
);
3382 size_t len
= timeString
.length();
3383 if ( timeString
.CmpNoCase(wxString(time
, len
)) == 0 )
3385 Set(stdTimes
[n
].hour
, 0, 0);
3391 // try all time formats we may think about in the order from longest to
3394 // 12hour with AM/PM?
3395 const wxChar
*result
= ParseFormat(time
, _T("%I:%M:%S %p"));
3399 // normally, it's the same, but why not try it?
3400 result
= ParseFormat(time
, _T("%H:%M:%S"));
3405 // 12hour with AM/PM but without seconds?
3406 result
= ParseFormat(time
, _T("%I:%M %p"));
3412 result
= ParseFormat(time
, _T("%H:%M"));
3417 // just the hour and AM/PM?
3418 result
= ParseFormat(time
, _T("%I %p"));
3424 result
= ParseFormat(time
, _T("%H"));
3429 // parse the standard format: normally it is one of the formats above
3430 // but it may be set to something completely different by the user
3431 result
= ParseFormat(time
, _T("%X"));
3434 // TODO: parse timezones
3439 // ----------------------------------------------------------------------------
3440 // Workdays and holidays support
3441 // ----------------------------------------------------------------------------
3443 bool wxDateTime::IsWorkDay(Country
WXUNUSED(country
)) const
3445 return !wxDateTimeHolidayAuthority::IsHoliday(*this);
3448 // ============================================================================
3450 // ============================================================================
3452 // this enum is only used in wxTimeSpan::Format() below but we can't declare
3453 // it locally to the method as it provokes an internal compiler error in egcs
3454 // 2.91.60 when building with -O2
3465 // not all strftime(3) format specifiers make sense here because, for example,
3466 // a time span doesn't have a year nor a timezone
3468 // Here are the ones which are supported (all of them are supported by strftime
3470 // %H hour in 24 hour format
3471 // %M minute (00 - 59)
3472 // %S second (00 - 59)
3475 // Also, for MFC CTimeSpan compatibility, we support
3476 // %D number of days
3478 // And, to be better than MFC :-), we also have
3479 // %E number of wEeks
3480 // %l milliseconds (000 - 999)
3481 wxString
wxTimeSpan::Format(const wxChar
*format
) const
3483 wxCHECK_MSG( format
, _T(""), _T("NULL format in wxTimeSpan::Format") );
3486 str
.Alloc(wxStrlen(format
));
3488 // Suppose we have wxTimeSpan ts(1 /* hour */, 2 /* min */, 3 /* sec */)
3490 // Then, of course, ts.Format("%H:%M:%S") must return "01:02:03", but the
3491 // question is what should ts.Format("%S") do? The code here returns "3273"
3492 // in this case (i.e. the total number of seconds, not just seconds % 60)
3493 // because, for me, this call means "give me entire time interval in
3494 // seconds" and not "give me the seconds part of the time interval"
3496 // If we agree that it should behave like this, it is clear that the
3497 // interpretation of each format specifier depends on the presence of the
3498 // other format specs in the string: if there was "%H" before "%M", we
3499 // should use GetMinutes() % 60, otherwise just GetMinutes() &c
3501 // we remember the most important unit found so far
3502 TimeSpanPart partBiggest
= Part_MSec
;
3504 for ( const wxChar
*pch
= format
; *pch
; pch
++ )
3508 if ( ch
== _T('%') )
3510 // the start of the format specification of the printf() below
3511 wxString fmtPrefix
= _T('%');
3516 ch
= *++pch
; // get the format spec char
3520 wxFAIL_MSG( _T("invalid format character") );
3526 // skip the part below switch
3531 if ( partBiggest
< Part_Day
)
3537 partBiggest
= Part_Day
;
3542 partBiggest
= Part_Week
;
3548 if ( partBiggest
< Part_Hour
)
3554 partBiggest
= Part_Hour
;
3557 fmtPrefix
+= _T("02");
3561 n
= GetMilliseconds().ToLong();
3562 if ( partBiggest
< Part_MSec
)
3566 //else: no need to reset partBiggest to Part_MSec, it is
3567 // the least significant one anyhow
3569 fmtPrefix
+= _T("03");
3574 if ( partBiggest
< Part_Min
)
3580 partBiggest
= Part_Min
;
3583 fmtPrefix
+= _T("02");
3587 n
= GetSeconds().ToLong();
3588 if ( partBiggest
< Part_Sec
)
3594 partBiggest
= Part_Sec
;
3597 fmtPrefix
+= _T("02");
3601 str
+= wxString::Format(fmtPrefix
+ _T("ld"), n
);
3605 // normal character, just copy
3613 // ============================================================================
3614 // wxDateTimeHolidayAuthority and related classes
3615 // ============================================================================
3617 #include "wx/arrimpl.cpp"
3619 WX_DEFINE_OBJARRAY(wxDateTimeArray
);
3621 static int wxCMPFUNC_CONV
3622 wxDateTimeCompareFunc(wxDateTime
**first
, wxDateTime
**second
)
3624 wxDateTime dt1
= **first
,
3627 return dt1
== dt2
? 0 : dt1
< dt2
? -1 : +1;
3630 // ----------------------------------------------------------------------------
3631 // wxDateTimeHolidayAuthority
3632 // ----------------------------------------------------------------------------
3634 wxHolidayAuthoritiesArray
wxDateTimeHolidayAuthority::ms_authorities
;
3637 bool wxDateTimeHolidayAuthority::IsHoliday(const wxDateTime
& dt
)
3639 size_t count
= ms_authorities
.GetCount();
3640 for ( size_t n
= 0; n
< count
; n
++ )
3642 if ( ms_authorities
[n
]->DoIsHoliday(dt
) )
3653 wxDateTimeHolidayAuthority::GetHolidaysInRange(const wxDateTime
& dtStart
,
3654 const wxDateTime
& dtEnd
,
3655 wxDateTimeArray
& holidays
)
3657 wxDateTimeArray hol
;
3661 size_t count
= ms_authorities
.GetCount();
3662 for ( size_t nAuth
= 0; nAuth
< count
; nAuth
++ )
3664 ms_authorities
[nAuth
]->DoGetHolidaysInRange(dtStart
, dtEnd
, hol
);
3666 WX_APPEND_ARRAY(holidays
, hol
);
3669 holidays
.Sort(wxDateTimeCompareFunc
);
3671 return holidays
.GetCount();
3675 void wxDateTimeHolidayAuthority::ClearAllAuthorities()
3677 WX_CLEAR_ARRAY(ms_authorities
);
3681 void wxDateTimeHolidayAuthority::AddAuthority(wxDateTimeHolidayAuthority
*auth
)
3683 ms_authorities
.Add(auth
);
3686 // ----------------------------------------------------------------------------
3687 // wxDateTimeWorkDays
3688 // ----------------------------------------------------------------------------
3690 bool wxDateTimeWorkDays::DoIsHoliday(const wxDateTime
& dt
) const
3692 wxDateTime::WeekDay wd
= dt
.GetWeekDay();
3694 return (wd
== wxDateTime::Sun
) || (wd
== wxDateTime::Sat
);
3697 size_t wxDateTimeWorkDays::DoGetHolidaysInRange(const wxDateTime
& dtStart
,
3698 const wxDateTime
& dtEnd
,
3699 wxDateTimeArray
& holidays
) const
3701 if ( dtStart
> dtEnd
)
3703 wxFAIL_MSG( _T("invalid date range in GetHolidaysInRange") );
3710 // instead of checking all days, start with the first Sat after dtStart and
3711 // end with the last Sun before dtEnd
3712 wxDateTime dtSatFirst
= dtStart
.GetNextWeekDay(wxDateTime::Sat
),
3713 dtSatLast
= dtEnd
.GetPrevWeekDay(wxDateTime::Sat
),
3714 dtSunFirst
= dtStart
.GetNextWeekDay(wxDateTime::Sun
),
3715 dtSunLast
= dtEnd
.GetPrevWeekDay(wxDateTime::Sun
),
3718 for ( dt
= dtSatFirst
; dt
<= dtSatLast
; dt
+= wxDateSpan::Week() )
3723 for ( dt
= dtSunFirst
; dt
<= dtSunLast
; dt
+= wxDateSpan::Week() )
3728 return holidays
.GetCount();
3731 #endif // wxUSE_DATETIME