diff --git a/interface/wx/datetime.h b/interface/wx/datetime.h index f7f1906921..e8d43ec2c2 100644 --- a/interface/wx/datetime.h +++ b/interface/wx/datetime.h @@ -799,6 +799,10 @@ public: function (http://www.cplusplus.com/reference/clibrary/ctime/strftime.html). Please see its description for the meaning of @a format parameter. + Notice that POSIX @c "%g", @c "%G", @c "%V" and @c "%z" format + specifiers are supported even if the standard library doesn't support + them (e.g. MSVC). + It also accepts a few wxWidgets-specific extensions: you can optionally specify the width of the field to follow using @c printf(3)-like syntax and the format specification @c "%l" can be used to get the number of diff --git a/src/common/datetimefmt.cpp b/src/common/datetimefmt.cpp index 2e0638904b..c6b49aaefd 100644 --- a/src/common/datetimefmt.cpp +++ b/src/common/datetimefmt.cpp @@ -271,6 +271,30 @@ GetWeekDayFromName(wxString::const_iterator& p, return wd; } +// return the year of the Monday of the week containing the given date +int +GetWeekBasedYear(const wxDateTime& dt) +{ + const wxDateTime::Tm tm = dt.GetTm(); + + int year = tm.year; + + // The week-based year can only be different from the normal year for few + // days in the beginning and the end of the year. + if ( tm.yday > 361 ) + { + if ( dt.GetWeekOfYear() == 1 ) + year++; + } + else if ( tm.yday < 5 ) + { + if ( dt.GetWeekOfYear() == 53 ) + year--; + } + + return year; +} + // parses string starting at given iterator using the specified format and, // optionally, a fall back format (and optionally another one... but it stops // there, really) @@ -321,17 +345,38 @@ wxString wxDateTime::Format(const wxString& formatp, const TimeZone& tz) const format.Replace("%X",wxLocale::GetInfo(wxLOCALE_TIME_FMT)); #endif // we have to use our own implementation if the date is out of range of - // 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) + // strftime() #ifdef wxHAS_STRFTIME time_t time = GetTicks(); - if ( (time != (time_t)-1) && !wxStrstr(format, wxT("%l")) + bool canUseStrftime = time != (time_t)-1; + + // We also can't use strftime() if we use non standard specifier: either + // our own extension "%l" or one of "%g", "%G", "%V", "%z" which are POSIX + // but not supported under Windows. + for ( wxString::const_iterator p = format.begin(); + canUseStrftime && p != format.end(); + ++p ) + { + if ( *p != '%' ) + continue; + + // set the default format + switch ( (*++p).GetValue() ) + { + case 'l': #ifdef __WINDOWS__ - && !wxStrstr(format, wxT("%z")) -#endif - ) + case 'g': + case 'G': + case 'V': + case 'z': +#endif // __WINDOWS__ + canUseStrftime = false; + break; + } + } + + if ( canUseStrftime ) { // use strftime() struct tm tmstruct; @@ -405,6 +450,7 @@ wxString wxDateTime::Format(const wxString& formatp, const TimeZone& tz) const switch ( (*++p).GetValue() ) { case wxT('Y'): // year has 4 digits + case wxT('G'): // (and ISO week year too) case wxT('z'): // time zone as well fmt = wxT("%04d"); break; @@ -576,6 +622,14 @@ wxString wxDateTime::Format(const wxString& formatp, const TimeZone& tz) const res += wxString::Format(fmt, tm.mday); break; + case wxT('g'): // 2-digit week-based year + res += wxString::Format(fmt, GetWeekBasedYear(*this) % 100); + break; + + case wxT('G'): // week-based year with century + res += wxString::Format(fmt, GetWeekBasedYear(*this)); + break; + case wxT('H'): // hour in 24h format (00-23) res += wxString::Format(fmt, tm.hour); break; @@ -621,6 +675,7 @@ wxString wxDateTime::Format(const wxString& formatp, const TimeZone& tz) const res += wxString::Format(fmt, GetWeekOfYear(Sunday_First, tz)); break; + case wxT('V'): // ISO week number case wxT('W'): // week number in the year (Monday 1st week day) res += wxString::Format(fmt, GetWeekOfYear(Monday_First, tz)); break; diff --git a/tests/datetime/datetimetest.cpp b/tests/datetime/datetimetest.cpp index 112e3291a6..2e57966e41 100644 --- a/tests/datetime/datetimetest.cpp +++ b/tests/datetime/datetimetest.cpp @@ -228,6 +228,7 @@ private: CPPUNIT_TEST( TestDateParse ); CPPUNIT_TEST( TestDateParseISO ); CPPUNIT_TEST( TestDateTimeParse ); + CPPUNIT_TEST( TestDateWeekFormat ); CPPUNIT_TEST( TestTimeArithmetics ); CPPUNIT_TEST( TestDSTBug ); CPPUNIT_TEST( TestDateOnly ); @@ -247,6 +248,7 @@ private: void TestDateParse(); void TestDateParseISO(); void TestDateTimeParse(); + void TestDateWeekFormat(); void TestTimeArithmetics(); void TestDSTBug(); void TestDateOnly(); @@ -1212,6 +1214,85 @@ void DateTimeTestCase::TestDateTimeParse() } } +void DateTimeTestCase::TestDateWeekFormat() +{ + static const struct DateWeekTestData + { + int y, m, d; + const char* result; // expected output of "%G%V" + } testWeeks[] = + { + // Some manual test cases. + { 2014, 1, 1, "2014-01" }, + { 2014, 1, 2, "2014-01" }, + { 2014, 1, 3, "2014-01" }, + { 2014, 1, 4, "2014-01" }, + { 2014, 1, 5, "2014-01" }, + { 2014, 1, 6, "2014-02" }, + { 2014, 1, 7, "2014-02" }, + { 2014, 12, 24, "2014-52" }, + { 2014, 12, 25, "2014-52" }, + { 2014, 12, 26, "2014-52" }, + { 2014, 12, 27, "2014-52" }, + { 2014, 12, 28, "2014-52" }, + { 2014, 12, 29, "2015-01" }, + { 2014, 12, 30, "2015-01" }, + { 2014, 12, 31, "2015-01" }, + { 2015, 12, 24, "2015-52" }, + { 2015, 12, 25, "2015-52" }, + { 2015, 12, 26, "2015-52" }, + { 2015, 12, 27, "2015-52" }, + { 2015, 12, 28, "2015-53" }, + { 2015, 12, 29, "2015-53" }, + { 2015, 12, 30, "2015-53" }, + { 2015, 12, 31, "2015-53" }, + { 2016, 1, 1, "2015-53" }, + { 2016, 1, 2, "2015-53" }, + { 2016, 1, 3, "2015-53" }, + { 2016, 1, 4, "2016-01" }, + { 2016, 1, 5, "2016-01" }, + { 2016, 1, 6, "2016-01" }, + { 2016, 1, 7, "2016-01" }, + + // The rest of the tests was generated using the following zsh command: + // + // for n in {0..19}; date --date $((RANDOM%100 + 2000))-$((RANDOM%12+1))-$((RANDOM%31+1)) +'{ %Y, %_m, %_d, "%G-%V" },' + // + // (ignore invalid dates if any are randomly created). + { 2017, 11, 28, "2017-48" }, + { 2086, 9, 6, "2086-36" }, + { 2060, 11, 11, "2060-46" }, + { 2009, 5, 10, "2009-19" }, + { 2032, 12, 8, "2032-50" }, + { 2025, 4, 7, "2025-15" }, + { 2080, 5, 20, "2080-21" }, + { 2077, 7, 19, "2077-29" }, + { 2084, 12, 17, "2084-50" }, + { 2071, 4, 13, "2071-16" }, + { 2006, 1, 3, "2006-01" }, + { 2053, 8, 1, "2053-31" }, + { 2097, 8, 14, "2097-33" }, + { 2067, 1, 3, "2067-01" }, + { 2039, 9, 27, "2039-39" }, + { 2095, 2, 10, "2095-06" }, + { 2004, 7, 7, "2004-28" }, + { 2049, 12, 27, "2049-52" }, + { 2071, 8, 19, "2071-34" }, + { 2010, 11, 30, "2010-48" }, + }; + + for ( size_t n = 0; n < WXSIZEOF(testWeeks); n++ ) + { + const DateWeekTestData& td = testWeeks[n]; + wxDateTime d(td.d, wxDateTime::Month(td.m - 1), td.y); + + CPPUNIT_ASSERT_EQUAL( td.result, d.Format("%G-%V") ); + + if ( td.y > 2000 ) + CPPUNIT_ASSERT_EQUAL( td.result + 2, d.Format("%g-%V") ); + } +} + void DateTimeTestCase::TestTimeArithmetics() { static const wxDateSpan testArithmData[] =