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