1  /* * mode: C++ ; cfilestyle: "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_scale_engine.h"


11  #include "qwt_math.h"


12  #include "qwt_transform.h"


13  #include <qdatetime.h>


14  #include <limits.h>


15 


16  static inline double qwtMsecsForType( int type )


17  {


18  static const double msecs[] =


19  {


20  1.0,


21  1000.0,


22  60.0 * 1000.0,


23  3600.0 * 1000.0,


24  24.0 * 3600.0 * 1000.0,


25  7.0 * 24.0 * 3600.0 * 1000.0,


26  30.0 * 24.0 * 3600.0 * 1000.0,


27  365.0 * 24.0 * 3600.0 * 1000.0,


28  };


29 


30  if ( type < 0  type >= static_cast<int>( sizeof( msecs ) / sizeof( msecs[0] ) ) )


31  return 1.0;


32 


33  return msecs[ type ];


34  }


35 


36  static inline int qwtAlignValue(


37  double value, double stepSize, bool up )


38  {


39  double d = value / stepSize;


40  d = up ? ::ceil( d ) : ::floor( d );


41 


42  return static_cast<int>( d * stepSize );


43  }


44 


45  static double qwtIntervalWidth( const QDateTime &minDate,


46  const QDateTime &maxDate, QwtDate::IntervalType intervalType )


47  {


48  switch( intervalType )


49  {


50  case QwtDate::Millisecond:


51  {


52  const double secsTo = minDate.secsTo( maxDate );


53  const double msecs = maxDate.time().msec() 


54  minDate.time().msec();


55 


56  return secsTo * 1000 + msecs;


57  }


58  case QwtDate::Second:


59  {


60  return minDate.secsTo( maxDate );


61  }


62  case QwtDate::Minute:


63  {


64  const double secsTo = minDate.secsTo( maxDate );


65  return ::floor( secsTo / 60 );


66  }


67  case QwtDate::Hour:


68  {


69  const double secsTo = minDate.secsTo( maxDate );


70  return ::floor( secsTo / 3600 );


71  }


72  case QwtDate::Day:


73  {


74  return minDate.daysTo( maxDate );


75  }


76  case QwtDate::Week:


77  {


78  return ::floor( minDate.daysTo( maxDate ) / 7.0 );


79  }


80  case QwtDate::Month:


81  {


82  const double years =


83  double( maxDate.date().year() )  minDate.date().year();


84 


85  int months = maxDate.date().month()  minDate.date().month();


86  if ( maxDate.date().day() < minDate.date().day() )


87  months;


88 


89  return years * 12 + months;


90  }


91  case QwtDate::Year:


92  {


93  double years =


94  double( maxDate.date().year() )  minDate.date().year();


95 


96  if ( maxDate.date().month() < minDate.date().month() )


97  years = 1.0;


98 


99  return years;


100  }


101  }


102 


103  return 0.0;


104  }


105 


106  static double qwtRoundedIntervalWidth(


107  const QDateTime &minDate, const QDateTime &maxDate,


108  QwtDate::IntervalType intervalType )


109  {


110  const QDateTime minD = QwtDate::floor( minDate, intervalType );


111  const QDateTime maxD = QwtDate::ceil( maxDate, intervalType );


112 


113  return qwtIntervalWidth( minD, maxD, intervalType );


114  }


115 


116  static inline int qwtStepCount( int intervalSize, int maxSteps,


117  const int limits[], size_t numLimits )


118  {


119  for ( uint i = 0; i < numLimits; i++ )


120  {


121  const int numSteps = intervalSize / limits[ i ];


122 


123  if ( numSteps > 1 && numSteps <= maxSteps &&


124  numSteps * limits[ i ] == intervalSize )


125  {


126  return numSteps;


127  }


128  }


129 


130  return 0;


131  }


132 


133  static int qwtStepSize( int intervalSize, int maxSteps, uint base )


134  {


135  if ( maxSteps <= 0 )


136  return 0;


137 


138  if ( maxSteps > 2 )


139  {


140  for ( int numSteps = maxSteps; numSteps > 1; numSteps )


141  {


142  const double stepSize = double( intervalSize ) / numSteps;


143 


144  const double p = ::floor( ::log( stepSize ) / ::log( double( base ) ) );


145  const double fraction = qPow( base, p );


146 


147  for ( uint n = base; n >= 1; n /= 2 )


148  {


149  if ( qFuzzyCompare( stepSize, n * fraction ) )


150  return qRound( stepSize );


151 


152  if ( n == 3 && ( base % 2 ) == 0 )


153  {


154  if ( qFuzzyCompare( stepSize, 2 * fraction ) )


155  return qRound( stepSize );


156  }


157  }


158  }


159  }


160 


161  return 0;


162  }


163 


164  static int qwtDivideInterval( double intervalSize, int numSteps,


165  const int limits[], size_t numLimits )


166  {


167  const int v = qCeil( intervalSize / double( numSteps ) );


168 


169  for ( uint i = 0; i < numLimits  1; i++ )


170  {


171  if ( v <= limits[i] )


172  return limits[i];


173  }


174 


175  return limits[ numLimits  1 ];


176  }


177 


178  static double qwtDivideScale( double intervalSize, int numSteps,


179  QwtDate::IntervalType intervalType )


180  {


181  if ( intervalType != QwtDate::Day )


182  {


183  if ( ( intervalSize > numSteps ) &&


184  ( intervalSize <= 2 * numSteps ) )


185  {


186  return 2.0;


187  }


188  }


189 


190  double stepSize;


191 


192  switch( intervalType )


193  {


194  case QwtDate::Second:


195  case QwtDate::Minute:


196  {


197  static int limits[] = { 1, 2, 5, 10, 15, 20, 30, 60 };


198 


199  stepSize = qwtDivideInterval( intervalSize, numSteps,


200  limits, sizeof( limits ) / sizeof( int ) );


201 


202  break;


203  }


204  case QwtDate::Hour:


205  {


206  static int limits[] = { 1, 2, 3, 4, 6, 12, 24 };


207 


208  stepSize = qwtDivideInterval( intervalSize, numSteps,


209  limits, sizeof( limits ) / sizeof( int ) );


210 


211  break;


212  }


213  case QwtDate::Day:


214  {


215  const double v = intervalSize / double( numSteps );


216  if ( v <= 5.0 )


217  stepSize = qCeil( v );


218  else


219  stepSize = qCeil( v / 7 ) * 7;


220 


221  break;


222  }


223  case QwtDate::Week:


224  {


225  static int limits[] = { 1, 2, 4, 8, 12, 26, 52 };


226 


227  stepSize = qwtDivideInterval( intervalSize, numSteps,


228  limits, sizeof( limits ) / sizeof( int ) );


229 


230  break;


231  }


232  case QwtDate::Month:


233  {


234  static int limits[] = { 1, 2, 3, 4, 6, 12 };


235 


236  stepSize = qwtDivideInterval( intervalSize, numSteps,


237  limits, sizeof( limits ) / sizeof( int ) );


238 


239  break;


240  }


241  case QwtDate::Year:


242  case QwtDate::Millisecond:


243  default:


244  {


245  stepSize = QwtScaleArithmetic::divideInterval(


246  intervalSize, numSteps, 10 );


247  }


248  }


249 


250  return stepSize;


251  }


252 


253  static double qwtDivideMajorStep( double stepSize, int maxMinSteps,


254  QwtDate::IntervalType intervalType )


255  {


256  double minStepSize = 0.0;


257 


258  switch( intervalType )


259  {


260  case QwtDate::Second:


261  {


262  minStepSize = qwtStepSize( stepSize, maxMinSteps, 10 );


263  if ( minStepSize == 0.0 )


264  minStepSize = 0.5 * stepSize;


265 


266  break;


267  }


268  case QwtDate::Minute:


269  {


270  static int limits[] = { 1, 2, 5, 10, 15, 20, 30, 60 };


271 


272  int numSteps;


273 


274  if ( stepSize > maxMinSteps )


275  {


276  numSteps = qwtStepCount( stepSize, maxMinSteps,


277  limits, sizeof( limits ) / sizeof( int ) );


278 


279  }


280  else


281  {


282  numSteps = qwtStepCount( stepSize * 60, maxMinSteps,


283  limits, sizeof( limits ) / sizeof( int ) );


284  }


285 


286  if ( numSteps > 0 )


287  minStepSize = double( stepSize ) / numSteps;


288 


289  break;


290  }


291  case QwtDate::Hour:


292  {


293  int numSteps = 0;


294 


295  if ( stepSize > maxMinSteps )


296  {


297  static int limits[] = { 1, 2, 3, 4, 6, 12, 24, 48, 72 };


298 


299  numSteps = qwtStepCount( stepSize, maxMinSteps,


300  limits, sizeof( limits ) / sizeof( int ) );


301  }


302  else


303  {


304  static int limits[] = { 1, 2, 5, 10, 15, 20, 30, 60 };


305 


306  numSteps = qwtStepCount( stepSize * 60, maxMinSteps,


307  limits, sizeof( limits ) / sizeof( int ) );


308  }


309 


310  if ( numSteps > 0 )


311  minStepSize = double( stepSize ) / numSteps;


312 


313  break;


314  }


315  case QwtDate::Day:


316  {


317  int numSteps = 0;


318 


319  if ( stepSize > maxMinSteps )


320  {


321  static int limits[] = { 1, 2, 3, 7, 14, 28 };


322 


323  numSteps = qwtStepCount( stepSize, maxMinSteps,


324  limits, sizeof( limits ) / sizeof( int ) );


325  }


326  else


327  {


328  static int limits[] = { 1, 2, 3, 4, 6, 12, 24, 48, 72 };


329 


330  numSteps = qwtStepCount( stepSize * 24, maxMinSteps,


331  limits, sizeof( limits ) / sizeof( int ) );


332  }


333 


334  if ( numSteps > 0 )


335  minStepSize = double( stepSize ) / numSteps;


336 


337  break;


338  }


339  case QwtDate::Week:


340  {


341  const int daysInStep = stepSize * 7;


342 


343  if ( maxMinSteps >= daysInStep )


344  {


345  // we want to have one tick per day


346  minStepSize = 1.0 / 7.0;


347  }


348  else


349  {


350  // when the stepSize is more than a week we want to


351  // have a tick for each week


352 


353  const int stepSizeInWeeks = stepSize;


354 


355  if ( stepSizeInWeeks <= maxMinSteps )


356  {


357  minStepSize = 1;


358  }


359  else


360  {


361  minStepSize = QwtScaleArithmetic::divideInterval(


362  stepSizeInWeeks, maxMinSteps, 10 );


363  }


364  }


365  break;


366  }


367  case QwtDate::Month:


368  {


369  // fractions of months doesn't make any sense


370 


371  if ( stepSize < maxMinSteps )


372  maxMinSteps = static_cast<int>( stepSize );


373 


374  static int limits[] = { 1, 2, 3, 4, 6, 12 };


375 


376  int numSteps = qwtStepCount( stepSize, maxMinSteps,


377  limits, sizeof( limits ) / sizeof( int ) );


378 


379  if ( numSteps > 0 )


380  minStepSize = double( stepSize ) / numSteps;


381 


382  break;


383  }


384  case QwtDate::Year:


385  {


386  if ( stepSize >= maxMinSteps )


387  {


388  minStepSize = QwtScaleArithmetic::divideInterval(


389  stepSize, maxMinSteps, 10 );


390  }


391  else


392  {


393  // something in months


394 


395  static int limits[] = { 1, 2, 3, 4, 6, 12 };


396 


397  int numSteps = qwtStepCount( 12 * stepSize, maxMinSteps,


398  limits, sizeof( limits ) / sizeof( int ) );


399 


400  if ( numSteps > 0 )


401  minStepSize = double( stepSize ) / numSteps;


402  }


403 


404  break;


405  }


406  default:


407  break;


408  }


409 


410  if ( intervalType != QwtDate::Month


411  && minStepSize == 0.0 )


412  {


413  minStepSize = 0.5 * stepSize;


414  }


415 


416  return minStepSize;


417  }


418 


419  static QList<double> qwtDstTicks( const QDateTime &dateTime,


420  int secondsMajor, int secondsMinor )


421  {


422  if ( secondsMinor <= 0 )


423  QList<double>();


424 


425  QDateTime minDate = dateTime.addSecs( secondsMajor );


426  minDate = QwtDate::floor( minDate, QwtDate::Hour );


427 


428  const double utcOffset = QwtDate::utcOffset( dateTime );


429 


430  // find the hours where daylight saving time happens


431 


432  double dstMin = QwtDate::toDouble( minDate );


433  while ( minDate < dateTime &&


434  QwtDate::utcOffset( minDate ) != utcOffset )


435  {


436  minDate = minDate.addSecs( 3600 );


437  dstMin += 3600 * 1000.0;


438  }


439 


440  QList<double> ticks;


441  for ( int i = 0; i < 3600; i += secondsMinor )


442  ticks += dstMin + i * 1000.0;


443 


444  return ticks;


445  }


446 


447  static QwtScaleDiv qwtDivideToSeconds(


448  const QDateTime &minDate, const QDateTime &maxDate,


449  double stepSize, int maxMinSteps,


450  QwtDate::IntervalType intervalType )


451  {


452  // calculate the min step size


453  double minStepSize = 0;


454 


455  if ( maxMinSteps > 1 )


456  {


457  minStepSize = qwtDivideMajorStep( stepSize,


458  maxMinSteps, intervalType );


459  }


460 


461  bool daylightSaving = false;


462  if ( minDate.timeSpec() == Qt::LocalTime )


463  {


464  daylightSaving = intervalType > QwtDate::Hour;


465  if ( intervalType == QwtDate::Hour )


466  {


467  daylightSaving = stepSize > 1;


468  }


469  }


470 


471  const double s = qwtMsecsForType( intervalType ) / 1000;


472  const int secondsMajor = static_cast<int>( stepSize * s );


473  const double secondsMinor = minStepSize * s;


474 


475  // UTC excludes daylight savings. So from the difference


476  // of a date and its UTC counterpart we can find out


477  // the daylight saving hours


478 


479  const double utcOffset = QwtDate::utcOffset( minDate );


480  double dstOff = 0;


481 


482  QList<double> majorTicks;


483  QList<double> mediumTicks;


484  QList<double> minorTicks;


485 


486  for ( QDateTime dt = minDate; dt <= maxDate;


487  dt = dt.addSecs( secondsMajor ) )


488  {


489  if ( !dt.isValid() )


490  break;


491 


492  double majorValue = QwtDate::toDouble( dt );


493 


494  if ( daylightSaving )


495  {


496  const double offset = utcOffset  QwtDate::utcOffset( dt );


497  majorValue += offset * 1000.0;


498 


499  if ( offset > dstOff )


500  {


501  // we add some minor ticks for the DST hour,


502  // otherwise the ticks will be unaligned: 0, 2, 3, 5 ...


503  minorTicks += qwtDstTicks(


504  dt, secondsMajor, qRound( secondsMinor ) );


505  }


506 


507  dstOff = offset;


508  }


509 


510  if ( majorTicks.isEmpty()  majorTicks.last() != majorValue )


511  majorTicks += majorValue;


512 


513  if ( secondsMinor > 0.0 )


514  {


515  const int numMinorSteps = qFloor( secondsMajor / secondsMinor );


516 


517  for ( int i = 1; i < numMinorSteps; i++ )


518  {


519  const QDateTime mt = dt.addMSecs(


520  qRound64( i * secondsMinor * 1000 ) );


521 


522  double minorValue = QwtDate::toDouble( mt );


523  if ( daylightSaving )


524  {


525  const double offset = utcOffset  QwtDate::utcOffset( mt );


526  minorValue += offset * 1000.0;


527  }


528 


529  if ( minorTicks.isEmpty()  minorTicks.last() != minorValue )


530  {


531  const bool isMedium = ( numMinorSteps % 2 == 0 )


532  && ( i != 1 ) && ( i == numMinorSteps / 2 );


533 


534  if ( isMedium )


535  mediumTicks += minorValue;


536  else


537  minorTicks += minorValue;


538  }


539  }


540  }


541  }


542 


543  QwtScaleDiv scaleDiv;


544 


545  scaleDiv.setInterval( QwtDate::toDouble( minDate ),


546  QwtDate::toDouble( maxDate ) );


547 


548  scaleDiv.setTicks( QwtScaleDiv::MajorTick, majorTicks );


549  scaleDiv.setTicks( QwtScaleDiv::MediumTick, mediumTicks );


550  scaleDiv.setTicks( QwtScaleDiv::MinorTick, minorTicks );


551 


552  return scaleDiv;


553  }


554 


555  static QwtScaleDiv qwtDivideToMonths(


556  QDateTime &minDate, const QDateTime &maxDate,


557  double stepSize, int maxMinSteps )


558  {


559  // months are intervals with non


560  // equidistant ( in ms ) steps: we have to build the


561  // scale division manually


562 


563  int minStepDays = 0;


564  int minStepSize = 0.0;


565 


566  if ( maxMinSteps > 1 )


567  {


568  if ( stepSize == 1 )


569  {


570  if ( maxMinSteps >= 30 )


571  minStepDays = 1;


572  else if ( maxMinSteps >= 6 )


573  minStepDays = 5;


574  else if ( maxMinSteps >= 3 )


575  minStepDays = 10;


576  else


577  minStepDays = 15;


578  }


579  else


580  {


581  minStepSize = qwtDivideMajorStep(


582  stepSize, maxMinSteps, QwtDate::Month );


583  }


584  }


585 


586  QList<double> majorTicks;


587  QList<double> mediumTicks;


588  QList<double> minorTicks;


589 


590  for ( QDateTime dt = minDate;


591  dt <= maxDate; dt = dt.addMonths( stepSize ) )


592  {


593  if ( !dt.isValid() )


594  break;


595 


596  majorTicks += QwtDate::toDouble( dt );


597 


598  if ( minStepDays > 0 )


599  {


600  for ( int days = minStepDays;


601  days < 30; days += minStepDays )


602  {


603  const double tick = QwtDate::toDouble( dt.addDays( days ) );


604 


605  if ( days == 15 && minStepDays != 15 )


606  mediumTicks += tick;


607  else


608  minorTicks += tick;


609  }


610  }


611  else if ( minStepSize > 0.0 )


612  {


613  const int numMinorSteps = qRound( stepSize / (double) minStepSize );


614 


615  for ( int i = 1; i < numMinorSteps; i++ )


616  {


617  const double minorValue =


618  QwtDate::toDouble( dt.addMonths( i * minStepSize ) );


619 


620  if ( ( numMinorSteps % 2 == 0 ) && ( i == numMinorSteps / 2 ) )


621  mediumTicks += minorValue;


622  else


623  minorTicks += minorValue;


624  }


625  }


626  }


627 


628  QwtScaleDiv scaleDiv;


629  scaleDiv.setInterval( QwtDate::toDouble( minDate ),


630  QwtDate::toDouble( maxDate ) );


631 


632  scaleDiv.setTicks( QwtScaleDiv::MajorTick, majorTicks );


633  scaleDiv.setTicks( QwtScaleDiv::MediumTick, mediumTicks );


634  scaleDiv.setTicks( QwtScaleDiv::MinorTick, minorTicks );


635 


636  return scaleDiv;


637  }


638 


639  static QwtScaleDiv qwtDivideToYears(


640  const QDateTime &minDate, const QDateTime &maxDate,


641  double stepSize, int maxMinSteps )


642  {


643  QList<double> majorTicks;


644  QList<double> mediumTicks;


645  QList<double> minorTicks;


646 


647  double minStepSize = 0.0;


648 


649  if ( maxMinSteps > 1 )


650  {


651  minStepSize = qwtDivideMajorStep(


652  stepSize, maxMinSteps, QwtDate::Year );


653  }


654 


655  int numMinorSteps = 0;


656  if ( minStepSize > 0.0 )


657  numMinorSteps = qFloor( stepSize / minStepSize );


658 


659  bool dateBC = minDate.date().year() < 1;


660 


661  for ( QDateTime dt = minDate; dt <= maxDate;


662  dt = dt.addYears( stepSize ) )


663  {


664  if ( dateBC && dt.date().year() > 1 )


665  {


666  // there is no year 0 in the Julian calendar


667  dt = dt.addYears( 1 );


668  dateBC = false;


669  }


670 


671  if ( !dt.isValid() )


672  break;


673 


674  majorTicks += QwtDate::toDouble( dt );


675 


676  for ( int i = 1; i < numMinorSteps; i++ )


677  {


678  QDateTime tickDate;


679 


680  const double years = qRound( i * minStepSize );


681  if ( years >= INT_MAX / 12 )


682  {


683  tickDate = dt.addYears( years );


684  }


685  else


686  {


687  tickDate = dt.addMonths( qRound( years * 12 ) );


688  }


689 


690  const bool isMedium = ( numMinorSteps > 2 ) &&


691  ( numMinorSteps % 2 == 0 ) && ( i == numMinorSteps / 2 );


692 


693  const double minorValue = QwtDate::toDouble( tickDate );


694  if ( isMedium )


695  mediumTicks += minorValue;


696  else


697  minorTicks += minorValue;


698  }


699 


700  if ( QwtDate::maxDate().addYears( stepSize ) < dt.date() )


701  {


702  break;


703  }


704  }


705 


706  QwtScaleDiv scaleDiv;


707  scaleDiv.setInterval( QwtDate::toDouble( minDate ),


708  QwtDate::toDouble( maxDate ) );


709 


710  scaleDiv.setTicks( QwtScaleDiv::MajorTick, majorTicks );


711  scaleDiv.setTicks( QwtScaleDiv::MediumTick, mediumTicks );


712  scaleDiv.setTicks( QwtScaleDiv::MinorTick, minorTicks );


713 


714  return scaleDiv;


715  }


716 


717  class QwtDateScaleEngine::PrivateData


718  {


719  public:


720  PrivateData( Qt::TimeSpec spec ):


721  timeSpec( spec ),


722  utcOffset( 0 ),


723  week0Type( QwtDate::FirstThursday ),


724  maxWeeks( 4 )


725  {


726  }


727 


728  Qt::TimeSpec timeSpec;


729  int utcOffset;


730  QwtDate::Week0Type week0Type;


731  int maxWeeks;


732  };


733 


734 


735  /*!


736  \brief Constructor


737 


738  The engine is initialized to build scales for the


739  given time specification. It classifies intervals > 4 weeks


740  as >= Qt::Month. The first week of a year is defined like


741  for QwtDate::FirstThursday.


742 


743  \param timeSpec Time specification


744 


745  \sa setTimeSpec(), setMaxWeeks(), setWeek0Type()


746  */


