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