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