747  QwtDateScaleEngine::QwtDateScaleEngine( Qt::TimeSpec timeSpec ):


748  QwtLinearScaleEngine( 10 )


749  {


750  d_data = new PrivateData( timeSpec );


751  }


752 


753  //! Destructor


754  QwtDateScaleEngine::~QwtDateScaleEngine()


755  {


756  delete d_data;


757  }


758 


759  /*!


760  Set the time specification used by the engine


761 


762  \param timeSpec Time specification


763  \sa timeSpec(), setUtcOffset(), toDateTime()


764  */


765  void QwtDateScaleEngine::setTimeSpec( Qt::TimeSpec timeSpec )


766  {


767  d_data>timeSpec = timeSpec;


768  }


769 


770  /*!


771  \return Time specification used by the engine


772  \sa setTimeSpec(), utcOffset(), toDateTime()


773  */


774  Qt::TimeSpec QwtDateScaleEngine::timeSpec() const


775  {


776  return d_data>timeSpec;


777  }


778 


779  /*!


780  Set the offset in seconds from Coordinated Universal Time


781 


782  \param seconds Offset in seconds


783 


784  \note The offset has no effect beside for the time specification


785  Qt::OffsetFromUTC.


786 


787  \sa QDate::utcOffset(), setTimeSpec(), toDateTime()


788  */


