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