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