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
1590 wxString tmp
, res
, fmt
;
1591 for ( const wxChar
*p
= format
; *p
; p
++ )
1593 if ( *p
!= _T('%') )
1601 // set the default format
1604 case _T('Y'): // year has 4 digits
1608 case _T('j'): // day of year has 3 digits
1613 // it's either another valid format specifier in which case
1614 // the format is "%02d" (for all the rest) or we have the
1615 // field width preceding the format in which case it will
1616 // override the default format anyhow
1621 // start of the format specification
1624 case _T('a'): // a weekday name
1626 // second parameter should be TRUE for abbreviated names
1627 res
+= GetWeekDayName(tm
.GetWeekDay(), *p
== _T('a'));
1630 case _T('b'): // a month name
1632 // second parameter should be TRUE for abbreviated names
1633 res
+= GetMonthName(tm
.mon
, *p
== _T('b'));
1636 case _T('c'): // locale default date and time representation
1637 case _T('x'): // locale default date representation
1639 // the problem: there is no way to know what do these format
1640 // specifications correspond to for the current locale.
1642 // the solution: use a hack and still use strftime(): first
1643 // find the YEAR which is a year in the strftime() range (1970
1644 // - 2038) whose Jan 1 falls on the same week day as the Jan 1
1645 // of the real year. Then make a copy of the format and
1646 // replace all occurences of YEAR in it with some unique
1647 // string not appearing anywhere else in it, then use
1648 // strftime() to format the date in year YEAR and then replace
1649 // YEAR back by the real year and the unique replacement
1650 // string back with YEAR. Notice that "all occurences of YEAR"
1651 // means all occurences of 4 digit as well as 2 digit form!
1653 // the bugs: we assume that neither of %c nor %x contains any
1654 // fields which may change between the YEAR and real year. For
1655 // example, the week number (%U, %W) and the day number (%j)
1656 // will change if one of these years is leap and the other one
1659 // find the YEAR: normally, for any year X, Jan 1 or the
1660 // year X + 28 is the same weekday as Jan 1 of X (because
1661 // the weekday advances by 1 for each normal X and by 2
1662 // for each leap X, hence by 5 every 4 years or by 35
1663 // which is 0 mod 7 every 28 years) but this rule breaks
1664 // down if there are years between X and Y which are
1665 // divisible by 4 but not leap (i.e. divisible by 100 but
1666 // not 400), hence the correction.
1668 int yearReal
= GetYear(tz
);
1669 int mod28
= yearReal
% 28;
1671 // be careful to not go too far - we risk to leave the
1676 year
= 1988 + mod28
; // 1988 == 0 (mod 28)
1680 year
= 1970 + mod28
- 10; // 1970 == 10 (mod 28)
1683 int nCentury
= year
/ 100,
1684 nCenturyReal
= yearReal
/ 100;
1686 // need to adjust for the years divisble by 400 which are
1687 // not leap but are counted like leap ones if we just take
1688 // the number of centuries in between for nLostWeekDays
1689 int nLostWeekDays
= (nCentury
- nCenturyReal
) -
1690 (nCentury
/ 4 - nCenturyReal
/ 4);
1692 // we have to gain back the "lost" weekdays: note that the
1693 // effect of this loop is to not do anything to
1694 // nLostWeekDays (which we won't use any more), but to
1695 // (indirectly) set the year correctly
1696 while ( (nLostWeekDays
% 7) != 0 )
1698 nLostWeekDays
+= year
++ % 4 ? 1 : 2;
1701 // at any rate, we couldn't go further than 1988 + 9 + 28!
1702 wxASSERT_MSG( year
< 2030,
1703 _T("logic error in wxDateTime::Format") );
1705 wxString strYear
, strYear2
;
1706 strYear
.Printf(_T("%d"), year
);
1707 strYear2
.Printf(_T("%d"), year
% 100);
1709 // find two strings not occuring in format (this is surely
1710 // not optimal way of doing it... improvements welcome!)
1711 wxString fmt
= format
;
1712 wxString replacement
= (wxChar
)-1;
1713 while ( fmt
.Find(replacement
) != wxNOT_FOUND
)
1715 replacement
<< (wxChar
)-1;
1718 wxString replacement2
= (wxChar
)-2;
1719 while ( fmt
.Find(replacement
) != wxNOT_FOUND
)
1721 replacement
<< (wxChar
)-2;
1724 // replace all occurences of year with it
1725 bool wasReplaced
= fmt
.Replace(strYear
, replacement
) > 0;
1727 wasReplaced
= fmt
.Replace(strYear2
, replacement2
) > 0;
1729 // use strftime() to format the same date but in supported
1732 // NB: we assume that strftime() doesn't check for the
1733 // date validity and will happily format the date
1734 // corresponding to Feb 29 of a non leap year (which
1735 // may happen if yearReal was leap and year is not)
1736 struct tm tmAdjusted
;
1737 tmAdjusted
.tm_hour
= tm
.hour
;
1738 tmAdjusted
.tm_min
= tm
.min
;
1739 tmAdjusted
.tm_sec
= tm
.sec
;
1740 tmAdjusted
.tm_wday
= tm
.GetWeekDay();
1741 tmAdjusted
.tm_yday
= GetDayOfYear();
1742 tmAdjusted
.tm_mday
= tm
.mday
;
1743 tmAdjusted
.tm_mon
= tm
.mon
;
1744 tmAdjusted
.tm_year
= year
- 1900;
1745 tmAdjusted
.tm_isdst
= 0; // no DST, already adjusted
1746 wxString str
= CallStrftime(*p
== _T('c') ? _T("%c")
1750 // now replace the occurence of 1999 with the real year
1751 wxString strYearReal
, strYearReal2
;
1752 strYearReal
.Printf(_T("%04d"), yearReal
);
1753 strYearReal2
.Printf(_T("%02d"), yearReal
% 100);
1754 str
.Replace(strYear
, strYearReal
);
1755 str
.Replace(strYear2
, strYearReal2
);
1757 // and replace back all occurences of replacement string
1760 str
.Replace(replacement2
, strYear2
);
1761 str
.Replace(replacement
, strYear
);
1768 case _T('d'): // day of a month (01-31)
1769 res
+= wxString::Format(fmt
, tm
.mday
);
1772 case _T('H'): // hour in 24h format (00-23)
1773 res
+= wxString::Format(fmt
, tm
.hour
);
1776 case _T('I'): // hour in 12h format (01-12)
1778 // 24h -> 12h, 0h -> 12h too
1779 int hour12
= tm
.hour
> 12 ? tm
.hour
- 12
1780 : tm
.hour
? tm
.hour
: 12;
1781 res
+= wxString::Format(fmt
, hour12
);
1785 case _T('j'): // day of the year
1786 res
+= wxString::Format(fmt
, GetDayOfYear(tz
));
1789 case _T('m'): // month as a number (01-12)
1790 res
+= wxString::Format(fmt
, tm
.mon
+ 1);
1793 case _T('M'): // minute as a decimal number (00-59)
1794 res
+= wxString::Format(fmt
, tm
.min
);
1797 case _T('p'): // AM or PM string
1798 res
+= CallStrftime(_T("%p"), &tmTimeOnly
);
1801 case _T('S'): // second as a decimal number (00-61)
1802 res
+= wxString::Format(fmt
, tm
.sec
);
1805 case _T('U'): // week number in the year (Sunday 1st week day)
1807 int week
= (GetDayOfYear(tz
) - tm
.GetWeekDay() + 7) / 7;
1808 res
+= wxString::Format(fmt
, week
);
1812 case _T('W'): // week number in the year (Monday 1st week day)
1813 res
+= wxString::Format(fmt
, GetWeekOfYear(tz
));
1816 case _T('w'): // weekday as a number (0-6), Sunday = 0
1817 res
+= wxString::Format(fmt
, tm
.GetWeekDay());
1820 // case _T('x'): -- handled with "%c"
1822 case _T('X'): // locale default time representation
1823 // just use strftime() to format the time for us
1824 res
+= CallStrftime(_T("%X"), &tmTimeOnly
);
1827 case _T('y'): // year without century (00-99)
1828 res
+= wxString::Format(fmt
, tm
.year
% 100);
1831 case _T('Y'): // year with century
1832 res
+= wxString::Format(fmt
, tm
.year
);
1835 case _T('Z'): // timezone name
1836 res
+= CallStrftime(_T("%Z"), &tmTimeOnly
);
1840 // is it the format width?
1842 while ( *p
== _T('-') || *p
== _T('+') ||
1843 *p
== _T(' ') || wxIsdigit(*p
) )
1848 if ( !fmt
.IsEmpty() )
1850 // we've only got the flags and width so far in fmt
1851 fmt
.Prepend(_T('%'));
1852 fmt
.Append(_T('d'));
1857 // no, it wasn't the width
1858 wxFAIL_MSG(_T("unknown format specificator"));
1860 // fall through and just copy it nevertheless
1862 case _T('%'): // a percent sign
1867 wxFAIL_MSG(_T("missing format at the end of string"));
1869 // just put the '%' which was the last char in format
1878 // this function parses a string in (strict) RFC 822 format: see the section 5
1879 // of the RFC for the detailed description, but briefly it's something of the
1880 // form "Sat, 18 Dec 1999 00:48:30 +0100"
1882 // this function is "strict" by design - it must reject anything except true
1883 // RFC822 time specs.
1885 // TODO a great candidate for using reg exps
1886 const wxChar
*wxDateTime::ParseRfc822Date(const wxChar
* date
)
1888 wxCHECK_MSG( date
, (wxChar
*)NULL
, _T("NULL pointer in wxDateTime::Parse") );
1890 const wxChar
*p
= date
;
1891 const wxChar
*comma
= wxStrchr(p
, _T(','));
1894 // the part before comma is the weekday
1896 // skip it for now - we don't use but might check that it really
1897 // corresponds to the specfied date
1900 if ( *p
!= _T(' ') )
1902 wxLogDebug(_T("no space after weekday in RFC822 time spec"));
1904 return (wxChar
*)NULL
;
1910 // the following 1 or 2 digits are the day number
1911 if ( !wxIsdigit(*p
) )
1913 wxLogDebug(_T("day number expected in RFC822 time spec, none found"));
1915 return (wxChar
*)NULL
;
1918 wxDateTime_t day
= *p
++ - _T('0');
1919 if ( wxIsdigit(*p
) )
1922 day
+= *p
++ - _T('0');
1925 if ( *p
++ != _T(' ') )
1927 return (wxChar
*)NULL
;
1930 // the following 3 letters specify the month
1931 wxString
monName(p
, 3);
1933 if ( monName
== _T("Jan") )
1935 else if ( monName
== _T("Feb") )
1937 else if ( monName
== _T("Mar") )
1939 else if ( monName
== _T("Apr") )
1941 else if ( monName
== _T("May") )
1943 else if ( monName
== _T("Jun") )
1945 else if ( monName
== _T("Jul") )
1947 else if ( monName
== _T("Aug") )
1949 else if ( monName
== _T("Sep") )
1951 else if ( monName
== _T("Oct") )
1953 else if ( monName
== _T("Nov") )
1955 else if ( monName
== _T("Dec") )
1959 wxLogDebug(_T("Invalid RFC 822 month name '%s'"), monName
.c_str());
1961 return (wxChar
*)NULL
;
1966 if ( *p
++ != _T(' ') )
1968 return (wxChar
*)NULL
;
1972 if ( !wxIsdigit(*p
) )
1975 return (wxChar
*)NULL
;
1978 int year
= *p
++ - _T('0');
1980 if ( !wxIsdigit(*p
) )
1982 // should have at least 2 digits in the year
1983 return (wxChar
*)NULL
;
1987 year
+= *p
++ - _T('0');
1989 // is it a 2 digit year (as per original RFC 822) or a 4 digit one?
1990 if ( wxIsdigit(*p
) )
1993 year
+= *p
++ - _T('0');
1995 if ( !wxIsdigit(*p
) )
1997 // no 3 digit years please
1998 return (wxChar
*)NULL
;
2002 year
+= *p
++ - _T('0');
2005 if ( *p
++ != _T(' ') )
2007 return (wxChar
*)NULL
;
2010 // time is in the format hh:mm:ss and seconds are optional
2011 if ( !wxIsdigit(*p
) )
2013 return (wxChar
*)NULL
;
2016 wxDateTime_t hour
= *p
++ - _T('0');
2018 if ( !wxIsdigit(*p
) )
2020 return (wxChar
*)NULL
;
2024 hour
+= *p
++ - _T('0');
2026 if ( *p
++ != _T(':') )
2028 return (wxChar
*)NULL
;
2031 if ( !wxIsdigit(*p
) )
2033 return (wxChar
*)NULL
;
2036 wxDateTime_t min
= *p
++ - _T('0');
2038 if ( !wxIsdigit(*p
) )
2040 return (wxChar
*)NULL
;
2044 min
+= *p
++ - _T('0');
2046 wxDateTime_t sec
= 0;
2047 if ( *p
++ == _T(':') )
2049 if ( !wxIsdigit(*p
) )
2051 return (wxChar
*)NULL
;
2054 sec
= *p
++ - _T('0');
2056 if ( !wxIsdigit(*p
) )
2058 return (wxChar
*)NULL
;
2062 sec
+= *p
++ - _T('0');
2065 if ( *p
++ != _T(' ') )
2067 return (wxChar
*)NULL
;
2070 // and now the interesting part: the timezone
2072 if ( *p
== _T('-') || *p
== _T('+') )
2074 // the explicit offset given: it has the form of hhmm
2075 bool plus
= *p
++ == _T('+');
2077 if ( !wxIsdigit(*p
) || !wxIsdigit(*(p
+ 1)) )
2079 return (wxChar
*)NULL
;
2083 offset
= 60*(10*(*p
- _T('0')) + (*(p
+ 1) - _T('0')));
2087 if ( !wxIsdigit(*p
) || !wxIsdigit(*(p
+ 1)) )
2089 return (wxChar
*)NULL
;
2093 offset
+= 10*(*p
- _T('0')) + (*(p
+ 1) - _T('0'));
2104 // the symbolic timezone given: may be either military timezone or one
2105 // of standard abbreviations
2108 // military: Z = UTC, J unused, A = -1, ..., Y = +12
2109 static const int offsets
[26] =
2111 //A B C D E F G H I J K L M
2112 -1, -2, -3, -4, -5, -6, -7, -8, -9, 0, -10, -11, -12,
2113 //N O P R Q S T U V W Z Y Z
2114 +1, +2, +3, +4, +5, +6, +7, +8, +9, +10, +11, +12, 0
2117 if ( *p
< _T('A') || *p
> _T('Z') || *p
== _T('J') )
2119 wxLogDebug(_T("Invalid militaty timezone '%c'"), *p
);
2121 return (wxChar
*)NULL
;
2124 offset
= offsets
[*p
++ - _T('A')];
2130 if ( tz
== _T("UT") || tz
== _T("UTC") || tz
== _T("GMT") )
2132 else if ( tz
== _T("AST") )
2133 offset
= AST
- GMT0
;
2134 else if ( tz
== _T("ADT") )
2135 offset
= ADT
- GMT0
;
2136 else if ( tz
== _T("EST") )
2137 offset
= EST
- GMT0
;
2138 else if ( tz
== _T("EDT") )
2139 offset
= EDT
- GMT0
;
2140 else if ( tz
== _T("CST") )
2141 offset
= CST
- GMT0
;
2142 else if ( tz
== _T("CDT") )
2143 offset
= CDT
- GMT0
;
2144 else if ( tz
== _T("MST") )
2145 offset
= MST
- GMT0
;
2146 else if ( tz
== _T("MDT") )
2147 offset
= MDT
- GMT0
;
2148 else if ( tz
== _T("PST") )
2149 offset
= PST
- GMT0
;
2150 else if ( tz
== _T("PDT") )
2151 offset
= PDT
- GMT0
;
2154 wxLogDebug(_T("Unknown RFC 822 timezone '%s'"), p
);
2156 return (wxChar
*)NULL
;
2166 // the spec was correct
2167 Set(day
, mon
, year
, hour
, min
, sec
);
2168 MakeTimezone(60*offset
);
2173 const wxChar
*wxDateTime::ParseFormat(const wxChar
*date
, const wxChar
*format
)
2175 wxCHECK_MSG( date
&& format
, (wxChar
*)NULL
,
2176 _T("NULL pointer in wxDateTime::Parse") );
2178 // there is a public domain version of getdate.y, but it only works for
2180 wxFAIL_MSG(_T("TODO"));
2182 return (wxChar
*)NULL
;
2185 const wxChar
*wxDateTime::ParseDateTime(const wxChar
*date
)
2187 wxCHECK_MSG( date
, (wxChar
*)NULL
, _T("NULL pointer in wxDateTime::Parse") );
2189 // find a public domain version of strptime() somewhere?
2190 wxFAIL_MSG(_T("TODO"));
2192 return (wxChar
*)NULL
;
2195 const wxChar
*wxDateTime::ParseDate(const wxChar
*date
)
2197 // this is a simplified version of ParseDateTime() which understands only
2198 // "today" (for wxDate compatibility) and digits only otherwise (and not
2199 // all esoteric constructions ParseDateTime() knows about)
2201 wxCHECK_MSG( date
, (wxChar
*)NULL
, _T("NULL pointer in wxDateTime::Parse") );
2203 const wxChar
*p
= date
;
2204 while ( wxIsspace(*p
) )
2207 wxString today
= _T("today");
2208 size_t len
= today
.length();
2209 if ( wxString(p
, len
).CmpNoCase(today
) == 0 )
2211 // nothing can follow this, so stop here
2220 bool haveDay
= FALSE
, // the months day?
2221 haveWDay
= FALSE
, // the day of week?
2222 haveMon
= FALSE
, // the month?
2223 haveYear
= FALSE
; // the year?
2225 // and the value of the items we have (init them to get rid of warnings)
2226 WeekDay wday
= Inv_WeekDay
;
2227 wxDateTime_t day
= 0;
2228 wxDateTime::Month mon
= Inv_Month
;
2231 // tokenize the string
2232 wxStringTokenizer
tok(p
, _T(",/-\t "));
2233 while ( tok
.HasMoreTokens() )
2235 wxString token
= tok
.GetNextToken();
2239 if ( token
.ToULong(&val
) )
2241 // guess what this number is
2245 // only years are counted from 0
2246 isYear
= (val
== 0) || (val
> 31);
2249 // may be the month or month day or the year, assume the
2250 // month day by default and fallback to month if the range
2251 // allow it or to the year if our assumption doesn't work
2254 // we already have the day, so may only be a month or year
2264 else // it may be day
2271 if ( val
> GetNumOfDaysInMonth(haveYear
? year
2275 // oops, it can't be a day finally
2291 // remember that we have this and stop the scan if it's the second
2292 // time we find this, except for the day logic (see there)
2302 // no roll over - 99 means 99, not 1999 for us
2314 mon
= (wxDateTime::Month
)val
;
2318 wxASSERT_MSG( isDay
, _T("logic error") );
2322 // may be were mistaken when we found it for the first
2323 // time? may be it was a month or year instead?
2325 // this ability to "backtrack" allows us to correctly parse
2326 // both things like 01/13 and 13/01 - but, of course, we
2327 // still can't resolve the ambiguity in 01/02 (it will be
2328 // Feb 1 for us, not Jan 2 as americans might expect!)
2329 if ( (day
<= 12) && !haveMon
)
2331 // exchange day and month
2332 mon
= (wxDateTime::Month
)day
;
2336 else if ( !haveYear
)
2338 // exchange day and year
2350 else // not a number
2352 mon
= GetMonthFromName(token
);
2353 if ( mon
!= Inv_Month
)
2365 wday
= GetWeekDayFromName(token
);
2366 if ( wday
!= Inv_WeekDay
)
2379 static const wxChar
*ordinals
[] =
2381 wxTRANSLATE("first"),
2382 wxTRANSLATE("second"),
2383 wxTRANSLATE("third"),
2384 wxTRANSLATE("fourth"),
2385 wxTRANSLATE("fifth"),
2386 wxTRANSLATE("sixth"),
2387 wxTRANSLATE("seventh"),
2388 wxTRANSLATE("eighth"),
2389 wxTRANSLATE("ninth"),
2390 wxTRANSLATE("tenth"),
2391 wxTRANSLATE("eleventh"),
2392 wxTRANSLATE("twelfth"),
2393 wxTRANSLATE("thirteenth"),
2394 wxTRANSLATE("fourteenth"),
2395 wxTRANSLATE("fifteenth"),
2396 wxTRANSLATE("sixteenth"),
2397 wxTRANSLATE("seventeenth"),
2398 wxTRANSLATE("eighteenth"),
2399 wxTRANSLATE("nineteenth"),
2400 wxTRANSLATE("twentieth"),
2401 // that's enough - otherwise we'd have problems with
2402 // composite (or not) ordinals otherwise
2406 for ( n
= 0; n
< WXSIZEOF(ordinals
); n
++ )
2408 if ( token
.CmpNoCase(ordinals
[n
]) == 0 )
2414 if ( n
== WXSIZEOF(ordinals
) )
2416 // stop here - something unknown
2423 // don't try anything here (as in case of numeric day
2424 // above) - the symbolic day spec should always
2425 // precede the month/year
2437 // either no more tokens or the scan was stopped by something we couldn't
2438 // parse - in any case, see if we can construct a date from what we have
2439 if ( !haveDay
&& !haveWDay
)
2441 wxLogDebug(_T("no day, no weekday hence no date."));
2443 return (wxChar
*)NULL
;
2446 if ( haveWDay
&& (haveMon
|| haveYear
|| haveDay
) &&
2447 !(haveMon
&& haveMon
&& haveYear
) )
2449 // without adjectives (which we don't support here) the week day only
2450 // makes sense completely separately or with the full date
2451 // specification (what would "Wed 1999" mean?)
2452 return (wxChar
*)NULL
;
2457 mon
= GetCurrentMonth();
2462 year
= GetCurrentYear();
2467 Set(day
, mon
, year
);
2471 // check that it is really the same
2472 if ( GetWeekDay() != wday
)
2474 // inconsistency detected
2475 return (wxChar
*)NULL
;
2483 SetToWeekDayInSameWeek(wday
);
2486 // return the pointer to the next char
2487 return p
+ wxStrlen(p
) - wxStrlen(tok
.GetString());
2490 const wxChar
*wxDateTime::ParseTime(const wxChar
*time
)
2492 wxCHECK_MSG( time
, (wxChar
*)NULL
, _T("NULL pointer in wxDateTime::Parse") );
2494 // this function should be able to parse different time formats as well as
2495 // timezones (take the code out from ParseRfc822Date()) and AM/PM.
2496 wxFAIL_MSG(_T("TODO"));
2498 return (wxChar
*)NULL
;
2501 // ============================================================================
2503 // ============================================================================
2505 // not all strftime(3) format specifiers make sense here because, for example,
2506 // a time span doesn't have a year nor a timezone
2508 // Here are the ones which are supported (all of them are supported by strftime
2510 // %H hour in 24 hour format
2511 // %M minute (00 - 59)
2512 // %S second (00 - 59)
2515 // Also, for MFC CTimeSpan compatibility, we support
2516 // %D number of days
2518 // And, to be better than MFC :-), we also have
2519 // %E number of wEeks
2520 // %l milliseconds (000 - 999)
2521 wxString
wxTimeSpan::Format(const wxChar
*format
) const
2523 wxCHECK_MSG( format
, _T(""), _T("NULL format in wxTimeSpan::Format") );
2526 str
.Alloc(strlen(format
));
2528 for ( const wxChar
*pch
= format
; pch
; pch
++ )
2540 wxFAIL_MSG( _T("invalid format character") );
2544 // will get to str << ch below
2548 tmp
.Printf(_T("%d"), GetDays());
2552 tmp
.Printf(_T("%d"), GetWeeks());
2556 tmp
.Printf(_T("%02d"), GetHours());
2560 tmp
.Printf(_T("%03ld"), GetMilliseconds().ToLong());
2564 tmp
.Printf(_T("%02d"), GetMinutes());
2568 tmp
.Printf(_T("%02ld"), GetSeconds().ToLong());
2576 // skip str += ch below