source: ntrip/trunk/BNC/qwt/qwt_date.cpp@ 10006

Last change on this file since 10006 was 9383, checked in by stoecker, 4 years ago

update to qwt verion 6.1.1 to fix build with newer Qt5

File size: 18.6 KB
Line 
1/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
2 * Qwt Widget Library
3 * Copyright (C) 1997 Josef Wilgen
4 * Copyright (C) 2002 Uwe Rathmann
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the Qwt License, Version 1.0
8 *****************************************************************************/
9
10#include "qwt_date.h"
11#include <qdebug.h>
12#include <qlocale.h>
13#include <math.h>
14#include <limits>
15#include <limits.h>
16
17#if QT_VERSION >= 0x050000
18
19typedef qint64 QwtJulianDay;
20static const QwtJulianDay minJulianDayD = Q_INT64_C( -784350574879 );
21static const QwtJulianDay maxJulianDayD = Q_INT64_C( 784354017364 );
22
23#else
24
25// QDate stores the Julian day as unsigned int, but
26// but it is QDate::fromJulianDay( int ). That's why
27// we have the range [ 1, INT_MAX ]
28typedef int QwtJulianDay;
29static const QwtJulianDay minJulianDayD = 1;
30static const QwtJulianDay maxJulianDayD = std::numeric_limits<int>::max();
31
32#endif
33
34static QString qwtExpandedFormat( const QString & format,
35 const QDateTime &dateTime, QwtDate::Week0Type week0Type )
36{
37 const int week = QwtDate::weekNumber( dateTime.date(), week0Type );
38
39 QString weekNo;
40 weekNo.setNum( week );
41
42 QString weekNoWW;
43 if ( weekNo.length() == 1 )
44 weekNoWW += "0";
45
46 weekNoWW += weekNo;
47
48 QString fmt = format;
49 fmt.replace( "ww", weekNoWW );
50 fmt.replace( "w", weekNo );
51
52 if ( week == 1 && dateTime.date().month() != 1 )
53 {
54 // in case of week 1, we might need to increment the year
55
56 static QString s_yyyy = "yyyy";
57 static QString s_yy = "yy";
58
59 // week 1 might start in the previous year
60
61 bool doReplaceYear = fmt.contains( s_yy );
62
63 if ( doReplaceYear )
64 {
65 if ( fmt.contains( 'M' ) )
66 {
67 // in case of also having 'M' we have a conflict about
68 // which year to show
69
70 doReplaceYear = false;
71 }
72 else
73 {
74 // in case of also having 'd' or 'dd' we have a conflict about
75 // which year to show
76
77 int numD = 0;
78
79 for ( int i = 0; i < fmt.size(); i++ )
80 {
81 if ( fmt[i] == 'd' )
82 {
83 numD++;
84 }
85 else
86 {
87 if ( numD > 0 && numD <= 2 )
88 break;
89
90 numD = 0;
91 }
92 }
93
94 if ( numD > 0 && numD <= 2 )
95 doReplaceYear = false;
96 }
97 }
98
99 if ( doReplaceYear )
100 {
101 const QDate dt( dateTime.date().year() + 1, 1, 1 );
102
103 if ( fmt.contains( s_yyyy ) )
104 {
105 fmt.replace( s_yyyy, dt.toString( s_yyyy ) );
106 }
107 else
108 {
109 fmt.replace( s_yy, dt.toString( s_yyyy ) );
110 }
111 }
112 }
113
114 return fmt;
115}
116
117static inline Qt::DayOfWeek qwtFirstDayOfWeek()
118{
119#if QT_VERSION >= 0x040800
120 return QLocale().firstDayOfWeek();
121#else
122
123 switch( QLocale().country() )
124 {
125 case QLocale::Maldives:
126 return Qt::Friday;
127
128 case QLocale::Afghanistan:
129 case QLocale::Algeria:
130 case QLocale::Bahrain:
131 case QLocale::Djibouti:
132 case QLocale::Egypt:
133 case QLocale::Eritrea:
134 case QLocale::Ethiopia:
135 case QLocale::Iran:
136 case QLocale::Iraq:
137 case QLocale::Jordan:
138 case QLocale::Kenya:
139 case QLocale::Kuwait:
140 case QLocale::LibyanArabJamahiriya:
141 case QLocale::Morocco:
142 case QLocale::Oman:
143 case QLocale::Qatar:
144 case QLocale::SaudiArabia:
145 case QLocale::Somalia:
146 case QLocale::Sudan:
147 case QLocale::Tunisia:
148 case QLocale::Yemen:
149 return Qt::Saturday;
150
151 case QLocale::AmericanSamoa:
152 case QLocale::Argentina:
153 case QLocale::Azerbaijan:
154 case QLocale::Botswana:
155 case QLocale::Canada:
156 case QLocale::China:
157 case QLocale::FaroeIslands:
158 case QLocale::Georgia:
159 case QLocale::Greenland:
160 case QLocale::Guam:
161 case QLocale::HongKong:
162 case QLocale::Iceland:
163 case QLocale::India:
164 case QLocale::Ireland:
165 case QLocale::Israel:
166 case QLocale::Jamaica:
167 case QLocale::Japan:
168 case QLocale::Kyrgyzstan:
169 case QLocale::Lao:
170 case QLocale::Malta:
171 case QLocale::MarshallIslands:
172 case QLocale::Macau:
173 case QLocale::Mongolia:
174 case QLocale::NewZealand:
175 case QLocale::NorthernMarianaIslands:
176 case QLocale::Pakistan:
177 case QLocale::Philippines:
178 case QLocale::RepublicOfKorea:
179 case QLocale::Singapore:
180 case QLocale::SyrianArabRepublic:
181 case QLocale::Taiwan:
182 case QLocale::Thailand:
183 case QLocale::TrinidadAndTobago:
184 case QLocale::UnitedStates:
185 case QLocale::UnitedStatesMinorOutlyingIslands:
186 case QLocale::USVirginIslands:
187 case QLocale::Uzbekistan:
188 case QLocale::Zimbabwe:
189 return Qt::Sunday;
190
191 default:
192 return Qt::Monday;
193 }
194#endif
195}
196
197static inline void qwtFloorTime(
198 QwtDate::IntervalType intervalType, QDateTime &dt )
199{
200 // when dt is inside the special hour where DST is ending
201 // an hour is no unique. Therefore we have to
202 // use UTC time.
203
204 const Qt::TimeSpec timeSpec = dt.timeSpec();
205
206 if ( timeSpec == Qt::LocalTime )
207 dt = dt.toTimeSpec( Qt::UTC );
208
209 const QTime t = dt.time();
210 switch( intervalType )
211 {
212 case QwtDate::Second:
213 {
214 dt.setTime( QTime( t.hour(), t.minute(), t.second() ) );
215 break;
216 }
217 case QwtDate::Minute:
218 {
219 dt.setTime( QTime( t.hour(), t.minute(), 0 ) );
220 break;
221 }
222 case QwtDate::Hour:
223 {
224 dt.setTime( QTime( t.hour(), 0, 0 ) );
225 break;
226 }
227 default:
228 break;
229 }
230
231 if ( timeSpec == Qt::LocalTime )
232 dt = dt.toTimeSpec( Qt::LocalTime );
233}
234
235static inline QDateTime qwtToTimeSpec(
236 const QDateTime &dt, Qt::TimeSpec spec )
237{
238 if ( dt.timeSpec() == spec )
239 return dt;
240
241 const qint64 jd = dt.date().toJulianDay();
242 if ( jd < 0 || jd >= INT_MAX )
243 {
244 // the conversion between local time and UTC
245 // is internally limited. To avoid
246 // overflows we simply ignore the difference
247 // for those dates
248
249 QDateTime dt2 = dt;
250 dt2.setTimeSpec( spec );
251 return dt2;
252 }
253
254 return dt.toTimeSpec( spec );
255}
256
257#if 0
258
259static inline double qwtToJulianDay( int year, int month, int day )
260{
261 // code from QDate but using doubles to avoid overflows
262 // for large values
263
264 const int m1 = ( month - 14 ) / 12;
265 const int m2 = ( 367 * ( month - 2 - 12 * m1 ) ) / 12;
266 const double y1 = ::floor( ( 4900.0 + year + m1 ) / 100 );
267
268 return ::floor( ( 1461.0 * ( year + 4800 + m1 ) ) / 4 ) + m2
269 - ::floor( ( 3 * y1 ) / 4 ) + day - 32075;
270}
271
272static inline qint64 qwtFloorDiv64( qint64 a, int b )
273{
274 if ( a < 0 )
275 a -= b - 1;
276
277 return a / b;
278}
279
280static inline qint64 qwtFloorDiv( int a, int b )
281{
282 if ( a < 0 )
283 a -= b - 1;
284
285 return a / b;
286}
287
288#endif
289
290static inline QDate qwtToDate( int year, int month = 1, int day = 1 )
291{
292#if QT_VERSION >= 0x050000
293 return QDate( year, month, day );
294#else
295 if ( year > 100000 )
296 {
297 // code from QDate but using doubles to avoid overflows
298 // for large values
299
300 const int m1 = ( month - 14 ) / 12;
301 const int m2 = ( 367 * ( month - 2 - 12 * m1 ) ) / 12;
302 const double y1 = ::floor( ( 4900.0 + year + m1 ) / 100 );
303
304 const double jd = ::floor( ( 1461.0 * ( year + 4800 + m1 ) ) / 4 ) + m2
305 - ::floor( ( 3 * y1 ) / 4 ) + day - 32075;
306
307 if ( jd > maxJulianDayD )
308 {
309 qWarning() << "qwtToDate: overflow";
310 return QDate();
311 }
312
313 return QDate::fromJulianDay( static_cast<QwtJulianDay>( jd ) );
314 }
315 else
316 {
317 return QDate( year, month, day );
318 }
319#endif
320}
321
322/*!
323 Translate from double to QDateTime
324
325 \param value Number of milliseconds since the epoch,
326 1970-01-01T00:00:00 UTC
327 \param timeSpec Time specification
328 \return Datetime value
329
330 \sa toDouble(), QDateTime::setMSecsSinceEpoch()
331 \note The return datetime for Qt::OffsetFromUTC will be Qt::UTC
332 */
333QDateTime QwtDate::toDateTime( double value, Qt::TimeSpec timeSpec )
334{
335 const int msecsPerDay = 86400000;
336
337 const double days = static_cast<qint64>( ::floor( value / msecsPerDay ) );
338
339 const double jd = QwtDate::JulianDayForEpoch + days;
340 if ( ( jd > maxJulianDayD ) || ( jd < minJulianDayD ) )
341 {
342 qWarning() << "QwtDate::toDateTime: overflow";
343 return QDateTime();
344 }
345
346 const QDate d = QDate::fromJulianDay( static_cast<QwtJulianDay>( jd ) );
347
348 const int msecs = static_cast<int>( value - days * msecsPerDay );
349
350 static const QTime timeNull( 0, 0, 0, 0 );
351
352 QDateTime dt( d, timeNull.addMSecs( msecs ), Qt::UTC );
353
354 if ( timeSpec == Qt::LocalTime )
355 dt = qwtToTimeSpec( dt, timeSpec );
356
357 return dt;
358}
359
360/*!
361 Translate from QDateTime to double
362
363 \param dateTime Datetime value
364 \return Number of milliseconds since 1970-01-01T00:00:00 UTC has passed.
365
366 \sa toDateTime(), QDateTime::toMSecsSinceEpoch()
367 \warning For values very far below or above 1970-01-01 UTC rounding errors
368 will happen due to the limited significance of a double.
369 */
370double QwtDate::toDouble( const QDateTime &dateTime )
371{
372 const int msecsPerDay = 86400000;
373
374 const QDateTime dt = qwtToTimeSpec( dateTime, Qt::UTC );
375
376 const double days = dt.date().toJulianDay() - QwtDate::JulianDayForEpoch;
377
378 const QTime time = dt.time();
379 const double secs = 3600.0 * time.hour() +
380 60.0 * time.minute() + time.second();
381
382 return days * msecsPerDay + time.msec() + 1000.0 * secs;
383}
384
385/*!
386 Ceil a datetime according the interval type
387
388 \param dateTime Datetime value
389 \param intervalType Interval type, how to ceil.
390 F.e. when intervalType = QwtDate::Months, the result
391 will be ceiled to the next beginning of a month
392 \return Ceiled datetime
393 \sa floor()
394 */
395QDateTime QwtDate::ceil( const QDateTime &dateTime, IntervalType intervalType )
396{
397 if ( dateTime.date() >= QwtDate::maxDate() )
398 return dateTime;
399
400 QDateTime dt = dateTime;
401
402 switch ( intervalType )
403 {
404 case QwtDate::Millisecond:
405 {
406 break;
407 }
408 case QwtDate::Second:
409 {
410 qwtFloorTime( QwtDate::Second, dt );
411 if ( dt < dateTime )
412 dt = dt.addSecs( 1 );
413
414 break;
415 }
416 case QwtDate::Minute:
417 {
418 qwtFloorTime( QwtDate::Minute, dt );
419 if ( dt < dateTime )
420 dt = dt.addSecs( 60 );
421
422 break;
423 }
424 case QwtDate::Hour:
425 {
426 qwtFloorTime( QwtDate::Hour, dt );
427 if ( dt < dateTime )
428 dt = dt.addSecs( 3600 );
429
430 break;
431 }
432 case QwtDate::Day:
433 {
434 dt.setTime( QTime( 0, 0 ) );
435 if ( dt < dateTime )
436 dt = dt.addDays( 1 );
437
438 break;
439 }
440 case QwtDate::Week:
441 {
442 dt.setTime( QTime( 0, 0 ) );
443 if ( dt < dateTime )
444 dt = dt.addDays( 1 );
445
446 int days = qwtFirstDayOfWeek() - dt.date().dayOfWeek();
447 if ( days < 0 )
448 days += 7;
449
450 dt = dt.addDays( days );
451
452 break;
453 }
454 case QwtDate::Month:
455 {
456 dt.setTime( QTime( 0, 0 ) );
457 dt.setDate( qwtToDate( dateTime.date().year(),
458 dateTime.date().month() ) );
459
460 if ( dt < dateTime )
461 dt = dt.addMonths( 1 );
462
463 break;
464 }
465 case QwtDate::Year:
466 {
467 dt.setTime( QTime( 0, 0 ) );
468
469 const QDate d = dateTime.date();
470
471 int year = d.year();
472 if ( d.month() > 1 || d.day() > 1 || !dateTime.time().isNull() )
473 year++;
474
475 if ( year == 0 )
476 year++; // there is no year 0
477
478 dt.setDate( qwtToDate( year ) );
479 break;
480 }
481 }
482
483 return dt;
484}
485
486/*!
487 Floor a datetime according the interval type
488
489 \param dateTime Datetime value
490 \param intervalType Interval type, how to ceil.
491 F.e. when intervalType = QwtDate::Months,
492 the result will be ceiled to the next
493 beginning of a month
494 \return Floored datetime
495 \sa floor()
496 */
497QDateTime QwtDate::floor( const QDateTime &dateTime,
498 IntervalType intervalType )
499{
500 if ( dateTime.date() <= QwtDate::minDate() )
501 return dateTime;
502
503 QDateTime dt = dateTime;
504
505 switch ( intervalType )
506 {
507 case QwtDate::Millisecond:
508 {
509 break;
510 }
511 case QwtDate::Second:
512 case QwtDate::Minute:
513 case QwtDate::Hour:
514 {
515 qwtFloorTime( intervalType, dt );
516 break;
517 }
518 case QwtDate::Day:
519 {
520 dt.setTime( QTime( 0, 0 ) );
521 break;
522 }
523 case QwtDate::Week:
524 {
525 dt.setTime( QTime( 0, 0 ) );
526
527 int days = dt.date().dayOfWeek() - qwtFirstDayOfWeek();
528 if ( days < 0 )
529 days += 7;
530
531 dt = dt.addDays( -days );
532
533 break;
534 }
535 case QwtDate::Month:
536 {
537 dt.setTime( QTime( 0, 0 ) );
538
539 const QDate date = qwtToDate( dt.date().year(),
540 dt.date().month() );
541 dt.setDate( date );
542
543 break;
544 }
545 case QwtDate::Year:
546 {
547 dt.setTime( QTime( 0, 0 ) );
548
549 const QDate date = qwtToDate( dt.date().year() );
550 dt.setDate( date );
551
552 break;
553 }
554 }
555
556 return dt;
557}
558
559/*!
560 Minimum for the supported date range
561
562 The range of valid dates depends on how QDate stores the
563 Julian day internally.
564
565 - For Qt4 it is "Tue Jan 2 -4713"
566 - For Qt5 it is "Thu Jan 1 -2147483648"
567
568 \return minimum of the date range
569 \sa maxDate()
570 */
571QDate QwtDate::minDate()
572{
573 static QDate date;
574 if ( !date.isValid() )
575 date = QDate::fromJulianDay( minJulianDayD );
576
577 return date;
578}
579
580/*!
581 Maximum for the supported date range
582
583 The range of valid dates depends on how QDate stores the
584 Julian day internally.
585
586 - For Qt4 it is "Tue Jun 3 5874898"
587 - For Qt5 it is "Tue Dec 31 2147483647"
588
589 \return maximum of the date range
590 \sa minDate()
591 \note The maximum differs between Qt4 and Qt5
592 */
593QDate QwtDate::maxDate()
594{
595 static QDate date;
596 if ( !date.isValid() )
597 date = QDate::fromJulianDay( maxJulianDayD );
598
599 return date;
600}
601
602/*!
603 \brief Date of the first day of the first week for a year
604
605 The first day of a week depends on the current locale
606 ( QLocale::firstDayOfWeek() ).
607
608 \param year Year
609 \param type Option how to identify the first week
610 \return First day of week 0
611
612 \sa QLocale::firstDayOfWeek(), weekNumber()
613 */
614QDate QwtDate::dateOfWeek0( int year, Week0Type type )
615{
616 const Qt::DayOfWeek firstDayOfWeek = qwtFirstDayOfWeek();
617
618 QDate dt0( year, 1, 1 );
619
620 // floor to the first day of the week
621 int days = dt0.dayOfWeek() - firstDayOfWeek;
622 if ( days < 0 )
623 days += 7;
624
625 dt0 = dt0.addDays( -days );
626
627 if ( type == QwtDate::FirstThursday )
628 {
629 // according to ISO 8601 the first week is defined
630 // by the first thursday.
631
632 int d = Qt::Thursday - firstDayOfWeek;
633 if ( d < 0 )
634 d += 7;
635
636 if ( dt0.addDays( d ).year() < year )
637 dt0 = dt0.addDays( 7 );
638 }
639
640 return dt0;
641}
642
643/*!
644 Find the week number of a date
645
646 - QwtDate::FirstThursday\n
647 Corresponding to ISO 8601 ( see QDate::weekNumber() ).
648
649 - QwtDate::FirstDay\n
650 Number of weeks that have begun since dateOfWeek0().
651
652 \param date Date
653 \param type Option how to identify the first week
654
655 \return Week number, starting with 1
656 */
657int QwtDate::weekNumber( const QDate &date, Week0Type type )
658{
659 int weekNo;
660
661 if ( type == QwtDate::FirstDay )
662 {
663 QDate day0;
664
665 if ( date.month() == 12 && date.day() >= 24 )
666 {
667 // week 1 usually starts in the previous years.
668 // and we have to check if we are already there
669
670 day0 = dateOfWeek0( date.year() + 1, type );
671 if ( day0.daysTo( date ) < 0 )
672 day0 = dateOfWeek0( date.year(), type );
673 }
674 else
675 {
676 day0 = dateOfWeek0( date.year(), type );
677 }
678
679 weekNo = day0.daysTo( date ) / 7 + 1;
680 }
681 else
682 {
683 weekNo = date.weekNumber();
684 }
685
686 return weekNo;
687}
688
689/*!
690 Offset in seconds from Coordinated Universal Time
691
692 The offset depends on the time specification of dateTime:
693
694 - Qt::UTC
695 0, dateTime has no offset
696 - Qt::OffsetFromUTC
697 returns dateTime.utcOffset()
698 - Qt::LocalTime:
699 number of seconds from the UTC
700
701 For Qt::LocalTime the offset depends on the timezone and
702 daylight savings.
703
704 \param dateTime Datetime value
705 \return Offset in seconds
706 */
707int QwtDate::utcOffset( const QDateTime &dateTime )
708{
709 int seconds = 0;
710
711 switch( dateTime.timeSpec() )
712 {
713 case Qt::UTC:
714 {
715 break;
716 }
717 case Qt::OffsetFromUTC:
718 {
719 seconds = dateTime.utcOffset();
720 break;
721 }
722 default:
723 {
724 const QDateTime dt1( dateTime.date(), dateTime.time(), Qt::UTC );
725 seconds = dateTime.secsTo( dt1 );
726 }
727 }
728
729 return seconds;
730}
731
732/*!
733 Translate a datetime into a string
734
735 Beside the format expressions documented in QDateTime::toString()
736 the following expressions are supported:
737
738 - w\n
739 week number: ( 1 - 53 )
740 - ww\n
741 week number with a leading zero ( 01 - 53 )
742
743 As week 1 usually starts in the previous year a special rule
744 is applied for formats, where the year is expected to match the
745 week number - even if the date belongs to the previous year.
746
747 \param dateTime Datetime value
748 \param format Format string
749 \param week0Type Specification of week 0
750
751 \return Datetime string
752 \sa QDateTime::toString(), weekNumber(), QwtDateScaleDraw
753 */
754QString QwtDate::toString( const QDateTime &dateTime,
755 const QString & format, Week0Type week0Type )
756{
757 QString fmt = format;
758 if ( fmt.contains( 'w' ) )
759 {
760 fmt = qwtExpandedFormat( fmt, dateTime, week0Type );
761 }
762
763 return dateTime.toString( fmt );
764}
Note: See TracBrowser for help on using the repository browser.