Coverage Report

Created: 2025-11-16 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wxwidgets/src/common/datetimefmt.cpp
Line
Count
Source
1
///////////////////////////////////////////////////////////////////////////////
2
// Name:        src/common/datetimefmt.cpp
3
// Purpose:     wxDateTime formatting & parsing code
4
// Author:      Vadim Zeitlin
5
// Created:     11.05.99
6
// Copyright:   (c) 1999 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
7
//              parts of code taken from sndcal library by Scott E. Lee:
8
//
9
//               Copyright 1993-1995, Scott E. Lee, all rights reserved.
10
//               Permission granted to use, copy, modify, distribute and sell
11
//               so long as the above copyright and this permission statement
12
//               are retained in all copies.
13
//
14
// Licence:     wxWindows licence
15
///////////////////////////////////////////////////////////////////////////////
16
17
// ============================================================================
18
// declarations
19
// ============================================================================
20
21
// ----------------------------------------------------------------------------
22
// headers
23
// ----------------------------------------------------------------------------
24
25
// For compilers that support precompilation, includes "wx.h".
26
#include "wx/wxprec.h"
27
28
29
#if !defined(wxUSE_DATETIME) || wxUSE_DATETIME
30
31
#ifndef WX_PRECOMP
32
    #ifdef __WINDOWS__
33
        #include "wx/msw/wrapwin.h"
34
    #endif
35
    #include "wx/string.h"
36
    #include "wx/log.h"
37
    #include "wx/intl.h"
38
    #include "wx/stopwatch.h"           // for wxGetLocalTimeMillis()
39
    #include "wx/module.h"
40
    #include "wx/crt.h"
41
#endif // WX_PRECOMP
42
43
#include "wx/thread.h"
44
45
#include <ctype.h>
46
47
#ifdef __WINDOWS__
48
    #include <winnls.h>
49
    #include <locale.h>
