]> git.saurik.com Git - wxWidgets.git/blob - src/common/datetime.cpp
added support for width and flags in wxDateTime::Format()
[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, fmt;
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 // set the default format
1602 switch ( *++p )
1603 {
1604 case _T('Y'): // year has 4 digits
1605 fmt = _T("%04d");
1606 break;
1607
1608 case _T('j'): // day of year has 3 digits
1609 fmt = _T("%03d");
1610 break;
1611
1612 default:
1613 // it's either another valid format specifier in which case
1614 // the format is "%02d" (for all the rest) or we have the
1615 // field width preceding the format in which case it will
1616 // override the default format anyhow
1617 fmt = _T("%02d");
1618 }
1619
1620 restart:
1621 // start of the format specification
1622 switch ( *p )
1623 {
1624 case _T('a'): // a weekday name
1625 case _T('A'):
1626 // second parameter should be TRUE for abbreviated names
1627 res += GetWeekDayName(tm.GetWeekDay(), *p == _T('a'));
1628 break;
1629
1630 case _T('b'): // a month name
1631 case _T('B'):
1632 // second parameter should be TRUE for abbreviated names
1633 res += GetMonthName(tm.mon, *p == _T('b'));
1634 break;
1635
1636 case _T('c'): // locale default date and time representation
1637 case _T('x'): // locale default date representation
1638 //
1639 // the problem: there is no way to know what do these format
1640 // specifications correspond to for the current locale.
1641 //
1642 // the solution: use a hack and still use strftime(): first
1643 // find the YEAR which is a year in the strftime() range (1970
1644 // - 2038) whose Jan 1 falls on the same week day as the Jan 1
1645 // of the real year. Then make a copy of the format and
1646 // replace all occurences of YEAR in it with some unique
1647 // string not appearing anywhere else in it, then use
1648 // strftime() to format the date in year YEAR and then replace
1649 // YEAR back by the real year and the unique replacement
1650 // string back with YEAR. Notice that "all occurences of YEAR"
1651 // means all occurences of 4 digit as well as 2 digit form!
1652 //
1653 // the bugs: we assume that neither of %c nor %x contains any
1654 // fields which may change between the YEAR and real year. For
1655 // example, the week number (%U, %W) and the day number (%j)
1656 // will change if one of these years is leap and the other one
1657 // is not!
1658 {
1659 // find the YEAR: normally, for any year X, Jan 1 or the
1660 // year X + 28 is the same weekday as Jan 1 of X (because
1661 // the weekday advances by 1 for each normal X and by 2
1662 // for each leap X, hence by 5 every 4 years or by 35
1663 // which is 0 mod 7 every 28 years) but this rule breaks
1664 // down if there are years between X and Y which are
1665 // divisible by 4 but not leap (i.e. divisible by 100 but
1666 // not 400), hence the correction.
1667
1668 int yearReal = GetYear(tz);
1669 int mod28 = yearReal % 28;
1670
1671 // be careful to not go too far - we risk to leave the
1672 // supported range
1673 int year;
1674 if ( mod28 < 10 )
1675 {
1676 year = 1988 + mod28; // 1988 == 0 (mod 28)
1677 }
1678 else
1679 {
1680 year = 1970 + mod28 - 10; // 1970 == 10 (mod 28)
1681 }
1682
1683 int nCentury = year / 100,
1684 nCenturyReal = yearReal / 100;
1685
1686 // need to adjust for the years divisble by 400 which are
1687 // not leap but are counted like leap ones if we just take
1688 // the number of centuries in between for nLostWeekDays
1689 int nLostWeekDays = (nCentury - nCenturyReal) -
1690 (nCentury / 4 - nCenturyReal / 4);
1691
1692 // we have to gain back the "lost" weekdays: note that the
1693 // effect of this loop is to not do anything to
1694 // nLostWeekDays (which we won't use any more), but to
1695 // (indirectly) set the year correctly
1696 while ( (nLostWeekDays % 7) != 0 )
1697 {
1698 nLostWeekDays += year++ % 4 ? 1 : 2;
1699 }
1700
1701 // at any rate, we couldn't go further than 1988 + 9 + 28!
1702 wxASSERT_MSG( year < 2030,
1703 _T("logic error in wxDateTime::Format") );
1704
1705 wxString strYear, strYear2;
1706 strYear.Printf(_T("%d"), year);
1707 strYear2.Printf(_T("%d"), year % 100);
1708
1709 // find two strings not occuring in format (this is surely
1710 // not optimal way of doing it... improvements welcome!)
1711 wxString fmt = format;
1712 wxString replacement = (wxChar)-1;
1713 while ( fmt.Find(replacement) != wxNOT_FOUND )
1714 {
1715 replacement << (wxChar)-1;
1716 }
1717
1718 wxString replacement2 = (wxChar)-2;
1719 while ( fmt.Find(replacement) != wxNOT_FOUND )
1720 {
1721 replacement << (wxChar)-2;
1722 }
1723
1724 // replace all occurences of year with it
1725 bool wasReplaced = fmt.Replace(strYear, replacement) > 0;
1726 if ( !wasReplaced )
1727 wasReplaced = fmt.Replace(strYear2, replacement2) > 0;
1728
1729 // use strftime() to format the same date but in supported
1730 // year
1731 //
1732 // NB: we assume that strftime() doesn't check for the
1733 // date validity and will happily format the date
1734 // corresponding to Feb 29 of a non leap year (which
1735 // may happen if yearReal was leap and year is not)
1736 struct tm tmAdjusted;
1737 tmAdjusted.tm_hour = tm.hour;
1738 tmAdjusted.tm_min = tm.min;
1739 tmAdjusted.tm_sec = tm.sec;
1740 tmAdjusted.tm_wday = tm.GetWeekDay();
1741 tmAdjusted.tm_yday = GetDayOfYear();
1742 tmAdjusted.tm_mday = tm.mday;
1743 tmAdjusted.tm_mon = tm.mon;
1744 tmAdjusted.tm_year = year - 1900;
1745 tmAdjusted.tm_isdst = 0; // no DST, already adjusted
1746 wxString str = CallStrftime(*p == _T('c') ? _T("%c")
1747 : _T("%x"),
1748 &tmAdjusted);
1749
1750 // now replace the occurence of 1999 with the real year
1751 wxString strYearReal, strYearReal2;
1752 strYearReal.Printf(_T("%04d"), yearReal);
1753 strYearReal2.Printf(_T("%02d"), yearReal % 100);
1754 str.Replace(strYear, strYearReal);
1755 str.Replace(strYear2, strYearReal2);
1756
1757 // and replace back all occurences of replacement string
1758 if ( wasReplaced )
1759 {
1760 str.Replace(replacement2, strYear2);
1761 str.Replace(replacement, strYear);
1762 }
1763
1764 res += str;
1765 }
1766 break;
1767
1768 case _T('d'): // day of a month (01-31)
1769 res += wxString::Format(fmt, tm.mday);
1770 break;
1771
1772 case _T('H'): // hour in 24h format (00-23)
1773 res += wxString::Format(fmt, tm.hour);
1774 break;
1775
1776 case _T('I'): // hour in 12h format (01-12)
1777 {
1778 // 24h -> 12h, 0h -> 12h too
1779 int hour12 = tm.hour > 12 ? tm.hour - 12
1780 : tm.hour ? tm.hour : 12;
1781 res += wxString::Format(fmt, hour12);
1782 }
1783 break;
1784
1785 case _T('j'): // day of the year
1786 res += wxString::Format(fmt, GetDayOfYear(tz));
1787 break;
1788
1789 case _T('m'): // month as a number (01-12)
1790 res += wxString::Format(fmt, tm.mon + 1);
1791 break;
1792
1793 case _T('M'): // minute as a decimal number (00-59)
1794 res += wxString::Format(fmt, tm.min);
1795 break;
1796
1797 case _T('p'): // AM or PM string
1798 res += CallStrftime(_T("%p"), &tmTimeOnly);
1799 break;
1800
1801 case _T('S'): // second as a decimal number (00-61)
1802 res += wxString::Format(fmt, tm.sec);
1803 break;
1804
1805 case _T('U'): // week number in the year (Sunday 1st week day)
1806 {
1807 int week = (GetDayOfYear(tz) - tm.GetWeekDay() + 7) / 7;
1808 res += wxString::Format(fmt, week);
1809 }
1810 break;
1811
1812 case _T('W'): // week number in the year (Monday 1st week day)
1813 res += wxString::Format(fmt, GetWeekOfYear(tz));
1814 break;
1815
1816 case _T('w'): // weekday as a number (0-6), Sunday = 0
1817 res += wxString::Format(fmt, tm.GetWeekDay());
1818 break;
1819
1820 // case _T('x'): -- handled with "%c"
1821
1822 case _T('X'): // locale default time representation
1823 // just use strftime() to format the time for us
1824 res += CallStrftime(_T("%X"), &tmTimeOnly);
1825 break;
1826
1827 case _T('y'): // year without century (00-99)
1828 res += wxString::Format(fmt, tm.year % 100);
1829 break;
1830
1831 case _T('Y'): // year with century
1832 res += wxString::Format(fmt, tm.year);
1833 break;
1834
1835 case _T('Z'): // timezone name
1836 res += CallStrftime(_T("%Z"), &tmTimeOnly);
1837 break;
1838
1839 default:
1840 // is it the format width?
1841 fmt.Empty();
1842 while ( *p == _T('-') || *p == _T('+') ||
1843 *p == _T(' ') || wxIsdigit(*p) )
1844 {
1845 fmt += *p;
1846 }
1847
1848 if ( !fmt.IsEmpty() )
1849 {
1850 // we've only got the flags and width so far in fmt
1851 fmt.Prepend(_T('%'));
1852 fmt.Append(_T('d'));
1853
1854 goto restart;
1855 }
1856
1857 // no, it wasn't the width
1858 wxFAIL_MSG(_T("unknown format specificator"));
1859
1860 // fall through and just copy it nevertheless
1861
1862 case _T('%'): // a percent sign
1863 res += *p;
1864 break;
1865
1866 case 0:
1867 wxFAIL_MSG(_T("missing format at the end of string"));
1868
1869 // just put the '%' which was the last char in format
1870 res += _T('%');
1871 break;
1872 }
1873 }
1874
1875 return res;
1876 }
1877
1878 // this function parses a string in (strict) RFC 822 format: see the section 5
1879 // of the RFC for the detailed description, but briefly it's something of the
1880 // form "Sat, 18 Dec 1999 00:48:30 +0100"
1881 //
1882 // this function is "strict" by design - it must reject anything except true
1883 // RFC822 time specs.
1884 //
1885 // TODO a great candidate for using reg exps
1886 const wxChar *wxDateTime::ParseRfc822Date(const wxChar* date)
1887 {
1888 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
1889
1890 const wxChar *p = date;
1891 const wxChar *comma = wxStrchr(p, _T(','));
1892 if ( comma )
1893 {
1894 // the part before comma is the weekday
1895
1896 // skip it for now - we don't use but might check that it really
1897 // corresponds to the specfied date
1898 p = comma + 1;
1899
1900 if ( *p != _T(' ') )
1901 {
1902 wxLogDebug(_T("no space after weekday in RFC822 time spec"));
1903
1904 return (wxChar *)NULL;
1905 }
1906
1907 p++; // skip space
1908 }
1909
1910 // the following 1 or 2 digits are the day number
1911 if ( !wxIsdigit(*p) )
1912 {
1913 wxLogDebug(_T("day number expected in RFC822 time spec, none found"));
1914
1915 return (wxChar *)NULL;
1916 }
1917
1918 wxDateTime_t day = *p++ - _T('0');
1919 if ( wxIsdigit(*p) )
1920 {
1921 day *= 10;
1922 day += *p++ - _T('0');
1923 }
1924
1925 if ( *p++ != _T(' ') )
1926 {
1927 return (wxChar *)NULL;
1928 }
1929
1930 // the following 3 letters specify the month
1931 wxString monName(p, 3);
1932 Month mon;
1933 if ( monName == _T("Jan") )
1934 mon = Jan;
1935 else if ( monName == _T("Feb") )
1936 mon = Feb;
1937 else if ( monName == _T("Mar") )
1938 mon = Mar;
1939 else if ( monName == _T("Apr") )
1940 mon = Apr;
1941 else if ( monName == _T("May") )
1942 mon = May;
1943 else if ( monName == _T("Jun") )
1944 mon = Jun;
1945 else if ( monName == _T("Jul") )
1946 mon = Jul;
1947 else if ( monName == _T("Aug") )
1948 mon = Aug;
1949 else if ( monName == _T("Sep") )
1950 mon = Sep;
1951 else if ( monName == _T("Oct") )
1952 mon = Oct;
1953 else if ( monName == _T("Nov") )
1954 mon = Nov;
1955 else if ( monName == _T("Dec") )
1956 mon = Dec;
1957 else
1958 {
1959 wxLogDebug(_T("Invalid RFC 822 month name '%s'"), monName.c_str());
1960
1961 return (wxChar *)NULL;
1962 }
1963
1964 p += 3;
1965
1966 if ( *p++ != _T(' ') )
1967 {
1968 return (wxChar *)NULL;
1969 }
1970
1971 // next is the year
1972 if ( !wxIsdigit(*p) )
1973 {
1974 // no year?
1975 return (wxChar *)NULL;
1976 }
1977
1978 int year = *p++ - _T('0');
1979
1980 if ( !wxIsdigit(*p) )
1981 {
1982 // should have at least 2 digits in the year
1983 return (wxChar *)NULL;
1984 }
1985
1986 year *= 10;
1987 year += *p++ - _T('0');
1988
1989 // is it a 2 digit year (as per original RFC 822) or a 4 digit one?
1990 if ( wxIsdigit(*p) )
1991 {
1992 year *= 10;
1993 year += *p++ - _T('0');
1994
1995 if ( !wxIsdigit(*p) )
1996 {
1997 // no 3 digit years please
1998 return (wxChar *)NULL;
1999 }
2000
2001 year *= 10;
2002 year += *p++ - _T('0');
2003 }
2004
2005 if ( *p++ != _T(' ') )
2006 {
2007 return (wxChar *)NULL;
2008 }
2009
2010 // time is in the format hh:mm:ss and seconds are optional
2011 if ( !wxIsdigit(*p) )
2012 {
2013 return (wxChar *)NULL;
2014 }
2015
2016 wxDateTime_t hour = *p++ - _T('0');
2017
2018 if ( !wxIsdigit(*p) )
2019 {
2020 return (wxChar *)NULL;
2021 }
2022
2023 hour *= 10;
2024 hour += *p++ - _T('0');
2025
2026 if ( *p++ != _T(':') )
2027 {
2028 return (wxChar *)NULL;
2029 }
2030
2031 if ( !wxIsdigit(*p) )
2032 {
2033 return (wxChar *)NULL;
2034 }
2035
2036 wxDateTime_t min = *p++ - _T('0');
2037
2038 if ( !wxIsdigit(*p) )
2039 {
2040 return (wxChar *)NULL;
2041 }
2042
2043 min *= 10;
2044 min += *p++ - _T('0');
2045
2046 wxDateTime_t sec = 0;
2047 if ( *p++ == _T(':') )
2048 {
2049 if ( !wxIsdigit(*p) )
2050 {
2051 return (wxChar *)NULL;
2052 }
2053
2054 sec = *p++ - _T('0');
2055
2056 if ( !wxIsdigit(*p) )
2057 {
2058 return (wxChar *)NULL;
2059 }
2060
2061 sec *= 10;
2062 sec += *p++ - _T('0');
2063 }
2064
2065 if ( *p++ != _T(' ') )
2066 {
2067 return (wxChar *)NULL;
2068 }
2069
2070 // and now the interesting part: the timezone
2071 int offset;
2072 if ( *p == _T('-') || *p == _T('+') )
2073 {
2074 // the explicit offset given: it has the form of hhmm
2075 bool plus = *p++ == _T('+');
2076
2077 if ( !wxIsdigit(*p) || !wxIsdigit(*(p + 1)) )
2078 {
2079 return (wxChar *)NULL;
2080 }
2081
2082 // hours
2083 offset = 60*(10*(*p - _T('0')) + (*(p + 1) - _T('0')));
2084
2085 p += 2;
2086
2087 if ( !wxIsdigit(*p) || !wxIsdigit(*(p + 1)) )
2088 {
2089 return (wxChar *)NULL;
2090 }
2091
2092 // minutes
2093 offset += 10*(*p - _T('0')) + (*(p + 1) - _T('0'));
2094
2095 if ( !plus )
2096 {
2097 offset = -offset;
2098 }
2099
2100 p += 2;
2101 }
2102 else
2103 {
2104 // the symbolic timezone given: may be either military timezone or one
2105 // of standard abbreviations
2106 if ( !*(p + 1) )
2107 {
2108 // military: Z = UTC, J unused, A = -1, ..., Y = +12
2109 static const int offsets[26] =
2110 {
2111 //A B C D E F G H I J K L M
2112 -1, -2, -3, -4, -5, -6, -7, -8, -9, 0, -10, -11, -12,
2113 //N O P R Q S T U V W Z Y Z
2114 +1, +2, +3, +4, +5, +6, +7, +8, +9, +10, +11, +12, 0
2115 };
2116
2117 if ( *p < _T('A') || *p > _T('Z') || *p == _T('J') )
2118 {
2119 wxLogDebug(_T("Invalid militaty timezone '%c'"), *p);
2120
2121 return (wxChar *)NULL;
2122 }
2123
2124 offset = offsets[*p++ - _T('A')];
2125 }
2126 else
2127 {
2128 // abbreviation
2129 wxString tz = p;
2130 if ( tz == _T("UT") || tz == _T("UTC") || tz == _T("GMT") )
2131 offset = 0;
2132 else if ( tz == _T("AST") )
2133 offset = AST - GMT0;
2134 else if ( tz == _T("ADT") )
2135 offset = ADT - GMT0;
2136 else if ( tz == _T("EST") )
2137 offset = EST - GMT0;
2138 else if ( tz == _T("EDT") )
2139 offset = EDT - GMT0;
2140 else if ( tz == _T("CST") )
2141 offset = CST - GMT0;
2142 else if ( tz == _T("CDT") )
2143 offset = CDT - GMT0;
2144 else if ( tz == _T("MST") )
2145 offset = MST - GMT0;
2146 else if ( tz == _T("MDT") )
2147 offset = MDT - GMT0;
2148 else if ( tz == _T("PST") )
2149 offset = PST - GMT0;
2150 else if ( tz == _T("PDT") )
2151 offset = PDT - GMT0;
2152 else
2153 {
2154 wxLogDebug(_T("Unknown RFC 822 timezone '%s'"), p);
2155
2156 return (wxChar *)NULL;
2157 }
2158
2159 p += tz.length();
2160 }
2161
2162 // make it minutes
2163 offset *= 60;
2164 }
2165
2166 // the spec was correct
2167 Set(day, mon, year, hour, min, sec);
2168 MakeTimezone(60*offset);
2169
2170 return p;
2171 }
2172
2173 const wxChar *wxDateTime::ParseFormat(const wxChar *date, const wxChar *format)
2174 {
2175 wxCHECK_MSG( date && format, (wxChar *)NULL,
2176 _T("NULL pointer in wxDateTime::Parse") );
2177
2178 // there is a public domain version of getdate.y, but it only works for
2179 // English...
2180 wxFAIL_MSG(_T("TODO"));
2181
2182 return (wxChar *)NULL;
2183 }
2184
2185 const wxChar *wxDateTime::ParseDateTime(const wxChar *date)
2186 {
2187 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
2188
2189 // find a public domain version of strptime() somewhere?
2190 wxFAIL_MSG(_T("TODO"));
2191
2192 return (wxChar *)NULL;
2193 }
2194
2195 const wxChar *wxDateTime::ParseDate(const wxChar *date)
2196 {
2197 // this is a simplified version of ParseDateTime() which understands only
2198 // "today" (for wxDate compatibility) and digits only otherwise (and not
2199 // all esoteric constructions ParseDateTime() knows about)
2200
2201 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
2202
2203 const wxChar *p = date;
2204 while ( wxIsspace(*p) )
2205 p++;
2206
2207 wxString today = _T("today");
2208 size_t len = today.length();
2209 if ( wxString(p, len).CmpNoCase(today) == 0 )
2210 {
2211 // nothing can follow this, so stop here
2212 p += len;
2213
2214 *this = Today();
2215
2216 return p;
2217 }
2218
2219 // what do we have?
2220 bool haveDay = FALSE, // the months day?
2221 haveWDay = FALSE, // the day of week?
2222 haveMon = FALSE, // the month?
2223 haveYear = FALSE; // the year?
2224
2225 // and the value of the items we have (init them to get rid of warnings)
2226 WeekDay wday = Inv_WeekDay;
2227 wxDateTime_t day = 0;
2228 wxDateTime::Month mon = Inv_Month;
2229 int year = 0;
2230
2231 // tokenize the string
2232 wxStringTokenizer tok(p, _T(",/-\t "));
2233 while ( tok.HasMoreTokens() )
2234 {
2235 wxString token = tok.GetNextToken();
2236
2237 // is it a number?
2238 unsigned long val;
2239 if ( token.ToULong(&val) )
2240 {
2241 // guess what this number is
2242
2243 bool isDay = FALSE,
2244 isMonth = FALSE,
2245 // only years are counted from 0
2246 isYear = (val == 0) || (val > 31);
2247 if ( !isYear )
2248 {
2249 // may be the month or month day or the year, assume the
2250 // month day by default and fallback to month if the range
2251 // allow it or to the year if our assumption doesn't work
2252 if ( haveDay )
2253 {
2254 // we already have the day, so may only be a month or year
2255 if ( val > 12 )
2256 {
2257 isYear = TRUE;
2258 }
2259 else
2260 {
2261 isMonth = TRUE;
2262 }
2263 }
2264 else // it may be day
2265 {
2266 isDay = TRUE;
2267
2268 // check the range
2269 if ( haveMon )
2270 {
2271 if ( val > GetNumOfDaysInMonth(haveYear ? year
2272 : Inv_Year,
2273 mon) )
2274 {
2275 // oops, it can't be a day finally
2276 isDay = FALSE;
2277
2278 if ( val > 12 )
2279 {
2280 isYear = TRUE;
2281 }
2282 else
2283 {
2284 isMonth = TRUE;
2285 }
2286 }
2287 }
2288 }
2289 }
2290
2291 // remember that we have this and stop the scan if it's the second
2292 // time we find this, except for the day logic (see there)
2293 if ( isYear )
2294 {
2295 if ( haveYear )
2296 {
2297 break;
2298 }
2299
2300 haveYear = TRUE;
2301
2302 // no roll over - 99 means 99, not 1999 for us
2303 year = val;
2304 }
2305 else if ( isMonth )
2306 {
2307 if ( haveMon )
2308 {
2309 break;
2310 }
2311
2312 haveMon = TRUE;
2313
2314 mon = (wxDateTime::Month)val;
2315 }
2316 else
2317 {
2318 wxASSERT_MSG( isDay, _T("logic error") );
2319
2320 if ( haveDay )
2321 {
2322 // may be were mistaken when we found it for the first
2323 // time? may be it was a month or year instead?
2324 //
2325 // this ability to "backtrack" allows us to correctly parse
2326 // both things like 01/13 and 13/01 - but, of course, we
2327 // still can't resolve the ambiguity in 01/02 (it will be
2328 // Feb 1 for us, not Jan 2 as americans might expect!)
2329 if ( (day <= 12) && !haveMon )
2330 {
2331 // exchange day and month
2332 mon = (wxDateTime::Month)day;
2333
2334 haveMon = TRUE;
2335 }
2336 else if ( !haveYear )
2337 {
2338 // exchange day and year
2339 year = day;
2340
2341 haveYear = TRUE;
2342 }
2343 }
2344
2345 haveDay = TRUE;
2346
2347 day = val;
2348 }
2349 }
2350 else // not a number
2351 {
2352 mon = GetMonthFromName(token);
2353 if ( mon != Inv_Month )
2354 {
2355 // it's a month
2356 if ( haveMon )
2357 {
2358 break;
2359 }
2360
2361 haveMon = TRUE;
2362 }
2363 else
2364 {
2365 wday = GetWeekDayFromName(token);
2366 if ( wday != Inv_WeekDay )
2367 {
2368 // a week day
2369 if ( haveWDay )
2370 {
2371 break;
2372 }
2373
2374 haveWDay = TRUE;
2375 }
2376 else
2377 {
2378 // try the ordinals
2379 static const wxChar *ordinals[] =
2380 {
2381 wxTRANSLATE("first"),
2382 wxTRANSLATE("second"),
2383 wxTRANSLATE("third"),
2384 wxTRANSLATE("fourth"),
2385 wxTRANSLATE("fifth"),
2386 wxTRANSLATE("sixth"),
2387 wxTRANSLATE("seventh"),
2388 wxTRANSLATE("eighth"),
2389 wxTRANSLATE("ninth"),
2390 wxTRANSLATE("tenth"),
2391 wxTRANSLATE("eleventh"),
2392 wxTRANSLATE("twelfth"),
2393 wxTRANSLATE("thirteenth"),
2394 wxTRANSLATE("fourteenth"),
2395 wxTRANSLATE("fifteenth"),
2396 wxTRANSLATE("sixteenth"),
2397 wxTRANSLATE("seventeenth"),
2398 wxTRANSLATE("eighteenth"),
2399 wxTRANSLATE("nineteenth"),
2400 wxTRANSLATE("twentieth"),
2401 // that's enough - otherwise we'd have problems with
2402 // composite (or not) ordinals otherwise
2403 };
2404
2405 size_t n;
2406 for ( n = 0; n < WXSIZEOF(ordinals); n++ )
2407 {
2408 if ( token.CmpNoCase(ordinals[n]) == 0 )
2409 {
2410 break;
2411 }
2412 }
2413
2414 if ( n == WXSIZEOF(ordinals) )
2415 {
2416 // stop here - something unknown
2417 break;
2418 }
2419
2420 // it's a day
2421 if ( haveDay )
2422 {
2423 // don't try anything here (as in case of numeric day
2424 // above) - the symbolic day spec should always
2425 // precede the month/year
2426 break;
2427 }
2428
2429 haveDay = TRUE;
2430
2431 day = n + 1;
2432 }
2433 }
2434 }
2435 }
2436
2437 // either no more tokens or the scan was stopped by something we couldn't
2438 // parse - in any case, see if we can construct a date from what we have
2439 if ( !haveDay && !haveWDay )
2440 {
2441 wxLogDebug(_T("no day, no weekday hence no date."));
2442
2443 return (wxChar *)NULL;
2444 }
2445
2446 if ( haveWDay && (haveMon || haveYear || haveDay) &&
2447 !(haveMon && haveMon && haveYear) )
2448 {
2449 // without adjectives (which we don't support here) the week day only
2450 // makes sense completely separately or with the full date
2451 // specification (what would "Wed 1999" mean?)
2452 return (wxChar *)NULL;
2453 }
2454
2455 if ( !haveMon )
2456 {
2457 mon = GetCurrentMonth();
2458 }
2459
2460 if ( !haveYear )
2461 {
2462 year = GetCurrentYear();
2463 }
2464
2465 if ( haveDay )
2466 {
2467 Set(day, mon, year);
2468
2469 if ( haveWDay )
2470 {
2471 // check that it is really the same
2472 if ( GetWeekDay() != wday )
2473 {
2474 // inconsistency detected
2475 return (wxChar *)NULL;
2476 }
2477 }
2478 }
2479 else // haveWDay
2480 {
2481 *this = Today();
2482
2483 SetToWeekDayInSameWeek(wday);
2484 }
2485
2486 // return the pointer to the next char
2487 return p + wxStrlen(p) - wxStrlen(tok.GetString());
2488 }
2489
2490 const wxChar *wxDateTime::ParseTime(const wxChar *time)
2491 {
2492 wxCHECK_MSG( time, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
2493
2494 // this function should be able to parse different time formats as well as
2495 // timezones (take the code out from ParseRfc822Date()) and AM/PM.
2496 wxFAIL_MSG(_T("TODO"));
2497
2498 return (wxChar *)NULL;
2499 }
2500
2501 // ============================================================================
2502 // wxTimeSpan
2503 // ============================================================================
2504
2505 // not all strftime(3) format specifiers make sense here because, for example,
2506 // a time span doesn't have a year nor a timezone
2507 //
2508 // Here are the ones which are supported (all of them are supported by strftime
2509 // as well):
2510 // %H hour in 24 hour format
2511 // %M minute (00 - 59)
2512 // %S second (00 - 59)
2513 // %% percent sign
2514 //
2515 // Also, for MFC CTimeSpan compatibility, we support
2516 // %D number of days
2517 //
2518 // And, to be better than MFC :-), we also have
2519 // %E number of wEeks
2520 // %l milliseconds (000 - 999)
2521 wxString wxTimeSpan::Format(const wxChar *format) const
2522 {
2523 wxCHECK_MSG( format, _T(""), _T("NULL format in wxTimeSpan::Format") );
2524
2525 wxString str;
2526 str.Alloc(strlen(format));
2527
2528 for ( const wxChar *pch = format; pch; pch++ )
2529 {
2530 wxChar ch = *pch;
2531
2532 if ( ch == '%' )
2533 {
2534 wxString tmp;
2535
2536 ch = *pch++;
2537 switch ( ch )
2538 {
2539 default:
2540 wxFAIL_MSG( _T("invalid format character") );
2541 // fall through
2542
2543 case '%':
2544 // will get to str << ch below
2545 break;
2546
2547 case 'D':
2548 tmp.Printf(_T("%d"), GetDays());
2549 break;
2550
2551 case 'E':
2552 tmp.Printf(_T("%d"), GetWeeks());
2553 break;
2554
2555 case 'H':
2556 tmp.Printf(_T("%02d"), GetHours());
2557 break;
2558
2559 case 'l':
2560 tmp.Printf(_T("%03ld"), GetMilliseconds().ToLong());
2561 break;
2562
2563 case 'M':
2564 tmp.Printf(_T("%02d"), GetMinutes());
2565 break;
2566
2567 case 'S':
2568 tmp.Printf(_T("%02ld"), GetSeconds().ToLong());
2569 break;
2570 }
2571
2572 if ( !!tmp )
2573 {
2574 str += tmp;
2575
2576 // skip str += ch below
2577 continue;
2578 }
2579 }
2580
2581 str += ch;
2582 }
2583
2584 return str;
2585 }