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