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