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