]> git.saurik.com Git - wxWidgets.git/blob - src/common/datetime.cpp
Committing in .
[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 #ifdef __VMS__ // nWeek is unsigned so avoid the warning
1641 int nweek2 = (int) nWeek;
1642 if ( nweek2 < 0 )
1643 #else
1644 if ( nWeek < 0 )
1645 #endif
1646 {
1647 // this may happen for January when Jan, 1 is the last week of the
1648 // previous year
1649 nWeek += IsLeapYear(tm.year - 1) ? 53 : 52;
1650 }
1651
1652 return nWeek;
1653 }
1654
1655 wxDateTime& wxDateTime::SetToYearDay(wxDateTime::wxDateTime_t yday)
1656 {
1657 int year = GetYear();
1658 wxCHECK_MSG( (0 < yday) && (yday <= GetNumberOfDays(year)),
1659 wxInvalidDateTime, _T("invalid year day") );
1660
1661 bool isLeap = IsLeapYear(year);
1662 for ( Month mon = Jan; mon < Inv_Month; wxNextMonth(mon) )
1663 {
1664 // for Dec, we can't compare with gs_cumulatedDays[mon + 1], but we
1665 // don't need it neither - because of the CHECK above we know that
1666 // yday lies in December then
1667 if ( (mon == Dec) || (yday < gs_cumulatedDays[isLeap][mon + 1]) )
1668 {
1669 Set(yday - gs_cumulatedDays[isLeap][mon], mon, year);
1670
1671 break;
1672 }
1673 }
1674
1675 return *this;
1676 }
1677
1678 // ----------------------------------------------------------------------------
1679 // Julian day number conversion and related stuff
1680 // ----------------------------------------------------------------------------
1681
1682 double wxDateTime::GetJulianDayNumber() const
1683 {
1684 // JDN are always expressed for the GMT dates
1685 Tm tm(ToTimezone(GMT0).GetTm(GMT0));
1686
1687 double result = GetTruncatedJDN(tm.mday, tm.mon, tm.year);
1688
1689 // add the part GetTruncatedJDN() neglected
1690 result += 0.5;
1691
1692 // and now add the time: 86400 sec = 1 JDN
1693 return result + ((double)(60*(60*tm.hour + tm.min) + tm.sec)) / 86400;
1694 }
1695
1696 double wxDateTime::GetRataDie() const
1697 {
1698 // March 1 of the year 0 is Rata Die day -306 and JDN 1721119.5
1699 return GetJulianDayNumber() - 1721119.5 - 306;
1700 }
1701
1702 // ----------------------------------------------------------------------------
1703 // timezone and DST stuff
1704 // ----------------------------------------------------------------------------
1705
1706 int wxDateTime::IsDST(wxDateTime::Country country) const
1707 {
1708 wxCHECK_MSG( country == Country_Default, -1,
1709 _T("country support not implemented") );
1710
1711 // use the C RTL for the dates in the standard range
1712 time_t timet = GetTicks();
1713 if ( timet != (time_t)-1 )
1714 {
1715 tm *tm = localtime(&timet);
1716
1717 wxCHECK_MSG( tm, -1, _T("localtime() failed") );
1718
1719 return tm->tm_isdst;
1720 }
1721 else
1722 {
1723 int year = GetYear();
1724
1725 if ( !IsDSTApplicable(year, country) )
1726 {
1727 // no DST time in this year in this country
1728 return -1;
1729 }
1730
1731 return IsBetween(GetBeginDST(year, country), GetEndDST(year, country));
1732 }
1733 }
1734
1735 wxDateTime& wxDateTime::MakeTimezone(const TimeZone& tz, bool noDST)
1736 {
1737 int secDiff = GetTimeZone() + tz.GetOffset();
1738
1739 // we need to know whether DST is or not in effect for this date unless
1740 // the test disabled by the caller
1741 if ( !noDST && (IsDST() == 1) )
1742 {
1743 // FIXME we assume that the DST is always shifted by 1 hour
1744 secDiff -= 3600;
1745 }
1746
1747 return Substract(wxTimeSpan::Seconds(secDiff));
1748 }
1749
1750 // ----------------------------------------------------------------------------
1751 // wxDateTime to/from text representations
1752 // ----------------------------------------------------------------------------
1753
1754 wxString wxDateTime::Format(const wxChar *format, const TimeZone& tz) const
1755 {
1756 wxCHECK_MSG( format, _T(""), _T("NULL format in wxDateTime::Format") );
1757
1758 time_t time = GetTicks();
1759 if ( time != (time_t)-1 )
1760 {
1761 // use strftime()
1762 tm *tm;
1763 if ( tz.GetOffset() == -GetTimeZone() )
1764 {
1765 // we are working with local time
1766 tm = localtime(&time);
1767
1768 // should never happen
1769 wxCHECK_MSG( tm, wxEmptyString, _T("localtime() failed") );
1770 }
1771 else
1772 {
1773 time += tz.GetOffset();
1774
1775 #ifdef __VMS__ // time is unsigned so avoid the warning
1776 int time2 = (int) time;
1777 if ( time2 >= 0 )
1778 #else
1779 if ( time >= 0 )
1780 #endif
1781 {
1782 tm = gmtime(&time);
1783
1784 // should never happen
1785 wxCHECK_MSG( tm, wxEmptyString, _T("gmtime() failed") );
1786 }
1787 else
1788 {
1789 tm = (struct tm *)NULL;
1790 }
1791 }
1792
1793 if ( tm )
1794 {
1795 return CallStrftime(format, tm);
1796 }
1797 //else: use generic code below
1798 }
1799
1800 // we only parse ANSI C format specifications here, no POSIX 2
1801 // complications, no GNU extensions
1802 Tm tm = GetTm(tz);
1803
1804 // used for calls to strftime() when we only deal with time
1805 struct tm tmTimeOnly;
1806 tmTimeOnly.tm_hour = tm.hour;
1807 tmTimeOnly.tm_min = tm.min;
1808 tmTimeOnly.tm_sec = tm.sec;
1809 tmTimeOnly.tm_wday = 0;
1810 tmTimeOnly.tm_yday = 0;
1811 tmTimeOnly.tm_mday = 1; // any date will do
1812 tmTimeOnly.tm_mon = 0;
1813 tmTimeOnly.tm_year = 76;
1814 tmTimeOnly.tm_isdst = 0; // no DST, we adjust for tz ourselves
1815
1816 wxString tmp, res, fmt;
1817 for ( const wxChar *p = format; *p; p++ )
1818 {
1819 if ( *p != _T('%') )
1820 {
1821 // copy as is
1822 res += *p;
1823
1824 continue;
1825 }
1826
1827 // set the default format
1828 switch ( *++p )
1829 {
1830 case _T('Y'): // year has 4 digits
1831 fmt = _T("%04d");
1832 break;
1833
1834 case _T('j'): // day of year has 3 digits
1835 fmt = _T("%03d");
1836 break;
1837
1838 default:
1839 // it's either another valid format specifier in which case
1840 // the format is "%02d" (for all the rest) or we have the
1841 // field width preceding the format in which case it will
1842 // override the default format anyhow
1843 fmt = _T("%02d");
1844 }
1845
1846 restart:
1847 // start of the format specification
1848 switch ( *p )
1849 {
1850 case _T('a'): // a weekday name
1851 case _T('A'):
1852 // second parameter should be TRUE for abbreviated names
1853 res += GetWeekDayName(tm.GetWeekDay(),
1854 *p == _T('a') ? Name_Abbr : Name_Full);
1855 break;
1856
1857 case _T('b'): // a month name
1858 case _T('B'):
1859 res += GetMonthName(tm.mon,
1860 *p == _T('b') ? Name_Abbr : Name_Full);
1861 break;
1862
1863 case _T('c'): // locale default date and time representation
1864 case _T('x'): // locale default date representation
1865 //
1866 // the problem: there is no way to know what do these format
1867 // specifications correspond to for the current locale.
1868 //
1869 // the solution: use a hack and still use strftime(): first
1870 // find the YEAR which is a year in the strftime() range (1970
1871 // - 2038) whose Jan 1 falls on the same week day as the Jan 1
1872 // of the real year. Then make a copy of the format and
1873 // replace all occurences of YEAR in it with some unique
1874 // string not appearing anywhere else in it, then use
1875 // strftime() to format the date in year YEAR and then replace
1876 // YEAR back by the real year and the unique replacement
1877 // string back with YEAR. Notice that "all occurences of YEAR"
1878 // means all occurences of 4 digit as well as 2 digit form!
1879 //
1880 // the bugs: we assume that neither of %c nor %x contains any
1881 // fields which may change between the YEAR and real year. For
1882 // example, the week number (%U, %W) and the day number (%j)
1883 // will change if one of these years is leap and the other one
1884 // is not!
1885 {
1886 // find the YEAR: normally, for any year X, Jan 1 or the
1887 // year X + 28 is the same weekday as Jan 1 of X (because
1888 // the weekday advances by 1 for each normal X and by 2
1889 // for each leap X, hence by 5 every 4 years or by 35
1890 // which is 0 mod 7 every 28 years) but this rule breaks
1891 // down if there are years between X and Y which are
1892 // divisible by 4 but not leap (i.e. divisible by 100 but
1893 // not 400), hence the correction.
1894
1895 int yearReal = GetYear(tz);
1896 int mod28 = yearReal % 28;
1897
1898 // be careful to not go too far - we risk to leave the
1899 // supported range
1900 int year;
1901 if ( mod28 < 10 )
1902 {
1903 year = 1988 + mod28; // 1988 == 0 (mod 28)
1904 }
1905 else
1906 {
1907 year = 1970 + mod28 - 10; // 1970 == 10 (mod 28)
1908 }
1909
1910 int nCentury = year / 100,
1911 nCenturyReal = yearReal / 100;
1912
1913 // need to adjust for the years divisble by 400 which are
1914 // not leap but are counted like leap ones if we just take
1915 // the number of centuries in between for nLostWeekDays
1916 int nLostWeekDays = (nCentury - nCenturyReal) -
1917 (nCentury / 4 - nCenturyReal / 4);
1918
1919 // we have to gain back the "lost" weekdays: note that the
1920 // effect of this loop is to not do anything to
1921 // nLostWeekDays (which we won't use any more), but to
1922 // (indirectly) set the year correctly
1923 while ( (nLostWeekDays % 7) != 0 )
1924 {
1925 nLostWeekDays += year++ % 4 ? 1 : 2;
1926 }
1927
1928 // at any rate, we couldn't go further than 1988 + 9 + 28!
1929 wxASSERT_MSG( year < 2030,
1930 _T("logic error in wxDateTime::Format") );
1931
1932 wxString strYear, strYear2;
1933 strYear.Printf(_T("%d"), year);
1934 strYear2.Printf(_T("%d"), year % 100);
1935
1936 // find two strings not occuring in format (this is surely
1937 // not optimal way of doing it... improvements welcome!)
1938 wxString fmt = format;
1939 wxString replacement = (wxChar)-1;
1940 while ( fmt.Find(replacement) != wxNOT_FOUND )
1941 {
1942 replacement << (wxChar)-1;
1943 }
1944
1945 wxString replacement2 = (wxChar)-2;
1946 while ( fmt.Find(replacement) != wxNOT_FOUND )
1947 {
1948 replacement << (wxChar)-2;
1949 }
1950
1951 // replace all occurences of year with it
1952 bool wasReplaced = fmt.Replace(strYear, replacement) > 0;
1953 if ( !wasReplaced )
1954 wasReplaced = fmt.Replace(strYear2, replacement2) > 0;
1955
1956 // use strftime() to format the same date but in supported
1957 // year
1958 //
1959 // NB: we assume that strftime() doesn't check for the
1960 // date validity and will happily format the date
1961 // corresponding to Feb 29 of a non leap year (which
1962 // may happen if yearReal was leap and year is not)
1963 struct tm tmAdjusted;
1964 InitTm(tmAdjusted);
1965 tmAdjusted.tm_hour = tm.hour;
1966 tmAdjusted.tm_min = tm.min;
1967 tmAdjusted.tm_sec = tm.sec;
1968 tmAdjusted.tm_wday = tm.GetWeekDay();
1969 tmAdjusted.tm_yday = GetDayOfYear();
1970 tmAdjusted.tm_mday = tm.mday;
1971 tmAdjusted.tm_mon = tm.mon;
1972 tmAdjusted.tm_year = year - 1900;
1973 tmAdjusted.tm_isdst = 0; // no DST, already adjusted
1974 wxString str = CallStrftime(*p == _T('c') ? _T("%c")
1975 : _T("%x"),
1976 &tmAdjusted);
1977
1978 // now replace the occurence of 1999 with the real year
1979 wxString strYearReal, strYearReal2;
1980 strYearReal.Printf(_T("%04d"), yearReal);
1981 strYearReal2.Printf(_T("%02d"), yearReal % 100);
1982 str.Replace(strYear, strYearReal);
1983 str.Replace(strYear2, strYearReal2);
1984
1985 // and replace back all occurences of replacement string
1986 if ( wasReplaced )
1987 {
1988 str.Replace(replacement2, strYear2);
1989 str.Replace(replacement, strYear);
1990 }
1991
1992 res += str;
1993 }
1994 break;
1995
1996 case _T('d'): // day of a month (01-31)
1997 res += wxString::Format(fmt, tm.mday);
1998 break;
1999
2000 case _T('H'): // hour in 24h format (00-23)
2001 res += wxString::Format(fmt, tm.hour);
2002 break;
2003
2004 case _T('I'): // hour in 12h format (01-12)
2005 {
2006 // 24h -> 12h, 0h -> 12h too
2007 int hour12 = tm.hour > 12 ? tm.hour - 12
2008 : tm.hour ? tm.hour : 12;
2009 res += wxString::Format(fmt, hour12);
2010 }
2011 break;
2012
2013 case _T('j'): // day of the year
2014 res += wxString::Format(fmt, GetDayOfYear(tz));
2015 break;
2016
2017 case _T('m'): // month as a number (01-12)
2018 res += wxString::Format(fmt, tm.mon + 1);
2019 break;
2020
2021 case _T('M'): // minute as a decimal number (00-59)
2022 res += wxString::Format(fmt, tm.min);
2023 break;
2024
2025 case _T('p'): // AM or PM string
2026 res += CallStrftime(_T("%p"), &tmTimeOnly);
2027 break;
2028
2029 case _T('S'): // second as a decimal number (00-61)
2030 res += wxString::Format(fmt, tm.sec);
2031 break;
2032
2033 case _T('U'): // week number in the year (Sunday 1st week day)
2034 res += wxString::Format(fmt, GetWeekOfYear(Sunday_First, tz));
2035 break;
2036
2037 case _T('W'): // week number in the year (Monday 1st week day)
2038 res += wxString::Format(fmt, GetWeekOfYear(Monday_First, tz));
2039 break;
2040
2041 case _T('w'): // weekday as a number (0-6), Sunday = 0
2042 res += wxString::Format(fmt, tm.GetWeekDay());
2043 break;
2044
2045 // case _T('x'): -- handled with "%c"
2046
2047 case _T('X'): // locale default time representation
2048 // just use strftime() to format the time for us
2049 res += CallStrftime(_T("%X"), &tmTimeOnly);
2050 break;
2051
2052 case _T('y'): // year without century (00-99)
2053 res += wxString::Format(fmt, tm.year % 100);
2054 break;
2055
2056 case _T('Y'): // year with century
2057 res += wxString::Format(fmt, tm.year);
2058 break;
2059
2060 case _T('Z'): // timezone name
2061 res += CallStrftime(_T("%Z"), &tmTimeOnly);
2062 break;
2063
2064 default:
2065 // is it the format width?
2066 fmt.Empty();
2067 while ( *p == _T('-') || *p == _T('+') ||
2068 *p == _T(' ') || wxIsdigit(*p) )
2069 {
2070 fmt += *p;
2071 }
2072
2073 if ( !fmt.IsEmpty() )
2074 {
2075 // we've only got the flags and width so far in fmt
2076 fmt.Prepend(_T('%'));
2077 fmt.Append(_T('d'));
2078
2079 goto restart;
2080 }
2081
2082 // no, it wasn't the width
2083 wxFAIL_MSG(_T("unknown format specificator"));
2084
2085 // fall through and just copy it nevertheless
2086
2087 case _T('%'): // a percent sign
2088 res += *p;
2089 break;
2090
2091 case 0: // the end of string
2092 wxFAIL_MSG(_T("missing format at the end of string"));
2093
2094 // just put the '%' which was the last char in format
2095 res += _T('%');
2096 break;
2097 }
2098 }
2099
2100 return res;
2101 }
2102
2103 // this function parses a string in (strict) RFC 822 format: see the section 5
2104 // of the RFC for the detailed description, but briefly it's something of the
2105 // form "Sat, 18 Dec 1999 00:48:30 +0100"
2106 //
2107 // this function is "strict" by design - it must reject anything except true
2108 // RFC822 time specs.
2109 //
2110 // TODO a great candidate for using reg exps
2111 const wxChar *wxDateTime::ParseRfc822Date(const wxChar* date)
2112 {
2113 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
2114
2115 const wxChar *p = date;
2116 const wxChar *comma = wxStrchr(p, _T(','));
2117 if ( comma )
2118 {
2119 // the part before comma is the weekday
2120
2121 // skip it for now - we don't use but might check that it really
2122 // corresponds to the specfied date
2123 p = comma + 1;
2124
2125 if ( *p != _T(' ') )
2126 {
2127 wxLogDebug(_T("no space after weekday in RFC822 time spec"));
2128
2129 return (wxChar *)NULL;
2130 }
2131
2132 p++; // skip space
2133 }
2134
2135 // the following 1 or 2 digits are the day number
2136 if ( !wxIsdigit(*p) )
2137 {
2138 wxLogDebug(_T("day number expected in RFC822 time spec, none found"));
2139
2140 return (wxChar *)NULL;
2141 }
2142
2143 wxDateTime_t day = *p++ - _T('0');
2144 if ( wxIsdigit(*p) )
2145 {
2146 day *= 10;
2147 day += *p++ - _T('0');
2148 }
2149
2150 if ( *p++ != _T(' ') )
2151 {
2152 return (wxChar *)NULL;
2153 }
2154
2155 // the following 3 letters specify the month
2156 wxString monName(p, 3);
2157 Month mon;
2158 if ( monName == _T("Jan") )
2159 mon = Jan;
2160 else if ( monName == _T("Feb") )
2161 mon = Feb;
2162 else if ( monName == _T("Mar") )
2163 mon = Mar;
2164 else if ( monName == _T("Apr") )
2165 mon = Apr;
2166 else if ( monName == _T("May") )
2167 mon = May;
2168 else if ( monName == _T("Jun") )
2169 mon = Jun;
2170 else if ( monName == _T("Jul") )
2171 mon = Jul;
2172 else if ( monName == _T("Aug") )
2173 mon = Aug;
2174 else if ( monName == _T("Sep") )
2175 mon = Sep;
2176 else if ( monName == _T("Oct") )
2177 mon = Oct;
2178 else if ( monName == _T("Nov") )
2179 mon = Nov;
2180 else if ( monName == _T("Dec") )
2181 mon = Dec;
2182 else
2183 {
2184 wxLogDebug(_T("Invalid RFC 822 month name '%s'"), monName.c_str());
2185
2186 return (wxChar *)NULL;
2187 }
2188
2189 p += 3;
2190
2191 if ( *p++ != _T(' ') )
2192 {
2193 return (wxChar *)NULL;
2194 }
2195
2196 // next is the year
2197 if ( !wxIsdigit(*p) )
2198 {
2199 // no year?
2200 return (wxChar *)NULL;
2201 }
2202
2203 int year = *p++ - _T('0');
2204
2205 if ( !wxIsdigit(*p) )
2206 {
2207 // should have at least 2 digits in the year
2208 return (wxChar *)NULL;
2209 }
2210
2211 year *= 10;
2212 year += *p++ - _T('0');
2213
2214 // is it a 2 digit year (as per original RFC 822) or a 4 digit one?
2215 if ( wxIsdigit(*p) )
2216 {
2217 year *= 10;
2218 year += *p++ - _T('0');
2219
2220 if ( !wxIsdigit(*p) )
2221 {
2222 // no 3 digit years please
2223 return (wxChar *)NULL;
2224 }
2225
2226 year *= 10;
2227 year += *p++ - _T('0');
2228 }
2229
2230 if ( *p++ != _T(' ') )
2231 {
2232 return (wxChar *)NULL;
2233 }
2234
2235 // time is in the format hh:mm:ss and seconds are optional
2236 if ( !wxIsdigit(*p) )
2237 {
2238 return (wxChar *)NULL;
2239 }
2240
2241 wxDateTime_t hour = *p++ - _T('0');
2242
2243 if ( !wxIsdigit(*p) )
2244 {
2245 return (wxChar *)NULL;
2246 }
2247
2248 hour *= 10;
2249 hour += *p++ - _T('0');
2250
2251 if ( *p++ != _T(':') )
2252 {
2253 return (wxChar *)NULL;
2254 }
2255
2256 if ( !wxIsdigit(*p) )
2257 {
2258 return (wxChar *)NULL;
2259 }
2260
2261 wxDateTime_t min = *p++ - _T('0');
2262
2263 if ( !wxIsdigit(*p) )
2264 {
2265 return (wxChar *)NULL;
2266 }
2267
2268 min *= 10;
2269 min += *p++ - _T('0');
2270
2271 wxDateTime_t sec = 0;
2272 if ( *p++ == _T(':') )
2273 {
2274 if ( !wxIsdigit(*p) )
2275 {
2276 return (wxChar *)NULL;
2277 }
2278
2279 sec = *p++ - _T('0');
2280
2281 if ( !wxIsdigit(*p) )
2282 {
2283 return (wxChar *)NULL;
2284 }
2285
2286 sec *= 10;
2287 sec += *p++ - _T('0');
2288 }
2289
2290 if ( *p++ != _T(' ') )
2291 {
2292 return (wxChar *)NULL;
2293 }
2294
2295 // and now the interesting part: the timezone
2296 int offset;
2297 if ( *p == _T('-') || *p == _T('+') )
2298 {
2299 // the explicit offset given: it has the form of hhmm
2300 bool plus = *p++ == _T('+');
2301
2302 if ( !wxIsdigit(*p) || !wxIsdigit(*(p + 1)) )
2303 {
2304 return (wxChar *)NULL;
2305 }
2306
2307 // hours
2308 offset = 60*(10*(*p - _T('0')) + (*(p + 1) - _T('0')));
2309
2310 p += 2;
2311
2312 if ( !wxIsdigit(*p) || !wxIsdigit(*(p + 1)) )
2313 {
2314 return (wxChar *)NULL;
2315 }
2316
2317 // minutes
2318 offset += 10*(*p - _T('0')) + (*(p + 1) - _T('0'));
2319
2320 if ( !plus )
2321 {
2322 offset = -offset;
2323 }
2324
2325 p += 2;
2326 }
2327 else
2328 {
2329 // the symbolic timezone given: may be either military timezone or one
2330 // of standard abbreviations
2331 if ( !*(p + 1) )
2332 {
2333 // military: Z = UTC, J unused, A = -1, ..., Y = +12
2334 static const int offsets[26] =
2335 {
2336 //A B C D E F G H I J K L M
2337 -1, -2, -3, -4, -5, -6, -7, -8, -9, 0, -10, -11, -12,
2338 //N O P R Q S T U V W Z Y Z
2339 +1, +2, +3, +4, +5, +6, +7, +8, +9, +10, +11, +12, 0
2340 };
2341
2342 if ( *p < _T('A') || *p > _T('Z') || *p == _T('J') )
2343 {
2344 wxLogDebug(_T("Invalid militaty timezone '%c'"), *p);
2345
2346 return (wxChar *)NULL;
2347 }
2348
2349 offset = offsets[*p++ - _T('A')];
2350 }
2351 else
2352 {
2353 // abbreviation
2354 wxString tz = p;
2355 if ( tz == _T("UT") || tz == _T("UTC") || tz == _T("GMT") )
2356 offset = 0;
2357 else if ( tz == _T("AST") )
2358 offset = AST - GMT0;
2359 else if ( tz == _T("ADT") )
2360 offset = ADT - GMT0;
2361 else if ( tz == _T("EST") )
2362 offset = EST - GMT0;
2363 else if ( tz == _T("EDT") )
2364 offset = EDT - GMT0;
2365 else if ( tz == _T("CST") )
2366 offset = CST - GMT0;
2367 else if ( tz == _T("CDT") )
2368 offset = CDT - GMT0;
2369 else if ( tz == _T("MST") )
2370 offset = MST - GMT0;
2371 else if ( tz == _T("MDT") )
2372 offset = MDT - GMT0;
2373 else if ( tz == _T("PST") )
2374 offset = PST - GMT0;
2375 else if ( tz == _T("PDT") )
2376 offset = PDT - GMT0;
2377 else
2378 {
2379 wxLogDebug(_T("Unknown RFC 822 timezone '%s'"), p);
2380
2381 return (wxChar *)NULL;
2382 }
2383
2384 p += tz.length();
2385 }
2386
2387 // make it minutes
2388 offset *= 60;
2389 }
2390
2391 // the spec was correct
2392 Set(day, mon, year, hour, min, sec);
2393 MakeTimezone((wxDateTime_t)(60*offset));
2394
2395 return p;
2396 }
2397
2398 const wxChar *wxDateTime::ParseFormat(const wxChar *date,
2399 const wxChar *format,
2400 const wxDateTime& dateDef)
2401 {
2402 wxCHECK_MSG( date && format, (wxChar *)NULL,
2403 _T("NULL pointer in wxDateTime::ParseFormat()") );
2404
2405 wxString str;
2406 unsigned long num;
2407
2408 // what fields have we found?
2409 bool haveWDay = FALSE,
2410 haveYDay = FALSE,
2411 haveDay = FALSE,
2412 haveMon = FALSE,
2413 haveYear = FALSE,
2414 haveHour = FALSE,
2415 haveMin = FALSE,
2416 haveSec = FALSE;
2417
2418 bool hourIsIn12hFormat = FALSE, // or in 24h one?
2419 isPM = FALSE; // AM by default
2420
2421 // and the value of the items we have (init them to get rid of warnings)
2422 wxDateTime_t sec = 0,
2423 min = 0,
2424 hour = 0;
2425 WeekDay wday = Inv_WeekDay;
2426 wxDateTime_t yday = 0,
2427 mday = 0;
2428 wxDateTime::Month mon = Inv_Month;
2429 int year = 0;
2430
2431 const wxChar *input = date;
2432 for ( const wxChar *fmt = format; *fmt; fmt++ )
2433 {
2434 if ( *fmt != _T('%') )
2435 {
2436 if ( wxIsspace(*fmt) )
2437 {
2438 // a white space in the format string matches 0 or more white
2439 // spaces in the input
2440 while ( wxIsspace(*input) )
2441 {
2442 input++;
2443 }
2444 }
2445 else // !space
2446 {
2447 // any other character (not whitespace, not '%') must be
2448 // matched by itself in the input
2449 if ( *input++ != *fmt )
2450 {
2451 // no match
2452 return (wxChar *)NULL;
2453 }
2454 }
2455
2456 // done with this format char
2457 continue;
2458 }
2459
2460 // start of a format specification
2461 switch ( *++fmt )
2462 {
2463 case _T('a'): // a weekday name
2464 case _T('A'):
2465 {
2466 int flag = *fmt == _T('a') ? Name_Abbr : Name_Full;
2467 wday = GetWeekDayFromName(GetAlphaToken(input), flag);
2468 if ( wday == Inv_WeekDay )
2469 {
2470 // no match
2471 return (wxChar *)NULL;
2472 }
2473 }
2474 haveWDay = TRUE;
2475 break;
2476
2477 case _T('b'): // a month name
2478 case _T('B'):
2479 {
2480 int flag = *fmt == _T('b') ? Name_Abbr : Name_Full;
2481 mon = GetMonthFromName(GetAlphaToken(input), flag);
2482 if ( mon == Inv_Month )
2483 {
2484 // no match
2485 return (wxChar *)NULL;
2486 }
2487 }
2488 haveMon = TRUE;
2489 break;
2490
2491 case _T('c'): // locale default date and time representation
2492 {
2493 wxDateTime dt;
2494
2495 // this is the format which corresponds to ctime() output
2496 // and strptime("%c") should parse it, so try it first
2497 static const wxChar *fmtCtime = _T("%a %b %d %H:%M:%S %Y");
2498
2499 const wxChar *result = dt.ParseFormat(input, fmtCtime);
2500 if ( !result )
2501 {
2502 result = dt.ParseFormat(input, _T("%x %X"));
2503 }
2504
2505 if ( !result )
2506 {
2507 result = dt.ParseFormat(input, _T("%X %x"));
2508 }
2509
2510 if ( !result )
2511 {
2512 // we've tried everything and still no match
2513 return (wxChar *)NULL;
2514 }
2515
2516 Tm tm = dt.GetTm();
2517
2518 haveDay = haveMon = haveYear =
2519 haveHour = haveMin = haveSec = TRUE;
2520
2521 hour = tm.hour;
2522 min = tm.min;
2523 sec = tm.sec;
2524
2525 year = tm.year;
2526 mon = tm.mon;
2527 mday = tm.mday;
2528
2529 input = result;
2530 }
2531 break;
2532
2533 case _T('d'): // day of a month (01-31)
2534 if ( !GetNumericToken(input, &num) || (num > 31) )
2535 {
2536 // no match
2537 return (wxChar *)NULL;
2538 }
2539
2540 // we can't check whether the day range is correct yet, will
2541 // do it later - assume ok for now
2542 haveDay = TRUE;
2543 mday = (wxDateTime_t)num;
2544 break;
2545
2546 case _T('H'): // hour in 24h format (00-23)
2547 if ( !GetNumericToken(input, &num) || (num > 23) )
2548 {
2549 // no match
2550 return (wxChar *)NULL;
2551 }
2552
2553 haveHour = TRUE;
2554 hour = (wxDateTime_t)num;
2555 break;
2556
2557 case _T('I'): // hour in 12h format (01-12)
2558 if ( !GetNumericToken(input, &num) || !num || (num > 12) )
2559 {
2560 // no match
2561 return (wxChar *)NULL;
2562 }
2563
2564 haveHour = TRUE;
2565 hourIsIn12hFormat = TRUE;
2566 hour = num % 12; // 12 should be 0
2567 break;
2568
2569 case _T('j'): // day of the year
2570 if ( !GetNumericToken(input, &num) || !num || (num > 366) )
2571 {
2572 // no match
2573 return (wxChar *)NULL;
2574 }
2575
2576 haveYDay = TRUE;
2577 yday = (wxDateTime_t)num;
2578 break;
2579
2580 case _T('m'): // month as a number (01-12)
2581 if ( !GetNumericToken(input, &num) || !num || (num > 12) )
2582 {
2583 // no match
2584 return (wxChar *)NULL;
2585 }
2586
2587 haveMon = TRUE;
2588 mon = (Month)(num - 1);
2589 break;
2590
2591 case _T('M'): // minute as a decimal number (00-59)
2592 if ( !GetNumericToken(input, &num) || (num > 59) )
2593 {
2594 // no match
2595 return (wxChar *)NULL;
2596 }
2597
2598 haveMin = TRUE;
2599 min = (wxDateTime_t)num;
2600 break;
2601
2602 case _T('p'): // AM or PM string
2603 {
2604 wxString am, pm, token = GetAlphaToken(input);
2605
2606 GetAmPmStrings(&am, &pm);
2607 if ( token.CmpNoCase(pm) == 0 )
2608 {
2609 isPM = TRUE;
2610 }
2611 else if ( token.CmpNoCase(am) != 0 )
2612 {
2613 // no match
2614 return (wxChar *)NULL;
2615 }
2616 }
2617 break;
2618
2619 case _T('r'): // time as %I:%M:%S %p
2620 {
2621 wxDateTime dt;
2622 input = dt.ParseFormat(input, _T("%I:%M:%S %p"));
2623 if ( !input )
2624 {
2625 // no match
2626 return (wxChar *)NULL;
2627 }
2628
2629 haveHour = haveMin = haveSec = TRUE;
2630
2631 Tm tm = dt.GetTm();
2632 hour = tm.hour;
2633 min = tm.min;
2634 sec = tm.sec;
2635 }
2636 break;
2637
2638 case _T('R'): // time as %H:%M
2639 {
2640 wxDateTime dt;
2641 input = dt.ParseFormat(input, _T("%H:%M"));
2642 if ( !input )
2643 {
2644 // no match
2645 return (wxChar *)NULL;
2646 }
2647
2648 haveHour = haveMin = TRUE;
2649
2650 Tm tm = dt.GetTm();
2651 hour = tm.hour;
2652 min = tm.min;
2653 }
2654
2655 case _T('S'): // second as a decimal number (00-61)
2656 if ( !GetNumericToken(input, &num) || (num > 61) )
2657 {
2658 // no match
2659 return (wxChar *)NULL;
2660 }
2661
2662 haveSec = TRUE;
2663 sec = (wxDateTime_t)num;
2664 break;
2665
2666 case _T('T'): // time as %H:%M:%S
2667 {
2668 wxDateTime dt;
2669 input = dt.ParseFormat(input, _T("%H:%M:%S"));
2670 if ( !input )
2671 {
2672 // no match
2673 return (wxChar *)NULL;
2674 }
2675
2676 haveHour = haveMin = haveSec = TRUE;
2677
2678 Tm tm = dt.GetTm();
2679 hour = tm.hour;
2680 min = tm.min;
2681 sec = tm.sec;
2682 }
2683 break;
2684
2685 case _T('w'): // weekday as a number (0-6), Sunday = 0
2686 if ( !GetNumericToken(input, &num) || (wday > 6) )
2687 {
2688 // no match
2689 return (wxChar *)NULL;
2690 }
2691
2692 haveWDay = TRUE;
2693 wday = (WeekDay)num;
2694 break;
2695
2696 case _T('x'): // locale default date representation
2697 #ifdef HAVE_STRPTIME
2698 // try using strptime() - it may fail even if the input is
2699 // correct but the date is out of range, so we will fall back
2700 // to our generic code anyhow (FIXME !Unicode friendly)
2701 {
2702 struct tm tm;
2703 const wxChar *result = strptime(input, "%x", &tm);
2704 if ( result )
2705 {
2706 input = result;
2707
2708 haveDay = haveMon = haveYear = TRUE;
2709
2710 year = 1900 + tm.tm_year;
2711 mon = (Month)tm.tm_mon;
2712 mday = tm.tm_mday;
2713
2714 break;
2715 }
2716 }
2717 #endif // HAVE_STRPTIME
2718
2719 // TODO query the LOCALE_IDATE setting under Win32
2720 {
2721 wxDateTime dt;
2722
2723 wxString fmtDate, fmtDateAlt;
2724 if ( IsWestEuropeanCountry(GetCountry()) ||
2725 GetCountry() == Russia )
2726 {
2727 fmtDate = _T("%d/%m/%y");
2728 fmtDateAlt = _T("%m/%d/%y");
2729 }
2730 else // assume USA
2731 {
2732 fmtDate = _T("%m/%d/%y");
2733 fmtDateAlt = _T("%d/%m/%y");
2734 }
2735
2736 const wxChar *result = dt.ParseFormat(input, fmtDate);
2737
2738 if ( !result )
2739 {
2740 // ok, be nice and try another one
2741 result = dt.ParseFormat(input, fmtDateAlt);
2742 }
2743
2744 if ( !result )
2745 {
2746 // bad luck
2747 return (wxChar *)NULL;
2748 }
2749
2750 Tm tm = dt.GetTm();
2751
2752 haveDay = haveMon = haveYear = TRUE;
2753
2754 year = tm.year;
2755 mon = tm.mon;
2756 mday = tm.mday;
2757
2758 input = result;
2759 }
2760
2761 break;
2762
2763 case _T('X'): // locale default time representation
2764 #ifdef HAVE_STRPTIME
2765 {
2766 // use strptime() to do it for us (FIXME !Unicode friendly)
2767 struct tm tm;
2768 input = strptime(input, "%X", &tm);
2769 if ( !input )
2770 {
2771 return (wxChar *)NULL;
2772 }
2773
2774 haveHour = haveMin = haveSec = TRUE;
2775
2776 hour = tm.tm_hour;
2777 min = tm.tm_min;
2778 sec = tm.tm_sec;
2779 }
2780 #else // !HAVE_STRPTIME
2781 // TODO under Win32 we can query the LOCALE_ITIME system
2782 // setting which says whether the default time format is
2783 // 24 or 12 hour
2784 {
2785 // try to parse what follows as "%H:%M:%S" and, if this
2786 // fails, as "%I:%M:%S %p" - this should catch the most
2787 // common cases
2788 wxDateTime dt;
2789
2790 const wxChar *result = dt.ParseFormat(input, _T("%T"));
2791 if ( !result )
2792 {
2793 result = dt.ParseFormat(input, _T("%r"));
2794 }
2795
2796 if ( !result )
2797 {
2798 // no match
2799 return (wxChar *)NULL;
2800 }
2801
2802 haveHour = haveMin = haveSec = TRUE;
2803
2804 Tm tm = dt.GetTm();
2805 hour = tm.hour;
2806 min = tm.min;
2807 sec = tm.sec;
2808
2809 input = result;
2810 }
2811 #endif // HAVE_STRPTIME/!HAVE_STRPTIME
2812 break;
2813
2814 case _T('y'): // year without century (00-99)
2815 if ( !GetNumericToken(input, &num) || (num > 99) )
2816 {
2817 // no match
2818 return (wxChar *)NULL;
2819 }
2820
2821 haveYear = TRUE;
2822 year = 1900 + num;
2823 break;
2824
2825 case _T('Y'): // year with century
2826 if ( !GetNumericToken(input, &num) )
2827 {
2828 // no match
2829 return (wxChar *)NULL;
2830 }
2831
2832 haveYear = TRUE;
2833 year = (wxDateTime_t)num;
2834 break;
2835
2836 case _T('Z'): // timezone name
2837 wxFAIL_MSG(_T("TODO"));
2838 break;
2839
2840 case _T('%'): // a percent sign
2841 if ( *input++ != _T('%') )
2842 {
2843 // no match
2844 return (wxChar *)NULL;
2845 }
2846 break;
2847
2848 case 0: // the end of string
2849 wxFAIL_MSG(_T("unexpected format end"));
2850
2851 // fall through
2852
2853 default: // not a known format spec
2854 return (wxChar *)NULL;
2855 }
2856 }
2857
2858 // format matched, try to construct a date from what we have now
2859 Tm tmDef;
2860 if ( dateDef.IsValid() )
2861 {
2862 // take this date as default
2863 tmDef = dateDef.GetTm();
2864 }
2865 else if ( m_time != 0 )
2866 {
2867 // if this date is valid, don't change it
2868 tmDef = GetTm();
2869 }
2870 else
2871 {
2872 // no default and this date is invalid - fall back to Today()
2873 tmDef = Today().GetTm();
2874 }
2875
2876 Tm tm = tmDef;
2877
2878 // set the date
2879 if ( haveYear )
2880 {
2881 tm.year = year;
2882 }
2883
2884 // TODO we don't check here that the values are consistent, if both year
2885 // day and month/day were found, we just ignore the year day and we
2886 // also always ignore the week day
2887 if ( haveMon && haveDay )
2888 {
2889 if ( mday > GetNumOfDaysInMonth(tm.year, mon) )
2890 {
2891 wxLogDebug(_T("bad month day in wxDateTime::ParseFormat"));
2892
2893 return (wxChar *)NULL;
2894 }
2895
2896 tm.mon = mon;
2897 tm.mday = mday;
2898 }
2899 else if ( haveYDay )
2900 {
2901 if ( yday > GetNumberOfDays(tm.year) )
2902 {
2903 wxLogDebug(_T("bad year day in wxDateTime::ParseFormat"));
2904
2905 return (wxChar *)NULL;
2906 }
2907
2908 Tm tm2 = wxDateTime(1, Jan, tm.year).SetToYearDay(yday).GetTm();
2909
2910 tm.mon = tm2.mon;
2911 tm.mday = tm2.mday;
2912 }
2913
2914 // deal with AM/PM
2915 if ( haveHour && hourIsIn12hFormat && isPM )
2916 {
2917 // translate to 24hour format
2918 hour += 12;
2919 }
2920 //else: either already in 24h format or no translation needed
2921
2922 // set the time
2923 if ( haveHour )
2924 {
2925 tm.hour = hour;
2926 }
2927
2928 if ( haveMin )
2929 {
2930 tm.min = min;
2931 }
2932
2933 if ( haveSec )
2934 {
2935 tm.sec = sec;
2936 }
2937
2938 Set(tm);
2939
2940 return input;
2941 }
2942
2943 const wxChar *wxDateTime::ParseDateTime(const wxChar *date)
2944 {
2945 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
2946
2947 // there is a public domain version of getdate.y, but it only works for
2948 // English...
2949 wxFAIL_MSG(_T("TODO"));
2950
2951 return (wxChar *)NULL;
2952 }
2953
2954 const wxChar *wxDateTime::ParseDate(const wxChar *date)
2955 {
2956 // this is a simplified version of ParseDateTime() which understands only
2957 // "today" (for wxDate compatibility) and digits only otherwise (and not
2958 // all esoteric constructions ParseDateTime() knows about)
2959
2960 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
2961
2962 const wxChar *p = date;
2963 while ( wxIsspace(*p) )
2964 p++;
2965
2966 wxString today = _T("today");
2967 size_t len = today.length();
2968 if ( wxString(p, len).CmpNoCase(today) == 0 )
2969 {
2970 // nothing can follow this, so stop here
2971 p += len;
2972
2973 *this = Today();
2974
2975 return p;
2976 }
2977
2978 // what do we have?
2979 bool haveDay = FALSE, // the months day?
2980 haveWDay = FALSE, // the day of week?
2981 haveMon = FALSE, // the month?
2982 haveYear = FALSE; // the year?
2983
2984 // and the value of the items we have (init them to get rid of warnings)
2985 WeekDay wday = Inv_WeekDay;
2986 wxDateTime_t day = 0;
2987 wxDateTime::Month mon = Inv_Month;
2988 int year = 0;
2989
2990 // tokenize the string
2991 wxStringTokenizer tok(p, _T(",/-\t\n "));
2992 while ( tok.HasMoreTokens() )
2993 {
2994 wxString token = tok.GetNextToken();
2995
2996 // is it a number?
2997 unsigned long val;
2998 if ( token.ToULong(&val) )
2999 {
3000 // guess what this number is
3001
3002 bool isDay = FALSE,
3003 isMonth = FALSE,
3004 // only years are counted from 0
3005 isYear = (val == 0) || (val > 31);
3006 if ( !isYear )
3007 {
3008 // may be the month or month day or the year, assume the
3009 // month day by default and fallback to month if the range
3010 // allow it or to the year if our assumption doesn't work
3011 if ( haveDay )
3012 {
3013 // we already have the day, so may only be a month or year
3014 if ( val > 12 )
3015 {
3016 isYear = TRUE;
3017 }
3018 else
3019 {
3020 isMonth = TRUE;
3021 }
3022 }
3023 else // it may be day
3024 {
3025 isDay = TRUE;
3026
3027 // check the range
3028 if ( haveMon )
3029 {
3030 if ( val > GetNumOfDaysInMonth(haveYear ? year
3031 : Inv_Year,
3032 mon) )
3033 {
3034 // oops, it can't be a day finally
3035 isDay = FALSE;
3036
3037 if ( val > 12 )
3038 {
3039 isYear = TRUE;
3040 }
3041 else
3042 {
3043 isMonth = TRUE;
3044 }
3045 }
3046 }
3047 }
3048 }
3049
3050 // remember that we have this and stop the scan if it's the second
3051 // time we find this, except for the day logic (see there)
3052 if ( isYear )
3053 {
3054 if ( haveYear )
3055 {
3056 break;
3057 }
3058
3059 haveYear = TRUE;
3060
3061 // no roll over - 99 means 99, not 1999 for us
3062 year = val;
3063 }
3064 else if ( isMonth )
3065 {
3066 if ( haveMon )
3067 {
3068 break;
3069 }
3070
3071 haveMon = TRUE;
3072
3073 mon = (wxDateTime::Month)val;
3074 }
3075 else
3076 {
3077 wxASSERT_MSG( isDay, _T("logic error") );
3078
3079 if ( haveDay )
3080 {
3081 // may be were mistaken when we found it for the first
3082 // time? may be it was a month or year instead?
3083 //
3084 // this ability to "backtrack" allows us to correctly parse
3085 // both things like 01/13 and 13/01 - but, of course, we
3086 // still can't resolve the ambiguity in 01/02 (it will be
3087 // Feb 1 for us, not Jan 2 as americans might expect!)
3088 if ( (day <= 12) && !haveMon )
3089 {
3090 // exchange day and month
3091 mon = (wxDateTime::Month)day;
3092
3093 haveMon = TRUE;
3094 }
3095 else if ( !haveYear )
3096 {
3097 // exchange day and year
3098 year = day;
3099
3100 haveYear = TRUE;
3101 }
3102 }
3103
3104 haveDay = TRUE;
3105
3106 day = val;
3107 }
3108 }
3109 else // not a number
3110 {
3111 mon = GetMonthFromName(token, Name_Full | Name_Abbr);
3112 if ( mon != Inv_Month )
3113 {
3114 // it's a month
3115 if ( haveMon )
3116 {
3117 break;
3118 }
3119
3120 haveMon = TRUE;
3121 }
3122 else
3123 {
3124 wday = GetWeekDayFromName(token, Name_Full | Name_Abbr);
3125 if ( wday != Inv_WeekDay )
3126 {
3127 // a week day
3128 if ( haveWDay )
3129 {
3130 break;
3131 }
3132
3133 haveWDay = TRUE;
3134 }
3135 else
3136 {
3137 // try the ordinals
3138 static const wxChar *ordinals[] =
3139 {
3140 wxTRANSLATE("first"),
3141 wxTRANSLATE("second"),
3142 wxTRANSLATE("third"),
3143 wxTRANSLATE("fourth"),
3144 wxTRANSLATE("fifth"),
3145 wxTRANSLATE("sixth"),
3146 wxTRANSLATE("seventh"),
3147 wxTRANSLATE("eighth"),
3148 wxTRANSLATE("ninth"),
3149 wxTRANSLATE("tenth"),
3150 wxTRANSLATE("eleventh"),
3151 wxTRANSLATE("twelfth"),
3152 wxTRANSLATE("thirteenth"),
3153 wxTRANSLATE("fourteenth"),
3154 wxTRANSLATE("fifteenth"),
3155 wxTRANSLATE("sixteenth"),
3156 wxTRANSLATE("seventeenth"),
3157 wxTRANSLATE("eighteenth"),
3158 wxTRANSLATE("nineteenth"),
3159 wxTRANSLATE("twentieth"),
3160 // that's enough - otherwise we'd have problems with
3161 // composite (or not) ordinals otherwise
3162 };
3163
3164 size_t n;
3165 for ( n = 0; n < WXSIZEOF(ordinals); n++ )
3166 {
3167 if ( token.CmpNoCase(ordinals[n]) == 0 )
3168 {
3169 break;
3170 }
3171 }
3172
3173 if ( n == WXSIZEOF(ordinals) )
3174 {
3175 // stop here - something unknown
3176 break;
3177 }
3178
3179 // it's a day
3180 if ( haveDay )
3181 {
3182 // don't try anything here (as in case of numeric day
3183 // above) - the symbolic day spec should always
3184 // precede the month/year
3185 break;
3186 }
3187
3188 haveDay = TRUE;
3189
3190 day = n + 1;
3191 }
3192 }
3193 }
3194 }
3195
3196 // either no more tokens or the scan was stopped by something we couldn't
3197 // parse - in any case, see if we can construct a date from what we have
3198 if ( !haveDay && !haveWDay )
3199 {
3200 wxLogDebug(_T("no day, no weekday hence no date."));
3201
3202 return (wxChar *)NULL;
3203 }
3204
3205 if ( haveWDay && (haveMon || haveYear || haveDay) &&
3206 !(haveMon && haveMon && haveYear) )
3207 {
3208 // without adjectives (which we don't support here) the week day only
3209 // makes sense completely separately or with the full date
3210 // specification (what would "Wed 1999" mean?)
3211 return (wxChar *)NULL;
3212 }
3213
3214 if ( !haveMon )
3215 {
3216 mon = GetCurrentMonth();
3217 }
3218
3219 if ( !haveYear )
3220 {
3221 year = GetCurrentYear();
3222 }
3223
3224 if ( haveDay )
3225 {
3226 Set(day, mon, year);
3227
3228 if ( haveWDay )
3229 {
3230 // check that it is really the same
3231 if ( GetWeekDay() != wday )
3232 {
3233 // inconsistency detected
3234 return (wxChar *)NULL;
3235 }
3236 }
3237 }
3238 else // haveWDay
3239 {
3240 *this = Today();
3241
3242 SetToWeekDayInSameWeek(wday);
3243 }
3244
3245 // return the pointer to the next char
3246 return p + wxStrlen(p) - wxStrlen(tok.GetString());
3247 }
3248
3249 const wxChar *wxDateTime::ParseTime(const wxChar *time)
3250 {
3251 wxCHECK_MSG( time, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
3252
3253 // first try some extra things
3254 static const struct
3255 {
3256 const wxChar *name;
3257 wxDateTime_t hour;
3258 } stdTimes[] =
3259 {
3260 { wxTRANSLATE("noon"), 12 },
3261 { wxTRANSLATE("midnight"), 00 },
3262 // anything else?
3263 };
3264
3265 for ( size_t n = 0; n < WXSIZEOF(stdTimes); n++ )
3266 {
3267 wxString timeString = wxGetTranslation(stdTimes[n].name);
3268 size_t len = timeString.length();
3269 if ( timeString.CmpNoCase(wxString(time, len)) == 0 )
3270 {
3271 Set(stdTimes[n].hour, 0, 0);
3272
3273 return time + len;
3274 }
3275 }
3276
3277 // try all time formats we may think about starting with the standard one
3278 const wxChar *result = ParseFormat(time, _T("%X"));
3279 if ( !result )
3280 {
3281 // normally, it's the same, but why not try it?
3282 result = ParseFormat(time, _T("%H:%M:%S"));
3283 }
3284
3285 if ( !result )
3286 {
3287 // 12hour with AM/PM?
3288 result = ParseFormat(time, _T("%I:%M:%S %p"));
3289 }
3290
3291 if ( !result )
3292 {
3293 // without seconds?
3294 result = ParseFormat(time, _T("%H:%M"));
3295 }
3296
3297 if ( !result )
3298 {
3299 // 12hour with AM/PM but without seconds?
3300 result = ParseFormat(time, _T("%I:%M %p"));
3301 }
3302
3303 if ( !result )
3304 {
3305 // just the hour?
3306 result = ParseFormat(time, _T("%H"));
3307 }
3308
3309 if ( !result )
3310 {
3311 // just the hour and AM/PM?
3312 result = ParseFormat(time, _T("%I %p"));
3313 }
3314
3315 // TODO: parse timezones
3316
3317 return result;
3318 }
3319
3320 // ----------------------------------------------------------------------------
3321 // Workdays and holidays support
3322 // ----------------------------------------------------------------------------
3323
3324 bool wxDateTime::IsWorkDay(Country WXUNUSED(country)) const
3325 {
3326 return !wxDateTimeHolidayAuthority::IsHoliday(*this);
3327 }
3328
3329 // ============================================================================
3330 // wxTimeSpan
3331 // ============================================================================
3332
3333 // not all strftime(3) format specifiers make sense here because, for example,
3334 // a time span doesn't have a year nor a timezone
3335 //
3336 // Here are the ones which are supported (all of them are supported by strftime
3337 // as well):
3338 // %H hour in 24 hour format
3339 // %M minute (00 - 59)
3340 // %S second (00 - 59)
3341 // %% percent sign
3342 //
3343 // Also, for MFC CTimeSpan compatibility, we support
3344 // %D number of days
3345 //
3346 // And, to be better than MFC :-), we also have
3347 // %E number of wEeks
3348 // %l milliseconds (000 - 999)
3349 wxString wxTimeSpan::Format(const wxChar *format) const
3350 {
3351 wxCHECK_MSG( format, _T(""), _T("NULL format in wxTimeSpan::Format") );
3352
3353 wxString str;
3354 str.Alloc(wxStrlen(format));
3355
3356 for ( const wxChar *pch = format; pch; pch++ )
3357 {
3358 wxChar ch = *pch;
3359
3360 if ( ch == '%' )
3361 {
3362 wxString tmp;
3363
3364 ch = *pch++;
3365 switch ( ch )
3366 {
3367 default:
3368 wxFAIL_MSG( _T("invalid format character") );
3369 // fall through
3370
3371 case '%':
3372 // will get to str << ch below
3373 break;
3374
3375 case 'D':
3376 tmp.Printf(_T("%d"), GetDays());
3377 break;
3378
3379 case 'E':
3380 tmp.Printf(_T("%d"), GetWeeks());
3381 break;
3382
3383 case 'H':
3384 tmp.Printf(_T("%02d"), GetHours());
3385 break;
3386
3387 case 'l':
3388 tmp.Printf(_T("%03ld"), GetMilliseconds().ToLong());
3389 break;
3390
3391 case 'M':
3392 tmp.Printf(_T("%02d"), GetMinutes());
3393 break;
3394
3395 case 'S':
3396 tmp.Printf(_T("%02ld"), GetSeconds().ToLong());
3397 break;
3398 }
3399
3400 if ( !!tmp )
3401 {
3402 str += tmp;
3403
3404 // skip str += ch below
3405 continue;
3406 }
3407 }
3408
3409 str += ch;
3410 }
3411
3412 return str;
3413 }
3414
3415 // ============================================================================
3416 // wxDateTimeHolidayAuthority and related classes
3417 // ============================================================================
3418
3419 #include "wx/arrimpl.cpp"
3420
3421 WX_DEFINE_OBJARRAY(wxDateTimeArray)
3422
3423 static int wxCMPFUNC_CONV
3424 wxDateTimeCompareFunc(wxDateTime **first, wxDateTime **second)
3425 {
3426 wxDateTime dt1 = **first,
3427 dt2 = **second;
3428
3429 return dt1 == dt2 ? 0 : dt1 < dt2 ? -1 : +1;
3430 }
3431
3432 // ----------------------------------------------------------------------------
3433 // wxDateTimeHolidayAuthority
3434 // ----------------------------------------------------------------------------
3435
3436 wxHolidayAuthoritiesArray wxDateTimeHolidayAuthority::ms_authorities;
3437
3438 /* static */
3439 bool wxDateTimeHolidayAuthority::IsHoliday(const wxDateTime& dt)
3440 {
3441 size_t count = ms_authorities.GetCount();
3442 for ( size_t n = 0; n < count; n++ )
3443 {
3444 if ( ms_authorities[n]->DoIsHoliday(dt) )
3445 {
3446 return TRUE;
3447 }
3448 }
3449
3450 return FALSE;
3451 }
3452
3453 /* static */
3454 size_t
3455 wxDateTimeHolidayAuthority::GetHolidaysInRange(const wxDateTime& dtStart,
3456 const wxDateTime& dtEnd,
3457 wxDateTimeArray& holidays)
3458 {
3459 wxDateTimeArray hol;
3460
3461 holidays.Empty();
3462
3463 size_t count = ms_authorities.GetCount();
3464 for ( size_t n = 0; n < count; n++ )
3465 {
3466 ms_authorities[n]->DoGetHolidaysInRange(dtStart, dtEnd, hol);
3467
3468 WX_APPEND_ARRAY(holidays, hol);
3469 }
3470
3471 holidays.Sort(wxDateTimeCompareFunc);
3472
3473 return holidays.GetCount();
3474 }
3475
3476 /* static */
3477 void wxDateTimeHolidayAuthority::ClearAllAuthorities()
3478 {
3479 WX_CLEAR_ARRAY(ms_authorities);
3480 }
3481
3482 /* static */
3483 void wxDateTimeHolidayAuthority::AddAuthority(wxDateTimeHolidayAuthority *auth)
3484 {
3485 ms_authorities.Add(auth);
3486 }
3487
3488 // ----------------------------------------------------------------------------
3489 // wxDateTimeWorkDays
3490 // ----------------------------------------------------------------------------
3491
3492 bool wxDateTimeWorkDays::DoIsHoliday(const wxDateTime& dt) const
3493 {
3494 wxDateTime::WeekDay wd = dt.GetWeekDay();
3495
3496 return (wd == wxDateTime::Sun) || (wd == wxDateTime::Sat);
3497 }
3498
3499 size_t wxDateTimeWorkDays::DoGetHolidaysInRange(const wxDateTime& dtStart,
3500 const wxDateTime& dtEnd,
3501 wxDateTimeArray& holidays) const
3502 {
3503 if ( dtStart > dtEnd )
3504 {
3505 wxFAIL_MSG( _T("invalid date range in GetHolidaysInRange") );
3506
3507 return 0u;
3508 }
3509
3510 holidays.Empty();
3511
3512 // instead of checking all days, start with the first Sat after dtStart and
3513 // end with the last Sun before dtEnd
3514 wxDateTime dtSatFirst = dtStart.GetNextWeekDay(wxDateTime::Sat),
3515 dtSatLast = dtEnd.GetPrevWeekDay(wxDateTime::Sat),
3516 dtSunFirst = dtStart.GetNextWeekDay(wxDateTime::Sun),
3517 dtSunLast = dtEnd.GetPrevWeekDay(wxDateTime::Sun),
3518 dt;
3519
3520 for ( dt = dtSatFirst; dt <= dtSatLast; dt += wxDateSpan::Week() )
3521 {
3522 holidays.Add(dt);
3523 }
3524
3525 for ( dt = dtSunFirst; dt <= dtSunLast; dt += wxDateSpan::Week() )
3526 {
3527 holidays.Add(dt);
3528 }
3529
3530 return holidays.GetCount();
3531 }