]> git.saurik.com Git - wxWidgets.git/blame - src/common/datetime.cpp
Add support for wxSL_INVERSE flag. Also uses new inverse logic to make Mac vertical...
[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
VZ
805{
806 wxCHECK_MSG( month != Inv_Month, _T(""), _T("invalid month") );
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
VZ
820{
821 wxCHECK_MSG( wday != Inv_WeekDay, _T(""), _T("invalid weekday") );
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
b76b015e 1216 (void)Set(*tm);
0979c962
VZ
1217
1218 // and finally adjust milliseconds
1219 return SetMillisecond(millisec);
1220}
1221
1222wxDateTime& wxDateTime::Set(wxDateTime_t day,
1223 Month month,
1224 int year,
1225 wxDateTime_t hour,
1226 wxDateTime_t minute,
1227 wxDateTime_t second,
1228 wxDateTime_t millisec)
1229{
384223b3
VZ
1230 wxDATETIME_CHECK( hour < 24 &&
1231 second < 62 &&
1232 minute < 60 &&
1233 millisec < 1000,
1234 _T("Invalid time in wxDateTime::Set()") );
0979c962 1235
2f02cb89 1236 ReplaceDefaultYearMonthWithCurrent(&year, &month);
0979c962 1237
384223b3
VZ
1238 wxDATETIME_CHECK( (0 < day) && (day <= GetNumberOfDays(month, year)),
1239 _T("Invalid date in wxDateTime::Set()") );
0979c962
VZ
1240
1241 // the range of time_t type (inclusive)
1242 static const int yearMinInRange = 1970;
1243 static const int yearMaxInRange = 2037;
1244
1245 // test only the year instead of testing for the exact end of the Unix
1246 // time_t range - it doesn't bring anything to do more precise checks
2f02cb89 1247 if ( year >= yearMinInRange && year <= yearMaxInRange )
0979c962
VZ
1248 {
1249 // use the standard library version if the date is in range - this is
b76b015e 1250 // probably more efficient than our code
0979c962 1251 struct tm tm;
b76b015e 1252 tm.tm_year = year - 1900;
0979c962
VZ
1253 tm.tm_mon = month;
1254 tm.tm_mday = day;
1255 tm.tm_hour = hour;
1256 tm.tm_min = minute;
1257 tm.tm_sec = second;
299fcbfe 1258 tm.tm_isdst = -1; // mktime() will guess it
0979c962
VZ
1259
1260 (void)Set(tm);
1261
1262 // and finally adjust milliseconds
3d78a532
RD
1263 if (IsValid())
1264 SetMillisecond(millisec);
1265
1266 return *this;
0979c962
VZ
1267 }
1268 else
1269 {
1270 // do time calculations ourselves: we want to calculate the number of
fcc3d7cb 1271 // milliseconds between the given date and the epoch
e6ec579c
VZ
1272
1273 // get the JDN for the midnight of this day
1274 m_time = GetTruncatedJDN(day, month, year);
1275 m_time -= EPOCH_JDN;
1276 m_time *= SECONDS_PER_DAY * TIME_T_FACTOR;
1277
299fcbfe
VZ
1278 // JDN corresponds to GMT, we take localtime
1279 Add(wxTimeSpan(hour, minute, second + GetTimeZone(), millisec));
b76b015e
VZ
1280 }
1281
1282 return *this;
1283}
1284
e6ec579c
VZ
1285wxDateTime& wxDateTime::Set(double jdn)
1286{
1ef54dcf
VZ
1287 // so that m_time will be 0 for the midnight of Jan 1, 1970 which is jdn
1288 // EPOCH_JDN + 0.5
1289 jdn -= EPOCH_JDN + 0.5;
1290
cd0b1709
VZ
1291 jdn *= MILLISECONDS_PER_DAY;
1292
8cc00d5f
VZ
1293 // JDNs always suppose an UTC date, so bring it back to local time zone
1294 // (also see GetJulianDayNumber() implementation)
1295 long tzDiff = GetTimeZone();
1296 if ( IsDST() == 1 )
1297 {
1298 // FIXME: again, we suppose that DST is always one hour
1299 tzDiff -= 3600;
1300 }
1301
1302 jdn += tzDiff*1000; // tzDiff is in seconds
1303
9c2882d9 1304 m_time.Assign(jdn);
e6ec579c
VZ
1305
1306 return *this;
1307}
1308
9d9b7755
VZ
1309wxDateTime& wxDateTime::ResetTime()
1310{
1311 Tm tm = GetTm();
1312
1313 if ( tm.hour || tm.min || tm.sec || tm.msec )
1314 {
1315 tm.msec =
1316 tm.sec =
1317 tm.min =
1318 tm.hour = 0;
1319
1320 Set(tm);
1321 }
1322
1323 return *this;
1324}
1325
2b5f62a0
VZ
1326// ----------------------------------------------------------------------------
1327// DOS Date and Time Format functions
1328// ----------------------------------------------------------------------------
1329// the dos date and time value is an unsigned 32 bit value in the format:
1330// YYYYYYYMMMMDDDDDhhhhhmmmmmmsssss
1331//
1332// Y = year offset from 1980 (0-127)
1333// M = month (1-12)
1334// D = day of month (1-31)
1335// h = hour (0-23)
1336// m = minute (0-59)
1337// s = bisecond (0-29) each bisecond indicates two seconds
1338// ----------------------------------------------------------------------------
1339
1340wxDateTime& wxDateTime::SetFromDOS(unsigned long ddt)
1341{
1342 struct tm tm;
4739346e 1343 InitTm(tm);
2b5f62a0
VZ
1344
1345 long year = ddt & 0xFE000000;
1346 year >>= 25;
1347 year += 80;
1348 tm.tm_year = year;
1349
1350 long month = ddt & 0x1E00000;
1351 month >>= 21;
1352 month -= 1;
1353 tm.tm_mon = month;
1354
1355 long day = ddt & 0x1F0000;
1356 day >>= 16;
1357 tm.tm_mday = day;
1358
1359 long hour = ddt & 0xF800;
1360 hour >>= 11;
1361 tm.tm_hour = hour;
1362
1363 long minute = ddt & 0x7E0;
1364 minute >>= 5;
1365 tm.tm_min = minute;
1366
1367 long second = ddt & 0x1F;
1368 tm.tm_sec = second * 2;
1369
1370 return Set(mktime(&tm));
1371}
1372
1373unsigned long wxDateTime::GetAsDOS() const
1374{
1375 unsigned long ddt;
1376 time_t ticks = GetTicks();
1377 struct tm *tm = localtime(&ticks);
1378
1379 long year = tm->tm_year;
1380 year -= 80;
1381 year <<= 25;
1382
1383 long month = tm->tm_mon;
1384 month += 1;
1385 month <<= 21;
1386
1387 long day = tm->tm_mday;
1388 day <<= 16;
1389
1390 long hour = tm->tm_hour;
1391 hour <<= 11;
1392
1393 long minute = tm->tm_min;
1394 minute <<= 5;
1395
1396 long second = tm->tm_sec;
1397 second /= 2;
1398
1399 ddt = year | month | day | hour | minute | second;
1400 return ddt;
1401}
1402
b76b015e
VZ
1403// ----------------------------------------------------------------------------
1404// time_t <-> broken down time conversions
1405// ----------------------------------------------------------------------------
1406
299fcbfe 1407wxDateTime::Tm wxDateTime::GetTm(const TimeZone& tz) const
b76b015e
VZ
1408{
1409 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1410
1411 time_t time = GetTicks();
1412 if ( time != (time_t)-1 )
1413 {
1414 // use C RTL functions
299fcbfe
VZ
1415 tm *tm;
1416 if ( tz.GetOffset() == -GetTimeZone() )
1417 {
1418 // we are working with local time
1419 tm = localtime(&time);
c5a1681b
VZ
1420
1421 // should never happen
f6bcfd97 1422 wxCHECK_MSG( tm, Tm(), _T("localtime() failed") );
299fcbfe
VZ
1423 }
1424 else
1425 {
13111b2a 1426 time += (time_t)tz.GetOffset();
33ac7e6f 1427#if defined(__VMS__) || defined(__WATCOMC__) // time is unsigned so avoid warning
13111b2a 1428 int time2 = (int) time;
9d9b7755 1429 if ( time2 >= 0 )
fb10f04c 1430#else
9d9b7755 1431 if ( time >= 0 )
fb10f04c 1432#endif
c5a1681b
VZ
1433 {
1434 tm = gmtime(&time);
b76b015e 1435
c5a1681b
VZ
1436 // should never happen
1437 wxCHECK_MSG( tm, Tm(), _T("gmtime() failed") );
1438 }
1439 else
1440 {
1441 tm = (struct tm *)NULL;
1442 }
1443 }
b76b015e 1444
c5a1681b
VZ
1445 if ( tm )
1446 {
f6bcfd97
BP
1447 // adjust the milliseconds
1448 Tm tm2(*tm, tz);
1449 long timeOnly = (m_time % MILLISECONDS_PER_DAY).ToLong();
1450 tm2.msec = (wxDateTime_t)(timeOnly % 1000);
1451 return tm2;
c5a1681b
VZ
1452 }
1453 //else: use generic code below
b76b015e 1454 }
e6ec579c 1455
c5a1681b
VZ
1456 // remember the time and do the calculations with the date only - this
1457 // eliminates rounding errors of the floating point arithmetics
299fcbfe 1458
c5a1681b 1459 wxLongLong timeMidnight = m_time + tz.GetOffset() * 1000;
1ef54dcf 1460
c5a1681b 1461 long timeOnly = (timeMidnight % MILLISECONDS_PER_DAY).ToLong();
1ef54dcf 1462
c5a1681b
VZ
1463 // we want to always have positive time and timeMidnight to be really
1464 // the midnight before it
1465 if ( timeOnly < 0 )
1466 {
1467 timeOnly = MILLISECONDS_PER_DAY + timeOnly;
1468 }
e6ec579c 1469
c5a1681b 1470 timeMidnight -= timeOnly;
1ef54dcf 1471
c5a1681b
VZ
1472 // calculate the Gregorian date from JDN for the midnight of our date:
1473 // this will yield day, month (in 1..12 range) and year
1ef54dcf 1474
c5a1681b
VZ
1475 // actually, this is the JDN for the noon of the previous day
1476 long jdn = (timeMidnight / MILLISECONDS_PER_DAY).ToLong() + EPOCH_JDN;
1ef54dcf 1477
c5a1681b 1478 // CREDIT: code below is by Scott E. Lee (but bugs are mine)
1ef54dcf 1479
c5a1681b 1480 wxASSERT_MSG( jdn > -2, _T("JDN out of range") );
1ef54dcf 1481
c5a1681b 1482 // calculate the century
479cd5de
VZ
1483 long temp = (jdn + JDN_OFFSET) * 4 - 1;
1484 long century = temp / DAYS_PER_400_YEARS;
1ef54dcf 1485
c5a1681b
VZ
1486 // then the year and day of year (1 <= dayOfYear <= 366)
1487 temp = ((temp % DAYS_PER_400_YEARS) / 4) * 4 + 3;
479cd5de
VZ
1488 long year = (century * 100) + (temp / DAYS_PER_4_YEARS);
1489 long dayOfYear = (temp % DAYS_PER_4_YEARS) / 4 + 1;
1ef54dcf 1490
c5a1681b
VZ
1491 // and finally the month and day of the month
1492 temp = dayOfYear * 5 - 3;
479cd5de
VZ
1493 long month = temp / DAYS_PER_5_MONTHS;
1494 long day = (temp % DAYS_PER_5_MONTHS) / 5 + 1;
c5a1681b
VZ
1495
1496 // month is counted from March - convert to normal
1497 if ( month < 10 )
1498 {
1499 month += 3;
1500 }
1501 else
1502 {
1503 year += 1;
1504 month -= 9;
1505 }
1ef54dcf 1506
c5a1681b
VZ
1507 // year is offset by 4800
1508 year -= 4800;
1ef54dcf 1509
c5a1681b
VZ
1510 // check that the algorithm gave us something reasonable
1511 wxASSERT_MSG( (0 < month) && (month <= 12), _T("invalid month") );
1512 wxASSERT_MSG( (1 <= day) && (day < 32), _T("invalid day") );
e6ec579c 1513
c5a1681b
VZ
1514 // construct Tm from these values
1515 Tm tm;
1516 tm.year = (int)year;
1517 tm.mon = (Month)(month - 1); // algorithm yields 1 for January, not 0
1518 tm.mday = (wxDateTime_t)day;
479cd5de 1519 tm.msec = (wxDateTime_t)(timeOnly % 1000);
c5a1681b
VZ
1520 timeOnly -= tm.msec;
1521 timeOnly /= 1000; // now we have time in seconds
e6ec579c 1522
479cd5de 1523 tm.sec = (wxDateTime_t)(timeOnly % 60);
c5a1681b
VZ
1524 timeOnly -= tm.sec;
1525 timeOnly /= 60; // now we have time in minutes
e6ec579c 1526
479cd5de 1527 tm.min = (wxDateTime_t)(timeOnly % 60);
c5a1681b 1528 timeOnly -= tm.min;
e6ec579c 1529
479cd5de 1530 tm.hour = (wxDateTime_t)(timeOnly / 60);
b76b015e 1531
c5a1681b 1532 return tm;
b76b015e
VZ
1533}
1534
1535wxDateTime& wxDateTime::SetYear(int year)
1536{
1537 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1538
1539 Tm tm(GetTm());
1540 tm.year = year;
1541 Set(tm);
1542
1543 return *this;
1544}
1545
1546wxDateTime& wxDateTime::SetMonth(Month month)
1547{
1548 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1549
1550 Tm tm(GetTm());
1551 tm.mon = month;
1552 Set(tm);
1553
1554 return *this;
1555}
1556
1557wxDateTime& wxDateTime::SetDay(wxDateTime_t mday)
1558{
1559 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1560
1561 Tm tm(GetTm());
1562 tm.mday = mday;
1563 Set(tm);
1564
1565 return *this;
1566}
1567
1568wxDateTime& wxDateTime::SetHour(wxDateTime_t hour)
1569{
1570 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1571
1572 Tm tm(GetTm());
1573 tm.hour = hour;
1574 Set(tm);
1575
1576 return *this;
1577}
1578
1579wxDateTime& wxDateTime::SetMinute(wxDateTime_t min)
1580{
1581 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1582
1583 Tm tm(GetTm());
1584 tm.min = min;
1585 Set(tm);
1586
1587 return *this;
1588}
1589
1590wxDateTime& wxDateTime::SetSecond(wxDateTime_t sec)
1591{
1592 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1593
1594 Tm tm(GetTm());
1595 tm.sec = sec;
1596 Set(tm);
0979c962
VZ
1597
1598 return *this;
1599}
b76b015e
VZ
1600
1601wxDateTime& wxDateTime::SetMillisecond(wxDateTime_t millisecond)
1602{
1603 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1604
1605 // we don't need to use GetTm() for this one
1606 m_time -= m_time % 1000l;
1607 m_time += millisecond;
1608
1609 return *this;
1610}
1611
1612// ----------------------------------------------------------------------------
1613// wxDateTime arithmetics
1614// ----------------------------------------------------------------------------
1615
1616wxDateTime& wxDateTime::Add(const wxDateSpan& diff)
1617{
1618 Tm tm(GetTm());
1619
1620 tm.year += diff.GetYears();
fcc3d7cb 1621 tm.AddMonths(diff.GetMonths());
4f6aed9c
VZ
1622
1623 // check that the resulting date is valid
1624 if ( tm.mday > GetNumOfDaysInMonth(tm.year, tm.mon) )
1625 {
1626 // We suppose that when adding one month to Jan 31 we want to get Feb
1627 // 28 (or 29), i.e. adding a month to the last day of the month should
1628 // give the last day of the next month which is quite logical.
1629 //
1630 // Unfortunately, there is no logic way to understand what should
1631 // Jan 30 + 1 month be - Feb 28 too or Feb 27 (assuming non leap year)?
1632 // We make it Feb 28 (last day too), but it is highly questionable.
1633 tm.mday = GetNumOfDaysInMonth(tm.year, tm.mon);
1634 }
1635
fcc3d7cb 1636 tm.AddDays(diff.GetTotalDays());
b76b015e
VZ
1637
1638 Set(tm);
1639
9d9b7755
VZ
1640 wxASSERT_MSG( IsSameTime(tm),
1641 _T("Add(wxDateSpan) shouldn't modify time") );
1642
b76b015e
VZ
1643 return *this;
1644}
1645
2f02cb89
VZ
1646// ----------------------------------------------------------------------------
1647// Weekday and monthday stuff
1648// ----------------------------------------------------------------------------
1649
4c27e2fa
VZ
1650// convert Sun, Mon, ..., Sat into 6, 0, ..., 5
1651static inline int ConvertWeekDayToMondayBase(int wd)
1652{
1653 return wd == wxDateTime::Sun ? 6 : wd - 1;
1654}
1655
1656/* static */
1657wxDateTime
1658wxDateTime::SetToWeekOfYear(int year, wxDateTime_t numWeek, WeekDay wd)
4f6aed9c 1659{
2b5f62a0
VZ
1660 wxASSERT_MSG( numWeek > 0,
1661 _T("invalid week number: weeks are counted from 1") );
1662
4c27e2fa
VZ
1663 // Jan 4 always lies in the 1st week of the year
1664 wxDateTime dt(4, Jan, year);
1665 dt.SetToWeekDayInSameWeek(wd);
1666 dt += wxDateSpan::Weeks(numWeek - 1);
1667
1668 return dt;
1669}
4f6aed9c 1670
4c27e2fa
VZ
1671// use a separate function to avoid warnings about using deprecated
1672// SetToTheWeek in GetWeek below
1673static wxDateTime
1674SetToTheWeek(int year,
1675 wxDateTime::wxDateTime_t numWeek,
1676 wxDateTime::WeekDay weekday,
1677 wxDateTime::WeekFlags flags)
1678{
4f6aed9c 1679 // Jan 4 always lies in the 1st week of the year
4c27e2fa
VZ
1680 wxDateTime dt(4, wxDateTime::Jan, year);
1681 dt.SetToWeekDayInSameWeek(weekday, flags);
1682 dt += wxDateSpan::Weeks(numWeek - 1);
4f6aed9c 1683
4c27e2fa
VZ
1684 return dt;
1685}
1686
1687bool wxDateTime::SetToTheWeek(wxDateTime_t numWeek,
1688 WeekDay weekday,
1689 WeekFlags flags)
1690{
1691 int year = GetYear();
1692 *this = ::SetToTheWeek(year, numWeek, weekday, flags);
4f6aed9c
VZ
1693 if ( GetYear() != year )
1694 {
1695 // oops... numWeek was too big
68379eaf 1696 return false;
4f6aed9c
VZ
1697 }
1698
68379eaf 1699 return true;
4f6aed9c
VZ
1700}
1701
4c27e2fa
VZ
1702wxDateTime wxDateTime::GetWeek(wxDateTime_t numWeek,
1703 WeekDay weekday,
1704 WeekFlags flags) const
1705{
1706 return ::SetToTheWeek(GetYear(), numWeek, weekday, flags);
1707}
1708
2f02cb89
VZ
1709wxDateTime& wxDateTime::SetToLastMonthDay(Month month,
1710 int year)
1711{
1712 // take the current month/year if none specified
1a8557b1
VZ
1713 if ( year == Inv_Year )
1714 year = GetYear();
1715 if ( month == Inv_Month )
1716 month = GetMonth();
2f02cb89 1717
fcc3d7cb 1718 return Set(GetNumOfDaysInMonth(year, month), month, year);
2f02cb89
VZ
1719}
1720
2b5f62a0 1721wxDateTime& wxDateTime::SetToWeekDayInSameWeek(WeekDay weekday, WeekFlags flags)
cd0b1709 1722{
384223b3 1723 wxDATETIME_CHECK( weekday != Inv_WeekDay, _T("invalid weekday") );
cd0b1709 1724
2b5f62a0 1725 int wdayThis = GetWeekDay();
cd0b1709
VZ
1726 if ( weekday == wdayThis )
1727 {
1728 // nothing to do
1729 return *this;
1730 }
2b5f62a0
VZ
1731
1732 if ( flags == Default_First )
1733 {
1734 flags = GetCountry() == USA ? Sunday_First : Monday_First;
1735 }
1736
1737 // the logic below based on comparing weekday and wdayThis works if Sun (0)
1738 // is the first day in the week, but breaks down for Monday_First case so
1739 // we adjust the week days in this case
1740 if( flags == Monday_First )
1741 {
1742 if ( wdayThis == Sun )
1743 wdayThis += 7;
1744 }
1745 //else: Sunday_First, nothing to do
1746
1747 // go forward or back in time to the day we want
1748 if ( weekday < wdayThis )
cd0b1709 1749 {
f6bcfd97 1750 return Subtract(wxDateSpan::Days(wdayThis - weekday));
cd0b1709
VZ
1751 }
1752 else // weekday > wdayThis
1753 {
4f6aed9c 1754 return Add(wxDateSpan::Days(weekday - wdayThis));
cd0b1709
VZ
1755 }
1756}
1757
1758wxDateTime& wxDateTime::SetToNextWeekDay(WeekDay weekday)
1759{
384223b3 1760 wxDATETIME_CHECK( weekday != Inv_WeekDay, _T("invalid weekday") );
cd0b1709
VZ
1761
1762 int diff;
1763 WeekDay wdayThis = GetWeekDay();
1764 if ( weekday == wdayThis )
1765 {
1766 // nothing to do
1767 return *this;
1768 }
1769 else if ( weekday < wdayThis )
1770 {
1771 // need to advance a week
1772 diff = 7 - (wdayThis - weekday);
1773 }
1774 else // weekday > wdayThis
1775 {
1776 diff = weekday - wdayThis;
1777 }
1778
4f6aed9c 1779 return Add(wxDateSpan::Days(diff));
cd0b1709
VZ
1780}
1781
1782wxDateTime& wxDateTime::SetToPrevWeekDay(WeekDay weekday)
1783{
384223b3 1784 wxDATETIME_CHECK( weekday != Inv_WeekDay, _T("invalid weekday") );
cd0b1709
VZ
1785
1786 int diff;
1787 WeekDay wdayThis = GetWeekDay();
1788 if ( weekday == wdayThis )
1789 {
1790 // nothing to do
1791 return *this;
1792 }
1793 else if ( weekday > wdayThis )
1794 {
1795 // need to go to previous week
1796 diff = 7 - (weekday - wdayThis);
1797 }
1798 else // weekday < wdayThis
1799 {
1800 diff = wdayThis - weekday;
1801 }
1802
f6bcfd97 1803 return Subtract(wxDateSpan::Days(diff));
cd0b1709
VZ
1804}
1805
2f02cb89
VZ
1806bool wxDateTime::SetToWeekDay(WeekDay weekday,
1807 int n,
1808 Month month,
1809 int year)
1810{
68379eaf 1811 wxCHECK_MSG( weekday != Inv_WeekDay, false, _T("invalid weekday") );
2f02cb89 1812
68379eaf 1813 // we don't check explicitly that -5 <= n <= 5 because we will return false
2f02cb89
VZ
1814 // anyhow in such case - but may be should still give an assert for it?
1815
1816 // take the current month/year if none specified
1817 ReplaceDefaultYearMonthWithCurrent(&year, &month);
1818
1819 wxDateTime dt;
1820
1821 // TODO this probably could be optimised somehow...
1822
1823 if ( n > 0 )
1824 {
1825 // get the first day of the month
1826 dt.Set(1, month, year);
1827
1828 // get its wday
1829 WeekDay wdayFirst = dt.GetWeekDay();
1830
1831 // go to the first weekday of the month
1832 int diff = weekday - wdayFirst;
1833 if ( diff < 0 )
1834 diff += 7;
1835
1836 // add advance n-1 weeks more
1837 diff += 7*(n - 1);
1838
239446b4 1839 dt += wxDateSpan::Days(diff);
2f02cb89 1840 }
239446b4 1841 else // count from the end of the month
2f02cb89
VZ
1842 {
1843 // get the last day of the month
1844 dt.SetToLastMonthDay(month, year);
1845
1846 // get its wday
1847 WeekDay wdayLast = dt.GetWeekDay();
1848
1849 // go to the last weekday of the month
1850 int diff = wdayLast - weekday;
1851 if ( diff < 0 )
1852 diff += 7;
1853
1854 // and rewind n-1 weeks from there
239446b4 1855 diff += 7*(-n - 1);
2f02cb89
VZ
1856
1857 dt -= wxDateSpan::Days(diff);
1858 }
1859
1860 // check that it is still in the same month
1861 if ( dt.GetMonth() == month )
1862 {
1863 *this = dt;
1864
68379eaf 1865 return true;
2f02cb89
VZ
1866 }
1867 else
1868 {
1869 // no such day in this month
68379eaf 1870 return false;
2f02cb89
VZ
1871 }
1872}
1873
1c5d27e2
VZ
1874static inline
1875wxDateTime::wxDateTime_t GetDayOfYearFromTm(const wxDateTime::Tm& tm)
1876{
907173e5 1877 return (wxDateTime::wxDateTime_t)(gs_cumulatedDays[wxDateTime::IsLeapYear(tm.year)][tm.mon] + tm.mday);
1c5d27e2
VZ
1878}
1879
239446b4
VZ
1880wxDateTime::wxDateTime_t wxDateTime::GetDayOfYear(const TimeZone& tz) const
1881{
1c5d27e2
VZ
1882 return GetDayOfYearFromTm(GetTm(tz));
1883}
239446b4 1884
1c5d27e2
VZ
1885wxDateTime::wxDateTime_t
1886wxDateTime::GetWeekOfYear(wxDateTime::WeekFlags flags, const TimeZone& tz) const
239446b4 1887{
9d9b7755
VZ
1888 if ( flags == Default_First )
1889 {
1890 flags = GetCountry() == USA ? Sunday_First : Monday_First;
1891 }
239446b4 1892
1c5d27e2
VZ
1893 Tm tm(GetTm(tz));
1894 wxDateTime_t nDayInYear = GetDayOfYearFromTm(tm);
239446b4 1895
1c5d27e2
VZ
1896 int wdTarget = GetWeekDay(tz);
1897 int wdYearStart = wxDateTime(1, Jan, GetYear()).GetWeekDay();
1898 int week;
9d9b7755
VZ
1899 if ( flags == Sunday_First )
1900 {
1c5d27e2
VZ
1901 // FIXME: First week is not calculated correctly.
1902 week = (nDayInYear - wdTarget + 7) / 7;
1903 if ( wdYearStart == Wed || wdYearStart == Thu )
1904 week++;
9d9b7755 1905 }
1c5d27e2 1906 else // week starts with monday
9d9b7755 1907 {
1c5d27e2
VZ
1908 // adjust the weekdays to non-US style.
1909 wdYearStart = ConvertWeekDayToMondayBase(wdYearStart);
1910 wdTarget = ConvertWeekDayToMondayBase(wdTarget);
239446b4 1911
1c5d27e2
VZ
1912 // quoting from http://www.cl.cam.ac.uk/~mgk25/iso-time.html:
1913 //
1914 // Week 01 of a year is per definition the first week that has the
1915 // Thursday in this year, which is equivalent to the week that
1916 // contains the fourth day of January. In other words, the first
1917 // week of a new year is the week that has the majority of its
1918 // days in the new year. Week 01 might also contain days from the
1919 // previous year and the week before week 01 of a year is the last
1920 // week (52 or 53) of the previous year even if it contains days
1921 // from the new year. A week starts with Monday (day 1) and ends
1922 // with Sunday (day 7).
1923 //
1924
1925 // if Jan 1 is Thursday or less, it is in the first week of this year
1926 if ( wdYearStart < 4 )
1927 {
1928 // count the number of entire weeks between Jan 1 and this date
1929 week = (nDayInYear + wdYearStart + 6 - wdTarget)/7;
1930
1931 // be careful to check for overflow in the next year
1932 if ( week == 53 && tm.mday - wdTarget > 28 )
1933 week = 1;
1934 }
1935 else // Jan 1 is in the last week of the previous year
1936 {
1937 // check if we happen to be at the last week of previous year:
1938 if ( tm.mon == Jan && tm.mday < 8 - wdYearStart )
1939 week = wxDateTime(31, Dec, GetYear()-1).GetWeekOfYear();
1940 else
1941 week = (nDayInYear + wdYearStart - 1 - wdTarget)/7;
1942 }
239446b4
VZ
1943 }
1944
907173e5 1945 return (wxDateTime::wxDateTime_t)week;
68ee7c47
VZ
1946}
1947
9d9b7755
VZ
1948wxDateTime::wxDateTime_t wxDateTime::GetWeekOfMonth(wxDateTime::WeekFlags flags,
1949 const TimeZone& tz) const
68ee7c47 1950{
9d9b7755
VZ
1951 Tm tm = GetTm(tz);
1952 wxDateTime dtMonthStart = wxDateTime(1, tm.mon, tm.year);
6dc6fda6 1953 int nWeek = GetWeekOfYear(flags) - dtMonthStart.GetWeekOfYear(flags) + 1;
9d9b7755 1954 if ( nWeek < 0 )
68ee7c47 1955 {
9d9b7755
VZ
1956 // this may happen for January when Jan, 1 is the last week of the
1957 // previous year
1958 nWeek += IsLeapYear(tm.year - 1) ? 53 : 52;
68ee7c47 1959 }
68ee7c47 1960
6dc6fda6 1961 return (wxDateTime::wxDateTime_t)nWeek;
239446b4
VZ
1962}
1963
f0f951fa
VZ
1964wxDateTime& wxDateTime::SetToYearDay(wxDateTime::wxDateTime_t yday)
1965{
1966 int year = GetYear();
384223b3
VZ
1967 wxDATETIME_CHECK( (0 < yday) && (yday <= GetNumberOfDays(year)),
1968 _T("invalid year day") );
f0f951fa
VZ
1969
1970 bool isLeap = IsLeapYear(year);
1971 for ( Month mon = Jan; mon < Inv_Month; wxNextMonth(mon) )
1972 {
1973 // for Dec, we can't compare with gs_cumulatedDays[mon + 1], but we
1974 // don't need it neither - because of the CHECK above we know that
1975 // yday lies in December then
f49d731f 1976 if ( (mon == Dec) || (yday <= gs_cumulatedDays[isLeap][mon + 1]) )
f0f951fa 1977 {
42841dfc 1978 Set((wxDateTime::wxDateTime_t)(yday - gs_cumulatedDays[isLeap][mon]), mon, year);
f0f951fa
VZ
1979
1980 break;
1981 }
1982 }
1983
1984 return *this;
1985}
1986
e6ec579c
VZ
1987// ----------------------------------------------------------------------------
1988// Julian day number conversion and related stuff
1989// ----------------------------------------------------------------------------
1990
1991double wxDateTime::GetJulianDayNumber() const
1992{
8cc00d5f
VZ
1993 // JDN are always expressed for the UTC dates
1994 Tm tm(ToTimezone(UTC).GetTm(UTC));
e6ec579c
VZ
1995
1996 double result = GetTruncatedJDN(tm.mday, tm.mon, tm.year);
1997
1998 // add the part GetTruncatedJDN() neglected
1999 result += 0.5;
2000
2001 // and now add the time: 86400 sec = 1 JDN
2002 return result + ((double)(60*(60*tm.hour + tm.min) + tm.sec)) / 86400;
2003}
2004
2005double wxDateTime::GetRataDie() const
2006{
2007 // March 1 of the year 0 is Rata Die day -306 and JDN 1721119.5
2008 return GetJulianDayNumber() - 1721119.5 - 306;
2009}
2010
fcc3d7cb 2011// ----------------------------------------------------------------------------
299fcbfe 2012// timezone and DST stuff
fcc3d7cb
VZ
2013// ----------------------------------------------------------------------------
2014
299fcbfe 2015int wxDateTime::IsDST(wxDateTime::Country country) const
fcc3d7cb 2016{
299fcbfe
VZ
2017 wxCHECK_MSG( country == Country_Default, -1,
2018 _T("country support not implemented") );
2019
2020 // use the C RTL for the dates in the standard range
2021 time_t timet = GetTicks();
2022 if ( timet != (time_t)-1 )
2023 {
2024 tm *tm = localtime(&timet);
2025
2026 wxCHECK_MSG( tm, -1, _T("localtime() failed") );
2027
2028 return tm->tm_isdst;
2029 }
2030 else
2031 {
239446b4
VZ
2032 int year = GetYear();
2033
2034 if ( !IsDSTApplicable(year, country) )
2035 {
2036 // no DST time in this year in this country
2037 return -1;
2038 }
299fcbfe 2039
239446b4 2040 return IsBetween(GetBeginDST(year, country), GetEndDST(year, country));
299fcbfe 2041 }
fcc3d7cb
VZ
2042}
2043
41acf5c0 2044wxDateTime& wxDateTime::MakeTimezone(const TimeZone& tz, bool noDST)
fcc3d7cb 2045{
479cd5de 2046 long secDiff = GetTimeZone() + tz.GetOffset();
fcc3d7cb 2047
41acf5c0
VZ
2048 // we need to know whether DST is or not in effect for this date unless
2049 // the test disabled by the caller
2050 if ( !noDST && (IsDST() == 1) )
299fcbfe
VZ
2051 {
2052 // FIXME we assume that the DST is always shifted by 1 hour
2053 secDiff -= 3600;
2054 }
2055
f6bcfd97 2056 return Subtract(wxTimeSpan::Seconds(secDiff));
fcc3d7cb
VZ
2057}
2058
b76b015e
VZ
2059// ----------------------------------------------------------------------------
2060// wxDateTime to/from text representations
2061// ----------------------------------------------------------------------------
2062
299fcbfe 2063wxString wxDateTime::Format(const wxChar *format, const TimeZone& tz) const
b76b015e 2064{
e6ec579c
VZ
2065 wxCHECK_MSG( format, _T(""), _T("NULL format in wxDateTime::Format") );
2066
f6bcfd97
BP
2067 // we have to use our own implementation if the date is out of range of
2068 // strftime() or if we use non standard specificators
b76b015e 2069 time_t time = GetTicks();
f6bcfd97 2070 if ( (time != (time_t)-1) && !wxStrstr(format, _T("%l")) )
b76b015e
VZ
2071 {
2072 // use strftime()
299fcbfe
VZ
2073 tm *tm;
2074 if ( tz.GetOffset() == -GetTimeZone() )
2075 {
2076 // we are working with local time
2077 tm = localtime(&time);
c5a1681b
VZ
2078
2079 // should never happen
2080 wxCHECK_MSG( tm, wxEmptyString, _T("localtime() failed") );
299fcbfe
VZ
2081 }
2082 else
2083 {
479cd5de 2084 time += (int)tz.GetOffset();
299fcbfe 2085
33ac7e6f 2086#if defined(__VMS__) || defined(__WATCOMC__) // time is unsigned so avoid warning
13111b2a 2087 int time2 = (int) time;
9c2882d9 2088 if ( time2 >= 0 )
fb10f04c 2089#else
9c2882d9 2090 if ( time >= 0 )
fb10f04c 2091#endif
c5a1681b
VZ
2092 {
2093 tm = gmtime(&time);
b76b015e 2094
c5a1681b
VZ
2095 // should never happen
2096 wxCHECK_MSG( tm, wxEmptyString, _T("gmtime() failed") );
2097 }
2098 else
2099 {
2100 tm = (struct tm *)NULL;
2101 }
2102 }
b76b015e 2103
c5a1681b 2104 if ( tm )
e6ec579c 2105 {
c5a1681b 2106 return CallStrftime(format, tm);
e6ec579c 2107 }
c5a1681b
VZ
2108 //else: use generic code below
2109 }
2110
68ee7c47 2111 // we only parse ANSI C format specifications here, no POSIX 2
f6bcfd97
BP
2112 // complications, no GNU extensions but we do add support for a "%l" format
2113 // specifier allowing to get the number of milliseconds
68ee7c47 2114 Tm tm = GetTm(tz);
e6ec579c 2115
68ee7c47
VZ
2116 // used for calls to strftime() when we only deal with time
2117 struct tm tmTimeOnly;
2118 tmTimeOnly.tm_hour = tm.hour;
2119 tmTimeOnly.tm_min = tm.min;
2120 tmTimeOnly.tm_sec = tm.sec;
2121 tmTimeOnly.tm_wday = 0;
2122 tmTimeOnly.tm_yday = 0;
2123 tmTimeOnly.tm_mday = 1; // any date will do
2124 tmTimeOnly.tm_mon = 0;
2125 tmTimeOnly.tm_year = 76;
2126 tmTimeOnly.tm_isdst = 0; // no DST, we adjust for tz ourselves
e6ec579c 2127
77c3e48a 2128 wxString tmp, res, fmt;
68ee7c47
VZ
2129 for ( const wxChar *p = format; *p; p++ )
2130 {
2131 if ( *p != _T('%') )
2132 {
2133 // copy as is
2134 res += *p;
b76b015e 2135
68ee7c47
VZ
2136 continue;
2137 }
e6ec579c 2138
77c3e48a 2139 // set the default format
68ee7c47 2140 switch ( *++p )
77c3e48a
VZ
2141 {
2142 case _T('Y'): // year has 4 digits
2143 fmt = _T("%04d");
2144 break;
2145
2146 case _T('j'): // day of year has 3 digits
f6bcfd97 2147 case _T('l'): // milliseconds have 3 digits
77c3e48a
VZ
2148 fmt = _T("%03d");
2149 break;
2150
59d04dff
VZ
2151 case _T('w'): // week day as number has only one
2152 fmt = _T("%d");
2153 break;
2154
77c3e48a
VZ
2155 default:
2156 // it's either another valid format specifier in which case
2157 // the format is "%02d" (for all the rest) or we have the
2158 // field width preceding the format in which case it will
2159 // override the default format anyhow
2160 fmt = _T("%02d");
2161 }
2162
68379eaf 2163 bool restart = true;
fc3398f8 2164 while ( restart )
68ee7c47 2165 {
68379eaf 2166 restart = false;
68ee7c47 2167
fc3398f8
VZ
2168 // start of the format specification
2169 switch ( *p )
2170 {
2171 case _T('a'): // a weekday name
2172 case _T('A'):
68379eaf 2173 // second parameter should be true for abbreviated names
fc3398f8
VZ
2174 res += GetWeekDayName(tm.GetWeekDay(),
2175 *p == _T('a') ? Name_Abbr : Name_Full);
2176 break;
68ee7c47 2177
fc3398f8
VZ
2178 case _T('b'): // a month name
2179 case _T('B'):
2180 res += GetMonthName(tm.mon,
2181 *p == _T('b') ? Name_Abbr : Name_Full);
2182 break;
2183
2184 case _T('c'): // locale default date and time representation
2185 case _T('x'): // locale default date representation
2186 //
2187 // the problem: there is no way to know what do these format
2188 // specifications correspond to for the current locale.
2189 //
2190 // the solution: use a hack and still use strftime(): first
2191 // find the YEAR which is a year in the strftime() range (1970
2192 // - 2038) whose Jan 1 falls on the same week day as the Jan 1
2193 // of the real year. Then make a copy of the format and
2194 // replace all occurences of YEAR in it with some unique
2195 // string not appearing anywhere else in it, then use
2196 // strftime() to format the date in year YEAR and then replace
2197 // YEAR back by the real year and the unique replacement
2198 // string back with YEAR. Notice that "all occurences of YEAR"
2199 // means all occurences of 4 digit as well as 2 digit form!
2200 //
2201 // the bugs: we assume that neither of %c nor %x contains any
2202 // fields which may change between the YEAR and real year. For
2203 // example, the week number (%U, %W) and the day number (%j)
2204 // will change if one of these years is leap and the other one
2205 // is not!
68ee7c47 2206 {
fc3398f8
VZ
2207 // find the YEAR: normally, for any year X, Jan 1 or the
2208 // year X + 28 is the same weekday as Jan 1 of X (because
2209 // the weekday advances by 1 for each normal X and by 2
2210 // for each leap X, hence by 5 every 4 years or by 35
2211 // which is 0 mod 7 every 28 years) but this rule breaks
2212 // down if there are years between X and Y which are
2213 // divisible by 4 but not leap (i.e. divisible by 100 but
2214 // not 400), hence the correction.
2215
2216 int yearReal = GetYear(tz);
2217 int mod28 = yearReal % 28;
2218
2219 // be careful to not go too far - we risk to leave the
2220 // supported range
2221 int year;
2222 if ( mod28 < 10 )
2223 {
2224 year = 1988 + mod28; // 1988 == 0 (mod 28)
2225 }
2226 else
2227 {
2228 year = 1970 + mod28 - 10; // 1970 == 10 (mod 28)
2229 }
e6ec579c 2230
fc3398f8
VZ
2231 int nCentury = year / 100,
2232 nCenturyReal = yearReal / 100;
c5a1681b 2233
fc3398f8
VZ
2234 // need to adjust for the years divisble by 400 which are
2235 // not leap but are counted like leap ones if we just take
2236 // the number of centuries in between for nLostWeekDays
2237 int nLostWeekDays = (nCentury - nCenturyReal) -
2238 (nCentury / 4 - nCenturyReal / 4);
c5a1681b 2239
fc3398f8
VZ
2240 // we have to gain back the "lost" weekdays: note that the
2241 // effect of this loop is to not do anything to
2242 // nLostWeekDays (which we won't use any more), but to
2243 // (indirectly) set the year correctly
2244 while ( (nLostWeekDays % 7) != 0 )
2245 {
2246 nLostWeekDays += year++ % 4 ? 1 : 2;
2247 }
c5a1681b 2248
fc3398f8
VZ
2249 // at any rate, we couldn't go further than 1988 + 9 + 28!
2250 wxASSERT_MSG( year < 2030,
2251 _T("logic error in wxDateTime::Format") );
c5a1681b 2252
fc3398f8
VZ
2253 wxString strYear, strYear2;
2254 strYear.Printf(_T("%d"), year);
2255 strYear2.Printf(_T("%d"), year % 100);
c5a1681b 2256
fc3398f8
VZ
2257 // find two strings not occuring in format (this is surely
2258 // not optimal way of doing it... improvements welcome!)
2259 wxString fmt = format;
2260 wxString replacement = (wxChar)-1;
2261 while ( fmt.Find(replacement) != wxNOT_FOUND )
2262 {
2263 replacement << (wxChar)-1;
2264 }
c5a1681b 2265
fc3398f8
VZ
2266 wxString replacement2 = (wxChar)-2;
2267 while ( fmt.Find(replacement) != wxNOT_FOUND )
2268 {
2269 replacement << (wxChar)-2;
2270 }
2271
2272 // replace all occurences of year with it
2273 bool wasReplaced = fmt.Replace(strYear, replacement) > 0;
2274 if ( !wasReplaced )
2275 wasReplaced = fmt.Replace(strYear2, replacement2) > 0;
2276
2277 // use strftime() to format the same date but in supported
2278 // year
2279 //
2280 // NB: we assume that strftime() doesn't check for the
2281 // date validity and will happily format the date
2282 // corresponding to Feb 29 of a non leap year (which
2283 // may happen if yearReal was leap and year is not)
2284 struct tm tmAdjusted;
2285 InitTm(tmAdjusted);
2286 tmAdjusted.tm_hour = tm.hour;
2287 tmAdjusted.tm_min = tm.min;
2288 tmAdjusted.tm_sec = tm.sec;
2289 tmAdjusted.tm_wday = tm.GetWeekDay();
2290 tmAdjusted.tm_yday = GetDayOfYear();
2291 tmAdjusted.tm_mday = tm.mday;
2292 tmAdjusted.tm_mon = tm.mon;
2293 tmAdjusted.tm_year = year - 1900;
2294 tmAdjusted.tm_isdst = 0; // no DST, already adjusted
2295 wxString str = CallStrftime(*p == _T('c') ? _T("%c")
2296 : _T("%x"),
2297 &tmAdjusted);
2298
2299 // now replace the occurence of 1999 with the real year
2300 wxString strYearReal, strYearReal2;
2301 strYearReal.Printf(_T("%04d"), yearReal);
2302 strYearReal2.Printf(_T("%02d"), yearReal % 100);
2303 str.Replace(strYear, strYearReal);
2304 str.Replace(strYear2, strYearReal2);
2305
2306 // and replace back all occurences of replacement string
2307 if ( wasReplaced )
2308 {
2309 str.Replace(replacement2, strYear2);
2310 str.Replace(replacement, strYear);
2311 }
2312
2313 res += str;
68ee7c47 2314 }
fc3398f8 2315 break;
c5a1681b 2316
fc3398f8
VZ
2317 case _T('d'): // day of a month (01-31)
2318 res += wxString::Format(fmt, tm.mday);
2319 break;
68ee7c47 2320
fc3398f8
VZ
2321 case _T('H'): // hour in 24h format (00-23)
2322 res += wxString::Format(fmt, tm.hour);
2323 break;
2324
2325 case _T('I'): // hour in 12h format (01-12)
68ee7c47 2326 {
fc3398f8
VZ
2327 // 24h -> 12h, 0h -> 12h too
2328 int hour12 = tm.hour > 12 ? tm.hour - 12
2329 : tm.hour ? tm.hour : 12;
2330 res += wxString::Format(fmt, hour12);
68ee7c47 2331 }
fc3398f8 2332 break;
c5a1681b 2333
fc3398f8
VZ
2334 case _T('j'): // day of the year
2335 res += wxString::Format(fmt, GetDayOfYear(tz));
2336 break;
68ee7c47 2337
f6bcfd97
BP
2338 case _T('l'): // milliseconds (NOT STANDARD)
2339 res += wxString::Format(fmt, GetMillisecond(tz));
2340 break;
2341
fc3398f8
VZ
2342 case _T('m'): // month as a number (01-12)
2343 res += wxString::Format(fmt, tm.mon + 1);
2344 break;
68ee7c47 2345
fc3398f8
VZ
2346 case _T('M'): // minute as a decimal number (00-59)
2347 res += wxString::Format(fmt, tm.min);
2348 break;
68ee7c47 2349
fc3398f8
VZ
2350 case _T('p'): // AM or PM string
2351 res += CallStrftime(_T("%p"), &tmTimeOnly);
2352 break;
68ee7c47 2353
fc3398f8
VZ
2354 case _T('S'): // second as a decimal number (00-61)
2355 res += wxString::Format(fmt, tm.sec);
2356 break;
68ee7c47 2357
fc3398f8
VZ
2358 case _T('U'): // week number in the year (Sunday 1st week day)
2359 res += wxString::Format(fmt, GetWeekOfYear(Sunday_First, tz));
2360 break;
68ee7c47 2361
fc3398f8
VZ
2362 case _T('W'): // week number in the year (Monday 1st week day)
2363 res += wxString::Format(fmt, GetWeekOfYear(Monday_First, tz));
2364 break;
68ee7c47 2365
fc3398f8
VZ
2366 case _T('w'): // weekday as a number (0-6), Sunday = 0
2367 res += wxString::Format(fmt, tm.GetWeekDay());
2368 break;
68ee7c47 2369
fc3398f8 2370 // case _T('x'): -- handled with "%c"
68ee7c47 2371
fc3398f8
VZ
2372 case _T('X'): // locale default time representation
2373 // just use strftime() to format the time for us
2374 res += CallStrftime(_T("%X"), &tmTimeOnly);
2375 break;
68ee7c47 2376
fc3398f8
VZ
2377 case _T('y'): // year without century (00-99)
2378 res += wxString::Format(fmt, tm.year % 100);
2379 break;
68ee7c47 2380
fc3398f8
VZ
2381 case _T('Y'): // year with century
2382 res += wxString::Format(fmt, tm.year);
2383 break;
68ee7c47 2384
fc3398f8
VZ
2385 case _T('Z'): // timezone name
2386 res += CallStrftime(_T("%Z"), &tmTimeOnly);
2387 break;
68ee7c47 2388
fc3398f8
VZ
2389 default:
2390 // is it the format width?
2391 fmt.Empty();
2392 while ( *p == _T('-') || *p == _T('+') ||
2393 *p == _T(' ') || wxIsdigit(*p) )
2394 {
2395 fmt += *p;
2396 }
68ee7c47 2397
bb8a8478 2398 if ( !fmt.empty() )
fc3398f8
VZ
2399 {
2400 // we've only got the flags and width so far in fmt
2401 fmt.Prepend(_T('%'));
2402 fmt.Append(_T('d'));
68ee7c47 2403
68379eaf 2404 restart = true;
68ee7c47 2405
fc3398f8
VZ
2406 break;
2407 }
68ee7c47 2408
fc3398f8
VZ
2409 // no, it wasn't the width
2410 wxFAIL_MSG(_T("unknown format specificator"));
68ee7c47 2411
fc3398f8 2412 // fall through and just copy it nevertheless
68ee7c47 2413
fc3398f8
VZ
2414 case _T('%'): // a percent sign
2415 res += *p;
2416 break;
2417
2418 case 0: // the end of string
2419 wxFAIL_MSG(_T("missing format at the end of string"));
2420
2421 // just put the '%' which was the last char in format
2422 res += _T('%');
2423 break;
2424 }
68ee7c47 2425 }
c5a1681b
VZ
2426 }
2427
68ee7c47 2428 return res;
b76b015e 2429}
fcc3d7cb 2430
cd0b1709
VZ
2431// this function parses a string in (strict) RFC 822 format: see the section 5
2432// of the RFC for the detailed description, but briefly it's something of the
2433// form "Sat, 18 Dec 1999 00:48:30 +0100"
2434//
2435// this function is "strict" by design - it must reject anything except true
2436// RFC822 time specs.
2437//
2438// TODO a great candidate for using reg exps
2439const wxChar *wxDateTime::ParseRfc822Date(const wxChar* date)
2440{
2441 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
2442
2443 const wxChar *p = date;
2444 const wxChar *comma = wxStrchr(p, _T(','));
2445 if ( comma )
2446 {
2447 // the part before comma is the weekday
2448
2449 // skip it for now - we don't use but might check that it really
2450 // corresponds to the specfied date
2451 p = comma + 1;
2452
2453 if ( *p != _T(' ') )
2454 {
2455 wxLogDebug(_T("no space after weekday in RFC822 time spec"));
2456
2457 return (wxChar *)NULL;
2458 }
2459
2460 p++; // skip space
2461 }
2462
2463 // the following 1 or 2 digits are the day number
2464 if ( !wxIsdigit(*p) )
2465 {
2466 wxLogDebug(_T("day number expected in RFC822 time spec, none found"));
2467
2468 return (wxChar *)NULL;
2469 }
2470
907173e5 2471 wxDateTime_t day = (wxDateTime_t)(*p++ - _T('0'));
cd0b1709
VZ
2472 if ( wxIsdigit(*p) )
2473 {
2474 day *= 10;
907173e5 2475 day = (wxDateTime_t)(day + (*p++ - _T('0')));
cd0b1709
VZ
2476 }
2477
2478 if ( *p++ != _T(' ') )
2479 {
2480 return (wxChar *)NULL;
2481 }
2482
2483 // the following 3 letters specify the month
2484 wxString monName(p, 3);
2485 Month mon;
2486 if ( monName == _T("Jan") )
2487 mon = Jan;
2488 else if ( monName == _T("Feb") )
2489 mon = Feb;
2490 else if ( monName == _T("Mar") )
2491 mon = Mar;
2492 else if ( monName == _T("Apr") )
2493 mon = Apr;
2494 else if ( monName == _T("May") )
2495 mon = May;
2496 else if ( monName == _T("Jun") )
2497 mon = Jun;
2498 else if ( monName == _T("Jul") )
2499 mon = Jul;
2500 else if ( monName == _T("Aug") )
2501 mon = Aug;
2502 else if ( monName == _T("Sep") )
2503 mon = Sep;
2504 else if ( monName == _T("Oct") )
2505 mon = Oct;
2506 else if ( monName == _T("Nov") )
2507 mon = Nov;
2508 else if ( monName == _T("Dec") )
2509 mon = Dec;
2510 else
2511 {
2512 wxLogDebug(_T("Invalid RFC 822 month name '%s'"), monName.c_str());
2513
2514 return (wxChar *)NULL;
2515 }
2516
2517 p += 3;
2518
2519 if ( *p++ != _T(' ') )
2520 {
2521 return (wxChar *)NULL;
2522 }
2523
2524 // next is the year
2525 if ( !wxIsdigit(*p) )
2526 {
2527 // no year?
2528 return (wxChar *)NULL;
2529 }
2530
2531 int year = *p++ - _T('0');
2532
2533 if ( !wxIsdigit(*p) )
2534 {
2535 // should have at least 2 digits in the year
2536 return (wxChar *)NULL;
2537 }
2538
2539 year *= 10;
2540 year += *p++ - _T('0');
2541
2542 // is it a 2 digit year (as per original RFC 822) or a 4 digit one?
2543 if ( wxIsdigit(*p) )
2544 {
2545 year *= 10;
2546 year += *p++ - _T('0');
2547
2548 if ( !wxIsdigit(*p) )
2549 {
2550 // no 3 digit years please
2551 return (wxChar *)NULL;
2552 }
2553
2554 year *= 10;
2555 year += *p++ - _T('0');
2556 }
2557
2558 if ( *p++ != _T(' ') )
2559 {
2560 return (wxChar *)NULL;
2561 }
2562
2563 // time is in the format hh:mm:ss and seconds are optional
2564 if ( !wxIsdigit(*p) )
2565 {
2566 return (wxChar *)NULL;
2567 }
2568
42841dfc 2569 wxDateTime_t hour = (wxDateTime_t)(*p++ - _T('0'));
cd0b1709
VZ
2570
2571 if ( !wxIsdigit(*p) )
2572 {
2573 return (wxChar *)NULL;
2574 }
2575
2576 hour *= 10;
42841dfc 2577 hour = (wxDateTime_t)(hour + (*p++ - _T('0')));
cd0b1709
VZ
2578
2579 if ( *p++ != _T(':') )
2580 {
2581 return (wxChar *)NULL;
2582 }
2583
2584 if ( !wxIsdigit(*p) )
2585 {
2586 return (wxChar *)NULL;
2587 }
2588
42841dfc 2589 wxDateTime_t min = (wxDateTime_t)(*p++ - _T('0'));
cd0b1709
VZ
2590
2591 if ( !wxIsdigit(*p) )
2592 {
2593 return (wxChar *)NULL;
2594 }
2595
2596 min *= 10;
42841dfc 2597 min = (wxDateTime_t)(min + *p++ - _T('0'));
cd0b1709
VZ
2598
2599 wxDateTime_t sec = 0;
2600 if ( *p++ == _T(':') )
2601 {
2602 if ( !wxIsdigit(*p) )
2603 {
2604 return (wxChar *)NULL;
2605 }
2606
42841dfc 2607 sec = (wxDateTime_t)(*p++ - _T('0'));
cd0b1709
VZ
2608
2609 if ( !wxIsdigit(*p) )
2610 {
2611 return (wxChar *)NULL;
2612 }
2613
2614 sec *= 10;
42841dfc 2615 sec = (wxDateTime_t)(sec + *p++ - _T('0'));
cd0b1709
VZ
2616 }
2617
2618 if ( *p++ != _T(' ') )
2619 {
2620 return (wxChar *)NULL;
2621 }
2622
2623 // and now the interesting part: the timezone
40973ea5 2624 int offset;
cd0b1709
VZ
2625 if ( *p == _T('-') || *p == _T('+') )
2626 {
2627 // the explicit offset given: it has the form of hhmm
2628 bool plus = *p++ == _T('+');
2629
2630 if ( !wxIsdigit(*p) || !wxIsdigit(*(p + 1)) )
2631 {
2632 return (wxChar *)NULL;
2633 }
2634
2635 // hours
2636 offset = 60*(10*(*p - _T('0')) + (*(p + 1) - _T('0')));
2637
2638 p += 2;
2639
2640 if ( !wxIsdigit(*p) || !wxIsdigit(*(p + 1)) )
2641 {
2642 return (wxChar *)NULL;
2643 }
2644
2645 // minutes
2646 offset += 10*(*p - _T('0')) + (*(p + 1) - _T('0'));
2647
2648 if ( !plus )
2649 {
2650 offset = -offset;
2651 }
2652
2653 p += 2;
2654 }
2655 else
2656 {
2657 // the symbolic timezone given: may be either military timezone or one
2658 // of standard abbreviations
2659 if ( !*(p + 1) )
2660 {
2661 // military: Z = UTC, J unused, A = -1, ..., Y = +12
2662 static const int offsets[26] =
2663 {
2664 //A B C D E F G H I J K L M
2665 -1, -2, -3, -4, -5, -6, -7, -8, -9, 0, -10, -11, -12,
2666 //N O P R Q S T U V W Z Y Z
2667 +1, +2, +3, +4, +5, +6, +7, +8, +9, +10, +11, +12, 0
2668 };
2669
2670 if ( *p < _T('A') || *p > _T('Z') || *p == _T('J') )
2671 {
2672 wxLogDebug(_T("Invalid militaty timezone '%c'"), *p);
2673
2674 return (wxChar *)NULL;
2675 }
2676
2677 offset = offsets[*p++ - _T('A')];
2678 }
2679 else
2680 {
2681 // abbreviation
2682 wxString tz = p;
2683 if ( tz == _T("UT") || tz == _T("UTC") || tz == _T("GMT") )
2684 offset = 0;
2685 else if ( tz == _T("AST") )
2686 offset = AST - GMT0;
2687 else if ( tz == _T("ADT") )
2688 offset = ADT - GMT0;
2689 else if ( tz == _T("EST") )
2690 offset = EST - GMT0;
2691 else if ( tz == _T("EDT") )
2692 offset = EDT - GMT0;
2693 else if ( tz == _T("CST") )
2694 offset = CST - GMT0;
2695 else if ( tz == _T("CDT") )
2696 offset = CDT - GMT0;
2697 else if ( tz == _T("MST") )
2698 offset = MST - GMT0;
2699 else if ( tz == _T("MDT") )
2700 offset = MDT - GMT0;
2701 else if ( tz == _T("PST") )
2702 offset = PST - GMT0;
2703 else if ( tz == _T("PDT") )
2704 offset = PDT - GMT0;
2705 else
2706 {
2707 wxLogDebug(_T("Unknown RFC 822 timezone '%s'"), p);
2708
2709 return (wxChar *)NULL;
2710 }
2711
2712 p += tz.length();
2713 }
2714
2715 // make it minutes
2716 offset *= 60;
2717 }
2718
2719 // the spec was correct
2720 Set(day, mon, year, hour, min, sec);
487c1f7e 2721 MakeTimezone((wxDateTime_t)(60*offset));
cd0b1709
VZ
2722
2723 return p;
2724}
2725
f0f951fa
VZ
2726const wxChar *wxDateTime::ParseFormat(const wxChar *date,
2727 const wxChar *format,
2728 const wxDateTime& dateDef)
cd0b1709
VZ
2729{
2730 wxCHECK_MSG( date && format, (wxChar *)NULL,
f0f951fa 2731 _T("NULL pointer in wxDateTime::ParseFormat()") );
cd0b1709 2732
f0f951fa
VZ
2733 wxString str;
2734 unsigned long num;
cd0b1709 2735
f0f951fa 2736 // what fields have we found?
68379eaf
WS
2737 bool haveWDay = false,
2738 haveYDay = false,
2739 haveDay = false,
2740 haveMon = false,
2741 haveYear = false,
2742 haveHour = false,
2743 haveMin = false,
2744 haveSec = false;
2745
2746 bool hourIsIn12hFormat = false, // or in 24h one?
2747 isPM = false; // AM by default
f0f951fa
VZ
2748
2749 // and the value of the items we have (init them to get rid of warnings)
2750 wxDateTime_t sec = 0,
2751 min = 0,
2752 hour = 0;
2753 WeekDay wday = Inv_WeekDay;
2754 wxDateTime_t yday = 0,
2755 mday = 0;
2756 wxDateTime::Month mon = Inv_Month;
2757 int year = 0;
2758
2759 const wxChar *input = date;
2760 for ( const wxChar *fmt = format; *fmt; fmt++ )
2761 {
2762 if ( *fmt != _T('%') )
2763 {
2764 if ( wxIsspace(*fmt) )
2765 {
2766 // a white space in the format string matches 0 or more white
2767 // spaces in the input
2768 while ( wxIsspace(*input) )
2769 {
2770 input++;
2771 }
2772 }
2773 else // !space
2774 {
2775 // any other character (not whitespace, not '%') must be
2776 // matched by itself in the input
2777 if ( *input++ != *fmt )
2778 {
2779 // no match
2780 return (wxChar *)NULL;
2781 }
2782 }
2783
2784 // done with this format char
2785 continue;
2786 }
2787
2788 // start of a format specification
f6bcfd97
BP
2789
2790 // parse the optional width
2791 size_t width = 0;
1c193821 2792 while ( wxIsdigit(*++fmt) )
f6bcfd97
BP
2793 {
2794 width *= 10;
2795 width += *fmt - _T('0');
2796 }
2797
4147efe9
VZ
2798 // the default widths for the various fields
2799 if ( !width )
2800 {
2801 switch ( *fmt )
2802 {
2803 case _T('Y'): // year has 4 digits
2804 width = 4;
2805 break;
2806
2807 case _T('j'): // day of year has 3 digits
2808 case _T('l'): // milliseconds have 3 digits
2809 width = 3;
2810 break;
2811
2812 case _T('w'): // week day as number has only one
2813 width = 1;
2814 break;
2815
2816 default:
2817 // default for all other fields
2818 width = 2;
2819 }
2820 }
2821
f6bcfd97
BP
2822 // then the format itself
2823 switch ( *fmt )
f0f951fa
VZ
2824 {
2825 case _T('a'): // a weekday name
2826 case _T('A'):
2827 {
2828 int flag = *fmt == _T('a') ? Name_Abbr : Name_Full;
2829 wday = GetWeekDayFromName(GetAlphaToken(input), flag);
2830 if ( wday == Inv_WeekDay )
2831 {
2832 // no match
2833 return (wxChar *)NULL;
2834 }
2835 }
68379eaf 2836 haveWDay = true;
f0f951fa
VZ
2837 break;
2838
2839 case _T('b'): // a month name
2840 case _T('B'):
2841 {
2842 int flag = *fmt == _T('b') ? Name_Abbr : Name_Full;
2843 mon = GetMonthFromName(GetAlphaToken(input), flag);
2844 if ( mon == Inv_Month )
2845 {
2846 // no match
2847 return (wxChar *)NULL;
2848 }
2849 }
68379eaf 2850 haveMon = true;
f0f951fa
VZ
2851 break;
2852
2853 case _T('c'): // locale default date and time representation
2854 {
2855 wxDateTime dt;
2856
2857 // this is the format which corresponds to ctime() output
2858 // and strptime("%c") should parse it, so try it first
41acf5c0 2859 static const wxChar *fmtCtime = _T("%a %b %d %H:%M:%S %Y");
f0f951fa
VZ
2860
2861 const wxChar *result = dt.ParseFormat(input, fmtCtime);
2862 if ( !result )
2863 {
2864 result = dt.ParseFormat(input, _T("%x %X"));
2865 }
2866
2867 if ( !result )
2868 {
2869 result = dt.ParseFormat(input, _T("%X %x"));
2870 }
2871
2872 if ( !result )
2873 {
2874 // we've tried everything and still no match
2875 return (wxChar *)NULL;
2876 }
2877
be4017f8
VZ
2878 Tm tm = dt.GetTm();
2879
2880 haveDay = haveMon = haveYear =
68379eaf 2881 haveHour = haveMin = haveSec = true;
be4017f8
VZ
2882
2883 hour = tm.hour;
2884 min = tm.min;
2885 sec = tm.sec;
2886
2887 year = tm.year;
2888 mon = tm.mon;
2889 mday = tm.mday;
2890
f0f951fa
VZ
2891 input = result;
2892 }
2893 break;
2894
2895 case _T('d'): // day of a month (01-31)
f6bcfd97
BP
2896 if ( !GetNumericToken(width, input, &num) ||
2897 (num > 31) || (num < 1) )
f0f951fa
VZ
2898 {
2899 // no match
2900 return (wxChar *)NULL;
2901 }
2902
2903 // we can't check whether the day range is correct yet, will
2904 // do it later - assume ok for now
68379eaf 2905 haveDay = true;
f0f951fa
VZ
2906 mday = (wxDateTime_t)num;
2907 break;
5f287370 2908
f0f951fa 2909 case _T('H'): // hour in 24h format (00-23)
f6bcfd97 2910 if ( !GetNumericToken(width, input, &num) || (num > 23) )
f0f951fa
VZ
2911 {
2912 // no match
2913 return (wxChar *)NULL;
2914 }
2915
68379eaf 2916 haveHour = true;
f0f951fa
VZ
2917 hour = (wxDateTime_t)num;
2918 break;
2919
2920 case _T('I'): // hour in 12h format (01-12)
f6bcfd97 2921 if ( !GetNumericToken(width, input, &num) || !num || (num > 12) )
f0f951fa
VZ
2922 {
2923 // no match
2924 return (wxChar *)NULL;
2925 }
2926
68379eaf
WS
2927 haveHour = true;
2928 hourIsIn12hFormat = true;
479cd5de 2929 hour = (wxDateTime_t)(num % 12); // 12 should be 0
f0f951fa
VZ
2930 break;
2931
2932 case _T('j'): // day of the year
f6bcfd97 2933 if ( !GetNumericToken(width, input, &num) || !num || (num > 366) )
f0f951fa
VZ
2934 {
2935 // no match
2936 return (wxChar *)NULL;
2937 }
2938
68379eaf 2939 haveYDay = true;
f0f951fa
VZ
2940 yday = (wxDateTime_t)num;
2941 break;
2942
2943 case _T('m'): // month as a number (01-12)
f6bcfd97 2944 if ( !GetNumericToken(width, input, &num) || !num || (num > 12) )
f0f951fa
VZ
2945 {
2946 // no match
2947 return (wxChar *)NULL;
2948 }
2949
68379eaf 2950 haveMon = true;
be4017f8 2951 mon = (Month)(num - 1);
f0f951fa
VZ
2952 break;
2953
2954 case _T('M'): // minute as a decimal number (00-59)
f6bcfd97 2955 if ( !GetNumericToken(width, input, &num) || (num > 59) )
f0f951fa
VZ
2956 {
2957 // no match
2958 return (wxChar *)NULL;
2959 }
2960
68379eaf 2961 haveMin = true;
f0f951fa
VZ
2962 min = (wxDateTime_t)num;
2963 break;
2964
2965 case _T('p'): // AM or PM string
2966 {
2967 wxString am, pm, token = GetAlphaToken(input);
2968
2969 GetAmPmStrings(&am, &pm);
bb8a8478 2970 if (am.empty() && pm.empty())
0308fd61 2971 return (wxChar *)NULL; // no am/pm strings defined
f0f951fa
VZ
2972 if ( token.CmpNoCase(pm) == 0 )
2973 {
68379eaf 2974 isPM = true;
f0f951fa
VZ
2975 }
2976 else if ( token.CmpNoCase(am) != 0 )
2977 {
2978 // no match
2979 return (wxChar *)NULL;
2980 }
2981 }
2982 break;
2983
2984 case _T('r'): // time as %I:%M:%S %p
2985 {
2986 wxDateTime dt;
2987 input = dt.ParseFormat(input, _T("%I:%M:%S %p"));
2988 if ( !input )
2989 {
2990 // no match
2991 return (wxChar *)NULL;
2992 }
2993
68379eaf 2994 haveHour = haveMin = haveSec = true;
f0f951fa
VZ
2995
2996 Tm tm = dt.GetTm();
2997 hour = tm.hour;
2998 min = tm.min;
2999 sec = tm.sec;
3000 }
3001 break;
3002
3003 case _T('R'): // time as %H:%M
3004 {
3005 wxDateTime dt;
3006 input = dt.ParseFormat(input, _T("%H:%M"));
3007 if ( !input )
3008 {
3009 // no match
3010 return (wxChar *)NULL;
3011 }
3012
68379eaf 3013 haveHour = haveMin = true;
f0f951fa
VZ
3014
3015 Tm tm = dt.GetTm();
3016 hour = tm.hour;
3017 min = tm.min;
3018 }
3019
3020 case _T('S'): // second as a decimal number (00-61)
f6bcfd97 3021 if ( !GetNumericToken(width, input, &num) || (num > 61) )
f0f951fa
VZ
3022 {
3023 // no match
3024 return (wxChar *)NULL;
3025 }
3026
68379eaf 3027 haveSec = true;
f0f951fa
VZ
3028 sec = (wxDateTime_t)num;
3029 break;
3030
3031 case _T('T'): // time as %H:%M:%S
3032 {
3033 wxDateTime dt;
3034 input = dt.ParseFormat(input, _T("%H:%M:%S"));
3035 if ( !input )
3036 {
3037 // no match
3038 return (wxChar *)NULL;
3039 }
3040
68379eaf 3041 haveHour = haveMin = haveSec = true;
f0f951fa
VZ
3042
3043 Tm tm = dt.GetTm();
3044 hour = tm.hour;
3045 min = tm.min;
3046 sec = tm.sec;
3047 }
be4017f8 3048 break;
f0f951fa
VZ
3049
3050 case _T('w'): // weekday as a number (0-6), Sunday = 0
f6bcfd97 3051 if ( !GetNumericToken(width, input, &num) || (wday > 6) )
f0f951fa
VZ
3052 {
3053 // no match
3054 return (wxChar *)NULL;
3055 }
3056
68379eaf 3057 haveWDay = true;
f0f951fa
VZ
3058 wday = (WeekDay)num;
3059 break;
3060
3061 case _T('x'): // locale default date representation
3062#ifdef HAVE_STRPTIME
2423ef22 3063 // try using strptime() -- it may fail even if the input is
f0f951fa 3064 // correct but the date is out of range, so we will fall back
2423ef22 3065 // to our generic code anyhow
f0f951fa
VZ
3066 {
3067 struct tm tm;
2423ef22
VZ
3068
3069 const wxChar *result = CallStrptime(input, "%x", &tm);
f0f951fa
VZ
3070 if ( result )
3071 {
3072 input = result;
3073
68379eaf 3074 haveDay = haveMon = haveYear = true;
f0f951fa
VZ
3075
3076 year = 1900 + tm.tm_year;
3077 mon = (Month)tm.tm_mon;
3078 mday = tm.tm_mday;
3079
3080 break;
3081 }
3082 }
3083#endif // HAVE_STRPTIME
3084
3085 // TODO query the LOCALE_IDATE setting under Win32
3086 {
3087 wxDateTime dt;
3088
41acf5c0 3089 wxString fmtDate, fmtDateAlt;
f0f951fa
VZ
3090 if ( IsWestEuropeanCountry(GetCountry()) ||
3091 GetCountry() == Russia )
3092 {
3093 fmtDate = _T("%d/%m/%y");
41acf5c0 3094 fmtDateAlt = _T("%m/%d/%y");
f0f951fa
VZ
3095 }
3096 else // assume USA
3097 {
3098 fmtDate = _T("%m/%d/%y");
41acf5c0 3099 fmtDateAlt = _T("%d/%m/%y");
f0f951fa
VZ
3100 }
3101
3102 const wxChar *result = dt.ParseFormat(input, fmtDate);
41acf5c0
VZ
3103
3104 if ( !result )
3105 {
3106 // ok, be nice and try another one
3107 result = dt.ParseFormat(input, fmtDateAlt);
3108 }
3109
f0f951fa
VZ
3110 if ( !result )
3111 {
3112 // bad luck
3113 return (wxChar *)NULL;
3114 }
3115
3116 Tm tm = dt.GetTm();
3117
68379eaf 3118 haveDay = haveMon = haveYear = true;
f0f951fa
VZ
3119
3120 year = tm.year;
3121 mon = tm.mon;
3122 mday = tm.mday;
3123
3124 input = result;
3125 }
3126
3127 break;
3128
3129 case _T('X'): // locale default time representation
3130#ifdef HAVE_STRPTIME
3131 {
3132 // use strptime() to do it for us (FIXME !Unicode friendly)
3133 struct tm tm;
2423ef22 3134 input = CallStrptime(input, "%X", &tm);
f0f951fa
VZ
3135 if ( !input )
3136 {
3137 return (wxChar *)NULL;
3138 }
3139
68379eaf 3140 haveHour = haveMin = haveSec = true;
f0f951fa
VZ
3141
3142 hour = tm.tm_hour;
3143 min = tm.tm_min;
3144 sec = tm.tm_sec;
3145 }
3146#else // !HAVE_STRPTIME
3147 // TODO under Win32 we can query the LOCALE_ITIME system
3148 // setting which says whether the default time format is
3149 // 24 or 12 hour
3150 {
3151 // try to parse what follows as "%H:%M:%S" and, if this
3152 // fails, as "%I:%M:%S %p" - this should catch the most
3153 // common cases
3154 wxDateTime dt;
3155
3156 const wxChar *result = dt.ParseFormat(input, _T("%T"));
3157 if ( !result )
3158 {
3159 result = dt.ParseFormat(input, _T("%r"));
3160 }
3161
3162 if ( !result )
3163 {
3164 // no match
3165 return (wxChar *)NULL;
3166 }
3167
68379eaf 3168 haveHour = haveMin = haveSec = true;
f0f951fa
VZ
3169
3170 Tm tm = dt.GetTm();
3171 hour = tm.hour;
3172 min = tm.min;
3173 sec = tm.sec;
3174
3175 input = result;
3176 }
3177#endif // HAVE_STRPTIME/!HAVE_STRPTIME
3178 break;
3179
3180 case _T('y'): // year without century (00-99)
f6bcfd97 3181 if ( !GetNumericToken(width, input, &num) || (num > 99) )
f0f951fa
VZ
3182 {
3183 // no match
3184 return (wxChar *)NULL;
3185 }
3186
68379eaf 3187 haveYear = true;
f6bcfd97
BP
3188
3189 // TODO should have an option for roll over date instead of
3190 // hard coding it here
3191 year = (num > 30 ? 1900 : 2000) + (wxDateTime_t)num;
f0f951fa
VZ
3192 break;
3193
3194 case _T('Y'): // year with century
f6bcfd97 3195 if ( !GetNumericToken(width, input, &num) )
f0f951fa
VZ
3196 {
3197 // no match
3198 return (wxChar *)NULL;
3199 }
3200
68379eaf 3201 haveYear = true;
be4017f8 3202 year = (wxDateTime_t)num;
f0f951fa
VZ
3203 break;
3204
3205 case _T('Z'): // timezone name
3206 wxFAIL_MSG(_T("TODO"));
3207 break;
3208
3209 case _T('%'): // a percent sign
3210 if ( *input++ != _T('%') )
3211 {
3212 // no match
3213 return (wxChar *)NULL;
3214 }
3215 break;
3216
3217 case 0: // the end of string
3218 wxFAIL_MSG(_T("unexpected format end"));
3219
3220 // fall through
3221
3222 default: // not a known format spec
3223 return (wxChar *)NULL;
3224 }
3225 }
3226
3227 // format matched, try to construct a date from what we have now
3228 Tm tmDef;
3229 if ( dateDef.IsValid() )
3230 {
3231 // take this date as default
3232 tmDef = dateDef.GetTm();
3233 }
c3302e7e 3234 else if ( IsValid() )
f0f951fa
VZ
3235 {
3236 // if this date is valid, don't change it
3237 tmDef = GetTm();
3238 }
3239 else
3240 {
3241 // no default and this date is invalid - fall back to Today()
3242 tmDef = Today().GetTm();
3243 }
3244
3245 Tm tm = tmDef;
3246
3247 // set the date
3248 if ( haveYear )
3249 {
3250 tm.year = year;
3251 }
3252
3253 // TODO we don't check here that the values are consistent, if both year
3254 // day and month/day were found, we just ignore the year day and we
3255 // also always ignore the week day
3256 if ( haveMon && haveDay )
3257 {
be4017f8
VZ
3258 if ( mday > GetNumOfDaysInMonth(tm.year, mon) )
3259 {
3260 wxLogDebug(_T("bad month day in wxDateTime::ParseFormat"));
3261
3262 return (wxChar *)NULL;
3263 }
3264
f0f951fa
VZ
3265 tm.mon = mon;
3266 tm.mday = mday;
3267 }
3268 else if ( haveYDay )
3269 {
be4017f8
VZ
3270 if ( yday > GetNumberOfDays(tm.year) )
3271 {
3272 wxLogDebug(_T("bad year day in wxDateTime::ParseFormat"));
3273
3274 return (wxChar *)NULL;
3275 }
3276
f0f951fa
VZ
3277 Tm tm2 = wxDateTime(1, Jan, tm.year).SetToYearDay(yday).GetTm();
3278
3279 tm.mon = tm2.mon;
3280 tm.mday = tm2.mday;
3281 }
3282
3283 // deal with AM/PM
3284 if ( haveHour && hourIsIn12hFormat && isPM )
3285 {
3286 // translate to 24hour format
3287 hour += 12;
3288 }
3289 //else: either already in 24h format or no translation needed
3290
3291 // set the time
3292 if ( haveHour )
3293 {
5f287370 3294 tm.hour = hour;
f0f951fa
VZ
3295 }
3296
3297 if ( haveMin )
3298 {
3299 tm.min = min;
3300 }
3301
3302 if ( haveSec )
3303 {
3304 tm.sec = sec;
3305 }
3306
3307 Set(tm);
3308
88a036ce
VZ
3309 // finally check that the week day is consistent -- if we had it
3310 if ( haveWDay && GetWeekDay() != wday )
3311 {
3312 wxLogDebug(_T("inconsistsnet week day in wxDateTime::ParseFormat()"));
3313
3314 return NULL;
3315 }
3316
f0f951fa 3317 return input;
cd0b1709
VZ
3318}
3319
3320const wxChar *wxDateTime::ParseDateTime(const wxChar *date)
3321{
3322 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
3323
de07d200
VZ
3324 // Set to current day and hour, so strings like '14:00' becomes today at
3325 // 14, not some other random date
3326 wxDateTime dtDate = wxDateTime::Today();
3327 wxDateTime dtTime = wxDateTime::Today();
cd0b1709 3328
de07d200
VZ
3329 const wxChar* pchTime;
3330
3331 // Try to parse the beginning of the string as a date
3332 const wxChar* pchDate = dtDate.ParseDate(date);
3333
3334 // We got a date in the beginning, see if there is a time specified after the date
3335 if ( pchDate )
3336 {
3337 // Skip spaces, as the ParseTime() function fails on spaces
3338 while ( wxIsspace(*pchDate) )
3339 pchDate++;
3340
3341 pchTime = dtTime.ParseTime(pchDate);
3342 }
3343 else // no date in the beginning
3344 {
3345 // check and see if we have a time followed by a date
3346 pchTime = dtTime.ParseTime(date);
3347 if ( pchTime )
3348 {
3349 while ( wxIsspace(*pchTime) )
3350 pchTime++;
3351
3352 pchDate = dtDate.ParseDate(pchTime);
3353 }
3354 }
3355
3356 // If we have a date specified, set our own data to the same date
3357 if ( !pchDate || !pchTime )
3358 return NULL;
3359
3360 Set(dtDate.GetDay(), dtDate.GetMonth(), dtDate.GetYear(),
3361 dtTime.GetHour(), dtTime.GetMinute(), dtTime.GetSecond(),
3362 dtTime.GetMillisecond());
3363
3364 // Return endpoint of scan
3365 return pchDate > pchTime ? pchDate : pchTime;
cd0b1709
VZ
3366}
3367
3368const wxChar *wxDateTime::ParseDate(const wxChar *date)
3369{
3370 // this is a simplified version of ParseDateTime() which understands only
3371 // "today" (for wxDate compatibility) and digits only otherwise (and not
3372 // all esoteric constructions ParseDateTime() knows about)
3373
3374 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
3375
3376 const wxChar *p = date;
3377 while ( wxIsspace(*p) )
3378 p++;
3379
fd9f9f4c
VZ
3380 // some special cases
3381 static struct
cd0b1709 3382 {
fd9f9f4c
VZ
3383 const wxChar *str;
3384 int dayDiffFromToday;
3385 } literalDates[] =
3386 {
3387 { wxTRANSLATE("today"), 0 },
3388 { wxTRANSLATE("yesterday"), -1 },
3389 { wxTRANSLATE("tomorrow"), 1 },
3390 };
3391
3392 for ( size_t n = 0; n < WXSIZEOF(literalDates); n++ )
3393 {
3394 wxString date = wxGetTranslation(literalDates[n].str);
3395 size_t len = date.length();
907173e5 3396 if ( wxStrlen(p) >= len )
fd9f9f4c 3397 {
907173e5
WS
3398 wxString str(p, len);
3399 if ( str.CmpNoCase(date) == 0 )
fd9f9f4c 3400 {
907173e5
WS
3401 // nothing can follow this, so stop here
3402 p += len;
3403
3404 int dayDiffFromToday = literalDates[n].dayDiffFromToday;
3405 *this = Today();
3406 if ( dayDiffFromToday )
3407 {
3408 *this += wxDateSpan::Days(dayDiffFromToday);
3409 }
cd0b1709 3410
907173e5
WS
3411 return p;
3412 }
fd9f9f4c 3413 }
cd0b1709
VZ
3414 }
3415
3ca6a5f0
BP
3416 // We try to guess what we have here: for each new (numeric) token, we
3417 // determine if it can be a month, day or a year. Of course, there is an
3418 // ambiguity as some numbers may be days as well as months, so we also
3419 // have the ability to back track.
3420
cd0b1709 3421 // what do we have?
68379eaf
WS
3422 bool haveDay = false, // the months day?
3423 haveWDay = false, // the day of week?
3424 haveMon = false, // the month?
3425 haveYear = false; // the year?
cd0b1709
VZ
3426
3427 // and the value of the items we have (init them to get rid of warnings)
3428 WeekDay wday = Inv_WeekDay;
3429 wxDateTime_t day = 0;
3430 wxDateTime::Month mon = Inv_Month;
3431 int year = 0;
3432
3433 // tokenize the string
f6bcfd97 3434 size_t nPosCur = 0;
62ae2780 3435 static const wxChar *dateDelimiters = _T(".,/-\t\r\n ");
f6bcfd97 3436 wxStringTokenizer tok(p, dateDelimiters);
cd0b1709
VZ
3437 while ( tok.HasMoreTokens() )
3438 {
3439 wxString token = tok.GetNextToken();
3ca6a5f0
BP
3440 if ( !token )
3441 continue;
cd0b1709
VZ
3442
3443 // is it a number?
3444 unsigned long val;
fde5a86b 3445 if ( token.ToULong(&val) )
cd0b1709
VZ
3446 {
3447 // guess what this number is
3448
68379eaf
WS
3449 bool isDay = false,
3450 isMonth = false,
3451 isYear = false;
3ca6a5f0
BP
3452
3453 if ( !haveMon && val > 0 && val <= 12 )
cd0b1709 3454 {
3ca6a5f0 3455 // assume it is month
68379eaf 3456 isMonth = true;
3ca6a5f0
BP
3457 }
3458 else // not the month
3459 {
560e2916 3460 if ( haveDay )
cd0b1709 3461 {
560e2916 3462 // this can only be the year
68379eaf 3463 isYear = true;
cd0b1709 3464 }
560e2916 3465 else // may be either day or year
cd0b1709 3466 {
42841dfc
WS
3467 wxDateTime_t maxDays = (wxDateTime_t)(
3468 haveMon
560e2916 3469 ? GetNumOfDaysInMonth(haveYear ? year : Inv_Year, mon)
42841dfc
WS
3470 : 31
3471 );
560e2916
VZ
3472
3473 // can it be day?
3474 if ( (val == 0) || (val > (unsigned long)maxDays) )
3475 {
3476 // no
68379eaf 3477 isYear = true;
560e2916
VZ
3478 }
3479 else // yes, suppose it's the day
3480 {
68379eaf 3481 isDay = true;
560e2916 3482 }
cd0b1709
VZ
3483 }
3484 }
3485
cd0b1709
VZ
3486 if ( isYear )
3487 {
3488 if ( haveYear )
cd0b1709 3489 break;
cd0b1709 3490
68379eaf 3491 haveYear = true;
cd0b1709 3492
479cd5de 3493 year = (wxDateTime_t)val;
cd0b1709 3494 }
3ca6a5f0 3495 else if ( isDay )
cd0b1709 3496 {
3ca6a5f0 3497 if ( haveDay )
cd0b1709 3498 break;
cd0b1709 3499
68379eaf 3500 haveDay = true;
cd0b1709 3501
3ca6a5f0 3502 day = (wxDateTime_t)val;
cd0b1709 3503 }
3ca6a5f0 3504 else if ( isMonth )
cd0b1709 3505 {
68379eaf 3506 haveMon = true;
cd0b1709 3507
3ca6a5f0 3508 mon = (Month)(val - 1);
cd0b1709
VZ
3509 }
3510 }
3511 else // not a number
3512 {
f6bcfd97
BP
3513 // be careful not to overwrite the current mon value
3514 Month mon2 = GetMonthFromName(token, Name_Full | Name_Abbr);
3515 if ( mon2 != Inv_Month )
cd0b1709
VZ
3516 {
3517 // it's a month
3518 if ( haveMon )
3519 {
6411a32c
VZ
3520 // but we already have a month - maybe we guessed wrong?
3521 if ( !haveDay )
3522 {
f5cd9787 3523 // no need to check in month range as always < 12, but
6411a32c 3524 // the days are counted from 1 unlike the months
42841dfc 3525 day = (wxDateTime_t)(mon + 1);
68379eaf 3526 haveDay = true;
6411a32c
VZ
3527 }
3528 else
3529 {
3530 // could possible be the year (doesn't the year come
3531 // before the month in the japanese format?) (FIXME)
3532 break;
f5cd9787 3533 }
cd0b1709
VZ
3534 }
3535
f6bcfd97
BP
3536 mon = mon2;
3537
68379eaf 3538 haveMon = true;
cd0b1709 3539 }
6411a32c 3540 else // not a valid month name
cd0b1709 3541 {
f0f951fa 3542 wday = GetWeekDayFromName(token, Name_Full | Name_Abbr);
cd0b1709
VZ
3543 if ( wday != Inv_WeekDay )
3544 {
3545 // a week day
3546 if ( haveWDay )
3547 {
3548 break;
3549 }
3550
68379eaf 3551 haveWDay = true;
cd0b1709 3552 }
6411a32c 3553 else // not a valid weekday name
cd0b1709
VZ
3554 {
3555 // try the ordinals
3556 static const wxChar *ordinals[] =
3557 {
3558 wxTRANSLATE("first"),
3559 wxTRANSLATE("second"),
3560 wxTRANSLATE("third"),
3561 wxTRANSLATE("fourth"),
3562 wxTRANSLATE("fifth"),
3563 wxTRANSLATE("sixth"),
3564 wxTRANSLATE("seventh"),
3565 wxTRANSLATE("eighth"),
3566 wxTRANSLATE("ninth"),
3567 wxTRANSLATE("tenth"),
3568 wxTRANSLATE("eleventh"),
3569 wxTRANSLATE("twelfth"),
3570 wxTRANSLATE("thirteenth"),
3571 wxTRANSLATE("fourteenth"),
3572 wxTRANSLATE("fifteenth"),
3573 wxTRANSLATE("sixteenth"),
3574 wxTRANSLATE("seventeenth"),
3575 wxTRANSLATE("eighteenth"),
3576 wxTRANSLATE("nineteenth"),
3577 wxTRANSLATE("twentieth"),
3578 // that's enough - otherwise we'd have problems with
6411a32c 3579 // composite (or not) ordinals
cd0b1709
VZ
3580 };
3581
3582 size_t n;
3583 for ( n = 0; n < WXSIZEOF(ordinals); n++ )
3584 {
3585 if ( token.CmpNoCase(ordinals[n]) == 0 )
3586 {
3587 break;
3588 }
3589 }
3590
3591 if ( n == WXSIZEOF(ordinals) )
3592 {
3593 // stop here - something unknown
3594 break;
3595 }
3596
3597 // it's a day
3598 if ( haveDay )
3599 {
3600 // don't try anything here (as in case of numeric day
3601 // above) - the symbolic day spec should always
3602 // precede the month/year
3603 break;
3604 }
3605
68379eaf 3606 haveDay = true;
cd0b1709 3607
33ac7e6f 3608 day = (wxDateTime_t)(n + 1);
cd0b1709
VZ
3609 }
3610 }
3611 }
f6bcfd97
BP
3612
3613 nPosCur = tok.GetPosition();
cd0b1709
VZ
3614 }
3615
3616 // either no more tokens or the scan was stopped by something we couldn't
3617 // parse - in any case, see if we can construct a date from what we have
3618 if ( !haveDay && !haveWDay )
3619 {
f6bcfd97 3620 wxLogDebug(_T("ParseDate: no day, no weekday hence no date."));
cd0b1709
VZ
3621
3622 return (wxChar *)NULL;
3623 }
3624
3625 if ( haveWDay && (haveMon || haveYear || haveDay) &&
f6bcfd97 3626 !(haveDay && haveMon && haveYear) )
cd0b1709
VZ
3627 {
3628 // without adjectives (which we don't support here) the week day only
3629 // makes sense completely separately or with the full date
3630 // specification (what would "Wed 1999" mean?)
3631 return (wxChar *)NULL;
3632 }
3633
f6bcfd97
BP
3634 if ( !haveWDay && haveYear && !(haveDay && haveMon) )
3635 {
3636 // may be we have month and day instead of day and year?
3637 if ( haveDay && !haveMon )
3638 {
3639 if ( day <= 12 )
3640 {
3641 // exchange day and month
3642 mon = (wxDateTime::Month)(day - 1);
3643
3644 // we're in the current year then
196be0f1 3645 if ( (year > 0) && (year <= (int)GetNumOfDaysInMonth(Inv_Year, mon)) )
f6bcfd97 3646 {
42841dfc 3647 day = (wxDateTime_t)year;
f6bcfd97 3648
68379eaf
WS
3649 haveMon = true;
3650 haveYear = false;
f6bcfd97 3651 }
68379eaf 3652 //else: no, can't exchange, leave haveMon == false
f6bcfd97
BP
3653 }
3654 }
3655
3656 if ( !haveMon )
3657 {
3658 // if we give the year, month and day must be given too
3659 wxLogDebug(_T("ParseDate: day and month should be specified if year is."));
3660
3661 return (wxChar *)NULL;
3662 }
3663 }
3664
cd0b1709
VZ
3665 if ( !haveMon )
3666 {
3667 mon = GetCurrentMonth();
3668 }
3669
3670 if ( !haveYear )
3671 {
3672 year = GetCurrentYear();
3673 }
3674
3675 if ( haveDay )
3676 {
3677 Set(day, mon, year);
3678
3679 if ( haveWDay )
3680 {
3681 // check that it is really the same
3682 if ( GetWeekDay() != wday )
3683 {
3684 // inconsistency detected
f6bcfd97
BP
3685 wxLogDebug(_T("ParseDate: inconsistent day/weekday."));
3686
cd0b1709
VZ
3687 return (wxChar *)NULL;
3688 }
3689 }
3690 }
3691 else // haveWDay
3692 {
3693 *this = Today();
3694
3695 SetToWeekDayInSameWeek(wday);
3696 }
3697
f6bcfd97
BP
3698 // return the pointer to the first unparsed char
3699 p += nPosCur;
3700 if ( nPosCur && wxStrchr(dateDelimiters, *(p - 1)) )
3701 {
3702 // if we couldn't parse the token after the delimiter, put back the
3703 // delimiter as well
3704 p--;
3705 }
3706
3707 return p;
cd0b1709
VZ
3708}
3709
3710const wxChar *wxDateTime::ParseTime(const wxChar *time)
3711{
3712 wxCHECK_MSG( time, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
3713
f0f951fa
VZ
3714 // first try some extra things
3715 static const struct
3716 {
3717 const wxChar *name;
3718 wxDateTime_t hour;
3719 } stdTimes[] =
3720 {
3721 { wxTRANSLATE("noon"), 12 },
3722 { wxTRANSLATE("midnight"), 00 },
3723 // anything else?
3724 };
cd0b1709 3725
f0f951fa
VZ
3726 for ( size_t n = 0; n < WXSIZEOF(stdTimes); n++ )
3727 {
3728 wxString timeString = wxGetTranslation(stdTimes[n].name);
3729 size_t len = timeString.length();
3730 if ( timeString.CmpNoCase(wxString(time, len)) == 0 )
3731 {
4ded51f2
CE
3732 // casts required by DigitalMars
3733 Set(stdTimes[n].hour, wxDateTime_t(0), wxDateTime_t(0));
f0f951fa
VZ
3734
3735 return time + len;
3736 }
3737 }
3738
c7f9a482
VZ
3739 // try all time formats we may think about in the order from longest to
3740 // shortest
3741
3742 // 12hour with AM/PM?
3743 const wxChar *result = ParseFormat(time, _T("%I:%M:%S %p"));
3744
f0f951fa
VZ
3745 if ( !result )
3746 {
3747 // normally, it's the same, but why not try it?
3748 result = ParseFormat(time, _T("%H:%M:%S"));
3749 }
3750
3751 if ( !result )
3752 {
c7f9a482
VZ
3753 // 12hour with AM/PM but without seconds?
3754 result = ParseFormat(time, _T("%I:%M %p"));
f0f951fa
VZ
3755 }
3756
3757 if ( !result )
3758 {
3759 // without seconds?
3760 result = ParseFormat(time, _T("%H:%M"));
3761 }
3762
3763 if ( !result )
3764 {
c7f9a482
VZ
3765 // just the hour and AM/PM?
3766 result = ParseFormat(time, _T("%I %p"));
f0f951fa
VZ
3767 }
3768
3769 if ( !result )
3770 {
3771 // just the hour?
3772 result = ParseFormat(time, _T("%H"));
3773 }
3774
3775 if ( !result )
3776 {
c7f9a482
VZ
3777 // parse the standard format: normally it is one of the formats above
3778 // but it may be set to something completely different by the user
3779 result = ParseFormat(time, _T("%X"));
f0f951fa
VZ
3780 }
3781
3782 // TODO: parse timezones
3783
3784 return result;
cd0b1709
VZ
3785}
3786
4f6aed9c
VZ
3787// ----------------------------------------------------------------------------
3788// Workdays and holidays support
3789// ----------------------------------------------------------------------------
3790
3791bool wxDateTime::IsWorkDay(Country WXUNUSED(country)) const
3792{
3793 return !wxDateTimeHolidayAuthority::IsHoliday(*this);
3794}
3795
fcc3d7cb
VZ
3796// ============================================================================
3797// wxTimeSpan
3798// ============================================================================
3799
033400eb
VZ
3800// this enum is only used in wxTimeSpan::Format() below but we can't declare
3801// it locally to the method as it provokes an internal compiler error in egcs
3802// 2.91.60 when building with -O2
3803enum TimeSpanPart
3804{
3805 Part_Week,
3806 Part_Day,
3807 Part_Hour,
3808 Part_Min,
3809 Part_Sec,
3810 Part_MSec
3811};
3812
e6ec579c
VZ
3813// not all strftime(3) format specifiers make sense here because, for example,
3814// a time span doesn't have a year nor a timezone
3815//
3816// Here are the ones which are supported (all of them are supported by strftime
3817// as well):
3818// %H hour in 24 hour format
3819// %M minute (00 - 59)
3820// %S second (00 - 59)
3821// %% percent sign
3822//
3823// Also, for MFC CTimeSpan compatibility, we support
3824// %D number of days
3825//
3826// And, to be better than MFC :-), we also have
3827// %E number of wEeks
3828// %l milliseconds (000 - 999)
fcc3d7cb
VZ
3829wxString wxTimeSpan::Format(const wxChar *format) const
3830{
e6ec579c 3831 wxCHECK_MSG( format, _T(""), _T("NULL format in wxTimeSpan::Format") );
fcc3d7cb
VZ
3832
3833 wxString str;
5b735202 3834 str.Alloc(wxStrlen(format));
e6ec579c 3835
df05cdc5
VZ
3836 // Suppose we have wxTimeSpan ts(1 /* hour */, 2 /* min */, 3 /* sec */)
3837 //
3838 // Then, of course, ts.Format("%H:%M:%S") must return "01:02:03", but the
3839 // question is what should ts.Format("%S") do? The code here returns "3273"
3840 // in this case (i.e. the total number of seconds, not just seconds % 60)
3841 // because, for me, this call means "give me entire time interval in
3842 // seconds" and not "give me the seconds part of the time interval"
3843 //
3844 // If we agree that it should behave like this, it is clear that the
3845 // interpretation of each format specifier depends on the presence of the
3846 // other format specs in the string: if there was "%H" before "%M", we
3847 // should use GetMinutes() % 60, otherwise just GetMinutes() &c
3848
3849 // we remember the most important unit found so far
033400eb 3850 TimeSpanPart partBiggest = Part_MSec;
df05cdc5 3851
f6bcfd97 3852 for ( const wxChar *pch = format; *pch; pch++ )
e6ec579c
VZ
3853 {
3854 wxChar ch = *pch;
3855
f6bcfd97 3856 if ( ch == _T('%') )
e6ec579c 3857 {
df05cdc5
VZ
3858 // the start of the format specification of the printf() below
3859 wxString fmtPrefix = _T('%');
3860
3861 // the number
3862 long n;
e6ec579c 3863
f6bcfd97 3864 ch = *++pch; // get the format spec char
e6ec579c
VZ
3865 switch ( ch )
3866 {
3867 default:
3868 wxFAIL_MSG( _T("invalid format character") );
3869 // fall through
3870
f6bcfd97 3871 case _T('%'):
df05cdc5
VZ
3872 str += ch;
3873
3874 // skip the part below switch
3875 continue;
e6ec579c 3876
f6bcfd97 3877 case _T('D'):
df05cdc5
VZ
3878 n = GetDays();
3879 if ( partBiggest < Part_Day )
3880 {
3881 n %= DAYS_PER_WEEK;
3882 }
3883 else
3884 {
3885 partBiggest = Part_Day;
3886 }
e6ec579c
VZ
3887 break;
3888
f6bcfd97 3889 case _T('E'):
df05cdc5
VZ
3890 partBiggest = Part_Week;
3891 n = GetWeeks();
e6ec579c
VZ
3892 break;
3893
f6bcfd97 3894 case _T('H'):
df05cdc5
VZ
3895 n = GetHours();
3896 if ( partBiggest < Part_Hour )
3897 {
3898 n %= HOURS_PER_DAY;
3899 }
3900 else
3901 {
3902 partBiggest = Part_Hour;
3903 }
3904
3905 fmtPrefix += _T("02");
e6ec579c
VZ
3906 break;
3907
f6bcfd97 3908 case _T('l'):
df05cdc5
VZ
3909 n = GetMilliseconds().ToLong();
3910 if ( partBiggest < Part_MSec )
3911 {
3912 n %= 1000;
3913 }
3914 //else: no need to reset partBiggest to Part_MSec, it is
3915 // the least significant one anyhow
3916
3917 fmtPrefix += _T("03");
e6ec579c
VZ
3918 break;
3919
f6bcfd97 3920 case _T('M'):
df05cdc5
VZ
3921 n = GetMinutes();
3922 if ( partBiggest < Part_Min )
3923 {
3924 n %= MIN_PER_HOUR;
3925 }
3926 else
3927 {
3928 partBiggest = Part_Min;
3929 }
3930
3931 fmtPrefix += _T("02");
e6ec579c
VZ
3932 break;
3933
f6bcfd97 3934 case _T('S'):
df05cdc5
VZ
3935 n = GetSeconds().ToLong();
3936 if ( partBiggest < Part_Sec )
3937 {
3938 n %= SEC_PER_MIN;
3939 }
3940 else
3941 {
3942 partBiggest = Part_Sec;
3943 }
3944
3945 fmtPrefix += _T("02");
e6ec579c
VZ
3946 break;
3947 }
3948
df05cdc5
VZ
3949 str += wxString::Format(fmtPrefix + _T("ld"), n);
3950 }
3951 else
3952 {
3953 // normal character, just copy
3954 str += ch;
e6ec579c 3955 }
e6ec579c 3956 }
fcc3d7cb
VZ
3957
3958 return str;
3959}
4f6aed9c
VZ
3960
3961// ============================================================================
3962// wxDateTimeHolidayAuthority and related classes
3963// ============================================================================
3964
3965#include "wx/arrimpl.cpp"
3966
f6bcfd97 3967WX_DEFINE_OBJARRAY(wxDateTimeArray);
4f6aed9c
VZ
3968
3969static int wxCMPFUNC_CONV
3970wxDateTimeCompareFunc(wxDateTime **first, wxDateTime **second)
3971{
3972 wxDateTime dt1 = **first,
3973 dt2 = **second;
3974
3975 return dt1 == dt2 ? 0 : dt1 < dt2 ? -1 : +1;
3976}
3977
3978// ----------------------------------------------------------------------------
3979// wxDateTimeHolidayAuthority
3980// ----------------------------------------------------------------------------
3981
3982wxHolidayAuthoritiesArray wxDateTimeHolidayAuthority::ms_authorities;
3983
3984/* static */
3985bool wxDateTimeHolidayAuthority::IsHoliday(const wxDateTime& dt)
3986{
df5168c4 3987 size_t count = ms_authorities.size();
4f6aed9c
VZ
3988 for ( size_t n = 0; n < count; n++ )
3989 {
3990 if ( ms_authorities[n]->DoIsHoliday(dt) )
3991 {
68379eaf 3992 return true;
4f6aed9c
VZ
3993 }
3994 }
3995
68379eaf 3996 return false;
4f6aed9c
VZ
3997}
3998
3999/* static */
4000size_t
4001wxDateTimeHolidayAuthority::GetHolidaysInRange(const wxDateTime& dtStart,
4002 const wxDateTime& dtEnd,
4003 wxDateTimeArray& holidays)
4004{
4005 wxDateTimeArray hol;
4006
df5168c4 4007 holidays.Clear();
4f6aed9c 4008
df5168c4 4009 size_t count = ms_authorities.size();
6dc6fda6 4010 for ( size_t nAuth = 0; nAuth < count; nAuth++ )
4f6aed9c 4011 {
6dc6fda6 4012 ms_authorities[nAuth]->DoGetHolidaysInRange(dtStart, dtEnd, hol);
4f6aed9c
VZ
4013
4014 WX_APPEND_ARRAY(holidays, hol);
4015 }
4016
4017 holidays.Sort(wxDateTimeCompareFunc);
4018
df5168c4 4019 return holidays.size();
4f6aed9c
VZ
4020}
4021
4022/* static */
4023void wxDateTimeHolidayAuthority::ClearAllAuthorities()
4024{
4025 WX_CLEAR_ARRAY(ms_authorities);
4026}
4027
4028/* static */
4029void wxDateTimeHolidayAuthority::AddAuthority(wxDateTimeHolidayAuthority *auth)
4030{
df5168c4 4031 ms_authorities.push_back(auth);
4f6aed9c
VZ
4032}
4033
5e233068
WS
4034wxDateTimeHolidayAuthority::~wxDateTimeHolidayAuthority()
4035{
4036 // required here for Darwin
4037}
4038
4f6aed9c
VZ
4039// ----------------------------------------------------------------------------
4040// wxDateTimeWorkDays
4041// ----------------------------------------------------------------------------
4042
4043bool wxDateTimeWorkDays::DoIsHoliday(const wxDateTime& dt) const
4044{
4045 wxDateTime::WeekDay wd = dt.GetWeekDay();
4046
4047 return (wd == wxDateTime::Sun) || (wd == wxDateTime::Sat);
4048}
4049
4050size_t wxDateTimeWorkDays::DoGetHolidaysInRange(const wxDateTime& dtStart,
4051 const wxDateTime& dtEnd,
4052 wxDateTimeArray& holidays) const
4053{
4054 if ( dtStart > dtEnd )
4055 {
4056 wxFAIL_MSG( _T("invalid date range in GetHolidaysInRange") );
4057
4058 return 0u;
4059 }
4060
4061 holidays.Empty();
4062
4063 // instead of checking all days, start with the first Sat after dtStart and
4064 // end with the last Sun before dtEnd
4065 wxDateTime dtSatFirst = dtStart.GetNextWeekDay(wxDateTime::Sat),
4066 dtSatLast = dtEnd.GetPrevWeekDay(wxDateTime::Sat),
4067 dtSunFirst = dtStart.GetNextWeekDay(wxDateTime::Sun),
4068 dtSunLast = dtEnd.GetPrevWeekDay(wxDateTime::Sun),
4069 dt;
4070
4071 for ( dt = dtSatFirst; dt <= dtSatLast; dt += wxDateSpan::Week() )
4072 {
4073 holidays.Add(dt);
4074 }
4075
4076 for ( dt = dtSunFirst; dt <= dtSunLast; dt += wxDateSpan::Week() )
4077 {
4078 holidays.Add(dt);
4079 }
4080
4081 return holidays.GetCount();
4082}
3e0b743f 4083
1e6feb95 4084#endif // wxUSE_DATETIME