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