]> git.saurik.com Git - wxWidgets.git/blame - src/common/datetime.cpp
made wxTextFile work in Unicode; also made it possible to use it with non seekable...
[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{
51485b76 330#ifdef WX_GMTOFF_IN_TM
68379eaf
WS
331 // set to true when the timezone is set
332 static bool s_timezoneSet = false;
0779cc9a 333 static long gmtoffset = LONG_MAX; // invalid timezone
c5432233 334
a452689b 335 // ensure that the timezone variable is set by calling wxLocaltime_r
fcc3d7cb
VZ
336 if ( !s_timezoneSet )
337 {
a452689b
SN
338 // just call wxLocaltime_r() instead of figuring out whether this
339 // system supports tzset(), _tzset() or something else
c5432233 340 time_t t = 0;
0779cc9a 341 struct tm *tm;
a452689b 342 struct tm tmstruct;
fcc3d7cb 343
a452689b 344 tm = wxLocaltime_r(&t, &tmstruct);
68379eaf 345 s_timezoneSet = true;
c5432233 346
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;
fcc3d7cb
VZ
351 }
352
0779cc9a 353 return (int)gmtoffset;
51485b76 354#else // !WX_GMTOFF_IN_TM
6de20863 355 return (int)WX_TIMEZONE;
51485b76 356#endif // WX_GMTOFF_IN_TM/!WX_GMTOFF_IN_TM
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
4c27e2fa
VZ
1814// use a separate function to avoid warnings about using deprecated
1815// SetToTheWeek in GetWeek below
1816static wxDateTime
1817SetToTheWeek(int year,
1818 wxDateTime::wxDateTime_t numWeek,
1819 wxDateTime::WeekDay weekday,
1820 wxDateTime::WeekFlags flags)
1821{
4f6aed9c 1822 // Jan 4 always lies in the 1st week of the year
4c27e2fa
VZ
1823 wxDateTime dt(4, wxDateTime::Jan, year);
1824 dt.SetToWeekDayInSameWeek(weekday, flags);
1825 dt += wxDateSpan::Weeks(numWeek - 1);
4f6aed9c 1826
4c27e2fa
VZ
1827 return dt;
1828}
1829
1830bool wxDateTime::SetToTheWeek(wxDateTime_t numWeek,
1831 WeekDay weekday,
1832 WeekFlags flags)
1833{
1834 int year = GetYear();
1835 *this = ::SetToTheWeek(year, numWeek, weekday, flags);
4f6aed9c
VZ
1836 if ( GetYear() != year )
1837 {
1838 // oops... numWeek was too big
68379eaf 1839 return false;
4f6aed9c
VZ
1840 }
1841
68379eaf 1842 return true;
4f6aed9c
VZ
1843}
1844
4c27e2fa
VZ
1845wxDateTime wxDateTime::GetWeek(wxDateTime_t numWeek,
1846 WeekDay weekday,
1847 WeekFlags flags) const
1848{
1849 return ::SetToTheWeek(GetYear(), numWeek, weekday, flags);
1850}
1851
2f02cb89
VZ
1852wxDateTime& wxDateTime::SetToLastMonthDay(Month month,
1853 int year)
1854{
1855 // take the current month/year if none specified
1a8557b1
VZ
1856 if ( year == Inv_Year )
1857 year = GetYear();
1858 if ( month == Inv_Month )
1859 month = GetMonth();
2f02cb89 1860
fcc3d7cb 1861 return Set(GetNumOfDaysInMonth(year, month), month, year);
2f02cb89
VZ
1862}
1863
2b5f62a0 1864wxDateTime& wxDateTime::SetToWeekDayInSameWeek(WeekDay weekday, WeekFlags flags)
cd0b1709 1865{
384223b3 1866 wxDATETIME_CHECK( weekday != Inv_WeekDay, _T("invalid weekday") );
cd0b1709 1867
af80bc92
VZ
1868 int wdayDst = weekday,
1869 wdayThis = GetWeekDay();
1870 if ( wdayDst == wdayThis )
cd0b1709
VZ
1871 {
1872 // nothing to do
1873 return *this;
1874 }
2b5f62a0
VZ
1875
1876 if ( flags == Default_First )
1877 {
1878 flags = GetCountry() == USA ? Sunday_First : Monday_First;
1879 }
1880
1881 // the logic below based on comparing weekday and wdayThis works if Sun (0)
1882 // is the first day in the week, but breaks down for Monday_First case so
1883 // we adjust the week days in this case
af80bc92 1884 if ( flags == Monday_First )
2b5f62a0
VZ
1885 {
1886 if ( wdayThis == Sun )
1887 wdayThis += 7;
af80bc92
VZ
1888 if ( wdayDst == Sun )
1889 wdayDst += 7;
2b5f62a0
VZ
1890 }
1891 //else: Sunday_First, nothing to do
1892
1893 // go forward or back in time to the day we want
af80bc92 1894 if ( wdayDst < wdayThis )
cd0b1709 1895 {
af80bc92 1896 return Subtract(wxDateSpan::Days(wdayThis - wdayDst));
cd0b1709
VZ
1897 }
1898 else // weekday > wdayThis
1899 {
af80bc92 1900 return Add(wxDateSpan::Days(wdayDst - wdayThis));
cd0b1709
VZ
1901 }
1902}
1903
1904wxDateTime& wxDateTime::SetToNextWeekDay(WeekDay weekday)
1905{
384223b3 1906 wxDATETIME_CHECK( weekday != Inv_WeekDay, _T("invalid weekday") );
cd0b1709
VZ
1907
1908 int diff;
1909 WeekDay wdayThis = GetWeekDay();
1910 if ( weekday == wdayThis )
1911 {
1912 // nothing to do
1913 return *this;
1914 }
1915 else if ( weekday < wdayThis )
1916 {
1917 // need to advance a week
1918 diff = 7 - (wdayThis - weekday);
1919 }
1920 else // weekday > wdayThis
1921 {
1922 diff = weekday - wdayThis;
1923 }
1924
4f6aed9c 1925 return Add(wxDateSpan::Days(diff));
cd0b1709
VZ
1926}
1927
1928wxDateTime& wxDateTime::SetToPrevWeekDay(WeekDay weekday)
1929{
384223b3 1930 wxDATETIME_CHECK( weekday != Inv_WeekDay, _T("invalid weekday") );
cd0b1709
VZ
1931
1932 int diff;
1933 WeekDay wdayThis = GetWeekDay();
1934 if ( weekday == wdayThis )
1935 {
1936 // nothing to do
1937 return *this;
1938 }
1939 else if ( weekday > wdayThis )
1940 {
1941 // need to go to previous week
1942 diff = 7 - (weekday - wdayThis);
1943 }
1944 else // weekday < wdayThis
1945 {
1946 diff = wdayThis - weekday;
1947 }
1948
f6bcfd97 1949 return Subtract(wxDateSpan::Days(diff));
cd0b1709
VZ
1950}
1951
2f02cb89
VZ
1952bool wxDateTime::SetToWeekDay(WeekDay weekday,
1953 int n,
1954 Month month,
1955 int year)
1956{
68379eaf 1957 wxCHECK_MSG( weekday != Inv_WeekDay, false, _T("invalid weekday") );
2f02cb89 1958
68379eaf 1959 // we don't check explicitly that -5 <= n <= 5 because we will return false
2f02cb89
VZ
1960 // anyhow in such case - but may be should still give an assert for it?
1961
1962 // take the current month/year if none specified
1963 ReplaceDefaultYearMonthWithCurrent(&year, &month);
1964
1965 wxDateTime dt;
1966
1967 // TODO this probably could be optimised somehow...
1968
1969 if ( n > 0 )
1970 {
1971 // get the first day of the month
1972 dt.Set(1, month, year);
1973
1974 // get its wday
1975 WeekDay wdayFirst = dt.GetWeekDay();
1976
1977 // go to the first weekday of the month
1978 int diff = weekday - wdayFirst;
1979 if ( diff < 0 )
1980 diff += 7;
1981
1982 // add advance n-1 weeks more
1983 diff += 7*(n - 1);
1984
239446b4 1985 dt += wxDateSpan::Days(diff);
2f02cb89 1986 }
239446b4 1987 else // count from the end of the month
2f02cb89
VZ
1988 {
1989 // get the last day of the month
1990 dt.SetToLastMonthDay(month, year);
1991
1992 // get its wday
1993 WeekDay wdayLast = dt.GetWeekDay();
1994
1995 // go to the last weekday of the month
1996 int diff = wdayLast - weekday;
1997 if ( diff < 0 )
1998 diff += 7;
1999
2000 // and rewind n-1 weeks from there
239446b4 2001 diff += 7*(-n - 1);
2f02cb89
VZ
2002
2003 dt -= wxDateSpan::Days(diff);
2004 }
2005
2006 // check that it is still in the same month
2007 if ( dt.GetMonth() == month )
2008 {
2009 *this = dt;
2010
68379eaf 2011 return true;
2f02cb89
VZ
2012 }
2013 else
2014 {
2015 // no such day in this month
68379eaf 2016 return false;
2f02cb89
VZ
2017 }
2018}
2019
1c5d27e2
VZ
2020static inline
2021wxDateTime::wxDateTime_t GetDayOfYearFromTm(const wxDateTime::Tm& tm)
2022{
907173e5 2023 return (wxDateTime::wxDateTime_t)(gs_cumulatedDays[wxDateTime::IsLeapYear(tm.year)][tm.mon] + tm.mday);
1c5d27e2
VZ
2024}
2025
239446b4
VZ
2026wxDateTime::wxDateTime_t wxDateTime::GetDayOfYear(const TimeZone& tz) const
2027{
1c5d27e2
VZ
2028 return GetDayOfYearFromTm(GetTm(tz));
2029}
239446b4 2030
1c5d27e2
VZ
2031wxDateTime::wxDateTime_t
2032wxDateTime::GetWeekOfYear(wxDateTime::WeekFlags flags, const TimeZone& tz) const
239446b4 2033{
9d9b7755
VZ
2034 if ( flags == Default_First )
2035 {
2036 flags = GetCountry() == USA ? Sunday_First : Monday_First;
2037 }
239446b4 2038
1c5d27e2
VZ
2039 Tm tm(GetTm(tz));
2040 wxDateTime_t nDayInYear = GetDayOfYearFromTm(tm);
239446b4 2041
1c5d27e2
VZ
2042 int wdTarget = GetWeekDay(tz);
2043 int wdYearStart = wxDateTime(1, Jan, GetYear()).GetWeekDay();
2044 int week;
9d9b7755
VZ
2045 if ( flags == Sunday_First )
2046 {
1c5d27e2
VZ
2047 // FIXME: First week is not calculated correctly.
2048 week = (nDayInYear - wdTarget + 7) / 7;
2049 if ( wdYearStart == Wed || wdYearStart == Thu )
2050 week++;
9d9b7755 2051 }
1c5d27e2 2052 else // week starts with monday
9d9b7755 2053 {
1c5d27e2
VZ
2054 // adjust the weekdays to non-US style.
2055 wdYearStart = ConvertWeekDayToMondayBase(wdYearStart);
2056 wdTarget = ConvertWeekDayToMondayBase(wdTarget);
239446b4 2057
1c5d27e2
VZ
2058 // quoting from http://www.cl.cam.ac.uk/~mgk25/iso-time.html:
2059 //
2060 // Week 01 of a year is per definition the first week that has the
2061 // Thursday in this year, which is equivalent to the week that
2062 // contains the fourth day of January. In other words, the first
2063 // week of a new year is the week that has the majority of its
2064 // days in the new year. Week 01 might also contain days from the
2065 // previous year and the week before week 01 of a year is the last
2066 // week (52 or 53) of the previous year even if it contains days
2067 // from the new year. A week starts with Monday (day 1) and ends
2068 // with Sunday (day 7).
2069 //
2070
2071 // if Jan 1 is Thursday or less, it is in the first week of this year
2072 if ( wdYearStart < 4 )
2073 {
2074 // count the number of entire weeks between Jan 1 and this date
2075 week = (nDayInYear + wdYearStart + 6 - wdTarget)/7;
2076
2077 // be careful to check for overflow in the next year
2078 if ( week == 53 && tm.mday - wdTarget > 28 )
2079 week = 1;
2080 }
2081 else // Jan 1 is in the last week of the previous year
2082 {
2083 // check if we happen to be at the last week of previous year:
2084 if ( tm.mon == Jan && tm.mday < 8 - wdYearStart )
2085 week = wxDateTime(31, Dec, GetYear()-1).GetWeekOfYear();
2086 else
2087 week = (nDayInYear + wdYearStart - 1 - wdTarget)/7;
2088 }
239446b4
VZ
2089 }
2090
907173e5 2091 return (wxDateTime::wxDateTime_t)week;
68ee7c47
VZ
2092}
2093
9d9b7755
VZ
2094wxDateTime::wxDateTime_t wxDateTime::GetWeekOfMonth(wxDateTime::WeekFlags flags,
2095 const TimeZone& tz) const
68ee7c47 2096{
9d9b7755
VZ
2097 Tm tm = GetTm(tz);
2098 wxDateTime dtMonthStart = wxDateTime(1, tm.mon, tm.year);
6dc6fda6 2099 int nWeek = GetWeekOfYear(flags) - dtMonthStart.GetWeekOfYear(flags) + 1;
9d9b7755 2100 if ( nWeek < 0 )
68ee7c47 2101 {
9d9b7755
VZ
2102 // this may happen for January when Jan, 1 is the last week of the
2103 // previous year
2104 nWeek += IsLeapYear(tm.year - 1) ? 53 : 52;
68ee7c47 2105 }
68ee7c47 2106
6dc6fda6 2107 return (wxDateTime::wxDateTime_t)nWeek;
239446b4
VZ
2108}
2109
f0f951fa
VZ
2110wxDateTime& wxDateTime::SetToYearDay(wxDateTime::wxDateTime_t yday)
2111{
2112 int year = GetYear();
384223b3
VZ
2113 wxDATETIME_CHECK( (0 < yday) && (yday <= GetNumberOfDays(year)),
2114 _T("invalid year day") );
f0f951fa
VZ
2115
2116 bool isLeap = IsLeapYear(year);
2117 for ( Month mon = Jan; mon < Inv_Month; wxNextMonth(mon) )
2118 {
2119 // for Dec, we can't compare with gs_cumulatedDays[mon + 1], but we
2120 // don't need it neither - because of the CHECK above we know that
2121 // yday lies in December then
f49d731f 2122 if ( (mon == Dec) || (yday <= gs_cumulatedDays[isLeap][mon + 1]) )
f0f951fa 2123 {
42841dfc 2124 Set((wxDateTime::wxDateTime_t)(yday - gs_cumulatedDays[isLeap][mon]), mon, year);
f0f951fa
VZ
2125
2126 break;
2127 }
2128 }
2129
2130 return *this;
2131}
2132
e6ec579c
VZ
2133// ----------------------------------------------------------------------------
2134// Julian day number conversion and related stuff
2135// ----------------------------------------------------------------------------
2136
2137double wxDateTime::GetJulianDayNumber() const
2138{
d26adb9d 2139 return m_time.ToDouble() / MILLISECONDS_PER_DAY + EPOCH_JDN + 0.5;
e6ec579c
VZ
2140}
2141
2142double wxDateTime::GetRataDie() const
2143{
2144 // March 1 of the year 0 is Rata Die day -306 and JDN 1721119.5
2145 return GetJulianDayNumber() - 1721119.5 - 306;
2146}
2147
fcc3d7cb 2148// ----------------------------------------------------------------------------
299fcbfe 2149// timezone and DST stuff
fcc3d7cb
VZ
2150// ----------------------------------------------------------------------------
2151
299fcbfe 2152int wxDateTime::IsDST(wxDateTime::Country country) const
fcc3d7cb 2153{
299fcbfe
VZ
2154 wxCHECK_MSG( country == Country_Default, -1,
2155 _T("country support not implemented") );
2156
2157 // use the C RTL for the dates in the standard range
2158 time_t timet = GetTicks();
2159 if ( timet != (time_t)-1 )
2160 {
a452689b
SN
2161 struct tm tmstruct;
2162 tm *tm = wxLocaltime_r(&timet, &tmstruct);
299fcbfe 2163
a452689b 2164 wxCHECK_MSG( tm, -1, _T("wxLocaltime_r() failed") );
299fcbfe
VZ
2165
2166 return tm->tm_isdst;
2167 }
2168 else
2169 {
239446b4
VZ
2170 int year = GetYear();
2171
2172 if ( !IsDSTApplicable(year, country) )
2173 {
2174 // no DST time in this year in this country
2175 return -1;
2176 }
299fcbfe 2177
239446b4 2178 return IsBetween(GetBeginDST(year, country), GetEndDST(year, country));
299fcbfe 2179 }
fcc3d7cb
VZ
2180}
2181
41acf5c0 2182wxDateTime& wxDateTime::MakeTimezone(const TimeZone& tz, bool noDST)
fcc3d7cb 2183{
479cd5de 2184 long secDiff = GetTimeZone() + tz.GetOffset();
fcc3d7cb 2185
41acf5c0
VZ
2186 // we need to know whether DST is or not in effect for this date unless
2187 // the test disabled by the caller
2188 if ( !noDST && (IsDST() == 1) )
299fcbfe
VZ
2189 {
2190 // FIXME we assume that the DST is always shifted by 1 hour
2191 secDiff -= 3600;
2192 }
2193
d26adb9d
VZ
2194 return Add(wxTimeSpan::Seconds(secDiff));
2195}
2196
2197wxDateTime& wxDateTime::MakeFromTimezone(const TimeZone& tz, bool noDST)
2198{
2199 long secDiff = GetTimeZone() + tz.GetOffset();
2200
2201 // we need to know whether DST is or not in effect for this date unless
2202 // the test disabled by the caller
2203 if ( !noDST && (IsDST() == 1) )
2204 {
2205 // FIXME we assume that the DST is always shifted by 1 hour
2206 secDiff -= 3600;
2207 }
2208
f6bcfd97 2209 return Subtract(wxTimeSpan::Seconds(secDiff));
fcc3d7cb
VZ
2210}
2211
b76b015e
VZ
2212// ----------------------------------------------------------------------------
2213// wxDateTime to/from text representations
2214// ----------------------------------------------------------------------------
2215
299fcbfe 2216wxString wxDateTime::Format(const wxChar *format, const TimeZone& tz) const
b76b015e 2217{
525d8583 2218 wxCHECK_MSG( format, wxEmptyString, _T("NULL format in wxDateTime::Format") );
e6ec579c 2219
f6bcfd97
BP
2220 // we have to use our own implementation if the date is out of range of
2221 // strftime() or if we use non standard specificators
b76b015e 2222 time_t time = GetTicks();
f6bcfd97 2223 if ( (time != (time_t)-1) && !wxStrstr(format, _T("%l")) )
b76b015e
VZ
2224 {
2225 // use strftime()
a452689b
SN
2226 struct tm tmstruct;
2227 struct tm *tm;
299fcbfe
VZ
2228 if ( tz.GetOffset() == -GetTimeZone() )
2229 {
2230 // we are working with local time
a452689b 2231 tm = wxLocaltime_r(&time, &tmstruct);
c5a1681b
VZ
2232
2233 // should never happen
a452689b 2234 wxCHECK_MSG( tm, wxEmptyString, _T("wxLocaltime_r() failed") );
299fcbfe
VZ
2235 }
2236 else
2237 {
479cd5de 2238 time += (int)tz.GetOffset();
299fcbfe 2239
33ac7e6f 2240#if defined(__VMS__) || defined(__WATCOMC__) // time is unsigned so avoid warning
13111b2a 2241 int time2 = (int) time;
9c2882d9 2242 if ( time2 >= 0 )
fb10f04c 2243#else
9c2882d9 2244 if ( time >= 0 )
fb10f04c 2245#endif
c5a1681b 2246 {
a452689b 2247 tm = wxGmtime_r(&time, &tmstruct);
b76b015e 2248
c5a1681b 2249 // should never happen
a452689b 2250 wxCHECK_MSG( tm, wxEmptyString, _T("wxGmtime_r() failed") );
c5a1681b
VZ
2251 }
2252 else
2253 {
2254 tm = (struct tm *)NULL;
2255 }
2256 }
77b88df2 2257#ifndef __WXWINCE__
ba9bbf13 2258 //Windows CE doesn't support strftime or wcsftime, so we use the generic implementation
c5a1681b 2259 if ( tm )
e6ec579c 2260 {
c5a1681b 2261 return CallStrftime(format, tm);
e6ec579c 2262 }
77b88df2 2263#endif
c5a1681b
VZ
2264 //else: use generic code below
2265 }
2266
68ee7c47 2267 // we only parse ANSI C format specifications here, no POSIX 2
f6bcfd97
BP
2268 // complications, no GNU extensions but we do add support for a "%l" format
2269 // specifier allowing to get the number of milliseconds
68ee7c47 2270 Tm tm = GetTm(tz);
e6ec579c 2271
68ee7c47
VZ
2272 // used for calls to strftime() when we only deal with time
2273 struct tm tmTimeOnly;
2274 tmTimeOnly.tm_hour = tm.hour;
2275 tmTimeOnly.tm_min = tm.min;
2276 tmTimeOnly.tm_sec = tm.sec;
2277 tmTimeOnly.tm_wday = 0;
2278 tmTimeOnly.tm_yday = 0;
2279 tmTimeOnly.tm_mday = 1; // any date will do
2280 tmTimeOnly.tm_mon = 0;
2281 tmTimeOnly.tm_year = 76;
2282 tmTimeOnly.tm_isdst = 0; // no DST, we adjust for tz ourselves
e6ec579c 2283
77c3e48a 2284 wxString tmp, res, fmt;
68ee7c47
VZ
2285 for ( const wxChar *p = format; *p; p++ )
2286 {
2287 if ( *p != _T('%') )
2288 {
2289 // copy as is
2290 res += *p;
b76b015e 2291
68ee7c47
VZ
2292 continue;
2293 }
e6ec579c 2294
77c3e48a 2295 // set the default format
68ee7c47 2296 switch ( *++p )
77c3e48a
VZ
2297 {
2298 case _T('Y'): // year has 4 digits
2299 fmt = _T("%04d");
2300 break;
2301
2302 case _T('j'): // day of year has 3 digits
f6bcfd97 2303 case _T('l'): // milliseconds have 3 digits
77c3e48a
VZ
2304 fmt = _T("%03d");
2305 break;
2306
59d04dff
VZ
2307 case _T('w'): // week day as number has only one
2308 fmt = _T("%d");
2309 break;
2310
77c3e48a
VZ
2311 default:
2312 // it's either another valid format specifier in which case
2313 // the format is "%02d" (for all the rest) or we have the
2314 // field width preceding the format in which case it will
2315 // override the default format anyhow
2316 fmt = _T("%02d");
2317 }
2318
68379eaf 2319 bool restart = true;
fc3398f8 2320 while ( restart )
68ee7c47 2321 {
68379eaf 2322 restart = false;
68ee7c47 2323
fc3398f8
VZ
2324 // start of the format specification
2325 switch ( *p )
2326 {
2327 case _T('a'): // a weekday name
2328 case _T('A'):
68379eaf 2329 // second parameter should be true for abbreviated names
fc3398f8
VZ
2330 res += GetWeekDayName(tm.GetWeekDay(),
2331 *p == _T('a') ? Name_Abbr : Name_Full);
2332 break;
68ee7c47 2333
fc3398f8
VZ
2334 case _T('b'): // a month name
2335 case _T('B'):
2336 res += GetMonthName(tm.mon,
2337 *p == _T('b') ? Name_Abbr : Name_Full);
2338 break;
2339
2340 case _T('c'): // locale default date and time representation
2341 case _T('x'): // locale default date representation
77b88df2 2342#ifndef __WXWINCE__
fc3398f8
VZ
2343 //
2344 // the problem: there is no way to know what do these format
2345 // specifications correspond to for the current locale.
2346 //
2347 // the solution: use a hack and still use strftime(): first
2348 // find the YEAR which is a year in the strftime() range (1970
2349 // - 2038) whose Jan 1 falls on the same week day as the Jan 1
2350 // of the real year. Then make a copy of the format and
3103e8a9 2351 // replace all occurrences of YEAR in it with some unique
fc3398f8
VZ
2352 // string not appearing anywhere else in it, then use
2353 // strftime() to format the date in year YEAR and then replace
2354 // YEAR back by the real year and the unique replacement
3103e8a9
JS
2355 // string back with YEAR. Notice that "all occurrences of YEAR"
2356 // means all occurrences of 4 digit as well as 2 digit form!
fc3398f8
VZ
2357 //
2358 // the bugs: we assume that neither of %c nor %x contains any
2359 // fields which may change between the YEAR and real year. For
2360 // example, the week number (%U, %W) and the day number (%j)
2361 // will change if one of these years is leap and the other one
2362 // is not!
68ee7c47 2363 {
fc3398f8
VZ
2364 // find the YEAR: normally, for any year X, Jan 1 or the
2365 // year X + 28 is the same weekday as Jan 1 of X (because
2366 // the weekday advances by 1 for each normal X and by 2
2367 // for each leap X, hence by 5 every 4 years or by 35
2368 // which is 0 mod 7 every 28 years) but this rule breaks
2369 // down if there are years between X and Y which are
2370 // divisible by 4 but not leap (i.e. divisible by 100 but
2371 // not 400), hence the correction.
2372
2373 int yearReal = GetYear(tz);
2374 int mod28 = yearReal % 28;
2375
2376 // be careful to not go too far - we risk to leave the
2377 // supported range
2378 int year;
2379 if ( mod28 < 10 )
2380 {
2381 year = 1988 + mod28; // 1988 == 0 (mod 28)
2382 }
2383 else
2384 {
2385 year = 1970 + mod28 - 10; // 1970 == 10 (mod 28)
2386 }
e6ec579c 2387
fc3398f8
VZ
2388 int nCentury = year / 100,
2389 nCenturyReal = yearReal / 100;
c5a1681b 2390
fc3398f8
VZ
2391 // need to adjust for the years divisble by 400 which are
2392 // not leap but are counted like leap ones if we just take
2393 // the number of centuries in between for nLostWeekDays
2394 int nLostWeekDays = (nCentury - nCenturyReal) -
2395 (nCentury / 4 - nCenturyReal / 4);
c5a1681b 2396
fc3398f8
VZ
2397 // we have to gain back the "lost" weekdays: note that the
2398 // effect of this loop is to not do anything to
2399 // nLostWeekDays (which we won't use any more), but to
2400 // (indirectly) set the year correctly
2401 while ( (nLostWeekDays % 7) != 0 )
2402 {
2403 nLostWeekDays += year++ % 4 ? 1 : 2;
2404 }
1d7bc913
RG
2405
2406 // Keep year below 2000 so the 2digit year number
2407 // can never match the month or day of the month
2408 if (year>=2000) year-=28;
fc3398f8
VZ
2409 // at any rate, we couldn't go further than 1988 + 9 + 28!
2410 wxASSERT_MSG( year < 2030,
2411 _T("logic error in wxDateTime::Format") );
c5a1681b 2412
1d7bc913 2413 wxString strYear, strYear2;
fc3398f8
VZ
2414 strYear.Printf(_T("%d"), year);
2415 strYear2.Printf(_T("%d"), year % 100);
1d7bc913
RG
2416
2417 // find four strings not occurring in format (this is surely
3103e8a9 2418 // not the optimal way of doing it... improvements welcome!)
17a1ebd1 2419 wxString fmt2 = format;
1d7bc913
RG
2420 wxString replacement,replacement2,replacement3,replacement4;
2421 for (int rnr=1; rnr<5 ; rnr++) {
2422 wxString r = (wxChar)-rnr;
2423 while ( fmt2.Find(r) != wxNOT_FOUND )
2424 {
2425 r << (wxChar)-rnr;
2426 }
2427
2428 switch (rnr) {
2429 case 1: replacement=r; break;
2430 case 2: replacement2=r; break;
2431 case 3: replacement3=r; break;
2432 case 4: replacement4=r; break;
2433 }
2434 }
3103e8a9 2435 // replace all occurrences of year with it
17a1ebd1 2436 bool wasReplaced = fmt2.Replace(strYear, replacement) > 0;
1d7bc913
RG
2437 // evaluation order ensures we always attempt the replacement.
2438 wasReplaced = (fmt2.Replace(strYear2, replacement2) > 0) | wasReplaced ;
fc3398f8
VZ
2439
2440 // use strftime() to format the same date but in supported
2441 // year
2442 //
2443 // NB: we assume that strftime() doesn't check for the
2444 // date validity and will happily format the date
2445 // corresponding to Feb 29 of a non leap year (which
2446 // may happen if yearReal was leap and year is not)
2447 struct tm tmAdjusted;
2448 InitTm(tmAdjusted);
2449 tmAdjusted.tm_hour = tm.hour;
2450 tmAdjusted.tm_min = tm.min;
2451 tmAdjusted.tm_sec = tm.sec;
2452 tmAdjusted.tm_wday = tm.GetWeekDay();
2453 tmAdjusted.tm_yday = GetDayOfYear();
2454 tmAdjusted.tm_mday = tm.mday;
2455 tmAdjusted.tm_mon = tm.mon;
2456 tmAdjusted.tm_year = year - 1900;
2457 tmAdjusted.tm_isdst = 0; // no DST, already adjusted
2458 wxString str = CallStrftime(*p == _T('c') ? _T("%c")
2459 : _T("%x"),
2460 &tmAdjusted);
2461
3103e8a9 2462 // now replace the occurrence of 1999 with the real year
1d7bc913
RG
2463 // we do this in two stages to stop the 2 digit year
2464 // matching any substring of the 4 digit year.
2465 // Any day,month hours and minutes components should be safe due
2466 // to ensuring the range of the years.
fc3398f8
VZ
2467 wxString strYearReal, strYearReal2;
2468 strYearReal.Printf(_T("%04d"), yearReal);
2469 strYearReal2.Printf(_T("%02d"), yearReal % 100);
1d7bc913
RG
2470 str.Replace(strYear, replacement3);
2471 str.Replace(strYear2,replacement4);
2472 str.Replace(replacement3, strYearReal);
2473 str.Replace(replacement4, strYearReal2);
fc3398f8 2474
3103e8a9 2475 // and replace back all occurrences of replacement string
fc3398f8
VZ
2476 if ( wasReplaced )
2477 {
2478 str.Replace(replacement2, strYear2);
2479 str.Replace(replacement, strYear);
2480 }
2481
2482 res += str;
68ee7c47 2483 }
77b88df2 2484#else
ba9bbf13
WS
2485 //Use "%m/%d/%y %H:%M:%S" format instead
2486 res += wxString::Format(wxT("%02d/%02d/%04d %02d:%02d:%02d"),
2487 tm.mon+1,tm.mday, tm.year, tm.hour, tm.min, tm.sec);
77b88df2 2488#endif
fc3398f8 2489 break;
c5a1681b 2490
fc3398f8
VZ
2491 case _T('d'): // day of a month (01-31)
2492 res += wxString::Format(fmt, tm.mday);
2493 break;
68ee7c47 2494
fc3398f8
VZ
2495 case _T('H'): // hour in 24h format (00-23)
2496 res += wxString::Format(fmt, tm.hour);
2497 break;
2498
2499 case _T('I'): // hour in 12h format (01-12)
68ee7c47 2500 {
fc3398f8
VZ
2501 // 24h -> 12h, 0h -> 12h too
2502 int hour12 = tm.hour > 12 ? tm.hour - 12
2503 : tm.hour ? tm.hour : 12;
2504 res += wxString::Format(fmt, hour12);
68ee7c47 2505 }
fc3398f8 2506 break;
c5a1681b 2507
fc3398f8
VZ
2508 case _T('j'): // day of the year
2509 res += wxString::Format(fmt, GetDayOfYear(tz));
2510 break;
68ee7c47 2511
f6bcfd97
BP
2512 case _T('l'): // milliseconds (NOT STANDARD)
2513 res += wxString::Format(fmt, GetMillisecond(tz));
2514 break;
2515
fc3398f8
VZ
2516 case _T('m'): // month as a number (01-12)
2517 res += wxString::Format(fmt, tm.mon + 1);
2518 break;
68ee7c47 2519
fc3398f8
VZ
2520 case _T('M'): // minute as a decimal number (00-59)
2521 res += wxString::Format(fmt, tm.min);
2522 break;
68ee7c47 2523
fc3398f8 2524 case _T('p'): // AM or PM string
77b88df2 2525#ifndef __WXWINCE__
fc3398f8 2526 res += CallStrftime(_T("%p"), &tmTimeOnly);
77b88df2 2527#else
ba9bbf13 2528 res += (tmTimeOnly.tm_hour > 12) ? wxT("pm") : wxT("am");
77b88df2 2529#endif
fc3398f8 2530 break;
68ee7c47 2531
fc3398f8
VZ
2532 case _T('S'): // second as a decimal number (00-61)
2533 res += wxString::Format(fmt, tm.sec);
2534 break;
68ee7c47 2535
fc3398f8
VZ
2536 case _T('U'): // week number in the year (Sunday 1st week day)
2537 res += wxString::Format(fmt, GetWeekOfYear(Sunday_First, tz));
2538 break;
68ee7c47 2539
fc3398f8
VZ
2540 case _T('W'): // week number in the year (Monday 1st week day)
2541 res += wxString::Format(fmt, GetWeekOfYear(Monday_First, tz));
2542 break;
68ee7c47 2543
fc3398f8
VZ
2544 case _T('w'): // weekday as a number (0-6), Sunday = 0
2545 res += wxString::Format(fmt, tm.GetWeekDay());
2546 break;
68ee7c47 2547
fc3398f8 2548 // case _T('x'): -- handled with "%c"
68ee7c47 2549
fc3398f8
VZ
2550 case _T('X'): // locale default time representation
2551 // just use strftime() to format the time for us
77b88df2 2552#ifndef __WXWINCE__
fc3398f8 2553 res += CallStrftime(_T("%X"), &tmTimeOnly);
77b88df2 2554#else
ba9bbf13 2555 res += wxString::Format(wxT("%02d:%02d:%02d"),tm.hour, tm.min, tm.sec);
77b88df2 2556#endif
fc3398f8 2557 break;
68ee7c47 2558
fc3398f8
VZ
2559 case _T('y'): // year without century (00-99)
2560 res += wxString::Format(fmt, tm.year % 100);
2561 break;
68ee7c47 2562
fc3398f8
VZ
2563 case _T('Y'): // year with century
2564 res += wxString::Format(fmt, tm.year);
2565 break;
68ee7c47 2566
fc3398f8 2567 case _T('Z'): // timezone name
77b88df2 2568#ifndef __WXWINCE__
fc3398f8 2569 res += CallStrftime(_T("%Z"), &tmTimeOnly);
77b88df2 2570#endif
fc3398f8 2571 break;
68ee7c47 2572
fc3398f8
VZ
2573 default:
2574 // is it the format width?
2575 fmt.Empty();
2576 while ( *p == _T('-') || *p == _T('+') ||
2577 *p == _T(' ') || wxIsdigit(*p) )
2578 {
2579 fmt += *p;
2580 }
68ee7c47 2581
bb8a8478 2582 if ( !fmt.empty() )
fc3398f8
VZ
2583 {
2584 // we've only got the flags and width so far in fmt
2585 fmt.Prepend(_T('%'));
2586 fmt.Append(_T('d'));
68ee7c47 2587
68379eaf 2588 restart = true;
68ee7c47 2589
fc3398f8
VZ
2590 break;
2591 }
68ee7c47 2592
fc3398f8
VZ
2593 // no, it wasn't the width
2594 wxFAIL_MSG(_T("unknown format specificator"));
68ee7c47 2595
fc3398f8 2596 // fall through and just copy it nevertheless
68ee7c47 2597
fc3398f8
VZ
2598 case _T('%'): // a percent sign
2599 res += *p;
2600 break;
2601
2602 case 0: // the end of string
2603 wxFAIL_MSG(_T("missing format at the end of string"));
2604
2605 // just put the '%' which was the last char in format
2606 res += _T('%');
2607 break;
2608 }
68ee7c47 2609 }
c5a1681b
VZ
2610 }
2611
68ee7c47 2612 return res;
b76b015e 2613}
fcc3d7cb 2614
cd0b1709
VZ
2615// this function parses a string in (strict) RFC 822 format: see the section 5
2616// of the RFC for the detailed description, but briefly it's something of the
2617// form "Sat, 18 Dec 1999 00:48:30 +0100"
2618//
2619// this function is "strict" by design - it must reject anything except true
2620// RFC822 time specs.
2621//
2622// TODO a great candidate for using reg exps
2623const wxChar *wxDateTime::ParseRfc822Date(const wxChar* date)
2624{
2625 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
2626
2627 const wxChar *p = date;
2628 const wxChar *comma = wxStrchr(p, _T(','));
2629 if ( comma )
2630 {
2631 // the part before comma is the weekday
2632
2633 // skip it for now - we don't use but might check that it really
2634 // corresponds to the specfied date
2635 p = comma + 1;
2636
2637 if ( *p != _T(' ') )
2638 {
2639 wxLogDebug(_T("no space after weekday in RFC822 time spec"));
2640
2641 return (wxChar *)NULL;
2642 }
2643
2644 p++; // skip space
2645 }
2646
2647 // the following 1 or 2 digits are the day number
2648 if ( !wxIsdigit(*p) )
2649 {
2650 wxLogDebug(_T("day number expected in RFC822 time spec, none found"));
2651
2652 return (wxChar *)NULL;
2653 }
2654
907173e5 2655 wxDateTime_t day = (wxDateTime_t)(*p++ - _T('0'));
cd0b1709
VZ
2656 if ( wxIsdigit(*p) )
2657 {
2658 day *= 10;
907173e5 2659 day = (wxDateTime_t)(day + (*p++ - _T('0')));
cd0b1709
VZ
2660 }
2661
2662 if ( *p++ != _T(' ') )
2663 {
2664 return (wxChar *)NULL;
2665 }
2666
2667 // the following 3 letters specify the month
2668 wxString monName(p, 3);
2669 Month mon;
2670 if ( monName == _T("Jan") )
2671 mon = Jan;
2672 else if ( monName == _T("Feb") )
2673 mon = Feb;
2674 else if ( monName == _T("Mar") )
2675 mon = Mar;
2676 else if ( monName == _T("Apr") )
2677 mon = Apr;
2678 else if ( monName == _T("May") )
2679 mon = May;
2680 else if ( monName == _T("Jun") )
2681 mon = Jun;
2682 else if ( monName == _T("Jul") )
2683 mon = Jul;
2684 else if ( monName == _T("Aug") )
2685 mon = Aug;
2686 else if ( monName == _T("Sep") )
2687 mon = Sep;
2688 else if ( monName == _T("Oct") )
2689 mon = Oct;
2690 else if ( monName == _T("Nov") )
2691 mon = Nov;
2692 else if ( monName == _T("Dec") )
2693 mon = Dec;
2694 else
2695 {
2696 wxLogDebug(_T("Invalid RFC 822 month name '%s'"), monName.c_str());
2697
2698 return (wxChar *)NULL;
2699 }
2700
2701 p += 3;
2702
2703 if ( *p++ != _T(' ') )
2704 {
2705 return (wxChar *)NULL;
2706 }
2707
2708 // next is the year
2709 if ( !wxIsdigit(*p) )
2710 {
2711 // no year?
2712 return (wxChar *)NULL;
2713 }
2714
2715 int year = *p++ - _T('0');
2716
2717 if ( !wxIsdigit(*p) )
2718 {
2719 // should have at least 2 digits in the year
2720 return (wxChar *)NULL;
2721 }
2722
2723 year *= 10;
2724 year += *p++ - _T('0');
2725
2726 // is it a 2 digit year (as per original RFC 822) or a 4 digit one?
2727 if ( wxIsdigit(*p) )
2728 {
2729 year *= 10;
2730 year += *p++ - _T('0');
2731
2732 if ( !wxIsdigit(*p) )
2733 {
2734 // no 3 digit years please
2735 return (wxChar *)NULL;
2736 }
2737
2738 year *= 10;
2739 year += *p++ - _T('0');
2740 }
2741
2742 if ( *p++ != _T(' ') )
2743 {
2744 return (wxChar *)NULL;
2745 }
2746
2747 // time is in the format hh:mm:ss and seconds are optional
2748 if ( !wxIsdigit(*p) )
2749 {
2750 return (wxChar *)NULL;
2751 }
2752
42841dfc 2753 wxDateTime_t hour = (wxDateTime_t)(*p++ - _T('0'));
cd0b1709
VZ
2754
2755 if ( !wxIsdigit(*p) )
2756 {
2757 return (wxChar *)NULL;
2758 }
2759
2760 hour *= 10;
42841dfc 2761 hour = (wxDateTime_t)(hour + (*p++ - _T('0')));
cd0b1709
VZ
2762
2763 if ( *p++ != _T(':') )
2764 {
2765 return (wxChar *)NULL;
2766 }
2767
2768 if ( !wxIsdigit(*p) )
2769 {
2770 return (wxChar *)NULL;
2771 }
2772
42841dfc 2773 wxDateTime_t min = (wxDateTime_t)(*p++ - _T('0'));
cd0b1709
VZ
2774
2775 if ( !wxIsdigit(*p) )
2776 {
2777 return (wxChar *)NULL;
2778 }
2779
2780 min *= 10;
42841dfc 2781 min = (wxDateTime_t)(min + *p++ - _T('0'));
cd0b1709
VZ
2782
2783 wxDateTime_t sec = 0;
2784 if ( *p++ == _T(':') )
2785 {
2786 if ( !wxIsdigit(*p) )
2787 {
2788 return (wxChar *)NULL;
2789 }
2790
42841dfc 2791 sec = (wxDateTime_t)(*p++ - _T('0'));
cd0b1709
VZ
2792
2793 if ( !wxIsdigit(*p) )
2794 {
2795 return (wxChar *)NULL;
2796 }
2797
2798 sec *= 10;
42841dfc 2799 sec = (wxDateTime_t)(sec + *p++ - _T('0'));
cd0b1709
VZ
2800 }
2801
2802 if ( *p++ != _T(' ') )
2803 {
2804 return (wxChar *)NULL;
2805 }
2806
2807 // and now the interesting part: the timezone
ba9bbf13 2808 int offset wxDUMMY_INITIALIZE(0);
cd0b1709
VZ
2809 if ( *p == _T('-') || *p == _T('+') )
2810 {
2811 // the explicit offset given: it has the form of hhmm
2812 bool plus = *p++ == _T('+');
2813
2814 if ( !wxIsdigit(*p) || !wxIsdigit(*(p + 1)) )
2815 {
2816 return (wxChar *)NULL;
2817 }
2818
2819 // hours
d26adb9d 2820 offset = MIN_PER_HOUR*(10*(*p - _T('0')) + (*(p + 1) - _T('0')));
cd0b1709
VZ
2821
2822 p += 2;
2823
2824 if ( !wxIsdigit(*p) || !wxIsdigit(*(p + 1)) )
2825 {
2826 return (wxChar *)NULL;
2827 }
2828
2829 // minutes
2830 offset += 10*(*p - _T('0')) + (*(p + 1) - _T('0'));
2831
2832 if ( !plus )
2833 {
2834 offset = -offset;
2835 }
2836
2837 p += 2;
2838 }
2839 else
2840 {
2841 // the symbolic timezone given: may be either military timezone or one
2842 // of standard abbreviations
2843 if ( !*(p + 1) )
2844 {
2845 // military: Z = UTC, J unused, A = -1, ..., Y = +12
2846 static const int offsets[26] =
2847 {
2848 //A B C D E F G H I J K L M
2849 -1, -2, -3, -4, -5, -6, -7, -8, -9, 0, -10, -11, -12,
2850 //N O P R Q S T U V W Z Y Z
2851 +1, +2, +3, +4, +5, +6, +7, +8, +9, +10, +11, +12, 0
2852 };
2853
2854 if ( *p < _T('A') || *p > _T('Z') || *p == _T('J') )
2855 {
2856 wxLogDebug(_T("Invalid militaty timezone '%c'"), *p);
2857
2858 return (wxChar *)NULL;
2859 }
2860
2861 offset = offsets[*p++ - _T('A')];
2862 }
2863 else
2864 {
2865 // abbreviation
2866 wxString tz = p;
2867 if ( tz == _T("UT") || tz == _T("UTC") || tz == _T("GMT") )
2868 offset = 0;
2869 else if ( tz == _T("AST") )
2870 offset = AST - GMT0;
2871 else if ( tz == _T("ADT") )
2872 offset = ADT - GMT0;
2873 else if ( tz == _T("EST") )
2874 offset = EST - GMT0;
2875 else if ( tz == _T("EDT") )
2876 offset = EDT - GMT0;
2877 else if ( tz == _T("CST") )
2878 offset = CST - GMT0;
2879 else if ( tz == _T("CDT") )
2880 offset = CDT - GMT0;
2881 else if ( tz == _T("MST") )
2882 offset = MST - GMT0;
2883 else if ( tz == _T("MDT") )
2884 offset = MDT - GMT0;
2885 else if ( tz == _T("PST") )
2886 offset = PST - GMT0;
2887 else if ( tz == _T("PDT") )
2888 offset = PDT - GMT0;
2889 else
2890 {
2891 wxLogDebug(_T("Unknown RFC 822 timezone '%s'"), p);
2892
2893 return (wxChar *)NULL;
2894 }
2895
2896 p += tz.length();
2897 }
96531a71 2898
cd0b1709 2899 // make it minutes
d26adb9d 2900 offset *= MIN_PER_HOUR;
cd0b1709
VZ
2901 }
2902
d26adb9d 2903 // the spec was correct, construct the date from the values we found
cd0b1709 2904 Set(day, mon, year, hour, min, sec);
18c9aebe 2905 MakeFromTimezone(TimeZone((wxDateTime_t)(offset*SEC_PER_MIN)));
cd0b1709
VZ
2906
2907 return p;
2908}
2909
64f9c100 2910#ifdef __WINDOWS__
e44501a0 2911
be4e7abf
VZ
2912// returns the string containing strftime() format used for short dates in the
2913// current locale or an empty string
2914static wxString GetLocaleDateFormat()
64f9c100 2915{
be4e7abf
VZ
2916 wxString fmtWX;
2917
05eb2d40
VZ
2918 // there is no setlocale() under Windows CE, so just always query the
2919 // system there
2920#ifndef __WXWINCE__
64f9c100 2921 if ( strcmp(setlocale(LC_ALL, NULL), "C") != 0 )
e44501a0 2922#endif
64f9c100
VS
2923 {
2924 // The locale was programatically set to non-C. We assume that this was
2925 // done using wxLocale, in which case thread's current locale is also
2926 // set to correct LCID value and we can use GetLocaleInfo to determine
2927 // the correct formatting string:
c5fe22f8
JS
2928#ifdef __WXWINCE__
2929 LCID lcid = LOCALE_USER_DEFAULT;
2930#else
64f9c100 2931 LCID lcid = GetThreadLocale();
c5fe22f8 2932#endif
be4e7abf
VZ
2933 // according to MSDN 80 chars is max allowed for short date format
2934 wxChar fmt[81];
2935 if ( ::GetLocaleInfo(lcid, LOCALE_SSHORTDATE, fmt, WXSIZEOF(fmt)) )
64f9c100 2936 {
be4e7abf
VZ
2937 wxChar chLast = _T('\0');
2938 size_t lastCount = 0;
2939 for ( const wxChar *p = fmt; /* NUL handled inside */; p++ )
64f9c100 2940 {
be4e7abf 2941 if ( *p == chLast )
64f9c100 2942 {
be4e7abf
VZ
2943 lastCount++;
2944 continue;
64f9c100 2945 }
be4e7abf
VZ
2946
2947 switch ( *p )
64f9c100 2948 {
be4e7abf
VZ
2949 // these characters come in groups, start counting them
2950 case _T('d'):
2951 case _T('M'):
2952 case _T('y'):
2953 case _T('g'):
2954 chLast = *p;
2955 lastCount = 1;
2956 break;
2957
2958 default:
2959 // first deal with any special characters we have had
2960 if ( lastCount )
2961 {
2962 switch ( chLast )
2963 {
2964 case _T('d'):
2965 switch ( lastCount )
2966 {
2967 case 1: // d
2968 case 2: // dd
2969 // these two are the same as we
2970 // don't distinguish between 1 and
2971 // 2 digits for days
2972 fmtWX += _T("%d");
2973 break;
2974
2975 case 3: // ddd
2976 fmtWX += _T("%a");
2977 break;
2978
2979 case 4: // dddd
2980 fmtWX += _T("%A");
2981 break;
2982
2983 default:
2984 wxFAIL_MSG( _T("too many 'd's") );
2985 }
2986 break;
2987
2988 case _T('M'):
2989 switch ( lastCount )
2990 {
2991 case 1: // M
2992 case 2: // MM
2993 // as for 'd' and 'dd' above
2994 fmtWX += _T("%m");
2995 break;
2996
2997 case 3:
2998 fmtWX += _T("%b");
2999 break;
3000
3001 case 4:
3002 fmtWX += _T("%B");
3003 break;
3004
3005 default:
3006 wxFAIL_MSG( _T("too many 'M's") );
3007 }
3008 break;
3009
3010 case _T('y'):
3011 switch ( lastCount )
3012 {
3013 case 1: // y
3014 case 2: // yy
3015 fmtWX += _T("%y");
3016 break;
3017
3018 case 4: // yyyy
3019 fmtWX += _T("%Y");
3020 break;
3021
3022 default:
3023 wxFAIL_MSG( _T("wrong number of 'y's") );
3024 }
3025 break;
3026
3027 case _T('g'):
3028 // strftime() doesn't have era string,
3029 // ignore this format
3030 wxASSERT_MSG( lastCount <= 2,
3031 _T("too many 'g's") );
3032 break;
3033
3034 default:
3035 wxFAIL_MSG( _T("unreachable") );
3036 }
3037
3038 chLast = _T('\0');
3039 lastCount = 0;
3040 }
3041
3042 // not a special character so must be just a separator,
3043 // treat as is
3044 if ( *p != _T('\0') )
3045 {
3046 if ( *p == _T('%') )
3047 {
3048 // this one needs to be escaped
3049 fmtWX += _T('%');
3050 }
3051
3052 fmtWX += *p;
3053 }
64f9c100 3054 }
be4e7abf
VZ
3055
3056 if ( *p == _T('\0') )
3057 break;
64f9c100
VS
3058 }
3059 }
be4e7abf
VZ
3060 //else: GetLocaleInfo() failed, leave fmtDate value unchanged and
3061 // try our luck with the default formats
64f9c100 3062 }
be4e7abf
VZ
3063 //else: default C locale, default formats should work
3064
3065 return fmtWX;
64f9c100 3066}
e44501a0 3067
64f9c100
VS
3068#endif // __WINDOWS__
3069
f0f951fa
VZ
3070const wxChar *wxDateTime::ParseFormat(const wxChar *date,
3071 const wxChar *format,
3072 const wxDateTime& dateDef)
cd0b1709
VZ
3073{
3074 wxCHECK_MSG( date && format, (wxChar *)NULL,
f0f951fa 3075 _T("NULL pointer in wxDateTime::ParseFormat()") );
cd0b1709 3076
f0f951fa
VZ
3077 wxString str;
3078 unsigned long num;
cd0b1709 3079
f0f951fa 3080 // what fields have we found?
68379eaf
WS
3081 bool haveWDay = false,
3082 haveYDay = false,
3083 haveDay = false,
3084 haveMon = false,
3085 haveYear = false,
3086 haveHour = false,
3087 haveMin = false,
3088 haveSec = false;
3089
3090 bool hourIsIn12hFormat = false, // or in 24h one?
3091 isPM = false; // AM by default
f0f951fa
VZ
3092
3093 // and the value of the items we have (init them to get rid of warnings)
3094 wxDateTime_t sec = 0,
3095 min = 0,
3096 hour = 0;
3097 WeekDay wday = Inv_WeekDay;
3098 wxDateTime_t yday = 0,
3099 mday = 0;
3100 wxDateTime::Month mon = Inv_Month;
3101 int year = 0;
3102
3103 const wxChar *input = date;
3104 for ( const wxChar *fmt = format; *fmt; fmt++ )
3105 {
3106 if ( *fmt != _T('%') )
3107 {
3108 if ( wxIsspace(*fmt) )
3109 {
3110 // a white space in the format string matches 0 or more white
3111 // spaces in the input
3112 while ( wxIsspace(*input) )
3113 {
3114 input++;
3115 }
3116 }
3117 else // !space
3118 {
3119 // any other character (not whitespace, not '%') must be
3120 // matched by itself in the input
3121 if ( *input++ != *fmt )
3122 {
3123 // no match
3124 return (wxChar *)NULL;
3125 }
3126 }
3127
3128 // done with this format char
3129 continue;
3130 }
3131
3132 // start of a format specification
f6bcfd97
BP
3133
3134 // parse the optional width
3135 size_t width = 0;
1c193821 3136 while ( wxIsdigit(*++fmt) )
f6bcfd97
BP
3137 {
3138 width *= 10;
3139 width += *fmt - _T('0');
3140 }
3141
4147efe9
VZ
3142 // the default widths for the various fields
3143 if ( !width )
3144 {
3145 switch ( *fmt )
3146 {
3147 case _T('Y'): // year has 4 digits
3148 width = 4;
3149 break;
3150
3151 case _T('j'): // day of year has 3 digits
3152 case _T('l'): // milliseconds have 3 digits
3153 width = 3;
3154 break;
3155
3156 case _T('w'): // week day as number has only one
3157 width = 1;
3158 break;
3159
3160 default:
3161 // default for all other fields
3162 width = 2;
3163 }
3164 }
3165
f6bcfd97
BP
3166 // then the format itself
3167 switch ( *fmt )
f0f951fa
VZ
3168 {
3169 case _T('a'): // a weekday name
3170 case _T('A'):
3171 {
3172 int flag = *fmt == _T('a') ? Name_Abbr : Name_Full;
3173 wday = GetWeekDayFromName(GetAlphaToken(input), flag);
3174 if ( wday == Inv_WeekDay )
3175 {
3176 // no match
3177 return (wxChar *)NULL;
3178 }
3179 }
68379eaf 3180 haveWDay = true;
f0f951fa
VZ
3181 break;
3182
3183 case _T('b'): // a month name
3184 case _T('B'):
3185 {
3186 int flag = *fmt == _T('b') ? Name_Abbr : Name_Full;
3187 mon = GetMonthFromName(GetAlphaToken(input), flag);
3188 if ( mon == Inv_Month )
3189 {
3190 // no match
3191 return (wxChar *)NULL;
3192 }
3193 }
68379eaf 3194 haveMon = true;
f0f951fa
VZ
3195 break;
3196
3197 case _T('c'): // locale default date and time representation
3198 {
3199 wxDateTime dt;
3200
3201 // this is the format which corresponds to ctime() output
3202 // and strptime("%c") should parse it, so try it first
41acf5c0 3203 static const wxChar *fmtCtime = _T("%a %b %d %H:%M:%S %Y");
f0f951fa
VZ
3204
3205 const wxChar *result = dt.ParseFormat(input, fmtCtime);
3206 if ( !result )
3207 {
3208 result = dt.ParseFormat(input, _T("%x %X"));
3209 }
3210
3211 if ( !result )
3212 {
3213 result = dt.ParseFormat(input, _T("%X %x"));
3214 }
3215
3216 if ( !result )
3217 {
3218 // we've tried everything and still no match
3219 return (wxChar *)NULL;
3220 }
3221
be4017f8
VZ
3222 Tm tm = dt.GetTm();
3223
3224 haveDay = haveMon = haveYear =
68379eaf 3225 haveHour = haveMin = haveSec = true;
be4017f8
VZ
3226
3227 hour = tm.hour;
3228 min = tm.min;
3229 sec = tm.sec;
3230
3231 year = tm.year;
3232 mon = tm.mon;
3233 mday = tm.mday;
3234
f0f951fa
VZ
3235 input = result;
3236 }
3237 break;
3238
3239 case _T('d'): // day of a month (01-31)
f6bcfd97
BP
3240 if ( !GetNumericToken(width, input, &num) ||
3241 (num > 31) || (num < 1) )
f0f951fa
VZ
3242 {
3243 // no match
3244 return (wxChar *)NULL;
3245 }
3246
3247 // we can't check whether the day range is correct yet, will
3248 // do it later - assume ok for now
68379eaf 3249 haveDay = true;
f0f951fa
VZ
3250 mday = (wxDateTime_t)num;
3251 break;
5f287370 3252
f0f951fa 3253 case _T('H'): // hour in 24h format (00-23)
f6bcfd97 3254 if ( !GetNumericToken(width, input, &num) || (num > 23) )
f0f951fa
VZ
3255 {
3256 // no match
3257 return (wxChar *)NULL;
3258 }
3259
68379eaf 3260 haveHour = true;
f0f951fa
VZ
3261 hour = (wxDateTime_t)num;
3262 break;
3263
3264 case _T('I'): // hour in 12h format (01-12)
f6bcfd97 3265 if ( !GetNumericToken(width, input, &num) || !num || (num > 12) )
f0f951fa
VZ
3266 {
3267 // no match
3268 return (wxChar *)NULL;
3269 }
3270
68379eaf
WS
3271 haveHour = true;
3272 hourIsIn12hFormat = true;
479cd5de 3273 hour = (wxDateTime_t)(num % 12); // 12 should be 0
f0f951fa
VZ
3274 break;
3275
3276 case _T('j'): // day of the year
f6bcfd97 3277 if ( !GetNumericToken(width, input, &num) || !num || (num > 366) )
f0f951fa
VZ
3278 {
3279 // no match
3280 return (wxChar *)NULL;
3281 }
3282
68379eaf 3283 haveYDay = true;
f0f951fa
VZ
3284 yday = (wxDateTime_t)num;
3285 break;
3286
3287 case _T('m'): // month as a number (01-12)
f6bcfd97 3288 if ( !GetNumericToken(width, input, &num) || !num || (num > 12) )
f0f951fa
VZ
3289 {
3290 // no match
3291 return (wxChar *)NULL;
3292 }
3293
68379eaf 3294 haveMon = true;
be4017f8 3295 mon = (Month)(num - 1);
f0f951fa
VZ
3296 break;
3297
3298 case _T('M'): // minute as a decimal number (00-59)
f6bcfd97 3299 if ( !GetNumericToken(width, input, &num) || (num > 59) )
f0f951fa
VZ
3300 {
3301 // no match
3302 return (wxChar *)NULL;
3303 }
3304
68379eaf 3305 haveMin = true;
f0f951fa
VZ
3306 min = (wxDateTime_t)num;
3307 break;
3308
3309 case _T('p'): // AM or PM string
3310 {
3311 wxString am, pm, token = GetAlphaToken(input);
3312
3313 GetAmPmStrings(&am, &pm);
bb8a8478 3314 if (am.empty() && pm.empty())
0308fd61 3315 return (wxChar *)NULL; // no am/pm strings defined
f0f951fa
VZ
3316 if ( token.CmpNoCase(pm) == 0 )
3317 {
68379eaf 3318 isPM = true;
f0f951fa
VZ
3319 }
3320 else if ( token.CmpNoCase(am) != 0 )
3321 {
3322 // no match
3323 return (wxChar *)NULL;
3324 }
3325 }
3326 break;
3327
3328 case _T('r'): // time as %I:%M:%S %p
3329 {
3330 wxDateTime dt;
3331 input = dt.ParseFormat(input, _T("%I:%M:%S %p"));
3332 if ( !input )
3333 {
3334 // no match
3335 return (wxChar *)NULL;
3336 }
3337
68379eaf 3338 haveHour = haveMin = haveSec = true;
f0f951fa
VZ
3339
3340 Tm tm = dt.GetTm();
3341 hour = tm.hour;
3342 min = tm.min;
3343 sec = tm.sec;
3344 }
3345 break;
3346
3347 case _T('R'): // time as %H:%M
3348 {
3349 wxDateTime dt;
3350 input = dt.ParseFormat(input, _T("%H:%M"));
3351 if ( !input )
3352 {
3353 // no match
3354 return (wxChar *)NULL;
3355 }
3356
68379eaf 3357 haveHour = haveMin = true;
f0f951fa
VZ
3358
3359 Tm tm = dt.GetTm();
3360 hour = tm.hour;
3361 min = tm.min;
3362 }
7d3e806d 3363 break;
f0f951fa
VZ
3364
3365 case _T('S'): // second as a decimal number (00-61)
f6bcfd97 3366 if ( !GetNumericToken(width, input, &num) || (num > 61) )
f0f951fa
VZ
3367 {
3368 // no match
3369 return (wxChar *)NULL;
3370 }
3371
68379eaf 3372 haveSec = true;
f0f951fa
VZ
3373 sec = (wxDateTime_t)num;
3374 break;
3375
3376 case _T('T'): // time as %H:%M:%S
3377 {
3378 wxDateTime dt;
3379 input = dt.ParseFormat(input, _T("%H:%M:%S"));
3380 if ( !input )
3381 {
3382 // no match
3383 return (wxChar *)NULL;
3384 }
3385
68379eaf 3386 haveHour = haveMin = haveSec = true;
f0f951fa
VZ
3387
3388 Tm tm = dt.GetTm();
3389 hour = tm.hour;
3390 min = tm.min;
3391 sec = tm.sec;
3392 }
be4017f8 3393 break;
f0f951fa
VZ
3394
3395 case _T('w'): // weekday as a number (0-6), Sunday = 0
f6bcfd97 3396 if ( !GetNumericToken(width, input, &num) || (wday > 6) )
f0f951fa
VZ
3397 {
3398 // no match
3399 return (wxChar *)NULL;
3400 }
3401
68379eaf 3402 haveWDay = true;
f0f951fa
VZ
3403 wday = (WeekDay)num;
3404 break;
3405
3406 case _T('x'): // locale default date representation
3407#ifdef HAVE_STRPTIME
2423ef22 3408 // try using strptime() -- it may fail even if the input is
f0f951fa 3409 // correct but the date is out of range, so we will fall back
2423ef22 3410 // to our generic code anyhow
f0f951fa
VZ
3411 {
3412 struct tm tm;
2423ef22
VZ
3413
3414 const wxChar *result = CallStrptime(input, "%x", &tm);
f0f951fa
VZ
3415 if ( result )
3416 {
3417 input = result;
3418
68379eaf 3419 haveDay = haveMon = haveYear = true;
f0f951fa
VZ
3420
3421 year = 1900 + tm.tm_year;
3422 mon = (Month)tm.tm_mon;
3423 mday = tm.tm_mday;
3424
3425 break;
3426 }
3427 }
3428#endif // HAVE_STRPTIME
3429
f0f951fa
VZ
3430 {
3431 wxDateTime dt;
be4e7abf
VZ
3432 wxString fmtDate,
3433 fmtDateAlt;
f0f951fa 3434
64f9c100
VS
3435#ifdef __WINDOWS__
3436 // The above doesn't work for all locales, try to query
3437 // Windows for the right way of formatting the date:
be4e7abf
VZ
3438 fmtDate = GetLocaleDateFormat();
3439 if ( fmtDate.empty() )
64f9c100 3440#endif
be4e7abf
VZ
3441 {
3442 if ( IsWestEuropeanCountry(GetCountry()) ||
3443 GetCountry() == Russia )
3444 {
3445 fmtDate = _T("%d/%m/%y");
3446 fmtDateAlt = _T("%m/%d/%y");
3447 }
3448 else // assume USA
3449 {
3450 fmtDate = _T("%m/%d/%y");
3451 fmtDateAlt = _T("%d/%m/%y");
3452 }
3453 }
64f9c100 3454
f0f951fa 3455 const wxChar *result = dt.ParseFormat(input, fmtDate);
41acf5c0 3456
be4e7abf 3457 if ( !result && !fmtDateAlt.empty() )
41acf5c0
VZ
3458 {
3459 // ok, be nice and try another one
3460 result = dt.ParseFormat(input, fmtDateAlt);
3461 }
3462
f0f951fa
VZ
3463 if ( !result )
3464 {
3465 // bad luck
3466 return (wxChar *)NULL;
3467 }
3468
3469 Tm tm = dt.GetTm();
3470
68379eaf 3471 haveDay = haveMon = haveYear = true;
f0f951fa
VZ
3472
3473 year = tm.year;
3474 mon = tm.mon;
3475 mday = tm.mday;
3476
3477 input = result;
3478 }
3479
3480 break;
3481
3482 case _T('X'): // locale default time representation
3483#ifdef HAVE_STRPTIME
3484 {
3485 // use strptime() to do it for us (FIXME !Unicode friendly)
3486 struct tm tm;
2423ef22 3487 input = CallStrptime(input, "%X", &tm);
f0f951fa
VZ
3488 if ( !input )
3489 {
3490 return (wxChar *)NULL;
3491 }
3492
68379eaf 3493 haveHour = haveMin = haveSec = true;
f0f951fa
VZ
3494
3495 hour = tm.tm_hour;
3496 min = tm.tm_min;
3497 sec = tm.tm_sec;
3498 }
3499#else // !HAVE_STRPTIME
3500 // TODO under Win32 we can query the LOCALE_ITIME system
3501 // setting which says whether the default time format is
3502 // 24 or 12 hour
3503 {
3504 // try to parse what follows as "%H:%M:%S" and, if this
3505 // fails, as "%I:%M:%S %p" - this should catch the most
3506 // common cases
3507 wxDateTime dt;
3508
3509 const wxChar *result = dt.ParseFormat(input, _T("%T"));
3510 if ( !result )
3511 {
3512 result = dt.ParseFormat(input, _T("%r"));
3513 }
3514
3515 if ( !result )
3516 {
3517 // no match
3518 return (wxChar *)NULL;
3519 }
3520
68379eaf 3521 haveHour = haveMin = haveSec = true;
f0f951fa
VZ
3522
3523 Tm tm = dt.GetTm();
3524 hour = tm.hour;
3525 min = tm.min;
3526 sec = tm.sec;
3527
3528 input = result;
3529 }
3530#endif // HAVE_STRPTIME/!HAVE_STRPTIME
3531 break;
3532
3533 case _T('y'): // year without century (00-99)
f6bcfd97 3534 if ( !GetNumericToken(width, input, &num) || (num > 99) )
f0f951fa
VZ
3535 {
3536 // no match
3537 return (wxChar *)NULL;
3538 }
3539
68379eaf 3540 haveYear = true;
f6bcfd97
BP
3541
3542 // TODO should have an option for roll over date instead of
3543 // hard coding it here
3544 year = (num > 30 ? 1900 : 2000) + (wxDateTime_t)num;
f0f951fa
VZ
3545 break;
3546
3547 case _T('Y'): // year with century
f6bcfd97 3548 if ( !GetNumericToken(width, input, &num) )
f0f951fa
VZ
3549 {
3550 // no match
3551 return (wxChar *)NULL;
3552 }
3553
68379eaf 3554 haveYear = true;
be4017f8 3555 year = (wxDateTime_t)num;
f0f951fa
VZ
3556 break;
3557
3558 case _T('Z'): // timezone name
3559 wxFAIL_MSG(_T("TODO"));
3560 break;
3561
3562 case _T('%'): // a percent sign
3563 if ( *input++ != _T('%') )
3564 {
3565 // no match
3566 return (wxChar *)NULL;
3567 }
3568 break;
3569
3570 case 0: // the end of string
3571 wxFAIL_MSG(_T("unexpected format end"));
3572
3573 // fall through
3574
3575 default: // not a known format spec
3576 return (wxChar *)NULL;
3577 }
3578 }
3579
3580 // format matched, try to construct a date from what we have now
3581 Tm tmDef;
3582 if ( dateDef.IsValid() )
3583 {
3584 // take this date as default
3585 tmDef = dateDef.GetTm();
3586 }
c3302e7e 3587 else if ( IsValid() )
f0f951fa
VZ
3588 {
3589 // if this date is valid, don't change it
3590 tmDef = GetTm();
3591 }
3592 else
3593 {
3594 // no default and this date is invalid - fall back to Today()
3595 tmDef = Today().GetTm();
3596 }
3597
3598 Tm tm = tmDef;
3599
3600 // set the date
3601 if ( haveYear )
3602 {
3603 tm.year = year;
3604 }
3605
3606 // TODO we don't check here that the values are consistent, if both year
3607 // day and month/day were found, we just ignore the year day and we
3608 // also always ignore the week day
3609 if ( haveMon && haveDay )
3610 {
be4017f8
VZ
3611 if ( mday > GetNumOfDaysInMonth(tm.year, mon) )
3612 {
3613 wxLogDebug(_T("bad month day in wxDateTime::ParseFormat"));
3614
3615 return (wxChar *)NULL;
3616 }
3617
f0f951fa
VZ
3618 tm.mon = mon;
3619 tm.mday = mday;
3620 }
3621 else if ( haveYDay )
3622 {
be4017f8
VZ
3623 if ( yday > GetNumberOfDays(tm.year) )
3624 {
3625 wxLogDebug(_T("bad year day in wxDateTime::ParseFormat"));
3626
3627 return (wxChar *)NULL;
3628 }
3629
f0f951fa
VZ
3630 Tm tm2 = wxDateTime(1, Jan, tm.year).SetToYearDay(yday).GetTm();
3631
3632 tm.mon = tm2.mon;
3633 tm.mday = tm2.mday;
3634 }
3635
3636 // deal with AM/PM
3637 if ( haveHour && hourIsIn12hFormat && isPM )
3638 {
3639 // translate to 24hour format
3640 hour += 12;
3641 }
3642 //else: either already in 24h format or no translation needed
3643
3644 // set the time
3645 if ( haveHour )
3646 {
5f287370 3647 tm.hour = hour;
f0f951fa
VZ
3648 }
3649
3650 if ( haveMin )
3651 {
3652 tm.min = min;
3653 }
3654
3655 if ( haveSec )
3656 {
3657 tm.sec = sec;
3658 }
3659
3660 Set(tm);
3661
88a036ce
VZ
3662 // finally check that the week day is consistent -- if we had it
3663 if ( haveWDay && GetWeekDay() != wday )
3664 {
3665 wxLogDebug(_T("inconsistsnet week day in wxDateTime::ParseFormat()"));
3666
3667 return NULL;
3668 }
3669
f0f951fa 3670 return input;
cd0b1709
VZ
3671}
3672
3673const wxChar *wxDateTime::ParseDateTime(const wxChar *date)
3674{
3675 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
3676
de07d200
VZ
3677 // Set to current day and hour, so strings like '14:00' becomes today at
3678 // 14, not some other random date
3679 wxDateTime dtDate = wxDateTime::Today();
3680 wxDateTime dtTime = wxDateTime::Today();
cd0b1709 3681
de07d200
VZ
3682 const wxChar* pchTime;
3683
3684 // Try to parse the beginning of the string as a date
3685 const wxChar* pchDate = dtDate.ParseDate(date);
3686
3687 // We got a date in the beginning, see if there is a time specified after the date
3688 if ( pchDate )
3689 {
3690 // Skip spaces, as the ParseTime() function fails on spaces
3691 while ( wxIsspace(*pchDate) )
3692 pchDate++;
3693
3694 pchTime = dtTime.ParseTime(pchDate);
3695 }
3696 else // no date in the beginning
3697 {
3698 // check and see if we have a time followed by a date
3699 pchTime = dtTime.ParseTime(date);
3700 if ( pchTime )
3701 {
3702 while ( wxIsspace(*pchTime) )
3703 pchTime++;
3704
3705 pchDate = dtDate.ParseDate(pchTime);
3706 }
3707 }
3708
3709 // If we have a date specified, set our own data to the same date
3710 if ( !pchDate || !pchTime )
3711 return NULL;
3712
3713 Set(dtDate.GetDay(), dtDate.GetMonth(), dtDate.GetYear(),
3714 dtTime.GetHour(), dtTime.GetMinute(), dtTime.GetSecond(),
3715 dtTime.GetMillisecond());
3716
3717 // Return endpoint of scan
3718 return pchDate > pchTime ? pchDate : pchTime;
cd0b1709
VZ
3719}
3720
3721const wxChar *wxDateTime::ParseDate(const wxChar *date)
3722{
3723 // this is a simplified version of ParseDateTime() which understands only
3724 // "today" (for wxDate compatibility) and digits only otherwise (and not
3725 // all esoteric constructions ParseDateTime() knows about)
3726
3727 wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
3728
3729 const wxChar *p = date;
3730 while ( wxIsspace(*p) )
3731 p++;
3732
fd9f9f4c
VZ
3733 // some special cases
3734 static struct
cd0b1709 3735 {
fd9f9f4c
VZ
3736 const wxChar *str;
3737 int dayDiffFromToday;
3738 } literalDates[] =
3739 {
3740 { wxTRANSLATE("today"), 0 },
3741 { wxTRANSLATE("yesterday"), -1 },
3742 { wxTRANSLATE("tomorrow"), 1 },
3743 };
3744
3745 for ( size_t n = 0; n < WXSIZEOF(literalDates); n++ )
3746 {
7a7bd38a
VZ
3747 const wxString dateStr = wxGetTranslation(literalDates[n].str);
3748 size_t len = dateStr.length();
907173e5 3749 if ( wxStrlen(p) >= len )
fd9f9f4c 3750 {
907173e5 3751 wxString str(p, len);
7a7bd38a 3752 if ( str.CmpNoCase(dateStr) == 0 )
fd9f9f4c 3753 {
907173e5
WS
3754 // nothing can follow this, so stop here
3755 p += len;
3756
3757 int dayDiffFromToday = literalDates[n].dayDiffFromToday;
3758 *this = Today();
3759 if ( dayDiffFromToday )
3760 {
3761 *this += wxDateSpan::Days(dayDiffFromToday);
3762 }
cd0b1709 3763
907173e5
WS
3764 return p;
3765 }
fd9f9f4c 3766 }
cd0b1709
VZ
3767 }
3768
3ca6a5f0
BP
3769 // We try to guess what we have here: for each new (numeric) token, we
3770 // determine if it can be a month, day or a year. Of course, there is an
3771 // ambiguity as some numbers may be days as well as months, so we also
3772 // have the ability to back track.
3773
cd0b1709 3774 // what do we have?
68379eaf
WS
3775 bool haveDay = false, // the months day?
3776 haveWDay = false, // the day of week?
3777 haveMon = false, // the month?
3778 haveYear = false; // the year?
cd0b1709
VZ
3779
3780 // and the value of the items we have (init them to get rid of warnings)
3781 WeekDay wday = Inv_WeekDay;
3782 wxDateTime_t day = 0;
3783 wxDateTime::Month mon = Inv_Month;
3784 int year = 0;
3785
3786 // tokenize the string
f6bcfd97 3787 size_t nPosCur = 0;
62ae2780 3788 static const wxChar *dateDelimiters = _T(".,/-\t\r\n ");
f6bcfd97 3789 wxStringTokenizer tok(p, dateDelimiters);
cd0b1709
VZ
3790 while ( tok.HasMoreTokens() )
3791 {
3792 wxString token = tok.GetNextToken();
3ca6a5f0
BP
3793 if ( !token )
3794 continue;
cd0b1709
VZ
3795
3796 // is it a number?
3797 unsigned long val;
fde5a86b 3798 if ( token.ToULong(&val) )
cd0b1709
VZ
3799 {
3800 // guess what this number is
3801
68379eaf
WS
3802 bool isDay = false,
3803 isMonth = false,
3804 isYear = false;
3ca6a5f0
BP
3805
3806 if ( !haveMon && val > 0 && val <= 12 )
cd0b1709 3807 {
3ca6a5f0 3808 // assume it is month
68379eaf 3809 isMonth = true;
3ca6a5f0
BP
3810 }
3811 else // not the month
3812 {
560e2916 3813 if ( haveDay )
cd0b1709 3814 {
560e2916 3815 // this can only be the year
68379eaf 3816 isYear = true;
cd0b1709 3817 }
560e2916 3818 else // may be either day or year
cd0b1709 3819 {
eca0692c
VZ
3820 // use a leap year if we don't have the year yet to allow
3821 // dates like 2/29/1976 which would be rejected otherwise
6a27c749 3822 wxDateTime_t max_days = (wxDateTime_t)(
42841dfc 3823 haveMon
eca0692c 3824 ? GetNumOfDaysInMonth(haveYear ? year : 1976, mon)
42841dfc
WS
3825 : 31
3826 );
560e2916
VZ
3827
3828 // can it be day?
6a27c749 3829 if ( (val == 0) || (val > (unsigned long)max_days) )
560e2916
VZ
3830 {
3831 // no
68379eaf 3832 isYear = true;
560e2916
VZ
3833 }
3834 else // yes, suppose it's the day
3835 {
68379eaf 3836 isDay = true;
560e2916 3837 }
cd0b1709
VZ
3838 }
3839 }
3840
cd0b1709
VZ
3841 if ( isYear )
3842 {
3843 if ( haveYear )
cd0b1709 3844 break;
cd0b1709 3845
68379eaf 3846 haveYear = true;
cd0b1709 3847
479cd5de 3848 year = (wxDateTime_t)val;
cd0b1709 3849 }
3ca6a5f0 3850 else if ( isDay )
cd0b1709 3851 {
3ca6a5f0 3852 if ( haveDay )
cd0b1709 3853 break;
cd0b1709 3854
68379eaf 3855 haveDay = true;
cd0b1709 3856
3ca6a5f0 3857 day = (wxDateTime_t)val;
cd0b1709 3858 }
3ca6a5f0 3859 else if ( isMonth )
cd0b1709 3860 {
68379eaf 3861 haveMon = true;
cd0b1709 3862
3ca6a5f0 3863 mon = (Month)(val - 1);
cd0b1709
VZ
3864 }
3865 }
3866 else // not a number
3867 {
f6bcfd97
BP
3868 // be careful not to overwrite the current mon value
3869 Month mon2 = GetMonthFromName(token, Name_Full | Name_Abbr);
3870 if ( mon2 != Inv_Month )
cd0b1709
VZ
3871 {
3872 // it's a month
3873 if ( haveMon )
3874 {
6411a32c
VZ
3875 // but we already have a month - maybe we guessed wrong?
3876 if ( !haveDay )
3877 {
f5cd9787 3878 // no need to check in month range as always < 12, but
6411a32c 3879 // the days are counted from 1 unlike the months
42841dfc 3880 day = (wxDateTime_t)(mon + 1);
68379eaf 3881 haveDay = true;
6411a32c
VZ
3882 }
3883 else
3884 {
3885 // could possible be the year (doesn't the year come
3886 // before the month in the japanese format?) (FIXME)
3887 break;
f5cd9787 3888 }
cd0b1709
VZ
3889 }
3890
f6bcfd97
BP
3891 mon = mon2;
3892
68379eaf 3893 haveMon = true;
cd0b1709 3894 }
6411a32c 3895 else // not a valid month name
cd0b1709 3896 {
f0f951fa 3897 wday = GetWeekDayFromName(token, Name_Full | Name_Abbr);
cd0b1709
VZ
3898 if ( wday != Inv_WeekDay )
3899 {
3900 // a week day
3901 if ( haveWDay )
3902 {
3903 break;
3904 }
3905
68379eaf 3906 haveWDay = true;
cd0b1709 3907 }
6411a32c 3908 else // not a valid weekday name
cd0b1709
VZ
3909 {
3910 // try the ordinals
3911 static const wxChar *ordinals[] =
3912 {
3913 wxTRANSLATE("first"),
3914 wxTRANSLATE("second"),
3915 wxTRANSLATE("third"),
3916 wxTRANSLATE("fourth"),
3917 wxTRANSLATE("fifth"),
3918 wxTRANSLATE("sixth"),
3919 wxTRANSLATE("seventh"),
3920 wxTRANSLATE("eighth"),
3921 wxTRANSLATE("ninth"),
3922 wxTRANSLATE("tenth"),
3923 wxTRANSLATE("eleventh"),
3924 wxTRANSLATE("twelfth"),
3925 wxTRANSLATE("thirteenth"),
3926 wxTRANSLATE("fourteenth"),
3927 wxTRANSLATE("fifteenth"),
3928 wxTRANSLATE("sixteenth"),
3929 wxTRANSLATE("seventeenth"),
3930 wxTRANSLATE("eighteenth"),
3931 wxTRANSLATE("nineteenth"),
3932 wxTRANSLATE("twentieth"),
3933 // that's enough - otherwise we'd have problems with
6411a32c 3934 // composite (or not) ordinals
cd0b1709
VZ
3935 };
3936
3937 size_t n;
3938 for ( n = 0; n < WXSIZEOF(ordinals); n++ )
3939 {
3940 if ( token.CmpNoCase(ordinals[n]) == 0 )
3941 {
3942 break;
3943 }
3944 }
3945
3946 if ( n == WXSIZEOF(ordinals) )
3947 {
3948 // stop here - something unknown
3949 break;
3950 }
3951
3952 // it's a day
3953 if ( haveDay )
3954 {
3955 // don't try anything here (as in case of numeric day
3956 // above) - the symbolic day spec should always
3957 // precede the month/year
3958 break;
3959 }
3960
68379eaf 3961 haveDay = true;
cd0b1709 3962
33ac7e6f 3963 day = (wxDateTime_t)(n + 1);
cd0b1709
VZ
3964 }
3965 }
3966 }
f6bcfd97
BP
3967
3968 nPosCur = tok.GetPosition();
cd0b1709
VZ
3969 }
3970
3971 // either no more tokens or the scan was stopped by something we couldn't
3972 // parse - in any case, see if we can construct a date from what we have
3973 if ( !haveDay && !haveWDay )
3974 {
f6bcfd97 3975 wxLogDebug(_T("ParseDate: no day, no weekday hence no date."));
cd0b1709 3976
eca0692c 3977 return NULL;
cd0b1709
VZ
3978 }
3979
3980 if ( haveWDay && (haveMon || haveYear || haveDay) &&
f6bcfd97 3981 !(haveDay && haveMon && haveYear) )
cd0b1709
VZ
3982 {
3983 // without adjectives (which we don't support here) the week day only
3984 // makes sense completely separately or with the full date
3985 // specification (what would "Wed 1999" mean?)
eca0692c 3986 return NULL;
cd0b1709
VZ
3987 }
3988
f6bcfd97
BP
3989 if ( !haveWDay && haveYear && !(haveDay && haveMon) )
3990 {
3991 // may be we have month and day instead of day and year?
3992 if ( haveDay && !haveMon )
3993 {
3994 if ( day <= 12 )
3995 {
3996 // exchange day and month
3997 mon = (wxDateTime::Month)(day - 1);
3998
3999 // we're in the current year then
196be0f1 4000 if ( (year > 0) && (year <= (int)GetNumOfDaysInMonth(Inv_Year, mon)) )
f6bcfd97 4001 {
42841dfc 4002 day = (wxDateTime_t)year;
f6bcfd97 4003
68379eaf
WS
4004 haveMon = true;
4005 haveYear = false;
f6bcfd97 4006 }
68379eaf 4007 //else: no, can't exchange, leave haveMon == false
f6bcfd97
BP
4008 }
4009 }
4010
4011 if ( !haveMon )
4012 {
4013 // if we give the year, month and day must be given too
4014 wxLogDebug(_T("ParseDate: day and month should be specified if year is."));
4015
eca0692c 4016 return NULL;
f6bcfd97
BP
4017 }
4018 }
4019
cd0b1709
VZ
4020 if ( !haveMon )
4021 {
4022 mon = GetCurrentMonth();
4023 }
4024
4025 if ( !haveYear )
4026 {
4027 year = GetCurrentYear();
4028 }
4029
4030 if ( haveDay )
4031 {
eca0692c
VZ
4032 // normally we check the day above but the check is optimistic in case
4033 // we find the day before its month/year so we have to redo it now
4034 if ( day > GetNumOfDaysInMonth(year, mon) )
4035 return NULL;
4036
cd0b1709
VZ
4037 Set(day, mon, year);
4038
4039 if ( haveWDay )
4040 {
4041 // check that it is really the same
4042 if ( GetWeekDay() != wday )
4043 {
4044 // inconsistency detected
f6bcfd97
BP
4045 wxLogDebug(_T("ParseDate: inconsistent day/weekday."));
4046
cd0b1709
VZ
4047 return (wxChar *)NULL;
4048 }
4049 }
4050 }
4051 else // haveWDay
4052 {
4053 *this = Today();
4054
4055 SetToWeekDayInSameWeek(wday);
4056 }
4057
f6bcfd97
BP
4058 // return the pointer to the first unparsed char
4059 p += nPosCur;
4060 if ( nPosCur && wxStrchr(dateDelimiters, *(p - 1)) )
4061 {
4062 // if we couldn't parse the token after the delimiter, put back the
4063 // delimiter as well
4064 p--;
4065 }
4066
4067 return p;
cd0b1709
VZ
4068}
4069
4070const wxChar *wxDateTime::ParseTime(const wxChar *time)
4071{
4072 wxCHECK_MSG( time, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
4073
f0f951fa
VZ
4074 // first try some extra things
4075 static const struct
4076 {
4077 const wxChar *name;
4078 wxDateTime_t hour;
4079 } stdTimes[] =
4080 {
4081 { wxTRANSLATE("noon"), 12 },
4082 { wxTRANSLATE("midnight"), 00 },
4083 // anything else?
4084 };
cd0b1709 4085
f0f951fa
VZ
4086 for ( size_t n = 0; n < WXSIZEOF(stdTimes); n++ )
4087 {
4088 wxString timeString = wxGetTranslation(stdTimes[n].name);
4089 size_t len = timeString.length();
4090 if ( timeString.CmpNoCase(wxString(time, len)) == 0 )
4091 {
4ded51f2
CE
4092 // casts required by DigitalMars
4093 Set(stdTimes[n].hour, wxDateTime_t(0), wxDateTime_t(0));
f0f951fa
VZ
4094
4095 return time + len;
4096 }
4097 }
4098
c7f9a482
VZ
4099 // try all time formats we may think about in the order from longest to
4100 // shortest
4101
4102 // 12hour with AM/PM?
4103 const wxChar *result = ParseFormat(time, _T("%I:%M:%S %p"));
4104
f0f951fa
VZ
4105 if ( !result )
4106 {
4107 // normally, it's the same, but why not try it?
4108 result = ParseFormat(time, _T("%H:%M:%S"));
4109 }
4110
4111 if ( !result )
4112 {
c7f9a482
VZ
4113 // 12hour with AM/PM but without seconds?
4114 result = ParseFormat(time, _T("%I:%M %p"));
f0f951fa
VZ
4115 }
4116
4117 if ( !result )
4118 {
4119 // without seconds?
4120 result = ParseFormat(time, _T("%H:%M"));
4121 }
4122
4123 if ( !result )
4124 {
c7f9a482
VZ
4125 // just the hour and AM/PM?
4126 result = ParseFormat(time, _T("%I %p"));
f0f951fa
VZ
4127 }
4128
4129 if ( !result )
4130 {
4131 // just the hour?
4132 result = ParseFormat(time, _T("%H"));
4133 }
4134
4135 if ( !result )
4136 {
c7f9a482
VZ
4137 // parse the standard format: normally it is one of the formats above
4138 // but it may be set to something completely different by the user
4139 result = ParseFormat(time, _T("%X"));
f0f951fa
VZ
4140 }
4141
4142 // TODO: parse timezones
4143
4144 return result;
cd0b1709
VZ
4145}
4146
4f6aed9c
VZ
4147// ----------------------------------------------------------------------------
4148// Workdays and holidays support
4149// ----------------------------------------------------------------------------
4150
4151bool wxDateTime::IsWorkDay(Country WXUNUSED(country)) const
4152{
4153 return !wxDateTimeHolidayAuthority::IsHoliday(*this);
4154}
4155
26364344
WS
4156// ============================================================================
4157// wxDateSpan
4158// ============================================================================
4159
4160wxDateSpan WXDLLIMPEXP_BASE operator*(int n, const wxDateSpan& ds)
4161{
4162 wxDateSpan ds1(ds);
4163 return ds1.Multiply(n);
4164}
4165
fcc3d7cb
VZ
4166// ============================================================================
4167// wxTimeSpan
4168// ============================================================================
4169
26364344
WS
4170wxTimeSpan WXDLLIMPEXP_BASE operator*(int n, const wxTimeSpan& ts)
4171{
4172 return wxTimeSpan(ts).Multiply(n);
4173}
4174
033400eb
VZ
4175// this enum is only used in wxTimeSpan::Format() below but we can't declare
4176// it locally to the method as it provokes an internal compiler error in egcs
4177// 2.91.60 when building with -O2
4178enum TimeSpanPart
4179{
4180 Part_Week,
4181 Part_Day,
4182 Part_Hour,
4183 Part_Min,
4184 Part_Sec,
4185 Part_MSec
4186};
4187
e6ec579c
VZ
4188// not all strftime(3) format specifiers make sense here because, for example,
4189// a time span doesn't have a year nor a timezone
4190//
4191// Here are the ones which are supported (all of them are supported by strftime
4192// as well):
4193// %H hour in 24 hour format
4194// %M minute (00 - 59)
4195// %S second (00 - 59)
4196// %% percent sign
4197//
4198// Also, for MFC CTimeSpan compatibility, we support
4199// %D number of days
4200//
4201// And, to be better than MFC :-), we also have
4202// %E number of wEeks
4203// %l milliseconds (000 - 999)
fcc3d7cb
VZ
4204wxString wxTimeSpan::Format(const wxChar *format) const
4205{
525d8583 4206 wxCHECK_MSG( format, wxEmptyString, _T("NULL format in wxTimeSpan::Format") );
fcc3d7cb
VZ
4207
4208 wxString str;
5b735202 4209 str.Alloc(wxStrlen(format));
e6ec579c 4210
df05cdc5
VZ
4211 // Suppose we have wxTimeSpan ts(1 /* hour */, 2 /* min */, 3 /* sec */)
4212 //
4213 // Then, of course, ts.Format("%H:%M:%S") must return "01:02:03", but the
4214 // question is what should ts.Format("%S") do? The code here returns "3273"
4215 // in this case (i.e. the total number of seconds, not just seconds % 60)
4216 // because, for me, this call means "give me entire time interval in
4217 // seconds" and not "give me the seconds part of the time interval"
4218 //
4219 // If we agree that it should behave like this, it is clear that the
4220 // interpretation of each format specifier depends on the presence of the
4221 // other format specs in the string: if there was "%H" before "%M", we
4222 // should use GetMinutes() % 60, otherwise just GetMinutes() &c
4223
4224 // we remember the most important unit found so far
033400eb 4225 TimeSpanPart partBiggest = Part_MSec;
df05cdc5 4226
f6bcfd97 4227 for ( const wxChar *pch = format; *pch; pch++ )
e6ec579c
VZ
4228 {
4229 wxChar ch = *pch;
4230
f6bcfd97 4231 if ( ch == _T('%') )
e6ec579c 4232 {
df05cdc5
VZ
4233 // the start of the format specification of the printf() below
4234 wxString fmtPrefix = _T('%');
4235
4236 // the number
4237 long n;
e6ec579c 4238
f6bcfd97 4239 ch = *++pch; // get the format spec char
e6ec579c
VZ
4240 switch ( ch )
4241 {
4242 default:
4243 wxFAIL_MSG( _T("invalid format character") );
4244 // fall through
4245
f6bcfd97 4246 case _T('%'):
df05cdc5
VZ
4247 str += ch;
4248
4249 // skip the part below switch
4250 continue;
e6ec579c 4251
f6bcfd97 4252 case _T('D'):
df05cdc5
VZ
4253 n = GetDays();
4254 if ( partBiggest < Part_Day )
4255 {
4256 n %= DAYS_PER_WEEK;
4257 }
4258 else
4259 {
4260 partBiggest = Part_Day;
4261 }
e6ec579c
VZ
4262 break;
4263
f6bcfd97 4264 case _T('E'):
df05cdc5
VZ
4265 partBiggest = Part_Week;
4266 n = GetWeeks();
e6ec579c
VZ
4267 break;
4268
f6bcfd97 4269 case _T('H'):
df05cdc5
VZ
4270 n = GetHours();
4271 if ( partBiggest < Part_Hour )
4272 {
4273 n %= HOURS_PER_DAY;
4274 }
4275 else
4276 {
4277 partBiggest = Part_Hour;
4278 }
4279
4280 fmtPrefix += _T("02");
e6ec579c
VZ
4281 break;
4282
f6bcfd97 4283 case _T('l'):
df05cdc5
VZ
4284 n = GetMilliseconds().ToLong();
4285 if ( partBiggest < Part_MSec )
4286 {
4287 n %= 1000;
4288 }
4289 //else: no need to reset partBiggest to Part_MSec, it is
4290 // the least significant one anyhow
4291
4292 fmtPrefix += _T("03");
e6ec579c
VZ
4293 break;
4294
f6bcfd97 4295 case _T('M'):
df05cdc5
VZ
4296 n = GetMinutes();
4297 if ( partBiggest < Part_Min )
4298 {
4299 n %= MIN_PER_HOUR;
4300 }
4301 else
4302 {
4303 partBiggest = Part_Min;
4304 }
4305
4306 fmtPrefix += _T("02");
e6ec579c
VZ
4307 break;
4308
f6bcfd97 4309 case _T('S'):
df05cdc5
VZ
4310 n = GetSeconds().ToLong();
4311 if ( partBiggest < Part_Sec )
4312 {
4313 n %= SEC_PER_MIN;
4314 }
4315 else
4316 {
4317 partBiggest = Part_Sec;
4318 }
4319
4320 fmtPrefix += _T("02");
e6ec579c
VZ
4321 break;
4322 }
4323
df05cdc5
VZ
4324 str += wxString::Format(fmtPrefix + _T("ld"), n);
4325 }
4326 else
4327 {
4328 // normal character, just copy
4329 str += ch;
e6ec579c 4330 }
e6ec579c 4331 }
fcc3d7cb
VZ
4332
4333 return str;
4334}
4f6aed9c
VZ
4335
4336// ============================================================================
4337// wxDateTimeHolidayAuthority and related classes
4338// ============================================================================
4339
4340#include "wx/arrimpl.cpp"
4341
4115960d 4342WX_DEFINE_OBJARRAY(wxDateTimeArray)
4f6aed9c
VZ
4343
4344static int wxCMPFUNC_CONV
4345wxDateTimeCompareFunc(wxDateTime **first, wxDateTime **second)
4346{
4347 wxDateTime dt1 = **first,
4348 dt2 = **second;
4349
4350 return dt1 == dt2 ? 0 : dt1 < dt2 ? -1 : +1;
4351}
4352
4353// ----------------------------------------------------------------------------
4354// wxDateTimeHolidayAuthority
4355// ----------------------------------------------------------------------------
4356
4357wxHolidayAuthoritiesArray wxDateTimeHolidayAuthority::ms_authorities;
4358
4359/* static */
4360bool wxDateTimeHolidayAuthority::IsHoliday(const wxDateTime& dt)
4361{
df5168c4 4362 size_t count = ms_authorities.size();
4f6aed9c
VZ
4363 for ( size_t n = 0; n < count; n++ )
4364 {
4365 if ( ms_authorities[n]->DoIsHoliday(dt) )
4366 {
68379eaf 4367 return true;
4f6aed9c
VZ
4368 }
4369 }
4370
68379eaf 4371 return false;
4f6aed9c
VZ
4372}
4373
4374/* static */
4375size_t
4376wxDateTimeHolidayAuthority::GetHolidaysInRange(const wxDateTime& dtStart,
4377 const wxDateTime& dtEnd,
4378 wxDateTimeArray& holidays)
4379{
4380 wxDateTimeArray hol;
4381
df5168c4 4382 holidays.Clear();
4f6aed9c 4383
7a7bd38a
VZ
4384 const size_t countAuth = ms_authorities.size();
4385 for ( size_t nAuth = 0; nAuth < countAuth; nAuth++ )
4f6aed9c 4386 {
6dc6fda6 4387 ms_authorities[nAuth]->DoGetHolidaysInRange(dtStart, dtEnd, hol);
4f6aed9c
VZ
4388
4389 WX_APPEND_ARRAY(holidays, hol);
4390 }
4391
4392 holidays.Sort(wxDateTimeCompareFunc);
4393
df5168c4 4394 return holidays.size();
4f6aed9c
VZ
4395}
4396
4397/* static */
4398void wxDateTimeHolidayAuthority::ClearAllAuthorities()
4399{
4400 WX_CLEAR_ARRAY(ms_authorities);
4401}
4402
4403/* static */
4404void wxDateTimeHolidayAuthority::AddAuthority(wxDateTimeHolidayAuthority *auth)
4405{
df5168c4 4406 ms_authorities.push_back(auth);
4f6aed9c
VZ
4407}
4408
5e233068
WS
4409wxDateTimeHolidayAuthority::~wxDateTimeHolidayAuthority()
4410{
4411 // required here for Darwin
4412}
4413
4f6aed9c
VZ
4414// ----------------------------------------------------------------------------
4415// wxDateTimeWorkDays
4416// ----------------------------------------------------------------------------
4417
4418bool wxDateTimeWorkDays::DoIsHoliday(const wxDateTime& dt) const
4419{
4420 wxDateTime::WeekDay wd = dt.GetWeekDay();
4421
4422 return (wd == wxDateTime::Sun) || (wd == wxDateTime::Sat);
4423}
4424
4425size_t wxDateTimeWorkDays::DoGetHolidaysInRange(const wxDateTime& dtStart,
4426 const wxDateTime& dtEnd,
4427 wxDateTimeArray& holidays) const
4428{
4429 if ( dtStart > dtEnd )
4430 {
4431 wxFAIL_MSG( _T("invalid date range in GetHolidaysInRange") );
4432
4433 return 0u;
4434 }
4435
4436 holidays.Empty();
4437
4438 // instead of checking all days, start with the first Sat after dtStart and
4439 // end with the last Sun before dtEnd
4440 wxDateTime dtSatFirst = dtStart.GetNextWeekDay(wxDateTime::Sat),
4441 dtSatLast = dtEnd.GetPrevWeekDay(wxDateTime::Sat),
4442 dtSunFirst = dtStart.GetNextWeekDay(wxDateTime::Sun),
4443 dtSunLast = dtEnd.GetPrevWeekDay(wxDateTime::Sun),
4444 dt;
4445
4446 for ( dt = dtSatFirst; dt <= dtSatLast; dt += wxDateSpan::Week() )
4447 {
4448 holidays.Add(dt);
4449 }
4450
4451 for ( dt = dtSunFirst; dt <= dtSunLast; dt += wxDateSpan::Week() )
4452 {
4453 holidays.Add(dt);
4454 }
4455
4456 return holidays.GetCount();
4457}
3e0b743f 4458
26364344
WS
4459// ============================================================================
4460// other helper functions
4461// ============================================================================
4462
4463// ----------------------------------------------------------------------------
4464// iteration helpers: can be used to write a for loop over enum variable like
4465// this:
4466// for ( m = wxDateTime::Jan; m < wxDateTime::Inv_Month; wxNextMonth(m) )
4467// ----------------------------------------------------------------------------
4468
4469WXDLLIMPEXP_BASE void wxNextMonth(wxDateTime::Month& m)
4470{
4471 wxASSERT_MSG( m < wxDateTime::Inv_Month, _T("invalid month") );
4472
4473 // no wrapping or the for loop above would never end!
4474 m = (wxDateTime::Month)(m + 1);
4475}
4476
4477WXDLLIMPEXP_BASE void wxPrevMonth(wxDateTime::Month& m)
4478{
4479 wxASSERT_MSG( m < wxDateTime::Inv_Month, _T("invalid month") );
4480
4481 m = m == wxDateTime::Jan ? wxDateTime::Inv_Month
4482 : (wxDateTime::Month)(m - 1);
4483}
4484
4485WXDLLIMPEXP_BASE void wxNextWDay(wxDateTime::WeekDay& wd)
4486{
4487 wxASSERT_MSG( wd < wxDateTime::Inv_WeekDay, _T("invalid week day") );
4488
4489 // no wrapping or the for loop above would never end!
4490 wd = (wxDateTime::WeekDay)(wd + 1);
4491}
4492
4493WXDLLIMPEXP_BASE void wxPrevWDay(wxDateTime::WeekDay& wd)
4494{
4495 wxASSERT_MSG( wd < wxDateTime::Inv_WeekDay, _T("invalid week day") );
4496
4497 wd = wd == wxDateTime::Sun ? wxDateTime::Inv_WeekDay
4498 : (wxDateTime::WeekDay)(wd - 1);
4499}
4500
1e6feb95 4501#endif // wxUSE_DATETIME