1///////////////////////////////////////////////////////////////////////////////
2// Name:        tests/datetime/datetime.cpp
3// Purpose:     wxDateTime unit test
4// Author:      Vadim Zeitlin
5// Created:     2004-06-23 (extracted from samples/console/console.cpp)
6// RCS-ID:      $Id: datetimetest.cpp 57474 2008-12-21 12:16:06Z VZ $
7// Copyright:   (c) 2004 Vadim Zeitlin <vadim@wxwindows.org>
8///////////////////////////////////////////////////////////////////////////////
9
10// ----------------------------------------------------------------------------
11// headers
12// ----------------------------------------------------------------------------
13
14#include "testprec.h"
15
16#ifdef __BORLANDC__
17    #pragma hdrstop
18#endif
19
20#ifndef WX_PRECOMP
21#endif // WX_PRECOMP
22
23#if wxUSE_DATETIME
24
25#include "wx/datetime.h"
26#include "wx/ioswrap.h"
27
28// need this to be able to use CPPUNIT_ASSERT_EQUAL with wxDateTime objects
29static std::ostream& operator<<(std::ostream& ostr, const wxDateTime& dt)
30{
31    ostr << dt.FormatISODate() << " " << dt.FormatISOTime();
32
33    return ostr;
34}
35
36// to test Today() meaningfully we must be able to change the system date which
37// is not usually the case, but if we're under Win32 we can try it -- define
38// the macro below to do it
39//#define CHANGE_SYSTEM_DATE
40
41#ifndef __WINDOWS__
42    #undef CHANGE_SYSTEM_DATE
43#endif
44
45#ifdef CHANGE_SYSTEM_DATE
46
47class DateChanger
48{
49public:
50    DateChanger(int year, int month, int day, int hour, int min, int sec)
51    {
52        SYSTEMTIME st;
53        st.wDay = day;
54        st.wMonth = month;
55        st.wYear = year;
56        st.wHour = hour;
57        st.wMinute = min;
58        st.wSecond = sec;
59        st.wMilliseconds = 0;
60
61        ::GetSystemTime(&m_savedTime);
62        ::GetTimeZoneInformation(&m_tzi);
63
64        m_changed = ::SetSystemTime(&st) != 0;
65    }
66
67    ~DateChanger()
68    {
69        if ( m_changed )
70        {
71            ::SetSystemTime(&m_savedTime);
72            ::SetTimeZoneInformation(&m_tzi);
73        }
74    }
75
76private:
77    SYSTEMTIME m_savedTime;
78    TIME_ZONE_INFORMATION m_tzi;
79    bool m_changed;
80};
81
82#endif // CHANGE_SYSTEM_DATE
83
84// ----------------------------------------------------------------------------
85// broken down date representation used for testing
86// ----------------------------------------------------------------------------
87
88struct Date
89{
90    wxDateTime::wxDateTime_t day;
91    wxDateTime::Month month;
92    int year;
93    wxDateTime::wxDateTime_t hour, min, sec;
94    double jdn;
95    wxDateTime::WeekDay wday;
96    time_t gmticks, ticks;
97
98    void Init(const wxDateTime::Tm& tm)
99    {
100        day = tm.mday;
101        month = tm.mon;
102        year = tm.year;
103        hour = tm.hour;
104        min = tm.min;
105        sec = tm.sec;
106        jdn = 0.0;
107        gmticks = ticks = -1;
108    }
109
110    wxDateTime DT() const
111        { return wxDateTime(day, month, year, hour, min, sec); }
112
113    bool SameDay(const wxDateTime::Tm& tm) const
114    {
115        return day == tm.mday && month == tm.mon && year == tm.year;
116    }
117
118    wxString Format() const
119    {
120        wxString s;
121        s.Printf(_T("%02d:%02d:%02d %10s %02d, %4d%s"),
122                 hour, min, sec,
123                 wxDateTime::GetMonthName(month).c_str(),
124                 day,
125                 abs(wxDateTime::ConvertYearToBC(year)),
126                 year > 0 ? _T("AD") : _T("BC"));
127        return s;
128    }
129
130    wxString FormatDate() const
131    {
132        wxString s;
133        s.Printf(_T("%02d-%s-%4d%s"),
134                 day,
135                 wxDateTime::GetMonthName(month, wxDateTime::Name_Abbr).c_str(),
136                 abs(wxDateTime::ConvertYearToBC(year)),
137                 year > 0 ? _T("AD") : _T("BC"));
138        return s;
139    }
140};
141
142// ----------------------------------------------------------------------------
143// test data
144// ----------------------------------------------------------------------------
145
146static const Date testDates[] =
147{
148    {  1, wxDateTime::Jan,  1970, 00, 00, 00, 2440587.5, wxDateTime::Thu,         0,     -3600 },
149    {  7, wxDateTime::Feb,  2036, 00, 00, 00, 2464730.5, wxDateTime::Thu,        -1,        -1 },
150    {  8, wxDateTime::Feb,  2036, 00, 00, 00, 2464731.5, wxDateTime::Fri,        -1,        -1 },
151    {  1, wxDateTime::Jan,  2037, 00, 00, 00, 2465059.5, wxDateTime::Thu,        -1,        -1 },
152    {  1, wxDateTime::Jan,  2038, 00, 00, 00, 2465424.5, wxDateTime::Fri,        -1,        -1 },
153    { 21, wxDateTime::Jan,  2222, 00, 00, 00, 2532648.5, wxDateTime::Mon,        -1,        -1 },
154    { 29, wxDateTime::May,  1976, 12, 00, 00, 2442928.0, wxDateTime::Sat, 202219200, 202212000 },
155    { 29, wxDateTime::Feb,  1976, 00, 00, 00, 2442837.5, wxDateTime::Sun, 194400000, 194396400 },
156    {  1, wxDateTime::Jan,  1900, 12, 00, 00, 2415021.0, wxDateTime::Mon,        -1,        -1 },
157    {  1, wxDateTime::Jan,  1900, 00, 00, 00, 2415020.5, wxDateTime::Mon,        -1,        -1 },
158    { 15, wxDateTime::Oct,  1582, 00, 00, 00, 2299160.5, wxDateTime::Fri,        -1,        -1 },
159    {  4, wxDateTime::Oct,  1582, 00, 00, 00, 2299149.5, wxDateTime::Mon,        -1,        -1 },
160    {  1, wxDateTime::Mar,     1, 00, 00, 00, 1721484.5, wxDateTime::Thu,        -1,        -1 },
161    {  1, wxDateTime::Jan,     1, 00, 00, 00, 1721425.5, wxDateTime::Mon,        -1,        -1 },
162    { 31, wxDateTime::Dec,     0, 00, 00, 00, 1721424.5, wxDateTime::Sun,        -1,        -1 },
163    {  1, wxDateTime::Jan,     0, 00, 00, 00, 1721059.5, wxDateTime::Sat,        -1,        -1 },
164    { 12, wxDateTime::Aug, -1234, 00, 00, 00, 1270573.5, wxDateTime::Fri,        -1,        -1 },
165    { 12, wxDateTime::Aug, -4000, 00, 00, 00,  260313.5, wxDateTime::Sat,        -1,        -1 },
166    { 24, wxDateTime::Nov, -4713, 00, 00, 00,      -0.5, wxDateTime::Mon,        -1,        -1 },
167};
168
169
170// ----------------------------------------------------------------------------
171// test class
172// ----------------------------------------------------------------------------
173
174class DateTimeTestCase : public CppUnit::TestCase
175{
176public:
177    DateTimeTestCase() { }
178
179private:
180    CPPUNIT_TEST_SUITE( DateTimeTestCase );
181        CPPUNIT_TEST( TestLeapYears );
182        CPPUNIT_TEST( TestTimeSet );
183        CPPUNIT_TEST( TestTimeJDN );
184        CPPUNIT_TEST( TestTimeWNumber );
185        CPPUNIT_TEST( TestTimeWDays );
186        CPPUNIT_TEST( TestTimeDST );
187        CPPUNIT_TEST( TestTimeFormat );
188        CPPUNIT_TEST( TestTimeSpanFormat );
189        CPPUNIT_TEST( TestTimeTicks );
190        CPPUNIT_TEST( TestParceRFC822 );
191        CPPUNIT_TEST( TestDateParse );
192        CPPUNIT_TEST( TestTimeArithmetics );
193        // FIXME: this test fails on test drive machine, disabling it until
194        //        someone has time to look at it
195        //CPPUNIT_TEST( TestDSTBug );
196        CPPUNIT_TEST( TestDateOnly );
197    CPPUNIT_TEST_SUITE_END();
198
199    void TestLeapYears();
200    void TestTimeSet();
201    void TestTimeJDN();
202    void TestTimeWNumber();
203    void TestTimeWDays();
204    void TestTimeDST();
205    void TestTimeFormat();
206    void TestTimeSpanFormat();
207    void TestTimeTicks();
208    void TestParceRFC822();
209    void TestDateParse();
210    void TestTimeArithmetics();
211    void TestDSTBug();
212    void TestDateOnly();
213
214    DECLARE_NO_COPY_CLASS(DateTimeTestCase)
215};
216
217// register in the unnamed registry so that these tests are run by default
218CPPUNIT_TEST_SUITE_REGISTRATION( DateTimeTestCase );
219
220// also include in it's own registry so that these tests can be run alone
221CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( DateTimeTestCase, "DateTimeTestCase" );
222
223// ============================================================================
224// implementation
225// ============================================================================
226
227// test leap years detection
228void DateTimeTestCase::TestLeapYears()
229{
230    static const struct LeapYearTestData
231    {
232        int year;
233        bool isLeap;
234    } years[] =
235    {
236        { 1900, false },
237        { 1990, false },
238        { 1976, true },
239        { 2000, true },
240        { 2030, false },
241        { 1984, true },
242        { 2100, false },
243        { 2400, true },
244    };
245
246    for ( size_t n = 0; n < WXSIZEOF(years); n++ )
247    {
248        const LeapYearTestData& y = years[n];
249
250        CPPUNIT_ASSERT( wxDateTime::IsLeapYear(y.year) == y.isLeap );
251    }
252}
253
254// test constructing wxDateTime objects
255void DateTimeTestCase::TestTimeSet()
256{
257    for ( size_t n = 0; n < WXSIZEOF(testDates); n++ )
258    {
259        const Date& d1 = testDates[n];
260        wxDateTime dt = d1.DT();
261
262        Date d2;
263        d2.Init(dt.GetTm());
264
265        wxString s1 = d1.Format(),
266                 s2 = d2.Format();
267
268        CPPUNIT_ASSERT( s1 == s2 );
269    }
270}
271
272// test conversions to JDN &c
273void DateTimeTestCase::TestTimeJDN()
274{
275    for ( size_t n = 0; n < WXSIZEOF(testDates); n++ )
276    {
277        const Date& d = testDates[n];
278        wxDateTime dt(d.day, d.month, d.year, d.hour, d.min, d.sec);
279
280        // JDNs must be computed for UTC times
281        double jdn = dt.FromUTC().GetJulianDayNumber();
282
283        CPPUNIT_ASSERT( jdn == d.jdn );
284
285        dt.Set(jdn);
286        CPPUNIT_ASSERT( dt.GetJulianDayNumber() == jdn );
287    }
288}
289
290// test week days computation
291void DateTimeTestCase::TestTimeWDays()
292{
293    // test GetWeekDay()
294    size_t n;
295    for ( n = 0; n < WXSIZEOF(testDates); n++ )
296    {
297        const Date& d = testDates[n];
298        wxDateTime dt(d.day, d.month, d.year, d.hour, d.min, d.sec);
299
300        wxDateTime::WeekDay wday = dt.GetWeekDay();
301        CPPUNIT_ASSERT( wday == d.wday );
302    }
303
304    // test SetToWeekDay()
305    struct WeekDateTestData
306    {
307        Date date;                  // the real date (precomputed)
308        int nWeek;                  // its week index in the month
309        wxDateTime::WeekDay wday;   // the weekday
310        wxDateTime::Month month;    // the month
311        int year;                   // and the year
312
313        wxString Format() const
314        {
315            wxString s, which;
316            switch ( nWeek < -1 ? -nWeek : nWeek )
317            {
318                case 1: which = _T("first"); break;
319                case 2: which = _T("second"); break;
320                case 3: which = _T("third"); break;
321                case 4: which = _T("fourth"); break;
322                case 5: which = _T("fifth"); break;
323
324                case -1: which = _T("last"); break;
325            }
326
327            if ( nWeek < -1 )
328            {
329                which += _T(" from end");
330            }
331
332            s.Printf(_T("The %s %s of %s in %d"),
333                     which.c_str(),
334                     wxDateTime::GetWeekDayName(wday).c_str(),
335                     wxDateTime::GetMonthName(month).c_str(),
336                     year);
337
338            return s;
339        }
340    };
341
342    // the array data was generated by the following python program
343    /*
344from DateTime import *
345from whrandom import *
346from string import *
347
348monthNames = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]
349wdayNames = [ 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun' ]
350
351week = DateTimeDelta(7)
352
353for n in range(20):
354    year = randint(1900, 2100)
355    month = randint(1, 12)
356    day = randint(1, 28)
357    dt = DateTime(year, month, day)
358    wday = dt.day_of_week
359
360    countFromEnd = choice([-1, 1])
361    weekNum = 0;
362
363    while dt.month is month:
364        dt = dt - countFromEnd * week
365        weekNum = weekNum + countFromEnd
366
367    data = { 'day': rjust(`day`, 2), 'month': monthNames[month - 1], 'year': year, 'weekNum': rjust(`weekNum`, 2), 'wday': wdayNames[wday] }
368
369    print "{ { %(day)s, wxDateTime::%(month)s, %(year)d }, %(weekNum)d, "\
370          "wxDateTime::%(wday)s, wxDateTime::%(month)s, %(year)d }," % data
371    */
372
373    static const WeekDateTestData weekDatesTestData[] =
374    {
375        { { 20, wxDateTime::Mar, 2045, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },  3, wxDateTime::Mon, wxDateTime::Mar, 2045 },
376        { {  5, wxDateTime::Jun, 1985, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, -4, wxDateTime::Wed, wxDateTime::Jun, 1985 },
377        { { 12, wxDateTime::Nov, 1961, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, -3, wxDateTime::Sun, wxDateTime::Nov, 1961 },
378        { { 27, wxDateTime::Feb, 2093, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, -1, wxDateTime::Fri, wxDateTime::Feb, 2093 },
379        { {  4, wxDateTime::Jul, 2070, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, -4, wxDateTime::Fri, wxDateTime::Jul, 2070 },
380        { {  2, wxDateTime::Apr, 1906, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, -5, wxDateTime::Mon, wxDateTime::Apr, 1906 },
381        { { 19, wxDateTime::Jul, 2023, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, -2, wxDateTime::Wed, wxDateTime::Jul, 2023 },
382        { {  5, wxDateTime::May, 1958, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, -4, wxDateTime::Mon, wxDateTime::May, 1958 },
383        { { 11, wxDateTime::Aug, 1900, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },  2, wxDateTime::Sat, wxDateTime::Aug, 1900 },
384        { { 14, wxDateTime::Feb, 1945, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },  2, wxDateTime::Wed, wxDateTime::Feb, 1945 },
385        { { 25, wxDateTime::Jul, 1967, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, -1, wxDateTime::Tue, wxDateTime::Jul, 1967 },
386        { {  9, wxDateTime::May, 1916, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, -4, wxDateTime::Tue, wxDateTime::May, 1916 },
387        { { 20, wxDateTime::Jun, 1927, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },  3, wxDateTime::Mon, wxDateTime::Jun, 1927 },
388        { {  2, wxDateTime::Aug, 2000, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },  1, wxDateTime::Wed, wxDateTime::Aug, 2000 },
389        { { 20, wxDateTime::Apr, 2044, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },  3, wxDateTime::Wed, wxDateTime::Apr, 2044 },
390        { { 20, wxDateTime::Feb, 1932, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, -2, wxDateTime::Sat, wxDateTime::Feb, 1932 },
391        { { 25, wxDateTime::Jul, 2069, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },  4, wxDateTime::Thu, wxDateTime::Jul, 2069 },
392        { {  3, wxDateTime::Apr, 1925, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },  1, wxDateTime::Fri, wxDateTime::Apr, 1925 },
393        { { 21, wxDateTime::Mar, 2093, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },  3, wxDateTime::Sat, wxDateTime::Mar, 2093 },
394        { {  3, wxDateTime::Dec, 2074, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, -5, wxDateTime::Mon, wxDateTime::Dec, 2074 }
395    };
396
397    wxDateTime dt;
398    for ( n = 0; n < WXSIZEOF(weekDatesTestData); n++ )
399    {
400        const WeekDateTestData& wd = weekDatesTestData[n];
401
402        dt.SetToWeekDay(wd.wday, wd.nWeek, wd.month, wd.year);
403
404        const Date& d = wd.date;
405        CPPUNIT_ASSERT( d.SameDay(dt.GetTm()) );
406    }
407}
408
409// test the computation of (ISO) week numbers
410void DateTimeTestCase::TestTimeWNumber()
411{
412    struct WeekNumberTestData
413    {
414        Date date;                          // the date
415        wxDateTime::wxDateTime_t week;      // the week number in the year
416        wxDateTime::wxDateTime_t wmon;      // the week number in the month
417        wxDateTime::wxDateTime_t wmon2;     // same but week starts with Sun
418        wxDateTime::wxDateTime_t dnum;      // day number in the year
419    };
420
421    // data generated with the following python script:
422    /*
423from DateTime import *
424from whrandom import *
425from string import *
426
427monthNames = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]
428wdayNames = [ 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun' ]
429
430def GetMonthWeek(dt):
431    weekNumMonth = dt.iso_week[1] - DateTime(dt.year, dt.month, 1).iso_week[1] + 1
432    if weekNumMonth < 0:
433        weekNumMonth = weekNumMonth + 53
434    return weekNumMonth
435
436def GetLastSundayBefore(dt):
437    if dt.iso_week[2] == 7:
438        return dt
439    else:
440        return dt - DateTimeDelta(dt.iso_week[2])
441
442for n in range(20):
443    year = randint(1900, 2100)
444    month = randint(1, 12)
445    day = randint(1, 28)
446    dt = DateTime(year, month, day)
447    dayNum = dt.day_of_year
448    weekNum = dt.iso_week[1]
449    weekNumMonth = GetMonthWeek(dt)
450
451    weekNumMonth2 = 0
452    dtSunday = GetLastSundayBefore(dt)
453
454    while dtSunday >= GetLastSundayBefore(DateTime(dt.year, dt.month, 1)):
455        weekNumMonth2 = weekNumMonth2 + 1
456        dtSunday = dtSunday - DateTimeDelta(7)
457
458    data = { 'day': rjust(`day`, 2), \
459             'month': monthNames[month - 1], \
460             'year': year, \
461             'weekNum': rjust(`weekNum`, 2), \
462             'weekNumMonth': weekNumMonth, \
463             'weekNumMonth2': weekNumMonth2, \
464             'dayNum': rjust(`dayNum`, 3) }
465
466    print "        { { %(day)s, "\
467          "wxDateTime::%(month)s, "\
468          "%(year)d }, "\
469          "%(weekNum)s, "\
470          "%(weekNumMonth)s, "\
471          "%(weekNumMonth2)s, "\
472          "%(dayNum)s }," % data
473
474    */
475    static const WeekNumberTestData weekNumberTestDates[] =
476    {
477        { { 27, wxDateTime::Dec, 1966, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, 52, 5, 5, 361 },
478        { { 22, wxDateTime::Jul, 1926, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, 29, 4, 4, 203 },
479        { { 22, wxDateTime::Oct, 2076, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, 43, 4, 4, 296 },
480        { {  1, wxDateTime::Jul, 1967, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, 26, 1, 1, 182 },
481        { {  8, wxDateTime::Nov, 2004, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, 46, 2, 2, 313 },
482        { { 21, wxDateTime::Mar, 1920, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, 12, 3, 4,  81 },
483        { {  7, wxDateTime::Jan, 1965, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },  1, 2, 2,   7 },
484        { { 19, wxDateTime::Oct, 1999, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, 42, 4, 4, 292 },
485        { { 13, wxDateTime::Aug, 1955, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, 32, 2, 2, 225 },
486        { { 18, wxDateTime::Jul, 2087, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, 29, 3, 3, 199 },
487        { {  2, wxDateTime::Sep, 2028, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, 35, 1, 1, 246 },
488        { { 28, wxDateTime::Jul, 1945, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, 30, 5, 4, 209 },
489        { { 15, wxDateTime::Jun, 1901, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, 24, 3, 3, 166 },
490        { { 10, wxDateTime::Oct, 1939, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, 41, 3, 2, 283 },
491        { {  3, wxDateTime::Dec, 1965, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, 48, 1, 1, 337 },
492        { { 23, wxDateTime::Feb, 1940, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },  8, 4, 4,  54 },
493        { {  2, wxDateTime::Jan, 1987, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },  1, 1, 1,   2 },
494        { { 11, wxDateTime::Aug, 2079, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, 32, 2, 2, 223 },
495        { {  2, wxDateTime::Feb, 2063, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },  5, 1, 1,  33 },
496        { { 16, wxDateTime::Oct, 1942, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, 42, 3, 3, 289 },
497        { { 30, wxDateTime::Dec, 2003, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },  1, 5, 5, 364 },
498        { {  2, wxDateTime::Jan, 2004, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },  1, 1, 1,   2 },
499    };
500
501    for ( size_t n = 0; n < WXSIZEOF(weekNumberTestDates); n++ )
502    {
503        const WeekNumberTestData& wn = weekNumberTestDates[n];
504        const Date& d = wn.date;
505
506        wxDateTime dt = d.DT();
507
508        wxDateTime::wxDateTime_t
509            week = dt.GetWeekOfYear(wxDateTime::Monday_First),
510            wmon = dt.GetWeekOfMonth(wxDateTime::Monday_First),
511            wmon2 = dt.GetWeekOfMonth(wxDateTime::Sunday_First),
512            dnum = dt.GetDayOfYear();
513
514        CPPUNIT_ASSERT( dnum == wn.dnum );
515        CPPUNIT_ASSERT( wmon == wn.wmon );
516        CPPUNIT_ASSERT( wmon2 == wn.wmon2 );
517        CPPUNIT_ASSERT( week == wn.week );
518
519        int year = d.year;
520        if ( week == 1 && d.month != wxDateTime::Jan )
521        {
522            // this means we're in the first week of the next year
523            year++;
524        }
525
526        wxDateTime
527            dt2 = wxDateTime::SetToWeekOfYear(year, week, dt.GetWeekDay());
528        CPPUNIT_ASSERT( dt2 == dt );
529    }
530}
531
532// test DST applicability
533void DateTimeTestCase::TestTimeDST()
534{
535    // taken from http://www.energy.ca.gov/daylightsaving.html
536    static const Date datesDST[2][2004 - 1900 + 1] =
537    {
538        {
539            { 1, wxDateTime::Apr, 1990, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
540            { 7, wxDateTime::Apr, 1991, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
541            { 5, wxDateTime::Apr, 1992, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
542            { 4, wxDateTime::Apr, 1993, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
543            { 3, wxDateTime::Apr, 1994, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
544            { 2, wxDateTime::Apr, 1995, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
545            { 7, wxDateTime::Apr, 1996, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
546            { 6, wxDateTime::Apr, 1997, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
547            { 5, wxDateTime::Apr, 1998, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
548            { 4, wxDateTime::Apr, 1999, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
549            { 2, wxDateTime::Apr, 2000, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
550            { 1, wxDateTime::Apr, 2001, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
551            { 7, wxDateTime::Apr, 2002, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
552            { 6, wxDateTime::Apr, 2003, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
553            { 4, wxDateTime::Apr, 2004, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
554        },
555        {
556            { 28, wxDateTime::Oct, 1990, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
557            { 27, wxDateTime::Oct, 1991, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
558            { 25, wxDateTime::Oct, 1992, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
559            { 31, wxDateTime::Oct, 1993, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
560            { 30, wxDateTime::Oct, 1994, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
561            { 29, wxDateTime::Oct, 1995, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
562            { 27, wxDateTime::Oct, 1996, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
563            { 26, wxDateTime::Oct, 1997, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
564            { 25, wxDateTime::Oct, 1998, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
565            { 31, wxDateTime::Oct, 1999, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
566            { 29, wxDateTime::Oct, 2000, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
567            { 28, wxDateTime::Oct, 2001, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
568            { 27, wxDateTime::Oct, 2002, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
569            { 26, wxDateTime::Oct, 2003, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
570            { 31, wxDateTime::Oct, 2004, 0, 0, 0, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
571        }
572    };
573
574    for ( int year = 1990; year < 2005; year++ )
575    {
576        wxDateTime dtBegin = wxDateTime::GetBeginDST(year, wxDateTime::USA),
577                   dtEnd = wxDateTime::GetEndDST(year, wxDateTime::USA);
578
579        size_t n = year - 1990;
580        const Date& dBegin = datesDST[0][n];
581        const Date& dEnd = datesDST[1][n];
582
583        CPPUNIT_ASSERT( dBegin.SameDay(dtBegin.GetTm()) );
584        CPPUNIT_ASSERT( dEnd.SameDay(dtEnd.GetTm()) );
585    }
586}
587
588// test wxDateTime -> text conversion
589void DateTimeTestCase::TestTimeFormat()
590{
591    // some information may be lost during conversion, so store what kind
592    // of info should we recover after a round trip
593    enum CompareKind
594    {
595        CompareNone,        // don't try comparing
596        CompareBoth,        // dates and times should be identical
597        CompareYear,        // don't compare centuries (fails for 2 digit years)
598        CompareDate,        // dates only
599        CompareTime         // time only
600    };
601
602    static const struct
603    {
604        CompareKind compareKind;
605        const wxChar *format;
606    } formatTestFormats[] =
607    {
608       { CompareYear, _T("---> %c") }, // %c could use 2 digit years
609       { CompareDate, _T("Date is %A, %d of %B, in year %Y") },
610       { CompareYear, _T("Date is %x, time is %X") }, // %x could use 2 digits
611       { CompareTime, _T("Time is %H:%M:%S or %I:%M:%S %p") },
612       { CompareNone, _T("The day of year: %j, the week of year: %W") },
613       { CompareDate, _T("ISO date without separators: %Y%m%d") },
614    };
615
616    static const Date formatTestDates[] =
617    {
618        { 29, wxDateTime::May, 1976, 18, 30, 00, 0.0, wxDateTime::Inv_WeekDay },
619        { 31, wxDateTime::Dec, 1999, 23, 30, 00, 0.0, wxDateTime::Inv_WeekDay },
620        {  6, wxDateTime::Feb, 1937, 23, 30, 00, 0.0, wxDateTime::Inv_WeekDay },
621        {  6, wxDateTime::Feb, 1856, 23, 30, 00, 0.0, wxDateTime::Inv_WeekDay },
622        {  6, wxDateTime::Feb, 1857, 23, 30, 00, 0.0, wxDateTime::Inv_WeekDay },
623        { 29, wxDateTime::May, 2076, 18, 30, 00, 0.0, wxDateTime::Inv_WeekDay },
624        { 29, wxDateTime::Feb, 2400, 02, 15, 25, 0.0, wxDateTime::Inv_WeekDay },
625#if 0
626        // Need to add support for BCE dates.
627        { 01, wxDateTime::Jan,  -52, 03, 16, 47, 0.0, wxDateTime::Inv_WeekDay },
628#endif
629    };
630
631    for ( size_t d = 0; d < WXSIZEOF(formatTestDates); d++ )
632    {
633        wxDateTime dt = formatTestDates[d].DT();
634        for ( size_t n = 0; n < WXSIZEOF(formatTestFormats); n++ )
635        {
636            const wxChar *fmt = formatTestFormats[n].format;
637            wxString s = dt.Format(fmt);
638
639            // what can we recover?
640            CompareKind kind = formatTestFormats[n].compareKind;
641
642            // convert back
643            wxDateTime dt2;
644            const wxChar *result = dt2.ParseFormat(s, fmt);
645            if ( !result )
646            {
647                // converion failed - should it have?
648                CPPUNIT_ASSERT( kind == CompareNone );
649            }
650            else // conversion succeeded
651            {
652                // should have parsed the entire string
653                CPPUNIT_ASSERT( !*result );
654
655                switch ( kind )
656                {
657                    case CompareYear:
658                        if ( dt2.GetCentury() != dt.GetCentury() )
659                        {
660                            CPPUNIT_ASSERT_EQUAL(dt.GetYear() % 100,
661                                                 dt2.GetYear() % 100);
662
663                            dt2.SetYear(dt.GetYear());
664                        }
665                        // fall through and compare everything
666
667                    case CompareBoth:
668                        CPPUNIT_ASSERT_EQUAL( dt, dt2 );
669                        break;
670
671                    case CompareDate:
672                        CPPUNIT_ASSERT( dt.IsSameDate(dt2) );
673                        break;
674
675                    case CompareTime:
676                        CPPUNIT_ASSERT( dt.IsSameTime(dt2) );
677                        break;
678
679                    case CompareNone:
680                        wxFAIL_MSG( _T("unexpected") );
681                        break;
682                }
683            }
684        }
685    }
686}
687
688void DateTimeTestCase::TestTimeSpanFormat()
689{
690    static const struct TimeSpanFormatTestData
691    {
692        long h, min, sec, msec;
693        const wxChar *fmt;
694        const wxChar *result;
695    } testSpans[] =
696    {
697        {   12, 34, 56, 789, _T("%H:%M:%S.%l"),   _T("12:34:56.789")          },
698        {    1,  2,  3,   0, _T("%H:%M:%S"),      _T("01:02:03")              },
699        {    1,  2,  3,   0, _T("%S"),            _T("3723")                  },
700        {   -1, -2, -3,   0, _T("%S"),            _T("-3723")                 },
701        {   -1, -2, -3,   0, _T("%H:%M:%S"),      _T("-01:02:03")             },
702        {   26,  0,  0,   0, _T("%H"),            _T("26")                    },
703        {   26,  0,  0,   0, _T("%D, %H"),        _T("1, 02")                 },
704        {  -26,  0,  0,   0, _T("%H"),            _T("-26")                   },
705        {  -26,  0,  0,   0, _T("%D, %H"),        _T("-1, 02")                },
706        {  219,  0,  0,   0, _T("%H"),            _T("219")                   },
707        {  219,  0,  0,   0, _T("%D, %H"),        _T("9, 03")                 },
708        {  219,  0,  0,   0, _T("%E, %D, %H"),    _T("1, 2, 03")              },
709        {    0, -1,  0,   0, _T("%H:%M:%S"),      _T("-00:01:00")             },
710        {    0,  0, -1,   0, _T("%H:%M:%S"),      _T("-00:00:01")             },
711    };
712
713    for ( size_t n = 0; n < WXSIZEOF(testSpans); n++ )
714    {
715        const TimeSpanFormatTestData& td = testSpans[n];
716        wxTimeSpan ts(td.h, td.min, td.sec, td.msec);
717        CPPUNIT_ASSERT_EQUAL( wxString(td.result), ts.Format(td.fmt) );
718    }
719}
720
721void DateTimeTestCase::TestTimeTicks()
722{
723    static const wxDateTime::TimeZone TZ_LOCAL(wxDateTime::Local);
724    static const wxDateTime::TimeZone TZ_TEST(wxDateTime::NZST);
725
726    // this offset is needed to make the test work in any time zone when we
727    // only have expected test results in UTC in testDates
728    static const long tzOffset = TZ_LOCAL.GetOffset() - TZ_TEST.GetOffset();
729
730    for ( size_t n = 0; n < WXSIZEOF(testDates); n++ )
731    {
732        const Date& d = testDates[n];
733        if ( d.gmticks == -1 )
734            continue;
735
736        wxDateTime dt = d.DT().MakeTimezone(TZ_TEST, true /* no DST */);
737
738        // GetValue() returns internal UTC-based representation, we need to
739        // convert it to local TZ before comparing
740        long ticks = (dt.GetValue() / 1000).ToLong() + TZ_LOCAL.GetOffset();
741        if ( dt.IsDST() )
742            ticks += 3600;
743        WX_ASSERT_TIME_T_EQUAL( d.gmticks, ticks + tzOffset );
744
745        dt = d.DT().FromTimezone(wxDateTime::UTC);
746        ticks = (dt.GetValue() / 1000).ToLong();
747        WX_ASSERT_TIME_T_EQUAL( d.gmticks, ticks );
748    }
749}
750
751// test parsing dates in RFC822 format
752void DateTimeTestCase::TestParceRFC822()
753{
754    static const struct ParseTestData
755    {
756        const wxChar *rfc822;
757        Date date;              // NB: this should be in UTC
758        bool good;
759    } parseTestDates[] =
760    {
761        {
762            _T("Sat, 18 Dec 1999 00:46:40 +0100"),
763            { 17, wxDateTime::Dec, 1999, 23, 46, 40 },
764            true
765        },
766        {
767            _T("Wed, 1 Dec 1999 05:17:20 +0300"),
768            {  1, wxDateTime::Dec, 1999, 2, 17, 20 },
769            true
770        },
771        {
772            _T("Sun, 28 Aug 2005 03:31:30 +0200"),
773            {  28, wxDateTime::Aug, 2005, 1, 31, 30 },
774            true
775        },
776
777        {
778            _T("Sat, 18 Dec 1999 10:48:30 -0500"),
779            {  18, wxDateTime::Dec, 1999, 15, 48, 30 },
780            true
781        },
782    };
783
784    for ( size_t n = 0; n < WXSIZEOF(parseTestDates); n++ )
785    {
786        wxDateTime dt;
787        if ( dt.ParseRfc822Date(parseTestDates[n].rfc822) )
788        {
789            CPPUNIT_ASSERT( parseTestDates[n].good );
790
791            wxDateTime dtReal = parseTestDates[n].date.DT().FromUTC();
792            CPPUNIT_ASSERT_EQUAL( dtReal, dt );
793        }
794        else // failed to parse
795        {
796            CPPUNIT_ASSERT( !parseTestDates[n].good );
797        }
798    }
799}
800
801// test parsing dates in free format
802void DateTimeTestCase::TestDateParse()
803{
804    static const struct ParseTestData
805    {
806        const wxChar *str;
807        Date date;              // NB: this should be in UTC
808        bool good;
809    } parseTestDates[] =
810    {
811        { _T("21 Mar 2006"), { 21, wxDateTime::Mar, 2006 }, true },
812        { _T("29 Feb 1976"), { 29, wxDateTime::Feb, 1976 }, true },
813        { _T("Feb 29 1976"), { 29, wxDateTime::Feb, 1976 }, true },
814        { _T("31/03/06"),    { 31, wxDateTime::Mar,    6 }, true },
815        { _T("31/03/2006"),  { 31, wxDateTime::Mar, 2006 }, true },
816
817        // some invalid ones too
818        { _T("29 Feb 2006") },
819        { _T("31/04/06") },
820        { _T("bloordyblop") }
821    };
822
823    // special cases
824    wxDateTime dt;
825    CPPUNIT_ASSERT( dt.ParseDate(_T("today")) );
826    CPPUNIT_ASSERT_EQUAL( wxDateTime::Today(), dt );
827
828    for ( size_t n = 0; n < WXSIZEOF(parseTestDates); n++ )
829    {
830        wxDateTime dt;
831        if ( dt.ParseDate(parseTestDates[n].str) )
832        {
833            CPPUNIT_ASSERT( parseTestDates[n].good );
834
835            CPPUNIT_ASSERT_EQUAL( parseTestDates[n].date.DT(), dt );
836        }
837        else // failed to parse
838        {
839            CPPUNIT_ASSERT( !parseTestDates[n].good );
840        }
841    }
842}
843
844void DateTimeTestCase::TestTimeArithmetics()
845{
846    static const wxDateSpan testArithmData[] =
847    {
848        wxDateSpan::Day(),
849        wxDateSpan::Week(),
850        wxDateSpan::Month(),
851        wxDateSpan::Year(),
852    };
853
854    // the test will *not* work with arbitrary date!
855    wxDateTime dt(2, wxDateTime::Dec, 1999),
856               dt1,
857               dt2;
858
859    for ( size_t n = 0; n < WXSIZEOF(testArithmData); n++ )
860    {
861        const wxDateSpan& span = testArithmData[n];
862        dt1 = dt + span;
863        dt2 = dt - span;
864
865        CPPUNIT_ASSERT( dt1 - span == dt );
866        CPPUNIT_ASSERT( dt2 + span == dt );
867        CPPUNIT_ASSERT( dt2 + 2*span == dt1 );
868    }
869}
870
871void DateTimeTestCase::TestDSTBug()
872{
873    /////////////////////////
874    // Test GetEndDST()
875    wxDateTime dt = wxDateTime::GetEndDST(2004);
876    CPPUNIT_ASSERT_EQUAL(31, (int)dt.GetDay());
877    CPPUNIT_ASSERT_EQUAL(wxDateTime::Oct, dt.GetMonth());
878    CPPUNIT_ASSERT_EQUAL(2004, (int)dt.GetYear());
879    CPPUNIT_ASSERT_EQUAL(2, (int)dt.GetHour());
880    CPPUNIT_ASSERT_EQUAL(0, (int)dt.GetMinute());
881    CPPUNIT_ASSERT_EQUAL(0, (int)dt.GetSecond());
882    CPPUNIT_ASSERT_EQUAL(0, (int)dt.GetMillisecond());
883
884    /////////////////////////
885    // Test ResetTime()
886    dt.SetHour(5);
887    CPPUNIT_ASSERT_EQUAL(5, (int)dt.GetHour());
888    dt.ResetTime();
889    CPPUNIT_ASSERT_EQUAL(31, (int)dt.GetDay());
890    CPPUNIT_ASSERT_EQUAL(wxDateTime::Oct, dt.GetMonth());
891    CPPUNIT_ASSERT_EQUAL(2004, (int)dt.GetYear());
892    CPPUNIT_ASSERT_EQUAL(0, (int)dt.GetHour());
893    CPPUNIT_ASSERT_EQUAL(0, (int)dt.GetMinute());
894    CPPUNIT_ASSERT_EQUAL(0, (int)dt.GetSecond());
895    CPPUNIT_ASSERT_EQUAL(0, (int)dt.GetMillisecond());
896
897    dt.Set(1, 0, 0, 0);
898    CPPUNIT_ASSERT_EQUAL(1, (int)dt.GetHour());
899
900    /////////////////////////
901    // Test Today()
902#ifdef CHANGE_SYSTEM_DATE
903    {
904        DateChanger change(2004, 10, 31, 5, 0, 0);
905        dt = wxDateTime::Today();
906    }
907
908    CPPUNIT_ASSERT_EQUAL(31, (int)dt.GetDay());
909    CPPUNIT_ASSERT_EQUAL(wxDateTime::Oct, dt.GetMonth());
910    CPPUNIT_ASSERT_EQUAL(2004, (int)dt.GetYear());
911    CPPUNIT_ASSERT_EQUAL(0, (int)dt.GetHour());
912    CPPUNIT_ASSERT_EQUAL(0, (int)dt.GetMinute());
913    CPPUNIT_ASSERT_EQUAL(0, (int)dt.GetSecond());
914    CPPUNIT_ASSERT_EQUAL(0, (int)dt.GetMillisecond());
915
916    /////////////////////////
917    // Test Set(hour, minute, second, milli)
918    wxDateTime dt2;
919    {
920        DateChanger change(2004, 10, 31, 5, 0, 0);
921        dt.Set(1, 30, 0, 0);
922        dt2.Set(5, 30, 0, 0);
923    }
924
925    CPPUNIT_ASSERT_EQUAL(31, (int)dt.GetDay());
926    CPPUNIT_ASSERT_EQUAL(wxDateTime::Oct, dt.GetMonth());
927    CPPUNIT_ASSERT_EQUAL(2004, (int)dt.GetYear());
928    CPPUNIT_ASSERT_EQUAL(1, (int)dt.GetHour());
929    CPPUNIT_ASSERT_EQUAL(30, (int)dt.GetMinute());
930    CPPUNIT_ASSERT_EQUAL(0, (int)dt.GetSecond());
931    CPPUNIT_ASSERT_EQUAL(0, (int)dt.GetMillisecond());
932
933    CPPUNIT_ASSERT_EQUAL(31, (int)dt2.GetDay());
934    CPPUNIT_ASSERT_EQUAL(wxDateTime::Oct, dt2.GetMonth());
935    CPPUNIT_ASSERT_EQUAL(2004, (int)dt2.GetYear());
936    CPPUNIT_ASSERT_EQUAL(5, (int)dt2.GetHour());
937    CPPUNIT_ASSERT_EQUAL(30, (int)dt2.GetMinute());
938    CPPUNIT_ASSERT_EQUAL(0, (int)dt2.GetSecond());
939    CPPUNIT_ASSERT_EQUAL(0, (int)dt2.GetMillisecond());
940#endif // CHANGE_SYSTEM_DATE
941}
942
943void DateTimeTestCase::TestDateOnly()
944{
945    wxDateTime dt(19, wxDateTime::Jan, 2007, 15, 01, 00);
946
947    static const wxDateTime::wxDateTime_t DATE_ZERO = 0;
948    CPPUNIT_ASSERT_EQUAL( DATE_ZERO, dt.GetDateOnly().GetHour() );
949    CPPUNIT_ASSERT_EQUAL( DATE_ZERO, dt.GetDateOnly().GetMinute() );
950    CPPUNIT_ASSERT_EQUAL( DATE_ZERO, dt.GetDateOnly().GetSecond() );
951    CPPUNIT_ASSERT_EQUAL( DATE_ZERO, dt.GetDateOnly().GetMillisecond() );
952
953    dt.ResetTime();
954    CPPUNIT_ASSERT_EQUAL( wxDateTime(19, wxDateTime::Jan, 2007), dt );
955
956    CPPUNIT_ASSERT_EQUAL( wxDateTime::Today(), wxDateTime::Now().GetDateOnly() );
957}
958
959#endif // wxUSE_DATETIME
960