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