]> git.saurik.com Git - wxWidgets.git/blame - src/common/datetimefmt.cpp
Added wxVector::swap().
[wxWidgets.git] / src / common / datetimefmt.cpp
CommitLineData
98919134
FM
1///////////////////////////////////////////////////////////////////////////////
2// Name: src/common/datetimefmt.cpp
3// Purpose: wxDateTime formatting & parsing code
4// Author: Vadim Zeitlin
5// Modified by:
6// Created: 11.05.99
7// RCS-ID: $Id$
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//
16// Licence: wxWindows licence
17///////////////////////////////////////////////////////////////////////////////
18
19// ============================================================================
20// declarations
21// ============================================================================
22
23// ----------------------------------------------------------------------------
24// headers
25// ----------------------------------------------------------------------------
26
27// For compilers that support precompilation, includes "wx.h".
28#include "wx/wxprec.h"
29
30#ifdef __BORLANDC__
31 #pragma hdrstop
32#endif
33
34#if !defined(wxUSE_DATETIME) || wxUSE_DATETIME
35
36#ifndef WX_PRECOMP
37 #ifdef __WXMSW__
38 #include "wx/msw/wrapwin.h"
39 #endif
40 #include "wx/string.h"
41 #include "wx/log.h"
42 #include "wx/intl.h"
43 #include "wx/stopwatch.h" // for wxGetLocalTimeMillis()
44 #include "wx/module.h"
45 #include "wx/crt.h"
46#endif // WX_PRECOMP
47
48#include "wx/thread.h"
98919134
FM
49
50#include <ctype.h>
51
52#ifdef __WINDOWS__
53 #include <winnls.h>
54 #ifndef __WXWINCE__
55 #include <locale.h>
56 #endif
57#endif
58
59#include "wx/datetime.h"
60
61// ============================================================================
62// implementation of wxDateTime
63// ============================================================================
64
1ee2f9d9
FM
65// ----------------------------------------------------------------------------
66// helpers shared between datetime.cpp and datetimefmt.cpp
67// ----------------------------------------------------------------------------
68
69extern void InitTm(struct tm& tm);
70
71extern int GetTimeZone();
72
73extern wxString CallStrftime(const wxString& format, const tm* tm);
74
54e660f8
FM
75// ----------------------------------------------------------------------------
76// constants (see also datetime.cpp)
77// ----------------------------------------------------------------------------
78
79static const int DAYS_PER_WEEK = 7;
80
81static const int HOURS_PER_DAY = 24;
82
83static const int SEC_PER_MIN = 60;
84
85static const int MIN_PER_HOUR = 60;
86
87// ----------------------------------------------------------------------------
88// parsing helpers
89// ----------------------------------------------------------------------------
90
66f22f4a
VZ
91namespace
92{
93
f253c22f
VZ
94// all the functions below taking non-const wxString::const_iterator p advance
95// it until the end of the match
96
97// scans all digits (but no more than len) and returns the resulting number
98bool GetNumericToken(size_t len,
99 wxString::const_iterator& p,
100 const wxString::const_iterator& end,
101 unsigned long *number)
102{
103 size_t n = 1;
104 wxString s;
105 while ( p != end && wxIsdigit(*p) )
106 {
107 s += *p++;
108
109 if ( len && ++n > len )
110 break;
111 }
112
113 return !s.empty() && s.ToULong(number);
114}
115
116// scans all alphabetic characters and returns the resulting string
117wxString
118GetAlphaToken(wxString::const_iterator& p,
119 const wxString::const_iterator& end)
120{
121 wxString s;
122 while ( p != end && wxIsalpha(*p) )
123 {
124 s += *p++;
125 }
126
127 return s;
128}
129
254696bb
VZ
130enum
131{
132 DateLang_English = 1,
133 DateLang_Local = 2
134};
135
54e660f8 136// return the month if the string is a month name or Inv_Month otherwise
254696bb
VZ
137//
138// flags can contain wxDateTime::Name_Abbr/Name_Full or both of them and lang
139// can be either DateLang_Local (default) to interpret string as a localized
140// month name or DateLang_English to parse it as a standard English name or
141// their combination to interpret it in any way
142wxDateTime::Month
f253c22f
VZ
143GetMonthFromName(wxString::const_iterator& p,
144 const wxString::const_iterator& end,
145 int flags,
146 int lang)
54e660f8 147{
f253c22f
VZ
148 const wxString::const_iterator pOrig = p;
149 const wxString name = GetAlphaToken(p, end);
150 if ( name.empty() )
151 return wxDateTime::Inv_Month;
152
54e660f8
FM
153 wxDateTime::Month mon;
154 for ( mon = wxDateTime::Jan; mon < wxDateTime::Inv_Month; wxNextMonth(mon) )
155 {
156 // case-insensitive comparison either one of or with both abbreviated
157 // and not versions
158 if ( flags & wxDateTime::Name_Full )
159 {
254696bb 160 if ( lang & DateLang_English )
54e660f8 161 {
254696bb
VZ
162 if ( name.CmpNoCase(wxDateTime::GetEnglishMonthName(mon,
163 wxDateTime::Name_Full)) == 0 )
164 break;
165 }
166
167 if ( lang & DateLang_Local )
168 {
169 if ( name.CmpNoCase(wxDateTime::GetMonthName(mon,
170 wxDateTime::Name_Full)) == 0 )
171 break;
54e660f8
FM
172 }
173 }
174
175 if ( flags & wxDateTime::Name_Abbr )
176 {
254696bb 177 if ( lang & DateLang_English )
54e660f8 178 {
254696bb
VZ
179 if ( name.CmpNoCase(wxDateTime::GetEnglishMonthName(mon,
180 wxDateTime::Name_Abbr)) == 0 )
181 break;
182 }
183
184 if ( lang & DateLang_Local )
185 {
f253c22f
VZ
186 // some locales (e.g. French one) use periods for the
187 // abbreviated month names but it's never part of name so
188 // compare it specially
189 wxString nameAbbr = wxDateTime::GetMonthName(mon,
190 wxDateTime::Name_Abbr);
191 const bool hasPeriod = *nameAbbr.rbegin() == '.';
192 if ( hasPeriod )
193 nameAbbr.erase(nameAbbr.end() - 1);
194
195 if ( name.CmpNoCase(nameAbbr) == 0 )
196 {
197 if ( hasPeriod )
198 {
199 // skip trailing period if it was part of the match
200 if ( *p == '.' )
201 ++p;
202 else // no match as no matching period
203 continue;
204 }
205
254696bb 206 break;
f253c22f 207 }
54e660f8
FM
208 }
209 }
210 }
211
f253c22f
VZ
212 if ( mon == wxDateTime::Inv_Month )
213 p = pOrig;
214
54e660f8
FM
215 return mon;
216}
217
218// return the weekday if the string is a weekday name or Inv_WeekDay otherwise
254696bb
VZ
219//
220// flags and lang parameters have the same meaning as for GetMonthFromName()
221// above
222wxDateTime::WeekDay
f253c22f
VZ
223GetWeekDayFromName(wxString::const_iterator& p,
224 const wxString::const_iterator& end,
225 int flags, int lang)
54e660f8 226{
f253c22f
VZ
227 const wxString::const_iterator pOrig = p;
228 const wxString name = GetAlphaToken(p, end);
229 if ( name.empty() )
230 return wxDateTime::Inv_WeekDay;
231
54e660f8
FM
232 wxDateTime::WeekDay wd;
233 for ( wd = wxDateTime::Sun; wd < wxDateTime::Inv_WeekDay; wxNextWDay(wd) )
234 {
54e660f8
FM
235 if ( flags & wxDateTime::Name_Full )
236 {
254696bb 237 if ( lang & DateLang_English )
54e660f8 238 {
254696bb
VZ
239 if ( name.CmpNoCase(wxDateTime::GetEnglishWeekDayName(wd,
240 wxDateTime::Name_Full)) == 0 )
241 break;
242 }
243
244 if ( lang & DateLang_Local )
245 {
246 if ( name.CmpNoCase(wxDateTime::GetWeekDayName(wd,
247 wxDateTime::Name_Full)) == 0 )
248 break;
54e660f8
FM
249 }
250 }
251
252 if ( flags & wxDateTime::Name_Abbr )
253 {
254696bb 254 if ( lang & DateLang_English )
54e660f8 255 {
254696bb
VZ
256 if ( name.CmpNoCase(wxDateTime::GetEnglishWeekDayName(wd,
257 wxDateTime::Name_Abbr)) == 0 )
258 break;
259 }
260
261 if ( lang & DateLang_Local )
262 {
263 if ( name.CmpNoCase(wxDateTime::GetWeekDayName(wd,
264 wxDateTime::Name_Abbr)) == 0 )
265 break;
54e660f8
FM
266 }
267 }
268 }
269
f253c22f
VZ
270 if ( wd == wxDateTime::Inv_WeekDay )
271 p = pOrig;
54e660f8 272
f253c22f 273 return wd;
6b26ab96
VZ
274}
275
66f22f4a
VZ
276// parses string starting at given iterator using the specified format and,
277// optionally, a fall back format (and optionally another one... but it stops
278// there, really)
279//
280// if unsuccessful, returns invalid wxDateTime without changing p; otherwise
281// advance p to the end of the match and returns wxDateTime containing the
282// results of the parsing
283wxDateTime
284ParseFormatAt(wxString::const_iterator& p,
285 const wxString::const_iterator& end,
286 const wxString& fmt,
115eded7
VZ
287 // FIXME-VC6: using wxString() instead of wxEmptyString in the
288 // line below results in error C2062: type 'class
289 // wxString (__cdecl *)(void)' unexpected
89a7e1ff 290 const wxString& fmtAlt = wxEmptyString)
66f22f4a
VZ
291{
292 const wxString str(p, end);
293 wxString::const_iterator endParse;
294 wxDateTime dt;
295 if ( dt.ParseFormat(str, fmt, &endParse) ||
89a7e1ff 296 (!fmtAlt.empty() && dt.ParseFormat(str, fmtAlt, &endParse)) )
66f22f4a
VZ
297 {
298 p += endParse - str.begin();
299 }
300 //else: all formats failed
301
302 return dt;
303}
304
305} // anonymous namespace
306
98919134
FM
307// ----------------------------------------------------------------------------
308// wxDateTime to/from text representations
309// ----------------------------------------------------------------------------
310
911907f4 311wxString wxDateTime::Format(const wxString& formatp, const TimeZone& tz) const
98919134 312{
911907f4 313 wxCHECK_MSG( !formatp.empty(), wxEmptyString,
98919134
FM
314 _T("NULL format in wxDateTime::Format") );
315
911907f4
SC
316 wxString format = formatp;
317#ifdef __WXOSX__
318 format.Replace("%c",wxLocale::GetInfo(wxLOCALE_DATE_TIME_FMT));
319 format.Replace("%x",wxLocale::GetInfo(wxLOCALE_SHORT_DATE_FMT));
320 format.Replace("%X",wxLocale::GetInfo(wxLOCALE_TIME_FMT));
321#endif
98919134
FM
322 // we have to use our own implementation if the date is out of range of
323 // strftime() or if we use non standard specificators
01b56a96 324#ifdef wxHAS_STRFTIME
98919134
FM
325 time_t time = GetTicks();
326
327 if ( (time != (time_t)-1) && !wxStrstr(format, _T("%l")) )
328 {
329 // use strftime()
330 struct tm tmstruct;
331 struct tm *tm;
332 if ( tz.GetOffset() == -GetTimeZone() )
333 {
334 // we are working with local time
335 tm = wxLocaltime_r(&time, &tmstruct);
336
337 // should never happen
338 wxCHECK_MSG( tm, wxEmptyString, _T("wxLocaltime_r() failed") );
339 }
340 else
341 {
342 time += (int)tz.GetOffset();
343
344#if defined(__VMS__) || defined(__WATCOMC__) // time is unsigned so avoid warning
345 int time2 = (int) time;
346 if ( time2 >= 0 )
347#else
348 if ( time >= 0 )
349#endif
350 {
351 tm = wxGmtime_r(&time, &tmstruct);
352
353 // should never happen
354 wxCHECK_MSG( tm, wxEmptyString, _T("wxGmtime_r() failed") );
355 }
356 else
357 {
358 tm = (struct tm *)NULL;
359 }
360 }
361
362 if ( tm )
363 {
364 return CallStrftime(format, tm);
365 }
366 }
367 //else: use generic code below
01b56a96 368#endif // wxHAS_STRFTIME
98919134
FM
369
370 // we only parse ANSI C format specifications here, no POSIX 2
371 // complications, no GNU extensions but we do add support for a "%l" format
372 // specifier allowing to get the number of milliseconds
373 Tm tm = GetTm(tz);
374
375 // used for calls to strftime() when we only deal with time
376 struct tm tmTimeOnly;
377 tmTimeOnly.tm_hour = tm.hour;
378 tmTimeOnly.tm_min = tm.min;
379 tmTimeOnly.tm_sec = tm.sec;
380 tmTimeOnly.tm_wday = 0;
381 tmTimeOnly.tm_yday = 0;
382 tmTimeOnly.tm_mday = 1; // any date will do
383 tmTimeOnly.tm_mon = 0;
384 tmTimeOnly.tm_year = 76;
385 tmTimeOnly.tm_isdst = 0; // no DST, we adjust for tz ourselves
386
387 wxString tmp, res, fmt;
388 for ( wxString::const_iterator p = format.begin(); p != format.end(); ++p )
389 {
390 if ( *p != _T('%') )
391 {
392 // copy as is
393 res += *p;
394
395 continue;
396 }
397
398 // set the default format
399 switch ( (*++p).GetValue() )
400 {
401 case _T('Y'): // year has 4 digits
402 fmt = _T("%04d");
403 break;
404
405 case _T('j'): // day of year has 3 digits
406 case _T('l'): // milliseconds have 3 digits
407 fmt = _T("%03d");
408 break;
409
410 case _T('w'): // week day as number has only one
411 fmt = _T("%d");
412 break;
413
414 default:
415 // it's either another valid format specifier in which case
416 // the format is "%02d" (for all the rest) or we have the
417 // field width preceding the format in which case it will
418 // override the default format anyhow
419 fmt = _T("%02d");
420 }
421
422 bool restart = true;
423 while ( restart )
424 {
425 restart = false;
426
427 // start of the format specification
428 switch ( (*p).GetValue() )
429 {
430 case _T('a'): // a weekday name
431 case _T('A'):
432 // second parameter should be true for abbreviated names
433 res += GetWeekDayName(tm.GetWeekDay(),
434 *p == _T('a') ? Name_Abbr : Name_Full);
435 break;
436
437 case _T('b'): // a month name
438 case _T('B'):
439 res += GetMonthName(tm.mon,
440 *p == _T('b') ? Name_Abbr : Name_Full);
441 break;
442
443 case _T('c'): // locale default date and time representation
444 case _T('x'): // locale default date representation
01b56a96 445#ifdef wxHAS_STRFTIME
98919134
FM
446 //
447 // the problem: there is no way to know what do these format
448 // specifications correspond to for the current locale.
449 //
450 // the solution: use a hack and still use strftime(): first
451 // find the YEAR which is a year in the strftime() range (1970
452 // - 2038) whose Jan 1 falls on the same week day as the Jan 1
453 // of the real year. Then make a copy of the format and
454 // replace all occurrences of YEAR in it with some unique
455 // string not appearing anywhere else in it, then use
456 // strftime() to format the date in year YEAR and then replace
457 // YEAR back by the real year and the unique replacement
458 // string back with YEAR. Notice that "all occurrences of YEAR"
459 // means all occurrences of 4 digit as well as 2 digit form!
460 //
461 // the bugs: we assume that neither of %c nor %x contains any
462 // fields which may change between the YEAR and real year. For
463 // example, the week number (%U, %W) and the day number (%j)
464 // will change if one of these years is leap and the other one
465 // is not!
466 {
467 // find the YEAR: normally, for any year X, Jan 1 of the
468 // year X + 28 is the same weekday as Jan 1 of X (because
469 // the weekday advances by 1 for each normal X and by 2
470 // for each leap X, hence by 5 every 4 years or by 35
471 // which is 0 mod 7 every 28 years) but this rule breaks
472 // down if there are years between X and Y which are
473 // divisible by 4 but not leap (i.e. divisible by 100 but
474 // not 400), hence the correction.
475
476 int yearReal = GetYear(tz);
477 int mod28 = yearReal % 28;
478
479 // be careful to not go too far - we risk to leave the
480 // supported range
481 int year;
482 if ( mod28 < 10 )
483 {
484 year = 1988 + mod28; // 1988 == 0 (mod 28)
485 }
486 else
487 {
488 year = 1970 + mod28 - 10; // 1970 == 10 (mod 28)
489 }
490
491 int nCentury = year / 100,
492 nCenturyReal = yearReal / 100;
493
494 // need to adjust for the years divisble by 400 which are
495 // not leap but are counted like leap ones if we just take
496 // the number of centuries in between for nLostWeekDays
497 int nLostWeekDays = (nCentury - nCenturyReal) -
498 (nCentury / 4 - nCenturyReal / 4);
499
500 // we have to gain back the "lost" weekdays: note that the
501 // effect of this loop is to not do anything to
502 // nLostWeekDays (which we won't use any more), but to
503 // (indirectly) set the year correctly
504 while ( (nLostWeekDays % 7) != 0 )
505 {
506 nLostWeekDays += year++ % 4 ? 1 : 2;
507 }
508
509 // finally move the year below 2000 so that the 2-digit
510 // year number can never match the month or day of the
511 // month when we do the replacements below
512 if ( year >= 2000 )
513 year -= 28;
514
515 wxASSERT_MSG( year >= 1970 && year < 2000,
516 _T("logic error in wxDateTime::Format") );
517
518
519 // use strftime() to format the same date but in supported
520 // year
521 //
522 // NB: we assume that strftime() doesn't check for the
523 // date validity and will happily format the date
524 // corresponding to Feb 29 of a non leap year (which
525 // may happen if yearReal was leap and year is not)
526 struct tm tmAdjusted;
527 InitTm(tmAdjusted);
528 tmAdjusted.tm_hour = tm.hour;
529 tmAdjusted.tm_min = tm.min;
530 tmAdjusted.tm_sec = tm.sec;
531 tmAdjusted.tm_wday = tm.GetWeekDay();
532 tmAdjusted.tm_yday = GetDayOfYear();
533 tmAdjusted.tm_mday = tm.mday;
534 tmAdjusted.tm_mon = tm.mon;
535 tmAdjusted.tm_year = year - 1900;
536 tmAdjusted.tm_isdst = 0; // no DST, already adjusted
537 wxString str = CallStrftime(*p == _T('c') ? _T("%c")
538 : _T("%x"),
539 &tmAdjusted);
540
541 // now replace the replacement year with the real year:
542 // notice that we have to replace the 4 digit year with
543 // a unique string not appearing in strftime() output
544 // first to prevent the 2 digit year from matching any
545 // substring of the 4 digit year (but any day, month,
546 // hours or minutes components should be safe because
547 // they are never in 70-99 range)
548 wxString replacement("|");
549 while ( str.find(replacement) != wxString::npos )
550 replacement += '|';
551
552 str.Replace(wxString::Format("%d", year),
553 replacement);
554 str.Replace(wxString::Format("%d", year % 100),
555 wxString::Format("%d", yearReal % 100));
556 str.Replace(replacement,
557 wxString::Format("%d", yearReal));
558
559 res += str;
560 }
01b56a96 561#else // !wxHAS_STRFTIME
98919134
FM
562 // Use "%m/%d/%y %H:%M:%S" format instead
563 res += wxString::Format(wxT("%02d/%02d/%04d %02d:%02d:%02d"),
564 tm.mon+1,tm.mday, tm.year, tm.hour, tm.min, tm.sec);
01b56a96 565#endif // wxHAS_STRFTIME/!wxHAS_STRFTIME
98919134
FM
566 break;
567
568 case _T('d'): // day of a month (01-31)
569 res += wxString::Format(fmt, tm.mday);
570 break;
571
572 case _T('H'): // hour in 24h format (00-23)
573 res += wxString::Format(fmt, tm.hour);
574 break;
575
576 case _T('I'): // hour in 12h format (01-12)
577 {
578 // 24h -> 12h, 0h -> 12h too
579 int hour12 = tm.hour > 12 ? tm.hour - 12
580 : tm.hour ? tm.hour : 12;
581 res += wxString::Format(fmt, hour12);
582 }
583 break;
584
585 case _T('j'): // day of the year
586 res += wxString::Format(fmt, GetDayOfYear(tz));
587 break;
588
589 case _T('l'): // milliseconds (NOT STANDARD)
590 res += wxString::Format(fmt, GetMillisecond(tz));
591 break;
592
593 case _T('m'): // month as a number (01-12)
594 res += wxString::Format(fmt, tm.mon + 1);
595 break;
596
597 case _T('M'): // minute as a decimal number (00-59)
598 res += wxString::Format(fmt, tm.min);
599 break;
600
601 case _T('p'): // AM or PM string
01b56a96 602#ifdef wxHAS_STRFTIME
98919134 603 res += CallStrftime(_T("%p"), &tmTimeOnly);
01b56a96 604#else // !wxHAS_STRFTIME
98919134 605 res += (tmTimeOnly.tm_hour > 12) ? wxT("pm") : wxT("am");
01b56a96 606#endif // wxHAS_STRFTIME/!wxHAS_STRFTIME
98919134
FM
607 break;
608
609 case _T('S'): // second as a decimal number (00-61)
610 res += wxString::Format(fmt, tm.sec);
611 break;
612
613 case _T('U'): // week number in the year (Sunday 1st week day)
614 res += wxString::Format(fmt, GetWeekOfYear(Sunday_First, tz));
615 break;
616
617 case _T('W'): // week number in the year (Monday 1st week day)
618 res += wxString::Format(fmt, GetWeekOfYear(Monday_First, tz));
619 break;
620
621 case _T('w'): // weekday as a number (0-6), Sunday = 0
622 res += wxString::Format(fmt, tm.GetWeekDay());
623 break;
624
625 // case _T('x'): -- handled with "%c"
626
627 case _T('X'): // locale default time representation
628 // just use strftime() to format the time for us
01b56a96 629#ifdef wxHAS_STRFTIME
98919134 630 res += CallStrftime(_T("%X"), &tmTimeOnly);
01b56a96 631#else // !wxHAS_STRFTIME
98919134 632 res += wxString::Format(wxT("%02d:%02d:%02d"),tm.hour, tm.min, tm.sec);
01b56a96 633#endif // wxHAS_STRFTIME/!wxHAS_STRFTIME
98919134
FM
634 break;
635
636 case _T('y'): // year without century (00-99)
637 res += wxString::Format(fmt, tm.year % 100);
638 break;
639
640 case _T('Y'): // year with century
641 res += wxString::Format(fmt, tm.year);
642 break;
643
644 case _T('Z'): // timezone name
01b56a96 645#ifdef wxHAS_STRFTIME
98919134
FM
646 res += CallStrftime(_T("%Z"), &tmTimeOnly);
647#endif
648 break;
649
650 default:
651 // is it the format width?
652 fmt.Empty();
653 while ( *p == _T('-') || *p == _T('+') ||
654 *p == _T(' ') || wxIsdigit(*p) )
655 {
656 fmt += *p;
657 }
658
659 if ( !fmt.empty() )
660 {
661 // we've only got the flags and width so far in fmt
662 fmt.Prepend(_T('%'));
663 fmt.Append(_T('d'));
664
665 restart = true;
666
667 break;
668 }
669
670 // no, it wasn't the width
671 wxFAIL_MSG(_T("unknown format specificator"));
672
673 // fall through and just copy it nevertheless
674
675 case _T('%'): // a percent sign
676 res += *p;
677 break;
678
679 case 0: // the end of string
680 wxFAIL_MSG(_T("missing format at the end of string"));
681
682 // just put the '%' which was the last char in format
683 res += _T('%');
684 break;
685 }
686 }
687 }
688
689 return res;
690}
691
692// this function parses a string in (strict) RFC 822 format: see the section 5
693// of the RFC for the detailed description, but briefly it's something of the
694// form "Sat, 18 Dec 1999 00:48:30 +0100"
695//
696// this function is "strict" by design - it must reject anything except true
697// RFC822 time specs.
c398434d 698bool
98919134
FM
699wxDateTime::ParseRfc822Date(const wxString& date, wxString::const_iterator *end)
700{
f253c22f 701 const wxString::const_iterator pEnd = date.end();
254696bb 702 wxString::const_iterator p = date.begin();
98919134 703
254696bb 704 // 1. week day
f253c22f
VZ
705 const wxDateTime::WeekDay
706 wd = GetWeekDayFromName(p, pEnd, Name_Abbr, DateLang_English);
707 if ( wd == Inv_WeekDay )
c398434d 708 return false;
254696bb
VZ
709 //else: ignore week day for now, we could also check that it really
710 // corresponds to the specified date
98919134 711
254696bb
VZ
712 // 2. separating comma
713 if ( *p++ != ',' || *p++ != ' ' )
c398434d 714 return false;
98919134 715
254696bb 716 // 3. day number
98919134 717 if ( !wxIsdigit(*p) )
c398434d 718 return false;
98919134 719
254696bb 720 wxDateTime_t day = (wxDateTime_t)(*p++ - '0');
98919134
FM
721 if ( wxIsdigit(*p) )
722 {
723 day *= 10;
254696bb 724 day = (wxDateTime_t)(day + (*p++ - '0'));
98919134
FM
725 }
726
254696bb 727 if ( *p++ != ' ' )
c398434d 728 return false;
98919134 729
254696bb 730 // 4. month name
f253c22f 731 const Month mon = GetMonthFromName(p, pEnd, Name_Abbr, DateLang_English);
254696bb 732 if ( mon == Inv_Month )
c398434d 733 return false;
98919134 734
254696bb 735 if ( *p++ != ' ' )
c398434d 736 return false;
98919134 737
254696bb 738 // 5. year
98919134 739 if ( !wxIsdigit(*p) )
c398434d 740 return false;
98919134 741
254696bb
VZ
742 int year = *p++ - '0';
743 if ( !wxIsdigit(*p) ) // should have at least 2 digits in the year
c398434d 744 return false;
98919134
FM
745
746 year *= 10;
254696bb 747 year += *p++ - '0';
98919134
FM
748
749 // is it a 2 digit year (as per original RFC 822) or a 4 digit one?
750 if ( wxIsdigit(*p) )
751 {
752 year *= 10;
254696bb 753 year += *p++ - '0';
98919134
FM
754
755 if ( !wxIsdigit(*p) )
756 {
757 // no 3 digit years please
c398434d 758 return false;
98919134
FM
759 }
760
761 year *= 10;
254696bb 762 year += *p++ - '0';
98919134
FM
763 }
764
254696bb 765 if ( *p++ != ' ' )
c398434d 766 return false;
98919134 767
254696bb 768 // 6. time in hh:mm:ss format with seconds being optional
98919134 769 if ( !wxIsdigit(*p) )
c398434d 770 return false;
98919134 771
254696bb 772 wxDateTime_t hour = (wxDateTime_t)(*p++ - '0');
98919134
FM
773
774 if ( !wxIsdigit(*p) )
c398434d 775 return false;
98919134
FM
776
777 hour *= 10;
254696bb 778 hour = (wxDateTime_t)(hour + (*p++ - '0'));
98919134 779
254696bb 780 if ( *p++ != ':' )
c398434d 781 return false;
98919134
FM
782
783 if ( !wxIsdigit(*p) )
c398434d 784 return false;
98919134 785
254696bb 786 wxDateTime_t min = (wxDateTime_t)(*p++ - '0');
98919134
FM
787
788 if ( !wxIsdigit(*p) )
c398434d 789 return false;
98919134
FM
790
791 min *= 10;
254696bb 792 min += (wxDateTime_t)(*p++ - '0');
98919134
FM
793
794 wxDateTime_t sec = 0;
254696bb 795 if ( *p == ':' )
98919134
FM
796 {
797 p++;
798 if ( !wxIsdigit(*p) )
c398434d 799 return false;
98919134 800
254696bb 801 sec = (wxDateTime_t)(*p++ - '0');
98919134
FM
802
803 if ( !wxIsdigit(*p) )
c398434d 804 return false;
98919134
FM
805
806 sec *= 10;
254696bb 807 sec += (wxDateTime_t)(*p++ - '0');
98919134
FM
808 }
809
254696bb 810 if ( *p++ != ' ' )
c398434d 811 return false;
98919134 812
254696bb 813 // 7. now the interesting part: the timezone
5ec58699 814 int offset = 0; // just to suppress warnings
254696bb 815 if ( *p == '-' || *p == '+' )
98919134
FM
816 {
817 // the explicit offset given: it has the form of hhmm
254696bb 818 bool plus = *p++ == '+';
98919134
FM
819
820 if ( !wxIsdigit(*p) || !wxIsdigit(*(p + 1)) )
c398434d 821 return false;
254696bb 822
98919134
FM
823
824 // hours
254696bb 825 offset = MIN_PER_HOUR*(10*(*p - '0') + (*(p + 1) - '0'));
98919134
FM
826
827 p += 2;
828
829 if ( !wxIsdigit(*p) || !wxIsdigit(*(p + 1)) )
c398434d 830 return false;
98919134
FM
831
832 // minutes
254696bb 833 offset += 10*(*p - '0') + (*(p + 1) - '0');
98919134
FM
834
835 if ( !plus )
98919134 836 offset = -offset;
98919134
FM
837
838 p += 2;
839 }
254696bb 840 else // not numeric
98919134
FM
841 {
842 // the symbolic timezone given: may be either military timezone or one
843 // of standard abbreviations
844 if ( !*(p + 1) )
845 {
846 // military: Z = UTC, J unused, A = -1, ..., Y = +12
847 static const int offsets[26] =
848 {
849 //A B C D E F G H I J K L M
850 -1, -2, -3, -4, -5, -6, -7, -8, -9, 0, -10, -11, -12,
851 //N O P R Q S T U V W Z Y Z
852 +1, +2, +3, +4, +5, +6, +7, +8, +9, +10, +11, +12, 0
853 };
854
855 if ( *p < _T('A') || *p > _T('Z') || *p == _T('J') )
c398434d 856 return false;
98919134 857
254696bb 858 offset = offsets[*p++ - 'A'];
98919134
FM
859 }
860 else
861 {
862 // abbreviation
254696bb 863 const wxString tz(p, date.end());
98919134
FM
864 if ( tz == _T("UT") || tz == _T("UTC") || tz == _T("GMT") )
865 offset = 0;
866 else if ( tz == _T("AST") )
867 offset = AST - GMT0;
868 else if ( tz == _T("ADT") )
869 offset = ADT - GMT0;
870 else if ( tz == _T("EST") )
871 offset = EST - GMT0;
872 else if ( tz == _T("EDT") )
873 offset = EDT - GMT0;
874 else if ( tz == _T("CST") )
875 offset = CST - GMT0;
876 else if ( tz == _T("CDT") )
877 offset = CDT - GMT0;
878 else if ( tz == _T("MST") )
879 offset = MST - GMT0;
880 else if ( tz == _T("MDT") )
881 offset = MDT - GMT0;
882 else if ( tz == _T("PST") )
883 offset = PST - GMT0;
884 else if ( tz == _T("PDT") )
885 offset = PDT - GMT0;
886 else
c398434d 887 return false;
98919134
FM
888
889 p += tz.length();
890 }
891
892 // make it minutes
893 offset *= MIN_PER_HOUR;
894 }
895
254696bb 896
98919134
FM
897 // the spec was correct, construct the date from the values we found
898 Set(day, mon, year, hour, min, sec);
899 MakeFromTimezone(TimeZone::Make(offset*SEC_PER_MIN));
900
98919134 901 if ( end )
254696bb 902 *end = p;
98919134 903
c398434d 904 return true;
98919134
FM
905}
906
c398434d 907bool
98919134
FM
908wxDateTime::ParseFormat(const wxString& date,
909 const wxString& format,
910 const wxDateTime& dateDef,
ed973feb 911 wxString::const_iterator *endParse)
98919134 912{
c398434d 913 wxCHECK_MSG( !format.empty(), false, "format can't be empty" );
ed973feb 914 wxCHECK_MSG( endParse, false, "end iterator pointer must be specified" );
98919134
FM
915
916 wxString str;
917 unsigned long num;
918
919 // what fields have we found?
920 bool haveWDay = false,
921 haveYDay = false,
922 haveDay = false,
923 haveMon = false,
924 haveYear = false,
925 haveHour = false,
926 haveMin = false,
927 haveSec = false,
928 haveMsec = false;
929
930 bool hourIsIn12hFormat = false, // or in 24h one?
931 isPM = false; // AM by default
932
933 // and the value of the items we have (init them to get rid of warnings)
934 wxDateTime_t msec = 0,
935 sec = 0,
936 min = 0,
937 hour = 0;
938 WeekDay wday = Inv_WeekDay;
939 wxDateTime_t yday = 0,
940 mday = 0;
941 wxDateTime::Month mon = Inv_Month;
942 int year = 0;
943
66f22f4a 944 wxString::const_iterator input = date.begin();
ed973feb 945 const wxString::const_iterator end = date.end();
98919134
FM
946 for ( wxString::const_iterator fmt = format.begin(); fmt != format.end(); ++fmt )
947 {
948 if ( *fmt != _T('%') )
949 {
950 if ( wxIsspace(*fmt) )
951 {
952 // a white space in the format string matches 0 or more white
953 // spaces in the input
694cb6c5 954 while ( input != end && wxIsspace(*input) )
98919134
FM
955 {
956 input++;
957 }
958 }
959 else // !space
960 {
961 // any other character (not whitespace, not '%') must be
962 // matched by itself in the input
694cb6c5 963 if ( input == end || *input++ != *fmt )
98919134
FM
964 {
965 // no match
c398434d 966 return false;
98919134
FM
967 }
968 }
969
970 // done with this format char
971 continue;
972 }
973
974 // start of a format specification
975
976 // parse the optional width
977 size_t width = 0;
978 while ( wxIsdigit(*++fmt) )
979 {
980 width *= 10;
254696bb 981 width += *fmt - '0';
98919134
FM
982 }
983
984 // the default widths for the various fields
985 if ( !width )
986 {
987 switch ( (*fmt).GetValue() )
988 {
989 case _T('Y'): // year has 4 digits
990 width = 4;
991 break;
992
993 case _T('j'): // day of year has 3 digits
994 case _T('l'): // milliseconds have 3 digits
995 width = 3;
996 break;
997
998 case _T('w'): // week day as number has only one
999 width = 1;
1000 break;
1001
1002 default:
1003 // default for all other fields
1004 width = 2;
1005 }
1006 }
1007
1008 // then the format itself
1009 switch ( (*fmt).GetValue() )
1010 {
1011 case _T('a'): // a weekday name
1012 case _T('A'):
1013 {
254696bb
VZ
1014 wday = GetWeekDayFromName
1015 (
f253c22f 1016 input, end,
254696bb
VZ
1017 *fmt == 'a' ? Name_Abbr : Name_Full,
1018 DateLang_Local
1019 );
98919134
FM
1020 if ( wday == Inv_WeekDay )
1021 {
1022 // no match
c398434d 1023 return false;
98919134
FM
1024 }
1025 }
1026 haveWDay = true;
1027 break;
1028
1029 case _T('b'): // a month name
1030 case _T('B'):
1031 {
254696bb
VZ
1032 mon = GetMonthFromName
1033 (
f253c22f 1034 input, end,
254696bb
VZ
1035 *fmt == 'b' ? Name_Abbr : Name_Full,
1036 DateLang_Local
1037 );
98919134
FM
1038 if ( mon == Inv_Month )
1039 {
1040 // no match
c398434d 1041 return false;
98919134
FM
1042 }
1043 }
1044 haveMon = true;
1045 break;
1046
1047 case _T('c'): // locale default date and time representation
1048 {
89a7e1ff 1049 wxDateTime dt;
48fd6e9d 1050
89a7e1ff
VZ
1051 const wxString
1052 fmtDateTime = wxLocale::GetInfo(wxLOCALE_DATE_TIME_FMT);
1053 if ( !fmtDateTime.empty() )
1054 dt = ParseFormatAt(input, end, fmtDateTime);
cac1cfce 1055
89a7e1ff
VZ
1056 if ( !dt.IsValid() )
1057 {
1058 // also try the format which corresponds to ctime()
1059 // output (i.e. the "C" locale default)
1060 dt = ParseFormatAt(input, end, wxS("%a %b %d %H:%M:%S %Y"));
cac1cfce 1061 }
89a7e1ff
VZ
1062
1063 if ( !dt.IsValid() )
66f22f4a 1064 {
89a7e1ff
VZ
1065 // and finally also the two generic date/time formats
1066 dt = ParseFormatAt(input, end, wxS("%x %X"), wxS("%X %x"));
1067 }
55fffc34 1068
89a7e1ff
VZ
1069 if ( !dt.IsValid() )
1070 return false;
98919134 1071
89a7e1ff 1072 const Tm tm = dt.GetTm();
cac1cfce 1073
89a7e1ff
VZ
1074 hour = tm.hour;
1075 min = tm.min;
1076 sec = tm.sec;
cac1cfce 1077
89a7e1ff
VZ
1078 year = tm.year;
1079 mon = tm.mon;
1080 mday = tm.mday;
66f22f4a
VZ
1081
1082 haveDay = haveMon = haveYear =
1083 haveHour = haveMin = haveSec = true;
98919134
FM
1084 }
1085 break;
1086
1087 case _T('d'): // day of a month (01-31)
ed973feb 1088 if ( !GetNumericToken(width, input, end, &num) ||
98919134
FM
1089 (num > 31) || (num < 1) )
1090 {
1091 // no match
c398434d 1092 return false;
98919134
FM
1093 }
1094
1095 // we can't check whether the day range is correct yet, will
1096 // do it later - assume ok for now
1097 haveDay = true;
1098 mday = (wxDateTime_t)num;
1099 break;
1100
1101 case _T('H'): // hour in 24h format (00-23)
ed973feb 1102 if ( !GetNumericToken(width, input, end, &num) || (num > 23) )
98919134
FM
1103 {
1104 // no match
c398434d 1105 return false;
98919134
FM
1106 }
1107
1108 haveHour = true;
1109 hour = (wxDateTime_t)num;
1110 break;
1111
1112 case _T('I'): // hour in 12h format (01-12)
ed973feb
VZ
1113 if ( !GetNumericToken(width, input, end, &num) ||
1114 !num || (num > 12) )
98919134
FM
1115 {
1116 // no match
c398434d 1117 return false;
98919134
FM
1118 }
1119
1120 haveHour = true;
1121 hourIsIn12hFormat = true;
1122 hour = (wxDateTime_t)(num % 12); // 12 should be 0
1123 break;
1124
1125 case _T('j'): // day of the year
ed973feb
VZ
1126 if ( !GetNumericToken(width, input, end, &num) ||
1127 !num || (num > 366) )
98919134
FM
1128 {
1129 // no match
c398434d 1130 return false;
98919134
FM
1131 }
1132
1133 haveYDay = true;
1134 yday = (wxDateTime_t)num;
1135 break;
1136
1137 case _T('l'): // milliseconds (0-999)
ed973feb 1138 if ( !GetNumericToken(width, input, end, &num) )
c398434d 1139 return false;
98919134
FM
1140
1141 haveMsec = true;
1142 msec = (wxDateTime_t)num;
1143 break;
1144
1145 case _T('m'): // month as a number (01-12)
ed973feb
VZ
1146 if ( !GetNumericToken(width, input, end, &num) ||
1147 !num || (num > 12) )
98919134
FM
1148 {
1149 // no match
c398434d 1150 return false;
98919134
FM
1151 }
1152
1153 haveMon = true;
1154 mon = (Month)(num - 1);
1155 break;
1156
1157 case _T('M'): // minute as a decimal number (00-59)
ed973feb
VZ
1158 if ( !GetNumericToken(width, input, end, &num) ||
1159 (num > 59) )
98919134
FM
1160 {
1161 // no match
c398434d 1162 return false;
98919134
FM
1163 }
1164
1165 haveMin = true;
1166 min = (wxDateTime_t)num;
1167 break;
1168
1169 case _T('p'): // AM or PM string
1170 {
79c4aa38
VZ
1171 wxString am, pm;
1172 GetAmPmStrings(&am, &pm);
66f22f4a 1173
79c4aa38
VZ
1174 // we can never match %p in locales which don't use AM/PM
1175 if ( am.empty() || pm.empty() )
1176 return false;
98919134 1177
79c4aa38
VZ
1178 const size_t pos = input - date.begin();
1179 if ( date.compare(pos, pm.length(), pm) == 0 )
98919134
FM
1180 {
1181 isPM = true;
79c4aa38 1182 input += pm.length();
98919134 1183 }
79c4aa38
VZ
1184 else if ( date.compare(pos, am.length(), am) == 0 )
1185 {
1186 input += am.length();
1187 }
1188 else // no match
98919134 1189 {
c398434d 1190 return false;
98919134
FM
1191 }
1192 }
1193 break;
1194
1195 case _T('r'): // time as %I:%M:%S %p
1196 {
1197 wxDateTime dt;
ed973feb 1198 if ( !dt.ParseFormat(wxString(input, end),
66f22f4a 1199 wxS("%I:%M:%S %p"), &input) )
c398434d 1200 return false;
98919134
FM
1201
1202 haveHour = haveMin = haveSec = true;
1203
89a7e1ff 1204 const Tm tm = dt.GetTm();
98919134
FM
1205 hour = tm.hour;
1206 min = tm.min;
1207 sec = tm.sec;
1208 }
1209 break;
1210
1211 case _T('R'): // time as %H:%M
1212 {
66f22f4a 1213 const wxDateTime
ed973feb 1214 dt = ParseFormatAt(input, end, wxS("%H:%M"));
66f22f4a 1215 if ( !dt.IsValid() )
c398434d 1216 return false;
98919134 1217
66f22f4a
VZ
1218 haveHour =
1219 haveMin = true;
98919134 1220
89a7e1ff 1221 const Tm tm = dt.GetTm();
98919134
FM
1222 hour = tm.hour;
1223 min = tm.min;
1224 }
1225 break;
1226
1227 case _T('S'): // second as a decimal number (00-61)
ed973feb
VZ
1228 if ( !GetNumericToken(width, input, end, &num) ||
1229 (num > 61) )
98919134
FM
1230 {
1231 // no match
c398434d 1232 return false;
98919134
FM
1233 }
1234
1235 haveSec = true;
1236 sec = (wxDateTime_t)num;
1237 break;
1238
1239 case _T('T'): // time as %H:%M:%S
1240 {
66f22f4a 1241 const wxDateTime
ed973feb 1242 dt = ParseFormatAt(input, end, wxS("%H:%M:%S"));
66f22f4a 1243 if ( !dt.IsValid() )
c398434d 1244 return false;
98919134 1245
66f22f4a
VZ
1246 haveHour =
1247 haveMin =
1248 haveSec = true;
98919134 1249
89a7e1ff 1250 const Tm tm = dt.GetTm();
98919134
FM
1251 hour = tm.hour;
1252 min = tm.min;
1253 sec = tm.sec;
1254 }
1255 break;
1256
1257 case _T('w'): // weekday as a number (0-6), Sunday = 0
ed973feb
VZ
1258 if ( !GetNumericToken(width, input, end, &num) ||
1259 (wday > 6) )
98919134
FM
1260 {
1261 // no match
c398434d 1262 return false;
98919134
FM
1263 }
1264
1265 haveWDay = true;
1266 wday = (WeekDay)num;
1267 break;
1268
1269 case _T('x'): // locale default date representation
98919134 1270 {
89a7e1ff
VZ
1271 wxString
1272 fmtDate = wxLocale::GetInfo(wxLOCALE_SHORT_DATE_FMT),
1273 fmtDateAlt = wxLocale::GetInfo(wxLOCALE_LONG_DATE_FMT);
98919134 1274
98919134 1275 if ( fmtDate.empty() )
98919134
FM
1276 {
1277 if ( IsWestEuropeanCountry(GetCountry()) ||
1278 GetCountry() == Russia )
1279 {
89a7e1ff
VZ
1280 fmtDate = wxS("%d/%m/%Y");
1281 fmtDateAlt = wxS("%m/%d/%Y");
be8dbffa 1282 }
98919134
FM
1283 else // assume USA
1284 {
89a7e1ff
VZ
1285 fmtDate = wxS("%m/%d/%Y");
1286 fmtDateAlt = wxS("%d/%m/%Y");
98919134
FM
1287 }
1288 }
1289
89a7e1ff
VZ
1290 wxDateTime
1291 dt = ParseFormatAt(input, end, fmtDate, fmtDateAlt);
55fffc34 1292
66f22f4a 1293 if ( !dt.IsValid() )
be8dbffa 1294 {
89a7e1ff
VZ
1295 // try with short years too
1296 fmtDate.Replace("%Y","%y");
1297 fmtDateAlt.Replace("%Y","%y");
1298 dt = ParseFormatAt(input, end, fmtDate, fmtDateAlt);
98919134 1299
89a7e1ff 1300 if ( !dt.IsValid() )
be8dbffa
SC
1301 return false;
1302 }
89a7e1ff
VZ
1303
1304 const Tm tm = dt.GetTm();
98919134
FM
1305
1306 haveDay =
1307 haveMon =
1308 haveYear = true;
1309
1310 year = tm.year;
1311 mon = tm.mon;
1312 mday = tm.mday;
98919134
FM
1313 }
1314
1315 break;
1316
1317 case _T('X'): // locale default time representation
98919134 1318 {
89a7e1ff
VZ
1319 wxString fmtTime = wxLocale::GetInfo(wxLOCALE_TIME_FMT),
1320 fmtTimeAlt;
98919134 1321
89a7e1ff
VZ
1322 if ( fmtTime.empty() )
1323 {
1324 // try to parse what follows as "%H:%M:%S" and, if this
1325 // fails, as "%I:%M:%S %p" - this should catch the most
1326 // common cases
1327 fmtTime = "%T";
1328 fmtTimeAlt = "%r";
1329 }
98919134 1330
66f22f4a 1331 const wxDateTime
89a7e1ff 1332 dt = ParseFormatAt(input, end, fmtTime, fmtTimeAlt);
66f22f4a 1333 if ( !dt.IsValid() )
c398434d 1334 return false;
98919134
FM
1335
1336 haveHour =
1337 haveMin =
1338 haveSec = true;
1339
89a7e1ff 1340 const Tm tm = dt.GetTm();
98919134
FM
1341 hour = tm.hour;
1342 min = tm.min;
1343 sec = tm.sec;
98919134 1344 }
98919134
FM
1345 break;
1346
1347 case _T('y'): // year without century (00-99)
ed973feb
VZ
1348 if ( !GetNumericToken(width, input, end, &num) ||
1349 (num > 99) )
98919134
FM
1350 {
1351 // no match
c398434d 1352 return false;
98919134
FM
1353 }
1354
1355 haveYear = true;
1356
1357 // TODO should have an option for roll over date instead of
1358 // hard coding it here
1359 year = (num > 30 ? 1900 : 2000) + (wxDateTime_t)num;
1360 break;
1361
1362 case _T('Y'): // year with century
ed973feb 1363 if ( !GetNumericToken(width, input, end, &num) )
98919134
FM
1364 {
1365 // no match
c398434d 1366 return false;
98919134
FM
1367 }
1368
1369 haveYear = true;
1370 year = (wxDateTime_t)num;
1371 break;
1372
1373 case _T('Z'): // timezone name
89a7e1ff
VZ
1374 // FIXME: currently we just ignore everything that looks like a
1375 // time zone here
1376 GetAlphaToken(input, end);
98919134
FM
1377 break;
1378
1379 case _T('%'): // a percent sign
1380 if ( *input++ != _T('%') )
1381 {
1382 // no match
c398434d 1383 return false;
98919134
FM
1384 }
1385 break;
1386
1387 case 0: // the end of string
1388 wxFAIL_MSG(_T("unexpected format end"));
1389
1390 // fall through
1391
1392 default: // not a known format spec
c398434d 1393 return false;
98919134
FM
1394 }
1395 }
1396
1397 // format matched, try to construct a date from what we have now
1398 Tm tmDef;
1399 if ( dateDef.IsValid() )
1400 {
1401 // take this date as default
1402 tmDef = dateDef.GetTm();
1403 }
1404 else if ( IsValid() )
1405 {
1406 // if this date is valid, don't change it
1407 tmDef = GetTm();
1408 }
1409 else
1410 {
1411 // no default and this date is invalid - fall back to Today()
1412 tmDef = Today().GetTm();
1413 }
1414
1415 Tm tm = tmDef;
1416
1417 // set the date
1418 if ( haveMon )
1419 {
1420 tm.mon = mon;
1421 }
1422
1423 if ( haveYear )
1424 {
1425 tm.year = year;
1426 }
1427
1428 // TODO we don't check here that the values are consistent, if both year
1429 // day and month/day were found, we just ignore the year day and we
1430 // also always ignore the week day
1431 if ( haveDay )
1432 {
c4e08560 1433 if ( mday > GetNumberOfDays(tm.mon, tm.year) )
c398434d 1434 return false;
98919134
FM
1435
1436 tm.mday = mday;
1437 }
1438 else if ( haveYDay )
1439 {
1440 if ( yday > GetNumberOfDays(tm.year) )
c398434d 1441 return false;
98919134
FM
1442
1443 Tm tm2 = wxDateTime(1, Jan, tm.year).SetToYearDay(yday).GetTm();
1444
1445 tm.mon = tm2.mon;
1446 tm.mday = tm2.mday;
1447 }
1448
1449 // deal with AM/PM
1450 if ( haveHour && hourIsIn12hFormat && isPM )
1451 {
1452 // translate to 24hour format
1453 hour += 12;
1454 }
1455 //else: either already in 24h format or no translation needed
1456
1457 // set the time
1458 if ( haveHour )
1459 {
1460 tm.hour = hour;
1461 }
1462
1463 if ( haveMin )
1464 {
1465 tm.min = min;
1466 }
1467
1468 if ( haveSec )
1469 {
1470 tm.sec = sec;
1471 }
1472
1473 if ( haveMsec )
1474 tm.msec = msec;
1475
1476 Set(tm);
1477
1478 // finally check that the week day is consistent -- if we had it
1479 if ( haveWDay && GetWeekDay() != wday )
c398434d 1480 return false;
98919134 1481
ed973feb 1482 *endParse = input;
98919134 1483
c398434d 1484 return true;
98919134
FM
1485}
1486
c398434d 1487bool
98919134
FM
1488wxDateTime::ParseDateTime(const wxString& date, wxString::const_iterator *end)
1489{
c398434d
VZ
1490 wxCHECK_MSG( end, false, "end iterator pointer must be specified" );
1491
98919134
FM
1492 // Set to current day and hour, so strings like '14:00' becomes today at
1493 // 14, not some other random date
1494 wxDateTime dtDate = wxDateTime::Today();
1495 wxDateTime dtTime = wxDateTime::Today();
1496
1497 wxString::const_iterator
1498 endTime,
1499 endDate,
1500 endBoth;
1501
1502 // If we got a date in the beginning, see if there is a time specified
1503 // after the date
1504 if ( dtDate.ParseDate(date, &endDate) )
1505 {
1506 // Skip spaces, as the ParseTime() function fails on spaces
1507 while ( endDate != date.end() && wxIsspace(*endDate) )
1508 ++endDate;
1509
1510 const wxString timestr(endDate, date.end());
1511 if ( !dtTime.ParseTime(timestr, &endTime) )
c398434d 1512 return false;
98919134
FM
1513
1514 endBoth = endDate + (endTime - timestr.begin());
1515 }
1516 else // no date in the beginning
1517 {
1518 // check if we have a time followed by a date
1519 if ( !dtTime.ParseTime(date, &endTime) )
c398434d 1520 return false;
98919134
FM
1521
1522 while ( endTime != date.end() && wxIsspace(*endTime) )
1523 ++endTime;
1524
1525 const wxString datestr(endTime, date.end());
1526 if ( !dtDate.ParseDate(datestr, &endDate) )
c398434d 1527 return false;
98919134
FM
1528
1529 endBoth = endTime + (endDate - datestr.begin());
1530 }
1531
1532 Set(dtDate.GetDay(), dtDate.GetMonth(), dtDate.GetYear(),
1533 dtTime.GetHour(), dtTime.GetMinute(), dtTime.GetSecond(),
1534 dtTime.GetMillisecond());
1535
c398434d 1536 *end = endBoth;
98919134 1537
c398434d 1538 return true;
98919134
FM
1539}
1540
c398434d 1541bool
98919134
FM
1542wxDateTime::ParseDate(const wxString& date, wxString::const_iterator *end)
1543{
c398434d
VZ
1544 wxCHECK_MSG( end, false, "end iterator pointer must be specified" );
1545
98919134
FM
1546 // this is a simplified version of ParseDateTime() which understands only
1547 // "today" (for wxDate compatibility) and digits only otherwise (and not
1548 // all esoteric constructions ParseDateTime() knows about)
1549
66f22f4a 1550 const wxString::const_iterator pBegin = date.begin();
f253c22f 1551 const wxString::const_iterator pEnd = date.end();
66f22f4a
VZ
1552
1553 wxString::const_iterator p = pBegin;
98919134
FM
1554 while ( wxIsspace(*p) )
1555 p++;
1556
1557 // some special cases
1558 static struct
1559 {
1560 const char *str;
1561 int dayDiffFromToday;
1562 } literalDates[] =
1563 {
1564 { wxTRANSLATE("today"), 0 },
1565 { wxTRANSLATE("yesterday"), -1 },
1566 { wxTRANSLATE("tomorrow"), 1 },
1567 };
1568
f253c22f 1569 const size_t lenRest = pEnd - p;
98919134
FM
1570 for ( size_t n = 0; n < WXSIZEOF(literalDates); n++ )
1571 {
1572 const wxString dateStr = wxGetTranslation(literalDates[n].str);
1573 size_t len = dateStr.length();
98919134 1574
ed973feb
VZ
1575 if ( len > lenRest )
1576 continue;
1577
66f22f4a
VZ
1578 const wxString::const_iterator pEnd = p + len;
1579 if ( wxString(p, pEnd).CmpNoCase(dateStr) == 0 )
1580 {
1581 // nothing can follow this, so stop here
98919134 1582
66f22f4a 1583 p = pEnd;
98919134 1584
66f22f4a
VZ
1585 int dayDiffFromToday = literalDates[n].dayDiffFromToday;
1586 *this = Today();
1587 if ( dayDiffFromToday )
1588 {
1589 *this += wxDateSpan::Days(dayDiffFromToday);
98919134 1590 }
66f22f4a 1591
c398434d 1592 *end = pEnd;
66f22f4a 1593
c398434d 1594 return true;
98919134
FM
1595 }
1596 }
1597
1598 // We try to guess what we have here: for each new (numeric) token, we
1599 // determine if it can be a month, day or a year. Of course, there is an
1600 // ambiguity as some numbers may be days as well as months, so we also
1601 // have the ability to back track.
1602
1603 // what do we have?
1604 bool haveDay = false, // the months day?
1605 haveWDay = false, // the day of week?
1606 haveMon = false, // the month?
1607 haveYear = false; // the year?
1608
f253c22f
VZ
1609 bool monWasNumeric = false; // was month specified as a number?
1610
98919134
FM
1611 // and the value of the items we have (init them to get rid of warnings)
1612 WeekDay wday = Inv_WeekDay;
1613 wxDateTime_t day = 0;
1614 wxDateTime::Month mon = Inv_Month;
1615 int year = 0;
1616
1617 // tokenize the string
f253c22f 1618 while ( p != pEnd )
98919134 1619 {
f253c22f
VZ
1620 // skip white space and date delimiters
1621 while ( wxStrchr(".,/-\t\r\n ", *p) )
1622 {
1623 ++p;
1624 }
98919134 1625
f253c22f
VZ
1626 // modify copy of the iterator as we're not sure if the next token is
1627 // still part of the date at all
1628 wxString::const_iterator pCopy = p;
1629
1630 // we can have either alphabetic or numeric token, start by testing if
1631 // it's the latter
98919134 1632 unsigned long val;
f253c22f 1633 if ( GetNumericToken(10 /* max length */, pCopy, pEnd, &val) )
98919134
FM
1634 {
1635 // guess what this number is
1636
1637 bool isDay = false,
1638 isMonth = false,
1639 isYear = false;
1640
1641 if ( !haveMon && val > 0 && val <= 12 )
1642 {
1643 // assume it is month
1644 isMonth = true;
1645 }
1646 else // not the month
1647 {
1648 if ( haveDay )
1649 {
1650 // this can only be the year
1651 isYear = true;
1652 }
1653 else // may be either day or year
1654 {
1655 // use a leap year if we don't have the year yet to allow
1656 // dates like 2/29/1976 which would be rejected otherwise
1657 wxDateTime_t max_days = (wxDateTime_t)(
1658 haveMon
c4e08560 1659 ? GetNumberOfDays(mon, haveYear ? year : 1976)
98919134
FM
1660 : 31
1661 );
1662
1663 // can it be day?
1664 if ( (val == 0) || (val > (unsigned long)max_days) )
1665 {
1666 // no
1667 isYear = true;
1668 }
1669 else // yes, suppose it's the day
1670 {
1671 isDay = true;
1672 }
1673 }
1674 }
1675
1676 if ( isYear )
1677 {
1678 if ( haveYear )
1679 break;
1680
1681 haveYear = true;
1682
1683 year = (wxDateTime_t)val;
1684 }
1685 else if ( isDay )
1686 {
1687 if ( haveDay )
1688 break;
1689
1690 haveDay = true;
1691
1692 day = (wxDateTime_t)val;
1693 }
1694 else if ( isMonth )
1695 {
1696 haveMon = true;
f253c22f 1697 monWasNumeric = true;
98919134
FM
1698
1699 mon = (Month)(val - 1);
1700 }
1701 }
1702 else // not a number
1703 {
1704 // be careful not to overwrite the current mon value
254696bb
VZ
1705 Month mon2 = GetMonthFromName
1706 (
f253c22f 1707 pCopy, pEnd,
254696bb
VZ
1708 Name_Full | Name_Abbr,
1709 DateLang_Local | DateLang_English
1710 );
98919134
FM
1711 if ( mon2 != Inv_Month )
1712 {
1713 // it's a month
1714 if ( haveMon )
1715 {
f253c22f
VZ
1716 // but we already have a month - maybe we guessed wrong
1717 // when we had interpreted that numeric value as a month
1718 // and it was the day number instead?
1719 if ( haveDay || !monWasNumeric )
98919134 1720 break;
f253c22f
VZ
1721
1722 // assume we did and change our mind: reinterpret the month
1723 // value as a day (notice that there is no need to check
1724 // that it is valid as month values are always < 12, but
1725 // the days are counted from 1 unlike the months)
1726 day = (wxDateTime_t)(mon + 1);
1727 haveDay = true;
98919134
FM
1728 }
1729
1730 mon = mon2;
1731
1732 haveMon = true;
1733 }
1734 else // not a valid month name
1735 {
254696bb
VZ
1736 WeekDay wday2 = GetWeekDayFromName
1737 (
f253c22f 1738 pCopy, pEnd,
254696bb
VZ
1739 Name_Full | Name_Abbr,
1740 DateLang_Local | DateLang_English
1741 );
98919134
FM
1742 if ( wday2 != Inv_WeekDay )
1743 {
1744 // a week day
1745 if ( haveWDay )
98919134 1746 break;
98919134
FM
1747
1748 wday = wday2;
1749
1750 haveWDay = true;
1751 }
1752 else // not a valid weekday name
1753 {
1754 // try the ordinals
1755 static const char *ordinals[] =
1756 {
1757 wxTRANSLATE("first"),
1758 wxTRANSLATE("second"),
1759 wxTRANSLATE("third"),
1760 wxTRANSLATE("fourth"),
1761 wxTRANSLATE("fifth"),
1762 wxTRANSLATE("sixth"),
1763 wxTRANSLATE("seventh"),
1764 wxTRANSLATE("eighth"),
1765 wxTRANSLATE("ninth"),
1766 wxTRANSLATE("tenth"),
1767 wxTRANSLATE("eleventh"),
1768 wxTRANSLATE("twelfth"),
1769 wxTRANSLATE("thirteenth"),
1770 wxTRANSLATE("fourteenth"),
1771 wxTRANSLATE("fifteenth"),
1772 wxTRANSLATE("sixteenth"),
1773 wxTRANSLATE("seventeenth"),
1774 wxTRANSLATE("eighteenth"),
1775 wxTRANSLATE("nineteenth"),
1776 wxTRANSLATE("twentieth"),
1777 // that's enough - otherwise we'd have problems with
1778 // composite (or not) ordinals
1779 };
1780
1781 size_t n;
1782 for ( n = 0; n < WXSIZEOF(ordinals); n++ )
1783 {
f253c22f
VZ
1784 const wxString ord = wxGetTranslation(ordinals[n]);
1785 const size_t len = ord.length();
1786 if ( date.compare(p - pBegin, len, ord) == 0 )
98919134 1787 {
f253c22f 1788 p += len;
98919134
FM
1789 break;
1790 }
1791 }
1792
1793 if ( n == WXSIZEOF(ordinals) )
1794 {
1795 // stop here - something unknown
1796 break;
1797 }
1798
1799 // it's a day
1800 if ( haveDay )
1801 {
1802 // don't try anything here (as in case of numeric day
1803 // above) - the symbolic day spec should always
1804 // precede the month/year
1805 break;
1806 }
1807
1808 haveDay = true;
1809
1810 day = (wxDateTime_t)(n + 1);
1811 }
1812 }
1813 }
1814
f253c22f
VZ
1815 // advance iterator past a successfully parsed token
1816 p = pCopy;
98919134
FM
1817 }
1818
1819 // either no more tokens or the scan was stopped by something we couldn't
1820 // parse - in any case, see if we can construct a date from what we have
1821 if ( !haveDay && !haveWDay )
c398434d 1822 return false;
98919134
FM
1823
1824 if ( haveWDay && (haveMon || haveYear || haveDay) &&
1825 !(haveDay && haveMon && haveYear) )
1826 {
1827 // without adjectives (which we don't support here) the week day only
1828 // makes sense completely separately or with the full date
1829 // specification (what would "Wed 1999" mean?)
c398434d 1830 return false;
98919134
FM
1831 }
1832
1833 if ( !haveWDay && haveYear && !(haveDay && haveMon) )
1834 {
1835 // may be we have month and day instead of day and year?
1836 if ( haveDay && !haveMon )
1837 {
1838 if ( day <= 12 )
1839 {
1840 // exchange day and month
1841 mon = (wxDateTime::Month)(day - 1);
1842
1843 // we're in the current year then
c4e08560 1844 if ( (year > 0) && (year <= (int)GetNumberOfDays(mon, Inv_Year)) )
98919134
FM
1845 {
1846 day = (wxDateTime_t)year;
1847
1848 haveMon = true;
1849 haveYear = false;
1850 }
1851 //else: no, can't exchange, leave haveMon == false
1852 }
1853 }
1854
1855 if ( !haveMon )
c398434d 1856 return false;
98919134
FM
1857 }
1858
1859 if ( !haveMon )
1860 {
1861 mon = GetCurrentMonth();
1862 }
1863
1864 if ( !haveYear )
1865 {
1866 year = GetCurrentYear();
1867 }
1868
1869 if ( haveDay )
1870 {
1871 // normally we check the day above but the check is optimistic in case
1872 // we find the day before its month/year so we have to redo it now
c4e08560 1873 if ( day > GetNumberOfDays(mon, year) )
c398434d 1874 return false;
98919134
FM
1875
1876 Set(day, mon, year);
1877
1878 if ( haveWDay )
1879 {
1880 // check that it is really the same
1881 if ( GetWeekDay() != wday )
c398434d 1882 return false;
98919134
FM
1883 }
1884 }
1885 else // haveWDay
1886 {
1887 *this = Today();
1888
1889 SetToWeekDayInSameWeek(wday);
1890 }
1891
c398434d 1892 *end = p;
98919134 1893
c398434d 1894 return true;
98919134
FM
1895}
1896
c398434d 1897bool
98919134
FM
1898wxDateTime::ParseTime(const wxString& time, wxString::const_iterator *end)
1899{
c398434d
VZ
1900 wxCHECK_MSG( end, false, "end iterator pointer must be specified" );
1901
98919134
FM
1902 // first try some extra things
1903 static const struct
1904 {
1905 const char *name;
254696bb 1906 wxDateTime_t hour;
98919134
FM
1907 } stdTimes[] =
1908 {
1909 { wxTRANSLATE("noon"), 12 },
1910 { wxTRANSLATE("midnight"), 00 },
1911 // anything else?
1912 };
1913
1914 for ( size_t n = 0; n < WXSIZEOF(stdTimes); n++ )
1915 {
254696bb
VZ
1916 const wxString timeString = wxGetTranslation(stdTimes[n].name);
1917 const wxString::const_iterator p = time.begin() + timeString.length();
1918 if ( timeString.CmpNoCase(wxString(time.begin(), p)) == 0 )
98919134
FM
1919 {
1920 // casts required by DigitalMars
1921 Set(stdTimes[n].hour, wxDateTime_t(0), wxDateTime_t(0));
1922
1923 if ( end )
254696bb 1924 *end = p;
98919134 1925
c398434d 1926 return true;
98919134
FM
1927 }
1928 }
1929
1930 // try all time formats we may think about in the order from longest to
1931 // shortest
1932 static const char *timeFormats[] =
1933 {
1934 "%I:%M:%S %p", // 12hour with AM/PM
1935 "%H:%M:%S", // could be the same or 24 hour one so try it too
1936 "%I:%M %p", // 12hour with AM/PM but without seconds
1937 "%H:%M:%S", // and a possibly 24 hour version without seconds
1938 "%X", // possibly something from above or maybe something
1939 // completely different -- try it last
1940
1941 // TODO: parse timezones
1942 };
1943
1944 for ( size_t nFmt = 0; nFmt < WXSIZEOF(timeFormats); nFmt++ )
1945 {
c398434d
VZ
1946 if ( ParseFormat(time, timeFormats[nFmt], end) )
1947 return true;
98919134
FM
1948 }
1949
c398434d 1950 return false;
98919134
FM
1951}
1952
1953// ----------------------------------------------------------------------------
1954// Workdays and holidays support
1955// ----------------------------------------------------------------------------
1956
1957bool wxDateTime::IsWorkDay(Country WXUNUSED(country)) const
1958{
1959 return !wxDateTimeHolidayAuthority::IsHoliday(*this);
1960}
1961
1962// ============================================================================
1963// wxDateSpan
1964// ============================================================================
1965
1966wxDateSpan WXDLLIMPEXP_BASE operator*(int n, const wxDateSpan& ds)
1967{
1968 wxDateSpan ds1(ds);
1969 return ds1.Multiply(n);
1970}
1971
1972// ============================================================================
1973// wxTimeSpan
1974// ============================================================================
1975
1976wxTimeSpan WXDLLIMPEXP_BASE operator*(int n, const wxTimeSpan& ts)
1977{
1978 return wxTimeSpan(ts).Multiply(n);
1979}
1980
1981// this enum is only used in wxTimeSpan::Format() below but we can't declare
1982// it locally to the method as it provokes an internal compiler error in egcs
1983// 2.91.60 when building with -O2
1984enum TimeSpanPart
1985{
1986 Part_Week,
1987 Part_Day,
1988 Part_Hour,
1989 Part_Min,
1990 Part_Sec,
1991 Part_MSec
1992};
1993
1994// not all strftime(3) format specifiers make sense here because, for example,
1995// a time span doesn't have a year nor a timezone
1996//
1997// Here are the ones which are supported (all of them are supported by strftime
1998// as well):
1999// %H hour in 24 hour format
2000// %M minute (00 - 59)
2001// %S second (00 - 59)
2002// %% percent sign
2003//
2004// Also, for MFC CTimeSpan compatibility, we support
2005// %D number of days
2006//
2007// And, to be better than MFC :-), we also have
2008// %E number of wEeks
2009// %l milliseconds (000 - 999)
2010wxString wxTimeSpan::Format(const wxString& format) const
2011{
2012 // we deal with only positive time spans here and just add the sign in
2013 // front for the negative ones
2014 if ( IsNegative() )
2015 {
2016 wxString str(Negate().Format(format));
2017 return "-" + str;
2018 }
2019
2020 wxCHECK_MSG( !format.empty(), wxEmptyString,
2021 _T("NULL format in wxTimeSpan::Format") );
2022
2023 wxString str;
2024 str.Alloc(format.length());
2025
2026 // Suppose we have wxTimeSpan ts(1 /* hour */, 2 /* min */, 3 /* sec */)
2027 //
2028 // Then, of course, ts.Format("%H:%M:%S") must return "01:02:03", but the
2029 // question is what should ts.Format("%S") do? The code here returns "3273"
2030 // in this case (i.e. the total number of seconds, not just seconds % 60)
2031 // because, for me, this call means "give me entire time interval in
2032 // seconds" and not "give me the seconds part of the time interval"
2033 //
2034 // If we agree that it should behave like this, it is clear that the
2035 // interpretation of each format specifier depends on the presence of the
2036 // other format specs in the string: if there was "%H" before "%M", we
2037 // should use GetMinutes() % 60, otherwise just GetMinutes() &c
2038
2039 // we remember the most important unit found so far
2040 TimeSpanPart partBiggest = Part_MSec;
2041
2042 for ( wxString::const_iterator pch = format.begin(); pch != format.end(); ++pch )
2043 {
2044 wxChar ch = *pch;
2045
2046 if ( ch == _T('%') )
2047 {
2048 // the start of the format specification of the printf() below
2049 wxString fmtPrefix(_T('%'));
2050
2051 // the number
2052 long n;
2053
2054 // the number of digits for the format string, 0 if unused
2055 unsigned digits = 0;
2056
2057 ch = *++pch; // get the format spec char
2058 switch ( ch )
2059 {
2060 default:
2061 wxFAIL_MSG( _T("invalid format character") );
2062 // fall through
2063
2064 case _T('%'):
2065 str += ch;
2066
2067 // skip the part below switch
2068 continue;
2069
2070 case _T('D'):
2071 n = GetDays();
2072 if ( partBiggest < Part_Day )
2073 {
2074 n %= DAYS_PER_WEEK;
2075 }
2076 else
2077 {
2078 partBiggest = Part_Day;
2079 }
2080 break;
2081
2082 case _T('E'):
2083 partBiggest = Part_Week;
2084 n = GetWeeks();
2085 break;
2086
2087 case _T('H'):
2088 n = GetHours();
2089 if ( partBiggest < Part_Hour )
2090 {
2091 n %= HOURS_PER_DAY;
2092 }
2093 else
2094 {
2095 partBiggest = Part_Hour;
2096 }
2097
2098 digits = 2;
2099 break;
2100
2101 case _T('l'):
2102 n = GetMilliseconds().ToLong();
2103 if ( partBiggest < Part_MSec )
2104 {
2105 n %= 1000;
2106 }
2107 //else: no need to reset partBiggest to Part_MSec, it is
2108 // the least significant one anyhow
2109
2110 digits = 3;
2111 break;
2112
2113 case _T('M'):
2114 n = GetMinutes();
2115 if ( partBiggest < Part_Min )
2116 {
2117 n %= MIN_PER_HOUR;
2118 }
2119 else
2120 {
2121 partBiggest = Part_Min;
2122 }
2123
2124 digits = 2;
2125 break;
2126
2127 case _T('S'):
2128 n = GetSeconds().ToLong();
2129 if ( partBiggest < Part_Sec )
2130 {
2131 n %= SEC_PER_MIN;
2132 }
2133 else
2134 {
2135 partBiggest = Part_Sec;
2136 }
2137
2138 digits = 2;
2139 break;
2140 }
2141
2142 if ( digits )
2143 {
2144 fmtPrefix << _T("0") << digits;
2145 }
2146
2147 str += wxString::Format(fmtPrefix + _T("ld"), n);
2148 }
2149 else
2150 {
2151 // normal character, just copy
2152 str += ch;
2153 }
2154 }
2155
2156 return str;
2157}
2158
2159#endif // wxUSE_DATETIME