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