789  void QwtDateScaleEngine::setUtcOffset( int seconds )


790  {


791  d_data>utcOffset = seconds;


792  }


793 


794  /*!


795  \return Offset in seconds from Coordinated Universal Time


796  \note The offset has no effect beside for the time specification


797  Qt::OffsetFromUTC.


798 


799  \sa QDate::setUtcOffset(), setTimeSpec(), toDateTime()


800  */


801  int QwtDateScaleEngine::utcOffset() const


802  {


803  return d_data>utcOffset;


804  }


805 


806  /*!


807  Sets how to identify the first week of a year.


808 


809  \param week0Type Mode how to identify the first week of a year


810 


811  \sa week0Type(), setMaxWeeks()


812  \note week0Type has no effect beside for intervals classified as


813  QwtDate::Week.


814  */


815  void QwtDateScaleEngine::setWeek0Type( QwtDate::Week0Type week0Type )


816  {


817  d_data>week0Type = week0Type;


818  }


819 


820  /*!


821  \return Setting how to identify the first week of a year.


822  \sa setWeek0Type(), maxWeeks()


823  */


824  QwtDate::Week0Type QwtDateScaleEngine::week0Type() const


825  {


826  return d_data>week0Type;


827  }


828 


829  /*!


830  Set a upper limit for the number of weeks, when an interval


831  can be classified as Qt::Week.


832 


833  The default setting is 4 weeks.


834 


835  \param weeks Upper limit for the number of weeks


836 


837  \note In business charts a year is often divided


838  into weeks [152]


839  \sa maxWeeks(), setWeek0Type()


840  */


