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