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