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