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