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