+ // what fields have we found?
+ bool haveWDay = FALSE,
+ haveYDay = FALSE,
+ haveDay = FALSE,
+ haveMon = FALSE,
+ haveYear = FALSE,
+ haveHour = FALSE,
+ haveMin = FALSE,
+ haveSec = FALSE;
+
+ bool hourIsIn12hFormat = FALSE, // or in 24h one?
+ isPM = FALSE; // AM by default
+
+ // and the value of the items we have (init them to get rid of warnings)
+ wxDateTime_t sec = 0,
+ min = 0,
+ hour = 0;
+ WeekDay wday = Inv_WeekDay;
+ wxDateTime_t yday = 0,
+ mday = 0;
+ wxDateTime::Month mon = Inv_Month;
+ int year = 0;
+
+ const wxChar *input = date;
+ for ( const wxChar *fmt = format; *fmt; fmt++ )
+ {
+ if ( *fmt != _T('%') )
+ {
+ if ( wxIsspace(*fmt) )
+ {
+ // a white space in the format string matches 0 or more white
+ // spaces in the input
+ while ( wxIsspace(*input) )
+ {
+ input++;
+ }
+ }
+ else // !space
+ {
+ // any other character (not whitespace, not '%') must be
+ // matched by itself in the input
+ if ( *input++ != *fmt )
+ {
+ // no match
+ return (wxChar *)NULL;
+ }
+ }
+
+ // done with this format char
+ continue;
+ }
+
+ // start of a format specification
+
+ // parse the optional width
+ size_t width = 0;
+ while ( isdigit(*++fmt) )
+ {
+ width *= 10;
+ width += *fmt - _T('0');
+ }
+
+ // then the format itself
+ switch ( *fmt )
+ {
+ case _T('a'): // a weekday name
+ case _T('A'):
+ {
+ int flag = *fmt == _T('a') ? Name_Abbr : Name_Full;
+ wday = GetWeekDayFromName(GetAlphaToken(input), flag);
+ if ( wday == Inv_WeekDay )
+ {
+ // no match
+ return (wxChar *)NULL;
+ }
+ }
+ haveWDay = TRUE;
+ break;
+
+ case _T('b'): // a month name
+ case _T('B'):
+ {
+ int flag = *fmt == _T('b') ? Name_Abbr : Name_Full;
+ mon = GetMonthFromName(GetAlphaToken(input), flag);
+ if ( mon == Inv_Month )
+ {
+ // no match
+ return (wxChar *)NULL;
+ }
+ }
+ haveMon = TRUE;
+ break;
+
+ case _T('c'): // locale default date and time representation
+ {
+ wxDateTime dt;
+
+ // this is the format which corresponds to ctime() output
+ // and strptime("%c") should parse it, so try it first
+ static const wxChar *fmtCtime = _T("%a %b %d %H:%M:%S %Y");
+
+ const wxChar *result = dt.ParseFormat(input, fmtCtime);
+ if ( !result )
+ {
+ result = dt.ParseFormat(input, _T("%x %X"));
+ }
+
+ if ( !result )
+ {
+ result = dt.ParseFormat(input, _T("%X %x"));
+ }
+
+ if ( !result )
+ {
+ // we've tried everything and still no match
+ return (wxChar *)NULL;
+ }
+
+ Tm tm = dt.GetTm();
+
+ haveDay = haveMon = haveYear =
+ haveHour = haveMin = haveSec = TRUE;
+
+ hour = tm.hour;
+ min = tm.min;
+ sec = tm.sec;
+
+ year = tm.year;
+ mon = tm.mon;
+ mday = tm.mday;
+
+ input = result;
+ }
+ break;
+
+ case _T('d'): // day of a month (01-31)
+ if ( !GetNumericToken(width, input, &num) ||
+ (num > 31) || (num < 1) )
+ {
+ // no match
+ return (wxChar *)NULL;
+ }
+
+ // we can't check whether the day range is correct yet, will
+ // do it later - assume ok for now
+ haveDay = TRUE;
+ mday = (wxDateTime_t)num;
+ break;
+
+ case _T('H'): // hour in 24h format (00-23)
+ if ( !GetNumericToken(width, input, &num) || (num > 23) )
+ {
+ // no match
+ return (wxChar *)NULL;
+ }
+
+ haveHour = TRUE;
+ hour = (wxDateTime_t)num;
+ break;
+
+ case _T('I'): // hour in 12h format (01-12)
+ if ( !GetNumericToken(width, input, &num) || !num || (num > 12) )
+ {
+ // no match
+ return (wxChar *)NULL;
+ }
+
+ haveHour = TRUE;
+ hourIsIn12hFormat = TRUE;
+ hour = (wxDateTime_t)(num % 12); // 12 should be 0
+ break;
+
+ case _T('j'): // day of the year
+ if ( !GetNumericToken(width, input, &num) || !num || (num > 366) )
+ {
+ // no match
+ return (wxChar *)NULL;
+ }
+
+ haveYDay = TRUE;
+ yday = (wxDateTime_t)num;
+ break;
+
+ case _T('m'): // month as a number (01-12)
+ if ( !GetNumericToken(width, input, &num) || !num || (num > 12) )
+ {
+ // no match
+ return (wxChar *)NULL;
+ }
+
+ haveMon = TRUE;
+ mon = (Month)(num - 1);
+ break;
+
+ case _T('M'): // minute as a decimal number (00-59)
+ if ( !GetNumericToken(width, input, &num) || (num > 59) )
+ {
+ // no match
+ return (wxChar *)NULL;
+ }
+
+ haveMin = TRUE;
+ min = (wxDateTime_t)num;
+ break;
+
+ case _T('p'): // AM or PM string
+ {
+ wxString am, pm, token = GetAlphaToken(input);
+
+ GetAmPmStrings(&am, &pm);
+ if ( token.CmpNoCase(pm) == 0 )
+ {
+ isPM = TRUE;
+ }
+ else if ( token.CmpNoCase(am) != 0 )
+ {
+ // no match
+ return (wxChar *)NULL;
+ }
+ }
+ break;
+
+ case _T('r'): // time as %I:%M:%S %p
+ {
+ wxDateTime dt;
+ input = dt.ParseFormat(input, _T("%I:%M:%S %p"));
+ if ( !input )
+ {
+ // no match
+ return (wxChar *)NULL;
+ }
+
+ haveHour = haveMin = haveSec = TRUE;
+
+ Tm tm = dt.GetTm();
+ hour = tm.hour;
+ min = tm.min;
+ sec = tm.sec;
+ }
+ break;
+
+ case _T('R'): // time as %H:%M
+ {
+ wxDateTime dt;
+ input = dt.ParseFormat(input, _T("%H:%M"));
+ if ( !input )
+ {
+ // no match
+ return (wxChar *)NULL;
+ }
+
+ haveHour = haveMin = TRUE;
+
+ Tm tm = dt.GetTm();
+ hour = tm.hour;
+ min = tm.min;
+ }
+
+ case _T('S'): // second as a decimal number (00-61)
+ if ( !GetNumericToken(width, input, &num) || (num > 61) )
+ {
+ // no match
+ return (wxChar *)NULL;
+ }
+
+ haveSec = TRUE;
+ sec = (wxDateTime_t)num;
+ break;
+
+ case _T('T'): // time as %H:%M:%S
+ {
+ wxDateTime dt;
+ input = dt.ParseFormat(input, _T("%H:%M:%S"));
+ if ( !input )
+ {
+ // no match
+ return (wxChar *)NULL;
+ }
+
+ haveHour = haveMin = haveSec = TRUE;
+
+ Tm tm = dt.GetTm();
+ hour = tm.hour;
+ min = tm.min;
+ sec = tm.sec;
+ }
+ break;
+
+ case _T('w'): // weekday as a number (0-6), Sunday = 0
+ if ( !GetNumericToken(width, input, &num) || (wday > 6) )
+ {
+ // no match
+ return (wxChar *)NULL;
+ }
+
+ haveWDay = TRUE;
+ wday = (WeekDay)num;
+ break;
+
+ case _T('x'): // locale default date representation
+#ifdef HAVE_STRPTIME
+ // try using strptime() - it may fail even if the input is
+ // correct but the date is out of range, so we will fall back
+ // to our generic code anyhow (FIXME !Unicode friendly)
+ {
+ struct tm tm;
+ const wxChar *result = strptime(input, "%x", &tm);
+ if ( result )
+ {
+ input = result;
+
+ haveDay = haveMon = haveYear = TRUE;
+
+ year = 1900 + tm.tm_year;
+ mon = (Month)tm.tm_mon;
+ mday = tm.tm_mday;
+
+ break;
+ }
+ }
+#endif // HAVE_STRPTIME
+
+ // TODO query the LOCALE_IDATE setting under Win32
+ {
+ wxDateTime dt;
+
+ wxString fmtDate, fmtDateAlt;
+ if ( IsWestEuropeanCountry(GetCountry()) ||
+ GetCountry() == Russia )
+ {
+ fmtDate = _T("%d/%m/%y");
+ fmtDateAlt = _T("%m/%d/%y");
+ }
+ else // assume USA
+ {
+ fmtDate = _T("%m/%d/%y");
+ fmtDateAlt = _T("%d/%m/%y");
+ }
+
+ const wxChar *result = dt.ParseFormat(input, fmtDate);
+
+ if ( !result )
+ {
+ // ok, be nice and try another one
+ result = dt.ParseFormat(input, fmtDateAlt);
+ }
+
+ if ( !result )
+ {
+ // bad luck
+ return (wxChar *)NULL;
+ }
+
+ Tm tm = dt.GetTm();
+
+ haveDay = haveMon = haveYear = TRUE;
+
+ year = tm.year;
+ mon = tm.mon;
+ mday = tm.mday;
+
+ input = result;
+ }
+
+ break;
+
+ case _T('X'): // locale default time representation
+#ifdef HAVE_STRPTIME
+ {
+ // use strptime() to do it for us (FIXME !Unicode friendly)
+ struct tm tm;
+ input = strptime(input, "%X", &tm);
+ if ( !input )
+ {
+ return (wxChar *)NULL;
+ }
+
+ haveHour = haveMin = haveSec = TRUE;
+
+ hour = tm.tm_hour;
+ min = tm.tm_min;
+ sec = tm.tm_sec;
+ }
+#else // !HAVE_STRPTIME
+ // TODO under Win32 we can query the LOCALE_ITIME system
+ // setting which says whether the default time format is
+ // 24 or 12 hour
+ {
+ // try to parse what follows as "%H:%M:%S" and, if this
+ // fails, as "%I:%M:%S %p" - this should catch the most
+ // common cases
+ wxDateTime dt;
+
+ const wxChar *result = dt.ParseFormat(input, _T("%T"));
+ if ( !result )
+ {
+ result = dt.ParseFormat(input, _T("%r"));
+ }
+
+ if ( !result )
+ {
+ // no match
+ return (wxChar *)NULL;
+ }
+
+ haveHour = haveMin = haveSec = TRUE;
+
+ Tm tm = dt.GetTm();
+ hour = tm.hour;
+ min = tm.min;
+ sec = tm.sec;
+
+ input = result;
+ }
+#endif // HAVE_STRPTIME/!HAVE_STRPTIME
+ break;
+
+ case _T('y'): // year without century (00-99)
+ if ( !GetNumericToken(width, input, &num) || (num > 99) )
+ {
+ // no match
+ return (wxChar *)NULL;
+ }
+
+ haveYear = TRUE;
+
+ // TODO should have an option for roll over date instead of
+ // hard coding it here
+ year = (num > 30 ? 1900 : 2000) + (wxDateTime_t)num;
+ break;
+
+ case _T('Y'): // year with century
+ if ( !GetNumericToken(width, input, &num) )
+ {
+ // no match
+ return (wxChar *)NULL;
+ }
+
+ haveYear = TRUE;
+ year = (wxDateTime_t)num;
+ break;
+
+ case _T('Z'): // timezone name
+ wxFAIL_MSG(_T("TODO"));
+ break;
+
+ case _T('%'): // a percent sign
+ if ( *input++ != _T('%') )
+ {
+ // no match
+ return (wxChar *)NULL;
+ }
+ break;
+
+ case 0: // the end of string
+ wxFAIL_MSG(_T("unexpected format end"));
+
+ // fall through
+
+ default: // not a known format spec
+ return (wxChar *)NULL;
+ }
+ }
+
+ // format matched, try to construct a date from what we have now
+ Tm tmDef;
+ if ( dateDef.IsValid() )
+ {
+ // take this date as default
+ tmDef = dateDef.GetTm();
+ }
+#ifdef __WIN16__
+ else if ( m_time != 0 )
+#else
+ else if ( m_time != wxLongLong(0) )
+#endif
+ {
+ // if this date is valid, don't change it
+ tmDef = GetTm();
+ }
+ else
+ {
+ // no default and this date is invalid - fall back to Today()
+ tmDef = Today().GetTm();
+ }
+
+ Tm tm = tmDef;
+
+ // set the date
+ if ( haveYear )
+ {
+ tm.year = year;
+ }
+
+ // TODO we don't check here that the values are consistent, if both year
+ // day and month/day were found, we just ignore the year day and we
+ // also always ignore the week day
+ if ( haveMon && haveDay )
+ {
+ if ( mday > GetNumOfDaysInMonth(tm.year, mon) )
+ {
+ wxLogDebug(_T("bad month day in wxDateTime::ParseFormat"));
+
+ return (wxChar *)NULL;
+ }
+
+ tm.mon = mon;
+ tm.mday = mday;
+ }
+ else if ( haveYDay )
+ {
+ if ( yday > GetNumberOfDays(tm.year) )
+ {
+ wxLogDebug(_T("bad year day in wxDateTime::ParseFormat"));
+
+ return (wxChar *)NULL;
+ }
+
+ Tm tm2 = wxDateTime(1, Jan, tm.year).SetToYearDay(yday).GetTm();
+
+ tm.mon = tm2.mon;
+ tm.mday = tm2.mday;
+ }
+
+ // deal with AM/PM
+ if ( haveHour && hourIsIn12hFormat && isPM )
+ {
+ // translate to 24hour format
+ hour += 12;
+ }
+ //else: either already in 24h format or no translation needed
+
+ // set the time
+ if ( haveHour )
+ {
+ tm.hour = hour;
+ }
+
+ if ( haveMin )
+ {
+ tm.min = min;
+ }
+
+ if ( haveSec )
+ {
+ tm.sec = sec;
+ }
+
+ Set(tm);
+
+ return input;