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:
@@ -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):
|
||||||
|
|
||||||
|
@@ -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;
|
||||||
|
@@ -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,78 +698,110 @@ void DateTimeTestCase::TestTimeFormat()
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
for ( size_t d = 0; d < WXSIZEOF(formatTestDates); d++ )
|
for ( unsigned idxtz = 0; idxtz < WXSIZEOF(timeZonesOffsets); ++idxtz )
|
||||||
{
|
{
|
||||||
wxDateTime dt = formatTestDates[d].DT();
|
wxDateTime::TimeZone tz(timeZonesOffsets[idxtz]);
|
||||||
for ( unsigned n = 0; n < WXSIZEOF(formatTestFormats); n++ )
|
const bool isLocalTz = tz.GetOffset() == -wxGetTimeZone();
|
||||||
|
|
||||||
|
for ( size_t d = 0; d < WXSIZEOF(formatTestDates); d++ )
|
||||||
{
|
{
|
||||||
const char *fmt = formatTestFormats[n].format;
|
wxDateTime dt = formatTestDates[d].DT();
|
||||||
|
for ( unsigned n = 0; n < WXSIZEOF(formatTestFormats); n++ )
|
||||||
// skip the check with %p for those locales which have empty AM/PM strings:
|
|
||||||
// for those locales it's impossible to pass the test with %p...
|
|
||||||
wxString am, pm;
|
|
||||||
wxDateTime::GetAmPmStrings(&am, &pm);
|
|
||||||
if (am.empty() && pm.empty() && wxStrstr(fmt, "%p") != NULL)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
wxString s = dt.Format(fmt);
|
|
||||||
|
|
||||||
// what can we recover?
|
|
||||||
CompareKind kind = formatTestFormats[n].compareKind;
|
|
||||||
|
|
||||||
// convert back
|
|
||||||
wxDateTime dt2;
|
|
||||||
const char *result = dt2.ParseFormat(s, fmt);
|
|
||||||
if ( !result )
|
|
||||||
{
|
{
|
||||||
// conversion failed - should it have?
|
const char *fmt = formatTestFormats[n].format;
|
||||||
WX_ASSERT_MESSAGE(
|
|
||||||
("Test #%u failed: failed to parse \"%s\"", n, s),
|
|
||||||
kind == CompareNone
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else // conversion succeeded
|
|
||||||
{
|
|
||||||
// currently ParseFormat() doesn't support "%Z" and so is
|
|
||||||
// incapable of parsing time zone part used at the end of date
|
|
||||||
// representations in many (but not "C") locales, compensate
|
|
||||||
// for it ourselves by simply consuming and ignoring it
|
|
||||||
while ( *result && (*result >= 'A' && *result <= 'Z') )
|
|
||||||
result++;
|
|
||||||
|
|
||||||
WX_ASSERT_MESSAGE(
|
// skip the check with %p for those locales which have empty AM/PM strings:
|
||||||
("Test #%u failed: \"%s\" was left unparsed in \"%s\"",
|
// for those locales it's impossible to pass the test with %p...
|
||||||
n, result, s),
|
wxString am, pm;
|
||||||
!*result
|
wxDateTime::GetAmPmStrings(&am, &pm);
|
||||||
);
|
if (am.empty() && pm.empty() && wxStrstr(fmt, "%p") != NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
switch ( kind )
|
// what can we recover?
|
||||||
|
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 )
|
||||||
{
|
{
|
||||||
case CompareYear:
|
// DST computation doesn't work correctly for dates above
|
||||||
if ( dt2.GetCentury() != dt.GetCentury() )
|
// 2038 currently on the systems with 32 bit time_t.
|
||||||
{
|
if ( dt.GetYear() >= 2038 )
|
||||||
CPPUNIT_ASSERT_EQUAL(dt.GetYear() % 100,
|
continue;
|
||||||
dt2.GetYear() % 100);
|
|
||||||
|
|
||||||
dt2.SetYear(dt.GetYear());
|
// We can't compare just dates nor just times when doing TZ
|
||||||
}
|
// conversion as both are affected by the DST: for the
|
||||||
// fall through and compare everything
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
case CompareBoth:
|
// do convert date to string
|
||||||
CPPUNIT_ASSERT_EQUAL( dt, dt2 );
|
wxString s = dt.Format(fmt, tz);
|
||||||
break;
|
|
||||||
|
|
||||||
case CompareDate:
|
// convert back
|
||||||
CPPUNIT_ASSERT( dt.IsSameDate(dt2) );
|
wxDateTime dt2;
|
||||||
break;
|
const char *result = dt2.ParseFormat(s, fmt);
|
||||||
|
if ( !result )
|
||||||
|
{
|
||||||
|
// conversion failed - should it have?
|
||||||
|
WX_ASSERT_MESSAGE(
|
||||||
|
("Test #%u failed: failed to parse \"%s\"", n, s),
|
||||||
|
kind == CompareNone
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else // conversion succeeded
|
||||||
|
{
|
||||||
|
// currently ParseFormat() doesn't support "%Z" and so is
|
||||||
|
// incapable of parsing time zone part used at the end of date
|
||||||
|
// representations in many (but not "C") locales, compensate
|
||||||
|
// for it ourselves by simply consuming and ignoring it
|
||||||
|
while ( *result && (*result >= 'A' && *result <= 'Z') )
|
||||||
|
result++;
|
||||||
|
|
||||||
case CompareTime:
|
WX_ASSERT_MESSAGE(
|
||||||
CPPUNIT_ASSERT( dt.IsSameTime(dt2) );
|
("Test #%u failed: \"%s\" was left unparsed in \"%s\"",
|
||||||
break;
|
n, result, s),
|
||||||
|
!*result
|
||||||
|
);
|
||||||
|
|
||||||
case CompareNone:
|
// Without "%z" we can't recover the time zone used in the
|
||||||
wxFAIL_MSG( wxT("unexpected") );
|
// call to Format() so we need to call MakeFromTimezone()
|
||||||
break;
|
// explicitly.
|
||||||
|
if ( !strstr(fmt, "%z") && !isLocalTz )
|
||||||
|
dt2.MakeFromTimezone(tz);
|
||||||
|
|
||||||
|
switch ( kind )
|
||||||
|
{
|
||||||
|
case CompareYear:
|
||||||
|
if ( dt2.GetCentury() != dt.GetCentury() )
|
||||||
|
{
|
||||||
|
CPPUNIT_ASSERT_EQUAL(dt.GetYear() % 100,
|
||||||
|
dt2.GetYear() % 100);
|
||||||
|
|
||||||
|
dt2.SetYear(dt.GetYear());
|
||||||
|
}
|
||||||
|
// fall through and compare everything
|
||||||
|
|
||||||
|
case CompareBoth:
|
||||||
|
CPPUNIT_ASSERT_EQUAL( dt, dt2 );
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CompareDate:
|
||||||
|
CPPUNIT_ASSERT( dt.IsSameDate(dt2) );
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CompareTime:
|
||||||
|
CPPUNIT_ASSERT( dt.IsSameTime(dt2) );
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CompareNone:
|
||||||
|
wxFAIL_MSG( wxT("unexpected") );
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
Reference in New Issue
Block a user