50
#endif
51
52
#include "wx/datetime.h"
53
#include "wx/time.h"
54
#include "wx/uilocale.h"
55
56
// ============================================================================
57
// implementation of wxDateTime
58
// ============================================================================
59
60
// ----------------------------------------------------------------------------
61
// helpers shared between datetime.cpp and datetimefmt.cpp
62
// ----------------------------------------------------------------------------
63
64
extern void wxInitTm(struct tm& tm);
65
extern const tm* wxTryGetTm(tm& tmstruct, time_t t, const wxDateTime::TimeZone& tz);
66
67
extern wxString wxCallStrftime(const wxString& format, const tm* tm);
68
69
// ----------------------------------------------------------------------------
70
// constants (see also datetime.cpp)
71
// ----------------------------------------------------------------------------
72
73
static const int DAYS_PER_WEEK = 7;
74
75
static const int HOURS_PER_DAY = 24;
76
77
static const int SEC_PER_MIN = 60;
78
79
static const int MIN_PER_HOUR = 60;
80
81
// ----------------------------------------------------------------------------
82
// parsing helpers
83
// ----------------------------------------------------------------------------
84
85
namespace
86
{
87
88
// all the functions below taking non-const wxString::const_iterator p advance
89
// it until the end of the match
90
91
// Scans all digits (but no more than len) and returns the resulting number.
92
// Optionally writes number of digits scanned to numScannedDigits.
93
bool GetNumericToken(size_t len,
94
                     wxString::const_iterator& p,
95
                     const wxString::const_iterator& end,
96
                     unsigned long *number,
97
                     size_t *numScannedDigits = nullptr)
98
0
{
99
0
    size_t n = 1;
100
0
    wxString s;
101
0
    while ( p != end && wxIsdigit(*p) )
102
0
    {
103
0
        s += *p++;
104
105
0
        if ( len && ++n > len )
106
0
            break;
107
0
    }
108
109
0
    if (numScannedDigits)
110
0
    {
111
0
        *numScannedDigits = n - 1;
112
0
    }
113
114
0
    return !s.empty() && s.ToULong(number);
115
0
}
116
117
// scans all alphabetic characters and returns the resulting string
118
wxString
119
GetAlphaToken(wxString::const_iterator& p,
120
              const wxString::const_iterator& end)
121
0
{
122
0
    wxString s;
123
0
    while ( p != end && wxIsalpha(*p) )
124
0
    {
125
0
        s += *p++;
126
0
    }
127
128
0
    return s;
129
0
}
130
131
enum
132
{
133
    DateLang_English = 1,
134
    DateLang_Local   = 2
135
};
136
137
// return the month if the string is a month name or Inv_Month otherwise
138
//
139
// flags can contain wxDateTime::Name_Abbr/Name_Full or both of them and lang
140
// can be either DateLang_Local (default) to interpret string as a localized
141
// month name or DateLang_English to parse it as a standard English name or
142
// their combination to interpret it in any way
143
wxDateTime::Month
144
GetMonthFromName(wxString::const_iterator& p,
145
                 const wxString::const_iterator& end,
146
                 int flags,
147
                 int lang)
148
0
{
149
0
    const wxString::const_iterator pOrig = p;
150
0
    const wxString name = GetAlphaToken(p, end);
151
0
    if ( name.empty() )
152
0
        return wxDateTime::Inv_Month;
153
154
0
    wxDateTime::Month mon;
155
0
    for ( mon = wxDateTime::Jan; mon < wxDateTime::Inv_Month; wxNextMonth(mon) )
156
0
    {
157
        // case-insensitive comparison either one of or with both abbreviated
158
        // and not versions
159
0
        if ( flags & wxDateTime::Name_Full )
160
0
        {
161
0
            if ( lang & DateLang_English )
162
0
            {
163
0
                if ( name.CmpNoCase(wxDateTime::GetEnglishMonthName(mon,
164
0
                        wxDateTime::Name_Full)) == 0 )
165
0
                    break;
166
0
            }
167
168
0
            if ( lang & DateLang_Local )
169
0
            {
170
0
                if ( name.CmpNoCase(wxDateTime::GetMonthName(mon,
171
0
                        wxDateTime::Name_Full)) == 0 )
172
0
                    break;
173
0
            }
174
0
        }
175
176
0
        if ( flags & wxDateTime::Name_Abbr )
177
0
        {
178
0
            if ( lang & DateLang_English )
179
0
            {
180
0
                if ( name.CmpNoCase(wxDateTime::GetEnglishMonthName(mon,
181
0
                        wxDateTime::Name_Abbr)) == 0 )
182
0
                    break;
183
0
            }
184
185
0
            if ( lang & DateLang_Local )
186
0
            {
187
                // some locales (e.g. French one) use periods for the
188
                // abbreviated month names but it's never part of name so
189
                // compare it specially
190
0
                wxString nameAbbr = wxDateTime::GetMonthName(mon,
191
0
                    wxDateTime::Name_Abbr);
192
0
                const bool hasPeriod = *nameAbbr.rbegin() == '.';
193
0
                if ( hasPeriod )
194
0
                    nameAbbr.erase(nameAbbr.end() - 1);
195
196
0
                if ( name.CmpNoCase(nameAbbr) == 0 )
197
0
                {
198
0
                    if ( hasPeriod )
199
0
                    {
200
                        // skip trailing period if it was part of the match
201
0
                        if ( *p == '.' )
202
0
                            ++p;
203
0
                        else // no match as no matching period
204
0
                            continue;
205
0
                    }
206
207
0
                    break;
208
0
                }
209
0
            }
210
0
        }
211
0
    }
212
213
0
    if ( mon == wxDateTime::Inv_Month )
214
0
        p = pOrig;
215
216
0
    return mon;
217
0
}
218
219
// return the weekday if the string is a weekday name or Inv_WeekDay otherwise
220
//
221
// flags and lang parameters have the same meaning as for GetMonthFromName()
222
// above
223
wxDateTime::WeekDay
224
GetWeekDayFromName(wxString::const_iterator& p,
225
                   const wxString::const_iterator& end,
226
                   int flags, int lang)
227
0
{
228
0
    const wxString::const_iterator pOrig = p;
229
0
    const wxString name = GetAlphaToken(p, end);
230
0
    if ( name.empty() )
231
0
        return wxDateTime::Inv_WeekDay;
232
233
0
    wxDateTime::WeekDay wd;
234
0
    for ( wd = wxDateTime::Sun; wd < wxDateTime::Inv_WeekDay; wxNextWDay(wd) )
235
0
    {
236
0
        if ( flags & wxDateTime::Name_Full )
237
0
        {
238
0
            if ( lang & DateLang_English )
239
0
            {
240
0
                if ( name.CmpNoCase(wxDateTime::GetEnglishWeekDayName(wd,
241
0
                        wxDateTime::Name_Full)) == 0 )
242
0
                    break;
243
0
            }
244
245
0
            if ( lang & DateLang_Local )
246
0
            {
247
0
                if ( name.CmpNoCase(wxDateTime::GetWeekDayName(wd,
248
0
                        wxDateTime::Name_Full)) == 0 )
249
0
                    break;
250
0
            }
251
0
        }
252
253
0
        if ( flags & wxDateTime::Name_Abbr )
254
0
        {
255
0
            if ( lang & DateLang_English )
256
0
            {
257
0
                if ( name.CmpNoCase(wxDateTime::GetEnglishWeekDayName(wd,
258
0
                        wxDateTime::Name_Abbr)) == 0 )
259
0
                    break;
260
0
            }
261
262
0
            if ( lang & DateLang_Local )
263
0
            {
264
0
                if ( name.CmpNoCase(wxDateTime::GetWeekDayName(wd,
265
0
                        wxDateTime::Name_Abbr)) == 0 )
266
0
                    break;
267
0
            }
268
0
        }
269
0
    }
270
271
0
    if ( wd == wxDateTime::Inv_WeekDay )
272
0
        p = pOrig;
273
274
0
    return wd;
275
0
}
276
277
// parses string starting at given iterator using the specified format and,
278
// optionally, a fall back format (and optionally another one... but it stops
279
// there, really)
280
//
281
// if unsuccessful, returns invalid wxDateTime without changing p; otherwise
282
// advance p to the end of the match and returns wxDateTime containing the
283
// results of the parsing
284
wxDateTime
285
ParseFormatAt(wxString::const_iterator& p,
286
              const wxString::const_iterator& end,
287
              const wxString& fmt,
288
              const wxString& fmtAlt = wxString())
289
0
{
290
0
    const wxString str(p, end);
291
0
    wxString::const_iterator endParse;
292
0
    wxDateTime dt;
293
294
    // Use a default date outside of the DST period to avoid problems with
295
    // parsing the time differently depending on the today's date (which is used
296
    // as the fall back date if none is explicitly specified).
297
0
    static const wxDateTime dtDef(1, wxDateTime::Jan, 2012);
298
299
0
    if ( dt.ParseFormat(str, fmt, dtDef, &endParse) ||
300
0
            (!fmtAlt.empty() && dt.ParseFormat(str, fmtAlt, dtDef, &endParse)) )
301
0
    {
302
0
        p += endParse - str.begin();
303
0
    }
304
    //else: all formats failed
305
306
0
    return dt;
307
0
}
308
309
} // anonymous namespace
310
311
// ----------------------------------------------------------------------------
312
// wxDateTime to/from text representations
313
// ----------------------------------------------------------------------------
314
315
wxString wxDateTime::Format(const wxString& formatp, const TimeZone& tz) const
316
0
{
317
0
    wxCHECK_MSG( !formatp.empty(), wxEmptyString,
318
0
                 wxT("null format in wxDateTime::Format") );
319
320
0
    wxString format = formatp;
321
#ifdef __WXOSX__
322
#if wxUSE_INTL
323
    if ( format.Contains("%c") )
324
        format.Replace("%c", wxUILocale::GetCurrent().GetInfo(wxLOCALE_DATE_TIME_FMT));
325
    if ( format.Contains("%x") )
326
        format.Replace("%x", wxUILocale::GetCurrent().GetInfo(wxLOCALE_SHORT_DATE_FMT));
327
    if ( format.Contains("%X") )
328
        format.Replace("%X", wxUILocale::GetCurrent().GetInfo(wxLOCALE_TIME_FMT));
329
#endif // wxUSE_INTL
330
#endif // __WXOSX__
331
    // we have to use our own implementation if the date is out of range of
332
    // strftime()
333
0
#ifdef wxHAS_STRFTIME
334
0
    time_t time = GetTicks();
335
336
0
    bool canUseStrftime = time != (time_t)-1;
337
0
    bool isPercent = false;
338
339
    // We also can't use strftime() if we use non standard specifier: either
340
    // our own extension "%l" or one of C99/POSIX specifiers not supported when
341
    // using MinGW, see https://sourceforge.net/p/mingw-w64/bugs/793/
342
0
    for ( wxString::const_iterator p = format.begin();
343
0
          canUseStrftime && p != format.end();
344
0
          ++p )
345
0
    {
346
0
        if (!isPercent)
347
0
        {
348
0
            isPercent = *p == '%';
349
0
            continue;
350
0
        }
351
0
        isPercent = false;
352
353
        // set the default format
354
0
        switch ( (*p).GetValue() )
355
0
        {
356
0
            case 'l':
357
#ifdef __MINGW32__
358
            case 'F':
359
            case 'g':
360
            case 'G':
361
            case 'V':
362
            case 'z':
363
#endif // __MINGW32__
364
0
                canUseStrftime = false;
365
0
                break;
366
0
        }
367
0
    }
368
369
0
    if ( canUseStrftime )
370
0
    {
371
        // Try using strftime()
372
0
        struct tm tmstruct;
373
0
        if ( const tm* tm = wxTryGetTm(tmstruct, time, tz) )
374
0
        {
375
0
            return wxCallStrftime(format, tm);
376
0
        }
377
0
    }
378
    //else: use generic code below
379
0
#endif // wxHAS_STRFTIME
380
381
    // we only parse ANSI C format specifications here, no POSIX 2
382
    // complications, no GNU extensions but we do add support for a "%l" format
383
    // specifier allowing to get the number of milliseconds
384
0
    Tm tm = GetTm(tz);
385
386
    // used for calls to strftime() when we only deal with time
387
0
    struct tm tmTimeOnly;
388
0
    memset(&tmTimeOnly, 0, sizeof(tmTimeOnly));
389
0
    tmTimeOnly.tm_hour = tm.hour;
390
0
    tmTimeOnly.tm_min = tm.min;
391
0
    tmTimeOnly.tm_sec = tm.sec;
392
0
    tmTimeOnly.tm_mday = 1;         // any date will do, use 1976-01-01
393
0
    tmTimeOnly.tm_mon = 0;
394
0
    tmTimeOnly.tm_year = 76;
395
0
    tmTimeOnly.tm_isdst = 0;        // no DST, we adjust for tz ourselves
396
397
0
    wxString res, fmt;
398
0
    for ( wxString::const_iterator p = format.begin(); p != format.end(); ++p )
399
0
    {
400
0
        if ( *p != wxT('%') )
401
0
        {
402
            // copy as is
403
0
            res += *p;
404
405
0
            continue;
406
0
        }
407
408
        // set the default format
409
0
        switch ( (*++p).GetValue() )
410
0
        {
411
0
            case wxT('Y'):               // year has 4 digits
412
0
            case wxT('G'):               // (and ISO week year too)
413
0
            case wxT('z'):               // time zone as well
414
0
                fmt = wxT("%04d");
415
0
                break;
416
417
0
            case wxT('j'):               // day of year has 3 digits
418
0
            case wxT('l'):               // milliseconds have 3 digits
419
0
                fmt = wxT("%03d");
420
0
                break;
421
422
0
            case wxT('w'):               // week day as number has only one
423
0
                fmt = wxT("%d");
424
0
                break;
425
426
0
            default:
427
                // it's either another valid format specifier in which case
428
                // the format is "%02d" (for all the rest) or we have the
429
                // field width preceding the format in which case it will
430
                // override the default format anyhow
431
0
                fmt = wxT("%02d");
432
0
        }
433
434
0
        bool restart = true;
435
0
        while ( restart )
436
0
        {
437
0
            restart = false;
438
439
            // start of the format specification
440
0
            switch ( (*p).GetValue() )
441
0
            {
442
0
                case wxT('a'):       // a weekday name
443
0
                case wxT('A'):
444
                    // second parameter should be true for abbreviated names
445
0
                    res += GetWeekDayName(tm.GetWeekDay(),
446
0
                                          *p == wxT('a') ? Name_Abbr : Name_Full);
447
0
                    break;
448
449
0
                case wxT('b'):       // a month name
450
0
                case wxT('B'):
451
0
                    res += GetMonthName(tm.mon,
452
0
                                        *p == wxT('b') ? Name_Abbr : Name_Full);
453
0
                    break;
454
455
0
                case wxT('c'):       // locale default date and time  representation
456
0
                case wxT('x'):       // locale default date representation
457
0
#ifdef wxHAS_STRFTIME
458
                    //
459
                    // the problem: there is no way to know what do these format
460
                    // specifications correspond to for the current locale.
461
                    //
462
                    // the solution: use a hack and still use strftime(): first
463
                    // find the YEAR which is a year in the strftime() range (1970
464
                    // - 2038) whose Jan 1 falls on the same week day as the Jan 1
465
                    // of the real year. Then make a copy of the format and
466
                    // replace all occurrences of YEAR in it with some unique
467
                    // string not appearing anywhere else in it, then use
468
                    // strftime() to format the date in year YEAR and then replace
469
                    // YEAR back by the real year and the unique replacement
470
                    // string back with YEAR. Notice that "all occurrences of YEAR"
471
                    // means all occurrences of 4 digit as well as 2 digit form!
472
                    //
473
                    // the bugs: we assume that neither of %c nor %x contains any
474
                    // fields which may change between the YEAR and real year. For
475
                    // example, the week number (%U, %W) and the day number (%j)
476
                    // will change if one of these years is leap and the other one
477
                    // is not!
478
0
                    {
479
                        // find the YEAR: normally, for any year X, Jan 1 of the
480
                        // year X + 28 is the same weekday as Jan 1 of X (because
481
                        // the weekday advances by 1 for each normal X and by 2
482
                        // for each leap X, hence by 5 every 4 years or by 35
483
                        // which is 0 mod 7 every 28 years) but this rule breaks
484
                        // down if there are years between X and Y which are
485
                        // divisible by 4 but not leap (i.e. divisible by 100 but
486
                        // not 400), hence the correction.
487
488
0
                        int yearReal = GetYear(tz);
489
0
                        int mod28 = yearReal % 28;
490
491
                        // be careful to not go too far - we risk to leave the
492
                        // supported range
493
0
                        int year;
494
0
                        if ( mod28 < 10 )
495
0
                        {
496
0
                            year = 1988 + mod28;      // 1988 == 0 (mod 28)
497
0
                        }
498
0
                        else
499
0
                        {
500
0
                            year = 1970 + mod28 - 10; // 1970 == 10 (mod 28)
501
0
                        }
502
503
0
                        int nCentury = year / 100,
504
0
                            nCenturyReal = yearReal / 100;
505
506
                        // need to adjust for the years divisble by 400 which are
507
                        // not leap but are counted like leap ones if we just take
508
                        // the number of centuries in between for nLostWeekDays
509
0
                        int nLostWeekDays = (nCentury - nCenturyReal) -
510
0
                                            (nCentury / 4 - nCenturyReal / 4);
511
512
                        // we have to gain back the "lost" weekdays: note that the
513
                        // effect of this loop is to not do anything to
514
                        // nLostWeekDays (which we won't use any more), but to
515
                        // (indirectly) set the year correctly
516
0
                        while ( (nLostWeekDays % 7) != 0 )
517
0
                        {
518
0
                            nLostWeekDays += (year++ % 4) ? 1 : 2;
519
0
                        }
520
521
                        // finally move the year below 2000 so that the 2-digit
522
                        // year number can never match the month or day of the
523
                        // month when we do the replacements below
524
0
                        if ( year >= 2000 )
525
0
                            year -= 28;
526
527
0
                        wxASSERT_MSG( year >= 1970 && year < 2000,
528
0
                                      wxT("logic error in wxDateTime::Format") );
529
530
531
                        // use strftime() to format the same date but in supported
532
                        // year
533
                        //
534
                        // NB: we assume that strftime() doesn't check for the
535
                        //     date validity and will happily format the date
536
                        //     corresponding to Feb 29 of a non leap year (which
537
                        //     may happen if yearReal was leap and year is not)
538
0
                        struct tm tmAdjusted;
539
0
                        wxInitTm(tmAdjusted);
540
0
                        tmAdjusted.tm_hour = tm.hour;
541
0
                        tmAdjusted.tm_min = tm.min;
542
0
                        tmAdjusted.tm_sec = tm.sec;
543
0
                        tmAdjusted.tm_wday = tm.GetWeekDay();
544
0
                        tmAdjusted.tm_yday = GetDayOfYear();
545
0
                        tmAdjusted.tm_mday = tm.mday;
546
0
                        tmAdjusted.tm_mon = tm.mon;
547
0
                        tmAdjusted.tm_year = year - 1900;
548
0
                        tmAdjusted.tm_isdst = 0; // no DST, already adjusted
549
0
                        wxString str = wxCallStrftime(*p == wxT('c') ? wxT("%c")
550
0
                                                                  : wxT("%x"),
551
0
                                                    &tmAdjusted);
552
553
                        // now replace the replacement year with the real year:
554
                        // notice that we have to replace the 4 digit year with
555
                        // a unique string not appearing in strftime() output
556
                        // first to prevent the 2 digit year from matching any
557
                        // substring of the 4 digit year (but any day, month,
558
                        // hours or minutes components should be safe because
559
                        // they are never in 70-99 range)
560
0
                        wxString replacement("|");
561
0
                        while ( str.find(replacement) != wxString::npos )
562
0
                            replacement += '|';
563
564
0
                        str.Replace(wxString::Format("%d", year),
565
0
                                    replacement);
566
0
                        str.Replace(wxString::Format("%d", year % 100),
567
0
                                    wxString::Format("%d", yearReal % 100));
568
0
                        str.Replace(replacement,
569
0
                                    wxString::Format("%d", yearReal));
570
571
0
                        res += str;
572
0
                    }
573
#else // !wxHAS_STRFTIME
574
                    // Use "%m/%d/%y %H:%M:%S" format instead
575
                    res += wxString::Format(wxT("%02d/%02d/%04d %02d:%02d:%02d"),
576
                            tm.mon+1,tm.mday, tm.year, tm.hour, tm.min, tm.sec);
577
#endif // wxHAS_STRFTIME/!wxHAS_STRFTIME
578
0
                    break;
579
580
0
                case wxT('d'):       // day of a month (01-31)
581
0
                    res += wxString::Format(fmt, tm.mday);
582
0
                    break;
583
584
0
                case wxT('F'):      // ISO 8601 date
585
0
                    res += wxString::Format(wxT("%04d-%02d-%02d"), tm.year, tm.mon + 1, tm.mday);
586
0
                    break;
587
588
0
                case wxT('g'):      // 2-digit week-based year
589
0
                    res += wxString::Format(fmt, GetWeekBasedYear() % 100);
590
0
                    break;
591
592
0
                case wxT('G'):       // week-based year with century
593
0
                    res += wxString::Format(fmt, GetWeekBasedYear());
594
0
                    break;
595
596
0
                case wxT('H'):       // hour in 24h format (00-23)
597
0
                    res += wxString::Format(fmt, tm.hour);
598
0
                    break;
599
600
0
                case wxT('I'):       // hour in 12h format (01-12)
601
0
                    {
602
                        // 24h -> 12h, 0h -> 12h too
603
0
                        int hour12 = tm.hour > 12 ? tm.hour - 12
604
0
                                                  : tm.hour ? tm.hour : 12;
605
0
                        res += wxString::Format(fmt, hour12);
606
0
                    }
607
0
                    break;
608
609
0
                case wxT('j'):       // day of the year
610
0
                    res += wxString::Format(fmt, GetDayOfYear(tz));
611
0
                    break;
612
613
0
                case wxT('l'):       // milliseconds (NOT STANDARD)
614
0
                    res += wxString::Format(fmt, GetMillisecond(tz));
615
0
                    break;
616
617
0
                case wxT('m'):       // month as a number (01-12)
618
0
                    res += wxString::Format(fmt, tm.mon + 1);
619
0
                    break;
620
621
0
                case wxT('M'):       // minute as a decimal number (00-59)
622
0
                    res += wxString::Format(fmt, tm.min);
623
0
                    break;
624
625
0
                case wxT('p'):       // AM or PM string
626
0
#ifdef wxHAS_STRFTIME
627
0
                    res += wxCallStrftime(wxS("%p"), &tmTimeOnly);
628
#else // !wxHAS_STRFTIME
629
                    res += (tmTimeOnly.tm_hour > 12) ? wxT("pm") : wxT("am");
630
#endif // wxHAS_STRFTIME/!wxHAS_STRFTIME
631
0
                    break;
632
633
0
                case wxT('S'):       // second as a decimal number (00-61)
634
0
                    res += wxString::Format(fmt, tm.sec);
635
0
                    break;
636
637
0
                case wxT('U'):       // week number in the year (Sunday 1st week day)
638
0
                    res += wxString::Format(fmt, GetWeekOfYear(Sunday_First, tz));
639
0
                    break;
640
641
0
                case wxT('V'):       // ISO week number
642
0
                case wxT('W'):       // week number in the year (Monday 1st week day)
643
0
                    res += wxString::Format(fmt, GetWeekOfYear(Monday_First, tz));
644
0
                    break;
645
646
0
                case wxT('w'):       // weekday as a number (0-6), Sunday = 0
647
0
                    res += wxString::Format(fmt, tm.GetWeekDay());
648
0
                    break;
649
650
                // case wxT('x'): -- handled with "%c"
651
652
0
                case wxT('X'):       // locale default time representation
653
                    // just use strftime() to format the time for us
654
0
#ifdef wxHAS_STRFTIME
655
0
                    res += wxCallStrftime(wxS("%X"), &tmTimeOnly);
656
#else // !wxHAS_STRFTIME
657
                    res += wxString::Format(wxT("%02d:%02d:%02d"),tm.hour, tm.min, tm.sec);
658
#endif // wxHAS_STRFTIME/!wxHAS_STRFTIME
659
0
                    break;
660
661
0
                case wxT('y'):       // year without century (00-99)
662
0
                    res += wxString::Format(fmt, tm.year % 100);
663
0
                    break;
664
665
0
                case wxT('Y'):       // year with century
666
0
                    res += wxString::Format(fmt, tm.year);
667
0
                    break;
668
669
0
                case wxT('z'):       // time zone as [-+]HHMM
670
0
                    {
671
0
                        int ofs = tz.GetOffset();
672
673
                        // The time zone offset does not include the DST, but
674
                        // we do need to take it into account when showing the
675
                        // time in the local time zone to the user.
676
0
                        if ( ofs == -wxGetTimeZone() && IsDST() == 1 )
677
0
                        {
678
0
                            ofs += DST_OFFSET;
679
0
                        }
680
681
0
                        if ( ofs < 0 )
682
0
                        {
683
0
                            res += '-';
684
0
                            ofs = -ofs;
685
0
                        }
686
0
                        else
687
0
                        {
688
0
                            res += '+';
689
0
                        }
690
691
                        // Converts seconds to HHMM representation.
692
0
                        res += wxString::Format(fmt,
693
0
                                                100*(ofs/3600) + (ofs/60)%60);
694
0
                    }
695
0
                    break;
696
697
0
                case wxT('Z'):       // timezone name
698
0
#ifdef wxHAS_STRFTIME
699
0
                    res += wxCallStrftime(wxS("%Z"), &tmTimeOnly);
700
0
#endif
701
0
                    break;
702
703
0
                default:
704
                    // is it the format width?
705
0
                    for ( fmt.clear();
706
0
                          *p == wxT('-') || *p == wxT('+') ||
707
0
                            *p == wxT(' ') || wxIsdigit(*p);
708
0
                          ++p )
709
0
                    {
710
0
                        fmt += *p;
711
0
                    }
712
713
0
                    if ( !fmt.empty() )
714
0
                    {
715
                        // we've only got the flags and width so far in fmt
716
0
                        fmt.Prepend(wxT('%'));
717
0
                        fmt.Append(wxT('d'));
718
719
0
                        restart = true;
720
721
0
                        break;
722
0
                    }
723
724
                    // no, it wasn't the width
725
0
                    wxFAIL_MSG(wxT("unknown format specifier"));
726
727
0
                    wxFALLTHROUGH;
728
729
0
                case wxT('%'):       // a percent sign
730
0
                    res += *p;
731
0
                    break;
732
733
0
                case 0:             // the end of string
734
0
                    wxFAIL_MSG(wxT("missing format at the end of string"));
735
736
                    // just put the '%' which was the last char in format
737
0
                    res += wxT('%');
738
0
                    break;
739
0
            }
740
0
        }
741
0
    }
742
743
0
    return res;
744
0
}
745
746
bool
747
wxDateTime::ParseRFC822TimeZone(wxString::const_iterator *iterator,
748
                                const wxString::const_iterator &pEnd)
