Implement support for "%z" in wxDateTime::Format() and Parse().

"%z" specifier can now be used when printing the dates out to specify the time
zone and is also recognized when parsing dates.

Closes #1215.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@70268 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Vadim Zeitlin
2012-01-05 00:47:10 +00:00
parent cd72035387
commit 444bc2b24d
3 changed files with 181 additions and 63 deletions

View File

@@ -451,6 +451,7 @@ Major new features in this release
All: All:
- Added wxLogFormatter to allow customizing wxLog output (Sébastien Gallou). - Added wxLogFormatter to allow customizing wxLog output (Sébastien Gallou).
- Added "%z" support to wxDateTime::Format() and Parse() (Armel Asselin).
All (GUI): All (GUI):

View File

@@ -319,11 +319,17 @@ wxString wxDateTime::Format(const wxString& formatp, const TimeZone& tz) const
format.Replace("%X",wxLocale::GetInfo(wxLOCALE_TIME_FMT)); format.Replace("%X",wxLocale::GetInfo(wxLOCALE_TIME_FMT));
#endif #endif
// we have to use our own implementation if the date is out of range of // we have to use our own implementation if the date is out of range of
// strftime() or if we use non standard specifiers // strftime() or if we use non standard specifiers (notice that "%z" is
// special because it is de facto standard under Unix but is not supported
// under Windows)
#ifdef wxHAS_STRFTIME #ifdef wxHAS_STRFTIME
time_t time = GetTicks(); time_t time = GetTicks();
if ( (time != (time_t)-1) && !wxStrstr(format, wxT("%l")) ) if ( (time != (time_t)-1) && !wxStrstr(format, wxT("%l"))
#ifdef __WXMSW__
&& !wxStrstr(format, wxT("%z"))
#endif
)
{ {
// use strftime() // use strftime()
struct tm tmstruct; struct tm tmstruct;
@@ -397,6 +403,7 @@ wxString wxDateTime::Format(const wxString& formatp, const TimeZone& tz) const
switch ( (*++p).GetValue() ) switch ( (*++p).GetValue() )
{ {
case wxT('Y'): // year has 4 digits case wxT('Y'): // year has 4 digits
case wxT('z'): // time zone as well
fmt = wxT("%04d"); fmt = wxT("%04d");
break; break;
@@ -639,6 +646,25 @@ wxString wxDateTime::Format(const wxString& formatp, const TimeZone& tz) const
res += wxString::Format(fmt, tm.year); res += wxString::Format(fmt, tm.year);
break; break;
case wxT('z'): // time zone as [-+]HHMM
{
int ofs = tz.GetOffset();
if ( ofs < 0 )
{
res += '-';
ofs = -ofs;
}
else
{
res += '+';
}
// Converts seconds to HHMM representation.
res += wxString::Format(fmt,
100*(ofs/3600) + (ofs/60)%60);
}
break;
case wxT('Z'): // timezone name case wxT('Z'): // timezone name
#ifdef wxHAS_STRFTIME #ifdef wxHAS_STRFTIME
res += CallStrftime(wxT("%Z"), &tmTimeOnly); res += CallStrftime(wxT("%Z"), &tmTimeOnly);
@@ -929,6 +955,8 @@ wxDateTime::ParseFormat(const wxString& date,
bool hourIsIn12hFormat = false, // or in 24h one? bool hourIsIn12hFormat = false, // or in 24h one?
isPM = false; // AM by default isPM = false; // AM by default
bool haveTimeZone = false;
// and the value of the items we have (init them to get rid of warnings) // and the value of the items we have (init them to get rid of warnings)
wxDateTime_t msec = 0, wxDateTime_t msec = 0,
sec = 0, sec = 0,
@@ -939,6 +967,7 @@ wxDateTime::ParseFormat(const wxString& date,
mday = 0; mday = 0;
wxDateTime::Month mon = Inv_Month; wxDateTime::Month mon = Inv_Month;
int year = 0; int year = 0;
long timeZone = 0; // time zone in seconds as expected in Tm structure
wxString::const_iterator input = date.begin(); wxString::const_iterator input = date.begin();
const wxString::const_iterator end = date.end(); const wxString::const_iterator end = date.end();
@@ -1377,6 +1406,36 @@ wxDateTime::ParseFormat(const wxString& date,
year = (wxDateTime_t)num; year = (wxDateTime_t)num;
break; break;
case wxT('z'):
{
bool minusFound;
if ( *input == wxT('-') )
minusFound = true;
else if ( *input == wxT('+') )
minusFound = false;
else
return false; // no match
// here should follow 4 digits HHMM
++input;
unsigned long tzHourMin;
if ( !GetNumericToken(4, input, end, &tzHourMin) )
return false; // no match
const unsigned hours = tzHourMin / 100;
const unsigned minutes = tzHourMin % 100;
if ( hours > 12 || minutes > 59 )
return false; // bad format
timeZone = 3600*hours + 60*minutes;
if ( minusFound )
timeZone = -timeZone;
haveTimeZone = true;
}
break;
case wxT('Z'): // timezone name case wxT('Z'): // timezone name
// FIXME: currently we just ignore everything that looks like a // FIXME: currently we just ignore everything that looks like a
// time zone here // time zone here
@@ -1482,6 +1541,14 @@ wxDateTime::ParseFormat(const wxString& date,
Set(tm); Set(tm);
// If a time zone was specified and it is not the local time zone, we need
// to shift the time accordingly.
//
// Note that avoiding the call to MakeFromTimeZone is necessary to avoid
// DST problems.
if ( haveTimeZone && timeZone != -wxGetTimeZone() )
MakeFromTimezone(timeZone);
// finally check that the week day is consistent -- if we had it // finally check that the week day is consistent -- if we had it
if ( haveWDay && GetWeekDay() != wday ) if ( haveWDay && GetWeekDay() != wday )
return false; return false;

View File

@@ -666,6 +666,18 @@ void DateTimeTestCase::TestTimeFormat()
{ CompareTime, "Time is %H:%M:%S or %I:%M:%S %p" }, { CompareTime, "Time is %H:%M:%S or %I:%M:%S %p" },
{ CompareNone, "The day of year: %j, the week of year: %W" }, { CompareNone, "The day of year: %j, the week of year: %W" },
{ CompareDate, "ISO date without separators: %Y%m%d" }, { CompareDate, "ISO date without separators: %Y%m%d" },
{ CompareBoth, "RFC 2822 string: %Y-%m-%d %H:%M:%S.%l %z" },
};
const long timeZonesOffsets[] =
{
wxDateTime::TimeZone(wxDateTime::Local).GetOffset(),
// Fictitious TimeZone offsets to ensure time zone formating and
// interpretation works
-(3600 + 2*60),
3*3600 + 30*60
}; };
static const Date formatTestDates[] = static const Date formatTestDates[] =
@@ -686,6 +698,11 @@ void DateTimeTestCase::TestTimeFormat()
#endif #endif
}; };
for ( unsigned idxtz = 0; idxtz < WXSIZEOF(timeZonesOffsets); ++idxtz )
{
wxDateTime::TimeZone tz(timeZonesOffsets[idxtz]);
const bool isLocalTz = tz.GetOffset() == -wxGetTimeZone();
for ( size_t d = 0; d < WXSIZEOF(formatTestDates); d++ ) for ( size_t d = 0; d < WXSIZEOF(formatTestDates); d++ )
{ {
wxDateTime dt = formatTestDates[d].DT(); wxDateTime dt = formatTestDates[d].DT();
@@ -700,11 +717,31 @@ void DateTimeTestCase::TestTimeFormat()
if (am.empty() && pm.empty() && wxStrstr(fmt, "%p") != NULL) if (am.empty() && pm.empty() && wxStrstr(fmt, "%p") != NULL)
continue; continue;
wxString s = dt.Format(fmt);
// what can we recover? // what can we recover?
CompareKind kind = formatTestFormats[n].compareKind; CompareKind kind = formatTestFormats[n].compareKind;
// When using a different time zone we must perform a time zone
// conversion below which doesn't always work correctly, check
// for the cases when it doesn't.
if ( !isLocalTz )
{
// DST computation doesn't work correctly for dates above
// 2038 currently on the systems with 32 bit time_t.
if ( dt.GetYear() >= 2038 )
continue;
// We can't compare just dates nor just times when doing TZ
// conversion as both are affected by the DST: for the
// dates, the DST can switch midnight to 23:00 of the
// previous day while for the times DST can be different
// for the original date and today.
if ( kind == CompareDate || kind == CompareTime )
continue;
}
// do convert date to string
wxString s = dt.Format(fmt, tz);
// convert back // convert back
wxDateTime dt2; wxDateTime dt2;
const char *result = dt2.ParseFormat(s, fmt); const char *result = dt2.ParseFormat(s, fmt);
@@ -731,6 +768,12 @@ void DateTimeTestCase::TestTimeFormat()
!*result !*result
); );
// Without "%z" we can't recover the time zone used in the
// call to Format() so we need to call MakeFromTimezone()
// explicitly.
if ( !strstr(fmt, "%z") && !isLocalTz )
dt2.MakeFromTimezone(tz);
switch ( kind ) switch ( kind )
{ {
case CompareYear: case CompareYear:
@@ -762,6 +805,7 @@ void DateTimeTestCase::TestTimeFormat()
} }
} }
} }
}
wxDateTime dt; wxDateTime dt;
@@ -1096,6 +1140,12 @@ void DateTimeTestCase::TestDateTimeParse()
{ 1, wxDateTime::Jan, 9999, 0, 0, 0}, { 1, wxDateTime::Jan, 9999, 0, 0, 0},
false false
}, },
{
"2012-01-01 10:12:05 +0100",
{ 1, wxDateTime::Jan, 2012, 10, 12, 5, -1 },
false // ParseDateTime does know yet +0100
},
}; };
// the test strings here use "PM" which is not available in all locales so // the test strings here use "PM" which is not available in all locales so