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