749
0
{
750
0
    wxString::const_iterator& p = *iterator;
751
0
    int offset = 0; // just to suppress warnings
752
0
    if ( *p == '-' || *p == '+' )
753
0
    {
754
        // the explicit offset given: it has the form of hhmm
755
0
        bool plus = *p++ == '+';
756
757
0
        if ( p == pEnd || !wxIsdigit(*p) ||
758
0
             p + 1 == pEnd || !wxIsdigit(*(p + 1)) )
759
0
            return false;
760
761
762
        // hours
763
0
        offset = MIN_PER_HOUR*(10*(*p - '0') + (*(p + 1) - '0'));
764
765
0
        p += 2;
766
767
0
        if ( p == pEnd || !wxIsdigit(*p) ||
768
0
             p + 1 == pEnd || !wxIsdigit(*(p + 1)) )
769
0
            return false;
770
771
        // minutes
772
0
        offset += 10*(*p - '0') + (*(p + 1) - '0');
773
774
0
        if ( !plus )
775
0
            offset = -offset;
776
777
0
        p += 2;
778
0
    }
779
0
    else // not numeric
780
0
    {
781
        // the symbolic timezone given: may be either military timezone or one
782
        // of standard abbreviations
783
0
        if ( p + 1 == pEnd )
784
0
        {
785
            // military: Z = UTC, J unused, A = -1, ..., Y = +12
786
0
            static const int offsets[26] =
787
0
            {
788
                //A  B   C   D   E   F   G   H   I    J    K    L    M
789
0
                -1, -2, -3, -4, -5, -6, -7, -8, -9,   0, -10, -11, -12,
790
                //N  O   P   R   Q   S   T   U   V    W    Z    Y    Z
791
0
                +1, +2, +3, +4, +5, +6, +7, +8, +9, +10, +11, +12, 0
792
0
            };
793
794
0
            if ( *p < wxT('A') || *p > wxT('Z') || *p == wxT('J') )
795
0
                return false;
796
797
0
            offset = offsets[*p++ - 'A'];
798
0
        }
799
0
        else
800
0
        {
801
            // TZ is max 3 characters long; we do not want to consume
802
            // characters beyond that.
803
0
            wxString::const_iterator pPlusMax3 = pEnd;
804
0
            if ( p != pEnd && p + 1 != pEnd && p + 2 != pEnd && p + 3 != pEnd )
805
0
                pPlusMax3 = p + 3;
806
            // abbreviation
807
0
            const wxString tz(p, pPlusMax3);
808
0
            if ( tz == wxT("UT") || tz == wxT("UTC") || tz == wxT("GMT") )
809
0
                offset = 0;
810
0
            else if ( tz == wxT("AST") )
811
0
                offset = AST - GMT0;
812
0
            else if ( tz == wxT("ADT") )
813
0
                offset = ADT - GMT0;
814
0
            else if ( tz == wxT("EST") )
815
0
                offset = EST - GMT0;
816
0
            else if ( tz == wxT("EDT") )
817
0
                offset = EDT - GMT0;
818
0
            else if ( tz == wxT("CST") )
819
0
                offset = CST - GMT0;
820
0
            else if ( tz == wxT("CDT") )
821
0
                offset = CDT - GMT0;
822
0
            else if ( tz == wxT("MST") )
823
0
                offset = MST - GMT0;
824
0
            else if ( tz == wxT("MDT") )
825
0
                offset = MDT - GMT0;
826
0
            else if ( tz == wxT("PST") )
827
0
                offset = PST - GMT0;
828
0
            else if ( tz == wxT("PDT") )
829
0
                offset = PDT - GMT0;
830
0
            else
831
0
                return false;
832
833
0
            p += tz.length();
834
0
        }
835
836
        // make it minutes
837
0
        offset *= MIN_PER_HOUR;
838
0
    }
839
840
    // As always, dealing with the time zone is the most interesting part: we
841
    // can't just use MakeFromTimeZone() here because it wouldn't handle the
842
    // DST correctly because the TZ specified in the string is DST-invariant
843
    // and so we have to manually shift to the UTC first and then convert to
844
    // the local TZ.
845
0
    *this -= wxTimeSpan::Minutes(offset);
846
0
    MakeFromUTC();
847
848
0
    return true;
849
0
}
850
851
// this function parses a string in (strict) RFC 822 format: see the section 5
852
// of the RFC for the detailed description, but briefly it's something of the
853
// form "Sat, 18 Dec 1999 00:48:30 +0100"
854
//
855
// this function is "strict" by design - it must reject anything except true
856
// RFC822 time specs.
857
bool
858
wxDateTime::ParseRfc822Date(const wxString& originalDate, wxString::const_iterator *end)
859
0
{
860
    // This implementation implicitly relies on the assumption that the
861
    // input never ends prematurely (all dereferencing of *p assumes that).
862
    // To avoid iterating beyond the end of buffer, let us append 32 zero bytes
863
    // to the date string (32 being the length of a typical RFC822 timestamp).
864
0
    const wxString date(originalDate + wxString(32, '\0'));
865
866
0
    const wxString::const_iterator pEnd = date.begin() + originalDate.length();
867
0
    wxString::const_iterator p = date.begin();
868
869
    // 1. week day (optional)
870
    // if there is a week day present, it must be separated
871
    // by a comma at position [3].
872
0
    if ( date.length() > 3 && date[3] == ',' )
873
0
    {
874
0
        const wxDateTime::WeekDay
875
0
            wd = GetWeekDayFromName(p, pEnd, Name_Abbr, DateLang_English);
876
0
        if ( wd == Inv_WeekDay )
877
0
            return false;
878
        //else: ignore week day for now, we could also check that it really
879
        //      corresponds to the specified date
880
881
        // 2. separating comma
882
0
        if ( *p++ != ',' || *p++ != ' ' )
883
0
            return false;
884
0
    }
885
886
    // 3. day number
887
0
    if ( !wxIsdigit(*p) )
888
0
        return false;
889
890
0
    wxDateTime_t day = (wxDateTime_t)(*p++ - '0');
891
0
    if ( wxIsdigit(*p) )
892
0
    {
893
0
        day *= 10;
894
0
        day = (wxDateTime_t)(day + (*p++ - '0'));
895
0
    }
896
897
0
    if ( *p++ != ' ' )
898
0
        return false;
899
900
    // 4. month name
901
0
    const Month mon = GetMonthFromName(p, pEnd, Name_Abbr, DateLang_English);
902
0
    if ( mon == Inv_Month )
903
0
        return false;
904
905
0
    if ( *p++ != ' ' )
906
0
        return false;
907
908
    // 5. year
909
0
    if ( !wxIsdigit(*p) )
910
0
        return false;
911
912
0
    int year = *p++ - '0';
913
0
    if ( !wxIsdigit(*p) ) // should have at least 2 digits in the year
914
0
        return false;
915
916
0
    year *= 10;
917
0
    year += *p++ - '0';
918
919
    // is it a 2 digit year (as per original RFC 822) or a 4 digit one?
920
0
    if ( wxIsdigit(*p) )
921
0
    {
922
0
        year *= 10;
923
0
        year += *p++ - '0';
924
925
0
        if ( !wxIsdigit(*p) )
926
0
        {
927
            // no 3 digit years please
928
0
            return false;
929
0
        }
930
931
0
        year *= 10;
932
0
        year += *p++ - '0';
933
0
    }
934
0
    else // a 2-digit year
935
0
    {
936
        // RFC 822 allows 2-digit years, stating that year is
937
        // "any numeric year 1900 or later".
938
        // So how do we interpret e.g. the year '95'? Does it mean:
939
        // a) 1995
940
        // b) 2095
941
        // c) literally 95 AD, two millennia ago?
942
        // NOTE! wx traditionally implemented option c!
943
        // However, the most sensible interpretation for 95 is
944
        // probably 1995, so we shall now use that.
945
        // Years 00..29 are considered to mean 20xx.
946
0
        if ( year >= 30 )
947
0
        {
948
0
            year += 1900;
949
0
        }
950
0
        else
951
0
        {
952
0
            year += 2000;
953
0
        }
954
0
    }
955
956
0
    if ( *p++ != ' ' )
957
0
        return false;
958
959
    // 6. time in hh:mm:ss format with seconds being optional
960
0
    if ( !wxIsdigit(*p) )
961
0
        return false;
962
963
0
    wxDateTime_t hour = (wxDateTime_t)(*p++ - '0');
964
965
0
    if ( !wxIsdigit(*p) )
966
0
        return false;
967
968
0
    hour *= 10;
969
0
    hour = (wxDateTime_t)(hour + (*p++ - '0'));
970
971
0
    if ( *p++ != ':' )
972
0
        return false;
973
974
0
    if ( !wxIsdigit(*p) )
975
0
        return false;
976
977
0
    wxDateTime_t min = (wxDateTime_t)(*p++ - '0');
978
979
0
    if ( !wxIsdigit(*p) )
980
0
        return false;
981
982
0
    min *= 10;
983
0
    min += (wxDateTime_t)(*p++ - '0');
984
985
0
    wxDateTime_t sec = 0;
986
0
    if ( *p == ':' )
987
0
    {
988
0
        ++p;
989
0
        if ( !wxIsdigit(*p) )
990
0
            return false;
991
992
0
        sec = (wxDateTime_t)(*p++ - '0');
993
994
0
        if ( !wxIsdigit(*p) )
995
0
            return false;
996
997
0
        sec *= 10;
998
0
        sec += (wxDateTime_t)(*p++ - '0');
999
0
    }
1000
1001
0
    if ( *p++ != ' ' )
1002
0
        return false;
1003
1004
    // the spec was correct, construct the date from the values we found
1005
0
    Set(day, mon, year, hour, min, sec);
1006
1007
    // 7. now the interesting part: the timezone
1008
1009
0
    if ( !ParseRFC822TimeZone(&p, pEnd) )
1010
0
        return false;
1011
1012
0
    if ( end )
1013
0
        *end = originalDate.begin() + (p - date.begin());
1014
1015
0
    return true;
1016
0
}
1017
1018
const char* wxDateTime::ParseRfc822Date(const char* date)
1019
0
{
1020
0
    wxString::const_iterator end;
1021
0
    wxString dateStr(date);
1022
0
    if ( !ParseRfc822Date(dateStr, &end) )
1023
0
        return nullptr;
1024
1025
0
    return date + dateStr.IterOffsetInMBStr(end);
1026
0
}
1027
1028
const wchar_t* wxDateTime::ParseRfc822Date(const wchar_t* date)
1029
0
{
1030
0
    wxString::const_iterator end;
1031
0
    wxString dateStr(date);
1032
0
    if ( !ParseRfc822Date(dateStr, &end) )
1033
0
        return nullptr;
1034
1035
0
    return date + (end - dateStr.begin());
1036
0
}
1037
1038
bool
1039
wxDateTime::ParseFormat(const wxString& date,
1040
                        const wxString& format,
1041
                        const wxDateTime& dateDef,
1042
                        wxString::const_iterator *endParse)
