]> git.saurik.com Git - wxWidgets.git/blame - src/common/datetime.cpp
correct idle tag type; remove unneeded includes
[wxWidgets.git] / src / common / datetime.cpp
CommitLineData
1ef54dcf 1///////////////////////////////////////////////////////////////////////////////
96531a71 2// Name: src/common/datetime.cpp
0979c962
VZ
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//
65571936 16// Licence: wxWindows licence
1ef54dcf
VZ
17///////////////////////////////////////////////////////////////////////////////
18
e44501a0
VZ
19// TODO: for $DEITY sake, someone please fix the #ifdef __WXWINCE__ everywhere,
20// the proper way to do it is to implement (subset of) wxStrftime() for
21// CE instead of this horror!!
22
1ef54dcf
VZ
23/*
24 * Implementation notes:
25 *
26 * 1. the time is stored as a 64bit integer containing the signed number of
299fcbfe
VZ
27 * milliseconds since Jan 1. 1970 (the Unix Epoch) - so it is always
28 * expressed in GMT.
1ef54dcf
VZ
29 *
30 * 2. the range is thus something about 580 million years, but due to current
31 * algorithms limitations, only dates from Nov 24, 4714BC are handled
32 *
33 * 3. standard ANSI C functions are used to do time calculations whenever
34 * possible, i.e. when the date is in the range Jan 1, 1970 to 2038
35 *
36 * 4. otherwise, the calculations are done by converting the date to/from JDN
37 * first (the range limitation mentioned above comes from here: the
38 * algorithm used by Scott E. Lee's code only works for positive JDNs, more
39 * or less)
40 *
299fcbfe
VZ
41 * 5. the object constructed for the given DD-MM-YYYY HH:MM:SS corresponds to
42 * this moment in local time and may be converted to the object
43 * corresponding to the same date/time in another time zone by using
44 * ToTimezone()
45 *
46 * 6. the conversions to the current (or any other) timezone are done when the
47 * internal time representation is converted to the broken-down one in
48 * wxDateTime::Tm.
1ef54dcf 49 */
0979c962
VZ
50
51// ============================================================================
52// declarations
53// ============================================================================
54
55// ----------------------------------------------------------------------------
56// headers
57// ----------------------------------------------------------------------------
58
0979c962
VZ
59// For compilers that support precompilation, includes "wx.h".
60#include "wx/wxprec.h"
61
62#ifdef __BORLANDC__
63 #pragma hdrstop
64#endif
65
1e6feb95
VZ
66#if !defined(wxUSE_DATETIME) || wxUSE_DATETIME
67
0979c962
VZ
68#ifndef WX_PRECOMP
69 #include "wx/string.h"
0979c962 70 #include "wx/log.h"
88a7a4e1 71 #include "wx/intl.h"
bb90a3e6 72 #include "wx/stopwatch.h" // for wxGetLocalTimeMillis()
0979c962
VZ
73#endif // WX_PRECOMP
74
fcc3d7cb 75#include "wx/thread.h"
cd0b1709 76#include "wx/tokenzr.h"
7281ab04 77#include "wx/module.h"
fcc3d7cb 78
ff0ea71c
GT
79#include <ctype.h>
80
64f9c100
VS
81#ifdef __WINDOWS__
82 #include "wx/msw/wrapwin.h"
83 #include <winnls.h>
74b99d0f
WS
84 #ifndef __WXWINCE__
85 #include <locale.h>
86 #endif
64f9c100
VS
87#endif
88
0979c962
VZ
89#include "wx/datetime.h"
90
bd4a25bc
JS
91const long wxDateTime::TIME_T_FACTOR = 1000l;
92
cb73e600
SC
93#if wxUSE_EXTENDED_RTTI
94
95template<> void wxStringReadValue(const wxString &s , wxDateTime &data )
96{
97 data.ParseFormat(s,wxT("%Y-%m-%d %H:%M:%S")) ;
98}
99
100template<> void wxStringWriteValue(wxString &s , const wxDateTime &data )
101{
102 s = data.Format(wxT("%Y-%m-%d %H:%M:%S")) ;
103}
104
8805dbab 105wxCUSTOM_TYPE_INFO(wxDateTime, wxToStringConverter<wxDateTime> , wxFromStringConverter<wxDateTime>)
cb73e600
SC
106
107#endif
108
109//
f0f951fa
VZ
110// ----------------------------------------------------------------------------
111// conditional compilation
112// ----------------------------------------------------------------------------
6de20863 113
2423ef22
VZ
114#if defined(HAVE_STRPTIME) && defined(__GLIBC__) && \
115 ((__GLIBC__ == 2) && (__GLIBC_MINOR__ == 0))
f0f951fa
VZ
116 // glibc 2.0.7 strptime() is broken - the following snippet causes it to
117 // crash (instead of just failing):
118 //
119 // strncpy(buf, "Tue Dec 21 20:25:40 1999", 128);
120 // strptime(buf, "%x", &tm);
121 //
122 // so don't use it
123 #undef HAVE_STRPTIME
124#endif // broken strptime()
125
dcbd3762
DE
126#if defined(HAVE_STRPTIME) && defined(__DARWIN__) && defined(_MSL_USING_MW_C_HEADERS) && _MSL_USING_MW_C_HEADERS
127 // configure detects strptime as linkable because it's in the OS X
128 // System library but MSL headers don't declare it.
7347d453
DE
129
130// char *strptime(const char *, const char *, struct tm *);
131 // However, we DON'T want to just provide it here because we would
132 // crash and/or overwrite data when strptime from OS X tries
133 // to fill in MW's struct tm which is two fields shorter (no TZ stuff)
134 // So for now let's just say we don't have strptime
135 #undef HAVE_STRPTIME
dcbd3762
DE
136#endif
137
31907d03
SC
138#if defined(__MWERKS__) && wxUSE_UNICODE
139 #include <wtime.h>
140#endif
141
0779cc9a 142#if !defined(WX_TIMEZONE) && !defined(WX_GMTOFF_IN_TM)
6a27c749
WS
143 #if defined(__WXPALMOS__)
144 #define WX_GMTOFF_IN_TM
145 #elif defined(__BORLANDC__) || defined(__MINGW32__) || defined(__VISAGECPP__)
f0f951fa 146 #define WX_TIMEZONE _timezone
8208e181 147 #elif defined(__MWERKS__)
f6bcfd97 148 long wxmw_timezone = 28800;
c4e1b7f2 149 #define WX_TIMEZONE wxmw_timezone
5283098e 150 #elif defined(__DJGPP__) || defined(__WINE__)
713a0efc 151 #include <sys/timeb.h>
c4e1b7f2 152 #include <values.h>
713a0efc
VS
153 static long wxGetTimeZone()
154 {
c4e1b7f2
VS
155 static long timezone = MAXLONG; // invalid timezone
156 if (timezone == MAXLONG)
157 {
158 struct timeb tb;
159 ftime(&tb);
160 timezone = tb.timezone;
161 }
162 return timezone;
713a0efc
VS
163 }
164 #define WX_TIMEZONE wxGetTimeZone()
0779cc9a
GD
165 #elif defined(__DARWIN__)
166 #define WX_GMTOFF_IN_TM
f0f951fa
VZ
167 #else // unknown platform - try timezone
168 #define WX_TIMEZONE timezone
169 #endif
0779cc9a 170#endif // !WX_TIMEZONE && !WX_GMTOFF_IN_TM
c25a510b 171
5b52a684 172#if (!defined(HAVE_LOCALTIME_R) || !defined(HAVE_GMTIME_R)) && wxUSE_THREADS && !defined(__WINDOWS__)
cf44a61c
SN
173static wxMutex timeLock;
174#endif
175
176#ifndef HAVE_LOCALTIME_R
177struct tm *wxLocaltime_r(const time_t* ticks, struct tm* temp)
178{
179#if wxUSE_THREADS && !defined(__WINDOWS__)
180 // No need to waste time with a mutex on windows since it's using
181 // thread local storage for localtime anyway.
6f721331 182 wxMutexLocker locker(timeLock);
cf44a61c
SN
183#endif
184 memcpy(temp, localtime(ticks), sizeof(struct tm));
185 return temp;
186}
187#endif
188
189#ifndef HAVE_GMTIME_R
190struct tm *wxGmtime_r(const time_t* ticks, struct tm* temp)
191{
192#if wxUSE_THREADS && !defined(__WINDOWS__)
193 // No need to waste time with a mutex on windows since it's
194 // using thread local storage for gmtime anyway.
6f721331 195 wxMutexLocker locker(timeLock);
cf44a61c
SN
196#endif
197 memcpy(temp, gmtime(ticks), sizeof(struct tm));
198 return temp;
199}
200#endif
201
384223b3
VZ
202// ----------------------------------------------------------------------------
203// macros
204// ----------------------------------------------------------------------------
205
206// debugging helper: just a convenient replacement of wxCHECK()
637b7e4f
VZ
207#define wxDATETIME_CHECK(expr, msg) \
208 wxCHECK2_MSG(expr, *this = wxInvalidDateTime; return *this, msg)
384223b3 209
4f6aed9c
VZ
210// ----------------------------------------------------------------------------
211// private classes
212// ----------------------------------------------------------------------------
213
214class wxDateTimeHolidaysModule : public wxModule
215{
216public:
217 virtual bool OnInit()
218 {
219 wxDateTimeHolidayAuthority::AddAuthority(new wxDateTimeWorkDays);
220
68379eaf 221 return true;
4f6aed9c
VZ
222 }
223
224 virtual void OnExit()
225 {
226 wxDateTimeHolidayAuthority::ClearAllAuthorities();
df5168c4 227 wxDateTimeHolidayAuthority::ms_authorities.clear();
4f6aed9c
VZ
228 }
229
230private:
231 DECLARE_DYNAMIC_CLASS(wxDateTimeHolidaysModule)
232};
233
234IMPLEMENT_DYNAMIC_CLASS(wxDateTimeHolidaysModule, wxModule)
235
b76b015e
VZ
236// ----------------------------------------------------------------------------
237// constants
238// ----------------------------------------------------------------------------
239
e6ec579c 240// some trivial ones
fcc3d7cb
VZ
241static const int MONTHS_IN_YEAR = 12;
242
df05cdc5
VZ
243static const int SEC_PER_MIN = 60;
244
245static const int MIN_PER_HOUR = 60;
246
247static const int HOURS_PER_DAY = 24;
fcc3d7cb 248
e6ec579c
VZ
249static const long SECONDS_PER_DAY = 86400l;
250
df05cdc5
VZ
251static const int DAYS_PER_WEEK = 7;
252
e6ec579c
VZ
253static const long MILLISECONDS_PER_DAY = 86400000l;
254
255// this is the integral part of JDN of the midnight of Jan 1, 1970
256// (i.e. JDN(Jan 1, 1970) = 2440587.5)
cd0b1709 257static const long EPOCH_JDN = 2440587l;
b76b015e 258
9ffaedfc
VZ
259// used only in asserts
260#ifdef __WXDEBUG__
1ef54dcf
VZ
261// the date of JDN -0.5 (as we don't work with fractional parts, this is the
262// reference date for us) is Nov 24, 4714BC
263static const int JDN_0_YEAR = -4713;
264static const int JDN_0_MONTH = wxDateTime::Nov;
265static const int JDN_0_DAY = 24;
9ffaedfc 266#endif // __WXDEBUG__
1ef54dcf
VZ
267
268// the constants used for JDN calculations
cd0b1709
VZ
269static const long JDN_OFFSET = 32046l;
270static const long DAYS_PER_5_MONTHS = 153l;
271static const long DAYS_PER_4_YEARS = 1461l;
272static const long DAYS_PER_400_YEARS = 146097l;
1ef54dcf 273
f0f951fa
VZ
274// this array contains the cumulated number of days in all previous months for
275// normal and leap years
276static const wxDateTime::wxDateTime_t gs_cumulatedDays[2][MONTHS_IN_YEAR] =
277{
278 { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 },
279 { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 }
280};
281
fcc3d7cb 282// ----------------------------------------------------------------------------
2ef31e80
VZ
283// global data
284// ----------------------------------------------------------------------------
285
1aaf88d2
WS
286const wxChar * wxDefaultDateTimeFormat = wxT("%c");
287const wxChar * wxDefaultTimeSpanFormat = wxT("%H:%M:%S");
288
384223b3
VZ
289// in the fine tradition of ANSI C we use our equivalent of (time_t)-1 to
290// indicate an invalid wxDateTime object
56d0c5a6 291const wxDateTime wxDefaultDateTime;
2ef31e80
VZ
292
293wxDateTime::Country wxDateTime::ms_country = wxDateTime::Country_Unknown;
294
b76b015e
VZ
295// ----------------------------------------------------------------------------
296// private functions
297// ----------------------------------------------------------------------------
298
4f6aed9c
VZ
299// debugger helper: shows what the date really is
300#ifdef __WXDEBUG__
301extern const wxChar *wxDumpDate(const wxDateTime* dt)
302{
0de868d9 303 static wxChar buf[128];
4f6aed9c
VZ
304
305 wxStrcpy(buf, dt->Format(_T("%Y-%m-%d (%a) %H:%M:%S")));
306
307 return buf;
308}
309#endif // Debug
310
fcc3d7cb
VZ
311// get the number of days in the given month of the given year
312static inline
313wxDateTime::wxDateTime_t GetNumOfDaysInMonth(int year, wxDateTime::Month month)
314{
e6ec579c
VZ
315 // the number of days in month in Julian/Gregorian calendar: the first line
316 // is for normal years, the second one is for the leap ones
317 static wxDateTime::wxDateTime_t daysInMonth[2][MONTHS_IN_YEAR] =
318 {
319 { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
320 { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
321 };
322
323 return daysInMonth[wxDateTime::IsLeapYear(year)][month];
fcc3d7cb
VZ
324}
325
c5432233
VZ
326// returns the time zone in the C sense, i.e. the difference UTC - local
327// (in seconds)
fcc3d7cb
VZ
328static int GetTimeZone()
329{
68379eaf
WS
330 // set to true when the timezone is set
331 static bool s_timezoneSet = false;
0779cc9a 332 static long gmtoffset = LONG_MAX; // invalid timezone
c5432233 333
a452689b 334 // ensure that the timezone variable is set by calling wxLocaltime_r
fcc3d7cb
VZ
335 if ( !s_timezoneSet )
336 {
a452689b
SN
337 // just call wxLocaltime_r() instead of figuring out whether this
338 // system supports tzset(), _tzset() or something else
c5432233 339 time_t t = 0;
3d561414 340 struct tm tm;
fcc3d7cb 341
3d561414 342 wxLocaltime_r(&t, &tm);
68379eaf 343 s_timezoneSet = true;
c5432233 344
44c930eb 345#ifdef WX_GMTOFF_IN_TM
c5432233
VZ
346 // note that GMT offset is the opposite of time zone and so to return
347 // consistent results in both WX_GMTOFF_IN_TM and !WX_GMTOFF_IN_TM
348 // cases we have to negate it
3d561414 349 gmtoffset = -tm.tm_gmtoff;
44c930eb
JG
350#else // !WX_GMTOFF_IN_TM
351 gmtoffset = WX_TIMEZONE;
352#endif // WX_GMTOFF_IN_TM/!WX_GMTOFF_IN_TM
fcc3d7cb
VZ
353 }
354
0779cc9a 355 return (int)gmtoffset;
fcc3d7cb
VZ
356}
357
e6ec579c 358// return the integral part of the JDN for the midnight of the given date (to
1ef54dcf
VZ
359// get the real JDN you need to add 0.5, this is, in fact, JDN of the
360// noon of the previous day)
e6ec579c
VZ
361static long GetTruncatedJDN(wxDateTime::wxDateTime_t day,
362 wxDateTime::Month mon,
363 int year)
364{
1ef54dcf
VZ
365 // CREDIT: code below is by Scott E. Lee (but bugs are mine)
366
367 // check the date validity
368 wxASSERT_MSG(
369 (year > JDN_0_YEAR) ||
370 ((year == JDN_0_YEAR) && (mon > JDN_0_MONTH)) ||
371 ((year == JDN_0_YEAR) && (mon == JDN_0_MONTH) && (day >= JDN_0_DAY)),
372 _T("date out of range - can't convert to JDN")
373 );
374
375 // make the year positive to avoid problems with negative numbers division
376 year += 4800;
377
378 // months are counted from March here
379 int month;
380 if ( mon >= wxDateTime::Mar )
e6ec579c 381 {
1ef54dcf 382 month = mon - 2;
e6ec579c 383 }
1ef54dcf 384 else
e6ec579c 385 {
1ef54dcf
VZ
386 month = mon + 10;
387 year--;
388 }
e6ec579c 389
1ef54dcf
VZ
390 // now we can simply add all the contributions together
391 return ((year / 100) * DAYS_PER_400_YEARS) / 4
392 + ((year % 100) * DAYS_PER_4_YEARS) / 4
393 + (month * DAYS_PER_5_MONTHS + 2) / 5
394 + day
395 - JDN_OFFSET;
e6ec579c
VZ
396}
397
74b99d0f 398#ifndef __WXWINCE__
2423ef22 399// this function is a wrapper around strftime(3) adding error checking
b76b015e
VZ
400static wxString CallStrftime(const wxChar *format, const tm* tm)
401{
68ee7c47 402 wxChar buf[4096];
594588aa
MW
403 // Create temp wxString here to work around mingw/cygwin bug 1046059
404 // http://sourceforge.net/tracker/?func=detail&atid=102435&aid=1046059&group_id=2435
405 wxString s;
406
ba9bbf13 407 if ( !wxStrftime(buf, WXSIZEOF(buf), format, tm) )
b76b015e 408 {
68ee7c47 409 // buffer is too small?
b76b015e
VZ
410 wxFAIL_MSG(_T("strftime() failed"));
411 }
412
594588aa
MW
413 s = buf;
414 return s;
b76b015e 415}
74b99d0f 416#endif
b76b015e 417
2423ef22
VZ
418#ifdef HAVE_STRPTIME
419
f536f005
VZ
420#if wxUSE_UNIX && !defined(HAVE_STRPTIME_DECL)
421 // configure detected that we had strptime() but not its declaration,
422 // provide it ourselves
e2620fda
VZ
423 extern "C" char *strptime(const char *, const char *, struct tm *);
424#endif
425
2423ef22
VZ
426// Unicode-friendly strptime() wrapper
427static const wxChar *
428CallStrptime(const wxChar *input, const char *fmt, tm *tm)
429{
430 // the problem here is that strptime() returns pointer into the string we
431 // passed to it while we're really interested in the pointer into the
432 // original, Unicode, string so we try to transform the pointer back
433#if wxUSE_UNICODE
434 wxCharBuffer inputMB(wxConvertWX2MB(input));
435#else // ASCII
436 const char * const inputMB = input;
437#endif // Unicode/Ascii
438
439 const char *result = strptime(inputMB, fmt, tm);
440 if ( !result )
441 return NULL;
442
443#if wxUSE_UNICODE
444 // FIXME: this is wrong in presence of surrogates &c
445 return input + (result - inputMB.data());
446#else // ASCII
447 return result;
448#endif // Unicode/Ascii
449}
450
451#endif // HAVE_STRPTIME
452
2f02cb89
VZ
453// if year and/or month have invalid values, replace them with the current ones
454static void ReplaceDefaultYearMonthWithCurrent(int *year,
455 wxDateTime::Month *month)
456{
457 struct tm *tmNow = NULL;
a452689b 458 struct tm tmstruct;
2f02cb89
VZ
459
460 if ( *year == wxDateTime::Inv_Year )
461 {
a452689b 462 tmNow = wxDateTime::GetTmNow(&tmstruct);
2f02cb89
VZ
463
464 *year = 1900 + tmNow->tm_year;
465 }
466
467 if ( *month == wxDateTime::Inv_Month )
468 {
469 if ( !tmNow )
a452689b 470 tmNow = wxDateTime::GetTmNow(&tmstruct);
2f02cb89
VZ
471
472 *month = (wxDateTime::Month)tmNow->tm_mon;
473 }
474}
475
f0f951fa
VZ
476// fll the struct tm with default values
477static void InitTm(struct tm& tm)
478{
479 // struct tm may have etxra fields (undocumented and with unportable
480 // names) which, nevertheless, must be set to 0
481 memset(&tm, 0, sizeof(struct tm));
482
483 tm.tm_mday = 1; // mday 0 is invalid
484 tm.tm_year = 76; // any valid year
485 tm.tm_isdst = -1; // auto determine
486}
487
cd0b1709
VZ
488// parsing helpers
489// ---------------
490
491// return the month if the string is a month name or Inv_Month otherwise
f0f951fa 492static wxDateTime::Month GetMonthFromName(const wxString& name, int flags)
cd0b1709
VZ
493{
494 wxDateTime::Month mon;
495 for ( mon = wxDateTime::Jan; mon < wxDateTime::Inv_Month; wxNextMonth(mon) )
496 {
f0f951fa
VZ
497 // case-insensitive comparison either one of or with both abbreviated
498 // and not versions
499 if ( flags & wxDateTime::Name_Full )
cd0b1709 500 {
f0f951fa
VZ
501 if ( name.CmpNoCase(wxDateTime::
502 GetMonthName(mon, wxDateTime::Name_Full)) == 0 )
503 {
504 break;
505 }
506 }
507
508 if ( flags & wxDateTime::Name_Abbr )
509 {
510 if ( name.CmpNoCase(wxDateTime::
511 GetMonthName(mon, wxDateTime::Name_Abbr)) == 0 )
512 {
513 break;
514 }
cd0b1709
VZ
515 }
516 }
517
518 return mon;
519}
520
521// return the weekday if the string is a weekday name or Inv_WeekDay otherwise
f0f951fa 522static wxDateTime::WeekDay GetWeekDayFromName(const wxString& name, int flags)
cd0b1709
VZ
523{
524 wxDateTime::WeekDay wd;
525 for ( wd = wxDateTime::Sun; wd < wxDateTime::Inv_WeekDay; wxNextWDay(wd) )
526 {
f0f951fa
VZ
527 // case-insensitive comparison either one of or with both abbreviated
528 // and not versions
529 if ( flags & wxDateTime::Name_Full )
cd0b1709 530 {
f0f951fa
VZ
531 if ( name.CmpNoCase(wxDateTime::
532 GetWeekDayName(wd, wxDateTime::Name_Full)) == 0 )
533 {
534 break;
535 }
536 }
537
538 if ( flags & wxDateTime::Name_Abbr )
539 {
540 if ( name.CmpNoCase(wxDateTime::
541 GetWeekDayName(wd, wxDateTime::Name_Abbr)) == 0 )
542 {
543 break;
544 }
cd0b1709
VZ
545 }
546 }
547
548 return wd;
549}
550
a452689b
SN
551/* static */
552struct tm *wxDateTime::GetTmNow(struct tm *tmstruct)
553{
554 time_t t = GetTimeNow();
555 return wxLocaltime_r(&t, tmstruct);
556}
557
f6bcfd97
BP
558// scans all digits (but no more than len) and returns the resulting number
559static bool GetNumericToken(size_t len, const wxChar*& p, unsigned long *number)
f0f951fa 560{
f6bcfd97 561 size_t n = 1;
f0f951fa
VZ
562 wxString s;
563 while ( wxIsdigit(*p) )
564 {
565 s += *p++;
f6bcfd97
BP
566
567 if ( len && ++n > len )
568 break;
f0f951fa
VZ
569 }
570
bb8a8478 571 return !s.empty() && s.ToULong(number);
f0f951fa
VZ
572}
573
574// scans all alphabetic characters and returns the resulting string
575static wxString GetAlphaToken(const wxChar*& p)
576{
577 wxString s;
578 while ( wxIsalpha(*p) )
579 {
580 s += *p++;
581 }
582
583 return s;
584}
585
0979c962
VZ
586// ============================================================================
587// implementation of wxDateTime
588// ============================================================================
589
b76b015e
VZ
590// ----------------------------------------------------------------------------
591// struct Tm
592// ----------------------------------------------------------------------------
593
594wxDateTime::Tm::Tm()
595{
596 year = (wxDateTime_t)wxDateTime::Inv_Year;
597 mon = wxDateTime::Inv_Month;
598 mday = 0;
e6ec579c 599 hour = min = sec = msec = 0;
b76b015e
VZ
600 wday = wxDateTime::Inv_WeekDay;
601}
602
299fcbfe
VZ
603wxDateTime::Tm::Tm(const struct tm& tm, const TimeZone& tz)
604 : m_tz(tz)
b76b015e 605{
e6ec579c 606 msec = 0;
907173e5
WS
607 sec = (wxDateTime::wxDateTime_t)tm.tm_sec;
608 min = (wxDateTime::wxDateTime_t)tm.tm_min;
609 hour = (wxDateTime::wxDateTime_t)tm.tm_hour;
610 mday = (wxDateTime::wxDateTime_t)tm.tm_mday;
fcc3d7cb 611 mon = (wxDateTime::Month)tm.tm_mon;
b76b015e 612 year = 1900 + tm.tm_year;
bb8a8478
WS
613 wday = (wxDateTime::wxDateTime_t)tm.tm_wday;
614 yday = (wxDateTime::wxDateTime_t)tm.tm_yday;
b76b015e
VZ
615}
616
617bool wxDateTime::Tm::IsValid() const
618{
619 // we allow for the leap seconds, although we don't use them (yet)
fcc3d7cb 620 return (year != wxDateTime::Inv_Year) && (mon != wxDateTime::Inv_Month) &&
c5a1681b 621 (mday <= GetNumOfDaysInMonth(year, mon)) &&
e6ec579c 622 (hour < 24) && (min < 60) && (sec < 62) && (msec < 1000);
b76b015e
VZ
623}
624
625void wxDateTime::Tm::ComputeWeekDay()
626{
c5a1681b
VZ
627 // compute the week day from day/month/year: we use the dumbest algorithm
628 // possible: just compute our JDN and then use the (simple to derive)
629 // formula: weekday = (JDN + 1.5) % 7
bb8a8478 630 wday = (wxDateTime::wxDateTime_t)((wxDateTime::WeekDay)(GetTruncatedJDN(mday, mon, year) + 2) % 7);
b76b015e
VZ
631}
632
e6ec579c 633void wxDateTime::Tm::AddMonths(int monDiff)
fcc3d7cb
VZ
634{
635 // normalize the months field
636 while ( monDiff < -mon )
637 {
638 year--;
639
640 monDiff += MONTHS_IN_YEAR;
641 }
642
2ef31e80 643 while ( monDiff + mon >= MONTHS_IN_YEAR )
fcc3d7cb
VZ
644 {
645 year++;
239446b4
VZ
646
647 monDiff -= MONTHS_IN_YEAR;
fcc3d7cb
VZ
648 }
649
650 mon = (wxDateTime::Month)(mon + monDiff);
651
e6ec579c 652 wxASSERT_MSG( mon >= 0 && mon < MONTHS_IN_YEAR, _T("logic error") );
4f6aed9c
VZ
653
654 // NB: we don't check here that the resulting date is valid, this function
655 // is private and the caller must check it if needed
fcc3d7cb
VZ
656}
657
e6ec579c 658void wxDateTime::Tm::AddDays(int dayDiff)
fcc3d7cb
VZ
659{
660 // normalize the days field
9d9b7755 661 while ( dayDiff + mday < 1 )
fcc3d7cb
VZ
662 {
663 AddMonths(-1);
664
9d9b7755 665 dayDiff += GetNumOfDaysInMonth(year, mon);
fcc3d7cb
VZ
666 }
667
907173e5 668 mday = (wxDateTime::wxDateTime_t)( mday + dayDiff );
fcc3d7cb
VZ
669 while ( mday > GetNumOfDaysInMonth(year, mon) )
670 {
671 mday -= GetNumOfDaysInMonth(year, mon);
672
673 AddMonths(1);
674 }
675
676 wxASSERT_MSG( mday > 0 && mday <= GetNumOfDaysInMonth(year, mon),
677 _T("logic error") );
678}
679
680// ----------------------------------------------------------------------------
681// class TimeZone
682// ----------------------------------------------------------------------------
683
684wxDateTime::TimeZone::TimeZone(wxDateTime::TZ tz)
685{
686 switch ( tz )
687 {
688 case wxDateTime::Local:
299fcbfe
VZ
689 // get the offset from C RTL: it returns the difference GMT-local
690 // while we want to have the offset _from_ GMT, hence the '-'
691 m_offset = -GetTimeZone();
fcc3d7cb
VZ
692 break;
693
694 case wxDateTime::GMT_12:
695 case wxDateTime::GMT_11:
696 case wxDateTime::GMT_10:
697 case wxDateTime::GMT_9:
698 case wxDateTime::GMT_8:
699 case wxDateTime::GMT_7:
700 case wxDateTime::GMT_6:
701 case wxDateTime::GMT_5:
702 case wxDateTime::GMT_4:
703 case wxDateTime::GMT_3:
704 case wxDateTime::GMT_2:
705 case wxDateTime::GMT_1:
299fcbfe 706 m_offset = -3600*(wxDateTime::GMT0 - tz);
fcc3d7cb
VZ
707 break;
708
709 case wxDateTime::GMT0:
710 case wxDateTime::GMT1:
711 case wxDateTime::GMT2:
712 case wxDateTime::GMT3:
713 case wxDateTime::GMT4:
714 case wxDateTime::GMT5:
715 case wxDateTime::GMT6:
716 case wxDateTime::GMT7:
717 case wxDateTime::GMT8:
718 case wxDateTime::GMT9:
719 case wxDateTime::GMT10:
720 case wxDateTime::GMT11:
721 case wxDateTime::GMT12:
299fcbfe 722 m_offset = 3600*(tz - wxDateTime::GMT0);
fcc3d7cb
VZ
723 break;
724
725 case wxDateTime::A_CST:
726 // Central Standard Time in use in Australia = UTC + 9.5
d26adb9d 727 m_offset = 60l*(9*MIN_PER_HOUR + MIN_PER_HOUR/2);
fcc3d7cb
VZ
728 break;
729
730 default:
731 wxFAIL_MSG( _T("unknown time zone") );
732 }
733}
734
b76b015e
VZ
735// ----------------------------------------------------------------------------
736// static functions
737// ----------------------------------------------------------------------------
738
739/* static */
740bool wxDateTime::IsLeapYear(int year, wxDateTime::Calendar cal)
741{
2f02cb89
VZ
742 if ( year == Inv_Year )
743 year = GetCurrentYear();
744
b76b015e
VZ
745 if ( cal == Gregorian )
746 {
747 // in Gregorian calendar leap years are those divisible by 4 except
748 // those divisible by 100 unless they're also divisible by 400
749 // (in some countries, like Russia and Greece, additional corrections
750 // exist, but they won't manifest themselves until 2700)
751 return (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0));
752 }
753 else if ( cal == Julian )
754 {
755 // in Julian calendar the rule is simpler
756 return year % 4 == 0;
757 }
758 else
759 {
760 wxFAIL_MSG(_T("unknown calendar"));
761
68379eaf 762 return false;
b76b015e
VZ
763 }
764}
765
fcc3d7cb
VZ
766/* static */
767int wxDateTime::GetCentury(int year)
768{
769 return year > 0 ? year / 100 : year / 100 - 1;
770}
771
b76b015e
VZ
772/* static */
773int wxDateTime::ConvertYearToBC(int year)
774{
775 // year 0 is BC 1
776 return year > 0 ? year : year - 1;
777}
778
779/* static */
780int wxDateTime::GetCurrentYear(wxDateTime::Calendar cal)
781{
782 switch ( cal )
783 {
784 case Gregorian:
785 return Now().GetYear();
786
787 case Julian:
788 wxFAIL_MSG(_T("TODO"));
789 break;
790
791 default:
792 wxFAIL_MSG(_T("unsupported calendar"));
793 break;
794 }
795
796 return Inv_Year;
797}
798
799/* static */
800wxDateTime::Month wxDateTime::GetCurrentMonth(wxDateTime::Calendar cal)
801{
802 switch ( cal )
803 {
804 case Gregorian:
805 return Now().GetMonth();
b76b015e
VZ
806
807 case Julian:
808 wxFAIL_MSG(_T("TODO"));
809 break;
810
811 default:
812 wxFAIL_MSG(_T("unsupported calendar"));
813 break;
814 }
815
816 return Inv_Month;
817}
818
2f02cb89
VZ
819/* static */
820wxDateTime::wxDateTime_t wxDateTime::GetNumberOfDays(int year, Calendar cal)
821{
822 if ( year == Inv_Year )
823 {
824 // take the current year if none given
825 year = GetCurrentYear();
826 }
827
828 switch ( cal )
829 {
830 case Gregorian:
831 case Julian:
832 return IsLeapYear(year) ? 366 : 365;
2f02cb89
VZ
833
834 default:
835 wxFAIL_MSG(_T("unsupported calendar"));
836 break;
837 }
838
839 return 0;
840}
841
b76b015e
VZ
842/* static */
843wxDateTime::wxDateTime_t wxDateTime::GetNumberOfDays(wxDateTime::Month month,
844 int year,
845 wxDateTime::Calendar cal)
846{
fcc3d7cb 847 wxCHECK_MSG( month < MONTHS_IN_YEAR, 0, _T("invalid month") );
b76b015e
VZ
848
849 if ( cal == Gregorian || cal == Julian )
850 {
851 if ( year == Inv_Year )
852 {
853 // take the current year if none given
854 year = GetCurrentYear();
855 }
856
fcc3d7cb 857 return GetNumOfDaysInMonth(year, month);
b76b015e
VZ
858 }
859 else
860 {
861 wxFAIL_MSG(_T("unsupported calendar"));
862
863 return 0;
864 }
865}
866
867/* static */
f0f951fa
VZ
868wxString wxDateTime::GetMonthName(wxDateTime::Month month,
869 wxDateTime::NameFlags flags)
b76b015e 870{
525d8583 871 wxCHECK_MSG( month != Inv_Month, wxEmptyString, _T("invalid month") );
77b88df2 872#ifndef __WXWINCE__
68ee7c47
VZ
873 // notice that we must set all the fields to avoid confusing libc (GNU one
874 // gets confused to a crash if we don't do this)
cd0b1709 875 tm tm;
f0f951fa 876 InitTm(tm);
cd0b1709 877 tm.tm_mon = month;
b76b015e 878
f0f951fa 879 return CallStrftime(flags == Name_Abbr ? _T("%b") : _T("%B"), &tm);
77b88df2 880#else
ba9bbf13
WS
881 wxString ret;
882 switch(month)
883 {
884 case Jan:
885 ret = (flags == Name_Abbr ? wxT("Jan"): wxT("January"));
886 break;
887 case Feb:
888 ret = (flags == Name_Abbr ? wxT("Feb"): wxT("Febuary"));
889 break;
890 case Mar:
891 ret = (flags == Name_Abbr ? wxT("Mar"): wxT("March"));
892 break;
893 case Apr:
894 ret = (flags == Name_Abbr ? wxT("Apr"): wxT("April"));
895 break;
896 case May:
897 ret = (flags == Name_Abbr ? wxT("May"): wxT("May"));
898 break;
899 case Jun:
900 ret = (flags == Name_Abbr ? wxT("Jun"): wxT("June"));
901 break;
902 case Jul:
903 ret = (flags == Name_Abbr ? wxT("Jul"): wxT("July"));
904 break;
905 case Aug:
906 ret = (flags == Name_Abbr ? wxT("Aug"): wxT("August"));
907 break;
908 case Sep:
909 ret = (flags == Name_Abbr ? wxT("Sep"): wxT("September"));
910 break;
911 case Oct:
912 ret = (flags == Name_Abbr ? wxT("Oct"): wxT("October"));
913 break;
914 case Nov:
915 ret = (flags == Name_Abbr ? wxT("Nov"): wxT("November"));
916 break;
917 case Dec:
918 ret = (flags == Name_Abbr ? wxT("Dec"): wxT("December"));
919 break;
920 }
921 return ret;
77b88df2 922#endif
b76b015e
VZ
923}
924
925/* static */
f0f951fa
VZ
926wxString wxDateTime::GetWeekDayName(wxDateTime::WeekDay wday,
927 wxDateTime::NameFlags flags)
b76b015e 928{
525d8583 929 wxCHECK_MSG( wday != Inv_WeekDay, wxEmptyString, _T("invalid weekday") );
77b88df2 930#ifndef __WXWINCE__
2c622d71
VZ
931 // take some arbitrary Sunday (but notice that the day should be such that
932 // after adding wday to it below we still have a valid date, e.g. don't
933 // take 28 here!)
f0f951fa
VZ
934 tm tm;
935 InitTm(tm);
2c622d71 936 tm.tm_mday = 21;
f0f951fa
VZ
937 tm.tm_mon = Nov;
938 tm.tm_year = 99;
b76b015e 939
1ef54dcf 940 // and offset it by the number of days needed to get the correct wday
b76b015e
VZ
941 tm.tm_mday += wday;
942
c5a1681b
VZ
943 // call mktime() to normalize it...
944 (void)mktime(&tm);
945
946 // ... and call strftime()
f0f951fa 947 return CallStrftime(flags == Name_Abbr ? _T("%a") : _T("%A"), &tm);
77b88df2 948#else
ba9bbf13
WS
949 wxString ret;
950 switch(wday)
951 {
952 case Sun:
953 ret = (flags == Name_Abbr ? wxT("Sun") : wxT("Sunday"));
954 break;
955 case Mon:
956 ret = (flags == Name_Abbr ? wxT("Mon") : wxT("Monday"));
957 break;
958 case Tue:
959 ret = (flags == Name_Abbr ? wxT("Tue") : wxT("Tuesday"));
960 break;
961 case Wed:
962 ret = (flags == Name_Abbr ? wxT("Wed") : wxT("Wednesday"));
963 break;
964 case Thu:
965 ret = (flags == Name_Abbr ? wxT("Thu") : wxT("Thursday"));
966 break;
967 case Fri:
968 ret = (flags == Name_Abbr ? wxT("Fri") : wxT("Friday"));
969 break;
970 case Sat:
971 ret = (flags == Name_Abbr ? wxT("Sat") : wxT("Saturday"));
972 break;
973 }
974 return ret;
77b88df2
JS
975
976#endif
f0f951fa
VZ
977}
978
979/* static */
980void wxDateTime::GetAmPmStrings(wxString *am, wxString *pm)
981{
982 tm tm;
983 InitTm(tm);
653567bb 984 wxChar buffer[64];
0308fd61
JS
985 // @Note: Do not call 'CallStrftime' here! CallStrftime checks the return code
986 // and causes an assertion failed if the buffer is to small (which is good) - OR -
987 // if strftime does not return anything because the format string is invalid - OR -
988 // if there are no 'am' / 'pm' tokens defined for the current locale (which is not good).
989 // wxDateTime::ParseTime will try several different formats to parse the time.
990 // As a result, GetAmPmStrings might get called, even if the current locale
991 // does not define any 'am' / 'pm' tokens. In this case, wxStrftime would
992 // assert, even though it is a perfectly legal use.
f0f951fa
VZ
993 if ( am )
994 {
10a2749b 995 if (wxStrftime(buffer, sizeof(buffer)/sizeof(wxChar), _T("%p"), &tm) > 0)
0308fd61
JS
996 *am = wxString(buffer);
997 else
998 *am = wxString();
f0f951fa
VZ
999 }
1000 if ( pm )
1001 {
1002 tm.tm_hour = 13;
10a2749b 1003 if (wxStrftime(buffer, sizeof(buffer)/sizeof(wxChar), _T("%p"), &tm) > 0)
0308fd61
JS
1004 *pm = wxString(buffer);
1005 else
1006 *pm = wxString();
f0f951fa 1007 }
b76b015e
VZ
1008}
1009
239446b4
VZ
1010// ----------------------------------------------------------------------------
1011// Country stuff: date calculations depend on the country (DST, work days,
1012// ...), so we need to know which rules to follow.
1013// ----------------------------------------------------------------------------
1014
1015/* static */
1016wxDateTime::Country wxDateTime::GetCountry()
1017{
f0f951fa 1018 // TODO use LOCALE_ICOUNTRY setting under Win32
77b88df2 1019#ifndef __WXWINCE__
239446b4
VZ
1020 if ( ms_country == Country_Unknown )
1021 {
1022 // try to guess from the time zone name
1023 time_t t = time(NULL);
a452689b
SN
1024 struct tm tmstruct;
1025 struct tm *tm = wxLocaltime_r(&t, &tmstruct);
239446b4
VZ
1026
1027 wxString tz = CallStrftime(_T("%Z"), tm);
1028 if ( tz == _T("WET") || tz == _T("WEST") )
1029 {
1030 ms_country = UK;
1031 }
1032 else if ( tz == _T("CET") || tz == _T("CEST") )
1033 {
1034 ms_country = Country_EEC;
1035 }
1036 else if ( tz == _T("MSK") || tz == _T("MSD") )
1037 {
1038 ms_country = Russia;
1039 }
1040 else if ( tz == _T("AST") || tz == _T("ADT") ||
1041 tz == _T("EST") || tz == _T("EDT") ||
1042 tz == _T("CST") || tz == _T("CDT") ||
1043 tz == _T("MST") || tz == _T("MDT") ||
1044 tz == _T("PST") || tz == _T("PDT") )
1045 {
1046 ms_country = USA;
1047 }
1048 else
1049 {
1050 // well, choose a default one
1051 ms_country = USA;
1052 }
1053 }
77b88df2
JS
1054#else
1055 ms_country = USA;
1056#endif
239446b4
VZ
1057
1058 return ms_country;
1059}
1060
1061/* static */
1062void wxDateTime::SetCountry(wxDateTime::Country country)
1063{
1064 ms_country = country;
1065}
1066
1067/* static */
1068bool wxDateTime::IsWestEuropeanCountry(Country country)
1069{
1070 if ( country == Country_Default )
1071 {
1072 country = GetCountry();
1073 }
1074
1075 return (Country_WesternEurope_Start <= country) &&
1076 (country <= Country_WesternEurope_End);
1077}
1078
1079// ----------------------------------------------------------------------------
1080// DST calculations: we use 3 different rules for the West European countries,
1081// USA and for the rest of the world. This is undoubtedly false for many
1082// countries, but I lack the necessary info (and the time to gather it),
1083// please add the other rules here!
1084// ----------------------------------------------------------------------------
1085
1086/* static */
1087bool wxDateTime::IsDSTApplicable(int year, Country country)
1088{
1089 if ( year == Inv_Year )
1090 {
1091 // take the current year if none given
1092 year = GetCurrentYear();
1093 }
1094
1095 if ( country == Country_Default )
1096 {
1097 country = GetCountry();
1098 }
1099
1100 switch ( country )
1101 {
1102 case USA:
1103 case UK:
1104 // DST was first observed in the US and UK during WWI, reused
1105 // during WWII and used again since 1966
1106 return year >= 1966 ||
1107 (year >= 1942 && year <= 1945) ||
1108 (year == 1918 || year == 1919);
1109
1110 default:
1111 // assume that it started after WWII
1112 return year > 1950;
1113 }
1114}
1115
1116/* static */
1117wxDateTime wxDateTime::GetBeginDST(int year, Country country)
1118{
1119 if ( year == Inv_Year )
1120 {
1121 // take the current year if none given
1122 year = GetCurrentYear();
1123 }
1124
1125 if ( country == Country_Default )
1126 {
1127 country = GetCountry();
1128 }
1129
1130 if ( !IsDSTApplicable(year, country) )
1131 {
2ef31e80 1132 return wxInvalidDateTime;
239446b4
VZ
1133 }
1134
1135 wxDateTime dt;
1136
1137 if ( IsWestEuropeanCountry(country) || (country == Russia) )
1138 {
1139 // DST begins at 1 a.m. GMT on the last Sunday of March
1140 if ( !dt.SetToLastWeekDay(Sun, Mar, year) )
1141 {
1142 // weird...
1143 wxFAIL_MSG( _T("no last Sunday in March?") );
1144 }
1145
1146 dt += wxTimeSpan::Hours(1);
1147
41acf5c0 1148 // disable DST tests because it could result in an infinite recursion!
68379eaf 1149 dt.MakeGMT(true);
239446b4
VZ
1150 }
1151 else switch ( country )
1152 {
1153 case USA:
1154 switch ( year )
1155 {
1156 case 1918:
1157 case 1919:
1158 // don't know for sure - assume it was in effect all year
1159
1160 case 1943:
1161 case 1944:
1162 case 1945:
1163 dt.Set(1, Jan, year);
1164 break;
1165
1166 case 1942:
1167 // DST was installed Feb 2, 1942 by the Congress
1168 dt.Set(2, Feb, year);
1169 break;
1170
1171 // Oil embargo changed the DST period in the US
1172 case 1974:
1173 dt.Set(6, Jan, 1974);
1174 break;
1175
1176 case 1975:
1177 dt.Set(23, Feb, 1975);
1178 break;
1179
1180 default:
1181 // before 1986, DST begun on the last Sunday of April, but
1182 // in 1986 Reagan changed it to begin at 2 a.m. of the
1183 // first Sunday in April
1184 if ( year < 1986 )
1185 {
1186 if ( !dt.SetToLastWeekDay(Sun, Apr, year) )
1187 {
1188 // weird...
1189 wxFAIL_MSG( _T("no first Sunday in April?") );
1190 }
1191 }
1192 else
1193 {
1194 if ( !dt.SetToWeekDay(Sun, 1, Apr, year) )
1195 {
1196 // weird...
1197 wxFAIL_MSG( _T("no first Sunday in April?") );
1198 }
1199 }
1200
1201 dt += wxTimeSpan::Hours(2);
1202
1203 // TODO what about timezone??
1204 }
1205
1206 break;
1207
1208 default:
1209 // assume Mar 30 as the start of the DST for the rest of the world
1210 // - totally bogus, of course
1211 dt.Set(30, Mar, year);
1212 }
1213
1214 return dt;
1215}
1216
1217/* static */
1218wxDateTime wxDateTime::GetEndDST(int year, Country country)
1219{
1220 if ( year == Inv_Year )
1221 {
1222 // take the current year if none given
1223 year = GetCurrentYear();
1224 }
1225
1226 if ( country == Country_Default )
1227 {
1228 country = GetCountry();
1229 }
1230
1231 if ( !IsDSTApplicable(year, country) )
1232 {
2ef31e80 1233 return wxInvalidDateTime;
239446b4
VZ
1234 }
1235
1236 wxDateTime dt;
1237
1238 if ( IsWestEuropeanCountry(country) || (country == Russia) )
1239 {
5f287370 1240 // DST ends at 1 a.m. GMT on the last Sunday of October
239446b4
VZ
1241 if ( !dt.SetToLastWeekDay(Sun, Oct, year) )
1242 {
1243 // weirder and weirder...
1244 wxFAIL_MSG( _T("no last Sunday in October?") );
1245 }
1246
1247 dt += wxTimeSpan::Hours(1);
1248
41acf5c0 1249 // disable DST tests because it could result in an infinite recursion!
68379eaf 1250 dt.MakeGMT(true);
239446b4
VZ
1251 }
1252 else switch ( country )
1253 {
1254 case USA:
1255 switch ( year )
1256 {
1257 case 1918:
1258 case 1919:
1259 // don't know for sure - assume it was in effect all year
1260
1261 case 1943:
1262 case 1944:
1263 dt.Set(31, Dec, year);
1264 break;
1265
1266 case 1945:
1267 // the time was reset after the end of the WWII
1268 dt.Set(30, Sep, year);
1269 break;
1270
1271 default:
5f287370 1272 // DST ends at 2 a.m. on the last Sunday of October
239446b4
VZ
1273 if ( !dt.SetToLastWeekDay(Sun, Oct, year) )
1274 {
1275 // weirder and weirder...
1276 wxFAIL_MSG( _T("no last Sunday in October?") );
1277 }
1278
1279 dt += wxTimeSpan::Hours(2);
1280
1281 // TODO what about timezone??
1282 }
1283 break;
1284
1285 default:
1286 // assume October 26th as the end of the DST - totally bogus too
1287 dt.Set(26, Oct, year);
1288 }
1289
1290 return dt;
1291}
1292
0979c962
VZ
1293// ----------------------------------------------------------------------------
1294// constructors and assignment operators
1295// ----------------------------------------------------------------------------
1296
f6bcfd97
BP
1297// return the current time with ms precision
1298/* static */ wxDateTime wxDateTime::UNow()
1299{
1300 return wxDateTime(wxGetLocalTimeMillis());
1301}
1302
299fcbfe
VZ
1303// the values in the tm structure contain the local time
1304wxDateTime& wxDateTime::Set(const struct tm& tm)
0979c962 1305{
299fcbfe 1306 struct tm tm2(tm);
b76b015e 1307 time_t timet = mktime(&tm2);
1ef54dcf 1308
4afd7529 1309 if ( timet == (time_t)-1 )
0979c962 1310 {
4afd7529
VZ
1311 // mktime() rather unintuitively fails for Jan 1, 1970 if the hour is
1312 // less than timezone - try to make it work for this case
1313 if ( tm2.tm_year == 70 && tm2.tm_mon == 0 && tm2.tm_mday == 1 )
1314 {
3d78a532
RD
1315 return Set((time_t)(
1316 GetTimeZone() +
1317 tm2.tm_hour * MIN_PER_HOUR * SEC_PER_MIN +
1318 tm2.tm_min * SEC_PER_MIN +
1319 tm2.tm_sec));
4afd7529
VZ
1320 }
1321
1322 wxFAIL_MSG( _T("mktime() failed") );
0979c962 1323
384223b3
VZ
1324 *this = wxInvalidDateTime;
1325
1326 return *this;
0979c962
VZ
1327 }
1328 else
1329 {
1330 return Set(timet);
1331 }
1332}
1333
1334wxDateTime& wxDateTime::Set(wxDateTime_t hour,
1335 wxDateTime_t minute,
1336 wxDateTime_t second,
1337 wxDateTime_t millisec)
1338{
1339 // we allow seconds to be 61 to account for the leap seconds, even if we
1340 // don't use them really
384223b3
VZ
1341 wxDATETIME_CHECK( hour < 24 &&
1342 second < 62 &&
1343 minute < 60 &&
1344 millisec < 1000,
1345 _T("Invalid time in wxDateTime::Set()") );
0979c962
VZ
1346
1347 // get the current date from system
a452689b
SN
1348 struct tm tmstruct;
1349 struct tm *tm = GetTmNow(&tmstruct);
299fcbfe 1350
a452689b 1351 wxDATETIME_CHECK( tm, _T("wxLocaltime_r() failed") );
0979c962 1352
456639ac
VZ
1353 // make a copy so it isn't clobbered by the call to mktime() below
1354 struct tm tm1(*tm);
1355
0979c962 1356 // adjust the time
456639ac
VZ
1357 tm1.tm_hour = hour;
1358 tm1.tm_min = minute;
1359 tm1.tm_sec = second;
0979c962 1360
9cc311d3 1361 // and the DST in case it changes on this date
456639ac 1362 struct tm tm2(tm1);
9cc311d3 1363 mktime(&tm2);
456639ac
VZ
1364 if ( tm2.tm_isdst != tm1.tm_isdst )
1365 tm1.tm_isdst = tm2.tm_isdst;
9cc311d3 1366
456639ac 1367 (void)Set(tm1);
0979c962
VZ
1368
1369 // and finally adjust milliseconds
1370 return SetMillisecond(millisec);
1371}
1372
1373wxDateTime& wxDateTime::Set(wxDateTime_t day,
1374 Month month,
1375 int year,
1376 wxDateTime_t hour,
1377 wxDateTime_t minute,
1378 wxDateTime_t second,
1379 wxDateTime_t millisec)
1380{
384223b3
VZ
1381 wxDATETIME_CHECK( hour < 24 &&
1382 second < 62 &&
1383 minute < 60 &&
1384 millisec < 1000,
1385 _T("Invalid time in wxDateTime::Set()") );
0979c962 1386
2f02cb89 1387 ReplaceDefaultYearMonthWithCurrent(&year, &month);
0979c962 1388
384223b3
VZ
1389 wxDATETIME_CHECK( (0 < day) && (day <= GetNumberOfDays(month, year)),
1390 _T("Invalid date in wxDateTime::Set()") );
0979c962
VZ
1391
1392 // the range of time_t type (inclusive)
1393 static const int yearMinInRange = 1970;
1394 static const int yearMaxInRange = 2037;
1395
1396 // test only the year instead of testing for the exact end of the Unix
1397 // time_t range - it doesn't bring anything to do more precise checks
2f02cb89 1398 if ( year >= yearMinInRange && year <= yearMaxInRange )
0979c962
VZ
1399 {
1400 // use the standard library version if the date is in range - this is
b76b015e 1401 // probably more efficient than our code
0979c962 1402 struct tm tm;
b76b015e 1403 tm.tm_year = year - 1900;
0979c962
VZ
1404 tm.tm_mon = month;
1405 tm.tm_mday = day;
1406 tm.tm_hour = hour;
1407 tm.tm_min = minute;
1408 tm.tm_sec = second;
299fcbfe 1409 tm.tm_isdst = -1; // mktime() will guess it
0979c962
VZ
1410
1411 (void)Set(tm);
1412
1413 // and finally adjust milliseconds
3d78a532
RD
1414 if (IsValid())
1415 SetMillisecond(millisec);
1416
1417 return *this;
0979c962
VZ
1418 }
1419 else
1420 {
1421 // do time calculations ourselves: we want to calculate the number of
fcc3d7cb 1422 // milliseconds between the given date and the epoch
e6ec579c
VZ
1423
1424 // get the JDN for the midnight of this day
1425 m_time = GetTruncatedJDN(day, month, year);
1426 m_time -= EPOCH_JDN;
1427 m_time *= SECONDS_PER_DAY * TIME_T_FACTOR;
1428
299fcbfe
VZ
1429 // JDN corresponds to GMT, we take localtime
1430 Add(wxTimeSpan(hour, minute, second + GetTimeZone(), millisec));
b76b015e
VZ
1431 }
1432
1433 return *this;
1434}
1435
e6ec579c
VZ
1436wxDateTime& wxDateTime::Set(double jdn)
1437{
1ef54dcf
VZ
1438 // so that m_time will be 0 for the midnight of Jan 1, 1970 which is jdn
1439 // EPOCH_JDN + 0.5
1440 jdn -= EPOCH_JDN + 0.5;
1441
d26adb9d 1442 m_time.Assign(jdn*MILLISECONDS_PER_DAY);
cd0b1709 1443
d26adb9d 1444 // JDNs always are in UTC, so we don't need any adjustments for time zone
e6ec579c
VZ
1445
1446 return *this;
1447}
1448
9d9b7755
VZ
1449wxDateTime& wxDateTime::ResetTime()
1450{
1451 Tm tm = GetTm();
1452
1453 if ( tm.hour || tm.min || tm.sec || tm.msec )
1454 {
1455 tm.msec =
1456 tm.sec =
1457 tm.min =
1458 tm.hour = 0;
1459
1460 Set(tm);
1461 }
1462
1463 return *this;
1464}
1465
2b5f62a0
VZ
1466// ----------------------------------------------------------------------------
1467// DOS Date and Time Format functions
1468// ----------------------------------------------------------------------------
1469// the dos date and time value is an unsigned 32 bit value in the format:
1470// YYYYYYYMMMMDDDDDhhhhhmmmmmmsssss
1471//
1472// Y = year offset from 1980 (0-127)
1473// M = month (1-12)
1474// D = day of month (1-31)
1475// h = hour (0-23)
1476// m = minute (0-59)
1477// s = bisecond (0-29) each bisecond indicates two seconds
1478// ----------------------------------------------------------------------------
1479
1480wxDateTime& wxDateTime::SetFromDOS(unsigned long ddt)
1481{
1482 struct tm tm;
4739346e 1483 InitTm(tm);
2b5f62a0
VZ
1484
1485 long year = ddt & 0xFE000000;
1486 year >>= 25;
1487 year += 80;
1488 tm.tm_year = year;
1489
1490 long month = ddt & 0x1E00000;
1491 month >>= 21;
1492 month -= 1;
1493 tm.tm_mon = month;
1494
1495 long day = ddt & 0x1F0000;
1496 day >>= 16;
1497 tm.tm_mday = day;
1498
1499 long hour = ddt & 0xF800;
1500 hour >>= 11;
1501 tm.tm_hour = hour;
1502
1503 long minute = ddt & 0x7E0;
1504 minute >>= 5;
1505 tm.tm_min = minute;
1506
1507 long second = ddt & 0x1F;
1508 tm.tm_sec = second * 2;
1509
1510 return Set(mktime(&tm));
1511}
1512
1513unsigned long wxDateTime::GetAsDOS() const
1514{
1515 unsigned long ddt;
1516 time_t ticks = GetTicks();
a452689b
SN
1517 struct tm tmstruct;
1518 struct tm *tm = wxLocaltime_r(&ticks, &tmstruct);
2b5f62a0
VZ
1519
1520 long year = tm->tm_year;
1521 year -= 80;
1522 year <<= 25;
1523
1524 long month = tm->tm_mon;
1525 month += 1;
1526 month <<= 21;
1527
1528 long day = tm->tm_mday;
1529 day <<= 16;
1530
1531 long hour = tm->tm_hour;
1532 hour <<= 11;
1533
1534 long minute = tm->tm_min;
1535 minute <<= 5;
1536
1537 long second = tm->tm_sec;
1538 second /= 2;
1539
1540 ddt = year | month | day | hour | minute | second;
1541 return ddt;
1542}
1543
b76b015e
VZ
1544// ----------------------------------------------------------------------------
1545// time_t <-> broken down time conversions
1546// ----------------------------------------------------------------------------
1547
299fcbfe 1548wxDateTime::Tm wxDateTime::GetTm(const TimeZone& tz) const
b76b015e
VZ
1549{
1550 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1551
1552 time_t time = GetTicks();
1553 if ( time != (time_t)-1 )
1554 {
1555 // use C RTL functions
a452689b 1556 struct tm tmstruct;
299fcbfe
VZ
1557 tm *tm;
1558 if ( tz.GetOffset() == -GetTimeZone() )
1559 {
1560 // we are working with local time
a452689b 1561 tm = wxLocaltime_r(&time, &tmstruct);
c5a1681b
VZ
1562
1563 // should never happen
a452689b 1564 wxCHECK_MSG( tm, Tm(), _T("wxLocaltime_r() failed") );
299fcbfe
VZ
1565 }
1566 else
1567 {
13111b2a 1568 time += (time_t)tz.GetOffset();
33ac7e6f 1569#if defined(__VMS__) || defined(__WATCOMC__) // time is unsigned so avoid warning
13111b2a 1570 int time2 = (int) time;
9d9b7755 1571 if ( time2 >= 0 )
fb10f04c 1572#else
9d9b7755 1573 if ( time >= 0 )
fb10f04c 1574#endif
c5a1681b 1575 {
a452689b 1576 tm = wxGmtime_r(&time, &tmstruct);
b76b015e 1577
c5a1681b 1578 // should never happen
a452689b 1579 wxCHECK_MSG( tm, Tm(), _T("wxGmtime_r() failed") );
c5a1681b
VZ
1580 }
1581 else
1582 {
1583 tm = (struct tm *)NULL;
1584 }
1585 }
b76b015e 1586
c5a1681b
VZ
1587 if ( tm )
1588 {
f6bcfd97
BP
1589 // adjust the milliseconds
1590 Tm tm2(*tm, tz);
1591 long timeOnly = (m_time % MILLISECONDS_PER_DAY).ToLong();
1592 tm2.msec = (wxDateTime_t)(timeOnly % 1000);
1593 return tm2;
c5a1681b
VZ
1594 }
1595 //else: use generic code below
b76b015e 1596 }
e6ec579c 1597
c5a1681b
VZ
1598 // remember the time and do the calculations with the date only - this
1599 // eliminates rounding errors of the floating point arithmetics
299fcbfe 1600
c5a1681b 1601 wxLongLong timeMidnight = m_time + tz.GetOffset() * 1000;
1ef54dcf 1602
c5a1681b 1603 long timeOnly = (timeMidnight % MILLISECONDS_PER_DAY).ToLong();
1ef54dcf 1604
c5a1681b
VZ
1605 // we want to always have positive time and timeMidnight to be really
1606 // the midnight before it
1607 if ( timeOnly < 0 )
1608 {
1609 timeOnly = MILLISECONDS_PER_DAY + timeOnly;
1610 }
e6ec579c 1611
c5a1681b 1612 timeMidnight -= timeOnly;
1ef54dcf 1613
c5a1681b
VZ
1614 // calculate the Gregorian date from JDN for the midnight of our date:
1615 // this will yield day, month (in 1..12 range) and year
1ef54dcf 1616
c5a1681b
VZ
1617 // actually, this is the JDN for the noon of the previous day
1618 long jdn = (timeMidnight / MILLISECONDS_PER_DAY).ToLong() + EPOCH_JDN;
1ef54dcf 1619
c5a1681b 1620 // CREDIT: code below is by Scott E. Lee (but bugs are mine)
1ef54dcf 1621
c5a1681b 1622 wxASSERT_MSG( jdn > -2, _T("JDN out of range") );
1ef54dcf 1623
c5a1681b 1624 // calculate the century
479cd5de
VZ
1625 long temp = (jdn + JDN_OFFSET) * 4 - 1;
1626 long century = temp / DAYS_PER_400_YEARS;
1ef54dcf 1627
c5a1681b
VZ
1628 // then the year and day of year (1 <= dayOfYear <= 366)
1629 temp = ((temp % DAYS_PER_400_YEARS) / 4) * 4 + 3;
479cd5de
VZ
1630 long year = (century * 100) + (temp / DAYS_PER_4_YEARS);
1631 long dayOfYear = (temp % DAYS_PER_4_YEARS) / 4 + 1;
1ef54dcf 1632
c5a1681b
VZ
1633 // and finally the month and day of the month
1634 temp = dayOfYear * 5 - 3;
479cd5de
VZ
1635 long month = temp / DAYS_PER_5_MONTHS;
1636 long day = (temp % DAYS_PER_5_MONTHS) / 5 + 1;
c5a1681b
VZ
1637
1638 // month is counted from March - convert to normal
1639 if ( month < 10 )
1640 {
1641 month += 3;
1642 }
1643 else
1644 {
1645 year += 1;
1646 month -= 9;
1647 }
1ef54dcf 1648
c5a1681b
VZ
1649 // year is offset by 4800
1650 year -= 4800;
1ef54dcf 1651
c5a1681b
VZ
1652 // check that the algorithm gave us something reasonable
1653 wxASSERT_MSG( (0 < month) && (month <= 12), _T("invalid month") );
1654 wxASSERT_MSG( (1 <= day) && (day < 32), _T("invalid day") );
e6ec579c 1655
c5a1681b
VZ
1656 // construct Tm from these values
1657 Tm tm;
1658 tm.year = (int)year;
1659 tm.mon = (Month)(month - 1); // algorithm yields 1 for January, not 0
1660 tm.mday = (wxDateTime_t)day;
479cd5de 1661 tm.msec = (wxDateTime_t)(timeOnly % 1000);
c5a1681b
VZ
1662 timeOnly -= tm.msec;
1663 timeOnly /= 1000; // now we have time in seconds
e6ec579c 1664
d26adb9d 1665 tm.sec = (wxDateTime_t)(timeOnly % SEC_PER_MIN);
c5a1681b 1666 timeOnly -= tm.sec;
d26adb9d 1667 timeOnly /= SEC_PER_MIN; // now we have time in minutes
e6ec579c 1668
d26adb9d 1669 tm.min = (wxDateTime_t)(timeOnly % MIN_PER_HOUR);
c5a1681b 1670 timeOnly -= tm.min;
e6ec579c 1671
d26adb9d 1672 tm.hour = (wxDateTime_t)(timeOnly / MIN_PER_HOUR);
b76b015e 1673
c5a1681b 1674 return tm;
b76b015e
VZ
1675}
1676
1677wxDateTime& wxDateTime::SetYear(int year)
1678{
1679 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1680
1681 Tm tm(GetTm());
1682 tm.year = year;
1683 Set(tm);
1684
1685 return *this;
1686}
1687
1688wxDateTime& wxDateTime::SetMonth(Month month)
1689{
1690 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1691
1692 Tm tm(GetTm());
1693 tm.mon = month;
1694 Set(tm);
1695
1696 return *this;
1697}
1698
1699wxDateTime& wxDateTime::SetDay(wxDateTime_t mday)
1700{
1701 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1702
1703 Tm tm(GetTm());
1704 tm.mday = mday;
1705 Set(tm);
1706
1707 return *this;
1708}
1709
1710wxDateTime& wxDateTime::SetHour(wxDateTime_t hour)
1711{
1712 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1713
1714 Tm tm(GetTm());
1715 tm.hour = hour;
1716 Set(tm);
1717
1718 return *this;
1719}
1720
1721wxDateTime& wxDateTime::SetMinute(wxDateTime_t min)
1722{
1723 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1724
1725 Tm tm(GetTm());
1726 tm.min = min;
1727 Set(tm);
1728
1729 return *this;
1730}
1731
1732wxDateTime& wxDateTime::SetSecond(wxDateTime_t sec)
1733{
1734 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1735
1736 Tm tm(GetTm());
1737 tm.sec = sec;
1738 Set(tm);
0979c962
VZ
1739
1740 return *this;
1741}
b76b015e
VZ
1742
1743wxDateTime& wxDateTime::SetMillisecond(wxDateTime_t millisecond)
1744{
1745 wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") );
1746
1747 // we don't need to use GetTm() for this one
1748 m_time -= m_time % 1000l;
1749 m_time += millisecond;
1750
1751 return *this;
1752}
1753
1754// ----------------------------------------------------------------------------
1755// wxDateTime arithmetics
1756// ----------------------------------------------------------------------------
1757
1758wxDateTime& wxDateTime::Add(const wxDateSpan& diff)
1759{
1760 Tm tm(GetTm());
1761
1762 tm.year += diff.GetYears();
fcc3d7cb 1763 tm.AddMonths(diff.GetMonths());
4f6aed9c
VZ
1764
1765 // check that the resulting date is valid
1766 if ( tm.mday > GetNumOfDaysInMonth(tm.year, tm.mon) )
1767 {
1768 // We suppose that when adding one month to Jan 31 we want to get Feb
1769 // 28 (or 29), i.e. adding a month to the last day of the month should
1770 // give the last day of the next month which is quite logical.
1771 //
1772 // Unfortunately, there is no logic way to understand what should
1773 // Jan 30 + 1 month be - Feb 28 too or Feb 27 (assuming non leap year)?
1774 // We make it Feb 28 (last day too), but it is highly questionable.
1775 tm.mday = GetNumOfDaysInMonth(tm.year, tm.mon);
1776 }
1777
fcc3d7cb 1778 tm.AddDays(diff.GetTotalDays());
b76b015e
VZ
1779
1780 Set(tm);
1781
9d9b7755
VZ
1782 wxASSERT_MSG( IsSameTime(tm),
1783 _T("Add(wxDateSpan) shouldn't modify time") );
1784
b76b015e
VZ
1785 return *this;
1786}
1787
2f02cb89
VZ
1788// ----------------------------------------------------------------------------
1789// Weekday and monthday stuff
1790// ----------------------------------------------------------------------------
1791
4c27e2fa
VZ
1792// convert Sun, Mon, ..., Sat into 6, 0, ..., 5
1793static inline int ConvertWeekDayToMondayBase(int wd)
1794{
1795 return wd == wxDateTime::Sun ? 6 : wd - 1;
1796}
1797
1798/* static */
1799wxDateTime
1800wxDateTime::SetToWeekOfYear(int year, wxDateTime_t numWeek, WeekDay wd)
4f6aed9c 1801{
2b5f62a0
VZ
1802 wxASSERT_MSG( numWeek > 0,
1803 _T("invalid week number: weeks are counted from 1") );
1804
4c27e2fa
VZ
1805 // Jan 4 always lies in the 1st week of the year
1806 wxDateTime dt(4, Jan, year);
1807 dt.SetToWeekDayInSameWeek(wd);
1808 dt += wxDateSpan::Weeks(numWeek - 1);
1809
1810 return dt;
1811}
4f6aed9c 1812
ca3e85cf 1813#if WXWIN_COMPATIBILITY_2_6
4c27e2fa
VZ
1814// use a separate function to avoid warnings about using deprecated
1815// SetToTheWeek in GetWeek below
1816static wxDateTime
1817SetToTheWeek(int year,
1818 wxDateTime::wxDateTime_t numWeek,
1819 wxDateTime::WeekDay weekday,
1820 wxDateTime::WeekFlags flags)
1821{
4f6aed9c 1822 // Jan 4 always lies in the 1st week of the year
4c27e2fa
VZ
1823 wxDateTime dt(4, wxDateTime::Jan, year);
1824 dt.SetToWeekDayInSameWeek(weekday, flags);
1825 dt += wxDateSpan::Weeks(numWeek - 1);
4f6aed9c 1826
4c27e2fa
VZ
1827 return dt;
1828}
1829
1830bool wxDateTime::SetToTheWeek(wxDateTime_t numWeek,
1831 WeekDay weekday,
1832 WeekFlags flags)
1833{
1834 int year = GetYear();
1835 *this = ::SetToTheWeek(year, numWeek, weekday, flags);
4f6aed9c
VZ
1836 if ( GetYear() != year )
1837 {
1838 // oops... numWeek was too big
68379eaf 1839 return false;
4f6aed9c
VZ
1840 }
1841
68379eaf 1842 return true;
4f6aed9c
VZ
1843}
1844
4c27e2fa
VZ
1845wxDateTime wxDateTime::GetWeek(wxDateTime_t numWeek,
1846 WeekDay weekday,
1847 WeekFlags flags) const
1848{
1849 return ::SetToTheWeek(GetYear(), numWeek, weekday, flags);
1850}
ca3e85cf 1851#endif // WXWIN_COMPATIBILITY_2_6
4c27e2fa 1852
2f02cb89
VZ
1853wxDateTime& wxDateTime::SetToLastMonthDay(Month month,
1854 int year)
1855{
1856 // take the current month/year if none specified
1a8557b1
VZ
1857 if ( year == Inv_Year )
1858 year = GetYear();
1859 if ( month == Inv_Month )
1860 month = GetMonth();
2f02cb89 1861
fcc3d7cb 1862 return Set(GetNumOfDaysInMonth(year, month), month, year);
2f02cb89
VZ
1863}
1864
2b5f62a0 1865wxDateTime& wxDateTime::SetToWeekDayInSameWeek(WeekDay weekday, WeekFlags flags)
cd0b1709 1866{
384223b3 1867 wxDATETIME_CHECK( weekday != Inv_WeekDay, _T("invalid weekday") );
cd0b1709 1868
af80bc92
VZ
1869 int wdayDst = weekday,
1870 wdayThis = GetWeekDay();
1871 if ( wdayDst == wdayThis )
cd0b1709
VZ
1872 {
1873 // nothing to do
1874 return *this;
1875 }
2b5f62a0
VZ
1876
1877 if ( flags == Default_First )
1878 {
1879 flags = GetCountry() == USA ? Sunday_First : Monday_First;
1880 }
1881
1882 // the logic below based on comparing weekday and wdayThis works if Sun (0)
1883 // is the first day in the week, but breaks down for Monday_First case so
1884 // we adjust the week days in this case
af80bc92 1885 if ( flags == Monday_First )
2b5f62a0
VZ
1886 {
1887 if ( wdayThis == Sun )
1888 wdayThis += 7;
af80bc92
VZ
1889 if ( wdayDst == Sun )
1890 wdayDst += 7;
2b5f62a0
VZ
1891 }
1892 //else: Sunday_First, nothing to do
1893
1894 // go forward or back in time to the day we want
af80bc92 1895 if ( wdayDst < wdayThis )
cd0b1709 1896 {
af80bc92 1897 return Subtract(wxDateSpan::Days(wdayThis - wdayDst));
cd0b1709
VZ
1898 }
1899 else // weekday > wdayThis
1900 {
af80bc92 1901 return Add(wxDateSpan::Days(wdayDst - wdayThis));
cd0b1709
VZ
1902 }
1903}
1904
1905wxDateTime& wxDateTime::SetToNextWeekDay(WeekDay weekday)
1906{
384223b3 1907 wxDATETIME_CHECK( weekday != Inv_WeekDay, _T("invalid weekday") );
cd0b1709
VZ
1908
1909 int diff;
1910 WeekDay wdayThis = GetWeekDay();
1911 if ( weekday == wdayThis )
1912 {
1913 // nothing to do
1914 return *this;
1915 }
1916 else if ( weekday < wdayThis )
1917 {
1918 // need to advance a week
1919 diff = 7 - (wdayThis - weekday);
1920 }
1921 else // weekday > wdayThis
1922 {
1923 diff = weekday - wdayThis;
1924 }
1925
4f6aed9c 1926 return Add(wxDateSpan::Days(diff));
cd0b1709
VZ
1927}
1928
1929wxDateTime& wxDateTime::SetToPrevWeekDay(WeekDay weekday)
1930{
384223b3 1931 wxDATETIME_CHECK( weekday != Inv_WeekDay, _T("invalid weekday") );
cd0b1709
VZ
1932
1933 int diff;
1934 WeekDay wdayThis = GetWeekDay();
1935 if ( weekday == wdayThis )
1936 {
1937 // nothing to do
1938 return *this;
1939 }
1940 else if ( weekday > wdayThis )
1941 {
1942 // need to go to previous week
1943 diff = 7 - (weekday - wdayThis);
1944 }
1945 else // weekday < wdayThis
1946 {
1947 diff = wdayThis - weekday;
1948 }
1949
f6bcfd97 1950 return Subtract(wxDateSpan::Days(diff));
cd0b1709
VZ
1951}
1952
2f02cb89
VZ
1953bool wxDateTime::SetToWeekDay(WeekDay weekday,
1954 int n,
1955 Month month,
1956 int year)
1957{
68379eaf 1958 wxCHECK_MSG( weekday != Inv_WeekDay, false, _T("invalid weekday") );
2f02cb89 1959
68379eaf 1960 // we don't check explicitly that -5 <= n <= 5 because we will return false
2f02cb89
VZ
1961 // anyhow in such case - but may be should still give an assert for it?
1962
1963 // take the current month/year if none specified
1964 ReplaceDefaultYearMonthWithCurrent(&year, &month);
1965
1966 wxDateTime dt;
1967
1968 // TODO this probably could be optimised somehow...
1969
1970 if ( n > 0 )
1971 {
1972 // get the first day of the month
1973 dt.Set(1, month, year);
1974
1975 // get its wday
1976 WeekDay wdayFirst = dt.GetWeekDay();
1977
1978 // go to the first weekday of the month
1979 int diff = weekday - wdayFirst;
1980 if ( diff < 0 )
1981 diff += 7;
1982
1983 // add advance n-1 weeks more
1984 diff += 7*(n - 1);
1985
239446b4 1986 dt += wxDateSpan::Days(diff);
2f02cb89 1987 }
239446b4 1988 else // count from the end of the month
2f02cb89
VZ
1989 {
1990 // get the last day of the month
1991 dt.SetToLastMonthDay(month, year);
1992
1993 // get its wday
1994 WeekDay wdayLast = dt.GetWeekDay();
1995
1996 // go to the last weekday of the month
1997 int diff = wdayLast - weekday;
1998 if ( diff < 0 )
1999 diff += 7;
2000
2001 // and rewind n-1 weeks from there
239446b4 2002 diff += 7*(-n - 1);
2f02cb89
VZ
2003
2004 dt -= wxDateSpan::Days(diff);
2005 }
2006
2007 // check that it is still in the same month
2008 if ( dt.GetMonth() == month )
2009 {
2010 *this = dt;
2011
68379eaf 2012 return true;
2f02cb89
VZ
2013 }
2014 else
2015 {
2016 // no such day in this month
68379eaf 2017 return false;
2f02cb89
VZ
2018 }
2019}
2020
1c5d27e2
VZ
2021static inline
2022wxDateTime::wxDateTime_t GetDayOfYearFromTm(const wxDateTime::Tm& tm)
2023{
907173e5 2024 return (wxDateTime::wxDateTime_t)(gs_cumulatedDays[wxDateTime::IsLeapYear(tm.year)][tm.mon] + tm.mday);
1c5d27e2
VZ
2025}
2026
239446b4
VZ
2027wxDateTime::wxDateTime_t wxDateTime::GetDayOfYear(const TimeZone& tz) const
2028{
1c5d27e2
VZ
2029 return GetDayOfYearFromTm(GetTm(tz));
2030}
239446b4 2031
1c5d27e2
VZ
2032wxDateTime::wxDateTime_t
2033wxDateTime::GetWeekOfYear(wxDateTime::WeekFlags flags, const TimeZone& tz) const
239446b4 2034{
9d9b7755
VZ
2035 if ( flags == Default_First )
2036 {
2037 flags = GetCountry() == USA ? Sunday_First : Monday_First;
2038 }
239446b4 2039
1c5d27e2
VZ
2040 Tm tm(GetTm(tz));
2041 wxDateTime_t nDayInYear = GetDayOfYearFromTm(tm);
239446b4 2042
1c5d27e2
VZ
2043 int wdTarget = GetWeekDay(tz);
2044 int wdYearStart = wxDateTime(1, Jan, GetYear()).GetWeekDay();
2045 int week;
9d9b7755
VZ
2046 if ( flags == Sunday_First )
2047 {
1c5d27e2
VZ
2048 // FIXME: First week is not calculated correctly.
2049 week = (nDayInYear - wdTarget + 7) / 7;
2050 if ( wdYearStart == Wed || wdYearStart == Thu )
2051 week++;
9d9b7755 2052 }
1c5d27e2 2053 else // week starts with monday
9d9b7755 2054 {
1c5d27e2
VZ
2055 // adjust the weekdays to non-US style.
2056 wdYearStart = ConvertWeekDayToMondayBase(wdYearStart);
2057 wdTarget = ConvertWeekDayToMondayBase(wdTarget);
239446b4 2058
1c5d27e2
VZ
2059 // quoting from http://www.cl.cam.ac.uk/~mgk25/iso-time.html:
2060 //
2061 // Week 01 of a year is per definition the first week that has the
2062 // Thursday in this year, which is equivalent to the week that
2063 // contains the fourth day of January. In other words, the first
2064 // week of a new year is the week that has the majority of its
2065 // days in the new year. Week 01 might also contain days from the
2066 // previous year and the week before week 01 of a year is the last
2067 // week (52 or 53) of the previous year even if it contains days
2068 // from the new year. A week starts with Monday (day 1) and ends
2069 // with Sunday (day 7).
2070 //
2071
2072 // if Jan 1 is Thursday or less, it is in the first week of this year
2073 if ( wdYearStart < 4 )
2074 {
2075 // count the number of entire weeks between Jan 1 and this date
2076 week = (nDayInYear + wdYearStart + 6 - wdTarget)/7;
2077
2078 // be careful to check for overflow in the next year
2079 if ( week == 53 && tm.mday - wdTarget > 28 )
2080 week = 1;
2081 }
2082 else // Jan 1 is in the last week of the previous year
2083 {
2084 // check if we happen to be at the last week of previous year:
2085 if ( tm.mon == Jan && tm.mday < 8 - wdYearStart )
2086 week = wxDateTime(31, Dec, GetYear()-1).GetWeekOfYear();
2087 else
2088 week = (nDayInYear + wdYearStart - 1 - wdTarget)/7;
2089 }
239446b4
VZ
2090 }
2091
907173e5 2092 return (wxDateTime::wxDateTime_t)week;
68ee7c47
VZ
2093}
2094
9d9b7755
VZ
2095wxDateTime::wxDateTime_t wxDateTime::GetWeekOfMonth(wxDateTime::WeekFlags flags,
2096 const TimeZone& tz) const
68ee7c47 2097{
9d9b7755
VZ
2098 Tm tm = GetTm(tz);
2099 wxDateTime dtMonthStart = wxDateTime(1, tm.mon, tm.year);
6dc6fda6 2100 int nWeek = GetWeekOfYear(flags) - dtMonthStart.GetWeekOfYear(flags) + 1;
9d9b7755 2101 if ( nWeek < 0 )
68ee7c47 2102 {
9d9b7755
VZ
2103 // this may happen for January when Jan, 1 is the last week of the
2104 // previous year
2105 nWeek += IsLeapYear(tm.year - 1) ? 53 : 52;
68ee7c47 2106 }
68ee7c47 2107
6dc6fda6 2108 return (wxDateTime::wxDateTime_t)nWeek;
239446b4
VZ
2109}
2110
f0f951fa
VZ
2111wxDateTime& wxDateTime::SetToYearDay(wxDateTime::wxDateTime_t yday)
2112{
2113 int year = GetYear();
384223b3
VZ
2114 wxDATETIME_CHECK( (0 < yday) && (yday <= GetNumberOfDays(year)),
2115 _T("invalid year day") );
f0f951fa
VZ
2116
2117 bool isLeap = IsLeapYear(year);
2118 for ( Month mon = Jan; mon < Inv_Month; wxNextMonth(mon) )
2119 {
2120 // for Dec, we can't compare with gs_cumulatedDays[mon + 1], but we
2121 // don't need it neither - because of the CHECK above we know that
2122 // yday lies in December then
f49d731f 2123 if ( (mon == Dec) || (yday <= gs_cumulatedDays[isLeap][mon + 1]) )
f0f951fa 2124 {
42841dfc 2125 Set((wxDateTime::wxDateTime_t)(yday - gs_cumulatedDays[isLeap][mon]), mon, year);
f0f951fa
VZ
2126
2127 break;
2128 }
2129 }
2130
2131 return *this;
2132}
2133
e6ec579c
VZ
2134// ----------------------------------------------------------------------------
2135// Julian day number conversion and related stuff
2136// ----------------------------------------------------------------------------
2137
2138double wxDateTime::GetJulianDayNumber() const
2139{
d26adb9d 2140 return m_time.ToDouble() / MILLISECONDS_PER_DAY + EPOCH_JDN + 0.5;
e6ec579c
VZ
2141}
2142
2143double wxDateTime::GetRataDie() const
2144{
2145 // March 1 of the year 0 is Rata Die day -306 and JDN 1721119.5
2146 return GetJulianDayNumber() - 1721119.5 - 306;
2147}
2148
fcc3d7cb 2149// ----------------------------------------------------------------------------
299fcbfe 2150// timezone and DST stuff
fcc3d7cb
VZ
2151// ----------------------------------------------------------------------------
2152
299fcbfe 2153int wxDateTime::IsDST(wxDateTime::Country country) const
fcc3d7cb 2154{
299fcbfe
VZ
2155 wxCHECK_MSG( country == Country_Default, -1,
2156 _T("country support not implemented") );
2157
2158 // use the C RTL for the dates in the standard range
2159 time_t timet = GetTicks();
2160 if ( timet != (time_t)-1 )
2161 {
a452689b
SN
2162 struct tm tmstruct;
2163 tm *tm = wxLocaltime_r(&timet, &tmstruct);
299fcbfe 2164
a452689b 2165 wxCHECK_MSG( tm, -1, _T("wxLocaltime_r() failed") );
299fcbfe
VZ
2166
2167 return tm->tm_isdst;
2168 }
2169 else
2170 {
239446b4
VZ
2171 int year = GetYear();
2172
2173 if ( !IsDSTApplicable(year, country) )
2174 {
2175 // no DST time in this year in this country
2176 return -1;
2177 }
299fcbfe 2178
239446b4 2179 return IsBetween(GetBeginDST(year, country), GetEndDST(year, country));
299fcbfe 2180 }
fcc3d7cb
VZ
2181}
2182
41acf5c0 2183wxDateTime& wxDateTime::MakeTimezone(const TimeZone& tz, bool noDST)
fcc3d7cb 2184{
479cd5de 2185 long secDiff = GetTimeZone() + tz.GetOffset();
fcc3d7cb 2186
41acf5c0
VZ
2187 // we need to know whether DST is or not in effect for this date unless
2188 // the test disabled by the caller
2189 if ( !noDST && (IsDST() == 1) )
299fcbfe
VZ
2190 {
2191 // FIXME we assume that the DST is always shifted by 1 hour
2192 secDiff -= 3600;
2193 }
2194
d26adb9d
VZ
2195 return Add(wxTimeSpan::Seconds(secDiff));
2196}
2197
2198wxDateTime& wxDateTime::MakeFromTimezone(const TimeZone& tz, bool noDST)
2199{
2200 long secDiff = GetTimeZone() + tz.GetOffset();
2201
2202 // we need to know whether DST is or not in effect for this date unless
2203 // the test disabled by the caller
2204 if ( !noDST && (IsDST() == 1) )
2205 {
2206 // FIXME we assume that the DST is always shifted by 1 hour
2207 secDiff -= 3600;
2208 }
2209
f6bcfd97 2210 return Subtract(wxTimeSpan::Seconds(secDiff));
fcc3d7cb
VZ
2211}
2212
b76b015e
VZ
2213// ----------------------------------------------------------------------------
2214// wxDateTime to/from text representations
2215// ----------------------------------------------------------------------------
2216
299fcbfe 2217wxString wxDateTime::Format(const wxChar *format, const TimeZone& tz) const
b76b015e 2218{
525d8583 2219 wxCHECK_MSG( format, wxEmptyString, _T("NULL format in wxDateTime::Format") );
e6ec579c 2220
f6bcfd97
BP
2221 // we have to use our own implementation if the date is out of range of
2222 // strftime() or if we use non standard specificators
b76b015e 2223 time_t time = GetTicks();
f6bcfd97 2224 if ( (time != (time_t)-1) && !wxStrstr(format, _T("%l")) )
b76b015e
VZ
2225 {
2226 // use strftime()
a452689b
SN
2227 struct tm tmstruct;
2228 struct tm *tm;
299fcbfe
VZ
2229 if ( tz.GetOffset() == -GetTimeZone() )
2230 {
2231 // we are working with local time
a452689b 2232 tm = wxLocaltime_r(&time, &tmstruct);
c5a1681b
VZ
2233
2234 // should never happen
a452689b 2235 wxCHECK_MSG( tm, wxEmptyString, _T("wxLocaltime_r() failed") );
299fcbfe
VZ
2236 }
2237 else
2238 {
479cd5de 2239 time += (int)tz.GetOffset();
299fcbfe 2240
33ac7e6f 2241#if defined(__VMS__) || defined(__WATCOMC__) // time is unsigned so avoid warning
13111b2a 2242 int time2 = (int) time;
9c2882d9 2243 if ( time2 >= 0 )
fb10f04c 2244#else
9c2882d9 2245 if ( time >= 0 )
fb10f04c 2246#endif
c5a1681b 2247 {
a452689b 2248 tm = wxGmtime_r(&time, &tmstruct);
b76b015e 2249
c5a1681b 2250 // should never happen
a452689b 2251 wxCHECK_MSG( tm, wxEmptyString, _T("wxGmtime_r() failed") );
c5a1681b
VZ
2252 }
2253 else
2254 {
2255 tm = (struct tm *)NULL;
2256 }
2257 }
77b88df2 2258#ifndef __WXWINCE__
ba9bbf13 2259 //Windows CE doesn't support strftime or wcsftime, so we use the generic implementation
c5a1681b 2260 if ( tm )
e6ec579c 2261 {
c5a1681b 2262 return CallStrftime(format, tm);
e6ec579c 2263 }
77b88df2 2264#endif
c5a1681b
VZ
2265 //else: use generic code below
2266 }
2267
68ee7c47 2268 // we only parse ANSI C format specifications here, no POSIX 2
f6bcfd97
BP
2269 // complications, no GNU extensions but we do add support for a "%l" format
2270 // specifier allowing to get the number of milliseconds
68ee7c47 2271 Tm tm = GetTm(tz);
e6ec579c 2272
68ee7c47
VZ
2273 // used for calls to strftime() when we only deal with time
2274 struct tm tmTimeOnly;
2275 tmTimeOnly.tm_hour = tm.hour;
2276 tmTimeOnly.tm_min = tm.min;
2277 tmTimeOnly.tm_sec = tm.sec;
2278 tmTimeOnly.tm_wday = 0;
2279 tmTimeOnly.tm_yday = 0;
2280 tmTimeOnly.tm_mday = 1; // any date will do
2281 tmTimeOnly.tm_mon = 0;
2282 tmTimeOnly.tm_year = 76;
2283 tmTimeOnly.tm_isdst = 0; // no DST, we adjust for tz ourselves
e6ec579c 2284
77c3e48a 2285 wxString tmp, res, fmt;
68ee7c47
VZ
2286 for ( const wxChar *p = format; *p; p++ )
2287 {
2288 if ( *p != _T('%') )
2289 {
2290 // copy as is
2291 res += *p;
b76b015e 2292
68ee7c47
VZ
2293 continue;
2294 }
e6ec579c 2295
77c3e48a 2296 // set the default format
68ee7c47 2297 switch ( *++p )
77c3e48a
VZ
2298 {
2299 case _T('Y'): // year has 4 digits
2300 fmt = _T("%04d");
2301 break;
2302
2303 case _T('j'): // day of year has 3 digits
f6bcfd97 2304 case _T('l'): // milliseconds have 3 digits
77c3e48a
VZ
2305 fmt = _T("%03d");
2306 break;
2307
59d04dff
VZ
2308 case _T('w'): // week day as number has only one
2309 fmt = _T("%d");
2310 break;
2311
77c3e48a
VZ
2312 default:
2313 // it's either another valid format specifier in which case
2314 // the format is "%02d" (for all the rest) or we have the
2315 // field width preceding the format in which case it will
2316 // override the default format anyhow
2317 fmt = _T("%02d");
2318 }
2319
68379eaf 2320 bool restart = true;
fc3398f8 2321 while ( restart )
68ee7c47 2322 {
68379eaf 2323 restart = false;
68ee7c47 2324
fc3398f8
VZ
2325 // start of the format specification
2326 switch ( *p )
2327 {
2328 case _T('a'): // a weekday name
2329 case _T('A'):
68379eaf 2330 // second parameter should be true for abbreviated names
fc3398f8
VZ
2331 res += GetWeekDayName(tm.GetWeekDay(),
2332 *p == _T('a') ? Name_Abbr : Name_Full);
2333 break;
68ee7c47 2334
fc3398f8
VZ
2335 case _T('b'): // a month name
2336 case _T('B'):
2337 res += GetMonthName(tm.mon,
2338 *p == _T('b') ? Name_Abbr : Name_Full);
2339 break;
2340
2341 case _T('c'): // locale default date and time representation
2342 case _T('x'): // locale default date representation
77b88df2 2343#ifndef __WXWINCE__
fc3398f8
VZ
2344 //
2345 // the problem: there is no way to know what do these format
2346 // specifications correspond to for the current locale.
2347 //
2348 // the solution: use a hack and still use strftime(): first
2349 // find the YEAR which is a year in the strftime() range (1970
2350 // - 2038) whose Jan 1 falls on the same week day as the Jan 1
2351 // of the real year. Then make a copy of the format and
3103e8a9 2352 // replace all occurrences of YEAR in it with some unique
fc3398f8
VZ
2353 // string not appearing anywhere else in it, then use
2354 // strftime() to format the date in year YEAR and then replace
2355 // YEAR back by the real year and the unique replacement
3103e8a9
JS
2356 // string back with YEAR. Notice that "all occurrences of YEAR"
2357 // means all occurrences of 4 digit as well as 2 digit form!
fc3398f8
VZ
2358 //
2359 // the bugs: we assume that neither of %c nor %x contains any
2360 // fields which may change between the YEAR and real year. For
2361 // example, the week number (%U, %W) and the day number (%j)
2362 // will change if one of these years is leap and the other one
2363 // is not!
68ee7c47 2364 {
fc3398f8
VZ
2365 // find the YEAR: normally, for any year X, Jan 1 or the
2366 // year X + 28 is the same weekday as Jan 1 of X (because
2367 // the weekday advances by 1 for each normal X and by 2
2368 // for each leap X, hence by 5 every 4 years or by 35
2369 // which is 0 mod 7 every 28 years) but this rule breaks
2370 // down if there are years between X and Y which are
2371 // divisible by 4 but not leap (i.e. divisible by 100 but
2372 // not 400), hence the correction.
2373
2374 int yearReal = GetYear(tz);
2375 int mod28 = yearReal % 28;
2376
2377 // be careful to not go too far - we risk to leave the
2378 // supported range
2379 int year;
2380 if ( mod28 < 10 )
2381 {
2382 year = 1988 + mod28; // 1988 == 0 (mod 28)
2383 }
2384 else
2385 {
2386 year = 1970 + mod28 - 10; // 1970 == 10 (mod 28)
2387 }
e6ec579c 2388
fc3398f8
VZ
2389 int nCentury = year / 100,
2390 nCenturyReal = yearReal / 100;
c5a1681b 2391
fc3398f8
VZ
2392 // need to adjust for the years divisble by 400 which are
2393 // not leap but are counted like leap ones if we just take
2394 // the number of centuries in between for nLostWeekDays
2395 int nLostWeekDays = (nCentury - nCenturyReal) -
2396 (nCentury / 4 - nCenturyReal / 4);
c5a1681b 2397
fc3398f8
VZ
2398 // we have to gain back the "lost" weekdays: note that the
2399 // effect of this loop is to not do anything to
2400 // nLostWeekDays (which we won't use any more), but to
2401 // (indirectly) set the year correctly
2402 while ( (nLostWeekDays % 7) != 0 )
2403 {
2404 nLostWeekDays += year++ % 4 ? 1 : 2;
2405 }
88a7a4e1
WS
2406
2407 // Keep year below 2000 so the 2digit year number
2408 // can never match the month or day of the month
2409 if (year>=2000) year-=28;
fc3398f8
VZ
2410 // at any rate, we couldn't go further than 1988 + 9 + 28!
2411 wxASSERT_MSG( year < 2030,
2412 _T("logic error in wxDateTime::Format") );
c5a1681b 2413
88a7a4e1 2414 wxString strYear, strYear2;
fc3398f8
VZ
2415 strYear.Printf(_T("%d"), year);
2416 strYear2.Printf(_T("%d"), year % 100);
88a7a4e1 2417
1d7bc913 2418 // find four strings not occurring in format (this is surely
3103e8a9 2419 // not the optimal way of doing it... improvements welcome!)
17a1ebd1 2420 wxString fmt2 = format;
88a7a4e1
WS
2421 wxString replacement,replacement2,replacement3,replacement4;
2422 for (int rnr=1; rnr<5 ; rnr++)
2423 {
2424 wxString r = (wxChar)-rnr;
2425 while ( fmt2.Find(r) != wxNOT_FOUND )
2426 {
2427 r << (wxChar)-rnr;
2428 }
2429
2430 switch (rnr)
2431 {
2432 case 1: replacement=r; break;
2433 case 2: replacement2=r; break;
2434 case 3: replacement3=r; break;
2435 case 4: replacement4=r; break;
2436 }
2437 }
3103e8a9 2438 // replace all occurrences of year with it
17a1ebd1 2439 bool wasReplaced = fmt2.Replace(strYear, replacement) > 0;
1d7bc913 2440 // evaluation order ensures we always attempt the replacement.
88a7a4e1 2441 wasReplaced = (fmt2.Replace(strYear2, replacement2) > 0) | wasReplaced ;
fc3398f8
VZ
2442
2443 // use strftime() to format the same date but in supported
2444 // year
2445 //
2446 // NB: we assume that strftime() doesn't check for the
2447 // date validity and will happily format the date
2448 // corresponding to Feb 29 of a non leap year (which
2449 // may happen if yearReal was leap and year is not)
2450 struct tm tmAdjusted;
2451 InitTm(tmAdjusted);
2452 tmAdjusted.tm_hour = tm.hour;
2453 tmAdjusted.tm_min = tm.min;
2454 tmAdjusted.tm_sec = tm.sec;
2455 tmAdjusted.tm_wday = tm.GetWeekDay();
2456 tmAdjusted.tm_yday = GetDayOfYear();
2457 tmAdjusted.tm_mday = tm.mday;
2458 tmAdjusted.tm_mon = tm.mon;
2459 tmAdjusted.tm_year = year - 1900;
2460 tmAdjusted.tm_isdst = 0; // no DST, already adjusted
2461 wxString str = CallStrftime(*p == _T('c') ? _T("%c")
2462 : _T("%x"),
2463 &tmAdjusted);
2464
3103e8a9 2465 // now replace the occurrence of 1999 with the real year
88a7a4e1
WS
2466 // we do this in two stages to stop the 2 digit year
2467 // matching any substring of the 4 digit year.
2468 // Any day,month hours and minutes components should be safe due
2469 // to ensuring the range of the years.
fc3398f8
VZ
2470 wxString strYearReal, strYearReal2;
2471 strYearReal.Printf(_T("%04d"), yearReal);
2472 strYearReal2.Printf(_T("%02d"), yearReal % 100);
1d7bc913
RG
2473 str.Replace(strYear, replacement3);
2474 str.Replace(strYear2,replacement4);
2475 str.Replace(replacement3, strYearReal);
2476 str.Replace(replacement4, strYearReal2);
fc3398f8 2477
3103e8a9 2478 // and replace back all occurrences of replacement string
fc3398f8
VZ
2479 if ( wasReplaced )
2480 {
2481 str.Replace(replacement2, strYear2);
2482 str.Replace(replacement, strYear);
2483 }
2484
2485 res += str;
68ee7c47 2486 }
77b88df2 2487#else
ba9bbf13
WS
2488 //Use "%m/%d/%y %H:%M:%S" format instead
2489 res += wxString::Format(wxT("%02d/%02d/%04d %02d:%02d:%02d"),
2490 tm.mon+1,tm.mday, tm.year, tm.hour, tm.min, tm.sec);
77b88df2 2491#endif
fc3398f8 2492 break;
c5a1681b 2493
fc3398f8
VZ
2494 case _T('d'): // day of a month (01-31)
2495 res += wxString::Format(fmt, tm.mday);
2496 break;
68ee7c47 2497
fc3398f8
VZ
2498 case _T('H'): // hour in 24h format (00-23)
2499 res += wxString::Format(fmt, tm.hour);
2500 break;
2501
2502 case _T('I'): // hour in 12h format (01-12)
68ee7c47 2503 {
fc3398f8
VZ
2504 // 24h -> 12h, 0h -> 12h too
2505 int hour12 = tm.hour > 12 ? tm.hour - 12
2506 : tm.hour ? tm.hour : 12;
2507 res += wxString::Format(fmt, hour12);
68ee7c47 2508 }
fc3398f8 2509 break;
c5a1681b 2510
fc3398f8
VZ
2511 case _T('j'): // day of the year
2512 res += wxString::Format(fmt, GetDayOfYear(tz));
2513 break;
68ee7c47 2514
f6bcfd97
BP
2515 case _T('l'): // milliseconds (NOT STANDARD)
2516 res += wxString::Format(fmt, GetMillisecond(tz));
2517 break;
2518
fc3398f8
VZ
2519 case _T('m'): // month as a number (01-12)
2520 res += wxString::Format(fmt, tm.mon + 1);
2521 break;
68ee7c47 2522
fc3398f8
VZ
2523 case _T('M'): // minute as a decimal number (00-59)
2524 res += wxString::Format(fmt, tm.min);
2525 break;
68ee7c47 2526
fc3398f8 2527 case _T('p'): // AM or PM string
77b88df2 2528#ifndef __WXWINCE__
fc3398f8 2529 res += CallStrftime(_T("%p"), &tmTimeOnly);
77b88df2 2530#else
ba9bbf13 2531 res += (tmTimeOnly.tm_hour > 12) ? wxT("pm") : wxT("am");
77b88df2 2532#endif
fc3398f8 2533 break;
68ee7c47 2534
fc3398f8
VZ
2535 case _T('S'): // second as a decimal number (00-61)
2536 res += wxString::Format(fmt, tm.sec);
2537 break;
68ee7c47 2538
fc3398f8
VZ
2539 case _T('U'): // week number in the year (Sunday 1st week day)
2540 res += wxString::Format(fmt, GetWeekOfYear(Sunday_First, tz));
2541 break;
68ee7c47 2542
fc3398f8
VZ
2543 case _T('W'): // week number in the year (Monday 1st week day)
2544 res += wxString::Format(fmt, GetWeekOfYear(Monday_First, tz));
2545 break;
68ee7c47 2546
fc3398f8
VZ
2547 case _T('w'): // weekday as a number (0-6), Sunday = 0
2548 res += wxString::Format(fmt, tm.GetWeekDay());
2549 break;
68ee7c47 2550
fc3398f8 2551 // case _T('x'): -- handled with "%c"
68ee7c47 2552
fc3398f8
VZ
2553 case _T('X'): // locale default time representation
2554 // just use strftime() to format the time for us
77b88df2 2555#ifndef __WXWINCE__
fc3398f8 2556 res += CallStrftime(_T("%X"), &tmTimeOnly);
77b88df2 2557#else
ba9bbf13 2558 res += wxString::Format(wxT("%02d:%02d:%02d"),tm.hour, tm.min, tm.sec);
77b88df2 2559#endif
fc3398f8 2560 break;
68ee7c47 2561
fc3398f8
VZ
2562 case _T('y'): // year without century (00-99)
2563 res += wxString::Format(fmt, tm.year % 100);
2564 break;
68ee7c47 2565
fc3398f8
VZ
2566 case _T('Y'): // year with century
2567 res += wxString::Format(fmt, tm.year);
2568 break;
68ee7c47 2569
fc3398f8 2570 case _T('Z'): // timezone name
77b88df2 2571#ifndef __WXWINCE__
fc3398f8 2572 res += CallStrftime(_T("%Z"), &tmTimeOnly);
77b88df2 2573#endif
fc3398f8 2574 break;
68ee7c47 2575
fc3398f8
VZ
2576 default:
2577 // is it the format width?
2578 fmt.Empty();
2579 while ( *p == _T('-') || *p == _T('+') ||
2580 *p == _T(' ') || wxIsdigit(*p) )
2581 {
2582 fmt += *p;
2583 }
68ee7c47 2584
bb8a8478 2585 if ( !fmt.empty() )
fc3398f8
VZ
2586 {
2587 // we've only got the flags and width so far in fmt
2588 fmt.Prepend(_T('%'));
2589 fmt.Append(_T('d'));
68ee7c47 2590
68379eaf 2591 restart = true;
68ee7c47 2592
fc3398f8
VZ
2593 break;
2594 }
68ee7c47 2595
fc3398f8
VZ
2596 // no, it wasn't the width
2597 wxFAIL_MSG(_T("unknown format specificator"));
68ee7c47 2598
fc3398f8 2599 // fall through and just copy it nevertheless
68ee7c47 2600
fc3398f8
VZ
2601 case _T('%'): // a percent sign
2602 res += *p;
2603 break;
2604
2605 case 0: // the end of string
2606 wxFAIL_MSG(_T("missing format at the end of string"));
2607
2608 // just put the '%' which was the last char in format
2609 res += _T('%');
2610 break;
2611 }
68ee7c47 2612 }
c5a1681b
VZ
2613 }
2614
68ee7c47 2615 return res;
b76b015e 2616}
fcc3d7cb 2617
cd0b1709
VZ
2618// this function parses a string in (strict) RFC 822 format: see the section 5
2619// of the RFC for the detailed description, but briefly it's something of the
2620// form "Sat, 18 Dec 1999 00:48:30 +0100"
2621//
2622// this function is "strict" by design - it must reject anything except true
2623// RFC822 time specs.
2624//
2625// TODO a great candidate for using reg exps
2626const wxChar *wxDateTime::ParseRfc822Date(const wxChar* date)
2627{
2628 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
2629
2630 const wxChar *p = date;
2631 const wxChar *comma = wxStrchr(p, _T(','));
2632 if ( comma )
2633 {
2634 // the part before comma is the weekday
2635
2636 // skip it for now - we don't use but might check that it really
2637 // corresponds to the specfied date
2638 p = comma + 1;
2639
2640 if ( *p != _T(' ') )
2641 {
2642 wxLogDebug(_T("no space after weekday in RFC822 time spec"));
2643
2644 return (wxChar *)NULL;
2645 }
2646
2647 p++; // skip space
2648 }
2649
2650 // the following 1 or 2 digits are the day number
2651 if ( !wxIsdigit(*p) )
2652 {
2653 wxLogDebug(_T("day number expected in RFC822 time spec, none found"));
2654
2655 return (wxChar *)NULL;
2656 }
2657
907173e5 2658 wxDateTime_t day = (wxDateTime_t)(*p++ - _T('0'));
cd0b1709
VZ
2659 if ( wxIsdigit(*p) )
2660 {
2661 day *= 10;
907173e5 2662 day = (wxDateTime_t)(day + (*p++ - _T('0')));
cd0b1709
VZ
2663 }
2664
2665 if ( *p++ != _T(' ') )
2666 {
2667 return (wxChar *)NULL;
2668 }
2669
2670 // the following 3 letters specify the month
2671 wxString monName(p, 3);
2672 Month mon;
2673 if ( monName == _T("Jan") )
2674 mon = Jan;
2675 else if ( monName == _T("Feb") )
2676 mon = Feb;
2677 else if ( monName == _T("Mar") )
2678 mon = Mar;
2679 else if ( monName == _T("Apr") )
2680 mon = Apr;
2681 else if ( monName == _T("May") )
2682 mon = May;
2683 else if ( monName == _T("Jun") )
2684 mon = Jun;
2685 else if ( monName == _T("Jul") )
2686 mon = Jul;
2687 else if ( monName == _T("Aug") )
2688 mon = Aug;
2689 else if ( monName == _T("Sep") )
2690 mon = Sep;
2691 else if ( monName == _T("Oct") )
2692 mon = Oct;
2693 else if ( monName == _T("Nov") )
2694 mon = Nov;
2695 else if ( monName == _T("Dec") )
2696 mon = Dec;
2697 else
2698 {
2699 wxLogDebug(_T("Invalid RFC 822 month name '%s'"), monName.c_str());
2700
2701 return (wxChar *)NULL;
2702 }
2703
2704 p += 3;
2705
2706 if ( *p++ != _T(' ') )
2707 {
2708 return (wxChar *)NULL;
2709 }
2710
2711 // next is the year
2712 if ( !wxIsdigit(*p) )
2713 {
2714 // no year?
2715 return (wxChar *)NULL;
2716 }
2717
2718 int year = *p++ - _T('0');
2719
2720 if ( !wxIsdigit(*p) )
2721 {
2722 // should have at least 2 digits in the year
2723 return (wxChar *)NULL;
2724 }
2725
2726 year *= 10;
2727 year += *p++ - _T('0');
2728
2729 // is it a 2 digit year (as per original RFC 822) or a 4 digit one?
2730 if ( wxIsdigit(*p) )
2731 {
2732 year *= 10;
2733 year += *p++ - _T('0');
2734
2735 if ( !wxIsdigit(*p) )
2736 {
2737 // no 3 digit years please
2738 return (wxChar *)NULL;
2739 }
2740
2741 year *= 10;
2742 year += *p++ - _T('0');
2743 }
2744
2745 if ( *p++ != _T(' ') )
2746 {
2747 return (wxChar *)NULL;
2748 }
2749
2750 // time is in the format hh:mm:ss and seconds are optional
2751 if ( !wxIsdigit(*p) )
2752 {
2753 return (wxChar *)NULL;
2754 }
2755
42841dfc 2756 wxDateTime_t hour = (wxDateTime_t)(*p++ - _T('0'));
cd0b1709
VZ
2757
2758 if ( !wxIsdigit(*p) )
2759 {
2760 return (wxChar *)NULL;
2761 }
2762
2763 hour *= 10;
42841dfc 2764 hour = (wxDateTime_t)(hour + (*p++ - _T('0')));
cd0b1709
VZ
2765
2766 if ( *p++ != _T(':') )
2767 {
2768 return (wxChar *)NULL;
2769 }
2770
2771 if ( !wxIsdigit(*p) )
2772 {
2773 return (wxChar *)NULL;
2774 }
2775
42841dfc 2776 wxDateTime_t min = (wxDateTime_t)(*p++ - _T('0'));
cd0b1709
VZ
2777
2778 if ( !wxIsdigit(*p) )
2779 {
2780 return (wxChar *)NULL;
2781 }
2782
2783 min *= 10;
42841dfc 2784 min = (wxDateTime_t)(min + *p++ - _T('0'));
cd0b1709
VZ
2785
2786 wxDateTime_t sec = 0;
2787 if ( *p++ == _T(':') )
2788 {
2789 if ( !wxIsdigit(*p) )
2790 {
2791 return (wxChar *)NULL;
2792 }
2793
42841dfc 2794 sec = (wxDateTime_t)(*p++ - _T('0'));
cd0b1709
VZ
2795
2796 if ( !wxIsdigit(*p) )
2797 {
2798 return (wxChar *)NULL;
2799 }
2800
2801 sec *= 10;
42841dfc 2802 sec = (wxDateTime_t)(sec + *p++ - _T('0'));
cd0b1709
VZ
2803 }
2804
2805 if ( *p++ != _T(' ') )
2806 {
2807 return (wxChar *)NULL;
2808 }
2809
2810 // and now the interesting part: the timezone
ba9bbf13 2811 int offset wxDUMMY_INITIALIZE(0);
cd0b1709
VZ
2812 if ( *p == _T('-') || *p == _T('+') )
2813 {
2814 // the explicit offset given: it has the form of hhmm
2815 bool plus = *p++ == _T('+');
2816
2817 if ( !wxIsdigit(*p) || !wxIsdigit(*(p + 1)) )
2818 {
2819 return (wxChar *)NULL;
2820 }
2821
2822 // hours
d26adb9d 2823 offset = MIN_PER_HOUR*(10*(*p - _T('0')) + (*(p + 1) - _T('0')));
cd0b1709
VZ
2824
2825 p += 2;
2826
2827 if ( !wxIsdigit(*p) || !wxIsdigit(*(p + 1)) )
2828 {
2829 return (wxChar *)NULL;
2830 }
2831
2832 // minutes
2833 offset += 10*(*p - _T('0')) + (*(p + 1) - _T('0'));
2834
2835 if ( !plus )
2836 {
2837 offset = -offset;
2838 }
2839
2840 p += 2;
2841 }
2842 else
2843 {
2844 // the symbolic timezone given: may be either military timezone or one
2845 // of standard abbreviations
2846 if ( !*(p + 1) )
2847 {
2848 // military: Z = UTC, J unused, A = -1, ..., Y = +12
2849 static const int offsets[26] =
2850 {
2851 //A B C D E F G H I J K L M
2852 -1, -2, -3, -4, -5, -6, -7, -8, -9, 0, -10, -11, -12,
2853 //N O P R Q S T U V W Z Y Z
2854 +1, +2, +3, +4, +5, +6, +7, +8, +9, +10, +11, +12, 0
2855 };
2856
2857 if ( *p < _T('A') || *p > _T('Z') || *p == _T('J') )
2858 {
2859 wxLogDebug(_T("Invalid militaty timezone '%c'"), *p);
2860
2861 return (wxChar *)NULL;
2862 }
2863
2864 offset = offsets[*p++ - _T('A')];
2865 }
2866 else
2867 {
2868 // abbreviation
2869 wxString tz = p;
2870 if ( tz == _T("UT") || tz == _T("UTC") || tz == _T("GMT") )
2871 offset = 0;
2872 else if ( tz == _T("AST") )
2873 offset = AST - GMT0;
2874 else if ( tz == _T("ADT") )
2875 offset = ADT - GMT0;
2876 else if ( tz == _T("EST") )
2877 offset = EST - GMT0;
2878 else if ( tz == _T("EDT") )
2879 offset = EDT - GMT0;
2880 else if ( tz == _T("CST") )
2881 offset = CST - GMT0;
2882 else if ( tz == _T("CDT") )
2883 offset = CDT - GMT0;
2884 else if ( tz == _T("MST") )
2885 offset = MST - GMT0;
2886 else if ( tz == _T("MDT") )
2887 offset = MDT - GMT0;
2888 else if ( tz == _T("PST") )
2889 offset = PST - GMT0;
2890 else if ( tz == _T("PDT") )
2891 offset = PDT - GMT0;
2892 else
2893 {
2894 wxLogDebug(_T("Unknown RFC 822 timezone '%s'"), p);
2895
2896 return (wxChar *)NULL;
2897 }
2898
2899 p += tz.length();
2900 }
96531a71 2901
cd0b1709 2902 // make it minutes
d26adb9d 2903 offset *= MIN_PER_HOUR;
cd0b1709
VZ
2904 }
2905
d26adb9d 2906 // the spec was correct, construct the date from the values we found
cd0b1709 2907 Set(day, mon, year, hour, min, sec);
18c9aebe 2908 MakeFromTimezone(TimeZone((wxDateTime_t)(offset*SEC_PER_MIN)));
cd0b1709
VZ
2909
2910 return p;
2911}
2912
64f9c100 2913#ifdef __WINDOWS__
e44501a0 2914
be4e7abf
VZ
2915// returns the string containing strftime() format used for short dates in the
2916// current locale or an empty string
2917static wxString GetLocaleDateFormat()
64f9c100 2918{
be4e7abf
VZ
2919 wxString fmtWX;
2920
05eb2d40
VZ
2921 // there is no setlocale() under Windows CE, so just always query the
2922 // system there
2923#ifndef __WXWINCE__
64f9c100 2924 if ( strcmp(setlocale(LC_ALL, NULL), "C") != 0 )
e44501a0 2925#endif
64f9c100
VS
2926 {
2927 // The locale was programatically set to non-C. We assume that this was
2928 // done using wxLocale, in which case thread's current locale is also
2929 // set to correct LCID value and we can use GetLocaleInfo to determine
2930 // the correct formatting string:
c5fe22f8
JS
2931#ifdef __WXWINCE__
2932 LCID lcid = LOCALE_USER_DEFAULT;
2933#else
64f9c100 2934 LCID lcid = GetThreadLocale();
c5fe22f8 2935#endif
be4e7abf
VZ
2936 // according to MSDN 80 chars is max allowed for short date format
2937 wxChar fmt[81];
2938 if ( ::GetLocaleInfo(lcid, LOCALE_SSHORTDATE, fmt, WXSIZEOF(fmt)) )
64f9c100 2939 {
be4e7abf
VZ
2940 wxChar chLast = _T('\0');
2941 size_t lastCount = 0;
2942 for ( const wxChar *p = fmt; /* NUL handled inside */; p++ )
64f9c100 2943 {
be4e7abf 2944 if ( *p == chLast )
64f9c100 2945 {
be4e7abf
VZ
2946 lastCount++;
2947 continue;
64f9c100 2948 }
be4e7abf
VZ
2949
2950 switch ( *p )
64f9c100 2951 {
be4e7abf
VZ
2952 // these characters come in groups, start counting them
2953 case _T('d'):
2954 case _T('M'):
2955 case _T('y'):
2956 case _T('g'):
2957 chLast = *p;
2958 lastCount = 1;
2959 break;
2960
2961 default:
2962 // first deal with any special characters we have had
2963 if ( lastCount )
2964 {
2965 switch ( chLast )
2966 {
2967 case _T('d'):
2968 switch ( lastCount )
2969 {
2970 case 1: // d
2971 case 2: // dd
2972 // these two are the same as we
2973 // don't distinguish between 1 and
2974 // 2 digits for days
2975 fmtWX += _T("%d");
2976 break;
2977
2978 case 3: // ddd
2979 fmtWX += _T("%a");
2980 break;
2981
2982 case 4: // dddd
2983 fmtWX += _T("%A");
2984 break;
2985
2986 default:
2987 wxFAIL_MSG( _T("too many 'd's") );
2988 }
2989 break;
2990
2991 case _T('M'):
2992 switch ( lastCount )
2993 {
2994 case 1: // M
2995 case 2: // MM
2996 // as for 'd' and 'dd' above
2997 fmtWX += _T("%m");
2998 break;
2999
3000 case 3:
3001 fmtWX += _T("%b");
3002 break;
3003
3004 case 4:
3005 fmtWX += _T("%B");
3006 break;
3007
3008 default:
3009 wxFAIL_MSG( _T("too many 'M's") );
3010 }
3011 break;
3012
3013 case _T('y'):
3014 switch ( lastCount )
3015 {
3016 case 1: // y
3017 case 2: // yy
3018 fmtWX += _T("%y");
3019 break;
3020
3021 case 4: // yyyy
3022 fmtWX += _T("%Y");
3023 break;
3024
3025 default:
3026 wxFAIL_MSG( _T("wrong number of 'y's") );
3027 }
3028 break;
3029
3030 case _T('g'):
3031 // strftime() doesn't have era string,
3032 // ignore this format
3033 wxASSERT_MSG( lastCount <= 2,
3034 _T("too many 'g's") );
3035 break;
3036
3037 default:
3038 wxFAIL_MSG( _T("unreachable") );
3039 }
3040
3041 chLast = _T('\0');
3042 lastCount = 0;
3043 }
3044
3045 // not a special character so must be just a separator,
3046 // treat as is
3047 if ( *p != _T('\0') )
3048 {
3049 if ( *p == _T('%') )
3050 {
3051 // this one needs to be escaped
3052 fmtWX += _T('%');
3053 }
3054
3055 fmtWX += *p;
3056 }
64f9c100 3057 }
be4e7abf
VZ
3058
3059 if ( *p == _T('\0') )
3060 break;
64f9c100
VS
3061 }
3062 }
be4e7abf
VZ
3063 //else: GetLocaleInfo() failed, leave fmtDate value unchanged and
3064 // try our luck with the default formats
64f9c100 3065 }
be4e7abf
VZ
3066 //else: default C locale, default formats should work
3067
3068 return fmtWX;
64f9c100 3069}
e44501a0 3070
64f9c100
VS
3071#endif // __WINDOWS__
3072
f0f951fa
VZ
3073const wxChar *wxDateTime::ParseFormat(const wxChar *date,
3074 const wxChar *format,
3075 const wxDateTime& dateDef)
cd0b1709
VZ
3076{
3077 wxCHECK_MSG( date && format, (wxChar *)NULL,
f0f951fa 3078 _T("NULL pointer in wxDateTime::ParseFormat()") );
cd0b1709 3079
f0f951fa
VZ
3080 wxString str;
3081 unsigned long num;
cd0b1709 3082
f0f951fa 3083 // what fields have we found?
68379eaf
WS
3084 bool haveWDay = false,
3085 haveYDay = false,
3086 haveDay = false,
3087 haveMon = false,
3088 haveYear = false,
3089 haveHour = false,
3090 haveMin = false,
3091 haveSec = false;
3092
3093 bool hourIsIn12hFormat = false, // or in 24h one?
3094 isPM = false; // AM by default
f0f951fa
VZ
3095
3096 // and the value of the items we have (init them to get rid of warnings)
3097 wxDateTime_t sec = 0,
3098 min = 0,
3099 hour = 0;
3100 WeekDay wday = Inv_WeekDay;
3101 wxDateTime_t yday = 0,
3102 mday = 0;
3103 wxDateTime::Month mon = Inv_Month;
3104 int year = 0;
3105
3106 const wxChar *input = date;
3107 for ( const wxChar *fmt = format; *fmt; fmt++ )
3108 {
3109 if ( *fmt != _T('%') )
3110 {
3111 if ( wxIsspace(*fmt) )
3112 {
3113 // a white space in the format string matches 0 or more white
3114 // spaces in the input
3115 while ( wxIsspace(*input) )
3116 {
3117 input++;
3118 }
3119 }
3120 else // !space
3121 {
3122 // any other character (not whitespace, not '%') must be
3123 // matched by itself in the input
3124 if ( *input++ != *fmt )
3125 {
3126 // no match
3127 return (wxChar *)NULL;
3128 }
3129 }
3130
3131 // done with this format char
3132 continue;
3133 }
3134
3135 // start of a format specification
f6bcfd97
BP
3136
3137 // parse the optional width
3138 size_t width = 0;
1c193821 3139 while ( wxIsdigit(*++fmt) )
f6bcfd97
BP
3140 {
3141 width *= 10;
3142 width += *fmt - _T('0');
3143 }
3144
4147efe9
VZ
3145 // the default widths for the various fields
3146 if ( !width )
3147 {
3148 switch ( *fmt )
3149 {
3150 case _T('Y'): // year has 4 digits
3151 width = 4;
3152 break;
3153
3154 case _T('j'): // day of year has 3 digits
3155 case _T('l'): // milliseconds have 3 digits
3156 width = 3;
3157 break;
3158
3159 case _T('w'): // week day as number has only one
3160 width = 1;
3161 break;
3162
3163 default:
3164 // default for all other fields
3165 width = 2;
3166 }
3167 }
3168
f6bcfd97
BP
3169 // then the format itself
3170 switch ( *fmt )
f0f951fa
VZ
3171 {
3172 case _T('a'): // a weekday name
3173 case _T('A'):
3174 {
3175 int flag = *fmt == _T('a') ? Name_Abbr : Name_Full;
3176 wday = GetWeekDayFromName(GetAlphaToken(input), flag);
3177 if ( wday == Inv_WeekDay )
3178 {
3179 // no match
3180 return (wxChar *)NULL;
3181 }
3182 }
68379eaf 3183 haveWDay = true;
f0f951fa
VZ
3184 break;
3185
3186 case _T('b'): // a month name
3187 case _T('B'):
3188 {
3189 int flag = *fmt == _T('b') ? Name_Abbr : Name_Full;
3190 mon = GetMonthFromName(GetAlphaToken(input), flag);
3191 if ( mon == Inv_Month )
3192 {
3193 // no match
3194 return (wxChar *)NULL;
3195 }
3196 }
68379eaf 3197 haveMon = true;
f0f951fa
VZ
3198 break;
3199
3200 case _T('c'): // locale default date and time representation
3201 {
3202 wxDateTime dt;
3203
3204 // this is the format which corresponds to ctime() output
3205 // and strptime("%c") should parse it, so try it first
41acf5c0 3206 static const wxChar *fmtCtime = _T("%a %b %d %H:%M:%S %Y");
f0f951fa
VZ
3207
3208 const wxChar *result = dt.ParseFormat(input, fmtCtime);
3209 if ( !result )
3210 {
3211 result = dt.ParseFormat(input, _T("%x %X"));
3212 }
3213
3214 if ( !result )
3215 {
3216 result = dt.ParseFormat(input, _T("%X %x"));
3217 }
3218
3219 if ( !result )
3220 {
3221 // we've tried everything and still no match
3222 return (wxChar *)NULL;
3223 }
3224
be4017f8
VZ
3225 Tm tm = dt.GetTm();
3226
3227 haveDay = haveMon = haveYear =
68379eaf 3228 haveHour = haveMin = haveSec = true;
be4017f8
VZ
3229
3230 hour = tm.hour;
3231 min = tm.min;
3232 sec = tm.sec;
3233
3234 year = tm.year;
3235 mon = tm.mon;
3236 mday = tm.mday;
3237
f0f951fa
VZ
3238 input = result;
3239 }
3240 break;
3241
3242 case _T('d'): // day of a month (01-31)
f6bcfd97
BP
3243 if ( !GetNumericToken(width, input, &num) ||
3244 (num > 31) || (num < 1) )
f0f951fa
VZ
3245 {
3246 // no match
3247 return (wxChar *)NULL;
3248 }
3249
3250 // we can't check whether the day range is correct yet, will
3251 // do it later - assume ok for now
68379eaf 3252 haveDay = true;
f0f951fa
VZ
3253 mday = (wxDateTime_t)num;
3254 break;
5f287370 3255
f0f951fa 3256 case _T('H'): // hour in 24h format (00-23)
f6bcfd97 3257 if ( !GetNumericToken(width, input, &num) || (num > 23) )
f0f951fa
VZ
3258 {
3259 // no match
3260 return (wxChar *)NULL;
3261 }
3262
68379eaf 3263 haveHour = true;
f0f951fa
VZ
3264 hour = (wxDateTime_t)num;
3265 break;
3266
3267 case _T('I'): // hour in 12h format (01-12)
f6bcfd97 3268 if ( !GetNumericToken(width, input, &num) || !num || (num > 12) )
f0f951fa
VZ
3269 {
3270 // no match
3271 return (wxChar *)NULL;
3272 }
3273
68379eaf
WS
3274 haveHour = true;
3275 hourIsIn12hFormat = true;
479cd5de 3276 hour = (wxDateTime_t)(num % 12); // 12 should be 0
f0f951fa
VZ
3277 break;
3278
3279 case _T('j'): // day of the year
f6bcfd97 3280 if ( !GetNumericToken(width, input, &num) || !num || (num > 366) )
f0f951fa
VZ
3281 {
3282 // no match
3283 return (wxChar *)NULL;
3284 }
3285
68379eaf 3286 haveYDay = true;
f0f951fa
VZ
3287 yday = (wxDateTime_t)num;
3288 break;
3289
3290 case _T('m'): // month as a number (01-12)
f6bcfd97 3291 if ( !GetNumericToken(width, input, &num) || !num || (num > 12) )
f0f951fa
VZ
3292 {
3293 // no match
3294 return (wxChar *)NULL;
3295 }
3296
68379eaf 3297 haveMon = true;
be4017f8 3298 mon = (Month)(num - 1);
f0f951fa
VZ
3299 break;
3300
3301 case _T('M'): // minute as a decimal number (00-59)
f6bcfd97 3302 if ( !GetNumericToken(width, input, &num) || (num > 59) )
f0f951fa
VZ
3303 {
3304 // no match
3305 return (wxChar *)NULL;
3306 }
3307
68379eaf 3308 haveMin = true;
f0f951fa
VZ
3309 min = (wxDateTime_t)num;
3310 break;
3311
3312 case _T('p'): // AM or PM string
3313 {
3314 wxString am, pm, token = GetAlphaToken(input);
3315
3316 GetAmPmStrings(&am, &pm);
bb8a8478 3317 if (am.empty() && pm.empty())
0308fd61 3318 return (wxChar *)NULL; // no am/pm strings defined
f0f951fa
VZ
3319 if ( token.CmpNoCase(pm) == 0 )
3320 {
68379eaf 3321 isPM = true;
f0f951fa
VZ
3322 }
3323 else if ( token.CmpNoCase(am) != 0 )
3324 {
3325 // no match
3326 return (wxChar *)NULL;
3327 }
3328 }
3329 break;
3330
3331 case _T('r'): // time as %I:%M:%S %p
3332 {
3333 wxDateTime dt;
3334 input = dt.ParseFormat(input, _T("%I:%M:%S %p"));
3335 if ( !input )
3336 {
3337 // no match
3338 return (wxChar *)NULL;
3339 }
3340
68379eaf 3341 haveHour = haveMin = haveSec = true;
f0f951fa
VZ
3342
3343 Tm tm = dt.GetTm();
3344 hour = tm.hour;
3345 min = tm.min;
3346 sec = tm.sec;
3347 }
3348 break;
3349
3350 case _T('R'): // time as %H:%M
3351 {
3352 wxDateTime dt;
3353 input = dt.ParseFormat(input, _T("%H:%M"));
3354 if ( !input )
3355 {
3356 // no match
3357 return (wxChar *)NULL;
3358 }
3359
68379eaf 3360 haveHour = haveMin = true;
f0f951fa
VZ
3361
3362 Tm tm = dt.GetTm();
3363 hour = tm.hour;
3364 min = tm.min;
3365 }
7d3e806d 3366 break;
f0f951fa
VZ
3367
3368 case _T('S'): // second as a decimal number (00-61)
f6bcfd97 3369 if ( !GetNumericToken(width, input, &num) || (num > 61) )
f0f951fa
VZ
3370 {
3371 // no match
3372 return (wxChar *)NULL;
3373 }
3374
68379eaf 3375 haveSec = true;
f0f951fa
VZ
3376 sec = (wxDateTime_t)num;
3377 break;
3378
3379 case _T('T'): // time as %H:%M:%S
3380 {
3381 wxDateTime dt;
3382 input = dt.ParseFormat(input, _T("%H:%M:%S"));
3383 if ( !input )
3384 {
3385 // no match
3386 return (wxChar *)NULL;
3387 }
3388
68379eaf 3389 haveHour = haveMin = haveSec = true;
f0f951fa
VZ
3390
3391 Tm tm = dt.GetTm();
3392 hour = tm.hour;
3393 min = tm.min;
3394 sec = tm.sec;
3395 }
be4017f8 3396 break;
f0f951fa
VZ
3397
3398 case _T('w'): // weekday as a number (0-6), Sunday = 0
f6bcfd97 3399 if ( !GetNumericToken(width, input, &num) || (wday > 6) )
f0f951fa
VZ
3400 {
3401 // no match
3402 return (wxChar *)NULL;
3403 }
3404
68379eaf 3405 haveWDay = true;
f0f951fa
VZ
3406 wday = (WeekDay)num;
3407 break;
3408
3409 case _T('x'): // locale default date representation
3410#ifdef HAVE_STRPTIME
2423ef22 3411 // try using strptime() -- it may fail even if the input is
f0f951fa 3412 // correct but the date is out of range, so we will fall back
2423ef22 3413 // to our generic code anyhow
f0f951fa
VZ
3414 {
3415 struct tm tm;
2423ef22
VZ
3416
3417 const wxChar *result = CallStrptime(input, "%x", &tm);
f0f951fa
VZ
3418 if ( result )
3419 {
3420 input = result;
3421
68379eaf 3422 haveDay = haveMon = haveYear = true;
f0f951fa
VZ
3423
3424 year = 1900 + tm.tm_year;
3425 mon = (Month)tm.tm_mon;
3426 mday = tm.tm_mday;
3427
3428 break;
3429 }
3430 }
3431#endif // HAVE_STRPTIME
3432
f0f951fa
VZ
3433 {
3434 wxDateTime dt;
be4e7abf
VZ
3435 wxString fmtDate,
3436 fmtDateAlt;
f0f951fa 3437
64f9c100
VS
3438#ifdef __WINDOWS__
3439 // The above doesn't work for all locales, try to query
3440 // Windows for the right way of formatting the date:
be4e7abf
VZ
3441 fmtDate = GetLocaleDateFormat();
3442 if ( fmtDate.empty() )
64f9c100 3443#endif
be4e7abf
VZ
3444 {
3445 if ( IsWestEuropeanCountry(GetCountry()) ||
3446 GetCountry() == Russia )
3447 {
3448 fmtDate = _T("%d/%m/%y");
3449 fmtDateAlt = _T("%m/%d/%y");
3450 }
3451 else // assume USA
3452 {
3453 fmtDate = _T("%m/%d/%y");
3454 fmtDateAlt = _T("%d/%m/%y");
3455 }
3456 }
64f9c100 3457
f0f951fa 3458 const wxChar *result = dt.ParseFormat(input, fmtDate);
41acf5c0 3459
be4e7abf 3460 if ( !result && !fmtDateAlt.empty() )
41acf5c0
VZ
3461 {
3462 // ok, be nice and try another one
3463 result = dt.ParseFormat(input, fmtDateAlt);
3464 }
3465
f0f951fa
VZ
3466 if ( !result )
3467 {
3468 // bad luck
3469 return (wxChar *)NULL;
3470 }
3471
3472 Tm tm = dt.GetTm();
3473
68379eaf 3474 haveDay = haveMon = haveYear = true;
f0f951fa
VZ
3475
3476 year = tm.year;
3477 mon = tm.mon;
3478 mday = tm.mday;
3479
3480 input = result;
3481 }
3482
3483 break;
3484
3485 case _T('X'): // locale default time representation
3486#ifdef HAVE_STRPTIME
3487 {
3488 // use strptime() to do it for us (FIXME !Unicode friendly)
3489 struct tm tm;
2423ef22 3490 input = CallStrptime(input, "%X", &tm);
f0f951fa
VZ
3491 if ( !input )
3492 {
3493 return (wxChar *)NULL;
3494 }
3495
68379eaf 3496 haveHour = haveMin = haveSec = true;
f0f951fa
VZ
3497
3498 hour = tm.tm_hour;
3499 min = tm.tm_min;
3500 sec = tm.tm_sec;
3501 }
3502#else // !HAVE_STRPTIME
3503 // TODO under Win32 we can query the LOCALE_ITIME system
3504 // setting which says whether the default time format is
3505 // 24 or 12 hour
3506 {
3507 // try to parse what follows as "%H:%M:%S" and, if this
3508 // fails, as "%I:%M:%S %p" - this should catch the most
3509 // common cases
3510 wxDateTime dt;
3511
3512 const wxChar *result = dt.ParseFormat(input, _T("%T"));
3513 if ( !result )
3514 {
3515 result = dt.ParseFormat(input, _T("%r"));
3516 }
3517
3518 if ( !result )
3519 {
3520 // no match
3521 return (wxChar *)NULL;
3522 }
3523
68379eaf 3524 haveHour = haveMin = haveSec = true;
f0f951fa
VZ
3525
3526 Tm tm = dt.GetTm();
3527 hour = tm.hour;
3528 min = tm.min;
3529 sec = tm.sec;
3530
3531 input = result;
3532 }
3533#endif // HAVE_STRPTIME/!HAVE_STRPTIME
3534 break;
3535
3536 case _T('y'): // year without century (00-99)
f6bcfd97 3537 if ( !GetNumericToken(width, input, &num) || (num > 99) )
f0f951fa
VZ
3538 {
3539 // no match
3540 return (wxChar *)NULL;
3541 }
3542
68379eaf 3543 haveYear = true;
f6bcfd97
BP
3544
3545 // TODO should have an option for roll over date instead of
3546 // hard coding it here
3547 year = (num > 30 ? 1900 : 2000) + (wxDateTime_t)num;
f0f951fa
VZ
3548 break;
3549
3550 case _T('Y'): // year with century
f6bcfd97 3551 if ( !GetNumericToken(width, input, &num) )
f0f951fa
VZ
3552 {
3553 // no match
3554 return (wxChar *)NULL;
3555 }
3556
68379eaf 3557 haveYear = true;
be4017f8 3558 year = (wxDateTime_t)num;
f0f951fa
VZ
3559 break;
3560
3561 case _T('Z'): // timezone name
3562 wxFAIL_MSG(_T("TODO"));
3563 break;
3564
3565 case _T('%'): // a percent sign
3566 if ( *input++ != _T('%') )
3567 {
3568 // no match
3569 return (wxChar *)NULL;
3570 }
3571 break;
3572
3573 case 0: // the end of string
3574 wxFAIL_MSG(_T("unexpected format end"));
3575
3576 // fall through
3577
3578 default: // not a known format spec
3579 return (wxChar *)NULL;
3580 }
3581 }
3582
3583 // format matched, try to construct a date from what we have now
3584 Tm tmDef;
3585 if ( dateDef.IsValid() )
3586 {
3587 // take this date as default
3588 tmDef = dateDef.GetTm();
3589 }
c3302e7e 3590 else if ( IsValid() )
f0f951fa
VZ
3591 {
3592 // if this date is valid, don't change it
3593 tmDef = GetTm();
3594 }
3595 else
3596 {
3597 // no default and this date is invalid - fall back to Today()
3598 tmDef = Today().GetTm();
3599 }
3600
3601 Tm tm = tmDef;
3602
3603 // set the date
3604 if ( haveYear )
3605 {
3606 tm.year = year;
3607 }
3608
3609 // TODO we don't check here that the values are consistent, if both year
3610 // day and month/day were found, we just ignore the year day and we
3611 // also always ignore the week day
3612 if ( haveMon && haveDay )
3613 {
be4017f8
VZ
3614 if ( mday > GetNumOfDaysInMonth(tm.year, mon) )
3615 {
3616 wxLogDebug(_T("bad month day in wxDateTime::ParseFormat"));
3617
3618 return (wxChar *)NULL;
3619 }
3620
f0f951fa
VZ
3621 tm.mon = mon;
3622 tm.mday = mday;
3623 }
3624 else if ( haveYDay )
3625 {
be4017f8
VZ
3626 if ( yday > GetNumberOfDays(tm.year) )
3627 {
3628 wxLogDebug(_T("bad year day in wxDateTime::ParseFormat"));
3629
3630 return (wxChar *)NULL;
3631 }
3632
f0f951fa
VZ
3633 Tm tm2 = wxDateTime(1, Jan, tm.year).SetToYearDay(yday).GetTm();
3634
3635 tm.mon = tm2.mon;
3636 tm.mday = tm2.mday;
3637 }
3638
3639 // deal with AM/PM
3640 if ( haveHour && hourIsIn12hFormat && isPM )
3641 {
3642 // translate to 24hour format
3643 hour += 12;
3644 }
3645 //else: either already in 24h format or no translation needed
3646
3647 // set the time
3648 if ( haveHour )
3649 {
5f287370 3650 tm.hour = hour;
f0f951fa
VZ
3651 }
3652
3653 if ( haveMin )
3654 {
3655 tm.min = min;
3656 }
3657
3658 if ( haveSec )
3659 {
3660 tm.sec = sec;
3661 }
3662
3663 Set(tm);
3664
88a036ce
VZ
3665 // finally check that the week day is consistent -- if we had it
3666 if ( haveWDay && GetWeekDay() != wday )
3667 {
3668 wxLogDebug(_T("inconsistsnet week day in wxDateTime::ParseFormat()"));
3669
3670 return NULL;
3671 }
3672
f0f951fa 3673 return input;
cd0b1709
VZ
3674}
3675
3676const wxChar *wxDateTime::ParseDateTime(const wxChar *date)
3677{
3678 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
3679
de07d200
VZ
3680 // Set to current day and hour, so strings like '14:00' becomes today at
3681 // 14, not some other random date
3682 wxDateTime dtDate = wxDateTime::Today();
3683 wxDateTime dtTime = wxDateTime::Today();
cd0b1709 3684
de07d200
VZ
3685 const wxChar* pchTime;
3686
3687 // Try to parse the beginning of the string as a date
3688 const wxChar* pchDate = dtDate.ParseDate(date);
3689
3690 // We got a date in the beginning, see if there is a time specified after the date
3691 if ( pchDate )
3692 {
3693 // Skip spaces, as the ParseTime() function fails on spaces
3694 while ( wxIsspace(*pchDate) )
3695 pchDate++;
3696
3697 pchTime = dtTime.ParseTime(pchDate);
3698 }
3699 else // no date in the beginning
3700 {
3701 // check and see if we have a time followed by a date
3702 pchTime = dtTime.ParseTime(date);
3703 if ( pchTime )
3704 {
3705 while ( wxIsspace(*pchTime) )
3706 pchTime++;
3707
3708 pchDate = dtDate.ParseDate(pchTime);
3709 }
3710 }
3711
3712 // If we have a date specified, set our own data to the same date
3713 if ( !pchDate || !pchTime )
3714 return NULL;
3715
3716 Set(dtDate.GetDay(), dtDate.GetMonth(), dtDate.GetYear(),
3717 dtTime.GetHour(), dtTime.GetMinute(), dtTime.GetSecond(),
3718 dtTime.GetMillisecond());
3719
3720 // Return endpoint of scan
3721 return pchDate > pchTime ? pchDate : pchTime;
cd0b1709
VZ
3722}
3723
3724const wxChar *wxDateTime::ParseDate(const wxChar *date)
3725{
3726 // this is a simplified version of ParseDateTime() which understands only
3727 // "today" (for wxDate compatibility) and digits only otherwise (and not
3728 // all esoteric constructions ParseDateTime() knows about)
3729
3730 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
3731
3732 const wxChar *p = date;
3733 while ( wxIsspace(*p) )
3734 p++;
3735
fd9f9f4c
VZ
3736 // some special cases
3737 static struct
cd0b1709 3738 {
fd9f9f4c
VZ
3739 const wxChar *str;
3740 int dayDiffFromToday;
3741 } literalDates[] =
3742 {
3743 { wxTRANSLATE("today"), 0 },
3744 { wxTRANSLATE("yesterday"), -1 },
3745 { wxTRANSLATE("tomorrow"), 1 },
3746 };
3747
3748 for ( size_t n = 0; n < WXSIZEOF(literalDates); n++ )
3749 {
7a7bd38a
VZ
3750 const wxString dateStr = wxGetTranslation(literalDates[n].str);
3751 size_t len = dateStr.length();
907173e5 3752 if ( wxStrlen(p) >= len )
fd9f9f4c 3753 {
907173e5 3754 wxString str(p, len);
7a7bd38a 3755 if ( str.CmpNoCase(dateStr) == 0 )
fd9f9f4c 3756 {
907173e5
WS
3757 // nothing can follow this, so stop here
3758 p += len;
3759
3760 int dayDiffFromToday = literalDates[n].dayDiffFromToday;
3761 *this = Today();
3762 if ( dayDiffFromToday )
3763 {
3764 *this += wxDateSpan::Days(dayDiffFromToday);
3765 }
cd0b1709 3766
907173e5
WS
3767 return p;
3768 }
fd9f9f4c 3769 }
cd0b1709
VZ
3770 }
3771
3ca6a5f0
BP
3772 // We try to guess what we have here: for each new (numeric) token, we
3773 // determine if it can be a month, day or a year. Of course, there is an
3774 // ambiguity as some numbers may be days as well as months, so we also
3775 // have the ability to back track.
3776
cd0b1709 3777 // what do we have?
68379eaf
WS
3778 bool haveDay = false, // the months day?
3779 haveWDay = false, // the day of week?
3780 haveMon = false, // the month?
3781 haveYear = false; // the year?
cd0b1709
VZ
3782
3783 // and the value of the items we have (init them to get rid of warnings)
3784 WeekDay wday = Inv_WeekDay;
3785 wxDateTime_t day = 0;
3786 wxDateTime::Month mon = Inv_Month;
3787 int year = 0;
3788
3789 // tokenize the string
f6bcfd97 3790 size_t nPosCur = 0;
62ae2780 3791 static const wxChar *dateDelimiters = _T(".,/-\t\r\n ");
f6bcfd97 3792 wxStringTokenizer tok(p, dateDelimiters);
cd0b1709
VZ
3793 while ( tok.HasMoreTokens() )
3794 {
3795 wxString token = tok.GetNextToken();
3ca6a5f0
BP
3796 if ( !token )
3797 continue;
cd0b1709
VZ
3798
3799 // is it a number?
3800 unsigned long val;
fde5a86b 3801 if ( token.ToULong(&val) )
cd0b1709
VZ
3802 {
3803 // guess what this number is
3804
68379eaf
WS
3805 bool isDay = false,
3806 isMonth = false,
3807 isYear = false;
3ca6a5f0
BP
3808
3809 if ( !haveMon && val > 0 && val <= 12 )
cd0b1709 3810 {
3ca6a5f0 3811 // assume it is month
68379eaf 3812 isMonth = true;
3ca6a5f0
BP
3813 }
3814 else // not the month
3815 {
560e2916 3816 if ( haveDay )
cd0b1709 3817 {
560e2916 3818 // this can only be the year
68379eaf 3819 isYear = true;
cd0b1709 3820 }
560e2916 3821 else // may be either day or year
cd0b1709 3822 {
eca0692c
VZ
3823 // use a leap year if we don't have the year yet to allow
3824 // dates like 2/29/1976 which would be rejected otherwise
6a27c749 3825 wxDateTime_t max_days = (wxDateTime_t)(
42841dfc 3826 haveMon
eca0692c 3827 ? GetNumOfDaysInMonth(haveYear ? year : 1976, mon)
42841dfc
WS
3828 : 31
3829 );
560e2916
VZ
3830
3831 // can it be day?
6a27c749 3832 if ( (val == 0) || (val > (unsigned long)max_days) )
560e2916
VZ
3833 {
3834 // no
68379eaf 3835 isYear = true;
560e2916
VZ
3836 }
3837 else // yes, suppose it's the day
3838 {
68379eaf 3839 isDay = true;
560e2916 3840 }
cd0b1709
VZ
3841 }
3842 }
3843
cd0b1709
VZ
3844 if ( isYear )
3845 {
3846 if ( haveYear )
cd0b1709 3847 break;
cd0b1709 3848
68379eaf 3849 haveYear = true;
cd0b1709 3850
479cd5de 3851 year = (wxDateTime_t)val;
cd0b1709 3852 }
3ca6a5f0 3853 else if ( isDay )
cd0b1709 3854 {
3ca6a5f0 3855 if ( haveDay )
cd0b1709 3856 break;
cd0b1709 3857
68379eaf 3858 haveDay = true;
cd0b1709 3859
3ca6a5f0 3860 day = (wxDateTime_t)val;
cd0b1709 3861 }
3ca6a5f0 3862 else if ( isMonth )
cd0b1709 3863 {
68379eaf 3864 haveMon = true;
cd0b1709 3865
3ca6a5f0 3866 mon = (Month)(val - 1);
cd0b1709
VZ
3867 }
3868 }
3869 else // not a number
3870 {
f6bcfd97
BP
3871 // be careful not to overwrite the current mon value
3872 Month mon2 = GetMonthFromName(token, Name_Full | Name_Abbr);
3873 if ( mon2 != Inv_Month )
cd0b1709
VZ
3874 {
3875 // it's a month
3876 if ( haveMon )
3877 {
6411a32c
VZ
3878 // but we already have a month - maybe we guessed wrong?
3879 if ( !haveDay )
3880 {
f5cd9787 3881 // no need to check in month range as always < 12, but
6411a32c 3882 // the days are counted from 1 unlike the months
42841dfc 3883 day = (wxDateTime_t)(mon + 1);
68379eaf 3884 haveDay = true;
6411a32c
VZ
3885 }
3886 else
3887 {
3888 // could possible be the year (doesn't the year come
3889 // before the month in the japanese format?) (FIXME)
3890 break;
f5cd9787 3891 }
cd0b1709
VZ
3892 }
3893
f6bcfd97
BP
3894 mon = mon2;
3895
68379eaf 3896 haveMon = true;
cd0b1709 3897 }
6411a32c 3898 else // not a valid month name
cd0b1709 3899 {
f0f951fa 3900 wday = GetWeekDayFromName(token, Name_Full | Name_Abbr);
cd0b1709
VZ
3901 if ( wday != Inv_WeekDay )
3902 {
3903 // a week day
3904 if ( haveWDay )
3905 {
3906 break;
3907 }
3908
68379eaf 3909 haveWDay = true;
cd0b1709 3910 }
6411a32c 3911 else // not a valid weekday name
cd0b1709
VZ
3912 {
3913 // try the ordinals
3914 static const wxChar *ordinals[] =
3915 {
3916 wxTRANSLATE("first"),
3917 wxTRANSLATE("second"),
3918 wxTRANSLATE("third"),
3919 wxTRANSLATE("fourth"),
3920 wxTRANSLATE("fifth"),
3921 wxTRANSLATE("sixth"),
3922 wxTRANSLATE("seventh"),
3923 wxTRANSLATE("eighth"),
3924 wxTRANSLATE("ninth"),
3925 wxTRANSLATE("tenth"),
3926 wxTRANSLATE("eleventh"),
3927 wxTRANSLATE("twelfth"),
3928 wxTRANSLATE("thirteenth"),
3929 wxTRANSLATE("fourteenth"),
3930 wxTRANSLATE("fifteenth"),
3931 wxTRANSLATE("sixteenth"),
3932 wxTRANSLATE("seventeenth"),
3933 wxTRANSLATE("eighteenth"),
3934 wxTRANSLATE("nineteenth"),
3935 wxTRANSLATE("twentieth"),
3936 // that's enough - otherwise we'd have problems with
6411a32c 3937 // composite (or not) ordinals
cd0b1709
VZ
3938 };
3939
3940 size_t n;
3941 for ( n = 0; n < WXSIZEOF(ordinals); n++ )
3942 {
3943 if ( token.CmpNoCase(ordinals[n]) == 0 )
3944 {
3945 break;
3946 }
3947 }
3948
3949 if ( n == WXSIZEOF(ordinals) )
3950 {
3951 // stop here - something unknown
3952 break;
3953 }
3954
3955 // it's a day
3956 if ( haveDay )
3957 {
3958 // don't try anything here (as in case of numeric day
3959 // above) - the symbolic day spec should always
3960 // precede the month/year
3961 break;
3962 }
3963
68379eaf 3964 haveDay = true;
cd0b1709 3965
33ac7e6f 3966 day = (wxDateTime_t)(n + 1);
cd0b1709
VZ
3967 }
3968 }
3969 }
f6bcfd97
BP
3970
3971 nPosCur = tok.GetPosition();
cd0b1709
VZ
3972 }
3973
3974 // either no more tokens or the scan was stopped by something we couldn't
3975 // parse - in any case, see if we can construct a date from what we have
3976 if ( !haveDay && !haveWDay )
3977 {
f6bcfd97 3978 wxLogDebug(_T("ParseDate: no day, no weekday hence no date."));
cd0b1709 3979
eca0692c 3980 return NULL;
cd0b1709
VZ
3981 }
3982
3983 if ( haveWDay && (haveMon || haveYear || haveDay) &&
f6bcfd97 3984 !(haveDay && haveMon && haveYear) )
cd0b1709
VZ
3985 {
3986 // without adjectives (which we don't support here) the week day only
3987 // makes sense completely separately or with the full date
3988 // specification (what would "Wed 1999" mean?)
eca0692c 3989 return NULL;
cd0b1709
VZ
3990 }
3991
f6bcfd97
BP
3992 if ( !haveWDay && haveYear && !(haveDay && haveMon) )
3993 {
3994 // may be we have month and day instead of day and year?
3995 if ( haveDay && !haveMon )
3996 {
3997 if ( day <= 12 )
3998 {
3999 // exchange day and month
4000 mon = (wxDateTime::Month)(day - 1);
4001
4002 // we're in the current year then
196be0f1 4003 if ( (year > 0) && (year <= (int)GetNumOfDaysInMonth(Inv_Year, mon)) )
f6bcfd97 4004 {
42841dfc 4005 day = (wxDateTime_t)year;
f6bcfd97 4006
68379eaf
WS
4007 haveMon = true;
4008 haveYear = false;
f6bcfd97 4009 }
68379eaf 4010 //else: no, can't exchange, leave haveMon == false
f6bcfd97
BP
4011 }
4012 }
4013
4014 if ( !haveMon )
4015 {
4016 // if we give the year, month and day must be given too
4017 wxLogDebug(_T("ParseDate: day and month should be specified if year is."));
4018
eca0692c 4019 return NULL;
f6bcfd97
BP
4020 }
4021 }
4022
cd0b1709
VZ
4023 if ( !haveMon )
4024 {
4025 mon = GetCurrentMonth();
4026 }
4027
4028 if ( !haveYear )
4029 {
4030 year = GetCurrentYear();
4031 }
4032
4033 if ( haveDay )
4034 {
eca0692c
VZ
4035 // normally we check the day above but the check is optimistic in case
4036 // we find the day before its month/year so we have to redo it now
4037 if ( day > GetNumOfDaysInMonth(year, mon) )
4038 return NULL;
4039
cd0b1709
VZ
4040 Set(day, mon, year);
4041
4042 if ( haveWDay )
4043 {
4044 // check that it is really the same
4045 if ( GetWeekDay() != wday )
4046 {
4047 // inconsistency detected
f6bcfd97
BP
4048 wxLogDebug(_T("ParseDate: inconsistent day/weekday."));
4049
cd0b1709
VZ
4050 return (wxChar *)NULL;
4051 }
4052 }
4053 }
4054 else // haveWDay
4055 {
4056 *this = Today();
4057
4058 SetToWeekDayInSameWeek(wday);
4059 }
4060
f6bcfd97
BP
4061 // return the pointer to the first unparsed char
4062 p += nPosCur;
4063 if ( nPosCur && wxStrchr(dateDelimiters, *(p - 1)) )
4064 {
4065 // if we couldn't parse the token after the delimiter, put back the
4066 // delimiter as well
4067 p--;
4068 }
4069
4070 return p;
cd0b1709
VZ
4071}
4072
4073const wxChar *wxDateTime::ParseTime(const wxChar *time)
4074{
4075 wxCHECK_MSG( time, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
4076
f0f951fa
VZ
4077 // first try some extra things
4078 static const struct
4079 {
4080 const wxChar *name;
4081 wxDateTime_t hour;
4082 } stdTimes[] =
4083 {
4084 { wxTRANSLATE("noon"), 12 },
4085 { wxTRANSLATE("midnight"), 00 },
4086 // anything else?
4087 };
cd0b1709 4088
f0f951fa
VZ
4089 for ( size_t n = 0; n < WXSIZEOF(stdTimes); n++ )
4090 {
4091 wxString timeString = wxGetTranslation(stdTimes[n].name);
4092 size_t len = timeString.length();
4093 if ( timeString.CmpNoCase(wxString(time, len)) == 0 )
4094 {
4ded51f2
CE
4095 // casts required by DigitalMars
4096 Set(stdTimes[n].hour, wxDateTime_t(0), wxDateTime_t(0));
f0f951fa
VZ
4097
4098 return time + len;
4099 }
4100 }
4101
c7f9a482
VZ
4102 // try all time formats we may think about in the order from longest to
4103 // shortest
4104
4105 // 12hour with AM/PM?
4106 const wxChar *result = ParseFormat(time, _T("%I:%M:%S %p"));
4107
f0f951fa
VZ
4108 if ( !result )
4109 {
4110 // normally, it's the same, but why not try it?
4111 result = ParseFormat(time, _T("%H:%M:%S"));
4112 }
4113
4114 if ( !result )
4115 {
c7f9a482
VZ
4116 // 12hour with AM/PM but without seconds?
4117 result = ParseFormat(time, _T("%I:%M %p"));
f0f951fa
VZ
4118 }
4119
4120 if ( !result )
4121 {
4122 // without seconds?
4123 result = ParseFormat(time, _T("%H:%M"));
4124 }
4125
4126 if ( !result )
4127 {
c7f9a482
VZ
4128 // just the hour and AM/PM?
4129 result = ParseFormat(time, _T("%I %p"));
f0f951fa
VZ
4130 }
4131
4132 if ( !result )
4133 {
4134 // just the hour?
4135 result = ParseFormat(time, _T("%H"));
4136 }
4137
4138 if ( !result )
4139 {
c7f9a482
VZ
4140 // parse the standard format: normally it is one of the formats above
4141 // but it may be set to something completely different by the user
4142 result = ParseFormat(time, _T("%X"));
f0f951fa
VZ
4143 }
4144
4145 // TODO: parse timezones
4146
4147 return result;
cd0b1709
VZ
4148}
4149
4f6aed9c
VZ
4150// ----------------------------------------------------------------------------
4151// Workdays and holidays support
4152// ----------------------------------------------------------------------------
4153
4154bool wxDateTime::IsWorkDay(Country WXUNUSED(country)) const
4155{
4156 return !wxDateTimeHolidayAuthority::IsHoliday(*this);
4157}
4158
26364344
WS
4159// ============================================================================
4160// wxDateSpan
4161// ============================================================================
4162
4163wxDateSpan WXDLLIMPEXP_BASE operator*(int n, const wxDateSpan& ds)
4164{
4165 wxDateSpan ds1(ds);
4166 return ds1.Multiply(n);
4167}
4168
fcc3d7cb
VZ
4169// ============================================================================
4170// wxTimeSpan
4171// ============================================================================
4172
26364344
WS
4173wxTimeSpan WXDLLIMPEXP_BASE operator*(int n, const wxTimeSpan& ts)
4174{
4175 return wxTimeSpan(ts).Multiply(n);
4176}
4177
033400eb
VZ
4178// this enum is only used in wxTimeSpan::Format() below but we can't declare
4179// it locally to the method as it provokes an internal compiler error in egcs
4180// 2.91.60 when building with -O2
4181enum TimeSpanPart
4182{
4183 Part_Week,
4184 Part_Day,
4185 Part_Hour,
4186 Part_Min,
4187 Part_Sec,
4188 Part_MSec
4189};
4190
e6ec579c
VZ
4191// not all strftime(3) format specifiers make sense here because, for example,
4192// a time span doesn't have a year nor a timezone
4193//
4194// Here are the ones which are supported (all of them are supported by strftime
4195// as well):
4196// %H hour in 24 hour format
4197// %M minute (00 - 59)
4198// %S second (00 - 59)
4199// %% percent sign
4200//
4201// Also, for MFC CTimeSpan compatibility, we support
4202// %D number of days
4203//
4204// And, to be better than MFC :-), we also have
4205// %E number of wEeks
4206// %l milliseconds (000 - 999)
fcc3d7cb
VZ
4207wxString wxTimeSpan::Format(const wxChar *format) const
4208{
525d8583 4209 wxCHECK_MSG( format, wxEmptyString, _T("NULL format in wxTimeSpan::Format") );
fcc3d7cb
VZ
4210
4211 wxString str;
5b735202 4212 str.Alloc(wxStrlen(format));
e6ec579c 4213
df05cdc5
VZ
4214 // Suppose we have wxTimeSpan ts(1 /* hour */, 2 /* min */, 3 /* sec */)
4215 //
4216 // Then, of course, ts.Format("%H:%M:%S") must return "01:02:03", but the
4217 // question is what should ts.Format("%S") do? The code here returns "3273"
4218 // in this case (i.e. the total number of seconds, not just seconds % 60)
4219 // because, for me, this call means "give me entire time interval in
4220 // seconds" and not "give me the seconds part of the time interval"
4221 //
4222 // If we agree that it should behave like this, it is clear that the
4223 // interpretation of each format specifier depends on the presence of the
4224 // other format specs in the string: if there was "%H" before "%M", we
4225 // should use GetMinutes() % 60, otherwise just GetMinutes() &c
4226
4227 // we remember the most important unit found so far
033400eb 4228 TimeSpanPart partBiggest = Part_MSec;
df05cdc5 4229
f6bcfd97 4230 for ( const wxChar *pch = format; *pch; pch++ )
e6ec579c
VZ
4231 {
4232 wxChar ch = *pch;
4233
f6bcfd97 4234 if ( ch == _T('%') )
e6ec579c 4235 {
df05cdc5 4236 // the start of the format specification of the printf() below
e566ccf1 4237 wxString fmtPrefix(_T('%'));
df05cdc5
VZ
4238
4239 // the number
4240 long n;
e6ec579c 4241
e566ccf1
VZ
4242 // the number of digits for the format string, 0 if unused
4243 unsigned digits = 0;
4244
f6bcfd97 4245 ch = *++pch; // get the format spec char
e6ec579c
VZ
4246 switch ( ch )
4247 {
4248 default:
4249 wxFAIL_MSG( _T("invalid format character") );
4250 // fall through
4251
f6bcfd97 4252 case _T('%'):
df05cdc5
VZ
4253 str += ch;
4254
4255 // skip the part below switch
4256 continue;
e6ec579c 4257
f6bcfd97 4258 case _T('D'):
df05cdc5
VZ
4259 n = GetDays();
4260 if ( partBiggest < Part_Day )
4261 {
4262 n %= DAYS_PER_WEEK;
4263 }
4264 else
4265 {
4266 partBiggest = Part_Day;
4267 }
e6ec579c
VZ
4268 break;
4269
f6bcfd97 4270 case _T('E'):
df05cdc5
VZ
4271 partBiggest = Part_Week;
4272 n = GetWeeks();
e6ec579c
VZ
4273 break;
4274
f6bcfd97 4275 case _T('H'):
df05cdc5
VZ
4276 n = GetHours();
4277 if ( partBiggest < Part_Hour )
4278 {
e566ccf1
VZ
4279 if ( n < 0 )
4280 {
4281 // the sign has already been taken into account
4282 // when outputting the biggest part
4283 n = -n;
4284 }
4285
df05cdc5
VZ
4286 n %= HOURS_PER_DAY;
4287 }
4288 else
4289 {
4290 partBiggest = Part_Hour;
4291 }
4292
e566ccf1 4293 digits = 2;
e6ec579c
VZ
4294 break;
4295
f6bcfd97 4296 case _T('l'):
df05cdc5
VZ
4297 n = GetMilliseconds().ToLong();
4298 if ( partBiggest < Part_MSec )
4299 {
e566ccf1
VZ
4300 if ( n < 0 )
4301 n = -n;
4302
df05cdc5
VZ
4303 n %= 1000;
4304 }
4305 //else: no need to reset partBiggest to Part_MSec, it is
4306 // the least significant one anyhow
4307
e566ccf1 4308 digits = 3;
e6ec579c
VZ
4309 break;
4310
f6bcfd97 4311 case _T('M'):
df05cdc5
VZ
4312 n = GetMinutes();
4313 if ( partBiggest < Part_Min )
4314 {
e566ccf1
VZ
4315 if ( n < 0 )
4316 n = -n;
4317
df05cdc5
VZ
4318 n %= MIN_PER_HOUR;
4319 }
4320 else
4321 {
4322 partBiggest = Part_Min;
4323 }
4324
e566ccf1 4325 digits = 2;
e6ec579c
VZ
4326 break;
4327
f6bcfd97 4328 case _T('S'):
df05cdc5
VZ
4329 n = GetSeconds().ToLong();
4330 if ( partBiggest < Part_Sec )
4331 {
e566ccf1
VZ
4332 if ( n < 0 )
4333 n = -n;
4334
df05cdc5
VZ
4335 n %= SEC_PER_MIN;
4336 }
4337 else
4338 {
4339 partBiggest = Part_Sec;
4340 }
4341
e566ccf1 4342 digits = 2;
e6ec579c
VZ
4343 break;
4344 }
4345
e566ccf1
VZ
4346 if ( digits )
4347 {
4348 // negative numbers need one extra position for '-' display
4349 if ( n < 0 )
4350 digits++;
4351
4352 fmtPrefix << _T("0") << digits;
4353 }
4354
df05cdc5
VZ
4355 str += wxString::Format(fmtPrefix + _T("ld"), n);
4356 }
4357 else
4358 {
4359 // normal character, just copy
4360 str += ch;
e6ec579c 4361 }
e6ec579c 4362 }
fcc3d7cb
VZ
4363
4364 return str;
4365}
4f6aed9c
VZ
4366
4367// ============================================================================
4368// wxDateTimeHolidayAuthority and related classes
4369// ============================================================================
4370
4371#include "wx/arrimpl.cpp"
4372
4115960d 4373WX_DEFINE_OBJARRAY(wxDateTimeArray)
4f6aed9c
VZ
4374
4375static int wxCMPFUNC_CONV
4376wxDateTimeCompareFunc(wxDateTime **first, wxDateTime **second)
4377{
4378 wxDateTime dt1 = **first,
4379 dt2 = **second;
4380
4381 return dt1 == dt2 ? 0 : dt1 < dt2 ? -1 : +1;
4382}
4383
4384// ----------------------------------------------------------------------------
4385// wxDateTimeHolidayAuthority
4386// ----------------------------------------------------------------------------
4387
4388wxHolidayAuthoritiesArray wxDateTimeHolidayAuthority::ms_authorities;
4389
4390/* static */
4391bool wxDateTimeHolidayAuthority::IsHoliday(const wxDateTime& dt)
4392{
df5168c4 4393 size_t count = ms_authorities.size();
4f6aed9c
VZ
4394 for ( size_t n = 0; n < count; n++ )
4395 {
4396 if ( ms_authorities[n]->DoIsHoliday(dt) )
4397 {
68379eaf 4398 return true;
4f6aed9c
VZ
4399 }
4400 }
4401
68379eaf 4402 return false;
4f6aed9c
VZ
4403}
4404
4405/* static */
4406size_t
4407wxDateTimeHolidayAuthority::GetHolidaysInRange(const wxDateTime& dtStart,
4408 const wxDateTime& dtEnd,
4409 wxDateTimeArray& holidays)
4410{
4411 wxDateTimeArray hol;
4412
df5168c4 4413 holidays.Clear();
4f6aed9c 4414
7a7bd38a
VZ
4415 const size_t countAuth = ms_authorities.size();
4416 for ( size_t nAuth = 0; nAuth < countAuth; nAuth++ )
4f6aed9c 4417 {
6dc6fda6 4418 ms_authorities[nAuth]->DoGetHolidaysInRange(dtStart, dtEnd, hol);
4f6aed9c
VZ
4419
4420 WX_APPEND_ARRAY(holidays, hol);
4421 }
4422
4423 holidays.Sort(wxDateTimeCompareFunc);
4424
df5168c4 4425 return holidays.size();
4f6aed9c
VZ
4426}
4427
4428/* static */
4429void wxDateTimeHolidayAuthority::ClearAllAuthorities()
4430{
4431 WX_CLEAR_ARRAY(ms_authorities);
4432}
4433
4434/* static */
4435void wxDateTimeHolidayAuthority::AddAuthority(wxDateTimeHolidayAuthority *auth)
4436{
df5168c4 4437 ms_authorities.push_back(auth);
4f6aed9c
VZ
4438}
4439
5e233068
WS
4440wxDateTimeHolidayAuthority::~wxDateTimeHolidayAuthority()
4441{
4442 // required here for Darwin
4443}
4444
4f6aed9c
VZ
4445// ----------------------------------------------------------------------------
4446// wxDateTimeWorkDays
4447// ----------------------------------------------------------------------------
4448
4449bool wxDateTimeWorkDays::DoIsHoliday(const wxDateTime& dt) const
4450{
4451 wxDateTime::WeekDay wd = dt.GetWeekDay();
4452
4453 return (wd == wxDateTime::Sun) || (wd == wxDateTime::Sat);
4454}
4455
4456size_t wxDateTimeWorkDays::DoGetHolidaysInRange(const wxDateTime& dtStart,
4457 const wxDateTime& dtEnd,
4458 wxDateTimeArray& holidays) const
4459{
4460 if ( dtStart > dtEnd )
4461 {
4462 wxFAIL_MSG( _T("invalid date range in GetHolidaysInRange") );
4463
4464 return 0u;
4465 }
4466
4467 holidays.Empty();
4468
4469 // instead of checking all days, start with the first Sat after dtStart and
4470 // end with the last Sun before dtEnd
4471 wxDateTime dtSatFirst = dtStart.GetNextWeekDay(wxDateTime::Sat),
4472 dtSatLast = dtEnd.GetPrevWeekDay(wxDateTime::Sat),
4473 dtSunFirst = dtStart.GetNextWeekDay(wxDateTime::Sun),
4474 dtSunLast = dtEnd.GetPrevWeekDay(wxDateTime::Sun),
4475 dt;
4476
4477 for ( dt = dtSatFirst; dt <= dtSatLast; dt += wxDateSpan::Week() )
4478 {
4479 holidays.Add(dt);
4480 }
4481
4482 for ( dt = dtSunFirst; dt <= dtSunLast; dt += wxDateSpan::Week() )
4483 {
4484 holidays.Add(dt);
4485 }
4486
4487 return holidays.GetCount();
4488}
3e0b743f 4489
26364344
WS
4490// ============================================================================
4491// other helper functions
4492// ============================================================================
4493
4494// ----------------------------------------------------------------------------
4495// iteration helpers: can be used to write a for loop over enum variable like
4496// this:
4497// for ( m = wxDateTime::Jan; m < wxDateTime::Inv_Month; wxNextMonth(m) )
4498// ----------------------------------------------------------------------------
4499
4500WXDLLIMPEXP_BASE void wxNextMonth(wxDateTime::Month& m)
4501{
4502 wxASSERT_MSG( m < wxDateTime::Inv_Month, _T("invalid month") );
4503
4504 // no wrapping or the for loop above would never end!
4505 m = (wxDateTime::Month)(m + 1);
4506}
4507
4508WXDLLIMPEXP_BASE void wxPrevMonth(wxDateTime::Month& m)
4509{
4510 wxASSERT_MSG( m < wxDateTime::Inv_Month, _T("invalid month") );
4511
4512 m = m == wxDateTime::Jan ? wxDateTime::Inv_Month
4513 : (wxDateTime::Month)(m - 1);
4514}
4515
4516WXDLLIMPEXP_BASE void wxNextWDay(wxDateTime::WeekDay& wd)
4517{
4518 wxASSERT_MSG( wd < wxDateTime::Inv_WeekDay, _T("invalid week day") );
4519
4520 // no wrapping or the for loop above would never end!
4521 wd = (wxDateTime::WeekDay)(wd + 1);
4522}
4523
4524WXDLLIMPEXP_BASE void wxPrevWDay(wxDateTime::WeekDay& wd)
4525{
4526 wxASSERT_MSG( wd < wxDateTime::Inv_WeekDay, _T("invalid week day") );
4527
4528 wd = wd == wxDateTime::Sun ? wxDateTime::Inv_WeekDay
4529 : (wxDateTime::WeekDay)(wd - 1);
4530}
4531
1e6feb95 4532#endif // wxUSE_DATETIME