]> git.saurik.com Git - wxWidgets.git/blame - src/common/datetime.cpp
wxUSE_IMAGE cleanup, fix warning
[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 {
10a2749b 938 if (wxStrftime(buffer, sizeof(buffer)/sizeof(wxChar), _T("%p"), &tm) > 0)
0308fd61
JS
939 *am = wxString(buffer);
940 else
941 *am = wxString();
f0f951fa
VZ
942 }
943 if ( pm )
944 {
945 tm.tm_hour = 13;
10a2749b 946 if (wxStrftime(buffer, sizeof(buffer)/sizeof(wxChar), _T("%p"), &tm) > 0)
0308fd61
JS
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:
c5fe22f8
JS
2847#ifdef __WXWINCE__
2848 LCID lcid = LOCALE_USER_DEFAULT;
2849#else
64f9c100 2850 LCID lcid = GetThreadLocale();
c5fe22f8 2851#endif
64f9c100
VS
2852 wxChar delim[5]; // fields deliminer, 4 chars max
2853 if ( GetLocaleInfo(lcid, LOCALE_SDATE, delim, 5) )
2854 {
2855 wxChar centurybuf[2]; // use %y or %Y, 1 char max
2856 wxChar century = 'y';
2857 if ( GetLocaleInfo(lcid, LOCALE_ICENTURY, centurybuf, 2) )
2858 {
2859 if ( centurybuf[0] == _T('1') )
2860 century = 'Y';
2861 // else 'y' as above
2862 }
2863
2864 wxChar order[2]; // order code, 1 char max
2865 if ( GetLocaleInfo(lcid, LOCALE_IDATE, order, 2) )
2866 {
2867 if ( order[0] == _T('0') ) // M-D-Y
2868 {
2869 *fmt = wxString::Format(_T("%%m%s%%d%s%%%c"),
2870 delim, delim, century);
2871 }
2872 else if ( order[0] == _T('1') ) // D-M-Y
2873 {
2874 *fmt = wxString::Format(_T("%%d%s%%m%s%%%c"),
2875 delim, delim, century);
2876 }
2877 else if ( order[0] == _T('2') ) // Y-M-D
2878 {
2879 *fmt = wxString::Format(_T("%%%c%s%%m%s%%d"),
2880 century, delim, delim);
2881 }
2882 else
2883 {
2884 wxFAIL_MSG(_T("unexpected GetLocaleInfo return value"));
2885 }
2886 }
2887 }
2888 // if we failed, leave fmtDate value unchanged and
2889 // try our luck with the default set above
2890 }
2891}
2892#endif // __WINDOWS__
2893
f0f951fa
VZ
2894const wxChar *wxDateTime::ParseFormat(const wxChar *date,
2895 const wxChar *format,
2896 const wxDateTime& dateDef)
cd0b1709
VZ
2897{
2898 wxCHECK_MSG( date && format, (wxChar *)NULL,
f0f951fa 2899 _T("NULL pointer in wxDateTime::ParseFormat()") );
cd0b1709 2900
f0f951fa
VZ
2901 wxString str;
2902 unsigned long num;
cd0b1709 2903
f0f951fa 2904 // what fields have we found?
68379eaf
WS
2905 bool haveWDay = false,
2906 haveYDay = false,
2907 haveDay = false,
2908 haveMon = false,
2909 haveYear = false,
2910 haveHour = false,
2911 haveMin = false,
2912 haveSec = false;
2913
2914 bool hourIsIn12hFormat = false, // or in 24h one?
2915 isPM = false; // AM by default
f0f951fa
VZ
2916
2917 // and the value of the items we have (init them to get rid of warnings)
2918 wxDateTime_t sec = 0,
2919 min = 0,
2920 hour = 0;
2921 WeekDay wday = Inv_WeekDay;
2922 wxDateTime_t yday = 0,
2923 mday = 0;
2924 wxDateTime::Month mon = Inv_Month;
2925 int year = 0;
2926
2927 const wxChar *input = date;
2928 for ( const wxChar *fmt = format; *fmt; fmt++ )
2929 {
2930 if ( *fmt != _T('%') )
2931 {
2932 if ( wxIsspace(*fmt) )
2933 {
2934 // a white space in the format string matches 0 or more white
2935 // spaces in the input
2936 while ( wxIsspace(*input) )
2937 {
2938 input++;
2939 }
2940 }
2941 else // !space
2942 {
2943 // any other character (not whitespace, not '%') must be
2944 // matched by itself in the input
2945 if ( *input++ != *fmt )
2946 {
2947 // no match
2948 return (wxChar *)NULL;
2949 }
2950 }
2951
2952 // done with this format char
2953 continue;
2954 }
2955
2956 // start of a format specification
f6bcfd97
BP
2957
2958 // parse the optional width
2959 size_t width = 0;
1c193821 2960 while ( wxIsdigit(*++fmt) )
f6bcfd97
BP
2961 {
2962 width *= 10;
2963 width += *fmt - _T('0');
2964 }
2965
4147efe9
VZ
2966 // the default widths for the various fields
2967 if ( !width )
2968 {
2969 switch ( *fmt )
2970 {
2971 case _T('Y'): // year has 4 digits
2972 width = 4;
2973 break;
2974
2975 case _T('j'): // day of year has 3 digits
2976 case _T('l'): // milliseconds have 3 digits
2977 width = 3;
2978 break;
2979
2980 case _T('w'): // week day as number has only one
2981 width = 1;
2982 break;
2983
2984 default:
2985 // default for all other fields
2986 width = 2;
2987 }
2988 }
2989
f6bcfd97
BP
2990 // then the format itself
2991 switch ( *fmt )
f0f951fa
VZ
2992 {
2993 case _T('a'): // a weekday name
2994 case _T('A'):
2995 {
2996 int flag = *fmt == _T('a') ? Name_Abbr : Name_Full;
2997 wday = GetWeekDayFromName(GetAlphaToken(input), flag);
2998 if ( wday == Inv_WeekDay )
2999 {
3000 // no match
3001 return (wxChar *)NULL;
3002 }
3003 }
68379eaf 3004 haveWDay = true;
f0f951fa
VZ
3005 break;
3006
3007 case _T('b'): // a month name
3008 case _T('B'):
3009 {
3010 int flag = *fmt == _T('b') ? Name_Abbr : Name_Full;
3011 mon = GetMonthFromName(GetAlphaToken(input), flag);
3012 if ( mon == Inv_Month )
3013 {
3014 // no match
3015 return (wxChar *)NULL;
3016 }
3017 }
68379eaf 3018 haveMon = true;
f0f951fa
VZ
3019 break;
3020
3021 case _T('c'): // locale default date and time representation
3022 {
3023 wxDateTime dt;
3024
3025 // this is the format which corresponds to ctime() output
3026 // and strptime("%c") should parse it, so try it first
41acf5c0 3027 static const wxChar *fmtCtime = _T("%a %b %d %H:%M:%S %Y");
f0f951fa
VZ
3028
3029 const wxChar *result = dt.ParseFormat(input, fmtCtime);
3030 if ( !result )
3031 {
3032 result = dt.ParseFormat(input, _T("%x %X"));
3033 }
3034
3035 if ( !result )
3036 {
3037 result = dt.ParseFormat(input, _T("%X %x"));
3038 }
3039
3040 if ( !result )
3041 {
3042 // we've tried everything and still no match
3043 return (wxChar *)NULL;
3044 }
3045
be4017f8
VZ
3046 Tm tm = dt.GetTm();
3047
3048 haveDay = haveMon = haveYear =
68379eaf 3049 haveHour = haveMin = haveSec = true;
be4017f8
VZ
3050
3051 hour = tm.hour;
3052 min = tm.min;
3053 sec = tm.sec;
3054
3055 year = tm.year;
3056 mon = tm.mon;
3057 mday = tm.mday;
3058
f0f951fa
VZ
3059 input = result;
3060 }
3061 break;
3062
3063 case _T('d'): // day of a month (01-31)
f6bcfd97
BP
3064 if ( !GetNumericToken(width, input, &num) ||
3065 (num > 31) || (num < 1) )
f0f951fa
VZ
3066 {
3067 // no match
3068 return (wxChar *)NULL;
3069 }
3070
3071 // we can't check whether the day range is correct yet, will
3072 // do it later - assume ok for now
68379eaf 3073 haveDay = true;
f0f951fa
VZ
3074 mday = (wxDateTime_t)num;
3075 break;
5f287370 3076
f0f951fa 3077 case _T('H'): // hour in 24h format (00-23)
f6bcfd97 3078 if ( !GetNumericToken(width, input, &num) || (num > 23) )
f0f951fa
VZ
3079 {
3080 // no match
3081 return (wxChar *)NULL;
3082 }
3083
68379eaf 3084 haveHour = true;
f0f951fa
VZ
3085 hour = (wxDateTime_t)num;
3086 break;
3087
3088 case _T('I'): // hour in 12h format (01-12)
f6bcfd97 3089 if ( !GetNumericToken(width, input, &num) || !num || (num > 12) )
f0f951fa
VZ
3090 {
3091 // no match
3092 return (wxChar *)NULL;
3093 }
3094
68379eaf
WS
3095 haveHour = true;
3096 hourIsIn12hFormat = true;
479cd5de 3097 hour = (wxDateTime_t)(num % 12); // 12 should be 0
f0f951fa
VZ
3098 break;
3099
3100 case _T('j'): // day of the year
f6bcfd97 3101 if ( !GetNumericToken(width, input, &num) || !num || (num > 366) )
f0f951fa
VZ
3102 {
3103 // no match
3104 return (wxChar *)NULL;
3105 }
3106
68379eaf 3107 haveYDay = true;
f0f951fa
VZ
3108 yday = (wxDateTime_t)num;
3109 break;
3110
3111 case _T('m'): // month as a number (01-12)
f6bcfd97 3112 if ( !GetNumericToken(width, input, &num) || !num || (num > 12) )
f0f951fa
VZ
3113 {
3114 // no match
3115 return (wxChar *)NULL;
3116 }
3117
68379eaf 3118 haveMon = true;
be4017f8 3119 mon = (Month)(num - 1);
f0f951fa
VZ
3120 break;
3121
3122 case _T('M'): // minute as a decimal number (00-59)
f6bcfd97 3123 if ( !GetNumericToken(width, input, &num) || (num > 59) )
f0f951fa
VZ
3124 {
3125 // no match
3126 return (wxChar *)NULL;
3127 }
3128
68379eaf 3129 haveMin = true;
f0f951fa
VZ
3130 min = (wxDateTime_t)num;
3131 break;
3132
3133 case _T('p'): // AM or PM string
3134 {
3135 wxString am, pm, token = GetAlphaToken(input);
3136
3137 GetAmPmStrings(&am, &pm);
bb8a8478 3138 if (am.empty() && pm.empty())
0308fd61 3139 return (wxChar *)NULL; // no am/pm strings defined
f0f951fa
VZ
3140 if ( token.CmpNoCase(pm) == 0 )
3141 {
68379eaf 3142 isPM = true;
f0f951fa
VZ
3143 }
3144 else if ( token.CmpNoCase(am) != 0 )
3145 {
3146 // no match
3147 return (wxChar *)NULL;
3148 }
3149 }
3150 break;
3151
3152 case _T('r'): // time as %I:%M:%S %p
3153 {
3154 wxDateTime dt;
3155 input = dt.ParseFormat(input, _T("%I:%M:%S %p"));
3156 if ( !input )
3157 {
3158 // no match
3159 return (wxChar *)NULL;
3160 }
3161
68379eaf 3162 haveHour = haveMin = haveSec = true;
f0f951fa
VZ
3163
3164 Tm tm = dt.GetTm();
3165 hour = tm.hour;
3166 min = tm.min;
3167 sec = tm.sec;
3168 }
3169 break;
3170
3171 case _T('R'): // time as %H:%M
3172 {
3173 wxDateTime dt;
3174 input = dt.ParseFormat(input, _T("%H:%M"));
3175 if ( !input )
3176 {
3177 // no match
3178 return (wxChar *)NULL;
3179 }
3180
68379eaf 3181 haveHour = haveMin = true;
f0f951fa
VZ
3182
3183 Tm tm = dt.GetTm();
3184 hour = tm.hour;
3185 min = tm.min;
3186 }
3187
3188 case _T('S'): // second as a decimal number (00-61)
f6bcfd97 3189 if ( !GetNumericToken(width, input, &num) || (num > 61) )
f0f951fa
VZ
3190 {
3191 // no match
3192 return (wxChar *)NULL;
3193 }
3194
68379eaf 3195 haveSec = true;
f0f951fa
VZ
3196 sec = (wxDateTime_t)num;
3197 break;
3198
3199 case _T('T'): // time as %H:%M:%S
3200 {
3201 wxDateTime dt;
3202 input = dt.ParseFormat(input, _T("%H:%M:%S"));
3203 if ( !input )
3204 {
3205 // no match
3206 return (wxChar *)NULL;
3207 }
3208
68379eaf 3209 haveHour = haveMin = haveSec = true;
f0f951fa
VZ
3210
3211 Tm tm = dt.GetTm();
3212 hour = tm.hour;
3213 min = tm.min;
3214 sec = tm.sec;
3215 }
be4017f8 3216 break;
f0f951fa
VZ
3217
3218 case _T('w'): // weekday as a number (0-6), Sunday = 0
f6bcfd97 3219 if ( !GetNumericToken(width, input, &num) || (wday > 6) )
f0f951fa
VZ
3220 {
3221 // no match
3222 return (wxChar *)NULL;
3223 }
3224
68379eaf 3225 haveWDay = true;
f0f951fa
VZ
3226 wday = (WeekDay)num;
3227 break;
3228
3229 case _T('x'): // locale default date representation
3230#ifdef HAVE_STRPTIME
2423ef22 3231 // try using strptime() -- it may fail even if the input is
f0f951fa 3232 // correct but the date is out of range, so we will fall back
2423ef22 3233 // to our generic code anyhow
f0f951fa
VZ
3234 {
3235 struct tm tm;
2423ef22
VZ
3236
3237 const wxChar *result = CallStrptime(input, "%x", &tm);
f0f951fa
VZ
3238 if ( result )
3239 {
3240 input = result;
3241
68379eaf 3242 haveDay = haveMon = haveYear = true;
f0f951fa
VZ
3243
3244 year = 1900 + tm.tm_year;
3245 mon = (Month)tm.tm_mon;
3246 mday = tm.tm_mday;
3247
3248 break;
3249 }
3250 }
3251#endif // HAVE_STRPTIME
3252
f0f951fa
VZ
3253 {
3254 wxDateTime dt;
3255
41acf5c0 3256 wxString fmtDate, fmtDateAlt;
64f9c100 3257
f0f951fa
VZ
3258 if ( IsWestEuropeanCountry(GetCountry()) ||
3259 GetCountry() == Russia )
3260 {
3261 fmtDate = _T("%d/%m/%y");
41acf5c0 3262 fmtDateAlt = _T("%m/%d/%y");
f0f951fa
VZ
3263 }
3264 else // assume USA
3265 {
3266 fmtDate = _T("%m/%d/%y");
41acf5c0 3267 fmtDateAlt = _T("%d/%m/%y");
f0f951fa
VZ
3268 }
3269
64f9c100
VS
3270#ifdef __WINDOWS__
3271 // The above doesn't work for all locales, try to query
3272 // Windows for the right way of formatting the date:
3273 GetLocaleDateFormat(&fmtDate);
3274#endif
3275
f0f951fa 3276 const wxChar *result = dt.ParseFormat(input, fmtDate);
41acf5c0
VZ
3277
3278 if ( !result )
3279 {
3280 // ok, be nice and try another one
3281 result = dt.ParseFormat(input, fmtDateAlt);
3282 }
3283
f0f951fa
VZ
3284 if ( !result )
3285 {
3286 // bad luck
3287 return (wxChar *)NULL;
3288 }
3289
3290 Tm tm = dt.GetTm();
3291
68379eaf 3292 haveDay = haveMon = haveYear = true;
f0f951fa
VZ
3293
3294 year = tm.year;
3295 mon = tm.mon;
3296 mday = tm.mday;
3297
3298 input = result;
3299 }
3300
3301 break;
3302
3303 case _T('X'): // locale default time representation
3304#ifdef HAVE_STRPTIME
3305 {
3306 // use strptime() to do it for us (FIXME !Unicode friendly)
3307 struct tm tm;
2423ef22 3308 input = CallStrptime(input, "%X", &tm);
f0f951fa
VZ
3309 if ( !input )
3310 {
3311 return (wxChar *)NULL;
3312 }
3313
68379eaf 3314 haveHour = haveMin = haveSec = true;
f0f951fa
VZ
3315
3316 hour = tm.tm_hour;
3317 min = tm.tm_min;
3318 sec = tm.tm_sec;
3319 }
3320#else // !HAVE_STRPTIME
3321 // TODO under Win32 we can query the LOCALE_ITIME system
3322 // setting which says whether the default time format is
3323 // 24 or 12 hour
3324 {
3325 // try to parse what follows as "%H:%M:%S" and, if this
3326 // fails, as "%I:%M:%S %p" - this should catch the most
3327 // common cases
3328 wxDateTime dt;
3329
3330 const wxChar *result = dt.ParseFormat(input, _T("%T"));
3331 if ( !result )
3332 {
3333 result = dt.ParseFormat(input, _T("%r"));
3334 }
3335
3336 if ( !result )
3337 {
3338 // no match
3339 return (wxChar *)NULL;
3340 }
3341
68379eaf 3342 haveHour = haveMin = haveSec = true;
f0f951fa
VZ
3343
3344 Tm tm = dt.GetTm();
3345 hour = tm.hour;
3346 min = tm.min;
3347 sec = tm.sec;
3348
3349 input = result;
3350 }
3351#endif // HAVE_STRPTIME/!HAVE_STRPTIME
3352 break;
3353
3354 case _T('y'): // year without century (00-99)
f6bcfd97 3355 if ( !GetNumericToken(width, input, &num) || (num > 99) )
f0f951fa
VZ
3356 {
3357 // no match
3358 return (wxChar *)NULL;
3359 }
3360
68379eaf 3361 haveYear = true;
f6bcfd97
BP
3362
3363 // TODO should have an option for roll over date instead of
3364 // hard coding it here
3365 year = (num > 30 ? 1900 : 2000) + (wxDateTime_t)num;
f0f951fa
VZ
3366 break;
3367
3368 case _T('Y'): // year with century
f6bcfd97 3369 if ( !GetNumericToken(width, input, &num) )
f0f951fa
VZ
3370 {
3371 // no match
3372 return (wxChar *)NULL;
3373 }
3374
68379eaf 3375 haveYear = true;
be4017f8 3376 year = (wxDateTime_t)num;
f0f951fa
VZ
3377 break;
3378
3379 case _T('Z'): // timezone name
3380 wxFAIL_MSG(_T("TODO"));
3381 break;
3382
3383 case _T('%'): // a percent sign
3384 if ( *input++ != _T('%') )
3385 {
3386 // no match
3387 return (wxChar *)NULL;
3388 }
3389 break;
3390
3391 case 0: // the end of string
3392 wxFAIL_MSG(_T("unexpected format end"));
3393
3394 // fall through
3395
3396 default: // not a known format spec
3397 return (wxChar *)NULL;
3398 }
3399 }
3400
3401 // format matched, try to construct a date from what we have now
3402 Tm tmDef;
3403 if ( dateDef.IsValid() )
3404 {
3405 // take this date as default
3406 tmDef = dateDef.GetTm();
3407 }
c3302e7e 3408 else if ( IsValid() )
f0f951fa
VZ
3409 {
3410 // if this date is valid, don't change it
3411 tmDef = GetTm();
3412 }
3413 else
3414 {
3415 // no default and this date is invalid - fall back to Today()
3416 tmDef = Today().GetTm();
3417 }
3418
3419 Tm tm = tmDef;
3420
3421 // set the date
3422 if ( haveYear )
3423 {
3424 tm.year = year;
3425 }
3426
3427 // TODO we don't check here that the values are consistent, if both year
3428 // day and month/day were found, we just ignore the year day and we
3429 // also always ignore the week day
3430 if ( haveMon && haveDay )
3431 {
be4017f8
VZ
3432 if ( mday > GetNumOfDaysInMonth(tm.year, mon) )
3433 {
3434 wxLogDebug(_T("bad month day in wxDateTime::ParseFormat"));
3435
3436 return (wxChar *)NULL;
3437 }
3438
f0f951fa
VZ
3439 tm.mon = mon;
3440 tm.mday = mday;
3441 }
3442 else if ( haveYDay )
3443 {
be4017f8
VZ
3444 if ( yday > GetNumberOfDays(tm.year) )
3445 {
3446 wxLogDebug(_T("bad year day in wxDateTime::ParseFormat"));
3447
3448 return (wxChar *)NULL;
3449 }
3450
f0f951fa
VZ
3451 Tm tm2 = wxDateTime(1, Jan, tm.year).SetToYearDay(yday).GetTm();
3452
3453 tm.mon = tm2.mon;
3454 tm.mday = tm2.mday;
3455 }
3456
3457 // deal with AM/PM
3458 if ( haveHour && hourIsIn12hFormat && isPM )
3459 {
3460 // translate to 24hour format
3461 hour += 12;
3462 }
3463 //else: either already in 24h format or no translation needed
3464
3465 // set the time
3466 if ( haveHour )
3467 {
5f287370 3468 tm.hour = hour;
f0f951fa
VZ
3469 }
3470
3471 if ( haveMin )
3472 {
3473 tm.min = min;
3474 }
3475
3476 if ( haveSec )
3477 {
3478 tm.sec = sec;
3479 }
3480
3481 Set(tm);
3482
88a036ce
VZ
3483 // finally check that the week day is consistent -- if we had it
3484 if ( haveWDay && GetWeekDay() != wday )
3485 {
3486 wxLogDebug(_T("inconsistsnet week day in wxDateTime::ParseFormat()"));
3487
3488 return NULL;
3489 }
3490
f0f951fa 3491 return input;
cd0b1709
VZ
3492}
3493
3494const wxChar *wxDateTime::ParseDateTime(const wxChar *date)
3495{
3496 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
3497
de07d200
VZ
3498 // Set to current day and hour, so strings like '14:00' becomes today at
3499 // 14, not some other random date
3500 wxDateTime dtDate = wxDateTime::Today();
3501 wxDateTime dtTime = wxDateTime::Today();
cd0b1709 3502
de07d200
VZ
3503 const wxChar* pchTime;
3504
3505 // Try to parse the beginning of the string as a date
3506 const wxChar* pchDate = dtDate.ParseDate(date);
3507
3508 // We got a date in the beginning, see if there is a time specified after the date
3509 if ( pchDate )
3510 {
3511 // Skip spaces, as the ParseTime() function fails on spaces
3512 while ( wxIsspace(*pchDate) )
3513 pchDate++;
3514
3515 pchTime = dtTime.ParseTime(pchDate);
3516 }
3517 else // no date in the beginning
3518 {
3519 // check and see if we have a time followed by a date
3520 pchTime = dtTime.ParseTime(date);
3521 if ( pchTime )
3522 {
3523 while ( wxIsspace(*pchTime) )
3524 pchTime++;
3525
3526 pchDate = dtDate.ParseDate(pchTime);
3527 }
3528 }
3529
3530 // If we have a date specified, set our own data to the same date
3531 if ( !pchDate || !pchTime )
3532 return NULL;
3533
3534 Set(dtDate.GetDay(), dtDate.GetMonth(), dtDate.GetYear(),
3535 dtTime.GetHour(), dtTime.GetMinute(), dtTime.GetSecond(),
3536 dtTime.GetMillisecond());
3537
3538 // Return endpoint of scan
3539 return pchDate > pchTime ? pchDate : pchTime;
cd0b1709
VZ
3540}
3541
3542const wxChar *wxDateTime::ParseDate(const wxChar *date)
3543{
3544 // this is a simplified version of ParseDateTime() which understands only
3545 // "today" (for wxDate compatibility) and digits only otherwise (and not
3546 // all esoteric constructions ParseDateTime() knows about)
3547
3548 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
3549
3550 const wxChar *p = date;
3551 while ( wxIsspace(*p) )
3552 p++;
3553
fd9f9f4c
VZ
3554 // some special cases
3555 static struct
cd0b1709 3556 {
fd9f9f4c
VZ
3557 const wxChar *str;
3558 int dayDiffFromToday;
3559 } literalDates[] =
3560 {
3561 { wxTRANSLATE("today"), 0 },
3562 { wxTRANSLATE("yesterday"), -1 },
3563 { wxTRANSLATE("tomorrow"), 1 },
3564 };
3565
3566 for ( size_t n = 0; n < WXSIZEOF(literalDates); n++ )
3567 {
3568 wxString date = wxGetTranslation(literalDates[n].str);
3569 size_t len = date.length();
907173e5 3570 if ( wxStrlen(p) >= len )
fd9f9f4c 3571 {
907173e5
WS
3572 wxString str(p, len);
3573 if ( str.CmpNoCase(date) == 0 )
fd9f9f4c 3574 {
907173e5
WS
3575 // nothing can follow this, so stop here
3576 p += len;
3577
3578 int dayDiffFromToday = literalDates[n].dayDiffFromToday;
3579 *this = Today();
3580 if ( dayDiffFromToday )
3581 {
3582 *this += wxDateSpan::Days(dayDiffFromToday);
3583 }
cd0b1709 3584
907173e5
WS
3585 return p;
3586 }
fd9f9f4c 3587 }
cd0b1709
VZ
3588 }
3589
3ca6a5f0
BP
3590 // We try to guess what we have here: for each new (numeric) token, we
3591 // determine if it can be a month, day or a year. Of course, there is an
3592 // ambiguity as some numbers may be days as well as months, so we also
3593 // have the ability to back track.
3594
cd0b1709 3595 // what do we have?
68379eaf
WS
3596 bool haveDay = false, // the months day?
3597 haveWDay = false, // the day of week?
3598 haveMon = false, // the month?
3599 haveYear = false; // the year?
cd0b1709
VZ
3600
3601 // and the value of the items we have (init them to get rid of warnings)
3602 WeekDay wday = Inv_WeekDay;
3603 wxDateTime_t day = 0;
3604 wxDateTime::Month mon = Inv_Month;
3605 int year = 0;
3606
3607 // tokenize the string
f6bcfd97 3608 size_t nPosCur = 0;
62ae2780 3609 static const wxChar *dateDelimiters = _T(".,/-\t\r\n ");
f6bcfd97 3610 wxStringTokenizer tok(p, dateDelimiters);
cd0b1709
VZ
3611 while ( tok.HasMoreTokens() )
3612 {
3613 wxString token = tok.GetNextToken();
3ca6a5f0
BP
3614 if ( !token )
3615 continue;
cd0b1709
VZ
3616
3617 // is it a number?
3618 unsigned long val;
fde5a86b 3619 if ( token.ToULong(&val) )
cd0b1709
VZ
3620 {
3621 // guess what this number is
3622
68379eaf
WS
3623 bool isDay = false,
3624 isMonth = false,
3625 isYear = false;
3ca6a5f0
BP
3626
3627 if ( !haveMon && val > 0 && val <= 12 )
cd0b1709 3628 {
3ca6a5f0 3629 // assume it is month
68379eaf 3630 isMonth = true;
3ca6a5f0
BP
3631 }
3632 else // not the month
3633 {
560e2916 3634 if ( haveDay )
cd0b1709 3635 {
560e2916 3636 // this can only be the year
68379eaf 3637 isYear = true;
cd0b1709 3638 }
560e2916 3639 else // may be either day or year
cd0b1709 3640 {
6a27c749 3641 wxDateTime_t max_days = (wxDateTime_t)(
42841dfc 3642 haveMon
560e2916 3643 ? GetNumOfDaysInMonth(haveYear ? year : Inv_Year, mon)
42841dfc
WS
3644 : 31
3645 );
560e2916
VZ
3646
3647 // can it be day?
6a27c749 3648 if ( (val == 0) || (val > (unsigned long)max_days) )
560e2916
VZ
3649 {
3650 // no
68379eaf 3651 isYear = true;
560e2916
VZ
3652 }
3653 else // yes, suppose it's the day
3654 {
68379eaf 3655 isDay = true;
560e2916 3656 }
cd0b1709
VZ
3657 }
3658 }
3659
cd0b1709
VZ
3660 if ( isYear )
3661 {
3662 if ( haveYear )
cd0b1709 3663 break;
cd0b1709 3664
68379eaf 3665 haveYear = true;
cd0b1709 3666
479cd5de 3667 year = (wxDateTime_t)val;
cd0b1709 3668 }
3ca6a5f0 3669 else if ( isDay )
cd0b1709 3670 {
3ca6a5f0 3671 if ( haveDay )
cd0b1709 3672 break;
cd0b1709 3673
68379eaf 3674 haveDay = true;
cd0b1709 3675
3ca6a5f0 3676 day = (wxDateTime_t)val;
cd0b1709 3677 }
3ca6a5f0 3678 else if ( isMonth )
cd0b1709 3679 {
68379eaf 3680 haveMon = true;
cd0b1709 3681
3ca6a5f0 3682 mon = (Month)(val - 1);
cd0b1709
VZ
3683 }
3684 }
3685 else // not a number
3686 {
f6bcfd97
BP
3687 // be careful not to overwrite the current mon value
3688 Month mon2 = GetMonthFromName(token, Name_Full | Name_Abbr);
3689 if ( mon2 != Inv_Month )
cd0b1709
VZ
3690 {
3691 // it's a month
3692 if ( haveMon )
3693 {
6411a32c
VZ
3694 // but we already have a month - maybe we guessed wrong?
3695 if ( !haveDay )
3696 {
f5cd9787 3697 // no need to check in month range as always < 12, but
6411a32c 3698 // the days are counted from 1 unlike the months
42841dfc 3699 day = (wxDateTime_t)(mon + 1);
68379eaf 3700 haveDay = true;
6411a32c
VZ
3701 }
3702 else
3703 {
3704 // could possible be the year (doesn't the year come
3705 // before the month in the japanese format?) (FIXME)
3706 break;
f5cd9787 3707 }
cd0b1709
VZ
3708 }
3709
f6bcfd97
BP
3710 mon = mon2;
3711
68379eaf 3712 haveMon = true;
cd0b1709 3713 }
6411a32c 3714 else // not a valid month name
cd0b1709 3715 {
f0f951fa 3716 wday = GetWeekDayFromName(token, Name_Full | Name_Abbr);
cd0b1709
VZ
3717 if ( wday != Inv_WeekDay )
3718 {
3719 // a week day
3720 if ( haveWDay )
3721 {
3722 break;
3723 }
3724
68379eaf 3725 haveWDay = true;
cd0b1709 3726 }
6411a32c 3727 else // not a valid weekday name
cd0b1709
VZ
3728 {
3729 // try the ordinals
3730 static const wxChar *ordinals[] =
3731 {
3732 wxTRANSLATE("first"),
3733 wxTRANSLATE("second"),
3734 wxTRANSLATE("third"),
3735 wxTRANSLATE("fourth"),
3736 wxTRANSLATE("fifth"),
3737 wxTRANSLATE("sixth"),
3738 wxTRANSLATE("seventh"),
3739 wxTRANSLATE("eighth"),
3740 wxTRANSLATE("ninth"),
3741 wxTRANSLATE("tenth"),
3742 wxTRANSLATE("eleventh"),
3743 wxTRANSLATE("twelfth"),
3744 wxTRANSLATE("thirteenth"),
3745 wxTRANSLATE("fourteenth"),
3746 wxTRANSLATE("fifteenth"),
3747 wxTRANSLATE("sixteenth"),
3748 wxTRANSLATE("seventeenth"),
3749 wxTRANSLATE("eighteenth"),
3750 wxTRANSLATE("nineteenth"),
3751 wxTRANSLATE("twentieth"),
3752 // that's enough - otherwise we'd have problems with
6411a32c 3753 // composite (or not) ordinals
cd0b1709
VZ
3754 };
3755
3756 size_t n;
3757 for ( n = 0; n < WXSIZEOF(ordinals); n++ )
3758 {
3759 if ( token.CmpNoCase(ordinals[n]) == 0 )
3760 {
3761 break;
3762 }
3763 }
3764
3765 if ( n == WXSIZEOF(ordinals) )
3766 {
3767 // stop here - something unknown
3768 break;
3769 }
3770
3771 // it's a day
3772 if ( haveDay )
3773 {
3774 // don't try anything here (as in case of numeric day
3775 // above) - the symbolic day spec should always
3776 // precede the month/year
3777 break;
3778 }
3779
68379eaf 3780 haveDay = true;
cd0b1709 3781
33ac7e6f 3782 day = (wxDateTime_t)(n + 1);
cd0b1709
VZ
3783 }
3784 }
3785 }
f6bcfd97
BP
3786
3787 nPosCur = tok.GetPosition();
cd0b1709
VZ
3788 }
3789
3790 // either no more tokens or the scan was stopped by something we couldn't
3791 // parse - in any case, see if we can construct a date from what we have
3792 if ( !haveDay && !haveWDay )
3793 {
f6bcfd97 3794 wxLogDebug(_T("ParseDate: no day, no weekday hence no date."));
cd0b1709
VZ
3795
3796 return (wxChar *)NULL;
3797 }
3798
3799 if ( haveWDay && (haveMon || haveYear || haveDay) &&
f6bcfd97 3800 !(haveDay && haveMon && haveYear) )
cd0b1709
VZ
3801 {
3802 // without adjectives (which we don't support here) the week day only
3803 // makes sense completely separately or with the full date
3804 // specification (what would "Wed 1999" mean?)
3805 return (wxChar *)NULL;
3806 }
3807
f6bcfd97
BP
3808 if ( !haveWDay && haveYear && !(haveDay && haveMon) )
3809 {
3810 // may be we have month and day instead of day and year?
3811 if ( haveDay && !haveMon )
3812 {
3813 if ( day <= 12 )
3814 {
3815 // exchange day and month
3816 mon = (wxDateTime::Month)(day - 1);
3817
3818 // we're in the current year then
196be0f1 3819 if ( (year > 0) && (year <= (int)GetNumOfDaysInMonth(Inv_Year, mon)) )
f6bcfd97 3820 {
42841dfc 3821 day = (wxDateTime_t)year;
f6bcfd97 3822
68379eaf
WS
3823 haveMon = true;
3824 haveYear = false;
f6bcfd97 3825 }
68379eaf 3826 //else: no, can't exchange, leave haveMon == false
f6bcfd97
BP
3827 }
3828 }
3829
3830 if ( !haveMon )
3831 {
3832 // if we give the year, month and day must be given too
3833 wxLogDebug(_T("ParseDate: day and month should be specified if year is."));
3834
3835 return (wxChar *)NULL;
3836 }
3837 }
3838
cd0b1709
VZ
3839 if ( !haveMon )
3840 {
3841 mon = GetCurrentMonth();
3842 }
3843
3844 if ( !haveYear )
3845 {
3846 year = GetCurrentYear();
3847 }
3848
3849 if ( haveDay )
3850 {
3851 Set(day, mon, year);
3852
3853 if ( haveWDay )
3854 {
3855 // check that it is really the same
3856 if ( GetWeekDay() != wday )
3857 {
3858 // inconsistency detected
f6bcfd97
BP
3859 wxLogDebug(_T("ParseDate: inconsistent day/weekday."));
3860
cd0b1709
VZ
3861 return (wxChar *)NULL;
3862 }
3863 }
3864 }
3865 else // haveWDay
3866 {
3867 *this = Today();
3868
3869 SetToWeekDayInSameWeek(wday);
3870 }
3871
f6bcfd97
BP
3872 // return the pointer to the first unparsed char
3873 p += nPosCur;
3874 if ( nPosCur && wxStrchr(dateDelimiters, *(p - 1)) )
3875 {
3876 // if we couldn't parse the token after the delimiter, put back the
3877 // delimiter as well
3878 p--;
3879 }
3880
3881 return p;
cd0b1709
VZ
3882}
3883
3884const wxChar *wxDateTime::ParseTime(const wxChar *time)
3885{
3886 wxCHECK_MSG( time, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
3887
f0f951fa
VZ
3888 // first try some extra things
3889 static const struct
3890 {
3891 const wxChar *name;
3892 wxDateTime_t hour;
3893 } stdTimes[] =
3894 {
3895 { wxTRANSLATE("noon"), 12 },
3896 { wxTRANSLATE("midnight"), 00 },
3897 // anything else?
3898 };
cd0b1709 3899
f0f951fa
VZ
3900 for ( size_t n = 0; n < WXSIZEOF(stdTimes); n++ )
3901 {
3902 wxString timeString = wxGetTranslation(stdTimes[n].name);
3903 size_t len = timeString.length();
3904 if ( timeString.CmpNoCase(wxString(time, len)) == 0 )
3905 {
4ded51f2
CE
3906 // casts required by DigitalMars
3907 Set(stdTimes[n].hour, wxDateTime_t(0), wxDateTime_t(0));
f0f951fa
VZ
3908
3909 return time + len;
3910 }
3911 }
3912
c7f9a482
VZ
3913 // try all time formats we may think about in the order from longest to
3914 // shortest
3915
3916 // 12hour with AM/PM?
3917 const wxChar *result = ParseFormat(time, _T("%I:%M:%S %p"));
3918
f0f951fa
VZ
3919 if ( !result )
3920 {
3921 // normally, it's the same, but why not try it?
3922 result = ParseFormat(time, _T("%H:%M:%S"));
3923 }
3924
3925 if ( !result )
3926 {
c7f9a482
VZ
3927 // 12hour with AM/PM but without seconds?
3928 result = ParseFormat(time, _T("%I:%M %p"));
f0f951fa
VZ
3929 }
3930
3931 if ( !result )
3932 {
3933 // without seconds?
3934 result = ParseFormat(time, _T("%H:%M"));
3935 }
3936
3937 if ( !result )
3938 {
c7f9a482
VZ
3939 // just the hour and AM/PM?
3940 result = ParseFormat(time, _T("%I %p"));
f0f951fa
VZ
3941 }
3942
3943 if ( !result )
3944 {
3945 // just the hour?
3946 result = ParseFormat(time, _T("%H"));
3947 }
3948
3949 if ( !result )
3950 {
c7f9a482
VZ
3951 // parse the standard format: normally it is one of the formats above
3952 // but it may be set to something completely different by the user
3953 result = ParseFormat(time, _T("%X"));
f0f951fa
VZ
3954 }
3955
3956 // TODO: parse timezones
3957
3958 return result;
cd0b1709
VZ
3959}
3960
4f6aed9c
VZ
3961// ----------------------------------------------------------------------------
3962// Workdays and holidays support
3963// ----------------------------------------------------------------------------
3964
3965bool wxDateTime::IsWorkDay(Country WXUNUSED(country)) const
3966{
3967 return !wxDateTimeHolidayAuthority::IsHoliday(*this);
3968}
3969
26364344
WS
3970// ============================================================================
3971// wxDateSpan
3972// ============================================================================
3973
3974wxDateSpan WXDLLIMPEXP_BASE operator*(int n, const wxDateSpan& ds)
3975{
3976 wxDateSpan ds1(ds);
3977 return ds1.Multiply(n);
3978}
3979
fcc3d7cb
VZ
3980// ============================================================================
3981// wxTimeSpan
3982// ============================================================================
3983
26364344
WS
3984wxTimeSpan WXDLLIMPEXP_BASE operator*(int n, const wxTimeSpan& ts)
3985{
3986 return wxTimeSpan(ts).Multiply(n);
3987}
3988
033400eb
VZ
3989// this enum is only used in wxTimeSpan::Format() below but we can't declare
3990// it locally to the method as it provokes an internal compiler error in egcs
3991// 2.91.60 when building with -O2
3992enum TimeSpanPart
3993{
3994 Part_Week,
3995 Part_Day,
3996 Part_Hour,
3997 Part_Min,
3998 Part_Sec,
3999 Part_MSec
4000};
4001
e6ec579c
VZ
4002// not all strftime(3) format specifiers make sense here because, for example,
4003// a time span doesn't have a year nor a timezone
4004//
4005// Here are the ones which are supported (all of them are supported by strftime
4006// as well):
4007// %H hour in 24 hour format
4008// %M minute (00 - 59)
4009// %S second (00 - 59)
4010// %% percent sign
4011//
4012// Also, for MFC CTimeSpan compatibility, we support
4013// %D number of days
4014//
4015// And, to be better than MFC :-), we also have
4016// %E number of wEeks
4017// %l milliseconds (000 - 999)
fcc3d7cb
VZ
4018wxString wxTimeSpan::Format(const wxChar *format) const
4019{
525d8583 4020 wxCHECK_MSG( format, wxEmptyString, _T("NULL format in wxTimeSpan::Format") );
fcc3d7cb
VZ
4021
4022 wxString str;
5b735202 4023 str.Alloc(wxStrlen(format));
e6ec579c 4024
df05cdc5
VZ
4025 // Suppose we have wxTimeSpan ts(1 /* hour */, 2 /* min */, 3 /* sec */)
4026 //
4027 // Then, of course, ts.Format("%H:%M:%S") must return "01:02:03", but the
4028 // question is what should ts.Format("%S") do? The code here returns "3273"
4029 // in this case (i.e. the total number of seconds, not just seconds % 60)
4030 // because, for me, this call means "give me entire time interval in
4031 // seconds" and not "give me the seconds part of the time interval"
4032 //
4033 // If we agree that it should behave like this, it is clear that the
4034 // interpretation of each format specifier depends on the presence of the
4035 // other format specs in the string: if there was "%H" before "%M", we
4036 // should use GetMinutes() % 60, otherwise just GetMinutes() &c
4037
4038 // we remember the most important unit found so far
033400eb 4039 TimeSpanPart partBiggest = Part_MSec;
df05cdc5 4040
f6bcfd97 4041 for ( const wxChar *pch = format; *pch; pch++ )
e6ec579c
VZ
4042 {
4043 wxChar ch = *pch;
4044
f6bcfd97 4045 if ( ch == _T('%') )
e6ec579c 4046 {
df05cdc5
VZ
4047 // the start of the format specification of the printf() below
4048 wxString fmtPrefix = _T('%');
4049
4050 // the number
4051 long n;
e6ec579c 4052
f6bcfd97 4053 ch = *++pch; // get the format spec char
e6ec579c
VZ
4054 switch ( ch )
4055 {
4056 default:
4057 wxFAIL_MSG( _T("invalid format character") );
4058 // fall through
4059
f6bcfd97 4060 case _T('%'):
df05cdc5
VZ
4061 str += ch;
4062
4063 // skip the part below switch
4064 continue;
e6ec579c 4065
f6bcfd97 4066 case _T('D'):
df05cdc5
VZ
4067 n = GetDays();
4068 if ( partBiggest < Part_Day )
4069 {
4070 n %= DAYS_PER_WEEK;
4071 }
4072 else
4073 {
4074 partBiggest = Part_Day;
4075 }
e6ec579c
VZ
4076 break;
4077
f6bcfd97 4078 case _T('E'):
df05cdc5
VZ
4079 partBiggest = Part_Week;
4080 n = GetWeeks();
e6ec579c
VZ
4081 break;
4082
f6bcfd97 4083 case _T('H'):
df05cdc5
VZ
4084 n = GetHours();
4085 if ( partBiggest < Part_Hour )
4086 {
4087 n %= HOURS_PER_DAY;
4088 }
4089 else
4090 {
4091 partBiggest = Part_Hour;
4092 }
4093
4094 fmtPrefix += _T("02");
e6ec579c
VZ
4095 break;
4096
f6bcfd97 4097 case _T('l'):
df05cdc5
VZ
4098 n = GetMilliseconds().ToLong();
4099 if ( partBiggest < Part_MSec )
4100 {
4101 n %= 1000;
4102 }
4103 //else: no need to reset partBiggest to Part_MSec, it is
4104 // the least significant one anyhow
4105
4106 fmtPrefix += _T("03");
e6ec579c
VZ
4107 break;
4108
f6bcfd97 4109 case _T('M'):
df05cdc5
VZ
4110 n = GetMinutes();
4111 if ( partBiggest < Part_Min )
4112 {
4113 n %= MIN_PER_HOUR;
4114 }
4115 else
4116 {
4117 partBiggest = Part_Min;
4118 }
4119
4120 fmtPrefix += _T("02");
e6ec579c
VZ
4121 break;
4122
f6bcfd97 4123 case _T('S'):
df05cdc5
VZ
4124 n = GetSeconds().ToLong();
4125 if ( partBiggest < Part_Sec )
4126 {
4127 n %= SEC_PER_MIN;
4128 }
4129 else
4130 {
4131 partBiggest = Part_Sec;
4132 }
4133
4134 fmtPrefix += _T("02");
e6ec579c
VZ
4135 break;
4136 }
4137
df05cdc5
VZ
4138 str += wxString::Format(fmtPrefix + _T("ld"), n);
4139 }
4140 else
4141 {
4142 // normal character, just copy
4143 str += ch;
e6ec579c 4144 }
e6ec579c 4145 }
fcc3d7cb
VZ
4146
4147 return str;
4148}
4f6aed9c
VZ
4149
4150// ============================================================================
4151// wxDateTimeHolidayAuthority and related classes
4152// ============================================================================
4153
4154#include "wx/arrimpl.cpp"
4155
f6bcfd97 4156WX_DEFINE_OBJARRAY(wxDateTimeArray);
4f6aed9c
VZ
4157
4158static int wxCMPFUNC_CONV
4159wxDateTimeCompareFunc(wxDateTime **first, wxDateTime **second)
4160{
4161 wxDateTime dt1 = **first,
4162 dt2 = **second;
4163
4164 return dt1 == dt2 ? 0 : dt1 < dt2 ? -1 : +1;
4165}
4166
4167// ----------------------------------------------------------------------------
4168// wxDateTimeHolidayAuthority
4169// ----------------------------------------------------------------------------
4170
4171wxHolidayAuthoritiesArray wxDateTimeHolidayAuthority::ms_authorities;
4172
4173/* static */
4174bool wxDateTimeHolidayAuthority::IsHoliday(const wxDateTime& dt)
4175{
df5168c4 4176 size_t count = ms_authorities.size();
4f6aed9c
VZ
4177 for ( size_t n = 0; n < count; n++ )
4178 {
4179 if ( ms_authorities[n]->DoIsHoliday(dt) )
4180 {
68379eaf 4181 return true;
4f6aed9c
VZ
4182 }
4183 }
4184
68379eaf 4185 return false;
4f6aed9c
VZ
4186}
4187
4188/* static */
4189size_t
4190wxDateTimeHolidayAuthority::GetHolidaysInRange(const wxDateTime& dtStart,
4191 const wxDateTime& dtEnd,
4192 wxDateTimeArray& holidays)
4193{
4194 wxDateTimeArray hol;
4195
df5168c4 4196 holidays.Clear();
4f6aed9c 4197
df5168c4 4198 size_t count = ms_authorities.size();
6dc6fda6 4199 for ( size_t nAuth = 0; nAuth < count; nAuth++ )
4f6aed9c 4200 {
6dc6fda6 4201 ms_authorities[nAuth]->DoGetHolidaysInRange(dtStart, dtEnd, hol);
4f6aed9c
VZ
4202
4203 WX_APPEND_ARRAY(holidays, hol);
4204 }
4205
4206 holidays.Sort(wxDateTimeCompareFunc);
4207
df5168c4 4208 return holidays.size();
4f6aed9c
VZ
4209}
4210
4211/* static */
4212void wxDateTimeHolidayAuthority::ClearAllAuthorities()
4213{
4214 WX_CLEAR_ARRAY(ms_authorities);
4215}
4216
4217/* static */
4218void wxDateTimeHolidayAuthority::AddAuthority(wxDateTimeHolidayAuthority *auth)
4219{
df5168c4 4220 ms_authorities.push_back(auth);
4f6aed9c
VZ
4221}
4222
5e233068
WS
4223wxDateTimeHolidayAuthority::~wxDateTimeHolidayAuthority()
4224{
4225 // required here for Darwin
4226}
4227
4f6aed9c
VZ
4228// ----------------------------------------------------------------------------
4229// wxDateTimeWorkDays
4230// ----------------------------------------------------------------------------
4231
4232bool wxDateTimeWorkDays::DoIsHoliday(const wxDateTime& dt) const
4233{
4234 wxDateTime::WeekDay wd = dt.GetWeekDay();
4235
4236 return (wd == wxDateTime::Sun) || (wd == wxDateTime::Sat);
4237}
4238
4239size_t wxDateTimeWorkDays::DoGetHolidaysInRange(const wxDateTime& dtStart,
4240 const wxDateTime& dtEnd,
4241 wxDateTimeArray& holidays) const
4242{
4243 if ( dtStart > dtEnd )
4244 {
4245 wxFAIL_MSG( _T("invalid date range in GetHolidaysInRange") );
4246
4247 return 0u;
4248 }
4249
4250 holidays.Empty();
4251
4252 // instead of checking all days, start with the first Sat after dtStart and
4253 // end with the last Sun before dtEnd
4254 wxDateTime dtSatFirst = dtStart.GetNextWeekDay(wxDateTime::Sat),
4255 dtSatLast = dtEnd.GetPrevWeekDay(wxDateTime::Sat),
4256 dtSunFirst = dtStart.GetNextWeekDay(wxDateTime::Sun),
4257 dtSunLast = dtEnd.GetPrevWeekDay(wxDateTime::Sun),
4258 dt;
4259
4260 for ( dt = dtSatFirst; dt <= dtSatLast; dt += wxDateSpan::Week() )
4261 {
4262 holidays.Add(dt);
4263 }
4264
4265 for ( dt = dtSunFirst; dt <= dtSunLast; dt += wxDateSpan::Week() )
4266 {
4267 holidays.Add(dt);
4268 }
4269
4270 return holidays.GetCount();
4271}
3e0b743f 4272
26364344
WS
4273// ============================================================================
4274// other helper functions
4275// ============================================================================
4276
4277// ----------------------------------------------------------------------------
4278// iteration helpers: can be used to write a for loop over enum variable like
4279// this:
4280// for ( m = wxDateTime::Jan; m < wxDateTime::Inv_Month; wxNextMonth(m) )
4281// ----------------------------------------------------------------------------
4282
4283WXDLLIMPEXP_BASE void wxNextMonth(wxDateTime::Month& m)
4284{
4285 wxASSERT_MSG( m < wxDateTime::Inv_Month, _T("invalid month") );
4286
4287 // no wrapping or the for loop above would never end!
4288 m = (wxDateTime::Month)(m + 1);
4289}
4290
4291WXDLLIMPEXP_BASE void wxPrevMonth(wxDateTime::Month& m)
4292{
4293 wxASSERT_MSG( m < wxDateTime::Inv_Month, _T("invalid month") );
4294
4295 m = m == wxDateTime::Jan ? wxDateTime::Inv_Month
4296 : (wxDateTime::Month)(m - 1);
4297}
4298
4299WXDLLIMPEXP_BASE void wxNextWDay(wxDateTime::WeekDay& wd)
4300{
4301 wxASSERT_MSG( wd < wxDateTime::Inv_WeekDay, _T("invalid week day") );
4302
4303 // no wrapping or the for loop above would never end!
4304 wd = (wxDateTime::WeekDay)(wd + 1);
4305}
4306
4307WXDLLIMPEXP_BASE void wxPrevWDay(wxDateTime::WeekDay& wd)
4308{
4309 wxASSERT_MSG( wd < wxDateTime::Inv_WeekDay, _T("invalid week day") );
4310
4311 wd = wd == wxDateTime::Sun ? wxDateTime::Inv_WeekDay
4312 : (wxDateTime::WeekDay)(wd - 1);
4313}
4314
1e6feb95 4315#endif // wxUSE_DATETIME