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