X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/8208e181cb576ec6cda37624923f95a59af43072..bbc3925a8a46e8c80b22277f5571dfb2465aca25:/src/common/datetime.cpp diff --git a/src/common/datetime.cpp b/src/common/datetime.cpp index 5326de0e31..418a72f51c 100644 --- a/src/common/datetime.cpp +++ b/src/common/datetime.cpp @@ -63,19 +63,24 @@ #pragma hdrstop #endif +#if !defined(wxUSE_DATETIME) || wxUSE_DATETIME + #ifndef WX_PRECOMP #include "wx/string.h" - #include "wx/intl.h" #include "wx/log.h" #endif // WX_PRECOMP +#include "wx/intl.h" #include "wx/thread.h" #include "wx/tokenzr.h" #include "wx/module.h" #define wxDEFINE_TIME_CONSTANTS // before including datetime.h +#include + #include "wx/datetime.h" +#include "wx/timer.h" // for wxGetLocalTimeMillis() // ---------------------------------------------------------------------------- // conditional compilation @@ -96,13 +101,41 @@ #if defined(__BORLANDC__) || defined(__MINGW32__) || defined(__VISAGECPP__) #define WX_TIMEZONE _timezone #elif defined(__MWERKS__) - long wxmw_timezone = 28800; - #define WX_TIMEZONE wxmw_timezone ; + long wxmw_timezone = 28800; + #define WX_TIMEZONE wxmw_timezone + #elif defined(__DJGPP__) + #include + #include + static long wxGetTimeZone() + { + static long timezone = MAXLONG; // invalid timezone + if (timezone == MAXLONG) + { + struct timeb tb; + ftime(&tb); + timezone = tb.timezone; + } + return timezone; + } + #define WX_TIMEZONE wxGetTimeZone() #else // unknown platform - try timezone #define WX_TIMEZONE timezone #endif #endif // !WX_TIMEZONE +// ---------------------------------------------------------------------------- +// macros +// ---------------------------------------------------------------------------- + +// debugging helper: just a convenient replacement of wxCHECK() +#define wxDATETIME_CHECK(expr, msg) \ + if ( !(expr) ) \ + { \ + wxFAIL_MSG(msg); \ + *this = wxInvalidDateTime; \ + return *this; \ + } + // ---------------------------------------------------------------------------- // private classes // ---------------------------------------------------------------------------- @@ -120,6 +153,7 @@ public: virtual void OnExit() { wxDateTimeHolidayAuthority::ClearAllAuthorities(); + wxDateTimeHolidayAuthority::ms_authorities.Clear(); } private: @@ -135,10 +169,16 @@ IMPLEMENT_DYNAMIC_CLASS(wxDateTimeHolidaysModule, wxModule) // some trivial ones static const int MONTHS_IN_YEAR = 12; -static const int SECONDS_IN_MINUTE = 60; +static const int SEC_PER_MIN = 60; + +static const int MIN_PER_HOUR = 60; + +static const int HOURS_PER_DAY = 24; static const long SECONDS_PER_DAY = 86400l; +static const int DAYS_PER_WEEK = 7; + static const long MILLISECONDS_PER_DAY = 86400000l; // this is the integral part of JDN of the midnight of Jan 1, 1970 @@ -169,9 +209,9 @@ static const wxDateTime::wxDateTime_t gs_cumulatedDays[2][MONTHS_IN_YEAR] = // global data // ---------------------------------------------------------------------------- -static wxDateTime gs_dtDefault; - -wxDateTime& wxDefaultDateTime = gs_dtDefault; +// in the fine tradition of ANSI C we use our equivalent of (time_t)-1 to +// indicate an invalid wxDateTime object +const wxDateTime wxDefaultDateTime; wxDateTime::Country wxDateTime::ms_country = wxDateTime::Country_Unknown; @@ -228,9 +268,9 @@ static int GetTimeZone() { // just call localtime() instead of figuring out whether this system // supports tzset(), _tzset() or something else - time_t t; - (void)localtime(&t); + time_t t = 0; + (void)localtime(&t); s_timezoneSet = TRUE; } @@ -387,13 +427,17 @@ static wxDateTime::WeekDay GetWeekDayFromName(const wxString& name, int flags) return wd; } -// scans all digits and returns the resulting number -static bool GetNumericToken(const wxChar*& p, unsigned long *number) +// scans all digits (but no more than len) and returns the resulting number +static bool GetNumericToken(size_t len, const wxChar*& p, unsigned long *number) { + size_t n = 1; wxString s; while ( wxIsdigit(*p) ) { s += *p++; + + if ( len && ++n > len ) + break; } return !!s && s.ToULong(number); @@ -1029,11 +1073,15 @@ wxDateTime wxDateTime::GetEndDST(int year, Country country) // constructors and assignment operators // ---------------------------------------------------------------------------- +// return the current time with ms precision +/* static */ wxDateTime wxDateTime::UNow() +{ + return wxDateTime(wxGetLocalTimeMillis()); +} + // the values in the tm structure contain the local time wxDateTime& wxDateTime::Set(const struct tm& tm) { - wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") ); - struct tm tm2(tm); time_t timet = mktime(&tm2); @@ -1057,7 +1105,9 @@ wxDateTime& wxDateTime::Set(const struct tm& tm) wxFAIL_MSG( _T("mktime() failed") ); - return wxInvalidDateTime; + *this = wxInvalidDateTime; + + return *this; } else { @@ -1070,18 +1120,18 @@ wxDateTime& wxDateTime::Set(wxDateTime_t hour, wxDateTime_t second, wxDateTime_t millisec) { - wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") ); - // we allow seconds to be 61 to account for the leap seconds, even if we // don't use them really - wxCHECK_MSG( hour < 24 && second < 62 && minute < 60 && millisec < 1000, - wxInvalidDateTime, - _T("Invalid time in wxDateTime::Set()") ); + wxDATETIME_CHECK( hour < 24 && + second < 62 && + minute < 60 && + millisec < 1000, + _T("Invalid time in wxDateTime::Set()") ); // get the current date from system struct tm *tm = GetTmNow(); - wxCHECK_MSG( tm, wxInvalidDateTime, _T("localtime() failed") ); + wxDATETIME_CHECK( tm, _T("localtime() failed") ); // adjust the time tm->tm_hour = hour; @@ -1102,17 +1152,16 @@ wxDateTime& wxDateTime::Set(wxDateTime_t day, wxDateTime_t second, wxDateTime_t millisec) { - wxASSERT_MSG( IsValid(), _T("invalid wxDateTime") ); - - wxCHECK_MSG( hour < 24 && second < 62 && minute < 60 && millisec < 1000, - wxInvalidDateTime, - _T("Invalid time in wxDateTime::Set()") ); + wxDATETIME_CHECK( hour < 24 && + second < 62 && + minute < 60 && + millisec < 1000, + _T("Invalid time in wxDateTime::Set()") ); ReplaceDefaultYearMonthWithCurrent(&year, &month); - wxCHECK_MSG( (0 < day) && (day <= GetNumberOfDays(month, year)), - wxInvalidDateTime, - _T("Invalid date in wxDateTime::Set()") ); + wxDATETIME_CHECK( (0 < day) && (day <= GetNumberOfDays(month, year)), + _T("Invalid date in wxDateTime::Set()") ); // the range of time_t type (inclusive) static const int yearMinInRange = 1970; @@ -1204,12 +1253,12 @@ wxDateTime::Tm wxDateTime::GetTm(const TimeZone& tz) const tm = localtime(&time); // should never happen - wxCHECK_MSG( tm, Tm(), _T("gmtime() failed") ); + wxCHECK_MSG( tm, Tm(), _T("localtime() failed") ); } else { time += (time_t)tz.GetOffset(); -#ifdef __VMS__ // time is unsigned so avoid warning +#if defined(__VMS__) || defined(__WATCOMC__) // time is unsigned so avoid warning int time2 = (int) time; if ( time2 >= 0 ) #else @@ -1229,7 +1278,11 @@ wxDateTime::Tm wxDateTime::GetTm(const TimeZone& tz) const if ( tm ) { - return Tm(*tm, tz); + // adjust the milliseconds + Tm tm2(*tm, tz); + long timeOnly = (m_time % MILLISECONDS_PER_DAY).ToLong(); + tm2.msec = (wxDateTime_t)(timeOnly % 1000); + return tm2; } //else: use generic code below } @@ -1291,8 +1344,6 @@ wxDateTime::Tm wxDateTime::GetTm(const TimeZone& tz) const // check that the algorithm gave us something reasonable wxASSERT_MSG( (0 < month) && (month <= 12), _T("invalid month") ); wxASSERT_MSG( (1 <= day) && (day < 32), _T("invalid day") ); - wxASSERT_MSG( (INT_MIN <= year) && (year <= INT_MAX), - _T("year range overflow") ); // construct Tm from these values Tm tm; @@ -1461,7 +1512,7 @@ wxDateTime& wxDateTime::SetToLastMonthDay(Month month, wxDateTime& wxDateTime::SetToWeekDayInSameWeek(WeekDay weekday) { - wxCHECK_MSG( weekday != Inv_WeekDay, wxInvalidDateTime, _T("invalid weekday") ); + wxDATETIME_CHECK( weekday != Inv_WeekDay, _T("invalid weekday") ); WeekDay wdayThis = GetWeekDay(); if ( weekday == wdayThis ) @@ -1471,7 +1522,7 @@ wxDateTime& wxDateTime::SetToWeekDayInSameWeek(WeekDay weekday) } else if ( weekday < wdayThis ) { - return Substract(wxDateSpan::Days(wdayThis - weekday)); + return Subtract(wxDateSpan::Days(wdayThis - weekday)); } else // weekday > wdayThis { @@ -1481,7 +1532,7 @@ wxDateTime& wxDateTime::SetToWeekDayInSameWeek(WeekDay weekday) wxDateTime& wxDateTime::SetToNextWeekDay(WeekDay weekday) { - wxCHECK_MSG( weekday != Inv_WeekDay, wxInvalidDateTime, _T("invalid weekday") ); + wxDATETIME_CHECK( weekday != Inv_WeekDay, _T("invalid weekday") ); int diff; WeekDay wdayThis = GetWeekDay(); @@ -1505,7 +1556,7 @@ wxDateTime& wxDateTime::SetToNextWeekDay(WeekDay weekday) wxDateTime& wxDateTime::SetToPrevWeekDay(WeekDay weekday) { - wxCHECK_MSG( weekday != Inv_WeekDay, wxInvalidDateTime, _T("invalid weekday") ); + wxDATETIME_CHECK( weekday != Inv_WeekDay, _T("invalid weekday") ); int diff; WeekDay wdayThis = GetWeekDay(); @@ -1524,7 +1575,7 @@ wxDateTime& wxDateTime::SetToPrevWeekDay(WeekDay weekday) diff = wdayThis - weekday; } - return Substract(wxDateSpan::Days(diff)); + return Subtract(wxDateSpan::Days(diff)); } bool wxDateTime::SetToWeekDay(WeekDay weekday, @@ -1639,27 +1690,22 @@ wxDateTime::wxDateTime_t wxDateTime::GetWeekOfMonth(wxDateTime::WeekFlags flags, { Tm tm = GetTm(tz); wxDateTime dtMonthStart = wxDateTime(1, tm.mon, tm.year); - size_t nWeek = GetWeekOfYear(flags) - dtMonthStart.GetWeekOfYear(flags) + 1; -#ifdef __VMS__ // nWeek is unsigned so avoid the warning - int nweek2 = (int) nWeek; - if ( nweek2 < 0 ) -#else + int nWeek = GetWeekOfYear(flags) - dtMonthStart.GetWeekOfYear(flags) + 1; if ( nWeek < 0 ) -#endif { // this may happen for January when Jan, 1 is the last week of the // previous year nWeek += IsLeapYear(tm.year - 1) ? 53 : 52; } - return nWeek; + return (wxDateTime::wxDateTime_t)nWeek; } wxDateTime& wxDateTime::SetToYearDay(wxDateTime::wxDateTime_t yday) { int year = GetYear(); - wxCHECK_MSG( (0 < yday) && (yday <= GetNumberOfDays(year)), - wxInvalidDateTime, _T("invalid year day") ); + wxDATETIME_CHECK( (0 < yday) && (yday <= GetNumberOfDays(year)), + _T("invalid year day") ); bool isLeap = IsLeapYear(year); for ( Month mon = Jan; mon < Inv_Month; wxNextMonth(mon) ) @@ -1747,7 +1793,7 @@ wxDateTime& wxDateTime::MakeTimezone(const TimeZone& tz, bool noDST) secDiff -= 3600; } - return Substract(wxTimeSpan::Seconds(secDiff)); + return Subtract(wxTimeSpan::Seconds(secDiff)); } // ---------------------------------------------------------------------------- @@ -1758,8 +1804,10 @@ wxString wxDateTime::Format(const wxChar *format, const TimeZone& tz) const { wxCHECK_MSG( format, _T(""), _T("NULL format in wxDateTime::Format") ); + // we have to use our own implementation if the date is out of range of + // strftime() or if we use non standard specificators time_t time = GetTicks(); - if ( time != (time_t)-1 ) + if ( (time != (time_t)-1) && !wxStrstr(format, _T("%l")) ) { // use strftime() tm *tm; @@ -1775,7 +1823,7 @@ wxString wxDateTime::Format(const wxChar *format, const TimeZone& tz) const { time += (int)tz.GetOffset(); -#ifdef __VMS__ // time is unsigned so avoid the warning +#if defined(__VMS__) || defined(__WATCOMC__) // time is unsigned so avoid warning int time2 = (int) time; if ( time2 >= 0 ) #else @@ -1801,7 +1849,8 @@ wxString wxDateTime::Format(const wxChar *format, const TimeZone& tz) const } // we only parse ANSI C format specifications here, no POSIX 2 - // complications, no GNU extensions + // complications, no GNU extensions but we do add support for a "%l" format + // specifier allowing to get the number of milliseconds Tm tm = GetTm(tz); // used for calls to strftime() when we only deal with time @@ -1835,6 +1884,7 @@ wxString wxDateTime::Format(const wxChar *format, const TimeZone& tz) const break; case _T('j'): // day of year has 3 digits + case _T('l'): // milliseconds have 3 digits fmt = _T("%03d"); break; @@ -1846,257 +1896,268 @@ wxString wxDateTime::Format(const wxChar *format, const TimeZone& tz) const fmt = _T("%02d"); } -restart: - // start of the format specification - switch ( *p ) + bool restart = TRUE; + while ( restart ) { - case _T('a'): // a weekday name - case _T('A'): - // second parameter should be TRUE for abbreviated names - res += GetWeekDayName(tm.GetWeekDay(), - *p == _T('a') ? Name_Abbr : Name_Full); - break; + restart = FALSE; - case _T('b'): // a month name - case _T('B'): - res += GetMonthName(tm.mon, - *p == _T('b') ? Name_Abbr : Name_Full); - break; + // start of the format specification + switch ( *p ) + { + case _T('a'): // a weekday name + case _T('A'): + // second parameter should be TRUE for abbreviated names + res += GetWeekDayName(tm.GetWeekDay(), + *p == _T('a') ? Name_Abbr : Name_Full); + break; - case _T('c'): // locale default date and time representation - case _T('x'): // locale default date representation - // - // the problem: there is no way to know what do these format - // specifications correspond to for the current locale. - // - // the solution: use a hack and still use strftime(): first - // find the YEAR which is a year in the strftime() range (1970 - // - 2038) whose Jan 1 falls on the same week day as the Jan 1 - // of the real year. Then make a copy of the format and - // replace all occurences of YEAR in it with some unique - // string not appearing anywhere else in it, then use - // strftime() to format the date in year YEAR and then replace - // YEAR back by the real year and the unique replacement - // string back with YEAR. Notice that "all occurences of YEAR" - // means all occurences of 4 digit as well as 2 digit form! - // - // the bugs: we assume that neither of %c nor %x contains any - // fields which may change between the YEAR and real year. For - // example, the week number (%U, %W) and the day number (%j) - // will change if one of these years is leap and the other one - // is not! - { - // find the YEAR: normally, for any year X, Jan 1 or the - // year X + 28 is the same weekday as Jan 1 of X (because - // the weekday advances by 1 for each normal X and by 2 - // for each leap X, hence by 5 every 4 years or by 35 - // which is 0 mod 7 every 28 years) but this rule breaks - // down if there are years between X and Y which are - // divisible by 4 but not leap (i.e. divisible by 100 but - // not 400), hence the correction. - - int yearReal = GetYear(tz); - int mod28 = yearReal % 28; - - // be careful to not go too far - we risk to leave the - // supported range - int year; - if ( mod28 < 10 ) - { - year = 1988 + mod28; // 1988 == 0 (mod 28) - } - else + case _T('b'): // a month name + case _T('B'): + res += GetMonthName(tm.mon, + *p == _T('b') ? Name_Abbr : Name_Full); + break; + + case _T('c'): // locale default date and time representation + case _T('x'): // locale default date representation + // + // the problem: there is no way to know what do these format + // specifications correspond to for the current locale. + // + // the solution: use a hack and still use strftime(): first + // find the YEAR which is a year in the strftime() range (1970 + // - 2038) whose Jan 1 falls on the same week day as the Jan 1 + // of the real year. Then make a copy of the format and + // replace all occurences of YEAR in it with some unique + // string not appearing anywhere else in it, then use + // strftime() to format the date in year YEAR and then replace + // YEAR back by the real year and the unique replacement + // string back with YEAR. Notice that "all occurences of YEAR" + // means all occurences of 4 digit as well as 2 digit form! + // + // the bugs: we assume that neither of %c nor %x contains any + // fields which may change between the YEAR and real year. For + // example, the week number (%U, %W) and the day number (%j) + // will change if one of these years is leap and the other one + // is not! { - year = 1970 + mod28 - 10; // 1970 == 10 (mod 28) - } + // find the YEAR: normally, for any year X, Jan 1 or the + // year X + 28 is the same weekday as Jan 1 of X (because + // the weekday advances by 1 for each normal X and by 2 + // for each leap X, hence by 5 every 4 years or by 35 + // which is 0 mod 7 every 28 years) but this rule breaks + // down if there are years between X and Y which are + // divisible by 4 but not leap (i.e. divisible by 100 but + // not 400), hence the correction. + + int yearReal = GetYear(tz); + int mod28 = yearReal % 28; + + // be careful to not go too far - we risk to leave the + // supported range + int year; + if ( mod28 < 10 ) + { + year = 1988 + mod28; // 1988 == 0 (mod 28) + } + else + { + year = 1970 + mod28 - 10; // 1970 == 10 (mod 28) + } - int nCentury = year / 100, - nCenturyReal = yearReal / 100; + int nCentury = year / 100, + nCenturyReal = yearReal / 100; - // need to adjust for the years divisble by 400 which are - // not leap but are counted like leap ones if we just take - // the number of centuries in between for nLostWeekDays - int nLostWeekDays = (nCentury - nCenturyReal) - - (nCentury / 4 - nCenturyReal / 4); + // need to adjust for the years divisble by 400 which are + // not leap but are counted like leap ones if we just take + // the number of centuries in between for nLostWeekDays + int nLostWeekDays = (nCentury - nCenturyReal) - + (nCentury / 4 - nCenturyReal / 4); - // we have to gain back the "lost" weekdays: note that the - // effect of this loop is to not do anything to - // nLostWeekDays (which we won't use any more), but to - // (indirectly) set the year correctly - while ( (nLostWeekDays % 7) != 0 ) - { - nLostWeekDays += year++ % 4 ? 1 : 2; - } + // we have to gain back the "lost" weekdays: note that the + // effect of this loop is to not do anything to + // nLostWeekDays (which we won't use any more), but to + // (indirectly) set the year correctly + while ( (nLostWeekDays % 7) != 0 ) + { + nLostWeekDays += year++ % 4 ? 1 : 2; + } - // at any rate, we couldn't go further than 1988 + 9 + 28! - wxASSERT_MSG( year < 2030, - _T("logic error in wxDateTime::Format") ); + // at any rate, we couldn't go further than 1988 + 9 + 28! + wxASSERT_MSG( year < 2030, + _T("logic error in wxDateTime::Format") ); - wxString strYear, strYear2; - strYear.Printf(_T("%d"), year); - strYear2.Printf(_T("%d"), year % 100); + wxString strYear, strYear2; + strYear.Printf(_T("%d"), year); + strYear2.Printf(_T("%d"), year % 100); - // find two strings not occuring in format (this is surely - // not optimal way of doing it... improvements welcome!) - wxString fmt = format; - wxString replacement = (wxChar)-1; - while ( fmt.Find(replacement) != wxNOT_FOUND ) - { - replacement << (wxChar)-1; - } + // find two strings not occuring in format (this is surely + // not optimal way of doing it... improvements welcome!) + wxString fmt = format; + wxString replacement = (wxChar)-1; + while ( fmt.Find(replacement) != wxNOT_FOUND ) + { + replacement << (wxChar)-1; + } - wxString replacement2 = (wxChar)-2; - while ( fmt.Find(replacement) != wxNOT_FOUND ) - { - replacement << (wxChar)-2; - } + wxString replacement2 = (wxChar)-2; + while ( fmt.Find(replacement) != wxNOT_FOUND ) + { + replacement << (wxChar)-2; + } - // replace all occurences of year with it - bool wasReplaced = fmt.Replace(strYear, replacement) > 0; - if ( !wasReplaced ) - wasReplaced = fmt.Replace(strYear2, replacement2) > 0; + // replace all occurences of year with it + bool wasReplaced = fmt.Replace(strYear, replacement) > 0; + if ( !wasReplaced ) + wasReplaced = fmt.Replace(strYear2, replacement2) > 0; + + // use strftime() to format the same date but in supported + // year + // + // NB: we assume that strftime() doesn't check for the + // date validity and will happily format the date + // corresponding to Feb 29 of a non leap year (which + // may happen if yearReal was leap and year is not) + struct tm tmAdjusted; + InitTm(tmAdjusted); + tmAdjusted.tm_hour = tm.hour; + tmAdjusted.tm_min = tm.min; + tmAdjusted.tm_sec = tm.sec; + tmAdjusted.tm_wday = tm.GetWeekDay(); + tmAdjusted.tm_yday = GetDayOfYear(); + tmAdjusted.tm_mday = tm.mday; + tmAdjusted.tm_mon = tm.mon; + tmAdjusted.tm_year = year - 1900; + tmAdjusted.tm_isdst = 0; // no DST, already adjusted + wxString str = CallStrftime(*p == _T('c') ? _T("%c") + : _T("%x"), + &tmAdjusted); + + // now replace the occurence of 1999 with the real year + wxString strYearReal, strYearReal2; + strYearReal.Printf(_T("%04d"), yearReal); + strYearReal2.Printf(_T("%02d"), yearReal % 100); + str.Replace(strYear, strYearReal); + str.Replace(strYear2, strYearReal2); + + // and replace back all occurences of replacement string + if ( wasReplaced ) + { + str.Replace(replacement2, strYear2); + str.Replace(replacement, strYear); + } - // use strftime() to format the same date but in supported - // year - // - // NB: we assume that strftime() doesn't check for the - // date validity and will happily format the date - // corresponding to Feb 29 of a non leap year (which - // may happen if yearReal was leap and year is not) - struct tm tmAdjusted; - InitTm(tmAdjusted); - tmAdjusted.tm_hour = tm.hour; - tmAdjusted.tm_min = tm.min; - tmAdjusted.tm_sec = tm.sec; - tmAdjusted.tm_wday = tm.GetWeekDay(); - tmAdjusted.tm_yday = GetDayOfYear(); - tmAdjusted.tm_mday = tm.mday; - tmAdjusted.tm_mon = tm.mon; - tmAdjusted.tm_year = year - 1900; - tmAdjusted.tm_isdst = 0; // no DST, already adjusted - wxString str = CallStrftime(*p == _T('c') ? _T("%c") - : _T("%x"), - &tmAdjusted); - - // now replace the occurence of 1999 with the real year - wxString strYearReal, strYearReal2; - strYearReal.Printf(_T("%04d"), yearReal); - strYearReal2.Printf(_T("%02d"), yearReal % 100); - str.Replace(strYear, strYearReal); - str.Replace(strYear2, strYearReal2); - - // and replace back all occurences of replacement string - if ( wasReplaced ) - { - str.Replace(replacement2, strYear2); - str.Replace(replacement, strYear); + res += str; } + break; - res += str; - } - break; + case _T('d'): // day of a month (01-31) + res += wxString::Format(fmt, tm.mday); + break; - case _T('d'): // day of a month (01-31) - res += wxString::Format(fmt, tm.mday); - break; + case _T('H'): // hour in 24h format (00-23) + res += wxString::Format(fmt, tm.hour); + break; - case _T('H'): // hour in 24h format (00-23) - res += wxString::Format(fmt, tm.hour); - break; + case _T('I'): // hour in 12h format (01-12) + { + // 24h -> 12h, 0h -> 12h too + int hour12 = tm.hour > 12 ? tm.hour - 12 + : tm.hour ? tm.hour : 12; + res += wxString::Format(fmt, hour12); + } + break; - case _T('I'): // hour in 12h format (01-12) - { - // 24h -> 12h, 0h -> 12h too - int hour12 = tm.hour > 12 ? tm.hour - 12 - : tm.hour ? tm.hour : 12; - res += wxString::Format(fmt, hour12); - } - break; + case _T('j'): // day of the year + res += wxString::Format(fmt, GetDayOfYear(tz)); + break; - case _T('j'): // day of the year - res += wxString::Format(fmt, GetDayOfYear(tz)); - break; + case _T('l'): // milliseconds (NOT STANDARD) + res += wxString::Format(fmt, GetMillisecond(tz)); + break; - case _T('m'): // month as a number (01-12) - res += wxString::Format(fmt, tm.mon + 1); - break; + case _T('m'): // month as a number (01-12) + res += wxString::Format(fmt, tm.mon + 1); + break; - case _T('M'): // minute as a decimal number (00-59) - res += wxString::Format(fmt, tm.min); - break; + case _T('M'): // minute as a decimal number (00-59) + res += wxString::Format(fmt, tm.min); + break; - case _T('p'): // AM or PM string - res += CallStrftime(_T("%p"), &tmTimeOnly); - break; + case _T('p'): // AM or PM string + res += CallStrftime(_T("%p"), &tmTimeOnly); + break; - case _T('S'): // second as a decimal number (00-61) - res += wxString::Format(fmt, tm.sec); - break; + case _T('S'): // second as a decimal number (00-61) + res += wxString::Format(fmt, tm.sec); + break; - case _T('U'): // week number in the year (Sunday 1st week day) - res += wxString::Format(fmt, GetWeekOfYear(Sunday_First, tz)); - break; + case _T('U'): // week number in the year (Sunday 1st week day) + res += wxString::Format(fmt, GetWeekOfYear(Sunday_First, tz)); + break; - case _T('W'): // week number in the year (Monday 1st week day) - res += wxString::Format(fmt, GetWeekOfYear(Monday_First, tz)); - break; + case _T('W'): // week number in the year (Monday 1st week day) + res += wxString::Format(fmt, GetWeekOfYear(Monday_First, tz)); + break; - case _T('w'): // weekday as a number (0-6), Sunday = 0 - res += wxString::Format(fmt, tm.GetWeekDay()); - break; + case _T('w'): // weekday as a number (0-6), Sunday = 0 + res += wxString::Format(fmt, tm.GetWeekDay()); + break; - // case _T('x'): -- handled with "%c" + // case _T('x'): -- handled with "%c" - case _T('X'): // locale default time representation - // just use strftime() to format the time for us - res += CallStrftime(_T("%X"), &tmTimeOnly); - break; + case _T('X'): // locale default time representation + // just use strftime() to format the time for us + res += CallStrftime(_T("%X"), &tmTimeOnly); + break; - case _T('y'): // year without century (00-99) - res += wxString::Format(fmt, tm.year % 100); - break; + case _T('y'): // year without century (00-99) + res += wxString::Format(fmt, tm.year % 100); + break; - case _T('Y'): // year with century - res += wxString::Format(fmt, tm.year); - break; + case _T('Y'): // year with century + res += wxString::Format(fmt, tm.year); + break; - case _T('Z'): // timezone name - res += CallStrftime(_T("%Z"), &tmTimeOnly); - break; + case _T('Z'): // timezone name + res += CallStrftime(_T("%Z"), &tmTimeOnly); + break; - default: - // is it the format width? - fmt.Empty(); - while ( *p == _T('-') || *p == _T('+') || - *p == _T(' ') || wxIsdigit(*p) ) - { - fmt += *p; - } + default: + // is it the format width? + fmt.Empty(); + while ( *p == _T('-') || *p == _T('+') || + *p == _T(' ') || wxIsdigit(*p) ) + { + fmt += *p; + } - if ( !fmt.IsEmpty() ) - { - // we've only got the flags and width so far in fmt - fmt.Prepend(_T('%')); - fmt.Append(_T('d')); + if ( !fmt.IsEmpty() ) + { + // we've only got the flags and width so far in fmt + fmt.Prepend(_T('%')); + fmt.Append(_T('d')); - goto restart; - } + restart = TRUE; - // no, it wasn't the width - wxFAIL_MSG(_T("unknown format specificator")); + break; + } - // fall through and just copy it nevertheless + // no, it wasn't the width + wxFAIL_MSG(_T("unknown format specificator")); - case _T('%'): // a percent sign - res += *p; - break; + // fall through and just copy it nevertheless - case 0: // the end of string - wxFAIL_MSG(_T("missing format at the end of string")); + case _T('%'): // a percent sign + res += *p; + break; - // just put the '%' which was the last char in format - res += _T('%'); - break; + case 0: // the end of string + wxFAIL_MSG(_T("missing format at the end of string")); + + // just put the '%' which was the last char in format + res += _T('%'); + break; + } } } @@ -2461,7 +2522,17 @@ const wxChar *wxDateTime::ParseFormat(const wxChar *date, } // start of a format specification - switch ( *++fmt ) + + // 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'): @@ -2534,7 +2605,8 @@ const wxChar *wxDateTime::ParseFormat(const wxChar *date, break; case _T('d'): // day of a month (01-31) - if ( !GetNumericToken(input, &num) || (num > 31) ) + if ( !GetNumericToken(width, input, &num) || + (num > 31) || (num < 1) ) { // no match return (wxChar *)NULL; @@ -2547,7 +2619,7 @@ const wxChar *wxDateTime::ParseFormat(const wxChar *date, break; case _T('H'): // hour in 24h format (00-23) - if ( !GetNumericToken(input, &num) || (num > 23) ) + if ( !GetNumericToken(width, input, &num) || (num > 23) ) { // no match return (wxChar *)NULL; @@ -2558,7 +2630,7 @@ const wxChar *wxDateTime::ParseFormat(const wxChar *date, break; case _T('I'): // hour in 12h format (01-12) - if ( !GetNumericToken(input, &num) || !num || (num > 12) ) + if ( !GetNumericToken(width, input, &num) || !num || (num > 12) ) { // no match return (wxChar *)NULL; @@ -2570,7 +2642,7 @@ const wxChar *wxDateTime::ParseFormat(const wxChar *date, break; case _T('j'): // day of the year - if ( !GetNumericToken(input, &num) || !num || (num > 366) ) + if ( !GetNumericToken(width, input, &num) || !num || (num > 366) ) { // no match return (wxChar *)NULL; @@ -2581,7 +2653,7 @@ const wxChar *wxDateTime::ParseFormat(const wxChar *date, break; case _T('m'): // month as a number (01-12) - if ( !GetNumericToken(input, &num) || !num || (num > 12) ) + if ( !GetNumericToken(width, input, &num) || !num || (num > 12) ) { // no match return (wxChar *)NULL; @@ -2592,7 +2664,7 @@ const wxChar *wxDateTime::ParseFormat(const wxChar *date, break; case _T('M'): // minute as a decimal number (00-59) - if ( !GetNumericToken(input, &num) || (num > 59) ) + if ( !GetNumericToken(width, input, &num) || (num > 59) ) { // no match return (wxChar *)NULL; @@ -2656,7 +2728,7 @@ const wxChar *wxDateTime::ParseFormat(const wxChar *date, } case _T('S'): // second as a decimal number (00-61) - if ( !GetNumericToken(input, &num) || (num > 61) ) + if ( !GetNumericToken(width, input, &num) || (num > 61) ) { // no match return (wxChar *)NULL; @@ -2686,7 +2758,7 @@ const wxChar *wxDateTime::ParseFormat(const wxChar *date, break; case _T('w'): // weekday as a number (0-6), Sunday = 0 - if ( !GetNumericToken(input, &num) || (wday > 6) ) + if ( !GetNumericToken(width, input, &num) || (wday > 6) ) { // no match return (wxChar *)NULL; @@ -2815,18 +2887,21 @@ const wxChar *wxDateTime::ParseFormat(const wxChar *date, break; case _T('y'): // year without century (00-99) - if ( !GetNumericToken(input, &num) || (num > 99) ) + if ( !GetNumericToken(width, input, &num) || (num > 99) ) { // no match return (wxChar *)NULL; } haveYear = TRUE; - year = 1900 + (wxDateTime_t)num; + + // 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(input, &num) ) + if ( !GetNumericToken(width, input, &num) ) { // no match return (wxChar *)NULL; @@ -2865,7 +2940,7 @@ const wxChar *wxDateTime::ParseFormat(const wxChar *date, // take this date as default tmDef = dateDef.GetTm(); } - else if ( m_time != 0 ) + else if ( IsValid() ) { // if this date is valid, don't change it tmDef = GetTm(); @@ -2982,7 +3057,7 @@ const wxChar *wxDateTime::ParseDate(const wxChar *date) { wxString date = wxGetTranslation(literalDates[n].str); size_t len = date.length(); - if ( wxString(p, len).CmpNoCase(date) == 0 ) + if ( wxStrlen(p) >= len && (wxString(p, len).CmpNoCase(date) == 0) ) { // nothing can follow this, so stop here p += len; @@ -2998,6 +3073,11 @@ const wxChar *wxDateTime::ParseDate(const wxChar *date) } } + // We try to guess what we have here: for each new (numeric) token, we + // determine if it can be a month, day or a year. Of course, there is an + // ambiguity as some numbers may be days as well as months, so we also + // have the ability to back track. + // what do we have? bool haveDay = FALSE, // the months day? haveWDay = FALSE, // the day of week? @@ -3011,10 +3091,14 @@ const wxChar *wxDateTime::ParseDate(const wxChar *date) int year = 0; // tokenize the string - wxStringTokenizer tok(p, _T(",/-\t\n ")); + size_t nPosCur = 0; + static const wxChar *dateDelimiters = _T(".,/-\t\n "); + wxStringTokenizer tok(p, dateDelimiters); while ( tok.HasMoreTokens() ) { wxString token = tok.GetNextToken(); + if ( !token ) + continue; // is it a number? unsigned long val; @@ -3024,125 +3108,85 @@ const wxChar *wxDateTime::ParseDate(const wxChar *date) bool isDay = FALSE, isMonth = FALSE, - // only years are counted from 0 - isYear = (val == 0) || (val > 31); - if ( !isYear ) + isYear = FALSE; + + if ( !haveMon && val > 0 && val <= 12 ) { - // 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 ) + // assume it is month + isMonth = TRUE; + } + else // not the month + { + wxDateTime_t maxDays = haveMon + ? GetNumOfDaysInMonth(haveYear ? year : Inv_Year, mon) + : 31; + + // can it be day? + if ( (val == 0) || (val > (unsigned long)maxDays) ) // cast to shut up compiler warning in BCC { - // we already have the day, so may only be a month or year - if ( val > 12 ) - { - isYear = TRUE; - } - else - { - isMonth = TRUE; - } + isYear = TRUE; } - else // it may be day + else { 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 = (wxDateTime_t)val; } - else if ( isMonth ) + else if ( isDay ) { - if ( haveMon ) - { + if ( haveDay ) break; - } - haveMon = TRUE; + haveDay = TRUE; - mon = (wxDateTime::Month)val; + day = (wxDateTime_t)val; } - else + else if ( isMonth ) { - 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; + haveMon = TRUE; - day = (wxDateTime_t)val; + mon = (Month)(val - 1); } } else // not a number { - mon = GetMonthFromName(token, Name_Full | Name_Abbr); - if ( mon != Inv_Month ) + // be careful not to overwrite the current mon value + Month mon2 = GetMonthFromName(token, Name_Full | Name_Abbr); + if ( mon2 != Inv_Month ) { // it's a month if ( haveMon ) { - break; + // but we already have a month - maybe we guessed wrong? + if ( !haveDay ) + { + // no need to check in month range as always < 12, but + // the days are counted from 1 unlike the months + day = (wxDateTime_t)mon + 1; + haveDay = TRUE; + } + else + { + // could possible be the year (doesn't the year come + // before the month in the japanese format?) (FIXME) + break; + } } + mon = mon2; + haveMon = TRUE; } - else + else // not a valid month name { wday = GetWeekDayFromName(token, Name_Full | Name_Abbr); if ( wday != Inv_WeekDay ) @@ -3155,7 +3199,7 @@ const wxChar *wxDateTime::ParseDate(const wxChar *date) haveWDay = TRUE; } - else + else // not a valid weekday name { // try the ordinals static const wxChar *ordinals[] = @@ -3181,7 +3225,7 @@ const wxChar *wxDateTime::ParseDate(const wxChar *date) wxTRANSLATE("nineteenth"), wxTRANSLATE("twentieth"), // that's enough - otherwise we'd have problems with - // composite (or not) ordinals otherwise + // composite (or not) ordinals }; size_t n; @@ -3210,23 +3254,25 @@ const wxChar *wxDateTime::ParseDate(const wxChar *date) haveDay = TRUE; - day = n + 1; + day = (wxDateTime_t)(n + 1); } } } + + nPosCur = tok.GetPosition(); } // 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.")); + wxLogDebug(_T("ParseDate: no day, no weekday hence no date.")); return (wxChar *)NULL; } if ( haveWDay && (haveMon || haveYear || haveDay) && - !(haveMon && haveMon && haveYear) ) + !(haveDay && haveMon && haveYear) ) { // without adjectives (which we don't support here) the week day only // makes sense completely separately or with the full date @@ -3234,6 +3280,38 @@ const wxChar *wxDateTime::ParseDate(const wxChar *date) return (wxChar *)NULL; } + if ( !haveWDay && haveYear && !(haveDay && haveMon) ) + { + // may be we have month and day instead of day and year? + if ( haveDay && !haveMon ) + { + if ( day <= 12 ) + { + // exchange day and month + mon = (wxDateTime::Month)(day - 1); + + // we're in the current year then + if ( (year > 0) && + (unsigned)year <= GetNumOfDaysInMonth(Inv_Year, mon) ) + { + day = year; + + haveMon = TRUE; + haveYear = FALSE; + } + //else: no, can't exchange, leave haveMon == FALSE + } + } + + if ( !haveMon ) + { + // if we give the year, month and day must be given too + wxLogDebug(_T("ParseDate: day and month should be specified if year is.")); + + return (wxChar *)NULL; + } + } + if ( !haveMon ) { mon = GetCurrentMonth(); @@ -3254,6 +3332,8 @@ const wxChar *wxDateTime::ParseDate(const wxChar *date) if ( GetWeekDay() != wday ) { // inconsistency detected + wxLogDebug(_T("ParseDate: inconsistent day/weekday.")); + return (wxChar *)NULL; } } @@ -3265,8 +3345,16 @@ const wxChar *wxDateTime::ParseDate(const wxChar *date) SetToWeekDayInSameWeek(wday); } - // return the pointer to the next char - return p + wxStrlen(p) - wxStrlen(tok.GetString()); + // return the pointer to the first unparsed char + p += nPosCur; + if ( nPosCur && wxStrchr(dateDelimiters, *(p - 1)) ) + { + // if we couldn't parse the token after the delimiter, put back the + // delimiter as well + p--; + } + + return p; } const wxChar *wxDateTime::ParseTime(const wxChar *time) @@ -3297,8 +3385,12 @@ const wxChar *wxDateTime::ParseTime(const wxChar *time) } } - // try all time formats we may think about starting with the standard one - const wxChar *result = ParseFormat(time, _T("%X")); + // try all time formats we may think about in the order from longest to + // shortest + + // 12hour with AM/PM? + const wxChar *result = ParseFormat(time, _T("%I:%M:%S %p")); + if ( !result ) { // normally, it's the same, but why not try it? @@ -3307,8 +3399,8 @@ const wxChar *wxDateTime::ParseTime(const wxChar *time) if ( !result ) { - // 12hour with AM/PM? - result = ParseFormat(time, _T("%I:%M:%S %p")); + // 12hour with AM/PM but without seconds? + result = ParseFormat(time, _T("%I:%M %p")); } if ( !result ) @@ -3319,8 +3411,8 @@ const wxChar *wxDateTime::ParseTime(const wxChar *time) if ( !result ) { - // 12hour with AM/PM but without seconds? - result = ParseFormat(time, _T("%I:%M %p")); + // just the hour and AM/PM? + result = ParseFormat(time, _T("%I %p")); } if ( !result ) @@ -3331,8 +3423,9 @@ const wxChar *wxDateTime::ParseTime(const wxChar *time) if ( !result ) { - // just the hour and AM/PM? - result = ParseFormat(time, _T("%I %p")); + // parse the standard format: normally it is one of the formats above + // but it may be set to something completely different by the user + result = ParseFormat(time, _T("%X")); } // TODO: parse timezones @@ -3353,6 +3446,19 @@ bool wxDateTime::IsWorkDay(Country WXUNUSED(country)) const // wxTimeSpan // ============================================================================ +// this enum is only used in wxTimeSpan::Format() below but we can't declare +// it locally to the method as it provokes an internal compiler error in egcs +// 2.91.60 when building with -O2 +enum TimeSpanPart +{ + Part_Week, + Part_Day, + Part_Hour, + Part_Min, + Part_Sec, + Part_MSec +}; + // not all strftime(3) format specifiers make sense here because, for example, // a time span doesn't have a year nor a timezone // @@ -3376,60 +3482,126 @@ wxString wxTimeSpan::Format(const wxChar *format) const wxString str; str.Alloc(wxStrlen(format)); - for ( const wxChar *pch = format; pch; pch++ ) + // Suppose we have wxTimeSpan ts(1 /* hour */, 2 /* min */, 3 /* sec */) + // + // Then, of course, ts.Format("%H:%M:%S") must return "01:02:03", but the + // question is what should ts.Format("%S") do? The code here returns "3273" + // in this case (i.e. the total number of seconds, not just seconds % 60) + // because, for me, this call means "give me entire time interval in + // seconds" and not "give me the seconds part of the time interval" + // + // If we agree that it should behave like this, it is clear that the + // interpretation of each format specifier depends on the presence of the + // other format specs in the string: if there was "%H" before "%M", we + // should use GetMinutes() % 60, otherwise just GetMinutes() &c + + // we remember the most important unit found so far + TimeSpanPart partBiggest = Part_MSec; + + for ( const wxChar *pch = format; *pch; pch++ ) { wxChar ch = *pch; - if ( ch == '%' ) + if ( ch == _T('%') ) { - wxString tmp; + // the start of the format specification of the printf() below + wxString fmtPrefix = _T('%'); + + // the number + long n; - ch = *pch++; + ch = *++pch; // get the format spec char switch ( ch ) { default: wxFAIL_MSG( _T("invalid format character") ); // fall through - case '%': - // will get to str << ch below - break; + case _T('%'): + str += ch; - case 'D': - tmp.Printf(_T("%d"), GetDays()); - break; + // skip the part below switch + continue; - case 'E': - tmp.Printf(_T("%d"), GetWeeks()); + case _T('D'): + n = GetDays(); + if ( partBiggest < Part_Day ) + { + n %= DAYS_PER_WEEK; + } + else + { + partBiggest = Part_Day; + } break; - case 'H': - tmp.Printf(_T("%02d"), GetHours()); + case _T('E'): + partBiggest = Part_Week; + n = GetWeeks(); break; - case 'l': - tmp.Printf(_T("%03ld"), GetMilliseconds().ToLong()); + case _T('H'): + n = GetHours(); + if ( partBiggest < Part_Hour ) + { + n %= HOURS_PER_DAY; + } + else + { + partBiggest = Part_Hour; + } + + fmtPrefix += _T("02"); break; - case 'M': - tmp.Printf(_T("%02d"), GetMinutes()); + case _T('l'): + n = GetMilliseconds().ToLong(); + if ( partBiggest < Part_MSec ) + { + n %= 1000; + } + //else: no need to reset partBiggest to Part_MSec, it is + // the least significant one anyhow + + fmtPrefix += _T("03"); break; - case 'S': - tmp.Printf(_T("%02ld"), GetSeconds().ToLong()); + case _T('M'): + n = GetMinutes(); + if ( partBiggest < Part_Min ) + { + n %= MIN_PER_HOUR; + } + else + { + partBiggest = Part_Min; + } + + fmtPrefix += _T("02"); break; - } - if ( !!tmp ) - { - str += tmp; + case _T('S'): + n = GetSeconds().ToLong(); + if ( partBiggest < Part_Sec ) + { + n %= SEC_PER_MIN; + } + else + { + partBiggest = Part_Sec; + } - // skip str += ch below - continue; + fmtPrefix += _T("02"); + break; } - } - str += ch; + str += wxString::Format(fmtPrefix + _T("ld"), n); + } + else + { + // normal character, just copy + str += ch; + } } return str; @@ -3441,7 +3613,7 @@ wxString wxTimeSpan::Format(const wxChar *format) const #include "wx/arrimpl.cpp" -WX_DEFINE_OBJARRAY(wxDateTimeArray) +WX_DEFINE_OBJARRAY(wxDateTimeArray); static int wxCMPFUNC_CONV wxDateTimeCompareFunc(wxDateTime **first, wxDateTime **second) @@ -3484,9 +3656,9 @@ wxDateTimeHolidayAuthority::GetHolidaysInRange(const wxDateTime& dtStart, holidays.Empty(); size_t count = ms_authorities.GetCount(); - for ( size_t n = 0; n < count; n++ ) + for ( size_t nAuth = 0; nAuth < count; nAuth++ ) { - ms_authorities[n]->DoGetHolidaysInRange(dtStart, dtEnd, hol); + ms_authorities[nAuth]->DoGetHolidaysInRange(dtStart, dtEnd, hol); WX_APPEND_ARRAY(holidays, hol); } @@ -3552,3 +3724,5 @@ size_t wxDateTimeWorkDays::DoGetHolidaysInRange(const wxDateTime& dtStart, return holidays.GetCount(); } + +#endif // wxUSE_DATETIME