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

Last change on this file since 8486 was 8127, checked in by stoecker, 8 years ago

update qwt and qwtpolar, many QT5 fixes (unfinished)

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
257static inline double qwtToJulianDay( int year, int month, int day )
258{
259 // code from QDate but using doubles to avoid overflows
260 // for large values
261
262 const int m1 = ( month - 14 ) / 12;
263 const int m2 = ( 367 * ( month - 2 - 12 * m1 ) ) / 12;
264 const double y1 = ::floor( ( 4900.0 + year + m1 ) / 100 );
265
266 return ::floor( ( 1461.0 * ( year + 4800 + m1 ) ) / 4 ) + m2
267 - ::floor( ( 3 * y1 ) / 4 ) + day - 32075;
268}
269
270static inline qint64 qwtFloorDiv64( qint64 a, int b )
271{
272 if ( a < 0 )
273 a -= b - 1;
274
275 return a / b;
276}
277
278static inline qint64 qwtFloorDiv( int a, int b )
279{
280 if ( a < 0 )
281 a -= b - 1;
282
283 return a / b;
284}
285
286static inline QDate qwtToDate( int year, int month = 1, int day = 1 )
287{
288#if QT_VERSION >= 0x050000
289 return QDate( year, month, day );
290#else
291 if ( year > 100000 )
292 {
293 // code from QDate but using doubles to avoid overflows
294 // for large values
295
296 const int m1 = ( month - 14 ) / 12;
297 const int m2 = ( 367 * ( month - 2 - 12 * m1 ) ) / 12;
298 const double y1 = ::floor( ( 4900.0 + year + m1 ) / 100 );
299
300 const double jd = ::floor( ( 1461.0 * ( year + 4800 + m1 ) ) / 4 ) + m2
301 - ::floor( ( 3 * y1 ) / 4 ) + day - 32075;
302
303 if ( jd > maxJulianDayD )
304 {
305 qWarning() << "qwtToDate: overflow";
306 return QDate();
307 }
308
309 return QDate::fromJulianDay( static_cast<QwtJulianDay>( jd ) );
310 }
311 else
312 {
313 return QDate( year, month, day );
314 }
315#endif
316}
317
318/*!
319 Translate from double to QDateTime
320
321 \param value Number of milliseconds since the epoch,
322 1970-01-01T00:00:00 UTC
323 \param timeSpec Time specification
324 \return Datetime value
325
326 \sa toDouble(), QDateTime::setMSecsSinceEpoch()
327 \note The return datetime for Qt::OffsetFromUTC will be Qt::UTC
328 */
329QDateTime QwtDate::toDateTime( double value, Qt::TimeSpec timeSpec )
330{
331 const int msecsPerDay = 86400000;
332
333 const double days = static_cast<qint64>( ::floor( value / msecsPerDay ) );
334
335 const double jd = QwtDate::JulianDayForEpoch + days;
336 if ( ( jd > maxJulianDayD ) || ( jd < minJulianDayD ) )
337 {
338 qWarning() << "QwtDate::toDateTime: overflow";
339 return QDateTime();
340 }
341
342 const QDate d = QDate::fromJulianDay( static_cast<QwtJulianDay>( jd ) );
343
344 const int msecs = static_cast<int>( value - days * msecsPerDay );
345
346 static const QTime timeNull( 0, 0, 0, 0 );
347
348 QDateTime dt( d, timeNull.addMSecs( msecs ), Qt::UTC );
349
350 if ( timeSpec == Qt::LocalTime )
351 dt = qwtToTimeSpec( dt, timeSpec );
352
353 return dt;
354}
355
356/*!
357 Translate from QDateTime to double
358
359 \param dateTime Datetime value
360 \return Number of milliseconds since 1970-01-01T00:00:00 UTC has passed.
361
362 \sa toDateTime(), QDateTime::toMSecsSinceEpoch()
363 \warning For values very far below or above 1970-01-01 UTC rounding errors
364 will happen due to the limited significance of a double.
365 */
366double QwtDate::toDouble( const QDateTime &dateTime )
367{
368 const int msecsPerDay = 86400000;
369
370 const QDateTime dt = qwtToTimeSpec( dateTime, Qt::UTC );
371
372 const double days = dt.date().toJulianDay() - QwtDate::JulianDayForEpoch;
373
374 const QTime time = dt.time();
375 const double secs = 3600.0 * time.hour() +
376 60.0 * time.minute() + time.second();
377
378 return days * msecsPerDay + time.msec() + 1000.0 * secs;
379}
380
381/*!
382 Ceil a datetime according the interval type
383
384 \param dateTime Datetime value
385 \param intervalType Interval type, how to ceil.
386 F.e. when intervalType = QwtDate::Months, the result
387 will be ceiled to the next beginning of a month
388 \return Ceiled datetime
389 \sa floor()
390 */
391QDateTime QwtDate::ceil( const QDateTime &dateTime, IntervalType intervalType )
392{
393 if ( dateTime.date() >= QwtDate::maxDate() )
394 return dateTime;
395
396 QDateTime dt = dateTime;
397
398 switch ( intervalType )
399 {
400 case QwtDate::Millisecond:
401 {
402 break;
403 }
404 case QwtDate::Second:
405 {
406 qwtFloorTime( QwtDate::Second, dt );
407 if ( dt < dateTime )
408 dt = dt.addSecs( 1 );
409
410 break;
411 }
412 case QwtDate::Minute:
413 {
414 qwtFloorTime( QwtDate::Minute, dt );
415 if ( dt < dateTime )
416 dt = dt.addSecs( 60 );
417
418 break;
419 }
420 case QwtDate::Hour:
421 {
422 qwtFloorTime( QwtDate::Hour, dt );
423 if ( dt < dateTime )
424 dt = dt.addSecs( 3600 );
425
426 break;
427 }
428 case QwtDate::Day:
429 {
430 dt.setTime( QTime( 0, 0 ) );
431 if ( dt < dateTime )
432 dt = dt.addDays( 1 );
433
434 break;
435 }
436 case QwtDate::Week:
437 {
438 dt.setTime( QTime( 0, 0 ) );
439 if ( dt < dateTime )
440 dt = dt.addDays( 1 );
441
442 int days = qwtFirstDayOfWeek() - dt.date().dayOfWeek();
443 if ( days < 0 )
444 days += 7;
445
446 dt = dt.addDays( days );
447
448 break;
449 }
450 case QwtDate::Month:
451 {
452 dt.setTime( QTime( 0, 0 ) );
453 dt.setDate( qwtToDate( dateTime.date().year(),
454 dateTime.date().month() ) );
455
456 if ( dt < dateTime )
457 dt = dt.addMonths( 1 );
458
459 break;
460 }
461 case QwtDate::Year:
462 {
463 dt.setTime( QTime( 0, 0 ) );
464
465 const QDate d = dateTime.date();
466
467 int year = d.year();
468 if ( d.month() > 1 || d.day() > 1 || !dateTime.time().isNull() )
469 year++;
470
471 if ( year == 0 )
472 year++; // there is no year 0
473
474 dt.setDate( qwtToDate( year ) );
475 break;
476 }
477 }
478
479 return dt;
480}
481
482/*!
483 Floor a datetime according the interval type
484
485 \param dateTime Datetime value
486 \param intervalType Interval type, how to ceil.
487 F.e. when intervalType = QwtDate::Months,
488 the result will be ceiled to the next
489 beginning of a month
490 \return Floored datetime
491 \sa floor()
492 */
493QDateTime QwtDate::floor( const QDateTime &dateTime,
494 IntervalType intervalType )
495{
496 if ( dateTime.date() <= QwtDate::minDate() )
497 return dateTime;
498
499 QDateTime dt = dateTime;
500
501 switch ( intervalType )
502 {
503 case QwtDate::Millisecond:
504 {
505 break;
506 }
507 case QwtDate::Second:
508 case QwtDate::Minute:
509 case QwtDate::Hour:
510 {
511 qwtFloorTime( intervalType, dt );
512 break;
513 }
514 case QwtDate::Day:
515 {
516 dt.setTime( QTime( 0, 0 ) );
517 break;
518 }
519 case QwtDate::Week:
520 {
521 dt.setTime( QTime( 0, 0 ) );
522
523 int days = dt.date().dayOfWeek() - qwtFirstDayOfWeek();
524 if ( days < 0 )
525 days += 7;
526
527 dt = dt.addDays( -days );
528
529 break;
530 }
531 case QwtDate::Month:
532 {
533 dt.setTime( QTime( 0, 0 ) );
534
535 const QDate date = qwtToDate( dt.date().year(),
536 dt.date().month() );
537 dt.setDate( date );
538
539 break;
540 }
541 case QwtDate::Year:
542 {
543 dt.setTime( QTime( 0, 0 ) );
544
545 const QDate date = qwtToDate( dt.date().year() );
546 dt.setDate( date );
547
548 break;
549 }
550 }
551
552 return dt;
553}
554
555/*!
556 Minimum for the supported date range
557
558 The range of valid dates depends on how QDate stores the
559 Julian day internally.
560
561 - For Qt4 it is "Tue Jan 2 -4713"
562 - For Qt5 it is "Thu Jan 1 -2147483648"
563
564 \return minimum of the date range
565 \sa maxDate()
566 */
567QDate QwtDate::minDate()
568{
569 static QDate date;
570 if ( !date.isValid() )
571 date = QDate::fromJulianDay( minJulianDayD );
572
573 return date;
574}
575
576/*!
577 Maximum for the supported date range
578
579 The range of valid dates depends on how QDate stores the
580 Julian day internally.
581
582 - For Qt4 it is "Tue Jun 3 5874898"
583 - For Qt5 it is "Tue Dec 31 2147483647"
584
585 \return maximum of the date range
586 \sa minDate()
587 \note The maximum differs between Qt4 and Qt5
588 */
589QDate QwtDate::maxDate()
590{
591 static QDate date;
592 if ( !date.isValid() )
593 date = QDate::fromJulianDay( maxJulianDayD );
594
595 return date;
596}
597
598/*!
599 \brief Date of the first day of the first week for a year
600
601 The first day of a week depends on the current locale
602 ( QLocale::firstDayOfWeek() ).
603
604 \param year Year
605 \param type Option how to identify the first week
606 \return First day of week 0
607
608 \sa QLocale::firstDayOfWeek(), weekNumber()
609 */
610QDate QwtDate::dateOfWeek0( int year, Week0Type type )
611{
612 const Qt::DayOfWeek firstDayOfWeek = qwtFirstDayOfWeek();
613
614 QDate dt0( year, 1, 1 );
615
616 // floor to the first day of the week
617 int days = dt0.dayOfWeek() - firstDayOfWeek;
618 if ( days < 0 )
619 days += 7;
620
621 dt0 = dt0.addDays( -days );
622
623 if ( type == QwtDate::FirstThursday )
624 {
625 // according to ISO 8601 the first week is defined
626 // by the first thursday.
627
628 int d = Qt::Thursday - firstDayOfWeek;
629 if ( d < 0 )
630 d += 7;
631
632 if ( dt0.addDays( d ).year() < year )
633 dt0 = dt0.addDays( 7 );
634 }
635
636 return dt0;
637}
638
639/*!
640 Find the week number of a date
641
642 - QwtDate::FirstThursday\n
643 Corresponding to ISO 8601 ( see QDate::weekNumber() ).
644
645 - QwtDate::FirstDay\n
646 Number of weeks that have begun since dateOfWeek0().
647
648 \param date Date
649 \param type Option how to identify the first week
650
651 \return Week number, starting with 1
652 */
653int QwtDate::weekNumber( const QDate &date, Week0Type type )
654{
655 int weekNo;
656
657 if ( type == QwtDate::FirstDay )
658 {
659 QDate day0;
660
661 if ( date.month() == 12 && date.day() >= 24 )
662 {
663 // week 1 usually starts in the previous years.
664 // and we have to check if we are already there
665
666 day0 = dateOfWeek0( date.year() + 1, type );
667 if ( day0.daysTo( date ) < 0 )
668 day0 = dateOfWeek0( date.year(), type );
669 }
670 else
671 {
672 day0 = dateOfWeek0( date.year(), type );
673 }
674
675 weekNo = day0.daysTo( date ) / 7 + 1;
676 }
677 else
678 {
679 weekNo = date.weekNumber();
680 }
681
682 return weekNo;
683}
684
685/*!
686 Offset in seconds from Coordinated Universal Time
687
688 The offset depends on the time specification of dateTime:
689
690 - Qt::UTC
691 0, dateTime has no offset
692 - Qt::OffsetFromUTC
693 returns dateTime.utcOffset()
694 - Qt::LocalTime:
695 number of seconds from the UTC
696
697 For Qt::LocalTime the offset depends on the timezone and
698 daylight savings.
699
700 \param dateTime Datetime value
701 \return Offset in seconds
702 */
703int QwtDate::utcOffset( const QDateTime &dateTime )
704{
705 int seconds = 0;
706
707 switch( dateTime.timeSpec() )
708 {
709 case Qt::UTC:
710 {
711 break;
712 }
713 case Qt::OffsetFromUTC:
714 {
715 seconds = dateTime.utcOffset();
716 break;
717 }
718 default:
719 {
720 const QDateTime dt1( dateTime.date(), dateTime.time(), Qt::UTC );
721 seconds = dateTime.secsTo( dt1 );
722 }
723 }
724
725 return seconds;
726}
727
728/*!
729 Translate a datetime into a string
730
731 Beside the format expressions documented in QDateTime::toString()
732 the following expressions are supported:
733
734 - w\n
735 week number: ( 1 - 53 )
736 - ww\n
737 week number with a leading zero ( 01 - 53 )
738
739 As week 1 usually starts in the previous year a special rule
740 is applied for formats, where the year is expected to match the
741 week number - even if the date belongs to the previous year.
742
743 \param dateTime Datetime value
744 \param format Format string
745 \param week0Type Specification of week 0
746
747 \return Datetime string
748 \sa QDateTime::toString(), weekNumber(), QwtDateScaleDraw
749 */
750QString QwtDate::toString( const QDateTime &dateTime,
751 const QString & format, Week0Type week0Type )
752{
753 QString fmt = format;
754 if ( fmt.contains( 'w' ) )
755 {
756 fmt = qwtExpandedFormat( fmt, dateTime, week0Type );
757 }
758
759 return dateTime.toString( fmt );
760}
Note: See TracBrowser for help on using the repository browser.