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