+
+ // the spec was correct
+ Set(day, mon, year, hour, min, sec);
+ MakeTimezone(60*(wxDateTime_t)offset);
+
+ return p;
+}
+
+const wxChar *wxDateTime::ParseFormat(const wxChar *date,
+ const wxChar *format,
+ const wxDateTime& dateDef)
+{
+ wxCHECK_MSG( date && format, (wxChar *)NULL,
+ _T("NULL pointer in wxDateTime::ParseFormat()") );
+
+ wxString str;
+ unsigned long num;
+
+ // 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
+ 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 %e %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(input, &num) || (num > 31) )
+ {
+ // 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(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(input, &num) || !num || (num > 12) )
+ {
+ // no match
+ return (wxChar *)NULL;
+ }
+
+ haveHour = TRUE;
+ hourIsIn12hFormat = TRUE;
+ hour = num % 12; // 12 should be 0
+ break;
+
+ case _T('j'): // day of the year
+ if ( !GetNumericToken(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(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(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(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(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;
+ if ( IsWestEuropeanCountry(GetCountry()) ||
+ GetCountry() == Russia )
+ {
+ fmtDate = _T("%d/%m/%y");
+ }
+ else // assume USA
+ {
+ fmtDate = _T("%m/%d/%y");
+ }
+
+ const wxChar *result = dt.ParseFormat(input, fmtDate);
+ 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(input, &num) || (num > 99) )
+ {
+ // no match
+ return (wxChar *)NULL;
+ }
+
+ haveYear = TRUE;
+ year = 1900 + num;
+ break;
+
+ case _T('Y'): // year with century
+ if ( !GetNumericToken(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();
+ }
+ else if ( m_time != 0 )
+ {
+ // 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;
+}
+
+const wxChar *wxDateTime::ParseDateTime(const wxChar *date)
+{
+ wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
+
+ // there is a public domain version of getdate.y, but it only works for
+ // English...
+ wxFAIL_MSG(_T("TODO"));
+
+ return (wxChar *)NULL;
+}
+
+const wxChar *wxDateTime::ParseDate(const wxChar *date)
+{
+ // this is a simplified version of ParseDateTime() which understands only
+ // "today" (for wxDate compatibility) and digits only otherwise (and not
+ // all esoteric constructions ParseDateTime() knows about)
+
+ wxCHECK_MSG( date, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
+
+ const wxChar *p = date;
+ while ( wxIsspace(*p) )
+ p++;
+
+ wxString today = _T("today");
+ size_t len = today.length();
+ if ( wxString(p, len).CmpNoCase(today) == 0 )
+ {
+ // nothing can follow this, so stop here
+ p += len;
+
+ *this = Today();
+
+ return p;
+ }
+
+ // what do we have?
+ bool haveDay = FALSE, // the months day?
+ haveWDay = FALSE, // the day of week?
+ haveMon = FALSE, // the month?
+ haveYear = FALSE; // the year?
+
+ // and the value of the items we have (init them to get rid of warnings)
+ WeekDay wday = Inv_WeekDay;
+ wxDateTime_t day = 0;
+ wxDateTime::Month mon = Inv_Month;
+ int year = 0;
+
+ // tokenize the string
+ wxStringTokenizer tok(p, _T(",/-\t "));
+ while ( tok.HasMoreTokens() )
+ {
+ wxString token = tok.GetNextToken();
+
+ // is it a number?
+ unsigned long val;
+ if ( token.ToULong(&val) )
+ {
+ // guess what this number is
+
+ bool isDay = FALSE,
+ isMonth = FALSE,
+ // only years are counted from 0
+ isYear = (val == 0) || (val > 31);
+ if ( !isYear )
+ {
+ // may be the month or month day or the year, assume the
+ // month day by default and fallback to month if the range
+ // allow it or to the year if our assumption doesn't work
+ if ( haveDay )
+ {
+ // we already have the day, so may only be a month or year
+ if ( val > 12 )
+ {
+ isYear = TRUE;
+ }
+ else
+ {
+ isMonth = TRUE;
+ }
+ }
+ else // it may be day
+ {
+ isDay = TRUE;
+
+ // check the range
+ if ( haveMon )
+ {
+ if ( val > GetNumOfDaysInMonth(haveYear ? year
+ : Inv_Year,
+ mon) )
+ {
+ // oops, it can't be a day finally
+ isDay = FALSE;
+
+ if ( val > 12 )
+ {
+ isYear = TRUE;
+ }
+ else
+ {
+ isMonth = TRUE;
+ }
+ }
+ }
+ }
+ }
+
+ // remember that we have this and stop the scan if it's the second
+ // time we find this, except for the day logic (see there)
+ if ( isYear )
+ {
+ if ( haveYear )
+ {
+ break;
+ }
+
+ haveYear = TRUE;
+
+ // no roll over - 99 means 99, not 1999 for us
+ year = val;
+ }
+ else if ( isMonth )
+ {
+ if ( haveMon )
+ {
+ break;
+ }
+
+ haveMon = TRUE;
+
+ mon = (wxDateTime::Month)val;
+ }
+ else
+ {
+ wxASSERT_MSG( isDay, _T("logic error") );
+
+ if ( haveDay )
+ {
+ // may be were mistaken when we found it for the first
+ // time? may be it was a month or year instead?
+ //
+ // this ability to "backtrack" allows us to correctly parse
+ // both things like 01/13 and 13/01 - but, of course, we
+ // still can't resolve the ambiguity in 01/02 (it will be
+ // Feb 1 for us, not Jan 2 as americans might expect!)
+ if ( (day <= 12) && !haveMon )
+ {
+ // exchange day and month
+ mon = (wxDateTime::Month)day;
+
+ haveMon = TRUE;
+ }
+ else if ( !haveYear )
+ {
+ // exchange day and year
+ year = day;
+
+ haveYear = TRUE;
+ }
+ }
+
+ haveDay = TRUE;
+
+ day = val;
+ }
+ }
+ else // not a number
+ {
+ mon = GetMonthFromName(token, Name_Full | Name_Abbr);
+ if ( mon != Inv_Month )
+ {
+ // it's a month
+ if ( haveMon )
+ {
+ break;
+ }
+
+ haveMon = TRUE;
+ }
+ else
+ {
+ wday = GetWeekDayFromName(token, Name_Full | Name_Abbr);
+ if ( wday != Inv_WeekDay )
+ {
+ // a week day
+ if ( haveWDay )
+ {
+ break;
+ }
+
+ haveWDay = TRUE;
+ }
+ else
+ {
+ // try the ordinals
+ static const wxChar *ordinals[] =
+ {
+ wxTRANSLATE("first"),
+ wxTRANSLATE("second"),
+ wxTRANSLATE("third"),
+ wxTRANSLATE("fourth"),
+ wxTRANSLATE("fifth"),
+ wxTRANSLATE("sixth"),
+ wxTRANSLATE("seventh"),
+ wxTRANSLATE("eighth"),
+ wxTRANSLATE("ninth"),
+ wxTRANSLATE("tenth"),
+ wxTRANSLATE("eleventh"),
+ wxTRANSLATE("twelfth"),
+ wxTRANSLATE("thirteenth"),
+ wxTRANSLATE("fourteenth"),
+ wxTRANSLATE("fifteenth"),
+ wxTRANSLATE("sixteenth"),
+ wxTRANSLATE("seventeenth"),
+ wxTRANSLATE("eighteenth"),
+ wxTRANSLATE("nineteenth"),
+ wxTRANSLATE("twentieth"),
+ // that's enough - otherwise we'd have problems with
+ // composite (or not) ordinals otherwise
+ };
+
+ size_t n;
+ for ( n = 0; n < WXSIZEOF(ordinals); n++ )
+ {
+ if ( token.CmpNoCase(ordinals[n]) == 0 )
+ {
+ break;
+ }
+ }
+
+ if ( n == WXSIZEOF(ordinals) )
+ {
+ // stop here - something unknown
+ break;
+ }
+
+ // it's a day
+ if ( haveDay )
+ {
+ // don't try anything here (as in case of numeric day
+ // above) - the symbolic day spec should always
+ // precede the month/year
+ break;
+ }
+
+ haveDay = TRUE;
+
+ day = n + 1;
+ }
+ }
+ }
+ }
+
+ // either no more tokens or the scan was stopped by something we couldn't
+ // parse - in any case, see if we can construct a date from what we have
+ if ( !haveDay && !haveWDay )
+ {
+ wxLogDebug(_T("no day, no weekday hence no date."));
+
+ return (wxChar *)NULL;
+ }
+
+ if ( haveWDay && (haveMon || haveYear || haveDay) &&
+ !(haveMon && haveMon && haveYear) )
+ {
+ // without adjectives (which we don't support here) the week day only
+ // makes sense completely separately or with the full date
+ // specification (what would "Wed 1999" mean?)
+ return (wxChar *)NULL;
+ }
+
+ if ( !haveMon )
+ {
+ mon = GetCurrentMonth();
+ }
+
+ if ( !haveYear )
+ {
+ year = GetCurrentYear();
+ }
+
+ if ( haveDay )
+ {
+ Set(day, mon, year);
+
+ if ( haveWDay )
+ {
+ // check that it is really the same
+ if ( GetWeekDay() != wday )
+ {
+ // inconsistency detected
+ return (wxChar *)NULL;
+ }
+ }
+ }
+ else // haveWDay
+ {
+ *this = Today();
+
+ SetToWeekDayInSameWeek(wday);
+ }
+
+ // return the pointer to the next char
+ return p + wxStrlen(p) - wxStrlen(tok.GetString());
+}
+
+const wxChar *wxDateTime::ParseTime(const wxChar *time)
+{
+ wxCHECK_MSG( time, (wxChar *)NULL, _T("NULL pointer in wxDateTime::Parse") );
+
+ // first try some extra things
+ static const struct
+ {
+ const wxChar *name;
+ wxDateTime_t hour;
+ } stdTimes[] =
+ {
+ { wxTRANSLATE("noon"), 12 },
+ { wxTRANSLATE("midnight"), 00 },
+ // anything else?
+ };
+
+ for ( size_t n = 0; n < WXSIZEOF(stdTimes); n++ )
+ {
+ wxString timeString = wxGetTranslation(stdTimes[n].name);
+ size_t len = timeString.length();
+ if ( timeString.CmpNoCase(wxString(time, len)) == 0 )
+ {
+ Set(stdTimes[n].hour, 0, 0);
+
+ return time + len;
+ }
+ }
+
+ // try all time formats we may think about starting with the standard one
+ const wxChar *result = ParseFormat(time, _T("%X"));
+ if ( !result )
+ {
+ // normally, it's the same, but why not try it?
+ result = ParseFormat(time, _T("%H:%M:%S"));
+ }
+
+ if ( !result )
+ {
+ // 12hour with AM/PM?
+ result = ParseFormat(time, _T("%I:%M:%S %p"));
+ }
+
+ if ( !result )
+ {
+ // without seconds?
+ result = ParseFormat(time, _T("%H:%M"));
+ }
+
+ if ( !result )
+ {
+ // 12hour with AM/PM but without seconds?
+ result = ParseFormat(time, _T("%I:%M %p"));
+ }
+
+ if ( !result )
+ {
+ // just the hour?
+ result = ParseFormat(time, _T("%H"));
+ }
+
+ if ( !result )
+ {
+ // just the hour and AM/PM?
+ result = ParseFormat(time, _T("%I %p"));
+ }
+
+ // TODO: parse timezones
+
+ return result;