1043
0
{
1044
0
    wxCHECK_MSG( !format.empty(), false, "format can't be empty" );
1045
0
    wxCHECK_MSG( endParse, false, "end iterator pointer must be specified" );
1046
1047
0
    unsigned long num;
1048
1049
    // what fields have we found?
1050
0
    bool haveWDay = false,
1051
0
         haveYDay = false,
1052
0
         haveDay = false,
1053
0
         haveMon = false,
1054
0
         haveYear = false,
1055
0
         haveHour = false,
1056
0
         haveMin = false,
1057
0
         haveSec = false,
1058
0
         haveMsec = false;
1059
1060
0
    bool hourIsIn12hFormat = false, // or in 24h one?
1061
0
         isPM = false;              // AM by default
1062
1063
0
    bool haveTimeZone = false;
1064
1065
    // and the value of the items we have (init them to get rid of warnings)
1066
0
    wxDateTime_t msec = 0,
1067
0
                 sec = 0,
1068
0
                 min = 0,
1069
0
                 hour = 0;
1070
0
    WeekDay wday = Inv_WeekDay;
1071
0
    wxDateTime_t yday = 0,
1072
0
                 mday = 0;
1073
0
    wxDateTime::Month mon = Inv_Month;
1074
0
    int year = 0;
1075
0
    long timeZone = 0;  // time zone in seconds as expected in Tm structure
1076
1077
0
    wxString::const_iterator input = date.begin();
1078
0
    const wxString::const_iterator end = date.end();
1079
0
    for ( wxString::const_iterator fmt = format.begin(); fmt != format.end(); ++fmt )
1080
0
    {
1081
0
        if ( *fmt != wxT('%') )
1082
0
        {
1083
0
            if ( wxIsspace(*fmt) )
1084
0
            {
1085
                // a white space in the format string matches 0 or more white
1086
                // spaces in the input
1087
0
                while ( input != end && wxIsspace(*input) )
1088
0
                {
1089
0
                    ++input;
1090
0
                }
1091
0
            }
1092
0
            else // !space
1093
0
            {
1094
                // any other character (not whitespace, not '%') must be
1095
                // matched by itself in the input
1096
0
                if ( input == end || *input++ != *fmt )
1097
0
                {
1098
                    // no match
1099
0
                    return false;
1100
0
                }
1101
0
            }
1102
1103
            // done with this format char
1104
0
            continue;
1105
0
        }
1106
1107
        // start of a format specification
1108
0
        ++fmt;
1109
1110
        // skip the optional character specifying the padding: this is a GNU
1111
        // extension which we need to support as this is used in the default
1112
        // date formats for some locales (but luckily this is simple to do)
1113
0
        if ( *fmt == '-' || *fmt == '_' || *fmt == '0' )
1114
0
            ++fmt;
1115
1116
        // parse the optional width
1117
0
        size_t width = 0;
1118
0
        while ( wxIsdigit(*fmt) )
1119
0
        {
1120
0
            width *= 10;
1121
0
            width += *fmt++ - '0';
1122
0
        }
1123
1124
        // the default widths for the various fields
1125
0
        if ( !width )
1126
0
        {
1127
0
            switch ( (*fmt).GetValue() )
1128
0
            {
1129
0
                case wxT('Y'):               // year has 4 digits
1130
0
                    width = 4;
1131
0
                    break;
1132
1133
0
                case wxT('j'):               // day of year has 3 digits
1134
0
                case wxT('l'):               // milliseconds have 3 digits
1135
0
                    width = 3;
1136
0
                    break;
1137
1138
0
                case wxT('w'):               // week day as number has only one
1139
0
                    width = 1;
1140
0
                    break;
1141
1142
0
                default:
1143
                    // default for all other fields
1144
0
                    width = 2;
1145
0
            }
1146
0
        }
1147
1148
        // then the format itself
1149
0
        switch ( (*fmt).GetValue() )
1150
0
        {
1151
0
            case wxT('a'):       // a weekday name
1152
0
            case wxT('A'):
1153
0
                {
1154
0
                    wday = GetWeekDayFromName
1155
0
                           (
1156
0
                            input, end,
1157
0
                            *fmt == 'a' ? Name_Abbr : Name_Full,
1158
0
                            DateLang_Local
1159
0
                           );
1160
0
                    if ( wday == Inv_WeekDay )
1161
0
                    {
1162
                        // no match
1163
0
                        return false;
1164
0
                    }
1165
0
                }
1166
0
                haveWDay = true;
1167
0
                break;
1168
1169
0
            case wxT('b'):       // a month name
1170
0
            case wxT('B'):
1171
0
                {
1172
0
                    mon = GetMonthFromName
1173
0
                          (
1174
0
                            input, end,
1175
0
                            *fmt == 'b' ? Name_Abbr : Name_Full,
1176
0
                            DateLang_Local
1177
0
                          );
1178
0
                    if ( mon == Inv_Month )
1179
0
                    {
1180
                        // no match
1181
0
                        return false;
1182
0
                    }
1183
0
                }
1184
0
                haveMon = true;
1185
0
                break;
1186
1187
0
            case wxT('c'):       // locale default date and time  representation
1188
0
                {
1189
0
                    wxDateTime dt;
1190
1191
0
#if wxUSE_INTL
1192
0
                    const wxString
1193
0
                        fmtDateTime = wxUILocale::GetCurrent().GetInfo(wxLOCALE_DATE_TIME_FMT);
1194
0
                    if ( !fmtDateTime.empty() )
1195
0
                        dt = ParseFormatAt(input, end, fmtDateTime);
1196
0
#endif // wxUSE_INTL
1197
0
                    if ( !dt.IsValid() )
1198
0
                    {
1199
                        // also try the format which corresponds to ctime()
1200
                        // output (i.e. the "C" locale default)
1201
0
                        dt = ParseFormatAt(input, end, wxS("%a %b %d %H:%M:%S %Y"));
1202
0
                    }
1203
1204
0
                    if ( !dt.IsValid() )
1205
0
                    {
1206
                        // and finally also the two generic date/time formats
1207
0
                        dt = ParseFormatAt(input, end, wxS("%x %X"), wxS("%X %x"));
1208
0
                    }
1209
1210
0
                    if ( !dt.IsValid() )
1211
0
                        return false;
1212
1213
0
                    const Tm tm = dt.GetTm();
1214
1215
0
                    hour = tm.hour;
1216
0
                    min = tm.min;
1217
0
                    sec = tm.sec;
1218
1219
0
                    year = tm.year;
1220
0
                    mon = tm.mon;
1221
0
                    mday = tm.mday;
1222
1223
0
                    haveDay = haveMon = haveYear =
1224
0
                    haveHour = haveMin = haveSec = true;
1225
0
                }
1226
0
                break;
1227
1228
0
            case wxT('d'):       // day of a month (01-31)
1229
0
            case 'e':           // day of a month (1-31) (GNU extension)
1230
0
                if ( !GetNumericToken(width, input, end, &num) ||
1231
0
                        (num > 31) || (num < 1) )
1232
0
                {
1233
                    // no match
1234
0
                    return false;
1235
0
                }
1236
1237
                // we can't check whether the day range is correct yet, will
1238
                // do it later - assume ok for now
1239
0
                haveDay = true;
1240
0
                mday = (wxDateTime_t)num;
1241
0
                break;
1242
1243
0
            case wxT('F'):       // ISO 8601 date
1244
0
                {
1245
0
                    wxDateTime dt = ParseFormatAt(input, end, wxS("%Y-%m-%d"));
1246
0
                    if ( !dt.IsValid() )
1247
0
                        return false;
1248
1249
0
                    const Tm tm = dt.GetTm();
1250
1251
0
                    year = tm.year;
1252
0
                    mon = tm.mon;
1253
0
                    mday = tm.mday;
1254
1255
0
                    haveDay = haveMon = haveYear = true;
1256
0
                }
1257
0
                break;
1258
1259
0
            case wxT('H'):       // hour in 24h format (00-23)
1260
0
                if ( !GetNumericToken(width, input, end, &num) || (num > 23) )
1261
0
                {
1262
                    // no match
1263
0
                    return false;
1264
0
                }
1265
1266
0
                haveHour = true;
1267
0
                hour = (wxDateTime_t)num;
1268
0
                break;
1269
1270
0
            case wxT('I'):       // hour in 12h format (01-12)
1271
0
                if ( !GetNumericToken(width, input, end, &num) ||
1272
0
                        !num || (num > 12) )
1273
0
                {
1274
                    // no match
1275
0
                    return false;
1276
0
                }
1277
1278
0
                haveHour = true;
1279
0
                hourIsIn12hFormat = true;
1280
0
                hour = (wxDateTime_t)(num % 12);        // 12 should be 0
1281
0
                break;
1282
1283
0
            case wxT('j'):       // day of the year
1284
0
                if ( !GetNumericToken(width, input, end, &num) ||
1285
0
                        !num || (num > 366) )
1286
0
                {
1287
                    // no match
1288
0
                    return false;
1289
0
                }
1290
1291
0
                haveYDay = true;
1292
0
                yday = (wxDateTime_t)num;
1293
0
                break;
1294
1295
0
            case wxT('l'):       // milliseconds (0-999)
1296
0
                if ( !GetNumericToken(width, input, end, &num) )
1297
0
                    return false;
1298
1299
0
                haveMsec = true;
1300
0
                msec = (wxDateTime_t)num;
1301
0
                break;
1302
1303
0
            case wxT('m'):       // month as a number (01-12)
1304
0
                if ( !GetNumericToken(width, input, end, &num) ||
1305
0
                        !num || (num > 12) )
1306
0
                {
1307
                    // no match
1308
0
                    return false;
1309
0
                }
1310
1311
0
                haveMon = true;
1312
0
                mon = (Month)(num - 1);
1313
0
                break;
1314
1315
0
            case wxT('M'):       // minute as a decimal number (00-59)
1316
0
                if ( !GetNumericToken(width, input, end, &num) ||
1317
0
                        (num > 59) )
1318
0
                {
1319
                    // no match
1320
0
                    return false;
1321
0
                }
1322
1323
0
                haveMin = true;
1324
0
                min = (wxDateTime_t)num;
1325
0
                break;
1326
1327
0
            case wxT('p'):       // AM or PM string
1328
0
                {
1329
0
                    wxString am, pm;
1330
0
                    GetAmPmStrings(&am, &pm);
1331
1332
                    // we can never match %p in locales which don't use AM/PM
1333
0
                    if ( am.empty() || pm.empty() )
1334
0
                        return false;
1335
1336
0
                    const size_t pos = input - date.begin();
1337
0
                    if ( date.compare(pos, pm.length(), pm) == 0 )
1338
0
                    {
1339
0
                        isPM = true;
1340
0
                        input += pm.length();
1341
0
                    }
1342
0
                    else if ( date.compare(pos, am.length(), am) == 0 )
1343
0
                    {
1344
0
                        input += am.length();
1345
0
                    }
1346
0
                    else // no match
1347
0
                    {
1348
0
                        return false;
1349
0
                    }
1350
0
                }
1351
0
                break;
1352
1353
0
            case wxT('r'):       // time as %I:%M:%S %p
1354
0
                {
1355
0
                    wxDateTime dt;
1356
0
                    if ( !dt.ParseFormat(wxString(input, end),
1357
0
                                         wxS("%I:%M:%S %p"), &input) )
1358
0
                        return false;
1359
1360
0
                    haveHour = haveMin = haveSec = true;
1361
1362
0
                    const Tm tm = dt.GetTm();
1363
0
                    hour = tm.hour;
1364
0
                    min = tm.min;
1365
0
                    sec = tm.sec;
1366
0
                }
1367
0
                break;
1368
1369
0
            case wxT('R'):       // time as %H:%M
1370
0
                {
1371
0
                    const wxDateTime
1372
0
                        dt = ParseFormatAt(input, end, wxS("%H:%M"));
1373
0
                    if ( !dt.IsValid() )
1374
0
                        return false;
1375
1376
0
                    haveHour =
1377
0
                    haveMin = true;
1378
1379
0
                    const Tm tm = dt.GetTm();
1380
0
                    hour = tm.hour;
1381
0
                    min = tm.min;
1382
0
                }
1383
0
                break;
1384
1385
0
            case wxT('S'):       // second as a decimal number (00-61)
1386
0
                if ( !GetNumericToken(width, input, end, &num) ||
1387
0
                        (num > 61) )
1388
0
                {
1389
                    // no match
1390
0
                    return false;
1391
0
                }
1392
1393
0
                haveSec = true;
1394
0
                sec = (wxDateTime_t)num;
1395
0
                break;
1396
1397
0
            case wxT('T'):       // time as %H:%M:%S
1398
0
                {
1399
0
                    const wxDateTime
1400
0
                        dt = ParseFormatAt(input, end, wxS("%H:%M:%S"));
1401
0
                    if ( !dt.IsValid() )
1402
0
                        return false;
1403
1404
0
                    haveHour =
1405
0
                    haveMin =
1406
0
                    haveSec = true;
1407
1408
0
                    const Tm tm = dt.GetTm();
1409
0
                    hour = tm.hour;
1410
0
                    min = tm.min;
1411
0
                    sec = tm.sec;
1412
0
                }
1413
0
                break;
1414
1415
0
            case wxT('w'):       // weekday as a number (0-6), Sunday = 0
1416
0
                if ( !GetNumericToken(width, input, end, &num) ||
1417
0
                        (wday > 6) )
1418
0
                {
1419
                    // no match
1420
0
                    return false;
1421
0
                }
1422
1423
0
                haveWDay = true;
1424
0
                wday = (WeekDay)num;
1425
0
                break;
1426
1427
0
            case wxT('x'):       // locale default date representation
1428
0
                {
1429
0
#if wxUSE_INTL
1430
0
                    wxString
1431
0
                        fmtDate = wxUILocale::GetCurrent().GetInfo(wxLOCALE_SHORT_DATE_FMT),
1432
0
                        fmtDateAlt = wxUILocale::GetCurrent().GetInfo(wxLOCALE_LONG_DATE_FMT);
1433
#else // !wxUSE_INTL
1434
                    wxString fmtDate, fmtDateAlt;
1435
#endif // wxUSE_INTL/!wxUSE_INTL
1436
0
                    if ( fmtDate.empty() )
1437
0
                    {
1438
0
                        if ( IsWestEuropeanCountry(GetCountry()) ||
1439
0
                             GetCountry() == Russia )
1440
0
                        {
1441
0
                            fmtDate = wxS("%d/%m/%Y");
1442
0
                            fmtDateAlt = wxS("%m/%d/%Y");
1443
0
                         }
1444
0
                        else // assume USA
1445
0
                        {
1446
0
                            fmtDate = wxS("%m/%d/%Y");
1447
0
                            fmtDateAlt = wxS("%d/%m/%Y");
1448
0
                        }
1449
0
                    }
1450
1451
0
                    wxDateTime
1452
0
                        dt = ParseFormatAt(input, end, fmtDate, fmtDateAlt);
1453
1454
0
                    if ( !dt.IsValid() )
1455
0
                    {
1456
                        // try with short years too
1457
0
                        fmtDate.Replace("%Y","%y");
1458
0
                        fmtDateAlt.Replace("%Y","%y");
1459
0
                        dt = ParseFormatAt(input, end, fmtDate, fmtDateAlt);
1460
1461
0
                        if ( !dt.IsValid() )
1462
0
                            return false;
1463
0
                    }
1464
1465
0
                    const Tm tm = dt.GetTm();
1466
1467
0
                    haveDay =
1468
0
                    haveMon =
1469
0
                    haveYear = true;
1470
1471
0
                    year = tm.year;
1472
0
                    mon = tm.mon;
1473
0
                    mday = tm.mday;
1474
0
                }
1475
1476
0
                break;
1477
1478
0
            case wxT('X'):       // locale default time representation
1479
0
                {
1480
0
#if wxUSE_INTL
1481
0
                    wxString fmtTime = wxUILocale::GetCurrent().GetInfo(wxLOCALE_TIME_FMT),
1482
0
                             fmtTimeAlt;
1483
#else // !wxUSE_INTL
1484
                    wxString fmtTime, fmtTimeAlt;
1485
#endif // wxUSE_INTL/!wxUSE_INTL
1486
0
                    if ( fmtTime.empty() )
1487
0
                    {
1488
                        // try to parse what follows as "%H:%M:%S" and, if this
1489
                        // fails, as "%I:%M:%S %p" - this should catch the most
1490
                        // common cases
1491
0
                        fmtTime = "%T";
1492
0
                        fmtTimeAlt = "%r";
1493
0
                    }
1494
1495
0
                    const wxDateTime
1496
0
                        dt = ParseFormatAt(input, end, fmtTime, fmtTimeAlt);
1497
0
                    if ( !dt.IsValid() )
1498
0
                        return false;
1499
1500
0
                    haveHour =
1501
0
                    haveMin =
1502
0
                    haveSec = true;
1503
1504
0
                    const Tm tm = dt.GetTm();
1505
0
                    hour = tm.hour;
1506
0
                    min = tm.min;
1507
0
                    sec = tm.sec;
1508
0
                }
1509
0
                break;
1510
1511
0
            case wxT('y'):       // year without century (00-99)
1512
0
                if ( !GetNumericToken(width, input, end, &num) ||
1513
0
                        (num > 99) )
1514
0
                {
1515
                    // no match
1516
0
                    return false;
1517
0
                }
1518
1519
0
                haveYear = true;
1520
1521
                // TODO should have an option for roll over date instead of
1522
                //      hard coding it here
1523
0
                year = (num > 30 ? 1900 : 2000) + (wxDateTime_t)num;
1524
0
                break;
1525
1526
0
            case wxT('Y'):       // year with century
1527
0
                if ( !GetNumericToken(width, input, end, &num) )
1528
0
                {
1529
                    // no match
1530
0
                    return false;
1531
0
                }
1532
1533
0
                haveYear = true;
1534
0
                year = (wxDateTime_t)num;
1535
0
                break;
1536
1537
0
            case wxT('z'):
1538
0
                {
1539
                    // check that we have something here at all
1540
0
                    if ( input == end )
1541
0
                        return false;
1542
1543
0
                    if ( *input == wxS('Z') )
1544
0
                    {
1545
                        // Time is in UTC.
1546
0
                        ++input;
1547
0
                        haveTimeZone = true;
1548
0
                        break;
1549
0
                    }
1550
1551
                    // Check if there's either a plus, hyphen-minus, or
1552
                    // minus sign.
1553
0
                    bool minusFound;
1554
0
                    if ( *input == wxS('+') )
1555
0
                        minusFound = false;
1556
0
                    else if
1557
0
                    (
1558
0
                        *input == wxS('-') ||
1559
0
                        *input == wxString::FromUTF8("−") // U+2212 MINUS SIGN
1560
0
                    )
1561
0
                        minusFound = true;
1562
0
                    else
1563
0
                        return false;   // no match
1564
1565
0
                    ++input;
1566
1567
                    // Here should follow exactly 2 digits for hours (HH).
1568
0
                    const size_t numRequiredDigits = 2;
1569
0
                    size_t numScannedDigits;
1570
1571
0
                    unsigned long hours;
1572
0
                    if ( !GetNumericToken(numRequiredDigits, input, end,
1573
0
                                          &hours, &numScannedDigits)
1574
0
                         || numScannedDigits != numRequiredDigits)
1575
0
                    {
1576
0
                        return false; // No match.
1577
0
                    }
1578
1579
                    // Optionally followed by a colon separator.
1580
0
                    bool mustHaveMinutes = false;
1581
0
                    if ( input != end && *input == wxS(':') )
1582
0
                    {
1583
0
                        mustHaveMinutes = true;
1584
0
                        ++input;
1585
0
                    }
1586
1587
                    // Optionally followed by exactly 2 digits for minutes (MM).
1588
0
                    unsigned long minutes = 0;
1589
0
                    if ( !GetNumericToken(numRequiredDigits, input, end,
1590
0
                                          &minutes, &numScannedDigits)
1591
0
                         || numScannedDigits != numRequiredDigits)
1592
0
                    {
1593
0
                        if (mustHaveMinutes || numScannedDigits)
1594
0
                        {
1595
                            // No match if we must have minutes, or digits
1596
                            // for minutes were specified but not exactly 2.
1597
0
                            return false;
1598
0
                        }
1599
0
                    }
1600
1601
                    /*
1602
                    Contemporary offset limits are -12:00 and +14:00.
1603
                    However historically offsets of over +/- 15 hours
1604
                    existed so be a bit more flexible. Info retrieved
1605
                    from Time Zone Database at
1606
                    https://www.iana.org/time-zones.
1607
                    */
1608
0
                    if ( hours > 15 || minutes > 59 )
1609
0
                        return false;   // bad format
1610
1611
0
                    timeZone = 3600*hours + 60*minutes;
1612
0
                    if ( minusFound )
1613
0
                        timeZone = -timeZone;
1614
1615
0
                    haveTimeZone = true;
1616
0
                }
1617
0
                break;
1618
1619
0
            case wxT('Z'):       // timezone name
1620
                // FIXME: currently we just ignore everything that looks like a
1621
                //        time zone here
1622
0
                GetAlphaToken(input, end);
1623
0
                break;
1624
1625
0
            case wxT('%'):       // a percent sign
1626
0
                if ( input == end || *input++ != wxT('%') )
1627
0
                {
1628
                    // no match
1629
0
                    return false;
1630
0
                }
1631
0
                break;
1632
1633
0
            case 0:             // the end of string
1634
0
                wxFAIL_MSG(wxT("unexpected format end"));
1635
1636
0
                wxFALLTHROUGH;
1637
1638
0
            default:            // not a known format spec
1639
0
                return false;
1640
0
        }
1641
0
    }
1642
1643
    // format matched, try to construct a date from what we have now
1644
0
    Tm tmDef;
1645
0
    if ( dateDef.IsValid() )
1646
0
    {
1647
        // take this date as default
1648
0
        tmDef = dateDef.GetTm();
1649
0
    }
1650
0
    else if ( IsValid() )
1651
0
    {
1652
        // if this date is valid, don't change it
1653
0
        tmDef = GetTm();
1654
0
    }
1655
0
    else
1656
0
    {
1657
        // no default and this date is invalid - fall back to Today()
1658
0
        tmDef = Today().GetTm();
1659
0
    }
1660
1661
0
    Tm tm = tmDef;
1662
1663
    // set the date
1664
0
    if ( haveMon )
1665
0
    {
1666
0
        tm.mon = mon;
1667
0
    }
1668
1669
0
    if ( haveYear )
1670
0
    {
1671
0
        tm.year = year;
1672
0
    }
1673
1674
    // TODO we don't check here that the values are consistent, if both year
1675
    //      day and month/day were found, we just ignore the year day and we
1676
    //      also always ignore the week day
1677
0
    if ( haveDay )
1678
0
    {
1679
0
        if ( mday > GetNumberOfDays(tm.mon, tm.year) )
1680
0
            return false;
1681
1682
0
        tm.mday = mday;
1683
0
    }
1684
0
    else if ( haveYDay )
1685
0
    {
1686
0
        if ( yday > GetNumberOfDays(tm.year) )
1687
0
            return false;
1688
1689
0
        Tm tm2 = wxDateTime(1, Jan, tm.year).SetToYearDay(yday).GetTm();
1690
1691
0
        tm.mon = tm2.mon;
1692
0
        tm.mday = tm2.mday;
1693
0
    }
1694
1695
    // deal with AM/PM
1696
0
    if ( haveHour && hourIsIn12hFormat && isPM )
1697
0
    {
1698
        // translate to 24hour format
1699
0
        hour += 12;
1700
0
    }
1701
    //else: either already in 24h format or no translation needed
1702
1703
    // set the time
1704
0
    if ( haveHour )
1705
0
    {
1706
0
        tm.hour = hour;
1707
0
    }
1708
1709
0
    if ( haveMin )
1710
0
    {
1711
0
        tm.min = min;
1712
0
    }
1713
1714
0
    if ( haveSec )
1715
0
    {
1716
0
        tm.sec = sec;
1717
0
    }
1718
1719
0
    if ( haveMsec )
1720
0
        tm.msec = msec;
1721
1722
0
    Set(tm);
1723
1724
0
    if ( haveTimeZone )
1725
0
        MakeFromTimezone(timeZone);
1726
1727
    // finally check that the week day is consistent -- if we had it
1728
0
    if ( haveWDay && GetWeekDay() != wday )
1729
0
        return false;
1730
1731
0
    *endParse = input;
1732
1733
0
    return true;
1734
0
}
1735
1736
const char*
1737
wxDateTime::ParseFormat(const char* date,
1738
                        const wxString& format,
1739
                        const wxDateTime& dateDef)
