1 ///////////////////////////////////////////////////////////////////////////////
3 // Purpose: implementation of time/date related classes
4 // Author: Vadim Zeitlin
8 // Copyright: (c) 1999 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // parts of code taken from sndcal library by Scott E. Lee:
11 // Copyright 1993-1995, Scott E. Lee, all rights reserved.
12 // Permission granted to use, copy, modify, distribute and sell
13 // so long as the above copyright and this permission statement
14 // are retained in all copies.
16 // Licence: wxWindows license
17 ///////////////////////////////////////////////////////////////////////////////
20 * Implementation notes:
22 * 1. the time is stored as a 64bit integer containing the signed number of
23 * milliseconds since Jan 1. 1970 (the Unix Epoch) - so it is always
26 * 2. the range is thus something about 580 million years, but due to current
27 * algorithms limitations, only dates from Nov 24, 4714BC are handled
29 * 3. standard ANSI C functions are used to do time calculations whenever
30 * possible, i.e. when the date is in the range Jan 1, 1970 to 2038
32 * 4. otherwise, the calculations are done by converting the date to/from JDN
33 * first (the range limitation mentioned above comes from here: the
34 * algorithm used by Scott E. Lee's code only works for positive JDNs, more
37 * 5. the object constructed for the given DD-MM-YYYY HH:MM:SS corresponds to
38 * this moment in local time and may be converted to the object
39 * corresponding to the same date/time in another time zone by using
42 * 6. the conversions to the current (or any other) timezone are done when the
43 * internal time representation is converted to the broken-down one in
47 // ============================================================================
49 // ============================================================================
51 // ----------------------------------------------------------------------------
53 // ----------------------------------------------------------------------------
56 #pragma implementation "datetime.h"
59 // For compilers that support precompilation, includes "wx.h".
60 #include "wx/wxprec.h"
67 #include "wx/string.h"
72 #include "wx/thread.h"
73 #include "wx/tokenzr.h"
75 #define wxDEFINE_TIME_CONSTANTS
77 #include "wx/datetime.h"
80 #define WX_TIMEZONE timezone
83 // Is this right? Just a guess. (JACS)
85 #define timezone _timezone
88 // ----------------------------------------------------------------------------
90 // ----------------------------------------------------------------------------
93 static const int MONTHS_IN_YEAR
= 12;
95 static const int SECONDS_IN_MINUTE
= 60;
97 static const long SECONDS_PER_DAY
= 86400l;
99 static const long MILLISECONDS_PER_DAY
= 86400000l;
101 // this is the integral part of JDN of the midnight of Jan 1, 1970
102 // (i.e. JDN(Jan 1, 1970) = 2440587.5)
103 static const long EPOCH_JDN
= 2440587l;
105 // the date of JDN -0.5 (as we don't work with fractional parts, this is the
106 // reference date for us) is Nov 24, 4714BC
107 static const int JDN_0_YEAR
= -4713;
108 static const int JDN_0_MONTH
= wxDateTime::Nov
;
109 static const int JDN_0_DAY
= 24;
111 // the constants used for JDN calculations
112 static const long JDN_OFFSET
= 32046l;
113 static const long DAYS_PER_5_MONTHS
= 153l;
114 static const long DAYS_PER_4_YEARS
= 1461l;
115 static const long DAYS_PER_400_YEARS
= 146097l;
117 // ----------------------------------------------------------------------------
119 // ----------------------------------------------------------------------------
121 // a critical section is needed to protect GetTimeZone() static
122 // variable in MT case
124 wxCriticalSection gs_critsectTimezone
;
125 #endif // wxUSE_THREADS
127 // the symbolic names for date spans
128 wxDateSpan wxYear
= wxDateSpan(1, 0, 0, 0);
129 wxDateSpan wxMonth
= wxDateSpan(0, 1, 0, 0);
130 wxDateSpan wxWeek
= wxDateSpan(0, 0, 1, 0);
131 wxDateSpan wxDay
= wxDateSpan(0, 0, 0, 1);
133 // ----------------------------------------------------------------------------
135 // ----------------------------------------------------------------------------
137 // get the number of days in the given month of the given year
139 wxDateTime::wxDateTime_t
GetNumOfDaysInMonth(int year
, wxDateTime::Month month
)
141 // the number of days in month in Julian/Gregorian calendar: the first line
142 // is for normal years, the second one is for the leap ones
143 static wxDateTime::wxDateTime_t daysInMonth
[2][MONTHS_IN_YEAR
] =
145 { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
146 { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
149 return daysInMonth
[wxDateTime::IsLeapYear(year
)][month
];
152 // ensure that the timezone variable is set by calling localtime
153 static int GetTimeZone()
155 // set to TRUE when the timezone is set
156 static bool s_timezoneSet
= FALSE
;
158 wxCRIT_SECT_LOCKER(lock
, gs_critsectTimezone
);
160 if ( !s_timezoneSet
)
162 // just call localtime() instead of figuring out whether this system
163 // supports tzset(), _tzset() or something else
167 s_timezoneSet
= TRUE
;
170 return (int)WX_TIMEZONE
;
173 // return the integral part of the JDN for the midnight of the given date (to
174 // get the real JDN you need to add 0.5, this is, in fact, JDN of the
175 // noon of the previous day)
176 static long GetTruncatedJDN(wxDateTime::wxDateTime_t day
,
177 wxDateTime::Month mon
,
180 // CREDIT: code below is by Scott E. Lee (but bugs are mine)
182 // check the date validity
184 (year
> JDN_0_YEAR
) ||
185 ((year
== JDN_0_YEAR
) && (mon
> JDN_0_MONTH
)) ||
186 ((year
== JDN_0_YEAR
) && (mon
== JDN_0_MONTH
) && (day
>= JDN_0_DAY
)),
187 _T("date out of range - can't convert to JDN")
190 // make the year positive to avoid problems with negative numbers division
193 // months are counted from March here
195 if ( mon
>= wxDateTime::Mar
)
205 // now we can simply add all the contributions together
206 return ((year
/ 100) * DAYS_PER_400_YEARS
) / 4
207 + ((year
% 100) * DAYS_PER_4_YEARS
) / 4
208 + (month
* DAYS_PER_5_MONTHS
+ 2) / 5
213 // this function is a wrapper around strftime(3)
214 static wxString
CallStrftime(const wxChar
*format
, const tm
* tm
)
217 if ( !wxStrftime(buf
, WXSIZEOF(buf
), format
, tm
) )
219 // buffer is too small?
220 wxFAIL_MSG(_T("strftime() failed"));
223 return wxString(buf
);
226 // if year and/or month have invalid values, replace them with the current ones
227 static void ReplaceDefaultYearMonthWithCurrent(int *year
,
228 wxDateTime::Month
*month
)
230 struct tm
*tmNow
= NULL
;
232 if ( *year
== wxDateTime::Inv_Year
)
234 tmNow
= wxDateTime::GetTmNow();
236 *year
= 1900 + tmNow
->tm_year
;
239 if ( *month
== wxDateTime::Inv_Month
)
242 tmNow
= wxDateTime::GetTmNow();
244 *month
= (wxDateTime::Month
)tmNow
->tm_mon
;
251 // return the month if the string is a month name or Inv_Month otherwise
252 static wxDateTime::Month
GetMonthFromName(const wxString
& name
)
254 wxDateTime::Month mon
;
255 for ( mon
= wxDateTime::Jan
; mon
< wxDateTime::Inv_Month
; wxNextMonth(mon
) )
257 // case-insensitive comparison with both abbreviated and not versions
258 if ( name
.CmpNoCase(wxDateTime::GetMonthName(mon
, TRUE
)) ||
259 name
.CmpNoCase(wxDateTime::GetMonthName(mon
, FALSE
)) )
268 // return the weekday if the string is a weekday name or Inv_WeekDay otherwise
269 static wxDateTime::WeekDay
GetWeekDayFromName(const wxString
& name
)
271 wxDateTime::WeekDay wd
;
272 for ( wd
= wxDateTime::Sun
; wd
< wxDateTime::Inv_WeekDay
; wxNextWDay(wd
) )
274 // case-insensitive comparison with both abbreviated and not versions
275 if ( name
.IsSameAs(wxDateTime::GetWeekDayName(wd
, TRUE
), FALSE
) ||
276 name
.IsSameAs(wxDateTime::GetWeekDayName(wd
, FALSE
), FALSE
) )
285 // ============================================================================
286 // implementation of wxDateTime
287 // ============================================================================
289 // ----------------------------------------------------------------------------
291 // ----------------------------------------------------------------------------
293 wxDateTime::Country
wxDateTime::ms_country
= wxDateTime::Country_Unknown
;
294 wxDateTime
wxDateTime::ms_InvDateTime
;
296 // ----------------------------------------------------------------------------
298 // ----------------------------------------------------------------------------
302 year
= (wxDateTime_t
)wxDateTime::Inv_Year
;
303 mon
= wxDateTime::Inv_Month
;
305 hour
= min
= sec
= msec
= 0;
306 wday
= wxDateTime::Inv_WeekDay
;
309 wxDateTime::Tm::Tm(const struct tm
& tm
, const TimeZone
& tz
)
317 mon
= (wxDateTime::Month
)tm
.tm_mon
;
318 year
= 1900 + tm
.tm_year
;
323 bool wxDateTime::Tm::IsValid() const
325 // we allow for the leap seconds, although we don't use them (yet)
326 return (year
!= wxDateTime::Inv_Year
) && (mon
!= wxDateTime::Inv_Month
) &&
327 (mday
<= GetNumOfDaysInMonth(year
, mon
)) &&
328 (hour
< 24) && (min
< 60) && (sec
< 62) && (msec
< 1000);
331 void wxDateTime::Tm::ComputeWeekDay()
333 // compute the week day from day/month/year: we use the dumbest algorithm
334 // possible: just compute our JDN and then use the (simple to derive)
335 // formula: weekday = (JDN + 1.5) % 7
336 wday
= (wxDateTime::WeekDay
)(GetTruncatedJDN(mday
, mon
, year
) + 2) % 7;
339 void wxDateTime::Tm::AddMonths(int monDiff
)
341 // normalize the months field
342 while ( monDiff
< -mon
)
346 monDiff
+= MONTHS_IN_YEAR
;
349 while ( monDiff
+ mon
> MONTHS_IN_YEAR
)
353 monDiff
-= MONTHS_IN_YEAR
;
356 mon
= (wxDateTime::Month
)(mon
+ monDiff
);
358 wxASSERT_MSG( mon
>= 0 && mon
< MONTHS_IN_YEAR
, _T("logic error") );
361 void wxDateTime::Tm::AddDays(int dayDiff
)
363 // normalize the days field
369 mday
+= GetNumOfDaysInMonth(year
, mon
);
372 while ( mday
> GetNumOfDaysInMonth(year
, mon
) )
374 mday
-= GetNumOfDaysInMonth(year
, mon
);
379 wxASSERT_MSG( mday
> 0 && mday
<= GetNumOfDaysInMonth(year
, mon
),
383 // ----------------------------------------------------------------------------
385 // ----------------------------------------------------------------------------
387 wxDateTime::TimeZone::TimeZone(wxDateTime::TZ tz
)
391 case wxDateTime::Local
:
392 // get the offset from C RTL: it returns the difference GMT-local
393 // while we want to have the offset _from_ GMT, hence the '-'
394 m_offset
= -GetTimeZone();
397 case wxDateTime::GMT_12
:
398 case wxDateTime::GMT_11
:
399 case wxDateTime::GMT_10
:
400 case wxDateTime::GMT_9
:
401 case wxDateTime::GMT_8
:
402 case wxDateTime::GMT_7
:
403 case wxDateTime::GMT_6
:
404 case wxDateTime::GMT_5
:
405 case wxDateTime::GMT_4
:
406 case wxDateTime::GMT_3
:
407 case wxDateTime::GMT_2
:
408 case wxDateTime::GMT_1
:
409 m_offset
= -3600*(wxDateTime::GMT0
- tz
);
412 case wxDateTime::GMT0
:
413 case wxDateTime::GMT1
:
414 case wxDateTime::GMT2
:
415 case wxDateTime::GMT3
:
416 case wxDateTime::GMT4
:
417 case wxDateTime::GMT5
:
418 case wxDateTime::GMT6
:
419 case wxDateTime::GMT7
:
420 case wxDateTime::GMT8
:
421 case wxDateTime::GMT9
:
422 case wxDateTime::GMT10
:
423 case wxDateTime::GMT11
:
424 case wxDateTime::GMT12
:
425 m_offset
= 3600*(tz
- wxDateTime::GMT0
);
428 case wxDateTime::A_CST
:
429 // Central Standard Time in use in Australia = UTC + 9.5
430 m_offset
= 60l*(9*60 + 30);
434 wxFAIL_MSG( _T("unknown time zone") );
438 // ----------------------------------------------------------------------------
440 // ----------------------------------------------------------------------------
443 bool wxDateTime::IsLeapYear(int year
, wxDateTime::Calendar cal
)
445 if ( year
== Inv_Year
)
446 year
= GetCurrentYear();
448 if ( cal
== Gregorian
)
450 // in Gregorian calendar leap years are those divisible by 4 except
451 // those divisible by 100 unless they're also divisible by 400
452 // (in some countries, like Russia and Greece, additional corrections
453 // exist, but they won't manifest themselves until 2700)
454 return (year
% 4 == 0) && ((year
% 100 != 0) || (year
% 400 == 0));
456 else if ( cal
== Julian
)
458 // in Julian calendar the rule is simpler
459 return year
% 4 == 0;
463 wxFAIL_MSG(_T("unknown calendar"));
470 int wxDateTime::GetCentury(int year
)
472 return year
> 0 ? year
/ 100 : year
/ 100 - 1;
476 int wxDateTime::ConvertYearToBC(int year
)
479 return year
> 0 ? year
: year
- 1;
483 int wxDateTime::GetCurrentYear(wxDateTime::Calendar cal
)
488 return Now().GetYear();
491 wxFAIL_MSG(_T("TODO"));
495 wxFAIL_MSG(_T("unsupported calendar"));
503 wxDateTime::Month
wxDateTime::GetCurrentMonth(wxDateTime::Calendar cal
)
508 return Now().GetMonth();
512 wxFAIL_MSG(_T("TODO"));
516 wxFAIL_MSG(_T("unsupported calendar"));
524 wxDateTime::wxDateTime_t
wxDateTime::GetNumberOfDays(int year
, Calendar cal
)
526 if ( year
== Inv_Year
)
528 // take the current year if none given
529 year
= GetCurrentYear();
536 return IsLeapYear(year
) ? 366 : 365;
540 wxFAIL_MSG(_T("unsupported calendar"));
548 wxDateTime::wxDateTime_t
wxDateTime::GetNumberOfDays(wxDateTime::Month month
,
550 wxDateTime::Calendar cal
)
552 wxCHECK_MSG( month
< MONTHS_IN_YEAR
, 0, _T("invalid month") );
554 if ( cal
== Gregorian
|| cal
== Julian
)
556 if ( year
== Inv_Year
)
558 // take the current year if none given
559 year
= GetCurrentYear();
562 return GetNumOfDaysInMonth(year
, month
);
566 wxFAIL_MSG(_T("unsupported calendar"));
573 wxString
wxDateTime::GetMonthName(wxDateTime::Month month
, bool abbr
)
575 wxCHECK_MSG( month
!= Inv_Month
, _T(""), _T("invalid month") );
577 // notice that we must set all the fields to avoid confusing libc (GNU one
578 // gets confused to a crash if we don't do this)
587 tm
.tm_year
= 76; // any year will do
590 return CallStrftime(abbr
? _T("%b") : _T("%B"), &tm
);
594 wxString
wxDateTime::GetWeekDayName(wxDateTime::WeekDay wday
, bool abbr
)
596 wxCHECK_MSG( wday
!= Inv_WeekDay
, _T(""), _T("invalid weekday") );
598 // take some arbitrary Sunday
599 tm tm
= { 0, 0, 0, 28, Nov
, 99 };
601 // and offset it by the number of days needed to get the correct wday
604 // call mktime() to normalize it...
607 // ... and call strftime()
608 return CallStrftime(abbr
? _T("%a") : _T("%A"), &tm
);
611 // ----------------------------------------------------------------------------
612 // Country stuff: date calculations depend on the country (DST, work days,
613 // ...), so we need to know which rules to follow.
614 // ----------------------------------------------------------------------------
617 wxDateTime::Country
wxDateTime::GetCountry()
619 if ( ms_country
== Country_Unknown
)
621 // try to guess from the time zone name
622 time_t t
= time(NULL
);
623 struct tm
*tm
= localtime(&t
);
625 wxString tz
= CallStrftime(_T("%Z"), tm
);
626 if ( tz
== _T("WET") || tz
== _T("WEST") )
630 else if ( tz
== _T("CET") || tz
== _T("CEST") )
632 ms_country
= Country_EEC
;
634 else if ( tz
== _T("MSK") || tz
== _T("MSD") )
638 else if ( tz
== _T("AST") || tz
== _T("ADT") ||
639 tz
== _T("EST") || tz
== _T("EDT") ||
640 tz
== _T("CST") || tz
== _T("CDT") ||
641 tz
== _T("MST") || tz
== _T("MDT") ||
642 tz
== _T("PST") || tz
== _T("PDT") )
648 // well, choose a default one
657 void wxDateTime::SetCountry(wxDateTime::Country country
)
659 ms_country
= country
;
663 bool wxDateTime::IsWestEuropeanCountry(Country country
)
665 if ( country
== Country_Default
)
667 country
= GetCountry();
670 return (Country_WesternEurope_Start
<= country
) &&
671 (country
<= Country_WesternEurope_End
);
674 // ----------------------------------------------------------------------------
675 // DST calculations: we use 3 different rules for the West European countries,
676 // USA and for the rest of the world. This is undoubtedly false for many
677 // countries, but I lack the necessary info (and the time to gather it),
678 // please add the other rules here!
679 // ----------------------------------------------------------------------------
682 bool wxDateTime::IsDSTApplicable(int year
, Country country
)
684 if ( year
== Inv_Year
)
686 // take the current year if none given
687 year
= GetCurrentYear();
690 if ( country
== Country_Default
)
692 country
= GetCountry();
699 // DST was first observed in the US and UK during WWI, reused
700 // during WWII and used again since 1966
701 return year
>= 1966 ||
702 (year
>= 1942 && year
<= 1945) ||
703 (year
== 1918 || year
== 1919);
706 // assume that it started after WWII
712 wxDateTime
wxDateTime::GetBeginDST(int year
, Country country
)
714 if ( year
== Inv_Year
)
716 // take the current year if none given
717 year
= GetCurrentYear();
720 if ( country
== Country_Default
)
722 country
= GetCountry();
725 if ( !IsDSTApplicable(year
, country
) )
727 return ms_InvDateTime
;
732 if ( IsWestEuropeanCountry(country
) || (country
== Russia
) )
734 // DST begins at 1 a.m. GMT on the last Sunday of March
735 if ( !dt
.SetToLastWeekDay(Sun
, Mar
, year
) )
738 wxFAIL_MSG( _T("no last Sunday in March?") );
741 dt
+= wxTimeSpan::Hours(1);
745 else switch ( country
)
752 // don't know for sure - assume it was in effect all year
757 dt
.Set(1, Jan
, year
);
761 // DST was installed Feb 2, 1942 by the Congress
762 dt
.Set(2, Feb
, year
);
765 // Oil embargo changed the DST period in the US
767 dt
.Set(6, Jan
, 1974);
771 dt
.Set(23, Feb
, 1975);
775 // before 1986, DST begun on the last Sunday of April, but
776 // in 1986 Reagan changed it to begin at 2 a.m. of the
777 // first Sunday in April
780 if ( !dt
.SetToLastWeekDay(Sun
, Apr
, year
) )
783 wxFAIL_MSG( _T("no first Sunday in April?") );
788 if ( !dt
.SetToWeekDay(Sun
, 1, Apr
, year
) )
791 wxFAIL_MSG( _T("no first Sunday in April?") );
795 dt
+= wxTimeSpan::Hours(2);
797 // TODO what about timezone??
803 // assume Mar 30 as the start of the DST for the rest of the world
804 // - totally bogus, of course
805 dt
.Set(30, Mar
, year
);
812 wxDateTime
wxDateTime::GetEndDST(int year
, Country country
)
814 if ( year
== Inv_Year
)
816 // take the current year if none given
817 year
= GetCurrentYear();
820 if ( country
== Country_Default
)
822 country
= GetCountry();
825 if ( !IsDSTApplicable(year
, country
) )
827 return ms_InvDateTime
;
832 if ( IsWestEuropeanCountry(country
) || (country
== Russia
) )
834 // DST ends at 1 a.m. GMT on the last Sunday of October
835 if ( !dt
.SetToLastWeekDay(Sun
, Oct
, year
) )
837 // weirder and weirder...
838 wxFAIL_MSG( _T("no last Sunday in October?") );
841 dt
+= wxTimeSpan::Hours(1);
845 else switch ( country
)
852 // don't know for sure - assume it was in effect all year
856 dt
.Set(31, Dec
, year
);
860 // the time was reset after the end of the WWII
861 dt
.Set(30, Sep
, year
);
865 // DST ends at 2 a.m. on the last Sunday of October
866 if ( !dt
.SetToLastWeekDay(Sun
, Oct
, year
) )
868 // weirder and weirder...
869 wxFAIL_MSG( _T("no last Sunday in October?") );
872 dt
+= wxTimeSpan::Hours(2);
874 // TODO what about timezone??
879 // assume October 26th as the end of the DST - totally bogus too
880 dt
.Set(26, Oct
, year
);
886 // ----------------------------------------------------------------------------
887 // constructors and assignment operators
888 // ----------------------------------------------------------------------------
890 // the values in the tm structure contain the local time
891 wxDateTime
& wxDateTime::Set(const struct tm
& tm
)
893 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
896 time_t timet
= mktime(&tm2
);
898 if ( timet
== (time_t)-1 )
900 // mktime() rather unintuitively fails for Jan 1, 1970 if the hour is
901 // less than timezone - try to make it work for this case
902 if ( tm2
.tm_year
== 70 && tm2
.tm_mon
== 0 && tm2
.tm_mday
== 1 )
904 // add timezone to make sure that date is in range
905 tm2
.tm_sec
-= GetTimeZone();
907 timet
= mktime(&tm2
);
908 if ( timet
!= (time_t)-1 )
910 timet
+= GetTimeZone();
916 wxFAIL_MSG( _T("mktime() failed") );
918 return ms_InvDateTime
;
926 wxDateTime
& wxDateTime::Set(wxDateTime_t hour
,
929 wxDateTime_t millisec
)
931 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
933 // we allow seconds to be 61 to account for the leap seconds, even if we
934 // don't use them really
935 wxCHECK_MSG( hour
< 24 && second
< 62 && minute
< 60 && millisec
< 1000,
937 _T("Invalid time in wxDateTime::Set()") );
939 // get the current date from system
940 time_t timet
= GetTimeNow();
941 struct tm
*tm
= localtime(&timet
);
943 wxCHECK_MSG( tm
, ms_InvDateTime
, _T("localtime() failed") );
952 // and finally adjust milliseconds
953 return SetMillisecond(millisec
);
956 wxDateTime
& wxDateTime::Set(wxDateTime_t day
,
962 wxDateTime_t millisec
)
964 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
966 wxCHECK_MSG( hour
< 24 && second
< 62 && minute
< 60 && millisec
< 1000,
968 _T("Invalid time in wxDateTime::Set()") );
970 ReplaceDefaultYearMonthWithCurrent(&year
, &month
);
972 wxCHECK_MSG( (0 < day
) && (day
<= GetNumberOfDays(month
, year
)),
974 _T("Invalid date in wxDateTime::Set()") );
976 // the range of time_t type (inclusive)
977 static const int yearMinInRange
= 1970;
978 static const int yearMaxInRange
= 2037;
980 // test only the year instead of testing for the exact end of the Unix
981 // time_t range - it doesn't bring anything to do more precise checks
982 if ( year
>= yearMinInRange
&& year
<= yearMaxInRange
)
984 // use the standard library version if the date is in range - this is
985 // probably more efficient than our code
987 tm
.tm_year
= year
- 1900;
993 tm
.tm_isdst
= -1; // mktime() will guess it
997 // and finally adjust milliseconds
998 return SetMillisecond(millisec
);
1002 // do time calculations ourselves: we want to calculate the number of
1003 // milliseconds between the given date and the epoch
1005 // get the JDN for the midnight of this day
1006 m_time
= GetTruncatedJDN(day
, month
, year
);
1007 m_time
-= EPOCH_JDN
;
1008 m_time
*= SECONDS_PER_DAY
* TIME_T_FACTOR
;
1010 // JDN corresponds to GMT, we take localtime
1011 Add(wxTimeSpan(hour
, minute
, second
+ GetTimeZone(), millisec
));
1017 wxDateTime
& wxDateTime::Set(double jdn
)
1019 // so that m_time will be 0 for the midnight of Jan 1, 1970 which is jdn
1021 jdn
-= EPOCH_JDN
+ 0.5;
1023 jdn
*= MILLISECONDS_PER_DAY
;
1030 // ----------------------------------------------------------------------------
1031 // time_t <-> broken down time conversions
1032 // ----------------------------------------------------------------------------
1034 wxDateTime::Tm
wxDateTime::GetTm(const TimeZone
& tz
) const
1036 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1038 time_t time
= GetTicks();
1039 if ( time
!= (time_t)-1 )
1041 // use C RTL functions
1043 if ( tz
.GetOffset() == -GetTimeZone() )
1045 // we are working with local time
1046 tm
= localtime(&time
);
1048 // should never happen
1049 wxCHECK_MSG( tm
, Tm(), _T("gmtime() failed") );
1053 time
+= tz
.GetOffset();
1058 // should never happen
1059 wxCHECK_MSG( tm
, Tm(), _T("gmtime() failed") );
1063 tm
= (struct tm
*)NULL
;
1071 //else: use generic code below
1074 // remember the time and do the calculations with the date only - this
1075 // eliminates rounding errors of the floating point arithmetics
1077 wxLongLong timeMidnight
= m_time
+ tz
.GetOffset() * 1000;
1079 long timeOnly
= (timeMidnight
% MILLISECONDS_PER_DAY
).ToLong();
1081 // we want to always have positive time and timeMidnight to be really
1082 // the midnight before it
1085 timeOnly
= MILLISECONDS_PER_DAY
+ timeOnly
;
1088 timeMidnight
-= timeOnly
;
1090 // calculate the Gregorian date from JDN for the midnight of our date:
1091 // this will yield day, month (in 1..12 range) and year
1093 // actually, this is the JDN for the noon of the previous day
1094 long jdn
= (timeMidnight
/ MILLISECONDS_PER_DAY
).ToLong() + EPOCH_JDN
;
1096 // CREDIT: code below is by Scott E. Lee (but bugs are mine)
1098 wxASSERT_MSG( jdn
> -2, _T("JDN out of range") );
1100 // calculate the century
1101 int temp
= (jdn
+ JDN_OFFSET
) * 4 - 1;
1102 int century
= temp
/ DAYS_PER_400_YEARS
;
1104 // then the year and day of year (1 <= dayOfYear <= 366)
1105 temp
= ((temp
% DAYS_PER_400_YEARS
) / 4) * 4 + 3;
1106 int year
= (century
* 100) + (temp
/ DAYS_PER_4_YEARS
);
1107 int dayOfYear
= (temp
% DAYS_PER_4_YEARS
) / 4 + 1;
1109 // and finally the month and day of the month
1110 temp
= dayOfYear
* 5 - 3;
1111 int month
= temp
/ DAYS_PER_5_MONTHS
;
1112 int day
= (temp
% DAYS_PER_5_MONTHS
) / 5 + 1;
1114 // month is counted from March - convert to normal
1125 // year is offset by 4800
1128 // check that the algorithm gave us something reasonable
1129 wxASSERT_MSG( (0 < month
) && (month
<= 12), _T("invalid month") );
1130 wxASSERT_MSG( (1 <= day
) && (day
< 32), _T("invalid day") );
1131 wxASSERT_MSG( (INT_MIN
<= year
) && (year
<= INT_MAX
),
1132 _T("year range overflow") );
1134 // construct Tm from these values
1136 tm
.year
= (int)year
;
1137 tm
.mon
= (Month
)(month
- 1); // algorithm yields 1 for January, not 0
1138 tm
.mday
= (wxDateTime_t
)day
;
1139 tm
.msec
= timeOnly
% 1000;
1140 timeOnly
-= tm
.msec
;
1141 timeOnly
/= 1000; // now we have time in seconds
1143 tm
.sec
= timeOnly
% 60;
1145 timeOnly
/= 60; // now we have time in minutes
1147 tm
.min
= timeOnly
% 60;
1150 tm
.hour
= timeOnly
/ 60;
1155 wxDateTime
& wxDateTime::SetYear(int year
)
1157 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1166 wxDateTime
& wxDateTime::SetMonth(Month month
)
1168 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1177 wxDateTime
& wxDateTime::SetDay(wxDateTime_t mday
)
1179 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1188 wxDateTime
& wxDateTime::SetHour(wxDateTime_t hour
)
1190 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1199 wxDateTime
& wxDateTime::SetMinute(wxDateTime_t min
)
1201 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1210 wxDateTime
& wxDateTime::SetSecond(wxDateTime_t sec
)
1212 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1221 wxDateTime
& wxDateTime::SetMillisecond(wxDateTime_t millisecond
)
1223 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1225 // we don't need to use GetTm() for this one
1226 m_time
-= m_time
% 1000l;
1227 m_time
+= millisecond
;
1232 // ----------------------------------------------------------------------------
1233 // wxDateTime arithmetics
1234 // ----------------------------------------------------------------------------
1236 wxDateTime
& wxDateTime::Add(const wxDateSpan
& diff
)
1240 tm
.year
+= diff
.GetYears();
1241 tm
.AddMonths(diff
.GetMonths());
1242 tm
.AddDays(diff
.GetTotalDays());
1249 // ----------------------------------------------------------------------------
1250 // Weekday and monthday stuff
1251 // ----------------------------------------------------------------------------
1253 wxDateTime
& wxDateTime::SetToLastMonthDay(Month month
,
1256 // take the current month/year if none specified
1257 ReplaceDefaultYearMonthWithCurrent(&year
, &month
);
1259 return Set(GetNumOfDaysInMonth(year
, month
), month
, year
);
1262 wxDateTime
& wxDateTime::SetToWeekDayInSameWeek(WeekDay weekday
)
1264 wxCHECK_MSG( weekday
!= Inv_WeekDay
, ms_InvDateTime
, _T("invalid weekday") );
1266 WeekDay wdayThis
= GetWeekDay();
1267 if ( weekday
== wdayThis
)
1272 else if ( weekday
< wdayThis
)
1274 return Substract(wxTimeSpan::Days(wdayThis
- weekday
));
1276 else // weekday > wdayThis
1278 return Add(wxTimeSpan::Days(weekday
- wdayThis
));
1282 wxDateTime
& wxDateTime::SetToNextWeekDay(WeekDay weekday
)
1284 wxCHECK_MSG( weekday
!= Inv_WeekDay
, ms_InvDateTime
, _T("invalid weekday") );
1287 WeekDay wdayThis
= GetWeekDay();
1288 if ( weekday
== wdayThis
)
1293 else if ( weekday
< wdayThis
)
1295 // need to advance a week
1296 diff
= 7 - (wdayThis
- weekday
);
1298 else // weekday > wdayThis
1300 diff
= weekday
- wdayThis
;
1303 return Add(wxTimeSpan::Days(diff
));
1306 wxDateTime
& wxDateTime::SetToPrevWeekDay(WeekDay weekday
)
1308 wxCHECK_MSG( weekday
!= Inv_WeekDay
, ms_InvDateTime
, _T("invalid weekday") );
1311 WeekDay wdayThis
= GetWeekDay();
1312 if ( weekday
== wdayThis
)
1317 else if ( weekday
> wdayThis
)
1319 // need to go to previous week
1320 diff
= 7 - (weekday
- wdayThis
);
1322 else // weekday < wdayThis
1324 diff
= wdayThis
- weekday
;
1327 return Substract(wxTimeSpan::Days(diff
));
1330 bool wxDateTime::SetToWeekDay(WeekDay weekday
,
1335 wxCHECK_MSG( weekday
!= Inv_WeekDay
, FALSE
, _T("invalid weekday") );
1337 // we don't check explicitly that -5 <= n <= 5 because we will return FALSE
1338 // anyhow in such case - but may be should still give an assert for it?
1340 // take the current month/year if none specified
1341 ReplaceDefaultYearMonthWithCurrent(&year
, &month
);
1345 // TODO this probably could be optimised somehow...
1349 // get the first day of the month
1350 dt
.Set(1, month
, year
);
1353 WeekDay wdayFirst
= dt
.GetWeekDay();
1355 // go to the first weekday of the month
1356 int diff
= weekday
- wdayFirst
;
1360 // add advance n-1 weeks more
1363 dt
+= wxDateSpan::Days(diff
);
1365 else // count from the end of the month
1367 // get the last day of the month
1368 dt
.SetToLastMonthDay(month
, year
);
1371 WeekDay wdayLast
= dt
.GetWeekDay();
1373 // go to the last weekday of the month
1374 int diff
= wdayLast
- weekday
;
1378 // and rewind n-1 weeks from there
1381 dt
-= wxDateSpan::Days(diff
);
1384 // check that it is still in the same month
1385 if ( dt
.GetMonth() == month
)
1393 // no such day in this month
1398 wxDateTime::wxDateTime_t
wxDateTime::GetDayOfYear(const TimeZone
& tz
) const
1400 // this array contains the cumulated number of days in all previous months
1401 // for normal and leap years
1402 static const wxDateTime_t cumulatedDays
[2][MONTHS_IN_YEAR
] =
1404 { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 },
1405 { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 }
1410 return cumulatedDays
[IsLeapYear(tm
.year
)][tm
.mon
] + tm
.mday
;
1413 wxDateTime::wxDateTime_t
wxDateTime::GetWeekOfYear(const TimeZone
& tz
) const
1416 // the first week of the year is the one which contains Jan, 4 (according
1417 // to ISO standard rule), so the year day N0 = 4 + 7*W always lies in the
1418 // week W+1. As any day N = 7*W + 4 + (N - 4)%7, it lies in the same week
1419 // as N0 or in the next one.
1421 // TODO this surely may be optimized - I got confused while writing it
1423 wxDateTime_t nDayInYear
= GetDayOfYear(tz
);
1425 // the week day of the day lying in the first week
1426 WeekDay wdayStart
= wxDateTime(4, Jan
, GetYear()).GetWeekDay();
1428 wxDateTime_t week
= (nDayInYear
- 4) / 7 + 1;
1430 // notice that Sunday shoould be counted as 7, not 0 here!
1431 if ( ((nDayInYear
- 4) % 7) + (!wdayStart
? 7 : wdayStart
) > 7 )
1437 #else // this seems to be a bit simpler and I believe is also correct
1438 return (WeekDay
)((GetDayOfYear() - (GetWeekDay() - 1 + 7) % 7 + 7) / 7);
1442 wxDateTime::wxDateTime_t
wxDateTime::GetWeekOfMonth(const TimeZone
& tz
) const
1446 wxDateTime
dt(*this);
1451 dt
-= wxTimeSpan::Week();
1453 while ( dt
.GetMonth(tz
) == GetMonth(tz
) );
1458 // ----------------------------------------------------------------------------
1459 // Julian day number conversion and related stuff
1460 // ----------------------------------------------------------------------------
1462 double wxDateTime::GetJulianDayNumber() const
1464 // JDN are always expressed for the GMT dates
1465 Tm
tm(ToTimezone(GMT0
).GetTm(GMT0
));
1467 double result
= GetTruncatedJDN(tm
.mday
, tm
.mon
, tm
.year
);
1469 // add the part GetTruncatedJDN() neglected
1472 // and now add the time: 86400 sec = 1 JDN
1473 return result
+ ((double)(60*(60*tm
.hour
+ tm
.min
) + tm
.sec
)) / 86400;
1476 double wxDateTime::GetRataDie() const
1478 // March 1 of the year 0 is Rata Die day -306 and JDN 1721119.5
1479 return GetJulianDayNumber() - 1721119.5 - 306;
1482 // ----------------------------------------------------------------------------
1483 // timezone and DST stuff
1484 // ----------------------------------------------------------------------------
1486 int wxDateTime::IsDST(wxDateTime::Country country
) const
1488 wxCHECK_MSG( country
== Country_Default
, -1,
1489 _T("country support not implemented") );
1491 // use the C RTL for the dates in the standard range
1492 time_t timet
= GetTicks();
1493 if ( timet
!= (time_t)-1 )
1495 tm
*tm
= localtime(&timet
);
1497 wxCHECK_MSG( tm
, -1, _T("localtime() failed") );
1499 return tm
->tm_isdst
;
1503 int year
= GetYear();
1505 if ( !IsDSTApplicable(year
, country
) )
1507 // no DST time in this year in this country
1511 return IsBetween(GetBeginDST(year
, country
), GetEndDST(year
, country
));
1515 wxDateTime
& wxDateTime::MakeTimezone(const TimeZone
& tz
)
1517 int secDiff
= GetTimeZone() + tz
.GetOffset();
1519 // we need to know whether DST is or not in effect for this date
1522 // FIXME we assume that the DST is always shifted by 1 hour
1526 return Substract(wxTimeSpan::Seconds(secDiff
));
1529 // ----------------------------------------------------------------------------
1530 // wxDateTime to/from text representations
1531 // ----------------------------------------------------------------------------
1533 wxString
wxDateTime::Format(const wxChar
*format
, const TimeZone
& tz
) const
1535 wxCHECK_MSG( format
, _T(""), _T("NULL format in wxDateTime::Format") );
1537 time_t time
= GetTicks();
1538 if ( time
!= (time_t)-1 )
1542 if ( tz
.GetOffset() == -GetTimeZone() )
1544 // we are working with local time
1545 tm
= localtime(&time
);
1547 // should never happen
1548 wxCHECK_MSG( tm
, wxEmptyString
, _T("localtime() failed") );
1552 time
+= tz
.GetOffset();
1558 // should never happen
1559 wxCHECK_MSG( tm
, wxEmptyString
, _T("gmtime() failed") );
1563 tm
= (struct tm
*)NULL
;
1569 return CallStrftime(format
, tm
);
1571 //else: use generic code below
1574 // we only parse ANSI C format specifications here, no POSIX 2
1575 // complications, no GNU extensions
1578 // used for calls to strftime() when we only deal with time
1579 struct tm tmTimeOnly
;
1580 tmTimeOnly
.tm_hour
= tm
.hour
;
1581 tmTimeOnly
.tm_min
= tm
.min
;
1582 tmTimeOnly
.tm_sec
= tm
.sec
;
1583 tmTimeOnly
.tm_wday
= 0;
1584 tmTimeOnly
.tm_yday
= 0;
1585 tmTimeOnly
.tm_mday
= 1; // any date will do
1586 tmTimeOnly
.tm_mon
= 0;
1587 tmTimeOnly
.tm_year
= 76;
1588 tmTimeOnly
.tm_isdst
= 0; // no DST, we adjust for tz ourselves
1591 for ( const wxChar
*p
= format
; *p
; p
++ )
1593 if ( *p
!= _T('%') )
1601 // start of the format specification
1604 case _T('a'): // a weekday name
1606 // second parameter should be TRUE for abbreviated names
1607 res
+= GetWeekDayName(tm
.GetWeekDay(), *p
== _T('a'));
1610 case _T('b'): // a month name
1612 // second parameter should be TRUE for abbreviated names
1613 res
+= GetMonthName(tm
.mon
, *p
== _T('b'));
1616 case _T('c'): // locale default date and time representation
1617 case _T('x'): // locale default date representation
1619 // the problem: there is no way to know what do these format
1620 // specifications correspond to for the current locale.
1622 // the solution: use a hack and still use strftime(): first
1623 // find the YEAR which is a year in the strftime() range (1970
1624 // - 2038) whose Jan 1 falls on the same week day as the Jan 1
1625 // of the real year. Then make a copy of the format and
1626 // replace all occurences of YEAR in it with some unique
1627 // string not appearing anywhere else in it, then use
1628 // strftime() to format the date in year YEAR and then replace
1629 // YEAR back by the real year and the unique replacement
1630 // string back with YEAR. Notice that "all occurences of YEAR"
1631 // means all occurences of 4 digit as well as 2 digit form!
1633 // the bugs: we assume that neither of %c nor %x contains any
1634 // fields which may change between the YEAR and real year. For
1635 // example, the week number (%U, %W) and the day number (%j)
1636 // will change if one of these years is leap and the other one
1639 // find the YEAR: normally, for any year X, Jan 1 or the
1640 // year X + 28 is the same weekday as Jan 1 of X (because
1641 // the weekday advances by 1 for each normal X and by 2
1642 // for each leap X, hence by 5 every 4 years or by 35
1643 // which is 0 mod 7 every 28 years) but this rule breaks
1644 // down if there are years between X and Y which are
1645 // divisible by 4 but not leap (i.e. divisible by 100 but
1646 // not 400), hence the correction.
1648 int yearReal
= GetYear(tz
);
1649 int mod28
= yearReal
% 28;
1651 // be careful to not go too far - we risk to leave the
1656 year
= 1988 + mod28
; // 1988 == 0 (mod 28)
1660 year
= 1970 + mod28
- 10; // 1970 == 10 (mod 28)
1663 int nCentury
= year
/ 100,
1664 nCenturyReal
= yearReal
/ 100;
1666 // need to adjust for the years divisble by 400 which are
1667 // not leap but are counted like leap ones if we just take
1668 // the number of centuries in between for nLostWeekDays
1669 int nLostWeekDays
= (nCentury
- nCenturyReal
) -
1670 (nCentury
/ 4 - nCenturyReal
/ 4);
1672 // we have to gain back the "lost" weekdays: note that the
1673 // effect of this loop is to not do anything to
1674 // nLostWeekDays (which we won't use any more), but to
1675 // (indirectly) set the year correctly
1676 while ( (nLostWeekDays
% 7) != 0 )
1678 nLostWeekDays
+= year
++ % 4 ? 1 : 2;
1681 // at any rate, we couldn't go further than 1988 + 9 + 28!
1682 wxASSERT_MSG( year
< 2030,
1683 _T("logic error in wxDateTime::Format") );
1685 wxString strYear
, strYear2
;
1686 strYear
.Printf(_T("%d"), year
);
1687 strYear2
.Printf(_T("%d"), year
% 100);
1689 // find two strings not occuring in format (this is surely
1690 // not optimal way of doing it... improvements welcome!)
1691 wxString fmt
= format
;
1692 wxString replacement
= (wxChar
)-1;
1693 while ( fmt
.Find(replacement
) != wxNOT_FOUND
)
1695 replacement
<< (wxChar
)-1;
1698 wxString replacement2
= (wxChar
)-2;
1699 while ( fmt
.Find(replacement
) != wxNOT_FOUND
)
1701 replacement
<< (wxChar
)-2;
1704 // replace all occurences of year with it
1705 bool wasReplaced
= fmt
.Replace(strYear
, replacement
) > 0;
1707 wasReplaced
= fmt
.Replace(strYear2
, replacement2
) > 0;
1709 // use strftime() to format the same date but in supported
1712 // NB: we assume that strftime() doesn't check for the
1713 // date validity and will happily format the date
1714 // corresponding to Feb 29 of a non leap year (which
1715 // may happen if yearReal was leap and year is not)
1716 struct tm tmAdjusted
;
1717 tmAdjusted
.tm_hour
= tm
.hour
;
1718 tmAdjusted
.tm_min
= tm
.min
;
1719 tmAdjusted
.tm_sec
= tm
.sec
;
1720 tmAdjusted
.tm_wday
= tm
.GetWeekDay();
1721 tmAdjusted
.tm_yday
= GetDayOfYear();
1722 tmAdjusted
.tm_mday
= tm
.mday
;
1723 tmAdjusted
.tm_mon
= tm
.mon
;
1724 tmAdjusted
.tm_year
= year
- 1900;
1725 tmAdjusted
.tm_isdst
= 0; // no DST, already adjusted
1726 wxString str
= CallStrftime(*p
== _T('c') ? _T("%c")
1730 // now replace the occurence of 1999 with the real year
1731 wxString strYearReal
, strYearReal2
;
1732 strYearReal
.Printf(_T("%04d"), yearReal
);
1733 strYearReal2
.Printf(_T("%02d"), yearReal
% 100);
1734 str
.Replace(strYear
, strYearReal
);
1735 str
.Replace(strYear2
, strYearReal2
);
1737 // and replace back all occurences of replacement string
1740 str
.Replace(replacement2
, strYear2
);
1741 str
.Replace(replacement
, strYear
);
1748 case _T('d'): // day of a month (01-31)
1749 tmp
.Printf(_T("%02d"), tm
.mday
);
1753 case _T('H'): // hour in 24h format (00-23)
1754 tmp
.Printf(_T("%02d"), tm
.hour
);
1758 case _T('I'): // hour in 12h format (01-12)
1760 // 24h -> 12h, 0h -> 12h too
1761 int hour12
= tm
.hour
> 12 ? tm
.hour
- 12
1762 : tm
.hour
? tm
.hour
: 12;
1763 tmp
.Printf(_T("%02d"), hour12
);
1768 case _T('j'): // day of the year
1769 tmp
.Printf(_T("%03d"), GetDayOfYear(tz
));
1773 case _T('m'): // month as a number (01-12)
1774 tmp
.Printf(_T("%02d"), tm
.mon
+ 1);
1778 case _T('M'): // minute as a decimal number (00-59)
1779 tmp
.Printf(_T("%02d"), tm
.min
);
1783 case _T('p'): // AM or PM string
1784 res
+= CallStrftime(_T("%p"), &tmTimeOnly
);
1787 case _T('S'): // second as a decimal number (00-61)
1788 tmp
.Printf(_T("%02d"), tm
.sec
);
1792 case _T('U'): // week number in the year (Sunday 1st week day)
1793 tmp
.Printf(_T("%02d"),
1794 (GetDayOfYear(tz
) - tm
.GetWeekDay() + 7) / 7);
1798 case _T('W'): // week number in the year (Monday 1st week day)
1799 tmp
.Printf(_T("%02d"), GetWeekOfYear(tz
));
1803 case _T('w'): // weekday as a number (0-6), Sunday = 0
1804 tmp
.Printf(_T("%d"), tm
.GetWeekDay());
1808 // case _T('x'): -- handled with "%c"
1810 case _T('X'): // locale default time representation
1811 // just use strftime() to format the time for us
1812 res
+= CallStrftime(_T("%X"), &tmTimeOnly
);
1815 case _T('y'): // year without century (00-99)
1816 tmp
.Printf(_T("%02d"), tm
.year
% 100);
1820 case _T('Y'): // year with century
1821 tmp
.Printf(_T("%04d"), tm
.year
);
1825 case _T('Z'): // timezone name
1826 res
+= CallStrftime(_T("%Z"), &tmTimeOnly
);
1830 wxFAIL_MSG(_T("unknown format specificator"));
1832 // fall through and just copy it nevertheless
1834 case _T('%'): // a percent sign
1839 wxFAIL_MSG(_T("missing format at the end of string"));
1841 // just put the '%' which was the last char in format
1850 // this function parses a string in (strict) RFC 822 format: see the section 5
1851 // of the RFC for the detailed description, but briefly it's something of the
1852 // form "Sat, 18 Dec 1999 00:48:30 +0100"
1854 // this function is "strict" by design - it must reject anything except true
1855 // RFC822 time specs.
1857 // TODO a great candidate for using reg exps
1858 const wxChar
*wxDateTime::ParseRfc822Date(const wxChar
* date
)
1860 wxCHECK_MSG( date
, (wxChar
*)NULL
, _T("NULL pointer in wxDateTime::Parse") );
1862 const wxChar
*p
= date
;
1863 const wxChar
*comma
= wxStrchr(p
, _T(','));
1866 // the part before comma is the weekday
1868 // skip it for now - we don't use but might check that it really
1869 // corresponds to the specfied date
1872 if ( *p
!= _T(' ') )
1874 wxLogDebug(_T("no space after weekday in RFC822 time spec"));
1876 return (wxChar
*)NULL
;
1882 // the following 1 or 2 digits are the day number
1883 if ( !wxIsdigit(*p
) )
1885 wxLogDebug(_T("day number expected in RFC822 time spec, none found"));
1887 return (wxChar
*)NULL
;
1890 wxDateTime_t day
= *p
++ - _T('0');
1891 if ( wxIsdigit(*p
) )
1894 day
+= *p
++ - _T('0');
1897 if ( *p
++ != _T(' ') )
1899 return (wxChar
*)NULL
;
1902 // the following 3 letters specify the month
1903 wxString
monName(p
, 3);
1905 if ( monName
== _T("Jan") )
1907 else if ( monName
== _T("Feb") )
1909 else if ( monName
== _T("Mar") )
1911 else if ( monName
== _T("Apr") )
1913 else if ( monName
== _T("May") )
1915 else if ( monName
== _T("Jun") )
1917 else if ( monName
== _T("Jul") )
1919 else if ( monName
== _T("Aug") )
1921 else if ( monName
== _T("Sep") )
1923 else if ( monName
== _T("Oct") )
1925 else if ( monName
== _T("Nov") )
1927 else if ( monName
== _T("Dec") )
1931 wxLogDebug(_T("Invalid RFC 822 month name '%s'"), monName
.c_str());
1933 return (wxChar
*)NULL
;
1938 if ( *p
++ != _T(' ') )
1940 return (wxChar
*)NULL
;
1944 if ( !wxIsdigit(*p
) )
1947 return (wxChar
*)NULL
;
1950 int year
= *p
++ - _T('0');
1952 if ( !wxIsdigit(*p
) )
1954 // should have at least 2 digits in the year
1955 return (wxChar
*)NULL
;
1959 year
+= *p
++ - _T('0');
1961 // is it a 2 digit year (as per original RFC 822) or a 4 digit one?
1962 if ( wxIsdigit(*p
) )
1965 year
+= *p
++ - _T('0');
1967 if ( !wxIsdigit(*p
) )
1969 // no 3 digit years please
1970 return (wxChar
*)NULL
;
1974 year
+= *p
++ - _T('0');
1977 if ( *p
++ != _T(' ') )
1979 return (wxChar
*)NULL
;
1982 // time is in the format hh:mm:ss and seconds are optional
1983 if ( !wxIsdigit(*p
) )
1985 return (wxChar
*)NULL
;
1988 wxDateTime_t hour
= *p
++ - _T('0');
1990 if ( !wxIsdigit(*p
) )
1992 return (wxChar
*)NULL
;
1996 hour
+= *p
++ - _T('0');
1998 if ( *p
++ != _T(':') )
2000 return (wxChar
*)NULL
;
2003 if ( !wxIsdigit(*p
) )
2005 return (wxChar
*)NULL
;
2008 wxDateTime_t min
= *p
++ - _T('0');
2010 if ( !wxIsdigit(*p
) )
2012 return (wxChar
*)NULL
;
2016 min
+= *p
++ - _T('0');
2018 wxDateTime_t sec
= 0;
2019 if ( *p
++ == _T(':') )
2021 if ( !wxIsdigit(*p
) )
2023 return (wxChar
*)NULL
;
2026 sec
= *p
++ - _T('0');
2028 if ( !wxIsdigit(*p
) )
2030 return (wxChar
*)NULL
;
2034 sec
+= *p
++ - _T('0');
2037 if ( *p
++ != _T(' ') )
2039 return (wxChar
*)NULL
;
2042 // and now the interesting part: the timezone
2044 if ( *p
== _T('-') || *p
== _T('+') )
2046 // the explicit offset given: it has the form of hhmm
2047 bool plus
= *p
++ == _T('+');
2049 if ( !wxIsdigit(*p
) || !wxIsdigit(*(p
+ 1)) )
2051 return (wxChar
*)NULL
;
2055 offset
= 60*(10*(*p
- _T('0')) + (*(p
+ 1) - _T('0')));
2059 if ( !wxIsdigit(*p
) || !wxIsdigit(*(p
+ 1)) )
2061 return (wxChar
*)NULL
;
2065 offset
+= 10*(*p
- _T('0')) + (*(p
+ 1) - _T('0'));
2076 // the symbolic timezone given: may be either military timezone or one
2077 // of standard abbreviations
2080 // military: Z = UTC, J unused, A = -1, ..., Y = +12
2081 static const int offsets
[26] =
2083 //A B C D E F G H I J K L M
2084 -1, -2, -3, -4, -5, -6, -7, -8, -9, 0, -10, -11, -12,
2085 //N O P R Q S T U V W Z Y Z
2086 +1, +2, +3, +4, +5, +6, +7, +8, +9, +10, +11, +12, 0
2089 if ( *p
< _T('A') || *p
> _T('Z') || *p
== _T('J') )
2091 wxLogDebug(_T("Invalid militaty timezone '%c'"), *p
);
2093 return (wxChar
*)NULL
;
2096 offset
= offsets
[*p
++ - _T('A')];
2102 if ( tz
== _T("UT") || tz
== _T("UTC") || tz
== _T("GMT") )
2104 else if ( tz
== _T("AST") )
2105 offset
= AST
- GMT0
;
2106 else if ( tz
== _T("ADT") )
2107 offset
= ADT
- GMT0
;
2108 else if ( tz
== _T("EST") )
2109 offset
= EST
- GMT0
;
2110 else if ( tz
== _T("EDT") )
2111 offset
= EDT
- GMT0
;
2112 else if ( tz
== _T("CST") )
2113 offset
= CST
- GMT0
;
2114 else if ( tz
== _T("CDT") )
2115 offset
= CDT
- GMT0
;
2116 else if ( tz
== _T("MST") )
2117 offset
= MST
- GMT0
;
2118 else if ( tz
== _T("MDT") )
2119 offset
= MDT
- GMT0
;
2120 else if ( tz
== _T("PST") )
2121 offset
= PST
- GMT0
;
2122 else if ( tz
== _T("PDT") )
2123 offset
= PDT
- GMT0
;
2126 wxLogDebug(_T("Unknown RFC 822 timezone '%s'"), p
);
2128 return (wxChar
*)NULL
;
2138 // the spec was correct
2139 Set(day
, mon
, year
, hour
, min
, sec
);
2140 MakeTimezone(60*offset
);
2145 const wxChar
*wxDateTime::ParseFormat(const wxChar
*date
, const wxChar
*format
)
2147 wxCHECK_MSG( date
&& format
, (wxChar
*)NULL
,
2148 _T("NULL pointer in wxDateTime::Parse") );
2150 // there is a public domain version of getdate.y, but it only works for
2152 wxFAIL_MSG(_T("TODO"));
2154 return (wxChar
*)NULL
;
2157 const wxChar
*wxDateTime::ParseDateTime(const wxChar
*date
)
2159 wxCHECK_MSG( date
, (wxChar
*)NULL
, _T("NULL pointer in wxDateTime::Parse") );
2161 // find a public domain version of strptime() somewhere?
2162 wxFAIL_MSG(_T("TODO"));
2164 return (wxChar
*)NULL
;
2167 const wxChar
*wxDateTime::ParseDate(const wxChar
*date
)
2169 // this is a simplified version of ParseDateTime() which understands only
2170 // "today" (for wxDate compatibility) and digits only otherwise (and not
2171 // all esoteric constructions ParseDateTime() knows about)
2173 wxCHECK_MSG( date
, (wxChar
*)NULL
, _T("NULL pointer in wxDateTime::Parse") );
2175 const wxChar
*p
= date
;
2176 while ( wxIsspace(*p
) )
2179 wxString today
= _T("today");
2180 size_t len
= today
.length();
2181 if ( wxString(p
, len
).CmpNoCase(today
) == 0 )
2183 // nothing can follow this, so stop here
2192 bool haveDay
= FALSE
, // the months day?
2193 haveWDay
= FALSE
, // the day of week?
2194 haveMon
= FALSE
, // the month?
2195 haveYear
= FALSE
; // the year?
2197 // and the value of the items we have (init them to get rid of warnings)
2198 WeekDay wday
= Inv_WeekDay
;
2199 wxDateTime_t day
= 0;
2200 wxDateTime::Month mon
= Inv_Month
;
2203 // tokenize the string
2204 wxStringTokenizer
tok(p
, _T(",/-\t "));
2205 while ( tok
.HasMoreTokens() )
2207 wxString token
= tok
.GetNextToken();
2211 if ( token
.ToULong(&val
) )
2213 // guess what this number is
2217 // only years are counted from 0
2218 isYear
= (val
== 0) || (val
> 31);
2221 // may be the month or month day or the year, assume the
2222 // month day by default and fallback to month if the range
2223 // allow it or to the year if our assumption doesn't work
2226 // we already have the day, so may only be a month or year
2236 else // it may be day
2243 if ( val
> GetNumOfDaysInMonth(haveYear
? year
2247 // oops, it can't be a day finally
2263 // remember that we have this and stop the scan if it's the second
2264 // time we find this, except for the day logic (see there)
2274 // no roll over - 99 means 99, not 1999 for us
2286 mon
= (wxDateTime::Month
)val
;
2290 wxASSERT_MSG( isDay
, _T("logic error") );
2294 // may be were mistaken when we found it for the first
2295 // time? may be it was a month or year instead?
2297 // this ability to "backtrack" allows us to correctly parse
2298 // both things like 01/13 and 13/01 - but, of course, we
2299 // still can't resolve the ambiguity in 01/02 (it will be
2300 // Feb 1 for us, not Jan 2 as americans might expect!)
2301 if ( (day
<= 12) && !haveMon
)
2303 // exchange day and month
2304 mon
= (wxDateTime::Month
)day
;
2308 else if ( !haveYear
)
2310 // exchange day and year
2322 else // not a number
2324 mon
= GetMonthFromName(token
);
2325 if ( mon
!= Inv_Month
)
2337 wday
= GetWeekDayFromName(token
);
2338 if ( wday
!= Inv_WeekDay
)
2351 static const wxChar
*ordinals
[] =
2353 wxTRANSLATE("first"),
2354 wxTRANSLATE("second"),
2355 wxTRANSLATE("third"),
2356 wxTRANSLATE("fourth"),
2357 wxTRANSLATE("fifth"),
2358 wxTRANSLATE("sixth"),
2359 wxTRANSLATE("seventh"),
2360 wxTRANSLATE("eighth"),
2361 wxTRANSLATE("ninth"),
2362 wxTRANSLATE("tenth"),
2363 wxTRANSLATE("eleventh"),
2364 wxTRANSLATE("twelfth"),
2365 wxTRANSLATE("thirteenth"),
2366 wxTRANSLATE("fourteenth"),
2367 wxTRANSLATE("fifteenth"),
2368 wxTRANSLATE("sixteenth"),
2369 wxTRANSLATE("seventeenth"),
2370 wxTRANSLATE("eighteenth"),
2371 wxTRANSLATE("nineteenth"),
2372 wxTRANSLATE("twentieth"),
2373 // that's enough - otherwise we'd have problems with
2374 // composite (or not) ordinals otherwise
2378 for ( n
= 0; n
< WXSIZEOF(ordinals
); n
++ )
2380 if ( token
.CmpNoCase(ordinals
[n
]) == 0 )
2386 if ( n
== WXSIZEOF(ordinals
) )
2388 // stop here - something unknown
2395 // don't try anything here (as in case of numeric day
2396 // above) - the symbolic day spec should always
2397 // precede the month/year
2409 // either no more tokens or the scan was stopped by something we couldn't
2410 // parse - in any case, see if we can construct a date from what we have
2411 if ( !haveDay
&& !haveWDay
)
2413 wxLogDebug(_T("no day, no weekday hence no date."));
2415 return (wxChar
*)NULL
;
2418 if ( haveWDay
&& (haveMon
|| haveYear
|| haveDay
) &&
2419 !(haveMon
&& haveMon
&& haveYear
) )
2421 // without adjectives (which we don't support here) the week day only
2422 // makes sense completely separately or with the full date
2423 // specification (what would "Wed 1999" mean?)
2424 return (wxChar
*)NULL
;
2429 mon
= GetCurrentMonth();
2434 year
= GetCurrentYear();
2439 Set(day
, mon
, year
);
2443 // check that it is really the same
2444 if ( GetWeekDay() != wday
)
2446 // inconsistency detected
2447 return (wxChar
*)NULL
;
2455 SetToWeekDayInSameWeek(wday
);
2458 // return the pointer to the next char
2459 return p
+ wxStrlen(p
) - wxStrlen(tok
.GetString());
2462 const wxChar
*wxDateTime::ParseTime(const wxChar
*time
)
2464 wxCHECK_MSG( time
, (wxChar
*)NULL
, _T("NULL pointer in wxDateTime::Parse") );
2466 // this function should be able to parse different time formats as well as
2467 // timezones (take the code out from ParseRfc822Date()) and AM/PM.
2468 wxFAIL_MSG(_T("TODO"));
2470 return (wxChar
*)NULL
;
2473 // ============================================================================
2475 // ============================================================================
2477 // not all strftime(3) format specifiers make sense here because, for example,
2478 // a time span doesn't have a year nor a timezone
2480 // Here are the ones which are supported (all of them are supported by strftime
2482 // %H hour in 24 hour format
2483 // %M minute (00 - 59)
2484 // %S second (00 - 59)
2487 // Also, for MFC CTimeSpan compatibility, we support
2488 // %D number of days
2490 // And, to be better than MFC :-), we also have
2491 // %E number of wEeks
2492 // %l milliseconds (000 - 999)
2493 wxString
wxTimeSpan::Format(const wxChar
*format
) const
2495 wxCHECK_MSG( format
, _T(""), _T("NULL format in wxTimeSpan::Format") );
2498 str
.Alloc(strlen(format
));
2500 for ( const wxChar
*pch
= format
; pch
; pch
++ )
2512 wxFAIL_MSG( _T("invalid format character") );
2516 // will get to str << ch below
2520 tmp
.Printf(_T("%d"), GetDays());
2524 tmp
.Printf(_T("%d"), GetWeeks());
2528 tmp
.Printf(_T("%02d"), GetHours());
2532 tmp
.Printf(_T("%03ld"), GetMilliseconds().ToLong());
2536 tmp
.Printf(_T("%02d"), GetMinutes());
2540 tmp
.Printf(_T("%02ld"), GetSeconds().ToLong());
2548 // skip str += ch below