]> git.saurik.com Git - wxWidgets.git/blame - src/common/datetime.cpp
Added missing newline for Windows debug output
[wxWidgets.git] / src / common / datetime.cpp
CommitLineData
1ef54dcf 1///////////////////////////////////////////////////////////////////////////////
0979c962
VZ
2// Name: wx/datetime.h
3// Purpose: implementation of time/date related classes
4// Author: Vadim Zeitlin
5// Modified by:
6// Created: 11.05.99
7// RCS-ID: $Id$
1ef54dcf
VZ
8// Copyright: (c) 1999 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9// parts of code taken from sndcal library by Scott E. Lee:
10//
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.
15//
65571936 16// Licence: wxWindows licence
1ef54dcf
VZ
17///////////////////////////////////////////////////////////////////////////////
18
19/*
20 * Implementation notes:
21 *
22 * 1. the time is stored as a 64bit integer containing the signed number of
299fcbfe
VZ
23 * milliseconds since Jan 1. 1970 (the Unix Epoch) - so it is always
24 * expressed in GMT.
1ef54dcf
VZ
25 *
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
28 *
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
31 *
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
35 * or less)
36 *
299fcbfe
VZ
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
40 * ToTimezone()
41 *
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
44 * wxDateTime::Tm.
1ef54dcf 45 */
0979c962
VZ
46
47// ============================================================================
48// declarations
49// ============================================================================
50
51// ----------------------------------------------------------------------------
52// headers
53// ----------------------------------------------------------------------------
54
14f355c2 55#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
0979c962
VZ
56 #pragma implementation "datetime.h"
57#endif
58
59// For compilers that support precompilation, includes "wx.h".
60#include "wx/wxprec.h"
61
62#ifdef __BORLANDC__
63 #pragma hdrstop
64#endif
65
1e6feb95
VZ
66#if !defined(wxUSE_DATETIME) || wxUSE_DATETIME
67
0979c962
VZ
68#ifndef WX_PRECOMP
69 #include "wx/string.h"
0979c962
VZ
70 #include "wx/log.h"
71#endif // WX_PRECOMP
72
54800df8 73#include "wx/intl.h"
fcc3d7cb 74#include "wx/thread.h"
cd0b1709 75#include "wx/tokenzr.h"
7281ab04 76#include "wx/module.h"
fcc3d7cb 77
ff0ea71c
GT
78#include <ctype.h>
79
0979c962 80#include "wx/datetime.h"
3422227a 81#include "wx/stopwatch.h" // for wxGetLocalTimeMillis()
0979c962 82
bd4a25bc
JS
83const long wxDateTime::TIME_T_FACTOR = 1000l;
84
cb73e600
SC
85#if wxUSE_EXTENDED_RTTI
86
87template<> void wxStringReadValue(const wxString &s , wxDateTime &data )
88{
89 data.ParseFormat(s,wxT("%Y-%m-%d %H:%M:%S")) ;
90}
91
92template<> void wxStringWriteValue(wxString &s , const wxDateTime &data )
93{
94 s = data.Format(wxT("%Y-%m-%d %H:%M:%S")) ;
95}
96
8805dbab 97wxCUSTOM_TYPE_INFO(wxDateTime, wxToStringConverter<wxDateTime> , wxFromStringConverter<wxDateTime>)
cb73e600
SC
98
99#endif
100
101//
f0f951fa
VZ
102// ----------------------------------------------------------------------------
103// conditional compilation
104// ----------------------------------------------------------------------------
6de20863 105
2423ef22
VZ
106#if defined(HAVE_STRPTIME) && defined(__GLIBC__) && \
107 ((__GLIBC__ == 2) && (__GLIBC_MINOR__ == 0))
f0f951fa
VZ
108 // glibc 2.0.7 strptime() is broken - the following snippet causes it to
109 // crash (instead of just failing):
110 //
111 // strncpy(buf, "Tue Dec 21 20:25:40 1999", 128);
112 // strptime(buf, "%x", &tm);
113 //
114 // so don't use it
115 #undef HAVE_STRPTIME
116#endif // broken strptime()
117
31907d03
SC
118#if defined(__MWERKS__) && wxUSE_UNICODE
119 #include <wtime.h>
120#endif
121
0779cc9a 122#if !defined(WX_TIMEZONE) && !defined(WX_GMTOFF_IN_TM)
ad0dc53b 123 #if defined(__BORLANDC__) || defined(__MINGW32__) || defined(__VISAGECPP__)
f0f951fa 124 #define WX_TIMEZONE _timezone
8208e181 125 #elif defined(__MWERKS__)
f6bcfd97 126 long wxmw_timezone = 28800;
c4e1b7f2 127 #define WX_TIMEZONE wxmw_timezone
5283098e 128 #elif defined(__DJGPP__) || defined(__WINE__)
713a0efc 129 #include <sys/timeb.h>
c4e1b7f2 130 #include <values.h>
713a0efc
VS
131 static long wxGetTimeZone()
132 {
c4e1b7f2
VS
133 static long timezone = MAXLONG; // invalid timezone
134 if (timezone == MAXLONG)
135 {
136 struct timeb tb;
137 ftime(&tb);
138 timezone = tb.timezone;
139 }
140 return timezone;
713a0efc
VS
141 }
142 #define WX_TIMEZONE wxGetTimeZone()
0779cc9a
GD
143 #elif defined(__DARWIN__)
144 #define WX_GMTOFF_IN_TM
f0f951fa
VZ
145 #else // unknown platform - try timezone
146 #define WX_TIMEZONE timezone
147 #endif
0779cc9a 148#endif // !WX_TIMEZONE && !WX_GMTOFF_IN_TM
c25a510b 149
384223b3
VZ
150// ----------------------------------------------------------------------------
151// macros
152// ----------------------------------------------------------------------------
153
154// debugging helper: just a convenient replacement of wxCHECK()
155#define wxDATETIME_CHECK(expr, msg) \
156 if ( !(expr) ) \
157 { \
158 wxFAIL_MSG(msg); \
159 *this = wxInvalidDateTime; \
160 return *this; \
161 }
162
4f6aed9c
VZ
163// ----------------------------------------------------------------------------
164// private classes
165// ----------------------------------------------------------------------------
166
167class wxDateTimeHolidaysModule : public wxModule
168{
169public:
170 virtual bool OnInit()
171 {
172 wxDateTimeHolidayAuthority::AddAuthority(new wxDateTimeWorkDays);
173
68379eaf 174 return true;
4f6aed9c
VZ
175 }
176
177 virtual void OnExit()
178 {
179 wxDateTimeHolidayAuthority::ClearAllAuthorities();
df5168c4 180 wxDateTimeHolidayAuthority::ms_authorities.clear();
4f6aed9c
VZ
181 }
182
183private:
184 DECLARE_DYNAMIC_CLASS(wxDateTimeHolidaysModule)
185};
186
187IMPLEMENT_DYNAMIC_CLASS(wxDateTimeHolidaysModule, wxModule)
188
b76b015e
VZ
189// ----------------------------------------------------------------------------
190// constants
191// ----------------------------------------------------------------------------
192
e6ec579c 193// some trivial ones
fcc3d7cb
VZ
194static const int MONTHS_IN_YEAR = 12;
195
df05cdc5
VZ
196static const int SEC_PER_MIN = 60;
197
198static const int MIN_PER_HOUR = 60;
199
200static const int HOURS_PER_DAY = 24;
fcc3d7cb 201
e6ec579c
VZ
202static const long SECONDS_PER_DAY = 86400l;
203
df05cdc5
VZ
204static const int DAYS_PER_WEEK = 7;
205
e6ec579c
VZ
206static const long MILLISECONDS_PER_DAY = 86400000l;
207
208// this is the integral part of JDN of the midnight of Jan 1, 1970
209// (i.e. JDN(Jan 1, 1970) = 2440587.5)
cd0b1709 210static const long EPOCH_JDN = 2440587l;
b76b015e 211
1ef54dcf
VZ
212// the date of JDN -0.5 (as we don't work with fractional parts, this is the
213// reference date for us) is Nov 24, 4714BC
214static const int JDN_0_YEAR = -4713;
215static const int JDN_0_MONTH = wxDateTime::Nov;
216static const int JDN_0_DAY = 24;
217
218// the constants used for JDN calculations
cd0b1709
VZ
219static const long JDN_OFFSET = 32046l;
220static const long DAYS_PER_5_MONTHS = 153l;
221static const long DAYS_PER_4_YEARS = 1461l;
222static const long DAYS_PER_400_YEARS = 146097l;
1ef54dcf 223
f0f951fa
VZ
224// this array contains the cumulated number of days in all previous months for
225// normal and leap years
226static const wxDateTime::wxDateTime_t gs_cumulatedDays[2][MONTHS_IN_YEAR] =
227{
228 { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 },
229 { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 }
230};
231
fcc3d7cb 232// ----------------------------------------------------------------------------
2ef31e80
VZ
233// global data
234// ----------------------------------------------------------------------------
235
384223b3
VZ
236// in the fine tradition of ANSI C we use our equivalent of (time_t)-1 to
237// indicate an invalid wxDateTime object
56d0c5a6 238const wxDateTime wxDefaultDateTime;
2ef31e80
VZ
239
240wxDateTime::Country wxDateTime::ms_country = wxDateTime::Country_Unknown;
241
b76b015e
VZ
242// ----------------------------------------------------------------------------
243// private functions
244// ----------------------------------------------------------------------------
245
4f6aed9c
VZ
246// debugger helper: shows what the date really is
247#ifdef __WXDEBUG__
248extern const wxChar *wxDumpDate(const wxDateTime* dt)
249{
0de868d9 250 static wxChar buf[128];
4f6aed9c
VZ
251
252 wxStrcpy(buf, dt->Format(_T("%Y-%m-%d (%a) %H:%M:%S")));
253
254 return buf;
255}
256#endif // Debug
257
fcc3d7cb
VZ
258// get the number of days in the given month of the given year
259static inline
260wxDateTime::wxDateTime_t GetNumOfDaysInMonth(int year, wxDateTime::Month month)
261{
e6ec579c
VZ
262 // the number of days in month in Julian/Gregorian calendar: the first line
263 // is for normal years, the second one is for the leap ones
264 static wxDateTime::wxDateTime_t daysInMonth[2][MONTHS_IN_YEAR] =
265 {
266 { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
267 { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
268 };
269
270 return daysInMonth[wxDateTime::IsLeapYear(year)][month];
fcc3d7cb
VZ
271}
272
c5432233
VZ
273// returns the time zone in the C sense, i.e. the difference UTC - local
274// (in seconds)
fcc3d7cb
VZ
275static int GetTimeZone()
276{
51485b76 277#ifdef WX_GMTOFF_IN_TM
68379eaf
WS
278 // set to true when the timezone is set
279 static bool s_timezoneSet = false;
0779cc9a 280 static long gmtoffset = LONG_MAX; // invalid timezone
c5432233 281
c5432233 282 // ensure that the timezone variable is set by calling localtime
fcc3d7cb
VZ
283 if ( !s_timezoneSet )
284 {
1ef54dcf 285 // just call localtime() instead of figuring out whether this system
e6ec579c 286 // supports tzset(), _tzset() or something else
c5432233 287 time_t t = 0;
0779cc9a 288 struct tm *tm;
fcc3d7cb 289
0779cc9a 290 tm = localtime(&t);
68379eaf 291 s_timezoneSet = true;
c5432233 292
c5432233
VZ
293 // note that GMT offset is the opposite of time zone and so to return
294 // consistent results in both WX_GMTOFF_IN_TM and !WX_GMTOFF_IN_TM
295 // cases we have to negate it
296 gmtoffset = -tm->tm_gmtoff;
fcc3d7cb
VZ
297 }
298
0779cc9a 299 return (int)gmtoffset;
51485b76 300#else // !WX_GMTOFF_IN_TM
6de20863 301 return (int)WX_TIMEZONE;
51485b76 302#endif // WX_GMTOFF_IN_TM/!WX_GMTOFF_IN_TM
fcc3d7cb
VZ
303}
304
e6ec579c 305// return the integral part of the JDN for the midnight of the given date (to
1ef54dcf
VZ
306// get the real JDN you need to add 0.5, this is, in fact, JDN of the
307// noon of the previous day)
e6ec579c
VZ
308static long GetTruncatedJDN(wxDateTime::wxDateTime_t day,
309 wxDateTime::Month mon,
310 int year)
311{
1ef54dcf
VZ
312 // CREDIT: code below is by Scott E. Lee (but bugs are mine)
313
314 // check the date validity
315 wxASSERT_MSG(
316 (year > JDN_0_YEAR) ||
317 ((year == JDN_0_YEAR) && (mon > JDN_0_MONTH)) ||
318 ((year == JDN_0_YEAR) && (mon == JDN_0_MONTH) && (day >= JDN_0_DAY)),
319 _T("date out of range - can't convert to JDN")
320 );
321
322 // make the year positive to avoid problems with negative numbers division
323 year += 4800;
324
325 // months are counted from March here
326 int month;
327 if ( mon >= wxDateTime::Mar )
e6ec579c 328 {
1ef54dcf 329 month = mon - 2;
e6ec579c 330 }
1ef54dcf 331 else
e6ec579c 332 {
1ef54dcf
VZ
333 month = mon + 10;
334 year--;
335 }
e6ec579c 336
1ef54dcf
VZ
337 // now we can simply add all the contributions together
338 return ((year / 100) * DAYS_PER_400_YEARS) / 4
339 + ((year % 100) * DAYS_PER_4_YEARS) / 4
340 + (month * DAYS_PER_5_MONTHS + 2) / 5
341 + day
342 - JDN_OFFSET;
e6ec579c
VZ
343}
344
2423ef22 345// this function is a wrapper around strftime(3) adding error checking
b76b015e
VZ
346static wxString CallStrftime(const wxChar *format, const tm* tm)
347{
68ee7c47 348 wxChar buf[4096];
b76b015e
VZ
349 if ( !wxStrftime(buf, WXSIZEOF(buf), format, tm) )
350 {
68ee7c47 351 // buffer is too small?
b76b015e
VZ
352 wxFAIL_MSG(_T("strftime() failed"));
353 }
354
355 return wxString(buf);
356}
357
2423ef22
VZ
358#ifdef HAVE_STRPTIME
359
e2620fda
VZ
360// glibc2 doesn't define this in the headers unless _XOPEN_SOURCE is defined
361// which, unfortunately, wreaks havoc elsewhere
362#if defined(__GLIBC__) && (__GLIBC__ == 2)
363 extern "C" char *strptime(const char *, const char *, struct tm *);
364#endif
365
2423ef22
VZ
366// Unicode-friendly strptime() wrapper
367static const wxChar *
368CallStrptime(const wxChar *input, const char *fmt, tm *tm)
369{
370 // the problem here is that strptime() returns pointer into the string we
371 // passed to it while we're really interested in the pointer into the
372 // original, Unicode, string so we try to transform the pointer back
373#if wxUSE_UNICODE
374 wxCharBuffer inputMB(wxConvertWX2MB(input));
375#else // ASCII
376 const char * const inputMB = input;
377#endif // Unicode/Ascii
378
379 const char *result = strptime(inputMB, fmt, tm);
380 if ( !result )
381 return NULL;
382
383#if wxUSE_UNICODE
384 // FIXME: this is wrong in presence of surrogates &c
385 return input + (result - inputMB.data());
386#else // ASCII
387 return result;
388#endif // Unicode/Ascii
389}
390
391#endif // HAVE_STRPTIME
392
2f02cb89
VZ
393// if year and/or month have invalid values, replace them with the current ones
394static void ReplaceDefaultYearMonthWithCurrent(int *year,
395 wxDateTime::Month *month)
396{
397 struct tm *tmNow = NULL;
398
399 if ( *year == wxDateTime::Inv_Year )
400 {
401 tmNow = wxDateTime::GetTmNow();
402
403 *year = 1900 + tmNow->tm_year;
404 }
405
406 if ( *month == wxDateTime::Inv_Month )
407 {
408 if ( !tmNow )
409 tmNow = wxDateTime::GetTmNow();
410
411 *month = (wxDateTime::Month)tmNow->tm_mon;
412 }
413}
414
f0f951fa
VZ
415// fll the struct tm with default values
416static void InitTm(struct tm& tm)
417{
418 // struct tm may have etxra fields (undocumented and with unportable
419 // names) which, nevertheless, must be set to 0
420 memset(&tm, 0, sizeof(struct tm));
421
422 tm.tm_mday = 1; // mday 0 is invalid
423 tm.tm_year = 76; // any valid year
424 tm.tm_isdst = -1; // auto determine
425}
426
cd0b1709
VZ
427// parsing helpers
428// ---------------
429
430// return the month if the string is a month name or Inv_Month otherwise
f0f951fa 431static wxDateTime::Month GetMonthFromName(const wxString& name, int flags)
cd0b1709
VZ
432{
433 wxDateTime::Month mon;
434 for ( mon = wxDateTime::Jan; mon < wxDateTime::Inv_Month; wxNextMonth(mon) )
435 {
f0f951fa
VZ
436 // case-insensitive comparison either one of or with both abbreviated
437 // and not versions
438 if ( flags & wxDateTime::Name_Full )
cd0b1709 439 {
f0f951fa
VZ
440 if ( name.CmpNoCase(wxDateTime::
441 GetMonthName(mon, wxDateTime::Name_Full)) == 0 )
442 {
443 break;
444 }
445 }
446
447 if ( flags & wxDateTime::Name_Abbr )
448 {
449 if ( name.CmpNoCase(wxDateTime::
450 GetMonthName(mon, wxDateTime::Name_Abbr)) == 0 )
451 {
452 break;
453 }
cd0b1709
VZ
454 }
455 }
456
457 return mon;
458}
459
460// return the weekday if the string is a weekday name or Inv_WeekDay otherwise
f0f951fa 461static wxDateTime::WeekDay GetWeekDayFromName(const wxString& name, int flags)
cd0b1709
VZ
462{
463 wxDateTime::WeekDay wd;
464 for ( wd = wxDateTime::Sun; wd < wxDateTime::Inv_WeekDay; wxNextWDay(wd) )
465 {
f0f951fa
VZ
466 // case-insensitive comparison either one of or with both abbreviated
467 // and not versions
468 if ( flags & wxDateTime::Name_Full )
cd0b1709 469 {
f0f951fa
VZ
470 if ( name.CmpNoCase(wxDateTime::
471 GetWeekDayName(wd, wxDateTime::Name_Full)) == 0 )
472 {
473 break;
474 }
475 }
476
477 if ( flags & wxDateTime::Name_Abbr )
478 {
479 if ( name.CmpNoCase(wxDateTime::
480 GetWeekDayName(wd, wxDateTime::Name_Abbr)) == 0 )
481 {
482 break;
483 }
cd0b1709
VZ
484 }
485 }
486
487 return wd;
488}
489
f6bcfd97
BP
490// scans all digits (but no more than len) and returns the resulting number
491static bool GetNumericToken(size_t len, const wxChar*& p, unsigned long *number)
f0f951fa 492{
f6bcfd97 493 size_t n = 1;
f0f951fa
VZ
494 wxString s;
495 while ( wxIsdigit(*p) )
496 {
497 s += *p++;
f6bcfd97
BP
498
499 if ( len && ++n > len )
500 break;
f0f951fa
VZ
501 }
502
bb8a8478 503 return !s.empty() && s.ToULong(number);
f0f951fa
VZ
504}
505
506// scans all alphabetic characters and returns the resulting string
507static wxString GetAlphaToken(const wxChar*& p)
508{
509 wxString s;
510 while ( wxIsalpha(*p) )
511 {
512 s += *p++;
513 }
514
515 return s;
516}
517
0979c962
VZ
518// ============================================================================
519// implementation of wxDateTime
520// ============================================================================
521
b76b015e
VZ
522// ----------------------------------------------------------------------------
523// struct Tm
524// ----------------------------------------------------------------------------
525
526wxDateTime::Tm::Tm()
527{
528 year = (wxDateTime_t)wxDateTime::Inv_Year;
529 mon = wxDateTime::Inv_Month;
530 mday = 0;
e6ec579c 531 hour = min = sec = msec = 0;
b76b015e
VZ
532 wday = wxDateTime::Inv_WeekDay;
533}
534
299fcbfe
VZ
535wxDateTime::Tm::Tm(const struct tm& tm, const TimeZone& tz)
536 : m_tz(tz)
b76b015e 537{
e6ec579c 538 msec = 0;
907173e5
WS
539 sec = (wxDateTime::wxDateTime_t)tm.tm_sec;
540 min = (wxDateTime::wxDateTime_t)tm.tm_min;
541 hour = (wxDateTime::wxDateTime_t)tm.tm_hour;
542 mday = (wxDateTime::wxDateTime_t)tm.tm_mday;
fcc3d7cb 543 mon = (wxDateTime::Month)tm.tm_mon;
b76b015e 544 year = 1900 + tm.tm_year;
bb8a8478
WS
545 wday = (wxDateTime::wxDateTime_t)tm.tm_wday;
546 yday = (wxDateTime::wxDateTime_t)tm.tm_yday;
b76b015e
VZ
547}
548
549bool wxDateTime::Tm::IsValid() const
550{
551 // we allow for the leap seconds, although we don't use them (yet)
fcc3d7cb 552 return (year != wxDateTime::Inv_Year) && (mon != wxDateTime::Inv_Month) &&
c5a1681b 553 (mday <= GetNumOfDaysInMonth(year, mon)) &&
e6ec579c 554 (hour < 24) && (min < 60) && (sec < 62) && (msec < 1000);
b76b015e
VZ
555}
556
557void wxDateTime::Tm::ComputeWeekDay()
558{
c5a1681b
VZ
559 // compute the week day from day/month/year: we use the dumbest algorithm
560 // possible: just compute our JDN and then use the (simple to derive)
561 // formula: weekday = (JDN + 1.5) % 7
bb8a8478 562 wday = (wxDateTime::wxDateTime_t)((wxDateTime::WeekDay)(GetTruncatedJDN(mday, mon, year) + 2) % 7);
b76b015e
VZ
563}
564
e6ec579c 565void wxDateTime::Tm::AddMonths(int monDiff)
fcc3d7cb
VZ
566{
567 // normalize the months field
568 while ( monDiff < -mon )
569 {
570 year--;
571
572 monDiff += MONTHS_IN_YEAR;
573 }
574
2ef31e80 575 while ( monDiff + mon >= MONTHS_IN_YEAR )
fcc3d7cb
VZ
576 {
577 year++;
239446b4
VZ
578
579 monDiff -= MONTHS_IN_YEAR;
fcc3d7cb
VZ
580 }
581
582 mon = (wxDateTime::Month)(mon + monDiff);
583
e6ec579c 584 wxASSERT_MSG( mon >= 0 && mon < MONTHS_IN_YEAR, _T("logic error") );
4f6aed9c
VZ
585
586 // NB: we don't check here that the resulting date is valid, this function
587 // is private and the caller must check it if needed
fcc3d7cb
VZ
588}
589
e6ec579c 590void wxDateTime::Tm::AddDays(int dayDiff)
fcc3d7cb
VZ
591{
592 // normalize the days field
9d9b7755 593 while ( dayDiff + mday < 1 )
fcc3d7cb
VZ
594 {
595 AddMonths(-1);
596
9d9b7755 597 dayDiff += GetNumOfDaysInMonth(year, mon);
fcc3d7cb
VZ
598 }
599
907173e5 600 mday = (wxDateTime::wxDateTime_t)( mday + dayDiff );
fcc3d7cb
VZ
601 while ( mday > GetNumOfDaysInMonth(year, mon) )
602 {
603 mday -= GetNumOfDaysInMonth(year, mon);
604
605 AddMonths(1);
606 }
607
608 wxASSERT_MSG( mday > 0 && mday <= GetNumOfDaysInMonth(year, mon),
609 _T("logic error") );
610}
611
612// ----------------------------------------------------------------------------
613// class TimeZone
614// ----------------------------------------------------------------------------
615
616wxDateTime::TimeZone::TimeZone(wxDateTime::TZ tz)
617{
618 switch ( tz )
619 {
620 case wxDateTime::Local:
299fcbfe
VZ
621 // get the offset from C RTL: it returns the difference GMT-local
622 // while we want to have the offset _from_ GMT, hence the '-'
623 m_offset = -GetTimeZone();
fcc3d7cb
VZ
624 break;
625
626 case wxDateTime::GMT_12:
627 case wxDateTime::GMT_11:
628 case wxDateTime::GMT_10:
629 case wxDateTime::GMT_9:
630 case wxDateTime::GMT_8:
631 case wxDateTime::GMT_7:
632 case wxDateTime::GMT_6:
633 case wxDateTime::GMT_5:
634 case wxDateTime::GMT_4:
635 case wxDateTime::GMT_3:
636 case wxDateTime::GMT_2:
637 case wxDateTime::GMT_1:
299fcbfe 638 m_offset = -3600*(wxDateTime::GMT0 - tz);
fcc3d7cb
VZ
639 break;
640
641 case wxDateTime::GMT0:
642 case wxDateTime::GMT1:
643 case wxDateTime::GMT2:
644 case wxDateTime::GMT3:
645 case wxDateTime::GMT4:
646 case wxDateTime::GMT5:
647 case wxDateTime::GMT6:
648 case wxDateTime::GMT7:
649 case wxDateTime::GMT8:
650 case wxDateTime::GMT9:
651 case wxDateTime::GMT10:
652 case wxDateTime::GMT11:
653 case wxDateTime::GMT12:
299fcbfe 654 m_offset = 3600*(tz - wxDateTime::GMT0);
fcc3d7cb
VZ
655 break;
656
657 case wxDateTime::A_CST:
658 // Central Standard Time in use in Australia = UTC + 9.5
cd0b1709 659 m_offset = 60l*(9*60 + 30);
fcc3d7cb
VZ
660 break;
661
662 default:
663 wxFAIL_MSG( _T("unknown time zone") );
664 }
665}
666
b76b015e
VZ
667// ----------------------------------------------------------------------------
668// static functions
669// ----------------------------------------------------------------------------
670
671/* static */
672bool wxDateTime::IsLeapYear(int year, wxDateTime::Calendar cal)
673{
2f02cb89
VZ
674 if ( year == Inv_Year )
675 year = GetCurrentYear();
676
b76b015e
VZ
677 if ( cal == Gregorian )
678 {
679 // in Gregorian calendar leap years are those divisible by 4 except
680 // those divisible by 100 unless they're also divisible by 400
681 // (in some countries, like Russia and Greece, additional corrections
682 // exist, but they won't manifest themselves until 2700)
683 return (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0));
684 }
685 else if ( cal == Julian )
686 {
687 // in Julian calendar the rule is simpler
688 return year % 4 == 0;
689 }
690 else
691 {
692 wxFAIL_MSG(_T("unknown calendar"));
693
68379eaf 694 return false;
b76b015e
VZ
695 }
696}
697
fcc3d7cb
VZ
698/* static */
699int wxDateTime::GetCentury(int year)
700{
701 return year > 0 ? year / 100 : year / 100 - 1;
702}
703
b76b015e
VZ
704/* static */
705int wxDateTime::ConvertYearToBC(int year)
706{
707 // year 0 is BC 1
708 return year > 0 ? year : year - 1;
709}
710
711/* static */
712int wxDateTime::GetCurrentYear(wxDateTime::Calendar cal)
713{
714 switch ( cal )
715 {
716 case Gregorian:
717 return Now().GetYear();
718
719 case Julian:
720 wxFAIL_MSG(_T("TODO"));
721 break;
722
723 default:
724 wxFAIL_MSG(_T("unsupported calendar"));
725 break;
726 }
727
728 return Inv_Year;
729}
730
731/* static */
732wxDateTime::Month wxDateTime::GetCurrentMonth(wxDateTime::Calendar cal)
733{
734 switch ( cal )
735 {
736 case Gregorian:
737 return Now().GetMonth();
b76b015e
VZ
738
739 case Julian:
740 wxFAIL_MSG(_T("TODO"));
741 break;
742
743 default:
744 wxFAIL_MSG(_T("unsupported calendar"));
745 break;
746 }
747
748 return Inv_Month;
749}
750
2f02cb89
VZ
751/* static */
752wxDateTime::wxDateTime_t wxDateTime::GetNumberOfDays(int year, Calendar cal)
753{
754 if ( year == Inv_Year )
755 {
756 // take the current year if none given
757 year = GetCurrentYear();
758 }
759
760 switch ( cal )
761 {
762 case Gregorian:
763 case Julian:
764 return IsLeapYear(year) ? 366 : 365;
2f02cb89
VZ
765
766 default:
767 wxFAIL_MSG(_T("unsupported calendar"));
768 break;
769 }
770
771 return 0;
772}
773
b76b015e
VZ
774/* static */
775wxDateTime::wxDateTime_t wxDateTime::GetNumberOfDays(wxDateTime::Month month,
776 int year,
777 wxDateTime::Calendar cal)
778{
fcc3d7cb 779 wxCHECK_MSG( month < MONTHS_IN_YEAR, 0, _T("invalid month") );
b76b015e
VZ
780
781 if ( cal == Gregorian || cal == Julian )
782 {
783 if ( year == Inv_Year )
784 {
785 // take the current year if none given
786 year = GetCurrentYear();
787 }
788
fcc3d7cb 789 return GetNumOfDaysInMonth(year, month);
b76b015e
VZ
790 }
791 else
792 {
793 wxFAIL_MSG(_T("unsupported calendar"));
794
795 return 0;
796 }
797}
798
799/* static */
f0f951fa
VZ
800wxString wxDateTime::GetMonthName(wxDateTime::Month month,
801 wxDateTime::NameFlags flags)
b76b015e
VZ
802{
803 wxCHECK_MSG( month != Inv_Month, _T(""), _T("invalid month") );
804
68ee7c47
VZ
805 // notice that we must set all the fields to avoid confusing libc (GNU one
806 // gets confused to a crash if we don't do this)
cd0b1709 807 tm tm;
f0f951fa 808 InitTm(tm);
cd0b1709 809 tm.tm_mon = month;
b76b015e 810
f0f951fa 811 return CallStrftime(flags == Name_Abbr ? _T("%b") : _T("%B"), &tm);
b76b015e
VZ
812}
813
814/* static */
f0f951fa
VZ
815wxString wxDateTime::GetWeekDayName(wxDateTime::WeekDay wday,
816 wxDateTime::NameFlags flags)
b76b015e
VZ
817{
818 wxCHECK_MSG( wday != Inv_WeekDay, _T(""), _T("invalid weekday") );
819
2c622d71
VZ
820 // take some arbitrary Sunday (but notice that the day should be such that
821 // after adding wday to it below we still have a valid date, e.g. don't
822 // take 28 here!)
f0f951fa
VZ
823 tm tm;
824 InitTm(tm);
2c622d71 825 tm.tm_mday = 21;
f0f951fa
VZ
826 tm.tm_mon = Nov;
827 tm.tm_year = 99;
b76b015e 828
1ef54dcf 829 // and offset it by the number of days needed to get the correct wday
b76b015e
VZ
830 tm.tm_mday += wday;
831
c5a1681b
VZ
832 // call mktime() to normalize it...
833 (void)mktime(&tm);
834
835 // ... and call strftime()
f0f951fa
VZ
836 return CallStrftime(flags == Name_Abbr ? _T("%a") : _T("%A"), &tm);
837}
838
839/* static */
840void wxDateTime::GetAmPmStrings(wxString *am, wxString *pm)
841{
842 tm tm;
843 InitTm(tm);
653567bb 844 wxChar buffer[64];
0308fd61
JS
845 // @Note: Do not call 'CallStrftime' here! CallStrftime checks the return code
846 // and causes an assertion failed if the buffer is to small (which is good) - OR -
847 // if strftime does not return anything because the format string is invalid - OR -
848 // if there are no 'am' / 'pm' tokens defined for the current locale (which is not good).
849 // wxDateTime::ParseTime will try several different formats to parse the time.
850 // As a result, GetAmPmStrings might get called, even if the current locale
851 // does not define any 'am' / 'pm' tokens. In this case, wxStrftime would
852 // assert, even though it is a perfectly legal use.
f0f951fa
VZ
853 if ( am )
854 {
0308fd61
JS
855 if (wxStrftime(buffer, sizeof buffer, _T("%p"), &tm) > 0)
856 *am = wxString(buffer);
857 else
858 *am = wxString();
f0f951fa
VZ
859 }
860 if ( pm )
861 {
862 tm.tm_hour = 13;
0308fd61
JS
863 if (wxStrftime(buffer, sizeof buffer, _T("%p"), &tm) > 0)
864 *pm = wxString(buffer);
865 else
866 *pm = wxString();
f0f951fa 867 }
b76b015e
VZ
868}
869
239446b4
VZ
870// ----------------------------------------------------------------------------
871// Country stuff: date calculations depend on the country (DST, work days,
872// ...), so we need to know which rules to follow.
873// ----------------------------------------------------------------------------
874
875/* static */
876wxDateTime::Country wxDateTime::GetCountry()
877{
f0f951fa
VZ
878 // TODO use LOCALE_ICOUNTRY setting under Win32
879
239446b4
VZ
880 if ( ms_country == Country_Unknown )
881 {
882 // try to guess from the time zone name
883 time_t t = time(NULL);
884 struct tm *tm = localtime(&t);
885
886 wxString tz = CallStrftime(_T("%Z"), tm);
887 if ( tz == _T("WET") || tz == _T("WEST") )
888 {
889 ms_country = UK;
890 }
891 else if ( tz == _T("CET") || tz == _T("CEST") )
892 {
893 ms_country = Country_EEC;
894 }
895 else if ( tz == _T("MSK") || tz == _T("MSD") )
896 {
897 ms_country = Russia;
898 }
899 else if ( tz == _T("AST") || tz == _T("ADT") ||
900 tz == _T("EST") || tz == _T("EDT") ||
901 tz == _T("CST") || tz == _T("CDT") ||
902 tz == _T("MST") || tz == _T("MDT") ||
903 tz == _T("PST") || tz == _T("PDT") )
904 {
905 ms_country = USA;
906 }
907 else
908 {
909 // well, choose a default one
910 ms_country = USA;
911 }
912 }
913
914 return ms_country;
915}
916
917/* static */
918void wxDateTime::SetCountry(wxDateTime::Country country)
919{
920 ms_country = country;
921}
922
923/* static */
924bool wxDateTime::IsWestEuropeanCountry(Country country)
925{
926 if ( country == Country_Default )
927 {
928 country = GetCountry();
929 }
930
931 return (Country_WesternEurope_Start <= country) &&
932 (country <= Country_WesternEurope_End);
933}
934
935// ----------------------------------------------------------------------------
936// DST calculations: we use 3 different rules for the West European countries,
937// USA and for the rest of the world. This is undoubtedly false for many
938// countries, but I lack the necessary info (and the time to gather it),
939// please add the other rules here!
940// ----------------------------------------------------------------------------
941
942/* static */
943bool wxDateTime::IsDSTApplicable(int year, Country country)
944{
945 if ( year == Inv_Year )
946 {
947 // take the current year if none given
948 year = GetCurrentYear();
949 }
950
951 if ( country == Country_Default )
952 {
953 country = GetCountry();
954 }
955
956 switch ( country )
957 {
958 case USA:
959 case UK:
960 // DST was first observed in the US and UK during WWI, reused
961 // during WWII and used again since 1966
962 return year >= 1966 ||
963 (year >= 1942 && year <= 1945) ||
964 (year == 1918 || year == 1919);
965
966 default:
967 // assume that it started after WWII
968 return year > 1950;
969 }
970}
971
972/* static */
973wxDateTime wxDateTime::GetBeginDST(int year, Country country)
974{
975 if ( year == Inv_Year )
976 {
977 // take the current year if none given
978 year = GetCurrentYear();
979 }
980
981 if ( country == Country_Default )
982 {
983 country = GetCountry();
984 }
985
986 if ( !IsDSTApplicable(year, country) )
987 {
2ef31e80 988 return wxInvalidDateTime;
239446b4
VZ
989 }
990
991 wxDateTime dt;
992
993 if ( IsWestEuropeanCountry(country) || (country == Russia) )
994 {
995 // DST begins at 1 a.m. GMT on the last Sunday of March
996 if ( !dt.SetToLastWeekDay(Sun, Mar, year) )
997 {
998 // weird...
999 wxFAIL_MSG( _T("no last Sunday in March?") );
1000 }
1001
1002 dt += wxTimeSpan::Hours(1);
1003
41acf5c0 1004 // disable DST tests because it could result in an infinite recursion!
68379eaf 1005 dt.MakeGMT(true);
239446b4
VZ
1006 }
1007 else switch ( country )
1008 {
1009 case USA:
1010 switch ( year )
1011 {
1012 case 1918:
1013 case 1919:
1014 // don't know for sure - assume it was in effect all year
1015
1016 case 1943:
1017 case 1944:
1018 case 1945:
1019 dt.Set(1, Jan, year);
1020 break;
1021
1022 case 1942:
1023 // DST was installed Feb 2, 1942 by the Congress
1024 dt.Set(2, Feb, year);
1025 break;
1026
1027 // Oil embargo changed the DST period in the US
1028 case 1974:
1029 dt.Set(6, Jan, 1974);
1030 break;
1031
1032 case 1975:
1033 dt.Set(23, Feb, 1975);
1034 break;
1035
1036 default:
1037 // before 1986, DST begun on the last Sunday of April, but
1038 // in 1986 Reagan changed it to begin at 2 a.m. of the
1039 // first Sunday in April
1040 if ( year < 1986 )
1041 {
1042 if ( !dt.SetToLastWeekDay(Sun, Apr, year) )
1043 {
1044 // weird...
1045 wxFAIL_MSG( _T("no first Sunday in April?") );
1046 }
1047 }
1048 else
1049 {
1050 if ( !dt.SetToWeekDay(Sun, 1, Apr, year) )
1051 {
1052 // weird...
1053 wxFAIL_MSG( _T("no first Sunday in April?") );
1054 }
1055 }
1056
1057 dt += wxTimeSpan::Hours(2);
1058
1059 // TODO what about timezone??
1060 }
1061
1062 break;
1063
1064 default:
1065 // assume Mar 30 as the start of the DST for the rest of the world
1066 // - totally bogus, of course
1067 dt.Set(30, Mar, year);
1068 }
1069
1070 return dt;
1071}
1072
1073/* static */
1074wxDateTime wxDateTime::GetEndDST(int year, Country country)
1075{
1076 if ( year == Inv_Year )
1077 {
1078 // take the current year if none given
1079 year = GetCurrentYear();
1080 }
1081
1082 if ( country == Country_Default )
1083 {
1084 country = GetCountry();
1085 }
1086
1087 if ( !IsDSTApplicable(year, country) )
1088 {
2ef31e80 1089 return wxInvalidDateTime;
239446b4
VZ
1090 }
1091
1092 wxDateTime dt;
1093
1094 if ( IsWestEuropeanCountry(country) || (country == Russia) )
1095 {
5f287370 1096 // DST ends at 1 a.m. GMT on the last Sunday of October
239446b4
VZ
1097 if ( !dt.SetToLastWeekDay(Sun, Oct, year) )
1098 {
1099 // weirder and weirder...
1100 wxFAIL_MSG( _T("no last Sunday in October?") );
1101 }
1102
1103 dt += wxTimeSpan::Hours(1);
1104
41acf5c0 1105 // disable DST tests because it could result in an infinite recursion!
68379eaf 1106 dt.MakeGMT(true);
239446b4
VZ
1107 }
1108 else switch ( country )
1109 {
1110 case USA:
1111 switch ( year )
1112 {
1113 case 1918:
1114 case 1919:
1115 // don't know for sure - assume it was in effect all year
1116
1117 case 1943:
1118 case 1944:
1119 dt.Set(31, Dec, year);
1120 break;
1121
1122 case 1945:
1123 // the time was reset after the end of the WWII
1124 dt.Set(30, Sep, year);
1125 break;
1126
1127 default:
5f287370 1128 // DST ends at 2 a.m. on the last Sunday of October
239446b4
VZ
1129 if ( !dt.SetToLastWeekDay(Sun, Oct, year) )
1130 {
1131 // weirder and weirder...
1132 wxFAIL_MSG( _T("no last Sunday in October?") );
1133 }
1134
1135 dt += wxTimeSpan::Hours(2);
1136
1137 // TODO what about timezone??
1138 }
1139 break;
1140
1141 default:
1142 // assume October 26th as the end of the DST - totally bogus too
1143 dt.Set(26, Oct, year);
1144 }
1145
1146 return dt;
1147}
1148
0979c962
VZ
1149// ----------------------------------------------------------------------------
1150// constructors and assignment operators
1151// ----------------------------------------------------------------------------
1152
f6bcfd97
BP
1153// return the current time with ms precision
1154/* static */ wxDateTime wxDateTime::UNow()
1155{
1156 return wxDateTime(wxGetLocalTimeMillis());
1157}
1158
299fcbfe
VZ
1159// the values in the tm structure contain the local time
1160wxDateTime& wxDateTime::Set(const struct tm& tm)
0979c962 1161{
299fcbfe 1162 struct tm tm2(tm);
b76b015e 1163 time_t timet = mktime(&tm2);
1ef54dcf 1164
4afd7529 1165 if ( timet == (time_t)-1 )
0979c962 1166 {
4afd7529
VZ
1167 // mktime() rather unintuitively fails for Jan 1, 1970 if the hour is
1168 // less than timezone - try to make it work for this case
1169 if ( tm2.tm_year == 70 && tm2.tm_mon == 0 && tm2.tm_mday == 1 )
1170 {
3d78a532
RD
1171 return Set((time_t)(
1172 GetTimeZone() +
1173 tm2.tm_hour * MIN_PER_HOUR * SEC_PER_MIN +
1174 tm2.tm_min * SEC_PER_MIN +
1175 tm2.tm_sec));
4afd7529
VZ
1176 }
1177
1178 wxFAIL_MSG( _T("mktime() failed") );
0979c962 1179
384223b3
VZ
1180 *this = wxInvalidDateTime;
1181
1182 return *this;
0979c962
VZ
1183 }
1184 else
1185 {
1186 return Set(timet);
1187 }
1188}
1189
1190wxDateTime& wxDateTime::Set(wxDateTime_t hour,
1191 wxDateTime_t minute,
1192 wxDateTime_t second,
1193 wxDateTime_t millisec)
1194{
1195 // we allow seconds to be 61 to account for the leap seconds, even if we
1196 // don't use them really
384223b3
VZ
1197 wxDATETIME_CHECK( hour < 24 &&
1198 second < 62 &&
1199 minute < 60 &&
1200 millisec < 1000,
1201 _T("Invalid time in wxDateTime::Set()") );
0979c962
VZ
1202
1203 // get the current date from system
9d9b7755 1204 struct tm *tm = GetTmNow();
299fcbfe 1205
384223b3 1206 wxDATETIME_CHECK( tm, _T("localtime() failed") );
0979c962
VZ
1207
1208 // adjust the time
1209 tm->tm_hour = hour;
1210 tm->tm_min = minute;
1211 tm->tm_sec = second;
1212
b76b015e 1213 (void)Set(*tm);
0979c962
VZ
1214
1215 // and finally adjust milliseconds
1216 return SetMillisecond(millisec);
1217}
1218
1219wxDateTime& wxDateTime::Set(wxDateTime_t day,
1220 Month month,
1221 int year,
1222 wxDateTime_t hour,
1223 wxDateTime_t minute,
1224 wxDateTime_t second,
1225 wxDateTime_t millisec)
1226{
384223b3
VZ
1227 wxDATETIME_CHECK( hour < 24 &&
1228 second < 62 &&
1229 minute < 60 &&
1230 millisec < 1000,
1231 _T("Invalid time in wxDateTime::Set()") );
0979c962 1232
2f02cb89 1233 ReplaceDefaultYearMonthWithCurrent(&year, &month);
0979c962 1234
384223b3
VZ
1235 wxDATETIME_CHECK( (0 < day) && (day <= GetNumberOfDays(month, year)),
1236 _T("Invalid date in wxDateTime::Set()") );
0979c962
VZ
1237
1238 // the range of time_t type (inclusive)
1239 static const int yearMinInRange = 1970;
1240 static const int yearMaxInRange = 2037;
1241
1242 // test only the year instead of testing for the exact end of the Unix
1243 // time_t range - it doesn't bring anything to do more precise checks
2f02cb89 1244 if ( year >= yearMinInRange && year <= yearMaxInRange )
0979c962
VZ
1245 {
1246 // use the standard library version if the date is in range - this is
b76b015e 1247 // probably more efficient than our code
0979c962 1248 struct tm tm;
b76b015e 1249 tm.tm_year = year - 1900;
0979c962
VZ
1250 tm.tm_mon = month;
1251 tm.tm_mday = day;
1252 tm.tm_hour = hour;
1253 tm.tm_min = minute;
1254 tm.tm_sec = second;
299fcbfe 1255 tm.tm_isdst = -1; // mktime() will guess it
0979c962
VZ
1256
1257 (void)Set(tm);
1258
1259 // and finally adjust milliseconds
3d78a532
RD
1260 if (IsValid())
1261 SetMillisecond(millisec);
1262
1263 return *this;
0979c962
VZ
1264 }
1265 else
1266 {
1267 // do time calculations ourselves: we want to calculate the number of
fcc3d7cb 1268 // milliseconds between the given date and the epoch
e6ec579c
VZ
1269
1270 // get the JDN for the midnight of this day
1271 m_time = GetTruncatedJDN(day, month, year);
1272 m_time -= EPOCH_JDN;
1273 m_time *= SECONDS_PER_DAY * TIME_T_FACTOR;
1274
299fcbfe
VZ
1275 // JDN corresponds to GMT, we take localtime
1276 Add(wxTimeSpan(hour, minute, second + GetTimeZone(), millisec));
b76b015e
VZ
1277 }
1278
1279 return *this;
1280}
1281
e6ec579c
VZ
1282wxDateTime& wxDateTime::Set(double jdn)
1283{
1ef54dcf
VZ
1284 // so that m_time will be 0 for the midnight of Jan 1, 1970 which is jdn
1285 // EPOCH_JDN + 0.5
1286 jdn -= EPOCH_JDN + 0.5;
1287
cd0b1709
VZ
1288 jdn *= MILLISECONDS_PER_DAY;
1289
8cc00d5f
VZ
1290 // JDNs always suppose an UTC date, so bring it back to local time zone
1291 // (also see GetJulianDayNumber() implementation)
1292 long tzDiff = GetTimeZone();
1293 if ( IsDST() == 1 )
1294 {
1295 // FIXME: again, we suppose that DST is always one hour
1296 tzDiff -= 3600;
1297 }
1298
1299 jdn += tzDiff*1000; // tzDiff is in seconds
1300
9c2882d9 1301 m_time.Assign(jdn);
e6ec579c
VZ
1302
1303 return *this;
1304}
1305
9d9b7755
VZ
1306wxDateTime& wxDateTime::ResetTime()
1307{
1308 Tm tm = GetTm();
1309
1310 if ( tm.hour || tm.min || tm.sec || tm.msec )
1311 {
1312 tm.msec =
1313 tm.sec =
1314 tm.min =
1315 tm.hour = 0;
1316
1317 Set(tm);
1318 }
1319
1320 return *this;
1321}
1322
2b5f62a0
VZ
1323// ----------------------------------------------------------------------------
1324// DOS Date and Time Format functions
1325// ----------------------------------------------------------------------------
1326// the dos date and time value is an unsigned 32 bit value in the format:
1327// YYYYYYYMMMMDDDDDhhhhhmmmmmmsssss
1328//
1329// Y = year offset from 1980 (0-127)
1330// M = month (1-12)
1331// D = day of month (1-31)
1332// h = hour (0-23)
1333// m = minute (0-59)
1334// s = bisecond (0-29) each bisecond indicates two seconds
1335// ----------------------------------------------------------------------------
1336
1337wxDateTime& wxDateTime::SetFromDOS(unsigned long ddt)
1338{
1339 struct tm tm;
4739346e 1340 InitTm(tm);
2b5f62a0
VZ
1341
1342 long year = ddt & 0xFE000000;
1343 year >>= 25;
1344 year += 80;
1345 tm.tm_year = year;
1346
1347 long month = ddt & 0x1E00000;
1348 month >>= 21;
1349 month -= 1;
1350 tm.tm_mon = month;
1351
1352 long day = ddt & 0x1F0000;
1353 day >>= 16;
1354 tm.tm_mday = day;
1355
1356 long hour = ddt & 0xF800;
1357 hour >>= 11;
1358 tm.tm_hour = hour;
1359
1360 long minute = ddt & 0x7E0;
1361 minute >>= 5;
1362 tm.tm_min = minute;
1363
1364 long second = ddt & 0x1F;
1365 tm.tm_sec = second * 2;
1366
1367 return Set(mktime(&tm));
1368}
1369
1370unsigned long wxDateTime::GetAsDOS() const
1371{
1372 unsigned long ddt;
1373 time_t ticks = GetTicks();
1374 struct tm *tm = localtime(&ticks);
1375
1376 long year = tm->tm_year;
1377 year -= 80;
1378 year <<= 25;
1379
1380 long month = tm->tm_mon;
1381 month += 1;
1382 month <<= 21;
1383
1384 long day = tm->tm_mday;
1385 day <<= 16;
1386
1387 long hour = tm->tm_hour;
1388 hour <<= 11;
1389
1390 long minute = tm->tm_min;
1391 minute <<= 5;
1392
1393 long second = tm->tm_sec;
1394 second /= 2;
1395
1396 ddt = year | month | day | hour | minute | second;
1397 return ddt;
1398}
1399
b76b015e
VZ
1400// ----------------------------------------------------------------------------
1401// time_t <-> broken down time conversions
1402// ----------------------------------------------------------------------------
1403
299fcbfe 1404wxDateTime::Tm wxDateTime::GetTm(const TimeZone& tz) const
b76b015e
VZ
1405{
1406 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1407
1408 time_t time = GetTicks();
1409 if ( time != (time_t)-1 )
1410 {
1411 // use C RTL functions
299fcbfe
VZ
1412 tm *tm;
1413 if ( tz.GetOffset() == -GetTimeZone() )
1414 {
1415 // we are working with local time
1416 tm = localtime(&time);
c5a1681b
VZ
1417
1418 // should never happen
f6bcfd97 1419 wxCHECK_MSG( tm, Tm(), _T("localtime() failed") );
299fcbfe
VZ
1420 }
1421 else
1422 {
13111b2a 1423 time += (time_t)tz.GetOffset();
33ac7e6f 1424#if defined(__VMS__) || defined(__WATCOMC__) // time is unsigned so avoid warning
13111b2a 1425 int time2 = (int) time;
9d9b7755 1426 if ( time2 >= 0 )
fb10f04c 1427#else
9d9b7755 1428 if ( time >= 0 )
fb10f04c 1429#endif
c5a1681b
VZ
1430 {
1431 tm = gmtime(&time);
b76b015e 1432
c5a1681b
VZ
1433 // should never happen
1434 wxCHECK_MSG( tm, Tm(), _T("gmtime() failed") );
1435 }
1436 else
1437 {
1438 tm = (struct tm *)NULL;
1439 }
1440 }
b76b015e 1441
c5a1681b
VZ
1442 if ( tm )
1443 {
f6bcfd97
BP
1444 // adjust the milliseconds
1445 Tm tm2(*tm, tz);
1446 long timeOnly = (m_time % MILLISECONDS_PER_DAY).ToLong();
1447 tm2.msec = (wxDateTime_t)(timeOnly % 1000);
1448 return tm2;
c5a1681b
VZ
1449 }
1450 //else: use generic code below
b76b015e 1451 }
e6ec579c 1452
c5a1681b
VZ
1453 // remember the time and do the calculations with the date only - this
1454 // eliminates rounding errors of the floating point arithmetics
299fcbfe 1455
c5a1681b 1456 wxLongLong timeMidnight = m_time + tz.GetOffset() * 1000;
1ef54dcf 1457
c5a1681b 1458 long timeOnly = (timeMidnight % MILLISECONDS_PER_DAY).ToLong();
1ef54dcf 1459
c5a1681b
VZ
1460 // we want to always have positive time and timeMidnight to be really
1461 // the midnight before it
1462 if ( timeOnly < 0 )
1463 {
1464 timeOnly = MILLISECONDS_PER_DAY + timeOnly;
1465 }
e6ec579c 1466
c5a1681b 1467 timeMidnight -= timeOnly;
1ef54dcf 1468
c5a1681b
VZ
1469 // calculate the Gregorian date from JDN for the midnight of our date:
1470 // this will yield day, month (in 1..12 range) and year
1ef54dcf 1471
c5a1681b
VZ
1472 // actually, this is the JDN for the noon of the previous day
1473 long jdn = (timeMidnight / MILLISECONDS_PER_DAY).ToLong() + EPOCH_JDN;
1ef54dcf 1474
c5a1681b 1475 // CREDIT: code below is by Scott E. Lee (but bugs are mine)
1ef54dcf 1476
c5a1681b 1477 wxASSERT_MSG( jdn > -2, _T("JDN out of range") );
1ef54dcf 1478
c5a1681b 1479 // calculate the century
479cd5de
VZ
1480 long temp = (jdn + JDN_OFFSET) * 4 - 1;
1481 long century = temp / DAYS_PER_400_YEARS;
1ef54dcf 1482
c5a1681b
VZ
1483 // then the year and day of year (1 <= dayOfYear <= 366)
1484 temp = ((temp % DAYS_PER_400_YEARS) / 4) * 4 + 3;
479cd5de
VZ
1485 long year = (century * 100) + (temp / DAYS_PER_4_YEARS);
1486 long dayOfYear = (temp % DAYS_PER_4_YEARS) / 4 + 1;
1ef54dcf 1487
c5a1681b
VZ
1488 // and finally the month and day of the month
1489 temp = dayOfYear * 5 - 3;
479cd5de
VZ
1490 long month = temp / DAYS_PER_5_MONTHS;
1491 long day = (temp % DAYS_PER_5_MONTHS) / 5 + 1;
c5a1681b
VZ
1492
1493 // month is counted from March - convert to normal
1494 if ( month < 10 )
1495 {
1496 month += 3;
1497 }
1498 else
1499 {
1500 year += 1;
1501 month -= 9;
1502 }
1ef54dcf 1503
c5a1681b
VZ
1504 // year is offset by 4800
1505 year -= 4800;
1ef54dcf 1506
c5a1681b
VZ
1507 // check that the algorithm gave us something reasonable
1508 wxASSERT_MSG( (0 < month) && (month <= 12), _T("invalid month") );
1509 wxASSERT_MSG( (1 <= day) && (day < 32), _T("invalid day") );
e6ec579c 1510
c5a1681b
VZ
1511 // construct Tm from these values
1512 Tm tm;
1513 tm.year = (int)year;
1514 tm.mon = (Month)(month - 1); // algorithm yields 1 for January, not 0
1515 tm.mday = (wxDateTime_t)day;
479cd5de 1516 tm.msec = (wxDateTime_t)(timeOnly % 1000);
c5a1681b
VZ
1517 timeOnly -= tm.msec;
1518 timeOnly /= 1000; // now we have time in seconds
e6ec579c 1519
479cd5de 1520 tm.sec = (wxDateTime_t)(timeOnly % 60);
c5a1681b
VZ
1521 timeOnly -= tm.sec;
1522 timeOnly /= 60; // now we have time in minutes
e6ec579c 1523
479cd5de 1524 tm.min = (wxDateTime_t)(timeOnly % 60);
c5a1681b 1525 timeOnly -= tm.min;
e6ec579c 1526
479cd5de 1527 tm.hour = (wxDateTime_t)(timeOnly / 60);
b76b015e 1528
c5a1681b 1529 return tm;
b76b015e
VZ
1530}
1531
1532wxDateTime& wxDateTime::SetYear(int year)
1533{
1534 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1535
1536 Tm tm(GetTm());
1537 tm.year = year;
1538 Set(tm);
1539
1540 return *this;
1541}
1542
1543wxDateTime& wxDateTime::SetMonth(Month month)
1544{
1545 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1546
1547 Tm tm(GetTm());
1548 tm.mon = month;
1549 Set(tm);
1550
1551 return *this;
1552}
1553
1554wxDateTime& wxDateTime::SetDay(wxDateTime_t mday)
1555{
1556 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1557
1558 Tm tm(GetTm());
1559 tm.mday = mday;
1560 Set(tm);
1561
1562 return *this;
1563}
1564
1565wxDateTime& wxDateTime::SetHour(wxDateTime_t hour)
1566{
1567 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1568
1569 Tm tm(GetTm());
1570 tm.hour = hour;
1571 Set(tm);
1572
1573 return *this;
1574}
1575
1576wxDateTime& wxDateTime::SetMinute(wxDateTime_t min)
1577{
1578 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1579
1580 Tm tm(GetTm());
1581 tm.min = min;
1582 Set(tm);
1583
1584 return *this;
1585}
1586
1587wxDateTime& wxDateTime::SetSecond(wxDateTime_t sec)
1588{
1589 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1590
1591 Tm tm(GetTm());
1592 tm.sec = sec;
1593 Set(tm);
0979c962
VZ
1594
1595 return *this;
1596}
b76b015e
VZ
1597
1598wxDateTime& wxDateTime::SetMillisecond(wxDateTime_t millisecond)
1599{
1600 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1601
1602 // we don't need to use GetTm() for this one
1603 m_time -= m_time % 1000l;
1604 m_time += millisecond;
1605
1606 return *this;
1607}
1608
1609// ----------------------------------------------------------------------------
1610// wxDateTime arithmetics
1611// ----------------------------------------------------------------------------
1612
1613wxDateTime& wxDateTime::Add(const wxDateSpan& diff)
1614{
1615 Tm tm(GetTm());
1616
1617 tm.year += diff.GetYears();
fcc3d7cb 1618 tm.AddMonths(diff.GetMonths());
4f6aed9c
VZ
1619
1620 // check that the resulting date is valid
1621 if ( tm.mday > GetNumOfDaysInMonth(tm.year, tm.mon) )
1622 {
1623 // We suppose that when adding one month to Jan 31 we want to get Feb
1624 // 28 (or 29), i.e. adding a month to the last day of the month should
1625 // give the last day of the next month which is quite logical.
1626 //
1627 // Unfortunately, there is no logic way to understand what should
1628 // Jan 30 + 1 month be - Feb 28 too or Feb 27 (assuming non leap year)?
1629 // We make it Feb 28 (last day too), but it is highly questionable.
1630 tm.mday = GetNumOfDaysInMonth(tm.year, tm.mon);
1631 }
1632
fcc3d7cb 1633 tm.AddDays(diff.GetTotalDays());
b76b015e
VZ
1634
1635 Set(tm);
1636
9d9b7755
VZ
1637 wxASSERT_MSG( IsSameTime(tm),
1638 _T("Add(wxDateSpan) shouldn't modify time") );
1639
b76b015e
VZ
1640 return *this;
1641}
1642
2f02cb89
VZ
1643// ----------------------------------------------------------------------------
1644// Weekday and monthday stuff
1645// ----------------------------------------------------------------------------
1646
4c27e2fa
VZ
1647// convert Sun, Mon, ..., Sat into 6, 0, ..., 5
1648static inline int ConvertWeekDayToMondayBase(int wd)
1649{
1650 return wd == wxDateTime::Sun ? 6 : wd - 1;
1651}
1652
1653/* static */
1654wxDateTime
1655wxDateTime::SetToWeekOfYear(int year, wxDateTime_t numWeek, WeekDay wd)
4f6aed9c 1656{
2b5f62a0
VZ
1657 wxASSERT_MSG( numWeek > 0,
1658 _T("invalid week number: weeks are counted from 1") );
1659
4c27e2fa
VZ
1660 // Jan 4 always lies in the 1st week of the year
1661 wxDateTime dt(4, Jan, year);
1662 dt.SetToWeekDayInSameWeek(wd);
1663 dt += wxDateSpan::Weeks(numWeek - 1);
1664
1665 return dt;
1666}
4f6aed9c 1667
4c27e2fa
VZ
1668// use a separate function to avoid warnings about using deprecated
1669// SetToTheWeek in GetWeek below
1670static wxDateTime
1671SetToTheWeek(int year,
1672 wxDateTime::wxDateTime_t numWeek,
1673 wxDateTime::WeekDay weekday,
1674 wxDateTime::WeekFlags flags)
1675{
4f6aed9c 1676 // Jan 4 always lies in the 1st week of the year
4c27e2fa
VZ
1677 wxDateTime dt(4, wxDateTime::Jan, year);
1678 dt.SetToWeekDayInSameWeek(weekday, flags);
1679 dt += wxDateSpan::Weeks(numWeek - 1);
4f6aed9c 1680
4c27e2fa
VZ
1681 return dt;
1682}
1683
1684bool wxDateTime::SetToTheWeek(wxDateTime_t numWeek,
1685 WeekDay weekday,
1686 WeekFlags flags)
1687{
1688 int year = GetYear();
1689 *this = ::SetToTheWeek(year, numWeek, weekday, flags);
4f6aed9c
VZ
1690 if ( GetYear() != year )
1691 {
1692 // oops... numWeek was too big
68379eaf 1693 return false;
4f6aed9c
VZ
1694 }
1695
68379eaf 1696 return true;
4f6aed9c
VZ
1697}
1698
4c27e2fa
VZ
1699wxDateTime wxDateTime::GetWeek(wxDateTime_t numWeek,
1700 WeekDay weekday,
1701 WeekFlags flags) const
1702{
1703 return ::SetToTheWeek(GetYear(), numWeek, weekday, flags);
1704}
1705
2f02cb89
VZ
1706wxDateTime& wxDateTime::SetToLastMonthDay(Month month,
1707 int year)
1708{
1709 // take the current month/year if none specified
1a8557b1
VZ
1710 if ( year == Inv_Year )
1711 year = GetYear();
1712 if ( month == Inv_Month )
1713 month = GetMonth();
2f02cb89 1714
fcc3d7cb 1715 return Set(GetNumOfDaysInMonth(year, month), month, year);
2f02cb89
VZ
1716}
1717
2b5f62a0 1718wxDateTime& wxDateTime::SetToWeekDayInSameWeek(WeekDay weekday, WeekFlags flags)
cd0b1709 1719{
384223b3 1720 wxDATETIME_CHECK( weekday != Inv_WeekDay, _T("invalid weekday") );
cd0b1709 1721
2b5f62a0 1722 int wdayThis = GetWeekDay();
cd0b1709
VZ
1723 if ( weekday == wdayThis )
1724 {
1725 // nothing to do
1726 return *this;
1727 }
2b5f62a0
VZ
1728
1729 if ( flags == Default_First )
1730 {
1731 flags = GetCountry() == USA ? Sunday_First : Monday_First;
1732 }
1733
1734 // the logic below based on comparing weekday and wdayThis works if Sun (0)
1735 // is the first day in the week, but breaks down for Monday_First case so
1736 // we adjust the week days in this case
1737 if( flags == Monday_First )
1738 {
1739 if ( wdayThis == Sun )
1740 wdayThis += 7;
1741 }
1742 //else: Sunday_First, nothing to do
1743
1744 // go forward or back in time to the day we want
1745 if ( weekday < wdayThis )
cd0b1709 1746 {
f6bcfd97 1747 return Subtract(wxDateSpan::Days(wdayThis - weekday));
cd0b1709
VZ
1748 }
1749 else // weekday > wdayThis
1750 {
4f6aed9c 1751 return Add(wxDateSpan::Days(weekday - wdayThis));
cd0b1709
VZ
1752 }
1753}
1754
1755wxDateTime& wxDateTime::SetToNextWeekDay(WeekDay weekday)
1756{
384223b3 1757 wxDATETIME_CHECK( weekday != Inv_WeekDay, _T("invalid weekday") );
cd0b1709
VZ
1758
1759 int diff;
1760 WeekDay wdayThis = GetWeekDay();
1761 if ( weekday == wdayThis )
1762 {
1763 // nothing to do
1764 return *this;
1765 }
1766 else if ( weekday < wdayThis )
1767 {
1768 // need to advance a week
1769 diff = 7 - (wdayThis - weekday);
1770 }
1771 else // weekday > wdayThis
1772 {
1773 diff = weekday - wdayThis;
1774 }
1775
4f6aed9c 1776 return Add(wxDateSpan::Days(diff));
cd0b1709
VZ
1777}
1778
1779wxDateTime& wxDateTime::SetToPrevWeekDay(WeekDay weekday)
1780{
384223b3 1781 wxDATETIME_CHECK( weekday != Inv_WeekDay, _T("invalid weekday") );
cd0b1709
VZ
1782
1783 int diff;
1784 WeekDay wdayThis = GetWeekDay();
1785 if ( weekday == wdayThis )
1786 {
1787 // nothing to do
1788 return *this;
1789 }
1790 else if ( weekday > wdayThis )
1791 {
1792 // need to go to previous week
1793 diff = 7 - (weekday - wdayThis);
1794 }
1795 else // weekday < wdayThis
1796 {
1797 diff = wdayThis - weekday;
1798 }
1799
f6bcfd97 1800 return Subtract(wxDateSpan::Days(diff));
cd0b1709
VZ
1801}
1802
2f02cb89
VZ
1803bool wxDateTime::SetToWeekDay(WeekDay weekday,
1804 int n,
1805 Month month,
1806 int year)
1807{
68379eaf 1808 wxCHECK_MSG( weekday != Inv_WeekDay, false, _T("invalid weekday") );
2f02cb89 1809
68379eaf 1810 // we don't check explicitly that -5 <= n <= 5 because we will return false
2f02cb89
VZ
1811 // anyhow in such case - but may be should still give an assert for it?
1812
1813 // take the current month/year if none specified
1814 ReplaceDefaultYearMonthWithCurrent(&year, &month);
1815
1816 wxDateTime dt;
1817
1818 // TODO this probably could be optimised somehow...
1819
1820 if ( n > 0 )
1821 {
1822 // get the first day of the month
1823 dt.Set(1, month, year);
1824
1825 // get its wday
1826 WeekDay wdayFirst = dt.GetWeekDay();
1827
1828 // go to the first weekday of the month
1829 int diff = weekday - wdayFirst;
1830 if ( diff < 0 )
1831 diff += 7;
1832
1833 // add advance n-1 weeks more
1834 diff += 7*(n - 1);
1835
239446b4 1836 dt += wxDateSpan::Days(diff);
2f02cb89 1837 }
239446b4 1838 else // count from the end of the month
2f02cb89
VZ
1839 {
1840 // get the last day of the month
1841 dt.SetToLastMonthDay(month, year);
1842
1843 // get its wday
1844 WeekDay wdayLast = dt.GetWeekDay();
1845
1846 // go to the last weekday of the month
1847 int diff = wdayLast - weekday;
1848 if ( diff < 0 )
1849 diff += 7;
1850
1851 // and rewind n-1 weeks from there
239446b4 1852 diff += 7*(-n - 1);
2f02cb89
VZ
1853
1854 dt -= wxDateSpan::Days(diff);
1855 }
1856
1857 // check that it is still in the same month
1858 if ( dt.GetMonth() == month )
1859 {
1860 *this = dt;
1861
68379eaf 1862 return true;
2f02cb89
VZ
1863 }
1864 else
1865 {
1866 // no such day in this month
68379eaf 1867 return false;
2f02cb89
VZ
1868 }
1869}
1870
1c5d27e2
VZ
1871static inline
1872wxDateTime::wxDateTime_t GetDayOfYearFromTm(const wxDateTime::Tm& tm)
1873{
907173e5 1874 return (wxDateTime::wxDateTime_t)(gs_cumulatedDays[wxDateTime::IsLeapYear(tm.year)][tm.mon] + tm.mday);
1c5d27e2
VZ
1875}
1876
239446b4
VZ
1877wxDateTime::wxDateTime_t wxDateTime::GetDayOfYear(const TimeZone& tz) const
1878{
1c5d27e2
VZ
1879 return GetDayOfYearFromTm(GetTm(tz));
1880}
239446b4 1881
1c5d27e2
VZ
1882wxDateTime::wxDateTime_t
1883wxDateTime::GetWeekOfYear(wxDateTime::WeekFlags flags, const TimeZone& tz) const
239446b4 1884{
9d9b7755
VZ
1885 if ( flags == Default_First )
1886 {
1887 flags = GetCountry() == USA ? Sunday_First : Monday_First;
1888 }
239446b4 1889
1c5d27e2
VZ
1890 Tm tm(GetTm(tz));
1891 wxDateTime_t nDayInYear = GetDayOfYearFromTm(tm);
239446b4 1892
1c5d27e2
VZ
1893 int wdTarget = GetWeekDay(tz);
1894 int wdYearStart = wxDateTime(1, Jan, GetYear()).GetWeekDay();
1895 int week;
9d9b7755
VZ
1896 if ( flags == Sunday_First )
1897 {
1c5d27e2
VZ
1898 // FIXME: First week is not calculated correctly.
1899 week = (nDayInYear - wdTarget + 7) / 7;
1900 if ( wdYearStart == Wed || wdYearStart == Thu )
1901 week++;
9d9b7755 1902 }
1c5d27e2 1903 else // week starts with monday
9d9b7755 1904 {
1c5d27e2
VZ
1905 // adjust the weekdays to non-US style.
1906 wdYearStart = ConvertWeekDayToMondayBase(wdYearStart);
1907 wdTarget = ConvertWeekDayToMondayBase(wdTarget);
239446b4 1908
1c5d27e2
VZ
1909 // quoting from http://www.cl.cam.ac.uk/~mgk25/iso-time.html:
1910 //
1911 // Week 01 of a year is per definition the first week that has the
1912 // Thursday in this year, which is equivalent to the week that
1913 // contains the fourth day of January. In other words, the first
1914 // week of a new year is the week that has the majority of its
1915 // days in the new year. Week 01 might also contain days from the
1916 // previous year and the week before week 01 of a year is the last
1917 // week (52 or 53) of the previous year even if it contains days
1918 // from the new year. A week starts with Monday (day 1) and ends
1919 // with Sunday (day 7).
1920 //
1921
1922 // if Jan 1 is Thursday or less, it is in the first week of this year
1923 if ( wdYearStart < 4 )
1924 {
1925 // count the number of entire weeks between Jan 1 and this date
1926 week = (nDayInYear + wdYearStart + 6 - wdTarget)/7;
1927
1928 // be careful to check for overflow in the next year
1929 if ( week == 53 && tm.mday - wdTarget > 28 )
1930 week = 1;
1931 }
1932 else // Jan 1 is in the last week of the previous year
1933 {
1934 // check if we happen to be at the last week of previous year:
1935 if ( tm.mon == Jan && tm.mday < 8 - wdYearStart )
1936 week = wxDateTime(31, Dec, GetYear()-1).GetWeekOfYear();
1937 else
1938 week = (nDayInYear + wdYearStart - 1 - wdTarget)/7;
1939 }
239446b4
VZ
1940 }
1941
907173e5 1942 return (wxDateTime::wxDateTime_t)week;
68ee7c47
VZ
1943}
1944
9d9b7755
VZ
1945wxDateTime::wxDateTime_t wxDateTime::GetWeekOfMonth(wxDateTime::WeekFlags flags,
1946 const TimeZone& tz) const
68ee7c47 1947{
9d9b7755
VZ
1948 Tm tm = GetTm(tz);
1949 wxDateTime dtMonthStart = wxDateTime(1, tm.mon, tm.year);
6dc6fda6 1950 int nWeek = GetWeekOfYear(flags) - dtMonthStart.GetWeekOfYear(flags) + 1;
9d9b7755 1951 if ( nWeek < 0 )
68ee7c47 1952 {
9d9b7755
VZ
1953 // this may happen for January when Jan, 1 is the last week of the
1954 // previous year
1955 nWeek += IsLeapYear(tm.year - 1) ? 53 : 52;
68ee7c47 1956 }
68ee7c47 1957
6dc6fda6 1958 return (wxDateTime::wxDateTime_t)nWeek;
239446b4
VZ
1959}
1960
f0f951fa
VZ
1961wxDateTime& wxDateTime::SetToYearDay(wxDateTime::wxDateTime_t yday)
1962{
1963 int year = GetYear();
384223b3
VZ
1964 wxDATETIME_CHECK( (0 < yday) && (yday <= GetNumberOfDays(year)),
1965 _T("invalid year day") );
f0f951fa
VZ
1966
1967 bool isLeap = IsLeapYear(year);
1968 for ( Month mon = Jan; mon < Inv_Month; wxNextMonth(mon) )
1969 {
1970 // for Dec, we can't compare with gs_cumulatedDays[mon + 1], but we
1971 // don't need it neither - because of the CHECK above we know that
1972 // yday lies in December then
f49d731f 1973 if ( (mon == Dec) || (yday <= gs_cumulatedDays[isLeap][mon + 1]) )
f0f951fa 1974 {
42841dfc 1975 Set((wxDateTime::wxDateTime_t)(yday - gs_cumulatedDays[isLeap][mon]), mon, year);
f0f951fa
VZ
1976
1977 break;
1978 }
1979 }
1980
1981 return *this;
1982}
1983
e6ec579c
VZ
1984// ----------------------------------------------------------------------------
1985// Julian day number conversion and related stuff
1986// ----------------------------------------------------------------------------
1987
1988double wxDateTime::GetJulianDayNumber() const
1989{
8cc00d5f
VZ
1990 // JDN are always expressed for the UTC dates
1991 Tm tm(ToTimezone(UTC).GetTm(UTC));
e6ec579c
VZ
1992
1993 double result = GetTruncatedJDN(tm.mday, tm.mon, tm.year);
1994
1995 // add the part GetTruncatedJDN() neglected
1996 result += 0.5;
1997
1998 // and now add the time: 86400 sec = 1 JDN
1999 return result + ((double)(60*(60*tm.hour + tm.min) + tm.sec)) / 86400;
2000}
2001
2002double wxDateTime::GetRataDie() const
2003{
2004 // March 1 of the year 0 is Rata Die day -306 and JDN 1721119.5
2005 return GetJulianDayNumber() - 1721119.5 - 306;
2006}
2007
fcc3d7cb 2008// ----------------------------------------------------------------------------
299fcbfe 2009// timezone and DST stuff
fcc3d7cb
VZ
2010// ----------------------------------------------------------------------------
2011
299fcbfe 2012int wxDateTime::IsDST(wxDateTime::Country country) const
fcc3d7cb 2013{
299fcbfe
VZ
2014 wxCHECK_MSG( country == Country_Default, -1,
2015 _T("country support not implemented") );
2016
2017 // use the C RTL for the dates in the standard range
2018 time_t timet = GetTicks();
2019 if ( timet != (time_t)-1 )
2020 {
2021 tm *tm = localtime(&timet);
2022
2023 wxCHECK_MSG( tm, -1, _T("localtime() failed") );
2024
2025 return tm->tm_isdst;
2026 }
2027 else
2028 {
239446b4
VZ
2029 int year = GetYear();
2030
2031 if ( !IsDSTApplicable(year, country) )
2032 {
2033 // no DST time in this year in this country
2034 return -1;
2035 }
299fcbfe 2036
239446b4 2037 return IsBetween(GetBeginDST(year, country), GetEndDST(year, country));
299fcbfe 2038 }
fcc3d7cb
VZ
2039}
2040
41acf5c0 2041wxDateTime& wxDateTime::MakeTimezone(const TimeZone& tz, bool noDST)
fcc3d7cb 2042{
479cd5de 2043 long secDiff = GetTimeZone() + tz.GetOffset();
fcc3d7cb 2044
41acf5c0
VZ
2045 // we need to know whether DST is or not in effect for this date unless
2046 // the test disabled by the caller
2047 if ( !noDST && (IsDST() == 1) )
299fcbfe
VZ
2048 {
2049 // FIXME we assume that the DST is always shifted by 1 hour
2050 secDiff -= 3600;
2051 }
2052
f6bcfd97 2053 return Subtract(wxTimeSpan::Seconds(secDiff));
fcc3d7cb
VZ
2054}
2055
b76b015e
VZ
2056// ----------------------------------------------------------------------------
2057// wxDateTime to/from text representations
2058// ----------------------------------------------------------------------------
2059
299fcbfe 2060wxString wxDateTime::Format(const wxChar *format, const TimeZone& tz) const
b76b015e 2061{
e6ec579c
VZ
2062 wxCHECK_MSG( format, _T(""), _T("NULL format in wxDateTime::Format") );
2063
f6bcfd97
BP
2064 // we have to use our own implementation if the date is out of range of
2065 // strftime() or if we use non standard specificators
b76b015e 2066 time_t time = GetTicks();
f6bcfd97 2067 if ( (time != (time_t)-1) && !wxStrstr(format, _T("%l")) )
b76b015e
VZ
2068 {
2069 // use strftime()
299fcbfe
VZ
2070 tm *tm;
2071 if ( tz.GetOffset() == -GetTimeZone() )
2072 {
2073 // we are working with local time
2074 tm = localtime(&time);
c5a1681b
VZ
2075
2076 // should never happen
2077 wxCHECK_MSG( tm, wxEmptyString, _T("localtime() failed") );
299fcbfe
VZ
2078 }
2079 else
2080 {
479cd5de 2081 time += (int)tz.GetOffset();
299fcbfe 2082
33ac7e6f 2083#if defined(__VMS__) || defined(__WATCOMC__) // time is unsigned so avoid warning
13111b2a 2084 int time2 = (int) time;
9c2882d9 2085 if ( time2 >= 0 )
fb10f04c 2086#else
9c2882d9 2087 if ( time >= 0 )
fb10f04c 2088#endif
c5a1681b
VZ
2089 {
2090 tm = gmtime(&time);
b76b015e 2091
c5a1681b
VZ
2092 // should never happen
2093 wxCHECK_MSG( tm, wxEmptyString, _T("gmtime() failed") );
2094 }
2095 else
2096 {
2097 tm = (struct tm *)NULL;
2098 }
2099 }
b76b015e 2100
c5a1681b 2101 if ( tm )
e6ec579c 2102 {
c5a1681b 2103 return CallStrftime(format, tm);
e6ec579c 2104 }
c5a1681b
VZ
2105 //else: use generic code below
2106 }
2107
68ee7c47 2108 // we only parse ANSI C format specifications here, no POSIX 2
f6bcfd97
BP
2109 // complications, no GNU extensions but we do add support for a "%l" format
2110 // specifier allowing to get the number of milliseconds
68ee7c47 2111 Tm tm = GetTm(tz);
e6ec579c 2112
68ee7c47
VZ
2113 // used for calls to strftime() when we only deal with time
2114 struct tm tmTimeOnly;
2115 tmTimeOnly.tm_hour = tm.hour;
2116 tmTimeOnly.tm_min = tm.min;
2117 tmTimeOnly.tm_sec = tm.sec;
2118 tmTimeOnly.tm_wday = 0;
2119 tmTimeOnly.tm_yday = 0;
2120 tmTimeOnly.tm_mday = 1; // any date will do
2121 tmTimeOnly.tm_mon = 0;
2122 tmTimeOnly.tm_year = 76;
2123 tmTimeOnly.tm_isdst = 0; // no DST, we adjust for tz ourselves
e6ec579c 2124
77c3e48a 2125 wxString tmp, res, fmt;
68ee7c47
VZ
2126 for ( const wxChar *p = format; *p; p++ )
2127 {
2128 if ( *p != _T('%') )
2129 {
2130 // copy as is
2131 res += *p;
b76b015e 2132
68ee7c47
VZ
2133 continue;
2134 }
e6ec579c 2135
77c3e48a 2136 // set the default format
68ee7c47 2137 switch ( *++p )
77c3e48a
VZ
2138 {
2139 case _T('Y'): // year has 4 digits
2140 fmt = _T("%04d");
2141 break;
2142
2143 case _T('j'): // day of year has 3 digits
f6bcfd97 2144 case _T('l'): // milliseconds have 3 digits
77c3e48a
VZ
2145 fmt = _T("%03d");
2146 break;
2147
59d04dff
VZ
2148 case _T('w'): // week day as number has only one
2149 fmt = _T("%d");
2150 break;
2151
77c3e48a
VZ
2152 default:
2153 // it's either another valid format specifier in which case
2154 // the format is "%02d" (for all the rest) or we have the
2155 // field width preceding the format in which case it will
2156 // override the default format anyhow
2157 fmt = _T("%02d");
2158 }
2159
68379eaf 2160 bool restart = true;
fc3398f8 2161 while ( restart )
68ee7c47 2162 {
68379eaf 2163 restart = false;
68ee7c47 2164
fc3398f8
VZ
2165 // start of the format specification
2166 switch ( *p )
2167 {
2168 case _T('a'): // a weekday name
2169 case _T('A'):
68379eaf 2170 // second parameter should be true for abbreviated names
fc3398f8
VZ
2171 res += GetWeekDayName(tm.GetWeekDay(),
2172 *p == _T('a') ? Name_Abbr : Name_Full);
2173 break;
68ee7c47 2174
fc3398f8
VZ
2175 case _T('b'): // a month name
2176 case _T('B'):
2177 res += GetMonthName(tm.mon,
2178 *p == _T('b') ? Name_Abbr : Name_Full);
2179 break;
2180
2181 case _T('c'): // locale default date and time representation
2182 case _T('x'): // locale default date representation
2183 //
2184 // the problem: there is no way to know what do these format
2185 // specifications correspond to for the current locale.
2186 //
2187 // the solution: use a hack and still use strftime(): first
2188 // find the YEAR which is a year in the strftime() range (1970
2189 // - 2038) whose Jan 1 falls on the same week day as the Jan 1
2190 // of the real year. Then make a copy of the format and
2191 // replace all occurences of YEAR in it with some unique
2192 // string not appearing anywhere else in it, then use
2193 // strftime() to format the date in year YEAR and then replace
2194 // YEAR back by the real year and the unique replacement
2195 // string back with YEAR. Notice that "all occurences of YEAR"
2196 // means all occurences of 4 digit as well as 2 digit form!
2197 //
2198 // the bugs: we assume that neither of %c nor %x contains any
2199 // fields which may change between the YEAR and real year. For
2200 // example, the week number (%U, %W) and the day number (%j)
2201 // will change if one of these years is leap and the other one
2202 // is not!
68ee7c47 2203 {
fc3398f8
VZ
2204 // find the YEAR: normally, for any year X, Jan 1 or the
2205 // year X + 28 is the same weekday as Jan 1 of X (because
2206 // the weekday advances by 1 for each normal X and by 2
2207 // for each leap X, hence by 5 every 4 years or by 35
2208 // which is 0 mod 7 every 28 years) but this rule breaks
2209 // down if there are years between X and Y which are
2210 // divisible by 4 but not leap (i.e. divisible by 100 but
2211 // not 400), hence the correction.
2212
2213 int yearReal = GetYear(tz);
2214 int mod28 = yearReal % 28;
2215
2216 // be careful to not go too far - we risk to leave the
2217 // supported range
2218 int year;
2219 if ( mod28 < 10 )
2220 {
2221 year = 1988 + mod28; // 1988 == 0 (mod 28)
2222 }
2223 else
2224 {
2225 year = 1970 + mod28 - 10; // 1970 == 10 (mod 28)
2226 }
e6ec579c 2227
fc3398f8
VZ
2228 int nCentury = year / 100,
2229 nCenturyReal = yearReal / 100;
c5a1681b 2230
fc3398f8
VZ
2231 // need to adjust for the years divisble by 400 which are
2232 // not leap but are counted like leap ones if we just take
2233 // the number of centuries in between for nLostWeekDays
2234 int nLostWeekDays = (nCentury - nCenturyReal) -
2235 (nCentury / 4 - nCenturyReal / 4);
c5a1681b 2236
fc3398f8
VZ
2237 // we have to gain back the "lost" weekdays: note that the
2238 // effect of this loop is to not do anything to
2239 // nLostWeekDays (which we won't use any more), but to
2240 // (indirectly) set the year correctly
2241 while ( (nLostWeekDays % 7) != 0 )
2242 {
2243 nLostWeekDays += year++ % 4 ? 1 : 2;
2244 }
c5a1681b 2245
fc3398f8
VZ
2246 // at any rate, we couldn't go further than 1988 + 9 + 28!
2247 wxASSERT_MSG( year < 2030,
2248 _T("logic error in wxDateTime::Format") );
c5a1681b 2249
fc3398f8
VZ
2250 wxString strYear, strYear2;
2251 strYear.Printf(_T("%d"), year);
2252 strYear2.Printf(_T("%d"), year % 100);
c5a1681b 2253
fc3398f8
VZ
2254 // find two strings not occuring in format (this is surely
2255 // not optimal way of doing it... improvements welcome!)
2256 wxString fmt = format;
2257 wxString replacement = (wxChar)-1;
2258 while ( fmt.Find(replacement) != wxNOT_FOUND )
2259 {
2260 replacement << (wxChar)-1;
2261 }
c5a1681b 2262
fc3398f8
VZ
2263 wxString replacement2 = (wxChar)-2;
2264 while ( fmt.Find(replacement) != wxNOT_FOUND )
2265 {
2266 replacement << (wxChar)-2;
2267 }
2268
2269 // replace all occurences of year with it
2270 bool wasReplaced = fmt.Replace(strYear, replacement) > 0;
2271 if ( !wasReplaced )
2272 wasReplaced = fmt.Replace(strYear2, replacement2) > 0;
2273
2274 // use strftime() to format the same date but in supported
2275 // year
2276 //
2277 // NB: we assume that strftime() doesn't check for the
2278 // date validity and will happily format the date
2279 // corresponding to Feb 29 of a non leap year (which
2280 // may happen if yearReal was leap and year is not)
2281 struct tm tmAdjusted;
2282 InitTm(tmAdjusted);
2283 tmAdjusted.tm_hour = tm.hour;
2284 tmAdjusted.tm_min = tm.min;
2285 tmAdjusted.tm_sec = tm.sec;
2286 tmAdjusted.tm_wday = tm.GetWeekDay();
2287 tmAdjusted.tm_yday = GetDayOfYear();
2288 tmAdjusted.tm_mday = tm.mday;
2289 tmAdjusted.tm_mon = tm.mon;
2290 tmAdjusted.tm_year = year - 1900;
2291 tmAdjusted.tm_isdst = 0; // no DST, already adjusted
2292 wxString str = CallStrftime(*p == _T('c') ? _T("%c")
2293 : _T("%x"),
2294 &tmAdjusted);
2295
2296 // now replace the occurence of 1999 with the real year
2297 wxString strYearReal, strYearReal2;
2298 strYearReal.Printf(_T("%04d"), yearReal);
2299 strYearReal2.Printf(_T("%02d"), yearReal % 100);
2300 str.Replace(strYear, strYearReal);
2301 str.Replace(strYear2, strYearReal2);
2302
2303 // and replace back all occurences of replacement string
2304 if ( wasReplaced )
2305 {
2306 str.Replace(replacement2, strYear2);
2307 str.Replace(replacement, strYear);
2308 }
2309
2310 res += str;
68ee7c47 2311 }
fc3398f8 2312 break;
c5a1681b 2313
fc3398f8
VZ
2314 case _T('d'): // day of a month (01-31)
2315 res += wxString::Format(fmt, tm.mday);
2316 break;
68ee7c47 2317
fc3398f8
VZ
2318 case _T('H'): // hour in 24h format (00-23)
2319 res += wxString::Format(fmt, tm.hour);
2320 break;
2321
2322 case _T('I'): // hour in 12h format (01-12)
68ee7c47 2323 {
fc3398f8
VZ
2324 // 24h -> 12h, 0h -> 12h too
2325 int hour12 = tm.hour > 12 ? tm.hour - 12
2326 : tm.hour ? tm.hour : 12;
2327 res += wxString::Format(fmt, hour12);
68ee7c47 2328 }
fc3398f8 2329 break;
c5a1681b 2330
fc3398f8
VZ
2331 case _T('j'): // day of the year
2332 res += wxString::Format(fmt, GetDayOfYear(tz));
2333 break;
68ee7c47 2334
f6bcfd97
BP
2335 case _T('l'): // milliseconds (NOT STANDARD)
2336 res += wxString::Format(fmt, GetMillisecond(tz));
2337 break;
2338
fc3398f8
VZ
2339 case _T('m'): // month as a number (01-12)
2340 res += wxString::Format(fmt, tm.mon + 1);
2341 break;
68ee7c47 2342
fc3398f8
VZ
2343 case _T('M'): // minute as a decimal number (00-59)
2344 res += wxString::Format(fmt, tm.min);
2345 break;
68ee7c47 2346
fc3398f8
VZ
2347 case _T('p'): // AM or PM string
2348 res += CallStrftime(_T("%p"), &tmTimeOnly);
2349 break;
68ee7c47 2350
fc3398f8
VZ
2351 case _T('S'): // second as a decimal number (00-61)
2352 res += wxString::Format(fmt, tm.sec);
2353 break;
68ee7c47 2354
fc3398f8
VZ
2355 case _T('U'): // week number in the year (Sunday 1st week day)
2356 res += wxString::Format(fmt, GetWeekOfYear(Sunday_First, tz));
2357 break;
68ee7c47 2358
fc3398f8
VZ
2359 case _T('W'): // week number in the year (Monday 1st week day)
2360 res += wxString::Format(fmt, GetWeekOfYear(Monday_First, tz));
2361 break;
68ee7c47 2362
fc3398f8
VZ
2363 case _T('w'): // weekday as a number (0-6), Sunday = 0
2364 res += wxString::Format(fmt, tm.GetWeekDay());
2365 break;
68ee7c47 2366
fc3398f8 2367 // case _T('x'): -- handled with "%c"
68ee7c47 2368
fc3398f8
VZ
2369 case _T('X'): // locale default time representation
2370 // just use strftime() to format the time for us
2371 res += CallStrftime(_T("%X"), &tmTimeOnly);
2372 break;
68ee7c47 2373
fc3398f8
VZ
2374 case _T('y'): // year without century (00-99)
2375 res += wxString::Format(fmt, tm.year % 100);
2376 break;
68ee7c47 2377
fc3398f8
VZ
2378 case _T('Y'): // year with century
2379 res += wxString::Format(fmt, tm.year);
2380 break;
68ee7c47 2381
fc3398f8
VZ
2382 case _T('Z'): // timezone name
2383 res += CallStrftime(_T("%Z"), &tmTimeOnly);
2384 break;
68ee7c47 2385
fc3398f8
VZ
2386 default:
2387 // is it the format width?
2388 fmt.Empty();
2389 while ( *p == _T('-') || *p == _T('+') ||
2390 *p == _T(' ') || wxIsdigit(*p) )
2391 {
2392 fmt += *p;
2393 }
68ee7c47 2394
bb8a8478 2395 if ( !fmt.empty() )
fc3398f8
VZ
2396 {
2397 // we've only got the flags and width so far in fmt
2398 fmt.Prepend(_T('%'));
2399 fmt.Append(_T('d'));
68ee7c47 2400
68379eaf 2401 restart = true;
68ee7c47 2402
fc3398f8
VZ
2403 break;
2404 }
68ee7c47 2405
fc3398f8
VZ
2406 // no, it wasn't the width
2407 wxFAIL_MSG(_T("unknown format specificator"));
68ee7c47 2408
fc3398f8 2409 // fall through and just copy it nevertheless
68ee7c47 2410
fc3398f8
VZ
2411 case _T('%'): // a percent sign
2412 res += *p;
2413 break;
2414
2415 case 0: // the end of string
2416 wxFAIL_MSG(_T("missing format at the end of string"));
2417
2418 // just put the '%' which was the last char in format
2419 res += _T('%');
2420 break;
2421 }
68ee7c47 2422 }
c5a1681b
VZ
2423 }
2424
68ee7c47 2425 return res;
b76b015e 2426}
fcc3d7cb 2427
cd0b1709
VZ
2428// this function parses a string in (strict) RFC 822 format: see the section 5
2429// of the RFC for the detailed description, but briefly it's something of the
2430// form "Sat, 18 Dec 1999 00:48:30 +0100"
2431//
2432// this function is "strict" by design - it must reject anything except true
2433// RFC822 time specs.
2434//
2435// TODO a great candidate for using reg exps
2436const wxChar *wxDateTime::ParseRfc822Date(const wxChar* date)
2437{
2438 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
2439
2440 const wxChar *p = date;
2441 const wxChar *comma = wxStrchr(p, _T(','));
2442 if ( comma )
2443 {
2444 // the part before comma is the weekday
2445
2446 // skip it for now - we don't use but might check that it really
2447 // corresponds to the specfied date
2448 p = comma + 1;
2449
2450 if ( *p != _T(' ') )
2451 {
2452 wxLogDebug(_T("no space after weekday in RFC822 time spec"));
2453
2454 return (wxChar *)NULL;
2455 }
2456
2457 p++; // skip space
2458 }
2459
2460 // the following 1 or 2 digits are the day number
2461 if ( !wxIsdigit(*p) )
2462 {
2463 wxLogDebug(_T("day number expected in RFC822 time spec, none found"));
2464
2465 return (wxChar *)NULL;
2466 }
2467
907173e5 2468 wxDateTime_t day = (wxDateTime_t)(*p++ - _T('0'));
cd0b1709
VZ
2469 if ( wxIsdigit(*p) )
2470 {
2471 day *= 10;
907173e5 2472 day = (wxDateTime_t)(day + (*p++ - _T('0')));
cd0b1709
VZ
2473 }
2474
2475 if ( *p++ != _T(' ') )
2476 {
2477 return (wxChar *)NULL;
2478 }
2479
2480 // the following 3 letters specify the month
2481 wxString monName(p, 3);
2482 Month mon;
2483 if ( monName == _T("Jan") )
2484 mon = Jan;
2485 else if ( monName == _T("Feb") )
2486 mon = Feb;
2487 else if ( monName == _T("Mar") )
2488 mon = Mar;
2489 else if ( monName == _T("Apr") )
2490 mon = Apr;
2491 else if ( monName == _T("May") )
2492 mon = May;
2493 else if ( monName == _T("Jun") )
2494 mon = Jun;
2495 else if ( monName == _T("Jul") )
2496 mon = Jul;
2497 else if ( monName == _T("Aug") )
2498 mon = Aug;
2499 else if ( monName == _T("Sep") )
2500 mon = Sep;
2501 else if ( monName == _T("Oct") )
2502 mon = Oct;
2503 else if ( monName == _T("Nov") )
2504 mon = Nov;
2505 else if ( monName == _T("Dec") )
2506 mon = Dec;
2507 else
2508 {
2509 wxLogDebug(_T("Invalid RFC 822 month name '%s'"), monName.c_str());
2510
2511 return (wxChar *)NULL;
2512 }
2513
2514 p += 3;
2515
2516 if ( *p++ != _T(' ') )
2517 {
2518 return (wxChar *)NULL;
2519 }
2520
2521 // next is the year
2522 if ( !wxIsdigit(*p) )
2523 {
2524 // no year?
2525 return (wxChar *)NULL;
2526 }
2527
2528 int year = *p++ - _T('0');
2529
2530 if ( !wxIsdigit(*p) )
2531 {
2532 // should have at least 2 digits in the year
2533 return (wxChar *)NULL;
2534 }
2535
2536 year *= 10;
2537 year += *p++ - _T('0');
2538
2539 // is it a 2 digit year (as per original RFC 822) or a 4 digit one?
2540 if ( wxIsdigit(*p) )
2541 {
2542 year *= 10;
2543 year += *p++ - _T('0');
2544
2545 if ( !wxIsdigit(*p) )
2546 {
2547 // no 3 digit years please
2548 return (wxChar *)NULL;
2549 }
2550
2551 year *= 10;
2552 year += *p++ - _T('0');
2553 }
2554
2555 if ( *p++ != _T(' ') )
2556 {
2557 return (wxChar *)NULL;
2558 }
2559
2560 // time is in the format hh:mm:ss and seconds are optional
2561 if ( !wxIsdigit(*p) )
2562 {
2563 return (wxChar *)NULL;
2564 }
2565
42841dfc 2566 wxDateTime_t hour = (wxDateTime_t)(*p++ - _T('0'));
cd0b1709
VZ
2567
2568 if ( !wxIsdigit(*p) )
2569 {
2570 return (wxChar *)NULL;
2571 }
2572
2573 hour *= 10;
42841dfc 2574 hour = (wxDateTime_t)(hour + (*p++ - _T('0')));
cd0b1709
VZ
2575
2576 if ( *p++ != _T(':') )
2577 {
2578 return (wxChar *)NULL;
2579 }
2580
2581 if ( !wxIsdigit(*p) )
2582 {
2583 return (wxChar *)NULL;
2584 }
2585
42841dfc 2586 wxDateTime_t min = (wxDateTime_t)(*p++ - _T('0'));
cd0b1709
VZ
2587
2588 if ( !wxIsdigit(*p) )
2589 {
2590 return (wxChar *)NULL;
2591 }
2592
2593 min *= 10;
42841dfc 2594 min = (wxDateTime_t)(min + *p++ - _T('0'));
cd0b1709
VZ
2595
2596 wxDateTime_t sec = 0;
2597 if ( *p++ == _T(':') )
2598 {
2599 if ( !wxIsdigit(*p) )
2600 {
2601 return (wxChar *)NULL;
2602 }
2603
42841dfc 2604 sec = (wxDateTime_t)(*p++ - _T('0'));
cd0b1709
VZ
2605
2606 if ( !wxIsdigit(*p) )
2607 {
2608 return (wxChar *)NULL;
2609 }
2610
2611 sec *= 10;
42841dfc 2612 sec = (wxDateTime_t)(sec + *p++ - _T('0'));
cd0b1709
VZ
2613 }
2614
2615 if ( *p++ != _T(' ') )
2616 {
2617 return (wxChar *)NULL;
2618 }
2619
2620 // and now the interesting part: the timezone
40973ea5 2621 int offset;
cd0b1709
VZ
2622 if ( *p == _T('-') || *p == _T('+') )
2623 {
2624 // the explicit offset given: it has the form of hhmm
2625 bool plus = *p++ == _T('+');
2626
2627 if ( !wxIsdigit(*p) || !wxIsdigit(*(p + 1)) )
2628 {
2629 return (wxChar *)NULL;
2630 }
2631
2632 // hours
2633 offset = 60*(10*(*p - _T('0')) + (*(p + 1) - _T('0')));
2634
2635 p += 2;
2636
2637 if ( !wxIsdigit(*p) || !wxIsdigit(*(p + 1)) )
2638 {
2639 return (wxChar *)NULL;
2640 }
2641
2642 // minutes
2643 offset += 10*(*p - _T('0')) + (*(p + 1) - _T('0'));
2644
2645 if ( !plus )
2646 {
2647 offset = -offset;
2648 }
2649
2650 p += 2;
2651 }
2652 else
2653 {
2654 // the symbolic timezone given: may be either military timezone or one
2655 // of standard abbreviations
2656 if ( !*(p + 1) )
2657 {
2658 // military: Z = UTC, J unused, A = -1, ..., Y = +12
2659 static const int offsets[26] =
2660 {
2661 //A B C D E F G H I J K L M
2662 -1, -2, -3, -4, -5, -6, -7, -8, -9, 0, -10, -11, -12,
2663 //N O P R Q S T U V W Z Y Z
2664 +1, +2, +3, +4, +5, +6, +7, +8, +9, +10, +11, +12, 0
2665 };
2666
2667 if ( *p < _T('A') || *p > _T('Z') || *p == _T('J') )
2668 {
2669 wxLogDebug(_T("Invalid militaty timezone '%c'"), *p);
2670
2671 return (wxChar *)NULL;
2672 }
2673
2674 offset = offsets[*p++ - _T('A')];
2675 }
2676 else
2677 {
2678 // abbreviation
2679 wxString tz = p;
2680 if ( tz == _T("UT") || tz == _T("UTC") || tz == _T("GMT") )
2681 offset = 0;
2682 else if ( tz == _T("AST") )
2683 offset = AST - GMT0;
2684 else if ( tz == _T("ADT") )
2685 offset = ADT - GMT0;
2686 else if ( tz == _T("EST") )
2687 offset = EST - GMT0;
2688 else if ( tz == _T("EDT") )
2689 offset = EDT - GMT0;
2690 else if ( tz == _T("CST") )
2691 offset = CST - GMT0;
2692 else if ( tz == _T("CDT") )
2693 offset = CDT - GMT0;
2694 else if ( tz == _T("MST") )
2695 offset = MST - GMT0;
2696 else if ( tz == _T("MDT") )
2697 offset = MDT - GMT0;
2698 else if ( tz == _T("PST") )
2699 offset = PST - GMT0;
2700 else if ( tz == _T("PDT") )
2701 offset = PDT - GMT0;
2702 else
2703 {
2704 wxLogDebug(_T("Unknown RFC 822 timezone '%s'"), p);
2705
2706 return (wxChar *)NULL;
2707 }
2708
2709 p += tz.length();
2710 }
2711
2712 // make it minutes
2713 offset *= 60;
2714 }
2715
2716 // the spec was correct
2717 Set(day, mon, year, hour, min, sec);
487c1f7e 2718 MakeTimezone((wxDateTime_t)(60*offset));
cd0b1709
VZ
2719
2720 return p;
2721}
2722
f0f951fa
VZ
2723const wxChar *wxDateTime::ParseFormat(const wxChar *date,
2724 const wxChar *format,
2725 const wxDateTime& dateDef)
cd0b1709
VZ
2726{
2727 wxCHECK_MSG( date && format, (wxChar *)NULL,
f0f951fa 2728 _T("NULL pointer in wxDateTime::ParseFormat()") );
cd0b1709 2729
f0f951fa
VZ
2730 wxString str;
2731 unsigned long num;
cd0b1709 2732
f0f951fa 2733 // what fields have we found?
68379eaf
WS
2734 bool haveWDay = false,
2735 haveYDay = false,
2736 haveDay = false,
2737 haveMon = false,
2738 haveYear = false,
2739 haveHour = false,
2740 haveMin = false,
2741 haveSec = false;
2742
2743 bool hourIsIn12hFormat = false, // or in 24h one?
2744 isPM = false; // AM by default
f0f951fa
VZ
2745
2746 // and the value of the items we have (init them to get rid of warnings)
2747 wxDateTime_t sec = 0,
2748 min = 0,
2749 hour = 0;
2750 WeekDay wday = Inv_WeekDay;
2751 wxDateTime_t yday = 0,
2752 mday = 0;
2753 wxDateTime::Month mon = Inv_Month;
2754 int year = 0;
2755
2756 const wxChar *input = date;
2757 for ( const wxChar *fmt = format; *fmt; fmt++ )
2758 {
2759 if ( *fmt != _T('%') )
2760 {
2761 if ( wxIsspace(*fmt) )
2762 {
2763 // a white space in the format string matches 0 or more white
2764 // spaces in the input
2765 while ( wxIsspace(*input) )
2766 {
2767 input++;
2768 }
2769 }
2770 else // !space
2771 {
2772 // any other character (not whitespace, not '%') must be
2773 // matched by itself in the input
2774 if ( *input++ != *fmt )
2775 {
2776 // no match
2777 return (wxChar *)NULL;
2778 }
2779 }
2780
2781 // done with this format char
2782 continue;
2783 }
2784
2785 // start of a format specification
f6bcfd97
BP
2786
2787 // parse the optional width
2788 size_t width = 0;
1c193821 2789 while ( wxIsdigit(*++fmt) )
f6bcfd97
BP
2790 {
2791 width *= 10;
2792 width += *fmt - _T('0');
2793 }
2794
4147efe9
VZ
2795 // the default widths for the various fields
2796 if ( !width )
2797 {
2798 switch ( *fmt )
2799 {
2800 case _T('Y'): // year has 4 digits
2801 width = 4;
2802 break;
2803
2804 case _T('j'): // day of year has 3 digits
2805 case _T('l'): // milliseconds have 3 digits
2806 width = 3;
2807 break;
2808
2809 case _T('w'): // week day as number has only one
2810 width = 1;
2811 break;
2812
2813 default:
2814 // default for all other fields
2815 width = 2;
2816 }
2817 }
2818
f6bcfd97
BP
2819 // then the format itself
2820 switch ( *fmt )
f0f951fa
VZ
2821 {
2822 case _T('a'): // a weekday name
2823 case _T('A'):
2824 {
2825 int flag = *fmt == _T('a') ? Name_Abbr : Name_Full;
2826 wday = GetWeekDayFromName(GetAlphaToken(input), flag);
2827 if ( wday == Inv_WeekDay )
2828 {
2829 // no match
2830 return (wxChar *)NULL;
2831 }
2832 }
68379eaf 2833 haveWDay = true;
f0f951fa
VZ
2834 break;
2835
2836 case _T('b'): // a month name
2837 case _T('B'):
2838 {
2839 int flag = *fmt == _T('b') ? Name_Abbr : Name_Full;
2840 mon = GetMonthFromName(GetAlphaToken(input), flag);
2841 if ( mon == Inv_Month )
2842 {
2843 // no match
2844 return (wxChar *)NULL;
2845 }
2846 }
68379eaf 2847 haveMon = true;
f0f951fa
VZ
2848 break;
2849
2850 case _T('c'): // locale default date and time representation
2851 {
2852 wxDateTime dt;
2853
2854 // this is the format which corresponds to ctime() output
2855 // and strptime("%c") should parse it, so try it first
41acf5c0 2856 static const wxChar *fmtCtime = _T("%a %b %d %H:%M:%S %Y");
f0f951fa
VZ
2857
2858 const wxChar *result = dt.ParseFormat(input, fmtCtime);
2859 if ( !result )
2860 {
2861 result = dt.ParseFormat(input, _T("%x %X"));
2862 }
2863
2864 if ( !result )
2865 {
2866 result = dt.ParseFormat(input, _T("%X %x"));
2867 }
2868
2869 if ( !result )
2870 {
2871 // we've tried everything and still no match
2872 return (wxChar *)NULL;
2873 }
2874
be4017f8
VZ
2875 Tm tm = dt.GetTm();
2876
2877 haveDay = haveMon = haveYear =
68379eaf 2878 haveHour = haveMin = haveSec = true;
be4017f8
VZ
2879
2880 hour = tm.hour;
2881 min = tm.min;
2882 sec = tm.sec;
2883
2884 year = tm.year;
2885 mon = tm.mon;
2886 mday = tm.mday;
2887
f0f951fa
VZ
2888 input = result;
2889 }
2890 break;
2891
2892 case _T('d'): // day of a month (01-31)
f6bcfd97
BP
2893 if ( !GetNumericToken(width, input, &num) ||
2894 (num > 31) || (num < 1) )
f0f951fa
VZ
2895 {
2896 // no match
2897 return (wxChar *)NULL;
2898 }
2899
2900 // we can't check whether the day range is correct yet, will
2901 // do it later - assume ok for now
68379eaf 2902 haveDay = true;
f0f951fa
VZ
2903 mday = (wxDateTime_t)num;
2904 break;
5f287370 2905
f0f951fa 2906 case _T('H'): // hour in 24h format (00-23)
f6bcfd97 2907 if ( !GetNumericToken(width, input, &num) || (num > 23) )
f0f951fa
VZ
2908 {
2909 // no match
2910 return (wxChar *)NULL;
2911 }
2912
68379eaf 2913 haveHour = true;
f0f951fa
VZ
2914 hour = (wxDateTime_t)num;
2915 break;
2916
2917 case _T('I'): // hour in 12h format (01-12)
f6bcfd97 2918 if ( !GetNumericToken(width, input, &num) || !num || (num > 12) )
f0f951fa
VZ
2919 {
2920 // no match
2921 return (wxChar *)NULL;
2922 }
2923
68379eaf
WS
2924 haveHour = true;
2925 hourIsIn12hFormat = true;
479cd5de 2926 hour = (wxDateTime_t)(num % 12); // 12 should be 0
f0f951fa
VZ
2927 break;
2928
2929 case _T('j'): // day of the year
f6bcfd97 2930 if ( !GetNumericToken(width, input, &num) || !num || (num > 366) )
f0f951fa
VZ
2931 {
2932 // no match
2933 return (wxChar *)NULL;
2934 }
2935
68379eaf 2936 haveYDay = true;
f0f951fa
VZ
2937 yday = (wxDateTime_t)num;
2938 break;
2939
2940 case _T('m'): // month as a number (01-12)
f6bcfd97 2941 if ( !GetNumericToken(width, input, &num) || !num || (num > 12) )
f0f951fa
VZ
2942 {
2943 // no match
2944 return (wxChar *)NULL;
2945 }
2946
68379eaf 2947 haveMon = true;
be4017f8 2948 mon = (Month)(num - 1);
f0f951fa
VZ
2949 break;
2950
2951 case _T('M'): // minute as a decimal number (00-59)
f6bcfd97 2952 if ( !GetNumericToken(width, input, &num) || (num > 59) )
f0f951fa
VZ
2953 {
2954 // no match
2955 return (wxChar *)NULL;
2956 }
2957
68379eaf 2958 haveMin = true;
f0f951fa
VZ
2959 min = (wxDateTime_t)num;
2960 break;
2961
2962 case _T('p'): // AM or PM string
2963 {
2964 wxString am, pm, token = GetAlphaToken(input);
2965
2966 GetAmPmStrings(&am, &pm);
bb8a8478 2967 if (am.empty() && pm.empty())
0308fd61 2968 return (wxChar *)NULL; // no am/pm strings defined
f0f951fa
VZ
2969 if ( token.CmpNoCase(pm) == 0 )
2970 {
68379eaf 2971 isPM = true;
f0f951fa
VZ
2972 }
2973 else if ( token.CmpNoCase(am) != 0 )
2974 {
2975 // no match
2976 return (wxChar *)NULL;
2977 }
2978 }
2979 break;
2980
2981 case _T('r'): // time as %I:%M:%S %p
2982 {
2983 wxDateTime dt;
2984 input = dt.ParseFormat(input, _T("%I:%M:%S %p"));
2985 if ( !input )
2986 {
2987 // no match
2988 return (wxChar *)NULL;
2989 }
2990
68379eaf 2991 haveHour = haveMin = haveSec = true;
f0f951fa
VZ
2992
2993 Tm tm = dt.GetTm();
2994 hour = tm.hour;
2995 min = tm.min;
2996 sec = tm.sec;
2997 }
2998 break;
2999
3000 case _T('R'): // time as %H:%M
3001 {
3002 wxDateTime dt;
3003 input = dt.ParseFormat(input, _T("%H:%M"));
3004 if ( !input )
3005 {
3006 // no match
3007 return (wxChar *)NULL;
3008 }
3009
68379eaf 3010 haveHour = haveMin = true;
f0f951fa
VZ
3011
3012 Tm tm = dt.GetTm();
3013 hour = tm.hour;
3014 min = tm.min;
3015 }
3016
3017 case _T('S'): // second as a decimal number (00-61)
f6bcfd97 3018 if ( !GetNumericToken(width, input, &num) || (num > 61) )
f0f951fa
VZ
3019 {
3020 // no match
3021 return (wxChar *)NULL;
3022 }
3023
68379eaf 3024 haveSec = true;
f0f951fa
VZ
3025 sec = (wxDateTime_t)num;
3026 break;
3027
3028 case _T('T'): // time as %H:%M:%S
3029 {
3030 wxDateTime dt;
3031 input = dt.ParseFormat(input, _T("%H:%M:%S"));
3032 if ( !input )
3033 {
3034 // no match
3035 return (wxChar *)NULL;
3036 }
3037
68379eaf 3038 haveHour = haveMin = haveSec = true;
f0f951fa
VZ
3039
3040 Tm tm = dt.GetTm();
3041 hour = tm.hour;
3042 min = tm.min;
3043 sec = tm.sec;
3044 }
be4017f8 3045 break;
f0f951fa
VZ
3046
3047 case _T('w'): // weekday as a number (0-6), Sunday = 0
f6bcfd97 3048 if ( !GetNumericToken(width, input, &num) || (wday > 6) )
f0f951fa
VZ
3049 {
3050 // no match
3051 return (wxChar *)NULL;
3052 }
3053
68379eaf 3054 haveWDay = true;
f0f951fa
VZ
3055 wday = (WeekDay)num;
3056 break;
3057
3058 case _T('x'): // locale default date representation
3059#ifdef HAVE_STRPTIME
2423ef22 3060 // try using strptime() -- it may fail even if the input is
f0f951fa 3061 // correct but the date is out of range, so we will fall back
2423ef22 3062 // to our generic code anyhow
f0f951fa
VZ
3063 {
3064 struct tm tm;
2423ef22
VZ
3065
3066 const wxChar *result = CallStrptime(input, "%x", &tm);
f0f951fa
VZ
3067 if ( result )
3068 {
3069 input = result;
3070
68379eaf 3071 haveDay = haveMon = haveYear = true;
f0f951fa
VZ
3072
3073 year = 1900 + tm.tm_year;
3074 mon = (Month)tm.tm_mon;
3075 mday = tm.tm_mday;
3076
3077 break;
3078 }
3079 }
3080#endif // HAVE_STRPTIME
3081
3082 // TODO query the LOCALE_IDATE setting under Win32
3083 {
3084 wxDateTime dt;
3085
41acf5c0 3086 wxString fmtDate, fmtDateAlt;
f0f951fa
VZ
3087 if ( IsWestEuropeanCountry(GetCountry()) ||
3088 GetCountry() == Russia )
3089 {
3090 fmtDate = _T("%d/%m/%y");
41acf5c0 3091 fmtDateAlt = _T("%m/%d/%y");
f0f951fa
VZ
3092 }
3093 else // assume USA
3094 {
3095 fmtDate = _T("%m/%d/%y");
41acf5c0 3096 fmtDateAlt = _T("%d/%m/%y");
f0f951fa
VZ
3097 }
3098
3099 const wxChar *result = dt.ParseFormat(input, fmtDate);
41acf5c0
VZ
3100
3101 if ( !result )
3102 {
3103 // ok, be nice and try another one
3104 result = dt.ParseFormat(input, fmtDateAlt);
3105 }
3106
f0f951fa
VZ
3107 if ( !result )
3108 {
3109 // bad luck
3110 return (wxChar *)NULL;
3111 }
3112
3113 Tm tm = dt.GetTm();
3114
68379eaf 3115 haveDay = haveMon = haveYear = true;
f0f951fa
VZ
3116
3117 year = tm.year;
3118 mon = tm.mon;
3119 mday = tm.mday;
3120
3121 input = result;
3122 }
3123
3124 break;
3125
3126 case _T('X'): // locale default time representation
3127#ifdef HAVE_STRPTIME
3128 {
3129 // use strptime() to do it for us (FIXME !Unicode friendly)
3130 struct tm tm;
2423ef22 3131 input = CallStrptime(input, "%X", &tm);
f0f951fa
VZ
3132 if ( !input )
3133 {
3134 return (wxChar *)NULL;
3135 }
3136
68379eaf 3137 haveHour = haveMin = haveSec = true;
f0f951fa
VZ
3138
3139 hour = tm.tm_hour;
3140 min = tm.tm_min;
3141 sec = tm.tm_sec;
3142 }
3143#else // !HAVE_STRPTIME
3144 // TODO under Win32 we can query the LOCALE_ITIME system
3145 // setting which says whether the default time format is
3146 // 24 or 12 hour
3147 {
3148 // try to parse what follows as "%H:%M:%S" and, if this
3149 // fails, as "%I:%M:%S %p" - this should catch the most
3150 // common cases
3151 wxDateTime dt;
3152
3153 const wxChar *result = dt.ParseFormat(input, _T("%T"));
3154 if ( !result )
3155 {
3156 result = dt.ParseFormat(input, _T("%r"));
3157 }
3158
3159 if ( !result )
3160 {
3161 // no match
3162 return (wxChar *)NULL;
3163 }
3164
68379eaf 3165 haveHour = haveMin = haveSec = true;
f0f951fa
VZ
3166
3167 Tm tm = dt.GetTm();
3168 hour = tm.hour;
3169 min = tm.min;
3170 sec = tm.sec;
3171
3172 input = result;
3173 }
3174#endif // HAVE_STRPTIME/!HAVE_STRPTIME
3175 break;
3176
3177 case _T('y'): // year without century (00-99)
f6bcfd97 3178 if ( !GetNumericToken(width, input, &num) || (num > 99) )
f0f951fa
VZ
3179 {
3180 // no match
3181 return (wxChar *)NULL;
3182 }
3183
68379eaf 3184 haveYear = true;
f6bcfd97
BP
3185
3186 // TODO should have an option for roll over date instead of
3187 // hard coding it here
3188 year = (num > 30 ? 1900 : 2000) + (wxDateTime_t)num;
f0f951fa
VZ
3189 break;
3190
3191 case _T('Y'): // year with century
f6bcfd97 3192 if ( !GetNumericToken(width, input, &num) )
f0f951fa
VZ
3193 {
3194 // no match
3195 return (wxChar *)NULL;
3196 }
3197
68379eaf 3198 haveYear = true;
be4017f8 3199 year = (wxDateTime_t)num;
f0f951fa
VZ
3200 break;
3201
3202 case _T('Z'): // timezone name
3203 wxFAIL_MSG(_T("TODO"));
3204 break;
3205
3206 case _T('%'): // a percent sign
3207 if ( *input++ != _T('%') )
3208 {
3209 // no match
3210 return (wxChar *)NULL;
3211 }
3212 break;
3213
3214 case 0: // the end of string
3215 wxFAIL_MSG(_T("unexpected format end"));
3216
3217 // fall through
3218
3219 default: // not a known format spec
3220 return (wxChar *)NULL;
3221 }
3222 }
3223
3224 // format matched, try to construct a date from what we have now
3225 Tm tmDef;
3226 if ( dateDef.IsValid() )
3227 {
3228 // take this date as default
3229 tmDef = dateDef.GetTm();
3230 }
c3302e7e 3231 else if ( IsValid() )
f0f951fa
VZ
3232 {
3233 // if this date is valid, don't change it
3234 tmDef = GetTm();
3235 }
3236 else
3237 {
3238 // no default and this date is invalid - fall back to Today()
3239 tmDef = Today().GetTm();
3240 }
3241
3242 Tm tm = tmDef;
3243
3244 // set the date
3245 if ( haveYear )
3246 {
3247 tm.year = year;
3248 }
3249
3250 // TODO we don't check here that the values are consistent, if both year
3251 // day and month/day were found, we just ignore the year day and we
3252 // also always ignore the week day
3253 if ( haveMon && haveDay )
3254 {
be4017f8
VZ
3255 if ( mday > GetNumOfDaysInMonth(tm.year, mon) )
3256 {
3257 wxLogDebug(_T("bad month day in wxDateTime::ParseFormat"));
3258
3259 return (wxChar *)NULL;
3260 }
3261
f0f951fa
VZ
3262 tm.mon = mon;
3263 tm.mday = mday;
3264 }
3265 else if ( haveYDay )
3266 {
be4017f8
VZ
3267 if ( yday > GetNumberOfDays(tm.year) )
3268 {
3269 wxLogDebug(_T("bad year day in wxDateTime::ParseFormat"));
3270
3271 return (wxChar *)NULL;
3272 }
3273
f0f951fa
VZ
3274 Tm tm2 = wxDateTime(1, Jan, tm.year).SetToYearDay(yday).GetTm();
3275
3276 tm.mon = tm2.mon;
3277 tm.mday = tm2.mday;
3278 }
3279
3280 // deal with AM/PM
3281 if ( haveHour && hourIsIn12hFormat && isPM )
3282 {
3283 // translate to 24hour format
3284 hour += 12;
3285 }
3286 //else: either already in 24h format or no translation needed
3287
3288 // set the time
3289 if ( haveHour )
3290 {
5f287370 3291 tm.hour = hour;
f0f951fa
VZ
3292 }
3293
3294 if ( haveMin )
3295 {
3296 tm.min = min;
3297 }
3298
3299 if ( haveSec )
3300 {
3301 tm.sec = sec;
3302 }
3303
3304 Set(tm);
3305
88a036ce
VZ
3306 // finally check that the week day is consistent -- if we had it
3307 if ( haveWDay && GetWeekDay() != wday )
3308 {
3309 wxLogDebug(_T("inconsistsnet week day in wxDateTime::ParseFormat()"));
3310
3311 return NULL;
3312 }
3313
f0f951fa 3314 return input;
cd0b1709
VZ
3315}
3316
3317const wxChar *wxDateTime::ParseDateTime(const wxChar *date)
3318{
3319 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
3320
de07d200
VZ
3321 // Set to current day and hour, so strings like '14:00' becomes today at
3322 // 14, not some other random date
3323 wxDateTime dtDate = wxDateTime::Today();
3324 wxDateTime dtTime = wxDateTime::Today();
cd0b1709 3325
de07d200
VZ
3326 const wxChar* pchTime;
3327
3328 // Try to parse the beginning of the string as a date
3329 const wxChar* pchDate = dtDate.ParseDate(date);
3330
3331 // We got a date in the beginning, see if there is a time specified after the date
3332 if ( pchDate )
3333 {
3334 // Skip spaces, as the ParseTime() function fails on spaces
3335 while ( wxIsspace(*pchDate) )
3336 pchDate++;
3337
3338 pchTime = dtTime.ParseTime(pchDate);
3339 }
3340 else // no date in the beginning
3341 {
3342 // check and see if we have a time followed by a date
3343 pchTime = dtTime.ParseTime(date);
3344 if ( pchTime )
3345 {
3346 while ( wxIsspace(*pchTime) )
3347 pchTime++;
3348
3349 pchDate = dtDate.ParseDate(pchTime);
3350 }
3351 }
3352
3353 // If we have a date specified, set our own data to the same date
3354 if ( !pchDate || !pchTime )
3355 return NULL;
3356
3357 Set(dtDate.GetDay(), dtDate.GetMonth(), dtDate.GetYear(),
3358 dtTime.GetHour(), dtTime.GetMinute(), dtTime.GetSecond(),
3359 dtTime.GetMillisecond());
3360
3361 // Return endpoint of scan
3362 return pchDate > pchTime ? pchDate : pchTime;
cd0b1709
VZ
3363}
3364
3365const wxChar *wxDateTime::ParseDate(const wxChar *date)
3366{
3367 // this is a simplified version of ParseDateTime() which understands only
3368 // "today" (for wxDate compatibility) and digits only otherwise (and not
3369 // all esoteric constructions ParseDateTime() knows about)
3370
3371 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
3372
3373 const wxChar *p = date;
3374 while ( wxIsspace(*p) )
3375 p++;
3376
fd9f9f4c
VZ
3377 // some special cases
3378 static struct
cd0b1709 3379 {
fd9f9f4c
VZ
3380 const wxChar *str;
3381 int dayDiffFromToday;
3382 } literalDates[] =
3383 {
3384 { wxTRANSLATE("today"), 0 },
3385 { wxTRANSLATE("yesterday"), -1 },
3386 { wxTRANSLATE("tomorrow"), 1 },
3387 };
3388
3389 for ( size_t n = 0; n < WXSIZEOF(literalDates); n++ )
3390 {
3391 wxString date = wxGetTranslation(literalDates[n].str);
3392 size_t len = date.length();
907173e5 3393 if ( wxStrlen(p) >= len )
fd9f9f4c 3394 {
907173e5
WS
3395 wxString str(p, len);
3396 if ( str.CmpNoCase(date) == 0 )
fd9f9f4c 3397 {
907173e5
WS
3398 // nothing can follow this, so stop here
3399 p += len;
3400
3401 int dayDiffFromToday = literalDates[n].dayDiffFromToday;
3402 *this = Today();
3403 if ( dayDiffFromToday )
3404 {
3405 *this += wxDateSpan::Days(dayDiffFromToday);
3406 }
cd0b1709 3407
907173e5
WS
3408 return p;
3409 }
fd9f9f4c 3410 }
cd0b1709
VZ
3411 }
3412
3ca6a5f0
BP
3413 // We try to guess what we have here: for each new (numeric) token, we
3414 // determine if it can be a month, day or a year. Of course, there is an
3415 // ambiguity as some numbers may be days as well as months, so we also
3416 // have the ability to back track.
3417
cd0b1709 3418 // what do we have?
68379eaf
WS
3419 bool haveDay = false, // the months day?
3420 haveWDay = false, // the day of week?
3421 haveMon = false, // the month?
3422 haveYear = false; // the year?
cd0b1709
VZ
3423
3424 // and the value of the items we have (init them to get rid of warnings)
3425 WeekDay wday = Inv_WeekDay;
3426 wxDateTime_t day = 0;
3427 wxDateTime::Month mon = Inv_Month;
3428 int year = 0;
3429
3430 // tokenize the string
f6bcfd97 3431 size_t nPosCur = 0;
62ae2780 3432 static const wxChar *dateDelimiters = _T(".,/-\t\r\n ");
f6bcfd97 3433 wxStringTokenizer tok(p, dateDelimiters);
cd0b1709
VZ
3434 while ( tok.HasMoreTokens() )
3435 {
3436 wxString token = tok.GetNextToken();
3ca6a5f0
BP
3437 if ( !token )
3438 continue;
cd0b1709
VZ
3439
3440 // is it a number?
3441 unsigned long val;
fde5a86b 3442 if ( token.ToULong(&val) )
cd0b1709
VZ
3443 {
3444 // guess what this number is
3445
68379eaf
WS
3446 bool isDay = false,
3447 isMonth = false,
3448 isYear = false;
3ca6a5f0
BP
3449
3450 if ( !haveMon && val > 0 && val <= 12 )
cd0b1709 3451 {
3ca6a5f0 3452 // assume it is month
68379eaf 3453 isMonth = true;
3ca6a5f0
BP
3454 }
3455 else // not the month
3456 {
560e2916 3457 if ( haveDay )
cd0b1709 3458 {
560e2916 3459 // this can only be the year
68379eaf 3460 isYear = true;
cd0b1709 3461 }
560e2916 3462 else // may be either day or year
cd0b1709 3463 {
42841dfc
WS
3464 wxDateTime_t maxDays = (wxDateTime_t)(
3465 haveMon
560e2916 3466 ? GetNumOfDaysInMonth(haveYear ? year : Inv_Year, mon)
42841dfc
WS
3467 : 31
3468 );
560e2916
VZ
3469
3470 // can it be day?
3471 if ( (val == 0) || (val > (unsigned long)maxDays) )
3472 {
3473 // no
68379eaf 3474 isYear = true;
560e2916
VZ
3475 }
3476 else // yes, suppose it's the day
3477 {
68379eaf 3478 isDay = true;
560e2916 3479 }
cd0b1709
VZ
3480 }
3481 }
3482
cd0b1709
VZ
3483 if ( isYear )
3484 {
3485 if ( haveYear )
cd0b1709 3486 break;
cd0b1709 3487
68379eaf 3488 haveYear = true;
cd0b1709 3489
479cd5de 3490 year = (wxDateTime_t)val;
cd0b1709 3491 }
3ca6a5f0 3492 else if ( isDay )
cd0b1709 3493 {
3ca6a5f0 3494 if ( haveDay )
cd0b1709 3495 break;
cd0b1709 3496
68379eaf 3497 haveDay = true;
cd0b1709 3498
3ca6a5f0 3499 day = (wxDateTime_t)val;
cd0b1709 3500 }
3ca6a5f0 3501 else if ( isMonth )
cd0b1709 3502 {
68379eaf 3503 haveMon = true;
cd0b1709 3504
3ca6a5f0 3505 mon = (Month)(val - 1);
cd0b1709
VZ
3506 }
3507 }
3508 else // not a number
3509 {
f6bcfd97
BP
3510 // be careful not to overwrite the current mon value
3511 Month mon2 = GetMonthFromName(token, Name_Full | Name_Abbr);
3512 if ( mon2 != Inv_Month )
cd0b1709
VZ
3513 {
3514 // it's a month
3515 if ( haveMon )
3516 {
6411a32c
VZ
3517 // but we already have a month - maybe we guessed wrong?
3518 if ( !haveDay )
3519 {
f5cd9787 3520 // no need to check in month range as always < 12, but
6411a32c 3521 // the days are counted from 1 unlike the months
42841dfc 3522 day = (wxDateTime_t)(mon + 1);
68379eaf 3523 haveDay = true;
6411a32c
VZ
3524 }
3525 else
3526 {
3527 // could possible be the year (doesn't the year come
3528 // before the month in the japanese format?) (FIXME)
3529 break;
f5cd9787 3530 }
cd0b1709
VZ
3531 }
3532
f6bcfd97
BP
3533 mon = mon2;
3534
68379eaf 3535 haveMon = true;
cd0b1709 3536 }
6411a32c 3537 else // not a valid month name
cd0b1709 3538 {
f0f951fa 3539 wday = GetWeekDayFromName(token, Name_Full | Name_Abbr);
cd0b1709
VZ
3540 if ( wday != Inv_WeekDay )
3541 {
3542 // a week day
3543 if ( haveWDay )
3544 {
3545 break;
3546 }
3547
68379eaf 3548 haveWDay = true;
cd0b1709 3549 }
6411a32c 3550 else // not a valid weekday name
cd0b1709
VZ
3551 {
3552 // try the ordinals
3553 static const wxChar *ordinals[] =
3554 {
3555 wxTRANSLATE("first"),
3556 wxTRANSLATE("second"),
3557 wxTRANSLATE("third"),
3558 wxTRANSLATE("fourth"),
3559 wxTRANSLATE("fifth"),
3560 wxTRANSLATE("sixth"),
3561 wxTRANSLATE("seventh"),
3562 wxTRANSLATE("eighth"),
3563 wxTRANSLATE("ninth"),
3564 wxTRANSLATE("tenth"),
3565 wxTRANSLATE("eleventh"),
3566 wxTRANSLATE("twelfth"),
3567 wxTRANSLATE("thirteenth"),
3568 wxTRANSLATE("fourteenth"),
3569 wxTRANSLATE("fifteenth"),
3570 wxTRANSLATE("sixteenth"),
3571 wxTRANSLATE("seventeenth"),
3572 wxTRANSLATE("eighteenth"),
3573 wxTRANSLATE("nineteenth"),
3574 wxTRANSLATE("twentieth"),
3575 // that's enough - otherwise we'd have problems with
6411a32c 3576 // composite (or not) ordinals
cd0b1709
VZ
3577 };
3578
3579 size_t n;
3580 for ( n = 0; n < WXSIZEOF(ordinals); n++ )
3581 {
3582 if ( token.CmpNoCase(ordinals[n]) == 0 )
3583 {
3584 break;
3585 }
3586 }
3587
3588 if ( n == WXSIZEOF(ordinals) )
3589 {
3590 // stop here - something unknown
3591 break;
3592 }
3593
3594 // it's a day
3595 if ( haveDay )
3596 {
3597 // don't try anything here (as in case of numeric day
3598 // above) - the symbolic day spec should always
3599 // precede the month/year
3600 break;
3601 }
3602
68379eaf 3603 haveDay = true;
cd0b1709 3604
33ac7e6f 3605 day = (wxDateTime_t)(n + 1);
cd0b1709
VZ
3606 }
3607 }
3608 }
f6bcfd97
BP
3609
3610 nPosCur = tok.GetPosition();
cd0b1709
VZ
3611 }
3612
3613 // either no more tokens or the scan was stopped by something we couldn't
3614 // parse - in any case, see if we can construct a date from what we have
3615 if ( !haveDay && !haveWDay )
3616 {
f6bcfd97 3617 wxLogDebug(_T("ParseDate: no day, no weekday hence no date."));
cd0b1709
VZ
3618
3619 return (wxChar *)NULL;
3620 }
3621
3622 if ( haveWDay && (haveMon || haveYear || haveDay) &&
f6bcfd97 3623 !(haveDay && haveMon && haveYear) )
cd0b1709
VZ
3624 {
3625 // without adjectives (which we don't support here) the week day only
3626 // makes sense completely separately or with the full date
3627 // specification (what would "Wed 1999" mean?)
3628 return (wxChar *)NULL;
3629 }
3630
f6bcfd97
BP
3631 if ( !haveWDay && haveYear && !(haveDay && haveMon) )
3632 {
3633 // may be we have month and day instead of day and year?
3634 if ( haveDay && !haveMon )
3635 {
3636 if ( day <= 12 )
3637 {
3638 // exchange day and month
3639 mon = (wxDateTime::Month)(day - 1);
3640
3641 // we're in the current year then
196be0f1 3642 if ( (year > 0) && (year <= (int)GetNumOfDaysInMonth(Inv_Year, mon)) )
f6bcfd97 3643 {
42841dfc 3644 day = (wxDateTime_t)year;
f6bcfd97 3645
68379eaf
WS
3646 haveMon = true;
3647 haveYear = false;
f6bcfd97 3648 }
68379eaf 3649 //else: no, can't exchange, leave haveMon == false
f6bcfd97
BP
3650 }
3651 }
3652
3653 if ( !haveMon )
3654 {
3655 // if we give the year, month and day must be given too
3656 wxLogDebug(_T("ParseDate: day and month should be specified if year is."));
3657
3658 return (wxChar *)NULL;
3659 }
3660 }
3661
cd0b1709
VZ
3662 if ( !haveMon )
3663 {
3664 mon = GetCurrentMonth();
3665 }
3666
3667 if ( !haveYear )
3668 {
3669 year = GetCurrentYear();
3670 }
3671
3672 if ( haveDay )
3673 {
3674 Set(day, mon, year);
3675
3676 if ( haveWDay )
3677 {
3678 // check that it is really the same
3679 if ( GetWeekDay() != wday )
3680 {
3681 // inconsistency detected
f6bcfd97
BP
3682 wxLogDebug(_T("ParseDate: inconsistent day/weekday."));
3683
cd0b1709
VZ
3684 return (wxChar *)NULL;
3685 }
3686 }
3687 }
3688 else // haveWDay
3689 {
3690 *this = Today();
3691
3692 SetToWeekDayInSameWeek(wday);
3693 }
3694
f6bcfd97
BP
3695 // return the pointer to the first unparsed char
3696 p += nPosCur;
3697 if ( nPosCur && wxStrchr(dateDelimiters, *(p - 1)) )
3698 {
3699 // if we couldn't parse the token after the delimiter, put back the
3700 // delimiter as well
3701 p--;
3702 }
3703
3704 return p;
cd0b1709
VZ
3705}
3706
3707const wxChar *wxDateTime::ParseTime(const wxChar *time)
3708{
3709 wxCHECK_MSG( time, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
3710
f0f951fa
VZ
3711 // first try some extra things
3712 static const struct
3713 {
3714 const wxChar *name;
3715 wxDateTime_t hour;
3716 } stdTimes[] =
3717 {
3718 { wxTRANSLATE("noon"), 12 },
3719 { wxTRANSLATE("midnight"), 00 },
3720 // anything else?
3721 };
cd0b1709 3722
f0f951fa
VZ
3723 for ( size_t n = 0; n < WXSIZEOF(stdTimes); n++ )
3724 {
3725 wxString timeString = wxGetTranslation(stdTimes[n].name);
3726 size_t len = timeString.length();
3727 if ( timeString.CmpNoCase(wxString(time, len)) == 0 )
3728 {
4ded51f2
CE
3729 // casts required by DigitalMars
3730 Set(stdTimes[n].hour, wxDateTime_t(0), wxDateTime_t(0));
f0f951fa
VZ
3731
3732 return time + len;
3733 }
3734 }
3735
c7f9a482
VZ
3736 // try all time formats we may think about in the order from longest to
3737 // shortest
3738
3739 // 12hour with AM/PM?
3740 const wxChar *result = ParseFormat(time, _T("%I:%M:%S %p"));
3741
f0f951fa
VZ
3742 if ( !result )
3743 {
3744 // normally, it's the same, but why not try it?
3745 result = ParseFormat(time, _T("%H:%M:%S"));
3746 }
3747
3748 if ( !result )
3749 {
c7f9a482
VZ
3750 // 12hour with AM/PM but without seconds?
3751 result = ParseFormat(time, _T("%I:%M %p"));
f0f951fa
VZ
3752 }
3753
3754 if ( !result )
3755 {
3756 // without seconds?
3757 result = ParseFormat(time, _T("%H:%M"));
3758 }
3759
3760 if ( !result )
3761 {
c7f9a482
VZ
3762 // just the hour and AM/PM?
3763 result = ParseFormat(time, _T("%I %p"));
f0f951fa
VZ
3764 }
3765
3766 if ( !result )
3767 {
3768 // just the hour?
3769 result = ParseFormat(time, _T("%H"));
3770 }
3771
3772 if ( !result )
3773 {
c7f9a482
VZ
3774 // parse the standard format: normally it is one of the formats above
3775 // but it may be set to something completely different by the user
3776 result = ParseFormat(time, _T("%X"));
f0f951fa
VZ
3777 }
3778
3779 // TODO: parse timezones
3780
3781 return result;
cd0b1709
VZ
3782}
3783
4f6aed9c
VZ
3784// ----------------------------------------------------------------------------
3785// Workdays and holidays support
3786// ----------------------------------------------------------------------------
3787
3788bool wxDateTime::IsWorkDay(Country WXUNUSED(country)) const
3789{
3790 return !wxDateTimeHolidayAuthority::IsHoliday(*this);
3791}
3792
fcc3d7cb
VZ
3793// ============================================================================
3794// wxTimeSpan
3795// ============================================================================
3796
033400eb
VZ
3797// this enum is only used in wxTimeSpan::Format() below but we can't declare
3798// it locally to the method as it provokes an internal compiler error in egcs
3799// 2.91.60 when building with -O2
3800enum TimeSpanPart
3801{
3802 Part_Week,
3803 Part_Day,
3804 Part_Hour,
3805 Part_Min,
3806 Part_Sec,
3807 Part_MSec
3808};
3809
e6ec579c
VZ
3810// not all strftime(3) format specifiers make sense here because, for example,
3811// a time span doesn't have a year nor a timezone
3812//
3813// Here are the ones which are supported (all of them are supported by strftime
3814// as well):
3815// %H hour in 24 hour format
3816// %M minute (00 - 59)
3817// %S second (00 - 59)
3818// %% percent sign
3819//
3820// Also, for MFC CTimeSpan compatibility, we support
3821// %D number of days
3822//
3823// And, to be better than MFC :-), we also have
3824// %E number of wEeks
3825// %l milliseconds (000 - 999)
fcc3d7cb
VZ
3826wxString wxTimeSpan::Format(const wxChar *format) const
3827{
e6ec579c 3828 wxCHECK_MSG( format, _T(""), _T("NULL format in wxTimeSpan::Format") );
fcc3d7cb
VZ
3829
3830 wxString str;
5b735202 3831 str.Alloc(wxStrlen(format));
e6ec579c 3832
df05cdc5
VZ
3833 // Suppose we have wxTimeSpan ts(1 /* hour */, 2 /* min */, 3 /* sec */)
3834 //
3835 // Then, of course, ts.Format("%H:%M:%S") must return "01:02:03", but the
3836 // question is what should ts.Format("%S") do? The code here returns "3273"
3837 // in this case (i.e. the total number of seconds, not just seconds % 60)
3838 // because, for me, this call means "give me entire time interval in
3839 // seconds" and not "give me the seconds part of the time interval"
3840 //
3841 // If we agree that it should behave like this, it is clear that the
3842 // interpretation of each format specifier depends on the presence of the
3843 // other format specs in the string: if there was "%H" before "%M", we
3844 // should use GetMinutes() % 60, otherwise just GetMinutes() &c
3845
3846 // we remember the most important unit found so far
033400eb 3847 TimeSpanPart partBiggest = Part_MSec;
df05cdc5 3848
f6bcfd97 3849 for ( const wxChar *pch = format; *pch; pch++ )
e6ec579c
VZ
3850 {
3851 wxChar ch = *pch;
3852
f6bcfd97 3853 if ( ch == _T('%') )
e6ec579c 3854 {
df05cdc5
VZ
3855 // the start of the format specification of the printf() below
3856 wxString fmtPrefix = _T('%');
3857
3858 // the number
3859 long n;
e6ec579c 3860
f6bcfd97 3861 ch = *++pch; // get the format spec char
e6ec579c
VZ
3862 switch ( ch )
3863 {
3864 default:
3865 wxFAIL_MSG( _T("invalid format character") );
3866 // fall through
3867
f6bcfd97 3868 case _T('%'):
df05cdc5
VZ
3869 str += ch;
3870
3871 // skip the part below switch
3872 continue;
e6ec579c 3873
f6bcfd97 3874 case _T('D'):
df05cdc5
VZ
3875 n = GetDays();
3876 if ( partBiggest < Part_Day )
3877 {
3878 n %= DAYS_PER_WEEK;
3879 }
3880 else
3881 {
3882 partBiggest = Part_Day;
3883 }
e6ec579c
VZ
3884 break;
3885
f6bcfd97 3886 case _T('E'):
df05cdc5
VZ
3887 partBiggest = Part_Week;
3888 n = GetWeeks();
e6ec579c
VZ
3889 break;
3890
f6bcfd97 3891 case _T('H'):
df05cdc5
VZ
3892 n = GetHours();
3893 if ( partBiggest < Part_Hour )
3894 {
3895 n %= HOURS_PER_DAY;
3896 }
3897 else
3898 {
3899 partBiggest = Part_Hour;
3900 }
3901
3902 fmtPrefix += _T("02");
e6ec579c
VZ
3903 break;
3904
f6bcfd97 3905 case _T('l'):
df05cdc5
VZ
3906 n = GetMilliseconds().ToLong();
3907 if ( partBiggest < Part_MSec )
3908 {
3909 n %= 1000;
3910 }
3911 //else: no need to reset partBiggest to Part_MSec, it is
3912 // the least significant one anyhow
3913
3914 fmtPrefix += _T("03");
e6ec579c
VZ
3915 break;
3916
f6bcfd97 3917 case _T('M'):
df05cdc5
VZ
3918 n = GetMinutes();
3919 if ( partBiggest < Part_Min )
3920 {
3921 n %= MIN_PER_HOUR;
3922 }
3923 else
3924 {
3925 partBiggest = Part_Min;
3926 }
3927
3928 fmtPrefix += _T("02");
e6ec579c
VZ
3929 break;
3930
f6bcfd97 3931 case _T('S'):
df05cdc5
VZ
3932 n = GetSeconds().ToLong();
3933 if ( partBiggest < Part_Sec )
3934 {
3935 n %= SEC_PER_MIN;
3936 }
3937 else
3938 {
3939 partBiggest = Part_Sec;
3940 }
3941
3942 fmtPrefix += _T("02");
e6ec579c
VZ
3943 break;
3944 }
3945
df05cdc5
VZ
3946 str += wxString::Format(fmtPrefix + _T("ld"), n);
3947 }
3948 else
3949 {
3950 // normal character, just copy
3951 str += ch;
e6ec579c 3952 }
e6ec579c 3953 }
fcc3d7cb
VZ
3954
3955 return str;
3956}
4f6aed9c
VZ
3957
3958// ============================================================================
3959// wxDateTimeHolidayAuthority and related classes
3960// ============================================================================
3961
3962#include "wx/arrimpl.cpp"
3963
f6bcfd97 3964WX_DEFINE_OBJARRAY(wxDateTimeArray);
4f6aed9c
VZ
3965
3966static int wxCMPFUNC_CONV
3967wxDateTimeCompareFunc(wxDateTime **first, wxDateTime **second)
3968{
3969 wxDateTime dt1 = **first,
3970 dt2 = **second;
3971
3972 return dt1 == dt2 ? 0 : dt1 < dt2 ? -1 : +1;
3973}
3974
3975// ----------------------------------------------------------------------------
3976// wxDateTimeHolidayAuthority
3977// ----------------------------------------------------------------------------
3978
3979wxHolidayAuthoritiesArray wxDateTimeHolidayAuthority::ms_authorities;
3980
3981/* static */
3982bool wxDateTimeHolidayAuthority::IsHoliday(const wxDateTime& dt)
3983{
df5168c4 3984 size_t count = ms_authorities.size();
4f6aed9c
VZ
3985 for ( size_t n = 0; n < count; n++ )
3986 {
3987 if ( ms_authorities[n]->DoIsHoliday(dt) )
3988 {
68379eaf 3989 return true;
4f6aed9c
VZ
3990 }
3991 }
3992
68379eaf 3993 return false;
4f6aed9c
VZ
3994}
3995
3996/* static */
3997size_t
3998wxDateTimeHolidayAuthority::GetHolidaysInRange(const wxDateTime& dtStart,
3999 const wxDateTime& dtEnd,
4000 wxDateTimeArray& holidays)
4001{
4002 wxDateTimeArray hol;
4003
df5168c4 4004 holidays.Clear();
4f6aed9c 4005
df5168c4 4006 size_t count = ms_authorities.size();
6dc6fda6 4007 for ( size_t nAuth = 0; nAuth < count; nAuth++ )
4f6aed9c 4008 {
6dc6fda6 4009 ms_authorities[nAuth]->DoGetHolidaysInRange(dtStart, dtEnd, hol);
4f6aed9c
VZ
4010
4011 WX_APPEND_ARRAY(holidays, hol);
4012 }
4013
4014 holidays.Sort(wxDateTimeCompareFunc);
4015
df5168c4 4016 return holidays.size();
4f6aed9c
VZ
4017}
4018
4019/* static */
4020void wxDateTimeHolidayAuthority::ClearAllAuthorities()
4021{
4022 WX_CLEAR_ARRAY(ms_authorities);
4023}
4024
4025/* static */
4026void wxDateTimeHolidayAuthority::AddAuthority(wxDateTimeHolidayAuthority *auth)
4027{
df5168c4 4028 ms_authorities.push_back(auth);
4f6aed9c
VZ
4029}
4030
5e233068
WS
4031wxDateTimeHolidayAuthority::~wxDateTimeHolidayAuthority()
4032{
4033 // required here for Darwin
4034}
4035
4f6aed9c
VZ
4036// ----------------------------------------------------------------------------
4037// wxDateTimeWorkDays
4038// ----------------------------------------------------------------------------
4039
4040bool wxDateTimeWorkDays::DoIsHoliday(const wxDateTime& dt) const
4041{
4042 wxDateTime::WeekDay wd = dt.GetWeekDay();
4043
4044 return (wd == wxDateTime::Sun) || (wd == wxDateTime::Sat);
4045}
4046
4047size_t wxDateTimeWorkDays::DoGetHolidaysInRange(const wxDateTime& dtStart,
4048 const wxDateTime& dtEnd,
4049 wxDateTimeArray& holidays) const
4050{
4051 if ( dtStart > dtEnd )
4052 {
4053 wxFAIL_MSG( _T("invalid date range in GetHolidaysInRange") );
4054
4055 return 0u;
4056 }
4057
4058 holidays.Empty();
4059
4060 // instead of checking all days, start with the first Sat after dtStart and
4061 // end with the last Sun before dtEnd
4062 wxDateTime dtSatFirst = dtStart.GetNextWeekDay(wxDateTime::Sat),
4063 dtSatLast = dtEnd.GetPrevWeekDay(wxDateTime::Sat),
4064 dtSunFirst = dtStart.GetNextWeekDay(wxDateTime::Sun),
4065 dtSunLast = dtEnd.GetPrevWeekDay(wxDateTime::Sun),
4066 dt;
4067
4068 for ( dt = dtSatFirst; dt <= dtSatLast; dt += wxDateSpan::Week() )
4069 {
4070 holidays.Add(dt);
4071 }
4072
4073 for ( dt = dtSunFirst; dt <= dtSunLast; dt += wxDateSpan::Week() )
4074 {
4075 holidays.Add(dt);
4076 }
4077
4078 return holidays.GetCount();
4079}
3e0b743f 4080
1e6feb95 4081#endif // wxUSE_DATETIME