841  void QwtDateScaleEngine::setMaxWeeks( int weeks )


842  {


843  d_data>maxWeeks = qMax( weeks, 0 );


844  }


845 


846  /*!


847  \return Upper limit for the number of weeks, when an interval


848  can be classified as Qt::Week.


849  \sa setMaxWeeks(), week0Type()


850  */


851  int QwtDateScaleEngine::maxWeeks() const


852  {


853  return d_data>maxWeeks;


854  }


855 


856  /*!


857  Classification of a date/time interval division


858 


859  \param minDate Minimum ( = earlier ) of the interval


860  \param maxDate Maximum ( = later ) of the interval


861  \param maxSteps Maximum for the number of steps


862 


863  \return Interval classification


864  */


865  QwtDate::IntervalType QwtDateScaleEngine::intervalType(


866  const QDateTime &minDate, const QDateTime &maxDate,


867  int maxSteps ) const


868  {


869  const double jdMin = minDate.date().toJulianDay();


870  const double jdMax = maxDate.date().toJulianDay();


871 


872  if ( ( jdMax  jdMin ) / 365 > maxSteps )


873  return QwtDate::Year;


874 


875  const int months = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Month );


876  if ( months > maxSteps * 6 )


877  return QwtDate::Year;


878 


879  const int days = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Day );


