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