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