880  const int weeks = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Week );


881 


882  if ( weeks > d_data>maxWeeks )


883  {


884  if ( days > 4 * maxSteps * 7 )


885  return QwtDate::Month;


886  }


887 


888  if ( days > maxSteps * 7 )


889  return QwtDate::Week;


890 


891  const int hours = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Hour );


892  if ( hours > maxSteps * 24 )


893  return QwtDate::Day;


894 


895  const int seconds = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Second );


896 


897  if ( seconds >= maxSteps * 3600 )


898  return QwtDate::Hour;


899 


900  if ( seconds >= maxSteps * 60 )


901  return QwtDate::Minute;


902 


903  if ( seconds >= maxSteps )


904  return QwtDate::Second;


905 


906  return QwtDate::Millisecond;


907  }


908 


909  /*!


910  Align and divide an interval


911 


912  The algorithm aligns and divides the interval into steps.


913 


914  Datetime interval divisions are usually not equidistant and the


915  calculated stepSize can only be used as an approximation


916  for the steps calculated by divideScale().


917 


918  \param maxNumSteps Max. number of steps


919  \param x1 First limit of the interval (In/Out)


920  \param x2 Second limit of the interval (In/Out)


921  \param stepSize Step size (Out)


922 


923  \sa QwtScaleEngine::setAttribute()


924  */


