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