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