925  void QwtDateScaleEngine::autoScale( int maxNumSteps,


926  double &x1, double &x2, double &stepSize ) const


927  {


928  stepSize = 0.0;


929 


930  QwtInterval interval( x1, x2 );


931  interval = interval.normalized();


932 


933  interval.setMinValue( interval.minValue()  lowerMargin() );


934  interval.setMaxValue( interval.maxValue() + upperMargin() );


935 


936  if ( testAttribute( QwtScaleEngine::Symmetric ) )


937  interval = interval.symmetrize( reference() );


938 


939  if ( testAttribute( QwtScaleEngine::IncludeReference ) )


940  interval = interval.extend( reference() );


941 


942  if ( interval.width() == 0.0 )


943  interval = buildInterval( interval.minValue() );


944 


945  const QDateTime from = toDateTime( interval.minValue() );


946  const QDateTime to = toDateTime( interval.maxValue() );


947 


948  if ( from.isValid() && to.isValid() )


949  {


950  if ( maxNumSteps < 1 )


951  maxNumSteps = 1;


952 


953  const QwtDate::IntervalType intvType =


954  intervalType( from, to, maxNumSteps );


955 


956  const double width = qwtIntervalWidth( from, to, intvType );


957 


958  const double stepWidth = qwtDivideScale( width, maxNumSteps, intvType );


959  if ( stepWidth != 0.0 && !testAttribute( QwtScaleEngine::Floating ) )


960  {


961  const QDateTime d1 = alignDate( from, stepWidth, intvType, false );


962  const QDateTime d2 = alignDate( to, stepWidth, intvType, true );


963 


964  interval.setMinValue( QwtDate::toDouble( d1 ) );


965  interval.setMaxValue( QwtDate::toDouble( d2 ) );


966  }


967 


968  stepSize = stepWidth * qwtMsecsForType( intvType );


969  }


970 


971  x1 = interval.minValue();


972  x2 = interval.maxValue();


973 


974  if ( testAttribute( QwtScaleEngine::Inverted ) )


975  {


976  qSwap( x1, x2 );


977  stepSize = stepSize;


978  }


979  }


980 


981  /*!


982  \brief Calculate a scale division for a date/time interval


983 


984  \param x1 First interval limit


985  \param x2 Second interval limit


986  \param maxMajorSteps Maximum for the number of major steps


987  \param maxMinorSteps Maximum number of minor steps


988  \param stepSize Step size. If stepSize == 0, the scaleEngine


989  calculates one.


990  \return Calculated scale division


991  */


992  QwtScaleDiv QwtDateScaleEngine::divideScale( double x1, double x2,


993  int maxMajorSteps, int maxMinorSteps, double stepSize ) const


