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