63e797a4290f637f861547202b8c5ca265385b7b
[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
76
77 #include "wx/datetime.h"
78
79 #ifndef WX_TIMEZONE
80 #define WX_TIMEZONE timezone
81 #endif
82
83 // Is this right? Just a guess. (JACS)
84 #ifdef __MINGW32__
85 #define timezone _timezone
86 #endif
87
88 // ----------------------------------------------------------------------------
89 // constants
90 // ----------------------------------------------------------------------------
91
92 // some trivial ones
93 static const int MONTHS_IN_YEAR = 12;
94
95 static const int SECONDS_IN_MINUTE = 60;
96
97 static const long SECONDS_PER_DAY = 86400l;
98
99 static const long MILLISECONDS_PER_DAY = 86400000l;
100
101 // this is the integral part of JDN of the midnight of Jan 1, 1970
102 // (i.e. JDN(Jan 1, 1970) = 2440587.5)
103 static const long EPOCH_JDN = 2440587l;
104
105 // the date of JDN -0.5 (as we don't work with fractional parts, this is the
106 // reference date for us) is Nov 24, 4714BC
107 static const int JDN_0_YEAR = -4713;
108 static const int JDN_0_MONTH = wxDateTime::Nov;
109 static const int JDN_0_DAY = 24;
110
111 // the constants used for JDN calculations
112 static const long JDN_OFFSET = 32046l;
113 static const long DAYS_PER_5_MONTHS = 153l;
114 static const long DAYS_PER_4_YEARS = 1461l;
115 static const long DAYS_PER_400_YEARS = 146097l;
116
117 // ----------------------------------------------------------------------------
118 // globals
119 // ----------------------------------------------------------------------------
120
121 // a critical section is needed to protect GetTimeZone() static
122 // variable in MT case
123 #if wxUSE_THREADS
124 wxCriticalSection gs_critsectTimezone;
125 #endif // wxUSE_THREADS
126
127 // the symbolic names for date spans
128 wxDateSpan wxYear = wxDateSpan(1, 0, 0, 0);
129 wxDateSpan wxMonth = wxDateSpan(0, 1, 0, 0);
130 wxDateSpan wxWeek = wxDateSpan(0, 0, 1, 0);
131 wxDateSpan wxDay = wxDateSpan(0, 0, 0, 1);
132
133 // ----------------------------------------------------------------------------
134 // private functions
135 // ----------------------------------------------------------------------------
136
137 // get the number of days in the given month of the given year
138 static inline
139 wxDateTime::wxDateTime_t GetNumOfDaysInMonth(int year, wxDateTime::Month month)
140 {
141 // the number of days in month in Julian/Gregorian calendar: the first line
142 // is for normal years, the second one is for the leap ones
143 static wxDateTime::wxDateTime_t daysInMonth[2][MONTHS_IN_YEAR] =
144 {
145 { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
146 { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
147 };
148
149 return daysInMonth[wxDateTime::IsLeapYear(year)][month];
150 }
151
152 // ensure that the timezone variable is set by calling localtime
153 static int GetTimeZone()
154 {
155 // set to TRUE when the timezone is set
156 static bool s_timezoneSet = FALSE;
157
158 wxCRIT_SECT_LOCKER(lock, gs_critsectTimezone);
159
160 if ( !s_timezoneSet )
161 {
162 // just call localtime() instead of figuring out whether this system
163 // supports tzset(), _tzset() or something else
164 time_t t;
165 (void)localtime(&t);
166
167 s_timezoneSet = TRUE;
168 }
169
170 return (int)WX_TIMEZONE;
171 }
172
173 // return the integral part of the JDN for the midnight of the given date (to
174 // get the real JDN you need to add 0.5, this is, in fact, JDN of the
175 // noon of the previous day)
176 static long GetTruncatedJDN(wxDateTime::wxDateTime_t day,
177 wxDateTime::Month mon,
178 int year)
179 {
180 // CREDIT: code below is by Scott E. Lee (but bugs are mine)
181
182 // check the date validity
183 wxASSERT_MSG(
184 (year > JDN_0_YEAR) ||
185 ((year == JDN_0_YEAR) && (mon > JDN_0_MONTH)) ||
186 ((year == JDN_0_YEAR) && (mon == JDN_0_MONTH) && (day >= JDN_0_DAY)),
187 _T("date out of range - can't convert to JDN")
188 );
189
190 // make the year positive to avoid problems with negative numbers division
191 year += 4800;
192
193 // months are counted from March here
194 int month;
195 if ( mon >= wxDateTime::Mar )
196 {
197 month = mon - 2;
198 }
199 else
200 {
201 month = mon + 10;
202 year--;
203 }
204
205 // now we can simply add all the contributions together
206 return ((year / 100) * DAYS_PER_400_YEARS) / 4
207 + ((year % 100) * DAYS_PER_4_YEARS) / 4
208 + (month * DAYS_PER_5_MONTHS + 2) / 5
209 + day
210 - JDN_OFFSET;
211 }
212
213 // this function is a wrapper around strftime(3)
214 static wxString CallStrftime(const wxChar *format, const tm* tm)
215 {
216 wxChar buf[4096];
217 if ( !wxStrftime(buf, WXSIZEOF(buf), format, tm) )
218 {
219 // buffer is too small?
220 wxFAIL_MSG(_T("strftime() failed"));
221 }
222
223 return wxString(buf);
224 }
225
226 // if year and/or month have invalid values, replace them with the current ones
227 static void ReplaceDefaultYearMonthWithCurrent(int *year,
228 wxDateTime::Month *month)
229 {
230 struct tm *tmNow = NULL;
231
232 if ( *year == wxDateTime::Inv_Year )
233 {
234 tmNow = wxDateTime::GetTmNow();
235
236 *year = 1900 + tmNow->tm_year;
237 }
238
239 if ( *month == wxDateTime::Inv_Month )
240 {
241 if ( !tmNow )
242 tmNow = wxDateTime::GetTmNow();
243
244 *month = (wxDateTime::Month)tmNow->tm_mon;
245 }
246 }
247
248 // parsing helpers
249 // ---------------
250
251 // return the month if the string is a month name or Inv_Month otherwise
252 static wxDateTime::Month GetMonthFromName(const wxString& name)
253 {
254 wxDateTime::Month mon;
255 for ( mon = wxDateTime::Jan; mon < wxDateTime::Inv_Month; wxNextMonth(mon) )
256 {
257 // case-insensitive comparison with both abbreviated and not versions
258 if ( name.CmpNoCase(wxDateTime::GetMonthName(mon, TRUE)) ||
259 name.CmpNoCase(wxDateTime::GetMonthName(mon, FALSE)) )
260 {
261 break;
262 }
263 }
264
265 return mon;
266 }
267
268 // return the weekday if the string is a weekday name or Inv_WeekDay otherwise
269 static wxDateTime::WeekDay GetWeekDayFromName(const wxString& name)
270 {
271 wxDateTime::WeekDay wd;
272 for ( wd = wxDateTime::Sun; wd < wxDateTime::Inv_WeekDay; wxNextWDay(wd) )
273 {
274 // case-insensitive comparison with both abbreviated and not versions
275 if ( name.IsSameAs(wxDateTime::GetWeekDayName(wd, TRUE), FALSE) ||
276 name.IsSameAs(wxDateTime::GetWeekDayName(wd, FALSE), FALSE) )
277 {
278 break;
279 }
280 }
281
282 return wd;
283 }
284
285 // ============================================================================
286 // implementation of wxDateTime
287 // ============================================================================
288
289 // ----------------------------------------------------------------------------
290 // static data
291 // ----------------------------------------------------------------------------
292
293 wxDateTime::Country wxDateTime::ms_country = wxDateTime::Country_Unknown;
294 wxDateTime wxDateTime::ms_InvDateTime;
295
296 // ----------------------------------------------------------------------------
297 // struct Tm
298 // ----------------------------------------------------------------------------
299
300 wxDateTime::Tm::Tm()
301 {
302 year = (wxDateTime_t)wxDateTime::Inv_Year;
303 mon = wxDateTime::Inv_Month;
304 mday = 0;
305 hour = min = sec = msec = 0;
306 wday = wxDateTime::Inv_WeekDay;
307 }
308
309 wxDateTime::Tm::Tm(const struct tm& tm, const TimeZone& tz)
310 : m_tz(tz)
311 {
312 msec = 0;
313 sec = tm.tm_sec;
314 min = tm.tm_min;
315 hour = tm.tm_hour;
316 mday = tm.tm_mday;
317 mon = (wxDateTime::Month)tm.tm_mon;
318 year = 1900 + tm.tm_year;
319 wday = tm.tm_wday;
320 yday = tm.tm_yday;
321 }
322
323 bool wxDateTime::Tm::IsValid() const
324 {
325 // we allow for the leap seconds, although we don't use them (yet)
326 return (year != wxDateTime::Inv_Year) && (mon != wxDateTime::Inv_Month) &&
327 (mday <= GetNumOfDaysInMonth(year, mon)) &&
328 (hour < 24) && (min < 60) && (sec < 62) && (msec < 1000);
329 }
330
331 void wxDateTime::Tm::ComputeWeekDay()
332 {
333 // compute the week day from day/month/year: we use the dumbest algorithm
334 // possible: just compute our JDN and then use the (simple to derive)
335 // formula: weekday = (JDN + 1.5) % 7
336 wday = (wxDateTime::WeekDay)(GetTruncatedJDN(mday, mon, year) + 2) % 7;
337 }
338
339 void wxDateTime::Tm::AddMonths(int monDiff)
340 {
341 // normalize the months field
342 while ( monDiff < -mon )
343 {
344 year--;
345
346 monDiff += MONTHS_IN_YEAR;
347 }
348
349 while ( monDiff + mon > MONTHS_IN_YEAR )
350 {
351 year++;
352
353 monDiff -= MONTHS_IN_YEAR;
354 }
355
356 mon = (wxDateTime::Month)(mon + monDiff);
357
358 wxASSERT_MSG( mon >= 0 && mon < MONTHS_IN_YEAR, _T("logic error") );
359 }
360
361 void wxDateTime::Tm::AddDays(int dayDiff)
362 {
363 // normalize the days field
364 mday += dayDiff;
365 while ( mday < 1 )
366 {
367 AddMonths(-1);
368
369 mday += GetNumOfDaysInMonth(year, mon);
370 }
371
372 while ( mday > GetNumOfDaysInMonth(year, mon) )
373 {
374 mday -= GetNumOfDaysInMonth(year, mon);
375
376 AddMonths(1);
377 }
378
379 wxASSERT_MSG( mday > 0 && mday <= GetNumOfDaysInMonth(year, mon),
380 _T("logic error") );
381 }
382
383 // ----------------------------------------------------------------------------
384 // class TimeZone
385 // ----------------------------------------------------------------------------
386
387 wxDateTime::TimeZone::TimeZone(wxDateTime::TZ tz)
388 {
389 switch ( tz )
390 {
391 case wxDateTime::Local:
392 // get the offset from C RTL: it returns the difference GMT-local
393 // while we want to have the offset _from_ GMT, hence the '-'
394 m_offset = -GetTimeZone();
395 break;
396
397 case wxDateTime::GMT_12:
398 case wxDateTime::GMT_11:
399 case wxDateTime::GMT_10:
400 case wxDateTime::GMT_9:
401 case wxDateTime::GMT_8:
402 case wxDateTime::GMT_7:
403 case wxDateTime::GMT_6:
404 case wxDateTime::GMT_5:
405 case wxDateTime::GMT_4:
406 case wxDateTime::GMT_3:
407 case wxDateTime::GMT_2:
408 case wxDateTime::GMT_1:
409 m_offset = -3600*(wxDateTime::GMT0 - tz);
410 break;
411
412 case wxDateTime::GMT0:
413 case wxDateTime::GMT1:
414 case wxDateTime::GMT2:
415 case wxDateTime::GMT3:
416 case wxDateTime::GMT4:
417 case wxDateTime::GMT5:
418 case wxDateTime::GMT6:
419 case wxDateTime::GMT7:
420 case wxDateTime::GMT8:
421 case wxDateTime::GMT9:
422 case wxDateTime::GMT10:
423 case wxDateTime::GMT11:
424 case wxDateTime::GMT12:
425 m_offset = 3600*(tz - wxDateTime::GMT0);
426 break;
427
428 case wxDateTime::A_CST:
429 // Central Standard Time in use in Australia = UTC + 9.5
430 m_offset = 60l*(9*60 + 30);
431 break;
432
433 default:
434 wxFAIL_MSG( _T("unknown time zone") );
435 }
436 }
437
438 // ----------------------------------------------------------------------------
439 // static functions
440 // ----------------------------------------------------------------------------
441
442 /* static */
443 bool wxDateTime::IsLeapYear(int year, wxDateTime::Calendar cal)
444 {
445 if ( year == Inv_Year )
446 year = GetCurrentYear();
447
448 if ( cal == Gregorian )
449 {
450 // in Gregorian calendar leap years are those divisible by 4 except
451 // those divisible by 100 unless they're also divisible by 400
452 // (in some countries, like Russia and Greece, additional corrections
453 // exist, but they won't manifest themselves until 2700)
454 return (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0));
455 }
456 else if ( cal == Julian )
457 {
458 // in Julian calendar the rule is simpler
459 return year % 4 == 0;
460 }
461 else
462 {
463 wxFAIL_MSG(_T("unknown calendar"));
464
465 return FALSE;
466 }
467 }
468
469 /* static */
470 int wxDateTime::GetCentury(int year)
471 {
472 return year > 0 ? year / 100 : year / 100 - 1;
473 }
474
475 /* static */
476 int wxDateTime::ConvertYearToBC(int year)
477 {
478 // year 0 is BC 1
479 return year > 0 ? year : year - 1;
480 }
481
482 /* static */
483 int wxDateTime::GetCurrentYear(wxDateTime::Calendar cal)
484 {
485 switch ( cal )
486 {
487 case Gregorian:
488 return Now().GetYear();
489
490 case Julian:
491 wxFAIL_MSG(_T("TODO"));
492 break;
493
494 default:
495 wxFAIL_MSG(_T("unsupported calendar"));
496 break;
497 }
498
499 return Inv_Year;
500 }
501
502 /* static */
503 wxDateTime::Month wxDateTime::GetCurrentMonth(wxDateTime::Calendar cal)
504 {
505 switch ( cal )
506 {
507 case Gregorian:
508 return Now().GetMonth();
509 break;
510
511 case Julian:
512 wxFAIL_MSG(_T("TODO"));
513 break;
514
515 default:
516 wxFAIL_MSG(_T("unsupported calendar"));
517 break;
518 }
519
520 return Inv_Month;
521 }
522
523 /* static */
524 wxDateTime::wxDateTime_t wxDateTime::GetNumberOfDays(int year, Calendar cal)
525 {
526 if ( year == Inv_Year )
527 {
528 // take the current year if none given
529 year = GetCurrentYear();
530 }
531
532 switch ( cal )
533 {
534 case Gregorian:
535 case Julian:
536 return IsLeapYear(year) ? 366 : 365;
537 break;
538
539 default:
540 wxFAIL_MSG(_T("unsupported calendar"));
541 break;
542 }
543
544 return 0;
545 }
546
547 /* static */
548 wxDateTime::wxDateTime_t wxDateTime::GetNumberOfDays(wxDateTime::Month month,
549 int year,
550 wxDateTime::Calendar cal)
551 {
552 wxCHECK_MSG( month < MONTHS_IN_YEAR, 0, _T("invalid month") );
553
554 if ( cal == Gregorian || cal == Julian )
555 {
556 if ( year == Inv_Year )
557 {
558 // take the current year if none given
559 year = GetCurrentYear();
560 }
561
562 return GetNumOfDaysInMonth(year, month);
563 }
564 else
565 {
566 wxFAIL_MSG(_T("unsupported calendar"));
567
568 return 0;
569 }
570 }
571
572 /* static */
573 wxString wxDateTime::GetMonthName(wxDateTime::Month month, bool abbr)
574 {
575 wxCHECK_MSG( month != Inv_Month, _T(""), _T("invalid month") );
576
577 // notice that we must set all the fields to avoid confusing libc (GNU one
578 // gets confused to a crash if we don't do this)
579 tm tm;
580 tm.tm_hour =
581 tm.tm_min =
582 tm.tm_sec =
583 tm.tm_wday =
584 tm.tm_yday = 0;
585 tm.tm_mday = 1;
586 tm.tm_mon = month;
587 tm.tm_year = 76; // any year will do
588 tm.tm_isdst = -1;
589
590 return CallStrftime(abbr ? _T("%b") : _T("%B"), &tm);
591 }
592
593 /* static */
594 wxString wxDateTime::GetWeekDayName(wxDateTime::WeekDay wday, bool abbr)
595 {
596 wxCHECK_MSG( wday != Inv_WeekDay, _T(""), _T("invalid weekday") );
597
598 // take some arbitrary Sunday
599 tm tm = { 0, 0, 0, 28, Nov, 99 };
600
601 // and offset it by the number of days needed to get the correct wday
602 tm.tm_mday += wday;
603
604 // call mktime() to normalize it...
605 (void)mktime(&tm);
606
607 // ... and call strftime()
608 return CallStrftime(abbr ? _T("%a") : _T("%A"), &tm);
609 }
610
611 // ----------------------------------------------------------------------------
612 // Country stuff: date calculations depend on the country (DST, work days,
613 // ...), so we need to know which rules to follow.
614 // ----------------------------------------------------------------------------
615
616 /* static */
617 wxDateTime::Country wxDateTime::GetCountry()
618 {
619 if ( ms_country == Country_Unknown )
620 {
621 // try to guess from the time zone name
622 time_t t = time(NULL);
623 struct tm *tm = localtime(&t);
624
625 wxString tz = CallStrftime(_T("%Z"), tm);
626 if ( tz == _T("WET") || tz == _T("WEST") )
627 {
628 ms_country = UK;
629 }
630 else if ( tz == _T("CET") || tz == _T("CEST") )
631 {
632 ms_country = Country_EEC;
633 }
634 else if ( tz == _T("MSK") || tz == _T("MSD") )
635 {
636 ms_country = Russia;
637 }
638 else if ( tz == _T("AST") || tz == _T("ADT") ||
639 tz == _T("EST") || tz == _T("EDT") ||
640 tz == _T("CST") || tz == _T("CDT") ||
641 tz == _T("MST") || tz == _T("MDT") ||
642 tz == _T("PST") || tz == _T("PDT") )
643 {
644 ms_country = USA;
645 }
646 else
647 {
648 // well, choose a default one
649 ms_country = USA;
650 }
651 }
652
653 return ms_country;
654 }
655
656 /* static */
657 void wxDateTime::SetCountry(wxDateTime::Country country)
658 {
659 ms_country = country;
660 }
661
662 /* static */
663 bool wxDateTime::IsWestEuropeanCountry(Country country)
664 {
665 if ( country == Country_Default )
666 {
667 country = GetCountry();
668 }
669
670 return (Country_WesternEurope_Start <= country) &&
671 (country <= Country_WesternEurope_End);
672 }
673
674 // ----------------------------------------------------------------------------
675 // DST calculations: we use 3 different rules for the West European countries,
676 // USA and for the rest of the world. This is undoubtedly false for many
677 // countries, but I lack the necessary info (and the time to gather it),
678 // please add the other rules here!
679 // ----------------------------------------------------------------------------
680
681 /* static */
682 bool wxDateTime::IsDSTApplicable(int year, Country country)
683 {
684 if ( year == Inv_Year )
685 {
686 // take the current year if none given
687 year = GetCurrentYear();
688 }
689
690 if ( country == Country_Default )
691 {
692 country = GetCountry();
693 }
694
695 switch ( country )
696 {
697 case USA:
698 case UK:
699 // DST was first observed in the US and UK during WWI, reused
700 // during WWII and used again since 1966
701 return year >= 1966 ||
702 (year >= 1942 && year <= 1945) ||
703 (year == 1918 || year == 1919);
704
705 default:
706 // assume that it started after WWII
707 return year > 1950;
708 }
709 }
710
711 /* static */
712 wxDateTime wxDateTime::GetBeginDST(int year, Country country)
713 {
714 if ( year == Inv_Year )
715 {
716 // take the current year if none given
717 year = GetCurrentYear();
718 }
719
720 if ( country == Country_Default )
721 {
722 country = GetCountry();
723 }
724
725 if ( !IsDSTApplicable(year, country) )
726 {
727 return ms_InvDateTime;
728 }
729
730 wxDateTime dt;
731
732 if ( IsWestEuropeanCountry(country) || (country == Russia) )
733 {
734 // DST begins at 1 a.m. GMT on the last Sunday of March
735 if ( !dt.SetToLastWeekDay(Sun, Mar, year) )
736 {
737 // weird...
738 wxFAIL_MSG( _T("no last Sunday in March?") );
739 }
740
741 dt += wxTimeSpan::Hours(1);
742
743 dt.MakeGMT();
744 }
745 else switch ( country )
746 {
747 case USA:
748 switch ( year )
749 {
750 case 1918:
751 case 1919:
752 // don't know for sure - assume it was in effect all year
753
754 case 1943:
755 case 1944:
756 case 1945:
757 dt.Set(1, Jan, year);
758 break;
759
760 case 1942:
761 // DST was installed Feb 2, 1942 by the Congress
762 dt.Set(2, Feb, year);
763 break;
764
765 // Oil embargo changed the DST period in the US
766 case 1974:
767 dt.Set(6, Jan, 1974);
768 break;
769
770 case 1975:
771 dt.Set(23, Feb, 1975);
772 break;
773
774 default:
775 // before 1986, DST begun on the last Sunday of April, but
776 // in 1986 Reagan changed it to begin at 2 a.m. of the
777 // first Sunday in April
778 if ( year < 1986 )
779 {
780 if ( !dt.SetToLastWeekDay(Sun, Apr, year) )
781 {
782 // weird...
783 wxFAIL_MSG( _T("no first Sunday in April?") );
784 }
785 }
786 else
787 {
788 if ( !dt.SetToWeekDay(Sun, 1, Apr, year) )
789 {
790 // weird...
791 wxFAIL_MSG( _T("no first Sunday in April?") );
792 }
793 }
794
795 dt += wxTimeSpan::Hours(2);
796
797 // TODO what about timezone??
798 }
799
800 break;
801
802 default:
803 // assume Mar 30 as the start of the DST for the rest of the world
804 // - totally bogus, of course
805 dt.Set(30, Mar, year);
806 }
807
808 return dt;
809 }
810
811 /* static */
812 wxDateTime wxDateTime::GetEndDST(int year, Country country)
813 {
814 if ( year == Inv_Year )
815 {
816 // take the current year if none given
817 year = GetCurrentYear();
818 }
819
820 if ( country == Country_Default )
821 {
822 country = GetCountry();
823 }
824
825 if ( !IsDSTApplicable(year, country) )
826 {
827 return ms_InvDateTime;
828 }
829
830 wxDateTime dt;
831
832 if ( IsWestEuropeanCountry(country) || (country == Russia) )
833 {
834 // DST ends at 1 a.m. GMT on the last Sunday of October
835 if ( !dt.SetToLastWeekDay(Sun, Oct, year) )
836 {
837 // weirder and weirder...
838 wxFAIL_MSG( _T("no last Sunday in October?") );
839 }
840
841 dt += wxTimeSpan::Hours(1);
842
843 dt.MakeGMT();
844 }
845 else switch ( country )
846 {
847 case USA:
848 switch ( year )
849 {
850 case 1918:
851 case 1919:
852 // don't know for sure - assume it was in effect all year
853
854 case 1943:
855 case 1944:
856 dt.Set(31, Dec, year);
857 break;
858
859 case 1945:
860 // the time was reset after the end of the WWII
861 dt.Set(30, Sep, year);
862 break;
863
864 default:
865 // DST ends at 2 a.m. on the last Sunday of October
866 if ( !dt.SetToLastWeekDay(Sun, Oct, year) )
867 {
868 // weirder and weirder...
869 wxFAIL_MSG( _T("no last Sunday in October?") );
870 }
871
872 dt += wxTimeSpan::Hours(2);
873
874 // TODO what about timezone??
875 }
876 break;
877
878 default:
879 // assume October 26th as the end of the DST - totally bogus too
880 dt.Set(26, Oct, year);
881 }
882
883 return dt;
884 }
885
886 // ----------------------------------------------------------------------------
887 // constructors and assignment operators
888 // ----------------------------------------------------------------------------
889
890 // the values in the tm structure contain the local time
891 wxDateTime& wxDateTime::Set(const struct tm& tm)
892 {
893 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
894
895 struct tm tm2(tm);
896 time_t timet = mktime(&tm2);
897
898 if ( timet == (time_t)-1 )
899 {
900 // mktime() rather unintuitively fails for Jan 1, 1970 if the hour is
901 // less than timezone - try to make it work for this case
902 if ( tm2.tm_year == 70 && tm2.tm_mon == 0 && tm2.tm_mday == 1 )
903 {
904 // add timezone to make sure that date is in range
905 tm2.tm_sec -= GetTimeZone();
906
907 timet = mktime(&tm2);
908 if ( timet != (time_t)-1 )
909 {
910 timet += GetTimeZone();
911
912 return Set(timet);
913 }
914 }
915
916 wxFAIL_MSG( _T("mktime() failed") );
917
918 return ms_InvDateTime;
919 }
920 else
921 {
922 return Set(timet);
923 }
924 }
925
926 wxDateTime& wxDateTime::Set(wxDateTime_t hour,
927 wxDateTime_t minute,
928 wxDateTime_t second,
929 wxDateTime_t millisec)
930 {
931 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
932
933 // we allow seconds to be 61 to account for the leap seconds, even if we
934 // don't use them really
935 wxCHECK_MSG( hour < 24 && second < 62 && minute < 60 && millisec < 1000,
936 ms_InvDateTime,
937 _T("Invalid time in wxDateTime::Set()") );
938
939 // get the current date from system
940 time_t timet = GetTimeNow();
941 struct tm *tm = localtime(&timet);
942
943 wxCHECK_MSG( tm, ms_InvDateTime, _T("localtime() failed") );
944
945 // adjust the time
946 tm->tm_hour = hour;
947 tm->tm_min = minute;
948 tm->tm_sec = second;
949
950 (void)Set(*tm);
951
952 // and finally adjust milliseconds
953 return SetMillisecond(millisec);
954 }
955
956 wxDateTime& wxDateTime::Set(wxDateTime_t day,
957 Month month,
958 int year,
959 wxDateTime_t hour,
960 wxDateTime_t minute,
961 wxDateTime_t second,
962 wxDateTime_t millisec)
963 {
964 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
965
966 wxCHECK_MSG( hour < 24 && second < 62 && minute < 60 && millisec < 1000,
967 ms_InvDateTime,
968 _T("Invalid time in wxDateTime::Set()") );
969
970 ReplaceDefaultYearMonthWithCurrent(&year, &month);
971
972 wxCHECK_MSG( (0 < day) && (day <= GetNumberOfDays(month, year)),
973 ms_InvDateTime,
974 _T("Invalid date in wxDateTime::Set()") );
975
976 // the range of time_t type (inclusive)
977 static const int yearMinInRange = 1970;
978 static const int yearMaxInRange = 2037;
979
980 // test only the year instead of testing for the exact end of the Unix
981 // time_t range - it doesn't bring anything to do more precise checks
982 if ( year >= yearMinInRange && year <= yearMaxInRange )
983 {
984 // use the standard library version if the date is in range - this is
985 // probably more efficient than our code
986 struct tm tm;
987 tm.tm_year = year - 1900;
988 tm.tm_mon = month;
989 tm.tm_mday = day;
990 tm.tm_hour = hour;
991 tm.tm_min = minute;
992 tm.tm_sec = second;
993 tm.tm_isdst = -1; // mktime() will guess it
994
995 (void)Set(tm);
996
997 // and finally adjust milliseconds
998 return SetMillisecond(millisec);
999 }
1000 else
1001 {
1002 // do time calculations ourselves: we want to calculate the number of
1003 // milliseconds between the given date and the epoch
1004
1005 // get the JDN for the midnight of this day
1006 m_time = GetTruncatedJDN(day, month, year);
1007 m_time -= EPOCH_JDN;
1008 m_time *= SECONDS_PER_DAY * TIME_T_FACTOR;
1009
1010 // JDN corresponds to GMT, we take localtime
1011 Add(wxTimeSpan(hour, minute, second + GetTimeZone(), millisec));
1012 }
1013
1014 return *this;
1015 }
1016
1017 wxDateTime& wxDateTime::Set(double jdn)
1018 {
1019 // so that m_time will be 0 for the midnight of Jan 1, 1970 which is jdn
1020 // EPOCH_JDN + 0.5
1021 jdn -= EPOCH_JDN + 0.5;
1022
1023 jdn *= MILLISECONDS_PER_DAY;
1024
1025 m_time = jdn;
1026
1027 return *this;
1028 }
1029
1030 // ----------------------------------------------------------------------------
1031 // time_t <-> broken down time conversions
1032 // ----------------------------------------------------------------------------
1033
1034 wxDateTime::Tm wxDateTime::GetTm(const TimeZone& tz) const
1035 {
1036 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1037
1038 time_t time = GetTicks();
1039 if ( time != (time_t)-1 )
1040 {
1041 // use C RTL functions
1042 tm *tm;
1043 if ( tz.GetOffset() == -GetTimeZone() )
1044 {
1045 // we are working with local time
1046 tm = localtime(&time);
1047
1048 // should never happen
1049 wxCHECK_MSG( tm, Tm(), _T("gmtime() failed") );
1050 }
1051 else
1052 {
1053 time += tz.GetOffset();
1054 if ( time >= 0 )
1055 {
1056 tm = gmtime(&time);
1057
1058 // should never happen
1059 wxCHECK_MSG( tm, Tm(), _T("gmtime() failed") );
1060 }
1061 else
1062 {
1063 tm = (struct tm *)NULL;
1064 }
1065 }
1066
1067 if ( tm )
1068 {
1069 return Tm(*tm, tz);
1070 }
1071 //else: use generic code below
1072 }
1073
1074 // remember the time and do the calculations with the date only - this
1075 // eliminates rounding errors of the floating point arithmetics
1076
1077 wxLongLong timeMidnight = m_time + tz.GetOffset() * 1000;
1078
1079 long timeOnly = (timeMidnight % MILLISECONDS_PER_DAY).ToLong();
1080
1081 // we want to always have positive time and timeMidnight to be really
1082 // the midnight before it
1083 if ( timeOnly < 0 )
1084 {
1085 timeOnly = MILLISECONDS_PER_DAY + timeOnly;
1086 }
1087
1088 timeMidnight -= timeOnly;
1089
1090 // calculate the Gregorian date from JDN for the midnight of our date:
1091 // this will yield day, month (in 1..12 range) and year
1092
1093 // actually, this is the JDN for the noon of the previous day
1094 long jdn = (timeMidnight / MILLISECONDS_PER_DAY).ToLong() + EPOCH_JDN;
1095
1096 // CREDIT: code below is by Scott E. Lee (but bugs are mine)
1097
1098 wxASSERT_MSG( jdn > -2, _T("JDN out of range") );
1099
1100 // calculate the century
1101 int temp = (jdn + JDN_OFFSET) * 4 - 1;
1102 int century = temp / DAYS_PER_400_YEARS;
1103
1104 // then the year and day of year (1 <= dayOfYear <= 366)
1105 temp = ((temp % DAYS_PER_400_YEARS) / 4) * 4 + 3;
1106 int year = (century * 100) + (temp / DAYS_PER_4_YEARS);
1107 int dayOfYear = (temp % DAYS_PER_4_YEARS) / 4 + 1;
1108
1109 // and finally the month and day of the month
1110 temp = dayOfYear * 5 - 3;
1111 int month = temp / DAYS_PER_5_MONTHS;
1112 int day = (temp % DAYS_PER_5_MONTHS) / 5 + 1;
1113
1114 // month is counted from March - convert to normal
1115 if ( month < 10 )
1116 {
1117 month += 3;
1118 }
1119 else
1120 {
1121 year += 1;
1122 month -= 9;
1123 }
1124
1125 // year is offset by 4800
1126 year -= 4800;
1127
1128 // check that the algorithm gave us something reasonable
1129 wxASSERT_MSG( (0 < month) && (month <= 12), _T("invalid month") );
1130 wxASSERT_MSG( (1 <= day) && (day < 32), _T("invalid day") );
1131 wxASSERT_MSG( (INT_MIN <= year) && (year <= INT_MAX),
1132 _T("year range overflow") );
1133
1134 // construct Tm from these values
1135 Tm tm;
1136 tm.year = (int)year;
1137 tm.mon = (Month)(month - 1); // algorithm yields 1 for January, not 0
1138 tm.mday = (wxDateTime_t)day;
1139 tm.msec = timeOnly % 1000;
1140 timeOnly -= tm.msec;
1141 timeOnly /= 1000; // now we have time in seconds
1142
1143 tm.sec = timeOnly % 60;
1144 timeOnly -= tm.sec;
1145 timeOnly /= 60; // now we have time in minutes
1146
1147 tm.min = timeOnly % 60;
1148 timeOnly -= tm.min;
1149
1150 tm.hour = timeOnly / 60;
1151
1152 return tm;
1153 }
1154
1155 wxDateTime& wxDateTime::SetYear(int year)
1156 {
1157 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1158
1159 Tm tm(GetTm());
1160 tm.year = year;
1161 Set(tm);
1162
1163 return *this;
1164 }
1165
1166 wxDateTime& wxDateTime::SetMonth(Month month)
1167 {
1168 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1169
1170 Tm tm(GetTm());
1171 tm.mon = month;
1172 Set(tm);
1173
1174 return *this;
1175 }
1176
1177 wxDateTime& wxDateTime::SetDay(wxDateTime_t mday)
1178 {
1179 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1180
1181 Tm tm(GetTm());
1182 tm.mday = mday;
1183 Set(tm);
1184
1185 return *this;
1186 }
1187
1188 wxDateTime& wxDateTime::SetHour(wxDateTime_t hour)
1189 {
1190 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1191
1192 Tm tm(GetTm());
1193 tm.hour = hour;
1194 Set(tm);
1195
1196 return *this;
1197 }
1198
1199 wxDateTime& wxDateTime::SetMinute(wxDateTime_t min)
1200 {
1201 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1202
1203 Tm tm(GetTm());
1204 tm.min = min;
1205 Set(tm);
1206
1207 return *this;
1208 }
1209
1210 wxDateTime& wxDateTime::SetSecond(wxDateTime_t sec)
1211 {
1212 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1213
1214 Tm tm(GetTm());
1215 tm.sec = sec;
1216 Set(tm);
1217
1218 return *this;
1219 }
1220
1221 wxDateTime& wxDateTime::SetMillisecond(wxDateTime_t millisecond)
1222 {
1223 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1224
1225 // we don't need to use GetTm() for this one
1226 m_time -= m_time % 1000l;
1227 m_time += millisecond;
1228
1229 return *this;
1230 }
1231
1232 // ----------------------------------------------------------------------------
1233 // wxDateTime arithmetics
1234 // ----------------------------------------------------------------------------
1235
1236 wxDateTime& wxDateTime::Add(const wxDateSpan& diff)
1237 {
1238 Tm tm(GetTm());
1239
1240 tm.year += diff.GetYears();
1241 tm.AddMonths(diff.GetMonths());
1242 tm.AddDays(diff.GetTotalDays());
1243
1244 Set(tm);
1245
1246 return *this;
1247 }
1248
1249 // ----------------------------------------------------------------------------
1250 // Weekday and monthday stuff
1251 // ----------------------------------------------------------------------------
1252
1253 wxDateTime& wxDateTime::SetToLastMonthDay(Month month,
1254 int year)
1255 {
1256 // take the current month/year if none specified
1257 ReplaceDefaultYearMonthWithCurrent(&year, &month);
1258
1259 return Set(GetNumOfDaysInMonth(year, month), month, year);
1260 }
1261
1262 wxDateTime& wxDateTime::SetToWeekDayInSameWeek(WeekDay weekday)
1263 {
1264 wxCHECK_MSG( weekday != Inv_WeekDay, ms_InvDateTime, _T("invalid weekday") );
1265
1266 WeekDay wdayThis = GetWeekDay();
1267 if ( weekday == wdayThis )
1268 {
1269 // nothing to do
1270 return *this;
1271 }
1272 else if ( weekday < wdayThis )
1273 {
1274 return Substract(wxTimeSpan::Days(wdayThis - weekday));
1275 }
1276 else // weekday > wdayThis
1277 {
1278 return Add(wxTimeSpan::Days(weekday - wdayThis));
1279 }
1280 }
1281
1282 wxDateTime& wxDateTime::SetToNextWeekDay(WeekDay weekday)
1283 {
1284 wxCHECK_MSG( weekday != Inv_WeekDay, ms_InvDateTime, _T("invalid weekday") );
1285
1286 int diff;
1287 WeekDay wdayThis = GetWeekDay();
1288 if ( weekday == wdayThis )
1289 {
1290 // nothing to do
1291 return *this;
1292 }
1293 else if ( weekday < wdayThis )
1294 {
1295 // need to advance a week
1296 diff = 7 - (wdayThis - weekday);
1297 }
1298 else // weekday > wdayThis
1299 {
1300 diff = weekday - wdayThis;
1301 }
1302
1303 return Add(wxTimeSpan::Days(diff));
1304 }
1305
1306 wxDateTime& wxDateTime::SetToPrevWeekDay(WeekDay weekday)
1307 {
1308 wxCHECK_MSG( weekday != Inv_WeekDay, ms_InvDateTime, _T("invalid weekday") );
1309
1310 int diff;
1311 WeekDay wdayThis = GetWeekDay();
1312 if ( weekday == wdayThis )
1313 {
1314 // nothing to do
1315 return *this;
1316 }
1317 else if ( weekday > wdayThis )
1318 {
1319 // need to go to previous week
1320 diff = 7 - (weekday - wdayThis);
1321 }
1322 else // weekday < wdayThis
1323 {
1324 diff = wdayThis - weekday;
1325 }
1326
1327 return Substract(wxTimeSpan::Days(diff));
1328 }
1329
1330 bool wxDateTime::SetToWeekDay(WeekDay weekday,
1331 int n,
1332 Month month,
1333 int year)
1334 {
1335 wxCHECK_MSG( weekday != Inv_WeekDay, FALSE, _T("invalid weekday") );
1336
1337 // we don't check explicitly that -5 <= n <= 5 because we will return FALSE
1338 // anyhow in such case - but may be should still give an assert for it?
1339
1340 // take the current month/year if none specified
1341 ReplaceDefaultYearMonthWithCurrent(&year, &month);
1342
1343 wxDateTime dt;
1344
1345 // TODO this probably could be optimised somehow...
1346
1347 if ( n > 0 )
1348 {
1349 // get the first day of the month
1350 dt.Set(1, month, year);
1351
1352 // get its wday
1353 WeekDay wdayFirst = dt.GetWeekDay();
1354
1355 // go to the first weekday of the month
1356 int diff = weekday - wdayFirst;
1357 if ( diff < 0 )
1358 diff += 7;
1359
1360 // add advance n-1 weeks more
1361 diff += 7*(n - 1);
1362
1363 dt += wxDateSpan::Days(diff);
1364 }
1365 else // count from the end of the month
1366 {
1367 // get the last day of the month
1368 dt.SetToLastMonthDay(month, year);
1369
1370 // get its wday
1371 WeekDay wdayLast = dt.GetWeekDay();
1372
1373 // go to the last weekday of the month
1374 int diff = wdayLast - weekday;
1375 if ( diff < 0 )
1376 diff += 7;
1377
1378 // and rewind n-1 weeks from there
1379 diff += 7*(-n - 1);
1380
1381 dt -= wxDateSpan::Days(diff);
1382 }
1383
1384 // check that it is still in the same month
1385 if ( dt.GetMonth() == month )
1386 {
1387 *this = dt;
1388
1389 return TRUE;
1390 }
1391 else
1392 {
1393 // no such day in this month
1394 return FALSE;
1395 }
1396 }
1397
1398 wxDateTime::wxDateTime_t wxDateTime::GetDayOfYear(const TimeZone& tz) const
1399 {
1400 // this array contains the cumulated number of days in all previous months
1401 // for normal and leap years
1402 static const wxDateTime_t cumulatedDays[2][MONTHS_IN_YEAR] =
1403 {
1404 { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 },
1405 { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 }
1406 };
1407
1408 Tm tm(GetTm(tz));
1409
1410 return cumulatedDays[IsLeapYear(tm.year)][tm.mon] + tm.mday;
1411 }
1412
1413 wxDateTime::wxDateTime_t wxDateTime::GetWeekOfYear(const TimeZone& tz) const
1414 {
1415 #if 0
1416 // the first week of the year is the one which contains Jan, 4 (according
1417 // to ISO standard rule), so the year day N0 = 4 + 7*W always lies in the
1418 // week W+1. As any day N = 7*W + 4 + (N - 4)%7, it lies in the same week
1419 // as N0 or in the next one.
1420
1421 // TODO this surely may be optimized - I got confused while writing it
1422
1423 wxDateTime_t nDayInYear = GetDayOfYear(tz);
1424
1425 // the week day of the day lying in the first week
1426 WeekDay wdayStart = wxDateTime(4, Jan, GetYear()).GetWeekDay();
1427
1428 wxDateTime_t week = (nDayInYear - 4) / 7 + 1;
1429
1430 // notice that Sunday shoould be counted as 7, not 0 here!
1431 if ( ((nDayInYear - 4) % 7) + (!wdayStart ? 7 : wdayStart) > 7 )
1432 {
1433 week++;
1434 }
1435
1436 return week;
1437 #else // this seems to be a bit simpler and I believe is also correct
1438 return (WeekDay)((GetDayOfYear() - (GetWeekDay() - 1 + 7) % 7 + 7) / 7);
1439 #endif // 0/1
1440 }
1441
1442 wxDateTime::wxDateTime_t wxDateTime::GetWeekOfMonth(const TimeZone& tz) const
1443 {
1444 size_t nWeek = 0;
1445
1446 wxDateTime dt(*this);
1447 do
1448 {
1449 nWeek++;
1450
1451 dt -= wxTimeSpan::Week();
1452 }
1453 while ( dt.GetMonth(tz) == GetMonth(tz) );
1454
1455 return nWeek;
1456 }
1457
1458 // ----------------------------------------------------------------------------
1459 // Julian day number conversion and related stuff
1460 // ----------------------------------------------------------------------------
1461
1462 double wxDateTime::GetJulianDayNumber() const
1463 {
1464 // JDN are always expressed for the GMT dates
1465 Tm tm(ToTimezone(GMT0).GetTm(GMT0));
1466
1467 double result = GetTruncatedJDN(tm.mday, tm.mon, tm.year);
1468
1469 // add the part GetTruncatedJDN() neglected
1470 result += 0.5;
1471
1472 // and now add the time: 86400 sec = 1 JDN
1473 return result + ((double)(60*(60*tm.hour + tm.min) + tm.sec)) / 86400;
1474 }
1475
1476 double wxDateTime::GetRataDie() const
1477 {
1478 // March 1 of the year 0 is Rata Die day -306 and JDN 1721119.5
1479 return GetJulianDayNumber() - 1721119.5 - 306;
1480 }
1481
1482 // ----------------------------------------------------------------------------
1483 // timezone and DST stuff
1484 // ----------------------------------------------------------------------------
1485
1486 int wxDateTime::IsDST(wxDateTime::Country country) const
1487 {
1488 wxCHECK_MSG( country == Country_Default, -1,
1489 _T("country support not implemented") );
1490
1491 // use the C RTL for the dates in the standard range
1492 time_t timet = GetTicks();
1493 if ( timet != (time_t)-1 )
1494 {
1495 tm *tm = localtime(&timet);
1496
1497 wxCHECK_MSG( tm, -1, _T("localtime() failed") );
1498
1499 return tm->tm_isdst;
1500 }
1501 else
1502 {
1503 int year = GetYear();
1504
1505 if ( !IsDSTApplicable(year, country) )
1506 {
1507 // no DST time in this year in this country
1508 return -1;
1509 }
1510
1511 return IsBetween(GetBeginDST(year, country), GetEndDST(year, country));
1512 }
1513 }
1514
1515 wxDateTime& wxDateTime::MakeTimezone(const TimeZone& tz)
1516 {
1517 int secDiff = GetTimeZone() + tz.GetOffset();
1518
1519 // we need to know whether DST is or not in effect for this date
1520 if ( IsDST() == 1 )
1521 {
1522 // FIXME we assume that the DST is always shifted by 1 hour
1523 secDiff -= 3600;
1524 }
1525
1526 return Substract(wxTimeSpan::Seconds(secDiff));
1527 }
1528
1529 // ----------------------------------------------------------------------------
1530 // wxDateTime to/from text representations
1531 // ----------------------------------------------------------------------------
1532
1533 wxString wxDateTime::Format(const wxChar *format, const TimeZone& tz) const
1534 {
1535 wxCHECK_MSG( format, _T(""), _T("NULL format in wxDateTime::Format") );
1536
1537 time_t time = GetTicks();
1538 if ( time != (time_t)-1 )
1539 {
1540 // use strftime()
1541 tm *tm;
1542 if ( tz.GetOffset() == -GetTimeZone() )
1543 {
1544 // we are working with local time
1545 tm = localtime(&time);
1546
1547 // should never happen
1548 wxCHECK_MSG( tm, wxEmptyString, _T("localtime() failed") );
1549 }
1550 else
1551 {
1552 time += tz.GetOffset();
1553
1554 if ( time >= 0 )
1555 {
1556 tm = gmtime(&time);
1557
1558 // should never happen
1559 wxCHECK_MSG( tm, wxEmptyString, _T("gmtime() failed") );
1560 }
1561 else
1562 {
1563 tm = (struct tm *)NULL;
1564 }
1565 }
1566
1567 if ( tm )
1568 {
1569 return CallStrftime(format, tm);
1570 }
1571 //else: use generic code below
1572 }
1573
1574 // we only parse ANSI C format specifications here, no POSIX 2
1575 // complications, no GNU extensions
1576 Tm tm = GetTm(tz);
1577
1578 // used for calls to strftime() when we only deal with time
1579 struct tm tmTimeOnly;
1580 tmTimeOnly.tm_hour = tm.hour;
1581 tmTimeOnly.tm_min = tm.min;
1582 tmTimeOnly.tm_sec = tm.sec;
1583 tmTimeOnly.tm_wday = 0;
1584 tmTimeOnly.tm_yday = 0;
1585 tmTimeOnly.tm_mday = 1; // any date will do
1586 tmTimeOnly.tm_mon = 0;
1587 tmTimeOnly.tm_year = 76;
1588 tmTimeOnly.tm_isdst = 0; // no DST, we adjust for tz ourselves
1589
1590 wxString tmp, res;
1591 for ( const wxChar *p = format; *p; p++ )
1592 {
1593 if ( *p != _T('%') )
1594 {
1595 // copy as is
1596 res += *p;
1597
1598 continue;
1599 }
1600
1601 // start of the format specification
1602 switch ( *++p )
1603 {
1604 case _T('a'): // a weekday name
1605 case _T('A'):
1606 // second parameter should be TRUE for abbreviated names
1607 res += GetWeekDayName(tm.GetWeekDay(), *p == _T('a'));
1608 break;
1609
1610 case _T('b'): // a month name
1611 case _T('B'):
1612 // second parameter should be TRUE for abbreviated names
1613 res += GetMonthName(tm.mon, *p == _T('b'));
1614 break;
1615
1616 case _T('c'): // locale default date and time representation
1617 case _T('x'): // locale default date representation
1618 //
1619 // the problem: there is no way to know what do these format
1620 // specifications correspond to for the current locale.
1621 //
1622 // the solution: use a hack and still use strftime(): first
1623 // find the YEAR which is a year in the strftime() range (1970
1624 // - 2038) whose Jan 1 falls on the same week day as the Jan 1
1625 // of the real year. Then make a copy of the format and
1626 // replace all occurences of YEAR in it with some unique
1627 // string not appearing anywhere else in it, then use
1628 // strftime() to format the date in year YEAR and then replace
1629 // YEAR back by the real year and the unique replacement
1630 // string back with YEAR. Notice that "all occurences of YEAR"
1631 // means all occurences of 4 digit as well as 2 digit form!
1632 //
1633 // the bugs: we assume that neither of %c nor %x contains any
1634 // fields which may change between the YEAR and real year. For
1635 // example, the week number (%U, %W) and the day number (%j)
1636 // will change if one of these years is leap and the other one
1637 // is not!
1638 {
1639 // find the YEAR: normally, for any year X, Jan 1 or the
1640 // year X + 28 is the same weekday as Jan 1 of X (because
1641 // the weekday advances by 1 for each normal X and by 2
1642 // for each leap X, hence by 5 every 4 years or by 35
1643 // which is 0 mod 7 every 28 years) but this rule breaks
1644 // down if there are years between X and Y which are
1645 // divisible by 4 but not leap (i.e. divisible by 100 but
1646 // not 400), hence the correction.
1647
1648 int yearReal = GetYear(tz);
1649 int mod28 = yearReal % 28;
1650
1651 // be careful to not go too far - we risk to leave the
1652 // supported range
1653 int year;
1654 if ( mod28 < 10 )
1655 {
1656 year = 1988 + mod28; // 1988 == 0 (mod 28)
1657 }
1658 else
1659 {
1660 year = 1970 + mod28 - 10; // 1970 == 10 (mod 28)
1661 }
1662
1663 int nCentury = year / 100,
1664 nCenturyReal = yearReal / 100;
1665
1666 // need to adjust for the years divisble by 400 which are
1667 // not leap but are counted like leap ones if we just take
1668 // the number of centuries in between for nLostWeekDays
1669 int nLostWeekDays = (nCentury - nCenturyReal) -
1670 (nCentury / 4 - nCenturyReal / 4);
1671
1672 // we have to gain back the "lost" weekdays: note that the
1673 // effect of this loop is to not do anything to
1674 // nLostWeekDays (which we won't use any more), but to
1675 // (indirectly) set the year correctly
1676 while ( (nLostWeekDays % 7) != 0 )
1677 {
1678 nLostWeekDays += year++ % 4 ? 1 : 2;
1679 }
1680
1681 // at any rate, we couldn't go further than 1988 + 9 + 28!
1682 wxASSERT_MSG( year < 2030,
1683 _T("logic error in wxDateTime::Format") );
1684
1685 wxString strYear, strYear2;
1686 strYear.Printf(_T("%d"), year);
1687 strYear2.Printf(_T("%d"), year % 100);
1688
1689 // find two strings not occuring in format (this is surely
1690 // not optimal way of doing it... improvements welcome!)
1691 wxString fmt = format;
1692 wxString replacement = (wxChar)-1;
1693 while ( fmt.Find(replacement) != wxNOT_FOUND )
1694 {
1695 replacement << (wxChar)-1;
1696 }
1697
1698 wxString replacement2 = (wxChar)-2;
1699 while ( fmt.Find(replacement) != wxNOT_FOUND )
1700 {
1701 replacement << (wxChar)-2;
1702 }
1703
1704 // replace all occurences of year with it
1705 bool wasReplaced = fmt.Replace(strYear, replacement) > 0;
1706 if ( !wasReplaced )
1707 wasReplaced = fmt.Replace(strYear2, replacement2) > 0;
1708
1709 // use strftime() to format the same date but in supported
1710 // year
1711 //
1712 // NB: we assume that strftime() doesn't check for the
1713 // date validity and will happily format the date
1714 // corresponding to Feb 29 of a non leap year (which
1715 // may happen if yearReal was leap and year is not)
1716 struct tm tmAdjusted;
1717 tmAdjusted.tm_hour = tm.hour;
1718 tmAdjusted.tm_min = tm.min;
1719 tmAdjusted.tm_sec = tm.sec;
1720 tmAdjusted.tm_wday = tm.GetWeekDay();
1721 tmAdjusted.tm_yday = GetDayOfYear();
1722 tmAdjusted.tm_mday = tm.mday;
1723 tmAdjusted.tm_mon = tm.mon;
1724 tmAdjusted.tm_year = year - 1900;
1725 tmAdjusted.tm_isdst = 0; // no DST, already adjusted
1726 wxString str = CallStrftime(*p == _T('c') ? _T("%c")
1727 : _T("%x"),
1728 &tmAdjusted);
1729
1730 // now replace the occurence of 1999 with the real year
1731 wxString strYearReal, strYearReal2;
1732 strYearReal.Printf(_T("%04d"), yearReal);
1733 strYearReal2.Printf(_T("%02d"), yearReal % 100);
1734 str.Replace(strYear, strYearReal);
1735 str.Replace(strYear2, strYearReal2);
1736
1737 // and replace back all occurences of replacement string
1738 if ( wasReplaced )
1739 {
1740 str.Replace(replacement2, strYear2);
1741 str.Replace(replacement, strYear);
1742 }
1743
1744 res += str;
1745 }
1746 break;
1747
1748 case _T('d'): // day of a month (01-31)
1749 tmp.Printf(_T("%02d"), tm.mday);
1750 res += tmp;
1751 break;
1752
1753 case _T('H'): // hour in 24h format (00-23)
1754 tmp.Printf(_T("%02d"), tm.hour);
1755 res += tmp;
1756 break;
1757
1758 case _T('I'): // hour in 12h format (01-12)
1759 {
1760 // 24h -> 12h, 0h -> 12h too
1761 int hour12 = tm.hour > 12 ? tm.hour - 12
1762 : tm.hour ? tm.hour : 12;
1763 tmp.Printf(_T("%02d"), hour12);
1764 res += tmp;
1765 }
1766 break;
1767
1768 case _T('j'): // day of the year
1769 tmp.Printf(_T("%03d"), GetDayOfYear(tz));
1770 res += tmp;
1771 break;
1772
1773 case _T('m'): // month as a number (01-12)
1774 tmp.Printf(_T("%02d"), tm.mon + 1);
1775 res += tmp;
1776 break;
1777
1778 case _T('M'): // minute as a decimal number (00-59)
1779 tmp.Printf(_T("%02d"), tm.min);
1780 res += tmp;
1781 break;
1782
1783 case _T('p'): // AM or PM string
1784 res += CallStrftime(_T("%p"), &tmTimeOnly);
1785 break;
1786
1787 case _T('S'): // second as a decimal number (00-61)
1788 tmp.Printf(_T("%02d"), tm.sec);
1789 res += tmp;
1790 break;
1791
1792 case _T('U'): // week number in the year (Sunday 1st week day)
1793 tmp.Printf(_T("%02d"),
1794 (GetDayOfYear(tz) - tm.GetWeekDay() + 7) / 7);
1795 res += tmp;
1796 break;
1797
1798 case _T('W'): // week number in the year (Monday 1st week day)
1799 tmp.Printf(_T("%02d"), GetWeekOfYear(tz));
1800 res += tmp;
1801 break;
1802
1803 case _T('w'): // weekday as a number (0-6), Sunday = 0
1804 tmp.Printf(_T("%d"), tm.GetWeekDay());
1805 res += tmp;
1806 break;
1807
1808 // case _T('x'): -- handled with "%c"
1809
1810 case _T('X'): // locale default time representation
1811 // just use strftime() to format the time for us
1812 res += CallStrftime(_T("%X"), &tmTimeOnly);
1813 break;
1814
1815 case _T('y'): // year without century (00-99)
1816 tmp.Printf(_T("%02d"), tm.year % 100);
1817 res += tmp;
1818 break;
1819
1820 case _T('Y'): // year with century
1821 tmp.Printf(_T("%04d"), tm.year);
1822 res += tmp;
1823 break;
1824
1825 case _T('Z'): // timezone name
1826 res += CallStrftime(_T("%Z"), &tmTimeOnly);
1827 break;
1828
1829 default:
1830 wxFAIL_MSG(_T("unknown format specificator"));
1831
1832 // fall through and just copy it nevertheless
1833
1834 case _T('%'): // a percent sign
1835 res += *p;
1836 break;
1837
1838 case 0:
1839 wxFAIL_MSG(_T("missing format at the end of string"));
1840
1841 // just put the '%' which was the last char in format
1842 res += _T('%');
1843 break;
1844 }
1845 }
1846
1847 return res;
1848 }
1849
1850 // this function parses a string in (strict) RFC 822 format: see the section 5
1851 // of the RFC for the detailed description, but briefly it's something of the
1852 // form "Sat, 18 Dec 1999 00:48:30 +0100"
1853 //
1854 // this function is "strict" by design - it must reject anything except true
1855 // RFC822 time specs.
1856 //
1857 // TODO a great candidate for using reg exps
1858 const wxChar *wxDateTime::ParseRfc822Date(const wxChar* date)
1859 {
1860 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
1861
1862 const wxChar *p = date;
1863 const wxChar *comma = wxStrchr(p, _T(','));
1864 if ( comma )
1865 {
1866 // the part before comma is the weekday
1867
1868 // skip it for now - we don't use but might check that it really
1869 // corresponds to the specfied date
1870 p = comma + 1;
1871
1872 if ( *p != _T(' ') )
1873 {
1874 wxLogDebug(_T("no space after weekday in RFC822 time spec"));
1875
1876 return (wxChar *)NULL;
1877 }
1878
1879 p++; // skip space
1880 }
1881
1882 // the following 1 or 2 digits are the day number
1883 if ( !wxIsdigit(*p) )
1884 {
1885 wxLogDebug(_T("day number expected in RFC822 time spec, none found"));
1886
1887 return (wxChar *)NULL;
1888 }
1889
1890 wxDateTime_t day = *p++ - _T('0');
1891 if ( wxIsdigit(*p) )
1892 {
1893 day *= 10;
1894 day += *p++ - _T('0');
1895 }
1896
1897 if ( *p++ != _T(' ') )
1898 {
1899 return (wxChar *)NULL;
1900 }
1901
1902 // the following 3 letters specify the month
1903 wxString monName(p, 3);
1904 Month mon;
1905 if ( monName == _T("Jan") )
1906 mon = Jan;
1907 else if ( monName == _T("Feb") )
1908 mon = Feb;
1909 else if ( monName == _T("Mar") )
1910 mon = Mar;
1911 else if ( monName == _T("Apr") )
1912 mon = Apr;
1913 else if ( monName == _T("May") )
1914 mon = May;
1915 else if ( monName == _T("Jun") )
1916 mon = Jun;
1917 else if ( monName == _T("Jul") )
1918 mon = Jul;
1919 else if ( monName == _T("Aug") )
1920 mon = Aug;
1921 else if ( monName == _T("Sep") )
1922 mon = Sep;
1923 else if ( monName == _T("Oct") )
1924 mon = Oct;
1925 else if ( monName == _T("Nov") )
1926 mon = Nov;
1927 else if ( monName == _T("Dec") )
1928 mon = Dec;
1929 else
1930 {
1931 wxLogDebug(_T("Invalid RFC 822 month name '%s'"), monName.c_str());
1932
1933 return (wxChar *)NULL;
1934 }
1935
1936 p += 3;
1937
1938 if ( *p++ != _T(' ') )
1939 {
1940 return (wxChar *)NULL;
1941 }
1942
1943 // next is the year
1944 if ( !wxIsdigit(*p) )
1945 {
1946 // no year?
1947 return (wxChar *)NULL;
1948 }
1949
1950 int year = *p++ - _T('0');
1951
1952 if ( !wxIsdigit(*p) )
1953 {
1954 // should have at least 2 digits in the year
1955 return (wxChar *)NULL;
1956 }
1957
1958 year *= 10;
1959 year += *p++ - _T('0');
1960
1961 // is it a 2 digit year (as per original RFC 822) or a 4 digit one?
1962 if ( wxIsdigit(*p) )
1963 {
1964 year *= 10;
1965 year += *p++ - _T('0');
1966
1967 if ( !wxIsdigit(*p) )
1968 {
1969 // no 3 digit years please
1970 return (wxChar *)NULL;
1971 }
1972
1973 year *= 10;
1974 year += *p++ - _T('0');
1975 }
1976
1977 if ( *p++ != _T(' ') )
1978 {
1979 return (wxChar *)NULL;
1980 }
1981
1982 // time is in the format hh:mm:ss and seconds are optional
1983 if ( !wxIsdigit(*p) )
1984 {
1985 return (wxChar *)NULL;
1986 }
1987
1988 wxDateTime_t hour = *p++ - _T('0');
1989
1990 if ( !wxIsdigit(*p) )
1991 {
1992 return (wxChar *)NULL;
1993 }
1994
1995 hour *= 10;
1996 hour += *p++ - _T('0');
1997
1998 if ( *p++ != _T(':') )
1999 {
2000 return (wxChar *)NULL;
2001 }
2002
2003 if ( !wxIsdigit(*p) )
2004 {
2005 return (wxChar *)NULL;
2006 }
2007
2008 wxDateTime_t min = *p++ - _T('0');
2009
2010 if ( !wxIsdigit(*p) )
2011 {
2012 return (wxChar *)NULL;
2013 }
2014
2015 min *= 10;
2016 min += *p++ - _T('0');
2017
2018 wxDateTime_t sec = 0;
2019 if ( *p++ == _T(':') )
2020 {
2021 if ( !wxIsdigit(*p) )
2022 {
2023 return (wxChar *)NULL;
2024 }
2025
2026 sec = *p++ - _T('0');
2027
2028 if ( !wxIsdigit(*p) )
2029 {
2030 return (wxChar *)NULL;
2031 }
2032
2033 sec *= 10;
2034 sec += *p++ - _T('0');
2035 }
2036
2037 if ( *p++ != _T(' ') )
2038 {
2039 return (wxChar *)NULL;
2040 }
2041
2042 // and now the interesting part: the timezone
2043 int offset;
2044 if ( *p == _T('-') || *p == _T('+') )
2045 {
2046 // the explicit offset given: it has the form of hhmm
2047 bool plus = *p++ == _T('+');
2048
2049 if ( !wxIsdigit(*p) || !wxIsdigit(*(p + 1)) )
2050 {
2051 return (wxChar *)NULL;
2052 }
2053
2054 // hours
2055 offset = 60*(10*(*p - _T('0')) + (*(p + 1) - _T('0')));
2056
2057 p += 2;
2058
2059 if ( !wxIsdigit(*p) || !wxIsdigit(*(p + 1)) )
2060 {
2061 return (wxChar *)NULL;
2062 }
2063
2064 // minutes
2065 offset += 10*(*p - _T('0')) + (*(p + 1) - _T('0'));
2066
2067 if ( !plus )
2068 {
2069 offset = -offset;
2070 }
2071
2072 p += 2;
2073 }
2074 else
2075 {
2076 // the symbolic timezone given: may be either military timezone or one
2077 // of standard abbreviations
2078 if ( !*(p + 1) )
2079 {
2080 // military: Z = UTC, J unused, A = -1, ..., Y = +12
2081 static const int offsets[26] =
2082 {
2083 //A B C D E F G H I J K L M
2084 -1, -2, -3, -4, -5, -6, -7, -8, -9, 0, -10, -11, -12,
2085 //N O P R Q S T U V W Z Y Z
2086 +1, +2, +3, +4, +5, +6, +7, +8, +9, +10, +11, +12, 0
2087 };
2088
2089 if ( *p < _T('A') || *p > _T('Z') || *p == _T('J') )
2090 {
2091 wxLogDebug(_T("Invalid militaty timezone '%c'"), *p);
2092
2093 return (wxChar *)NULL;
2094 }
2095
2096 offset = offsets[*p++ - _T('A')];
2097 }
2098 else
2099 {
2100 // abbreviation
2101 wxString tz = p;
2102 if ( tz == _T("UT") || tz == _T("UTC") || tz == _T("GMT") )
2103 offset = 0;
2104 else if ( tz == _T("AST") )
2105 offset = AST - GMT0;
2106 else if ( tz == _T("ADT") )
2107 offset = ADT - GMT0;
2108 else if ( tz == _T("EST") )
2109 offset = EST - GMT0;
2110 else if ( tz == _T("EDT") )
2111 offset = EDT - GMT0;
2112 else if ( tz == _T("CST") )
2113 offset = CST - GMT0;
2114 else if ( tz == _T("CDT") )
2115 offset = CDT - GMT0;
2116 else if ( tz == _T("MST") )
2117 offset = MST - GMT0;
2118 else if ( tz == _T("MDT") )
2119 offset = MDT - GMT0;
2120 else if ( tz == _T("PST") )
2121 offset = PST - GMT0;
2122 else if ( tz == _T("PDT") )
2123 offset = PDT - GMT0;
2124 else
2125 {
2126 wxLogDebug(_T("Unknown RFC 822 timezone '%s'"), p);
2127
2128 return (wxChar *)NULL;
2129 }
2130
2131 p += tz.length();
2132 }
2133
2134 // make it minutes
2135 offset *= 60;
2136 }
2137
2138 // the spec was correct
2139 Set(day, mon, year, hour, min, sec);
2140 MakeTimezone(60*offset);
2141
2142 return p;
2143 }
2144
2145 const wxChar *wxDateTime::ParseFormat(const wxChar *date, const wxChar *format)
2146 {
2147 wxCHECK_MSG( date && format, (wxChar *)NULL,
2148 _T("NULL pointer in wxDateTime::Parse") );
2149
2150 // there is a public domain version of getdate.y, but it only works for
2151 // English...
2152 wxFAIL_MSG(_T("TODO"));
2153
2154 return (wxChar *)NULL;
2155 }
2156
2157 const wxChar *wxDateTime::ParseDateTime(const wxChar *date)
2158 {
2159 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
2160
2161 // find a public domain version of strptime() somewhere?
2162 wxFAIL_MSG(_T("TODO"));
2163
2164 return (wxChar *)NULL;
2165 }
2166
2167 const wxChar *wxDateTime::ParseDate(const wxChar *date)
2168 {
2169 // this is a simplified version of ParseDateTime() which understands only
2170 // "today" (for wxDate compatibility) and digits only otherwise (and not
2171 // all esoteric constructions ParseDateTime() knows about)
2172
2173 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
2174
2175 const wxChar *p = date;
2176 while ( wxIsspace(*p) )
2177 p++;
2178
2179 wxString today = _T("today");
2180 size_t len = today.length();
2181 if ( wxString(p, len).CmpNoCase(today) == 0 )
2182 {
2183 // nothing can follow this, so stop here
2184 p += len;
2185
2186 *this = Today();
2187
2188 return p;
2189 }
2190
2191 // what do we have?
2192 bool haveDay = FALSE, // the months day?
2193 haveWDay = FALSE, // the day of week?
2194 haveMon = FALSE, // the month?
2195 haveYear = FALSE; // the year?
2196
2197 // and the value of the items we have (init them to get rid of warnings)
2198 WeekDay wday = Inv_WeekDay;
2199 wxDateTime_t day = 0;
2200 wxDateTime::Month mon = Inv_Month;
2201 int year = 0;
2202
2203 // tokenize the string
2204 wxStringTokenizer tok(p, _T(",/-\t "));
2205 while ( tok.HasMoreTokens() )
2206 {
2207 wxString token = tok.GetNextToken();
2208
2209 // is it a number?
2210 unsigned long val;
2211 if ( token.ToULong(&val) )
2212 {
2213 // guess what this number is
2214
2215 bool isDay = FALSE,
2216 isMonth = FALSE,
2217 // only years are counted from 0
2218 isYear = (val == 0) || (val > 31);
2219 if ( !isYear )
2220 {
2221 // may be the month or month day or the year, assume the
2222 // month day by default and fallback to month if the range
2223 // allow it or to the year if our assumption doesn't work
2224 if ( haveDay )
2225 {
2226 // we already have the day, so may only be a month or year
2227 if ( val > 12 )
2228 {
2229 isYear = TRUE;
2230 }
2231 else
2232 {
2233 isMonth = TRUE;
2234 }
2235 }
2236 else // it may be day
2237 {
2238 isDay = TRUE;
2239
2240 // check the range
2241 if ( haveMon )
2242 {
2243 if ( val > GetNumOfDaysInMonth(haveYear ? year
2244 : Inv_Year,
2245 mon) )
2246 {
2247 // oops, it can't be a day finally
2248 isDay = FALSE;
2249
2250 if ( val > 12 )
2251 {
2252 isYear = TRUE;
2253 }
2254 else
2255 {
2256 isMonth = TRUE;
2257 }
2258 }
2259 }
2260 }
2261 }
2262
2263 // remember that we have this and stop the scan if it's the second
2264 // time we find this, except for the day logic (see there)
2265 if ( isYear )
2266 {
2267 if ( haveYear )
2268 {
2269 break;
2270 }
2271
2272 haveYear = TRUE;
2273
2274 // no roll over - 99 means 99, not 1999 for us
2275 year = val;
2276 }
2277 else if ( isMonth )
2278 {
2279 if ( haveMon )
2280 {
2281 break;
2282 }
2283
2284 haveMon = TRUE;
2285
2286 mon = (wxDateTime::Month)val;
2287 }
2288 else
2289 {
2290 wxASSERT_MSG( isDay, _T("logic error") );
2291
2292 if ( haveDay )
2293 {
2294 // may be were mistaken when we found it for the first
2295 // time? may be it was a month or year instead?
2296 //
2297 // this ability to "backtrack" allows us to correctly parse
2298 // both things like 01/13 and 13/01 - but, of course, we
2299 // still can't resolve the ambiguity in 01/02 (it will be
2300 // Feb 1 for us, not Jan 2 as americans might expect!)
2301 if ( (day <= 12) && !haveMon )
2302 {
2303 // exchange day and month
2304 mon = (wxDateTime::Month)day;
2305
2306 haveMon = TRUE;
2307 }
2308 else if ( !haveYear )
2309 {
2310 // exchange day and year
2311 year = day;
2312
2313 haveYear = TRUE;
2314 }
2315 }
2316
2317 haveDay = TRUE;
2318
2319 day = val;
2320 }
2321 }
2322 else // not a number
2323 {
2324 mon = GetMonthFromName(token);
2325 if ( mon != Inv_Month )
2326 {
2327 // it's a month
2328 if ( haveMon )
2329 {
2330 break;
2331 }
2332
2333 haveMon = TRUE;
2334 }
2335 else
2336 {
2337 wday = GetWeekDayFromName(token);
2338 if ( wday != Inv_WeekDay )
2339 {
2340 // a week day
2341 if ( haveWDay )
2342 {
2343 break;
2344 }
2345
2346 haveWDay = TRUE;
2347 }
2348 else
2349 {
2350 // try the ordinals
2351 static const wxChar *ordinals[] =
2352 {
2353 wxTRANSLATE("first"),
2354 wxTRANSLATE("second"),
2355 wxTRANSLATE("third"),
2356 wxTRANSLATE("fourth"),
2357 wxTRANSLATE("fifth"),
2358 wxTRANSLATE("sixth"),
2359 wxTRANSLATE("seventh"),
2360 wxTRANSLATE("eighth"),
2361 wxTRANSLATE("ninth"),
2362 wxTRANSLATE("tenth"),
2363 wxTRANSLATE("eleventh"),
2364 wxTRANSLATE("twelfth"),
2365 wxTRANSLATE("thirteenth"),
2366 wxTRANSLATE("fourteenth"),
2367 wxTRANSLATE("fifteenth"),
2368 wxTRANSLATE("sixteenth"),
2369 wxTRANSLATE("seventeenth"),
2370 wxTRANSLATE("eighteenth"),
2371 wxTRANSLATE("nineteenth"),
2372 wxTRANSLATE("twentieth"),
2373 // that's enough - otherwise we'd have problems with
2374 // composite (or not) ordinals otherwise
2375 };
2376
2377 size_t n;
2378 for ( n = 0; n < WXSIZEOF(ordinals); n++ )
2379 {
2380 if ( token.CmpNoCase(ordinals[n]) == 0 )
2381 {
2382 break;
2383 }
2384 }
2385
2386 if ( n == WXSIZEOF(ordinals) )
2387 {
2388 // stop here - something unknown
2389 break;
2390 }
2391
2392 // it's a day
2393 if ( haveDay )
2394 {
2395 // don't try anything here (as in case of numeric day
2396 // above) - the symbolic day spec should always
2397 // precede the month/year
2398 break;
2399 }
2400
2401 haveDay = TRUE;
2402
2403 day = n + 1;
2404 }
2405 }
2406 }
2407 }
2408
2409 // either no more tokens or the scan was stopped by something we couldn't
2410 // parse - in any case, see if we can construct a date from what we have
2411 if ( !haveDay && !haveWDay )
2412 {
2413 wxLogDebug(_T("no day, no weekday hence no date."));
2414
2415 return (wxChar *)NULL;
2416 }
2417
2418 if ( haveWDay && (haveMon || haveYear || haveDay) &&
2419 !(haveMon && haveMon && haveYear) )
2420 {
2421 // without adjectives (which we don't support here) the week day only
2422 // makes sense completely separately or with the full date
2423 // specification (what would "Wed 1999" mean?)
2424 return (wxChar *)NULL;
2425 }
2426
2427 if ( !haveMon )
2428 {
2429 mon = GetCurrentMonth();
2430 }
2431
2432 if ( !haveYear )
2433 {
2434 year = GetCurrentYear();
2435 }
2436
2437 if ( haveDay )
2438 {
2439 Set(day, mon, year);
2440
2441 if ( haveWDay )
2442 {
2443 // check that it is really the same
2444 if ( GetWeekDay() != wday )
2445 {
2446 // inconsistency detected
2447 return (wxChar *)NULL;
2448 }
2449 }
2450 }
2451 else // haveWDay
2452 {
2453 *this = Today();
2454
2455 SetToWeekDayInSameWeek(wday);
2456 }
2457
2458 // return the pointer to the next char
2459 return p + wxStrlen(p) - wxStrlen(tok.GetString());
2460 }
2461
2462 const wxChar *wxDateTime::ParseTime(const wxChar *time)
2463 {
2464 wxCHECK_MSG( time, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
2465
2466 // this function should be able to parse different time formats as well as
2467 // timezones (take the code out from ParseRfc822Date()) and AM/PM.
2468 wxFAIL_MSG(_T("TODO"));
2469
2470 return (wxChar *)NULL;
2471 }
2472
2473 // ============================================================================
2474 // wxTimeSpan
2475 // ============================================================================
2476
2477 // not all strftime(3) format specifiers make sense here because, for example,
2478 // a time span doesn't have a year nor a timezone
2479 //
2480 // Here are the ones which are supported (all of them are supported by strftime
2481 // as well):
2482 // %H hour in 24 hour format
2483 // %M minute (00 - 59)
2484 // %S second (00 - 59)
2485 // %% percent sign
2486 //
2487 // Also, for MFC CTimeSpan compatibility, we support
2488 // %D number of days
2489 //
2490 // And, to be better than MFC :-), we also have
2491 // %E number of wEeks
2492 // %l milliseconds (000 - 999)
2493 wxString wxTimeSpan::Format(const wxChar *format) const
2494 {
2495 wxCHECK_MSG( format, _T(""), _T("NULL format in wxTimeSpan::Format") );
2496
2497 wxString str;
2498 str.Alloc(strlen(format));
2499
2500 for ( const wxChar *pch = format; pch; pch++ )
2501 {
2502 wxChar ch = *pch;
2503
2504 if ( ch == '%' )
2505 {
2506 wxString tmp;
2507
2508 ch = *pch++;
2509 switch ( ch )
2510 {
2511 default:
2512 wxFAIL_MSG( _T("invalid format character") );
2513 // fall through
2514
2515 case '%':
2516 // will get to str << ch below
2517 break;
2518
2519 case 'D':
2520 tmp.Printf(_T("%d"), GetDays());
2521 break;
2522
2523 case 'E':
2524 tmp.Printf(_T("%d"), GetWeeks());
2525 break;
2526
2527 case 'H':
2528 tmp.Printf(_T("%02d"), GetHours());
2529 break;
2530
2531 case 'l':
2532 tmp.Printf(_T("%03ld"), GetMilliseconds().ToLong());
2533 break;
2534
2535 case 'M':
2536 tmp.Printf(_T("%02d"), GetMinutes());
2537 break;
2538
2539 case 'S':
2540 tmp.Printf(_T("%02ld"), GetSeconds().ToLong());
2541 break;
2542 }
2543
2544 if ( !!tmp )
2545 {
2546 str += tmp;
2547
2548 // skip str += ch below
2549 continue;
2550 }
2551 }
2552
2553 str += ch;
2554 }
2555
2556 return str;
2557 }