994  {


995  if ( maxMajorSteps < 1 )


996  maxMajorSteps = 1;


997 


998  const double min = qMin( x1, x2 );


999  const double max = qMax( x1, x2 );


1000 


1001  const QDateTime from = toDateTime( min );


1002  const QDateTime to = toDateTime( max );


1003 


1004  if ( from == to )


1005  return QwtScaleDiv();


1006 


1007  stepSize = qAbs( stepSize );


1008  if ( stepSize > 0.0 )


1009  {


1010  // as interval types above hours are not equidistant


1011  // ( even days might have 23/25 hours because of daylight saving )


1012  // the stepSize is used as a hint only


1013 


1014  maxMajorSteps = qCeil( ( max  min ) / stepSize );


1015  }


1016 


1017  const QwtDate::IntervalType intvType =


1018  intervalType( from, to, maxMajorSteps );


1019 


1020  QwtScaleDiv scaleDiv;


1021 


1022  if ( intvType == QwtDate::Millisecond )


1023  {


1024  // for milliseconds and below we can use the decimal system


1025  scaleDiv = QwtLinearScaleEngine::divideScale( min, max,


1026  maxMajorSteps, maxMinorSteps, stepSize );


1027  }


1028  else


1029  {


1030  const QDateTime minDate = QwtDate::floor( from, intvType );


1031  const QDateTime maxDate = QwtDate::ceil( to, intvType );


1032 


1033  scaleDiv = buildScaleDiv( minDate, maxDate,


1034  maxMajorSteps, maxMinorSteps, intvType );


1035 


1036  // scaleDiv has been calculated from an extended interval


1037  // adjusted to the step size. We have to shrink it again.


1038 


1039  scaleDiv = scaleDiv.bounded( min, max );


1040  }


1041 


1042  if ( x1 > x2 )


1043  scaleDiv.invert();


1044 


1045  return scaleDiv;


1046  }


1047 


1048  QwtScaleDiv QwtDateScaleEngine::buildScaleDiv(


1049  const QDateTime &minDate, const QDateTime &maxDate,


1050  int maxMajorSteps, int maxMinorSteps,


1051  QwtDate::IntervalType intervalType ) const


1052  {


1053  // calculate the step size


1054  const double stepSize = qwtDivideScale(


1055  qwtIntervalWidth( minDate, maxDate, intervalType ),


1056  maxMajorSteps, intervalType );


1057 


1058  // align minDate to the step size


1059  QDateTime dt0 = alignDate( minDate, stepSize, intervalType, false );


1060  if ( !dt0.isValid() )


1061  {


1062  // the floored date is out of the range of a


1063  // QDateTime  we ceil instead.


1064  dt0 = alignDate( minDate, stepSize, intervalType, true );


1065  }


1066 


1067  QwtScaleDiv scaleDiv;


1068 


1069  if ( intervalType <= QwtDate::Week )


1070  {


1071  scaleDiv = qwtDivideToSeconds( dt0, maxDate,


1072  stepSize, maxMinorSteps, intervalType );


1073  }


1074  else


1075  {


1076  if( intervalType == QwtDate::Month )


1077  {


1078  scaleDiv = qwtDivideToMonths( dt0, maxDate,


1079  stepSize, maxMinorSteps );


1080  }


1081  else if ( intervalType == QwtDate::Year )


1082  {


1083  scaleDiv = qwtDivideToYears( dt0, maxDate,


1084  stepSize, maxMinorSteps );


1085  }


1086  }


1087 


1088 


1089  return scaleDiv;


1090  }


1091 


1092  /*!


1093  Align a date/time value for a step size


1094 


1095  For Qt::Day alignments there is no "natural day 0" 


1096  instead the first day of the year is used to avoid jumping


1097  major ticks positions when panning a scale. For other alignments


1098  ( f.e according to the first day of the month ) alignDate()


1099  has to be overloaded.


1100 


1101  \param dateTime Date/time value


1102  \param stepSize Step size


1103  \param intervalType Interval type


1104  \param up When true dateTime is ceiled  otherwise it is floored


1105 


1106  \return Aligned date/time value


1107  */


1108  QDateTime QwtDateScaleEngine::alignDate(


1109  const QDateTime &dateTime, double stepSize,


1110  QwtDate::IntervalType intervalType, bool up ) const


