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