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