1740
0
{
1741
0
    wxString::const_iterator end;
1742
0
    wxString dateStr(date);
1743
0
    if ( !ParseFormat(dateStr, format, dateDef, &end) )
1744
0
        return nullptr;
1745
1746
0
    return date + dateStr.IterOffsetInMBStr(end);
1747
0
}
1748
1749
const wchar_t*
1750
wxDateTime::ParseFormat(const wchar_t* date,
1751
                        const wxString& format,
1752
                        const wxDateTime& dateDef)
1753
0
{
1754
0
    wxString::const_iterator end;
1755
0
    wxString dateStr(date);
1756
0
    if ( !ParseFormat(dateStr, format, dateDef, &end) )
1757
0
        return nullptr;
1758
1759
0
    return date + (end - dateStr.begin());
1760
0
}
1761
1762
bool
1763
wxDateTime::ParseDateTime(const wxString& date, wxString::const_iterator *end)
1764
0
{
1765
0
    wxCHECK_MSG( end, false, "end iterator pointer must be specified" );
1766
1767
0
    wxDateTime
1768
0
        dtDate,
1769
0
        dtTime;
1770
1771
0
    wxString::const_iterator
1772
0
        endTime,
1773
0
        endDate,
1774
0
        endBoth;
1775
1776
    // If we got a date in the beginning, see if there is a time specified
1777
    // after the date
1778
0
    if ( dtDate.ParseDate(date, &endDate) )
1779
0
    {
1780
        // Skip spaces, as the ParseTime() function fails on spaces
1781
0
        while ( endDate != date.end() && wxIsspace(*endDate) )
1782
0
            ++endDate;
1783
1784
        // Skip possible 'T' separator in front of time component
1785
0
        if ( endDate != date.end() && *endDate == 'T' )
1786
0
            ++endDate;
1787
1788
0
        const wxString timestr(endDate, date.end());
1789
0
        if ( !dtTime.ParseTime(timestr, &endTime) )
1790
0
            return false;
1791
1792
0
        endBoth = endDate + (endTime - timestr.begin());
1793
0
    }
1794
0
    else // no date in the beginning
1795
0
    {
1796
        // check if we have a time followed by a date
1797
0
        if ( !dtTime.ParseTime(date, &endTime) )
1798
0
            return false;
1799
1800
0
        while ( endTime != date.end() && wxIsspace(*endTime) )
1801
0
            ++endTime;
1802
1803
0
        const wxString datestr(endTime, date.end());
1804
0
        if ( !dtDate.ParseDate(datestr, &endDate) )
1805
0
            return false;
1806
1807
0
        endBoth = endTime + (endDate - datestr.begin());
1808
0
    }
1809
1810
0
    Set(dtDate.GetDay(), dtDate.GetMonth(), dtDate.GetYear(),
1811
0
        dtTime.GetHour(), dtTime.GetMinute(), dtTime.GetSecond(),
1812
0
        dtTime.GetMillisecond());
1813
1814
    // let's see if there is a time zone specified
1815
    // after date and/or time
1816
0
    if ( endBoth != date.end() && *endBoth == ' ' )
1817
0
    {
1818
0
        wxString::const_iterator tz = endBoth + 1;
1819
0
        if ( tz != date.end() &&
1820
0
             ParseRFC822TimeZone(&tz, date.end())
1821
0
           )
1822
0
        {
1823
0
            endBoth = tz;
1824
0
        }
1825
0
    }
1826
1827
0
    *end = endBoth;
1828
1829
0
    return true;
1830
0
}
1831
1832
const char* wxDateTime::ParseDateTime(const char* date)
1833
0
{
1834
0
    wxString::const_iterator end;
1835
0
    wxString dateStr(date);
1836
0
    if ( !ParseDateTime(dateStr, &end) )
1837
0
        return nullptr;
1838
1839
0
    return date + dateStr.IterOffsetInMBStr(end);
1840
0
}
1841
1842
const wchar_t* wxDateTime::ParseDateTime(const wchar_t* date)
1843
0
{
1844
0
    wxString::const_iterator end;
1845
0
    wxString dateStr(date);
1846
0
    if ( !ParseDateTime(dateStr, &end) )
1847
0
        return nullptr;
1848
1849
0
    return date + (end - dateStr.begin());
1850
0
}
1851
1852
bool
1853
wxDateTime::ParseDate(const wxString& date, wxString::const_iterator *end)
1854
0
{
1855
0
    wxCHECK_MSG( end, false, "end iterator pointer must be specified" );
1856
1857
    // this is a simplified version of ParseDateTime() which understands only
1858
    // "today" (for wxDate compatibility) and digits only otherwise (and not
1859
    // all esoteric constructions ParseDateTime() knows about)
1860
1861
0
    const wxString::const_iterator pBegin = date.begin();
1862
0
    const wxString::const_iterator pEnd = date.end();
1863
1864
0
    wxString::const_iterator p = pBegin;
1865
0
    while ( p != pEnd && wxIsspace(*p) )
1866
0
        ++p;
1867
1868
    // some special cases
1869
0
    static struct
1870
0
    {
1871
0
        const char *str;
1872
0
        int dayDiffFromToday;
1873
0
    } literalDates[] =
1874
0
    {
1875
0
        { wxTRANSLATE("today"),             0 },
1876
0
        { wxTRANSLATE("yesterday"),        -1 },
1877
0
        { wxTRANSLATE("tomorrow"),          1 },
1878
0
    };
1879
1880
0
    const size_t lenRest = pEnd - p;
1881
0
    for ( size_t n = 0; n < WXSIZEOF(literalDates); n++ )
1882
0
    {
1883
0
        const wxString dateStr = wxGetTranslation(literalDates[n].str);
1884
0
        size_t len = dateStr.length();
1885
1886
0
        if ( len > lenRest )
1887
0
            continue;
1888
1889
0
        const wxString::const_iterator pEndStr = p + len;
1890
0
        if ( wxString(p, pEndStr).CmpNoCase(dateStr) == 0 )
1891
0
        {
1892
            // nothing can follow this, so stop here
1893
1894
0
            p = pEndStr;
1895
1896
0
            int dayDiffFromToday = literalDates[n].dayDiffFromToday;
1897
0
            *this = Today();
1898
0
            if ( dayDiffFromToday )
1899
0
            {
1900
0
                *this += wxDateSpan::Days(dayDiffFromToday);
1901
0
            }
1902
1903
0
            *end = pEndStr;
1904
1905
0
            return true;
1906
0
        }
1907
0
    }
1908
1909
    // We try to guess what we have here: for each new (numeric) token, we
1910
    // determine if it can be a month, day or a year. Of course, there is an
1911
    // ambiguity as some numbers may be days as well as months, so we also
1912
    // have the ability to back track.
1913
1914
    // what do we have?
1915
0
    bool haveDay = false,       // the months day?
1916
0
         haveWDay = false,      // the day of week?
1917
0
         haveMon = false,       // the month?
1918
0
         haveYear = false;      // the year?
1919
1920
0
    bool monWasNumeric = false; // was month specified as a number?
1921
1922
    // and the value of the items we have (init them to get rid of warnings)
1923
0
    WeekDay wday = Inv_WeekDay;
1924
0
    wxDateTime_t day = 0;
1925
0
    wxDateTime::Month mon = Inv_Month;
1926
0
    int year = 0;
1927
1928
    // tokenize the string
1929
0
    while ( p != pEnd )
1930
0
    {
1931
        // modify copy of the iterator as we're not sure if the next token is
1932
        // still part of the date at all
1933
0
        wxString::const_iterator pCopy = p;
1934
1935
        // skip white space and date delimiters
1936
0
        while ( pCopy != pEnd && wxStrchr(".,/-\t\r\n ", *pCopy) )
1937
0
        {
1938
0
            ++pCopy;
1939
0
        }
1940
1941
0
        if ( pCopy == pEnd )
1942
0
            break;
1943
1944
        // we can have either alphabetic or numeric token, start by testing if
1945
        // it's the latter
1946
0
        unsigned long val;
1947
0
        if ( GetNumericToken(10 /* max length */, pCopy, pEnd, &val) )
1948
0
        {
1949
            // guess what this number is
1950
1951
0
            bool isDay = false,
1952
0
                 isMonth = false,
1953
0
                 isYear = false;
1954
1955
0
            if ( !haveMon && val > 0 && val <= 12 )
1956
0
            {
1957
                // assume it is month
1958
0
                isMonth = true;
1959
0
            }
1960
0
            else // not the month
1961
0
            {
1962
0
                if ( haveDay )
1963
0
                {
1964
                    // this can only be the year
1965
0
                    isYear = true;
1966
0
                }
1967
0
                else // may be either day or year
1968
0
                {
1969
                    // use a leap year if we don't have the year yet to allow
1970
                    // dates like 2/29/1976 which would be rejected otherwise
1971
0
                    wxDateTime_t max_days = (wxDateTime_t)(
1972
0
                        haveMon
1973
0
                        ? GetNumberOfDays(mon, haveYear ? year : 1976)
1974
0
                        : 31
1975
0
                    );
1976
1977
                    // can it be day?
1978
0
                    if ( (val == 0) || (val > (unsigned long)max_days) )
1979
0
                    {
1980
                        // no
1981
0
                        isYear = true;
1982
0
                    }
1983
0
                    else // yes, suppose it's the day
1984
0
                    {
1985
0
                        isDay = true;
1986
0
                    }
1987
0
                }
1988
0
            }
1989
1990
0
            if ( isYear )
1991
0
            {
1992
0
                if ( haveYear )
1993
0
                    break;
1994
1995
0
                haveYear = true;
1996
1997
0
                year = (wxDateTime_t)val;
1998
0
            }
1999
0
            else if ( isDay )
2000
0
            {
2001
0
                if ( haveDay )
2002
0
                    break;
2003
2004
0
                haveDay = true;
2005
2006
0
                day = (wxDateTime_t)val;
2007
0
            }
2008
0
            else if ( isMonth )
2009
0
            {
2010
0
                haveMon = true;
2011
0
                monWasNumeric = true;
2012
2013
0
                mon = (Month)(val - 1);
2014
0
            }
2015
0
        }
2016
0
        else // not a number
2017
0
        {
2018
            // be careful not to overwrite the current mon value
2019
0
            Month mon2 = GetMonthFromName
2020
0
                         (
2021
0
                            pCopy, pEnd,
2022
0
                            Name_Full | Name_Abbr,
2023
0
                            DateLang_Local | DateLang_English
2024
0
                         );
2025
0
            if ( mon2 != Inv_Month )
2026
0
            {
2027
                // it's a month
2028
0
                if ( haveMon )
2029
0
                {
2030
                    // but we already have a month - maybe we guessed wrong
2031
                    // when we had interpreted that numeric value as a month
2032
                    // and it was the day number instead?
2033
0
                    if ( haveDay || !monWasNumeric )
2034
0
                        break;
2035
2036
                    // assume we did and change our mind: reinterpret the month
2037
                    // value as a day (notice that there is no need to check
2038
                    // that it is valid as month values are always < 12, but
2039
                    // the days are counted from 1 unlike the months)
2040
0
                    day = (wxDateTime_t)(mon + 1);
2041
0
                    haveDay = true;
2042
0
                }
2043
2044
0
                mon = mon2;
2045
2046
0
                haveMon = true;
2047
0
            }
2048
0
            else // not a valid month name
2049
0
            {
2050
0
                WeekDay wday2 = GetWeekDayFromName
2051
0
                                (
2052
0
                                    pCopy, pEnd,
2053
0
                                    Name_Full | Name_Abbr,
2054
0
                                    DateLang_Local | DateLang_English
2055
0
                                );
2056
0
                if ( wday2 != Inv_WeekDay )
2057
0
                {
2058
                    // a week day
2059
0
                    if ( haveWDay )
2060
0
                        break;
2061
2062
0
                    wday = wday2;
2063
2064
0
                    haveWDay = true;
2065
0
                }
2066
0
                else // not a valid weekday name
2067
0
                {
2068
                    // try the ordinals
2069
0
                    static const char *const ordinals[] =
2070
0
                    {
2071
0
                        wxTRANSLATE("first"),
2072
0
                        wxTRANSLATE("second"),
2073
0
                        wxTRANSLATE("third"),
2074
0
                        wxTRANSLATE("fourth"),
2075
0
                        wxTRANSLATE("fifth"),
2076
0
                        wxTRANSLATE("sixth"),
2077
0
                        wxTRANSLATE("seventh"),
2078
0
                        wxTRANSLATE("eighth"),
2079
0
                        wxTRANSLATE("ninth"),
2080
0
                        wxTRANSLATE("tenth"),
2081
0
                        wxTRANSLATE("eleventh"),
2082
0
                        wxTRANSLATE("twelfth"),
2083
0
                        wxTRANSLATE("thirteenth"),
2084
0
                        wxTRANSLATE("fourteenth"),
2085
0
                        wxTRANSLATE("fifteenth"),
2086
0
                        wxTRANSLATE("sixteenth"),
2087
0
                        wxTRANSLATE("seventeenth"),
2088
0
                        wxTRANSLATE("eighteenth"),
2089
0
                        wxTRANSLATE("nineteenth"),
2090
0
                        wxTRANSLATE("twentieth"),
2091
                        // that's enough - otherwise we'd have problems with
2092
                        // composite (or not) ordinals
2093
0
                    };
2094
2095
0
                    size_t n;
2096
0
                    for ( n = 0; n < WXSIZEOF(ordinals); n++ )
2097
0
                    {
2098
0
                        const wxString ord = wxGetTranslation(ordinals[n]);
2099
0
                        const size_t len = ord.length();
2100
0
                        if ( date.compare(pCopy - pBegin, len, ord) == 0 )
2101
0
                        {
2102
0
                            pCopy += len;
2103
0
                            break;
2104
0
                        }
2105
0
                    }
2106
2107
0
                    if ( n == WXSIZEOF(ordinals) )
2108
0
                    {
2109
                        // stop here - something unknown
2110
0
                        break;
2111
0
                    }
2112
2113
                    // it's a day
2114
0
                    if ( haveDay )
2115
0
                    {
2116
                        // don't try anything here (as in case of numeric day
2117
                        // above) - the symbolic day spec should always
2118
                        // precede the month/year
2119
0
                        break;
2120
0
                    }
2121
2122
0
                    haveDay = true;
2123
2124
0
                    day = (wxDateTime_t)(n + 1);
2125
0
                }
2126
0
            }
2127
0
        }
2128
2129
        // advance iterator past a successfully parsed token
2130
0
        p = pCopy;
2131
0
    }
2132
2133
    // either no more tokens or the scan was stopped by something we couldn't
2134
    // parse - in any case, see if we can construct a date from what we have
2135
0
    if ( !haveDay && !haveWDay )
2136
0
        return false;
2137
2138
    // even if haveDay == true, we cannot construct a date from a day number
2139
    // alone, without at least a weekday or month
2140
0
    if ( !haveWDay && !haveMon )
2141
0
        return false;
2142
2143
0
    if ( haveWDay && (haveMon || haveYear || haveDay) &&
2144
0
         !(haveDay && haveMon && haveYear) )
2145
0
    {
2146
        // without adjectives (which we don't support here) the week day only
2147
        // makes sense completely separately or with the full date
2148
        // specification (what would "Wed 1999" mean?)
2149
0
        return false;
2150
0
    }
2151
2152
0
    if ( !haveWDay && haveYear && !(haveDay && haveMon) )
2153
0
    {
2154
        // may be we have month and day instead of day and year?
2155
0
        if ( haveDay && !haveMon )
2156
0
        {
2157
0
            if ( day <= 12  )
2158
0
            {
2159
                // exchange day and month
2160
0
                mon = (wxDateTime::Month)(day - 1);
2161
2162
                // we're in the current year then
2163
0
                if ( (year > 0) && (year <= (int)GetNumberOfDays(mon, Inv_Year)) )
2164
0
                {
2165
0
                    day = (wxDateTime_t)year;
2166
2167
0
                    haveMon = true;
2168
0
                    haveYear = false;
2169
0
                }
2170
                //else: no, can't exchange, leave haveMon == false
2171
0
            }
2172
0
        }
2173
2174
0
        if ( !haveMon )
2175
0
            return false;
2176
0
    }
2177
2178
0
    if ( !haveMon )
2179
0
    {
2180
0
        mon = GetCurrentMonth();
2181
0
    }
2182
2183
0
    if ( !haveYear )
2184
0
    {
2185
0
        year = GetCurrentYear();
2186
0
    }
2187
2188
0
    if ( haveDay )
2189
0
    {
2190
        // normally we check the day above but the check is optimistic in case
2191
        // we find the day before its month/year so we have to redo it now
2192
0
        if ( day > GetNumberOfDays(mon, year) )
2193
0
            return false;
2194
2195
0
        Set(day, mon, year);
2196
2197
0
        if ( haveWDay )
2198
0
        {
2199
            // check that it is really the same
2200
0
            if ( GetWeekDay() != wday )
2201
0
                return false;
2202
0
        }
2203
0
    }
2204
0
    else // haveWDay
2205
0
    {
2206
0
        *this = Today();
2207
2208
0
        SetToWeekDayInSameWeek(wday);
2209
0
    }
2210
2211
0
    *end = p;
2212
2213
0
    return true;
2214
0
}
2215
2216
const char* wxDateTime::ParseDate(const char* date)
2217
0
{
2218
0
    wxString::const_iterator end;
2219
0
    wxString dateStr(date);
2220
0
    if ( !ParseDate(dateStr, &end) )
2221
0
        return nullptr;
2222
2223
0
    return date + dateStr.IterOffsetInMBStr(end);
2224
0
}
2225
2226
const wchar_t* wxDateTime::ParseDate(const wchar_t* date)
2227
0
{
2228
0
    wxString::const_iterator end;
2229
0
    wxString dateStr(date);
2230
0
    if ( !ParseDate(dateStr, &end) )
2231
0
        return nullptr;
2232
2233
0
    return date + (end - dateStr.begin());
2234
0
}
2235
2236
bool
2237
wxDateTime::ParseTime(const wxString& time, wxString::const_iterator *end)
2238
0
{
2239
0
    wxCHECK_MSG( end, false, "end iterator pointer must be specified" );
2240
2241
    // first try some extra things
2242
0
    static const struct
2243
0
    {
2244
0
        const char *name;
2245
0
        wxDateTime_t hour;
2246
0
    } stdTimes[] =
2247
0
    {
2248
0
        { wxTRANSLATE("noon"),      12 },
2249
0
        { wxTRANSLATE("midnight"),  00 },
2250
        // anything else?
2251
0
    };
2252
2253
0
    for ( size_t n = 0; n < WXSIZEOF(stdTimes); n++ )
2254
0
    {
2255
0
        const wxString timeString = wxGetTranslation(stdTimes[n].name);
2256
0
        if ( timeString.CmpNoCase(wxString(time, timeString.length())) == 0 )
2257
0
        {
2258
0
            Set(stdTimes[n].hour, 0, 0);
2259
2260
0
            if ( end )
2261
0
                *end = time.begin() + timeString.length();
2262
2263
0
            return true;
2264
0
        }
2265
0
    }
2266
2267
    // try all time formats we may think about in the order from longest to
2268
    // shortest
2269
0
    static const char *const timeFormats[] =
2270
0
    {
2271
0
        "%I:%M:%S %p",  // 12hour with AM/PM
2272
0
        "%H:%M:%S",     // could be the same or 24 hour one so try it too
2273
0
        "%I:%M %p",     // 12hour with AM/PM but without seconds
2274
0
        "%H:%M",        // and a possibly 24 hour version without seconds
2275
0
        "%I %p",        // just hour with AM/AM
2276
0
        "%H",           // just hour in 24 hour version
2277
0
        "%X",           // possibly something from above or maybe something
2278
                        // completely different -- try it last
2279
2280
        // TODO: parse timezones
2281
0
    };
2282
2283
0
    for ( size_t nFmt = 0; nFmt < WXSIZEOF(timeFormats); nFmt++ )
2284
0
    {
2285
0
        if ( ParseFormat(time, timeFormats[nFmt], end) )
2286
0
            return true;
2287
0
    }
2288
2289
0
    return false;
2290
0
}
2291
2292
const char* wxDateTime::ParseTime(const char* date)
2293
0
{
2294
0
    wxString::const_iterator end;
2295
0
    wxString dateStr(date);
2296
0
    if ( !ParseTime(dateStr, &end) )
2297
0
        return nullptr;
2298
2299
0
    return date + dateStr.IterOffsetInMBStr(end);
2300
0
}
2301
2302
const wchar_t* wxDateTime::ParseTime(const wchar_t* date)
2303
0
{
2304
0
    wxString::const_iterator end;
2305
0
    wxString dateStr(date);
2306
0
    if ( !ParseTime(dateStr, &end) )
2307
0
        return nullptr;
2308
2309
0
    return date + (end - dateStr.begin());
2310
0
}
2311
2312
// ----------------------------------------------------------------------------
2313
// Workdays and holidays support
2314
// ----------------------------------------------------------------------------
2315
2316
bool wxDateTime::IsWorkDay(Country WXUNUSED(country)) const
2317
0
{
2318
0
    return !wxDateTimeHolidayAuthority::IsHoliday(*this);
2319
0
}
2320
2321
// ============================================================================
2322
// wxDateSpan
2323
// ============================================================================
2324
2325
wxDateSpan WXDLLIMPEXP_BASE operator*(int n, const wxDateSpan& ds)
2326
0
{
2327
0
    wxDateSpan ds1(ds);
2328
0
    return ds1.Multiply(n);
2329
0
}
2330
2331
// ============================================================================
2332
// wxTimeSpan
2333
// ============================================================================
2334
2335
wxTimeSpan WXDLLIMPEXP_BASE operator*(int n, const wxTimeSpan& ts)
2336
0
{
2337
0
    return wxTimeSpan(ts).Multiply(n);
2338
0
}
2339
2340
// this enum is only used in wxTimeSpan::Format() below but we can't declare
2341
// it locally to the method as it provokes an internal compiler error in egcs
2342
// 2.91.60 when building with -O2
2343
enum TimeSpanPart
2344
{
2345
    Part_Week,
2346
    Part_Day,
2347
    Part_Hour,
2348
    Part_Min,
2349
    Part_Sec,
2350
    Part_MSec
2351
};
2352
2353
// not all strftime(3) format specifiers make sense here because, for example,
2354
// a time span doesn't have a year nor a timezone
2355
//
2356
// Here are the ones which are supported (all of them are supported by strftime
2357
// as well):
2358
//  %H          hour in 24 hour format
2359
//  %M          minute (00 - 59)
2360
//  %S          second (00 - 59)
2361
//  %%          percent sign
2362
//
2363
// Also, for MFC CTimeSpan compatibility, we support
2364
//  %D          number of days
2365
//
2366
// And, to be better than MFC :-), we also have
2367
//  %E          number of wEeks
2368
//  %l          milliseconds (000 - 999)
2369
wxString wxTimeSpan::Format(const wxString& format) const
2370
0
{
2371
0
    wxString str;
2372
2373
    // we deal with only positive time spans here and just add the sign in
2374
    // front for the negative ones
2375
0
    if ( IsNegative() )
2376
0
    {
2377
0
        str = "-";
2378
0
        str << Negate().Format(format);
2379
0
        return str;
2380
0
    }
2381
2382
0
    wxCHECK_MSG( !format.empty(), str,
2383
0
                 wxT("null format in wxTimeSpan::Format") );
2384
2385
0
    str.Alloc(format.length());
2386
2387
    // Suppose we have wxTimeSpan ts(1 /* hour */, 2 /* min */, 3 /* sec */)
2388
    //
2389
    // Then, of course, ts.Format("%H:%M:%S") must return "01:02:03", but the
2390
    // question is what should ts.Format("%S") do? The code here returns "3273"
2391
    // in this case (i.e. the total number of seconds, not just seconds % 60)
2392
    // because, for me, this call means "give me entire time interval in
2393
    // seconds" and not "give me the seconds part of the time interval"
2394
    //
2395
    // If we agree that it should behave like this, it is clear that the
2396
    // interpretation of each format specifier depends on the presence of the
2397
    // other format specs in the string: if there was "%H" before "%M", we
2398
    // should use GetMinutes() % 60, otherwise just GetMinutes() &c
2399
2400
    // we remember the most important unit found so far
2401
0
    TimeSpanPart partBiggest = Part_MSec;
2402
2403
0
    for ( wxString::const_iterator pch = format.begin(); pch != format.end(); ++pch )
2404
0
    {
2405
0
        wxUniChar ch = *pch;
2406
2407
0
        if ( ch == wxT('%') )
2408
0
        {
2409
0
            if ( ++pch == format.end() )
2410
0
            {
2411
0
                wxFAIL_MSG( wxT("trailing '%' in format string") );
2412
0
                break;
2413
0
            }
2414
2415
            // the start of the format specification of the printf() below
2416
0
            wxString fmtPrefix(wxT('%'));
2417
2418
            // the number
2419
0
            long n;
2420
2421
            // the number of digits for the format string, 0 if unused
2422
0
            unsigned digits = 0;
2423
2424
0
            ch = *pch;    // get the format spec char
2425
0
            switch ( ch.GetValue() )
2426
0
            {
2427
0
                default:
2428
0
                    wxFAIL_MSG( wxT("invalid format character") );
2429
0
                    wxFALLTHROUGH;
2430
2431
0
                case wxT('%'):
2432
0
                    str += ch;
2433
2434
                    // skip the part below switch
2435
0
                    continue;
2436
2437
0
                case wxT('D'):
2438
0
                    n = GetDays();
2439
0
                    if ( partBiggest < Part_Day )
2440
0
                    {
2441
0
                        n %= DAYS_PER_WEEK;
2442
0
                    }
2443
0
                    else
2444
0
                    {
2445
0
                        partBiggest = Part_Day;
2446
0
                    }
2447
0
                    break;
2448
2449
0
                case wxT('E'):
2450
0
                    partBiggest = Part_Week;
2451
0
                    n = GetWeeks();
2452
0
                    break;
2453
2454
0
                case wxT('H'):
2455
0
                    n = GetHours();
2456
0
                    if ( partBiggest < Part_Hour )
2457
0
                    {
2458
0
                        n %= HOURS_PER_DAY;
2459
0
                    }
2460
0
                    else
2461
0
                    {
2462
0
                        partBiggest = Part_Hour;
2463
0
                    }
2464
2465
0
                    digits = 2;
2466
0
                    break;
2467
2468
0
                case wxT('l'):
2469
0
                    n = GetMilliseconds().ToLong();
2470
0
                    if ( partBiggest < Part_MSec )
2471
0
                    {
2472
0
                        n %= 1000;
2473
0
                    }
2474
                    //else: no need to reset partBiggest to Part_MSec, it is
2475
                    //      the least significant one anyhow
2476
2477
0
                    digits = 3;
2478
0
                    break;
2479
2480
0
                case wxT('M'):
2481
0
                    n = GetMinutes();
2482
0
                    if ( partBiggest < Part_Min )
2483
0
                    {
2484
0
                        n %= MIN_PER_HOUR;
2485
0
                    }
2486
0
                    else
2487
0
                    {
2488
0
                        partBiggest = Part_Min;
2489
0
                    }
2490
2491
0
                    digits = 2;
2492
0
                    break;
2493
2494
0
                case wxT('S'):
2495
0
                    n = GetSeconds().ToLong();
2496
0
                    if ( partBiggest < Part_Sec )
2497
0
                    {
2498
0
                        n %= SEC_PER_MIN;
2499
0
                    }
2500
0
                    else
2501
0
                    {
2502
0
                        partBiggest = Part_Sec;
2503
0
                    }
2504
2505
0
                    digits = 2;
2506
0
                    break;
2507
0
            }
2508
2509
0
            if ( digits )
2510
0
            {
2511
0
                fmtPrefix << wxT("0") << digits;
2512
0
            }
2513
2514
0
            str += wxString::Format(fmtPrefix + wxT("ld"), n);
2515
0
        }
2516
0
        else
2517
0
        {
2518
            // normal character, just copy
2519
0
            str += ch;
2520
0
        }
2521
0
    }
2522
2523
0
    return str;
2524
0
}
2525
2526
#endif // wxUSE_DATETIME