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