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