1111  {


1112  // what about: (year == 1582 && month == 10 && day > 4 && day < 15) ??


1113 


1114  QDateTime dt = dateTime;


1115 


1116  if ( dateTime.timeSpec() == Qt::OffsetFromUTC )


1117  {


1118  dt.setUtcOffset( 0 );


1119  }


1120 


1121  switch( intervalType )


1122  {


1123  case QwtDate::Millisecond:


1124  {


1125  const int ms = qwtAlignValue(


1126  dt.time().msec(), stepSize, up ) ;


1127 


1128  dt = QwtDate::floor( dateTime, QwtDate::Second );


1129  dt = dt.addMSecs( ms );


1130 


1131  break;


1132  }


1133  case QwtDate::Second:


1134  {


1135  int second = dt.time().second();


1136  if ( up )


1137  {


1138  if ( dt.time().msec() > 0 )


1139  second++;


1140  }


1141 


1142  const int s = qwtAlignValue( second, stepSize, up );


1143 


1144  dt = QwtDate::floor( dt, QwtDate::Minute );


1145  dt = dt.addSecs( s );


1146 


1147  break;


1148  }


1149  case QwtDate::Minute:


1150  {


1151  int minute = dt.time().minute();


1152  if ( up )


1153  {


1154  if ( dt.time().msec() > 0  dt.time().second() > 0 )


1155  minute++;


1156  }


1157 


1158  const int m = qwtAlignValue( minute, stepSize, up );


1159 


1160  dt = QwtDate::floor( dt, QwtDate::Hour );


1161  dt = dt.addSecs( m * 60 );


1162 


1163  break;


1164  }


1165  case QwtDate::Hour:


1166  {


1167  int hour = dt.time().hour();


1168  if ( up )


1169  {


1170  if ( dt.time().msec() > 0  dt.time().second() > 0


1171   dt.time().minute() > 0 )


1172  {


1173  hour++;


1174  }


1175  }


1176  const int h = qwtAlignValue( hour, stepSize, up );


1177 


1178  dt = QwtDate::floor( dt, QwtDate::Day );


1179  dt = dt.addSecs( h * 3600 );


1180 


1181  break;


1182  }


1183  case QwtDate::Day:


1184  {


1185  // What date do we expect f.e. from an alignment of 5 days ??


1186  // Aligning them to the beginning of the year avoids at least


1187  // jumping major ticks when panning


1188 


1189  int day = dt.date().dayOfYear();


1190  if ( up )


1191  {


1192  if ( dt.time() > QTime( 0, 0 ) )


1193  day++;


1194  }


1195 


1196  const int d = qwtAlignValue( day, stepSize, up );


1197 


1198  dt = QwtDate::floor( dt, QwtDate::Year );


1199  dt = dt.addDays( d  1 );


1200 


1201  break;


1202  }


1203  case QwtDate::Week:


1204  {


1205  const QDate date = QwtDate::dateOfWeek0(


1206  dt.date().year(), d_data>week0Type );


1207 


1208  int numWeeks = date.daysTo( dt.date() ) / 7;


1209  if ( up )


1210  {


1211  if ( dt.time() > QTime( 0, 0 ) 


1212  date.daysTo( dt.date() ) % 7 )


1213  {


1214  numWeeks++;


1215  }


1216  }


1217 


1218  const int d = qwtAlignValue( numWeeks, stepSize, up ) * 7;


1219 


1220  dt = QwtDate::floor( dt, QwtDate::Day );


1221  dt.setDate( date );


1222  dt = dt.addDays( d );


1223 


1224  break;


1225  }


1226  case QwtDate::Month:


1227  {


1228  int month = dt.date().month();


1229  if ( up )


1230  {


1231  if ( dt.date().day() > 1 


1232  dt.time() > QTime( 0, 0 ) )


1233  {


1234  month++;


1235  }


1236  }


1237 


1238  const int m = qwtAlignValue( month  1, stepSize, up );


1239 


1240  dt = QwtDate::floor( dt, QwtDate::Year );


1241  dt = dt.addMonths( m );


1242 


1243  break;


1244  }


1245  case QwtDate::Year:


1246  {


1247  int year = dateTime.date().year();


1248  if ( up )


1249  {


1250  if ( dateTime.date().dayOfYear() > 1 


1251  dt.time() > QTime( 0, 0 ) )


1252  {


1253  year++;


1254  }


1255  }


1256 


1257  const int y = qwtAlignValue( year, stepSize, up );


1258 


1259  dt = QwtDate::floor( dt, QwtDate::Day );


1260  if ( y == 0 )


1261  {


1262  // there is no year 0 in the Julian calendar


1263  dt.setDate( QDate( stepSize, 1, 1 ).addYears( stepSize ) );


1264  }


1265  else


1266  {


1267  dt.setDate( QDate( y, 1, 1 ) );


1268  }


1269 


1270  break;


1271  }


1272  }


1273 


1274  if ( dateTime.timeSpec() == Qt::OffsetFromUTC )


1275  {


1276  dt.setUtcOffset( dateTime.utcOffset() );


1277  }


1278 


1279  return dt;


1280  }


1281 


1282  /*!


1283  Translate a double value into a QDateTime object.


1284 


1285  For QDateTime result is bounded by QwtDate::minDate() and QwtDate::maxDate()


1286 


1287  \return QDateTime object initialized with timeSpec() and utcOffset().


1288  \sa timeSpec(), utcOffset(), QwtDate::toDateTime()


1289  */


1290  QDateTime QwtDateScaleEngine::toDateTime( double value ) const


1291  {


1292  QDateTime dt = QwtDate::toDateTime( value, d_data>timeSpec );


1293  if ( !dt.isValid() )


1294  {


1295  const QDate date = ( value <= 0.0 )


1296  ? QwtDate::minDate() : QwtDate::maxDate();


1297 


1298  dt = QDateTime( date, QTime( 0, 0 ), d_data>timeSpec );


1299  }


1300 


1301  if ( d_data>timeSpec == Qt::OffsetFromUTC )


1302  {


1303  dt = dt.addSecs( d_data>utcOffset );


1304  dt.setUtcOffset( d_data>utcOffset );


1305  }


1306 


1307  return dt;


1308  }


1309 

