/***************************************************************************
**
**  This file is part of QGpCoreTools.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This file is distributed in the hope that it will be useful, but WITHOUT
**  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
**  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
**  License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2009-10-24
**  Copyright: 2009-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "DateTime.h"
#include "Translations.h"
#include "CoreApplication.h"
#include "StringSection.h"

namespace QGpCoreTools {

/*!
  \class DateTime DateTime.h
  \brief A high precision time class without limitation (e.g. 1970-01-01)

  It is based upon QDate for calendar operations (e.g. handling of leap years). Calendar limitation are
  those of QDate, i.e., from January 2nd, 4713 BCE, to some time in the year 11 million CE.
  Leap seconds are currently not handled (TODO in a future release).
  Fraction of seconds are stored as double offering a precision much smaller than nanoseconds.
  Local time is not considered, all times are considered as UTC.
*/

const DateTime DateTime::null;
const DateTime DateTime::minimumTime(QDate(-2000, 1, 1), 0, 0.0);
const DateTime DateTime::maximumTime(QDate(6000, 1, 1), 86399, 0.999999999);
const QString DateTime::defaultFormat("CyyyyMMddhhmmssz");
const QString DateTime::defaultUserFormat("Dyyyy-MM-dd hh:mm:ssz");

class DateTimeParser
{
  TRANSLATIONS("DateTimeParser")
public:
  DateTimeParser() {_defaultValues=false;}
  virtual ~DateTimeParser() {}

  bool parse(const QString& format);
protected:
  virtual bool C_parsed();
  virtual bool D_parsed();
  virtual bool d_parsed()=0;
  virtual bool dd_parsed()=0;
  virtual bool ddd_parsed()=0;
  virtual bool dddd_parsed()=0;
  virtual bool M_parsed()=0;
  virtual bool MM_parsed()=0;
  virtual bool MMM_parsed()=0;
  virtual bool MMMM_parsed()=0;
  virtual bool yy_parsed()=0;
  virtual bool yyyy_parsed()=0;
  virtual bool h_parsed()=0;
  virtual bool hh_parsed()=0;
  virtual bool H_parsed()=0;
  virtual bool HH_parsed()=0;
  virtual bool m_parsed()=0;
  virtual bool mm_parsed()=0;
  virtual bool s_parsed()=0;
  virtual bool ss_parsed()=0;
  virtual bool sz_parsed()=0;
  virtual bool ssz_parsed()=0;
  virtual bool z_parsed()=0;
  virtual bool zzz_parsed()=0;
  virtual bool ap_parsed()=0;
  virtual bool AP_parsed()=0;
  virtual bool quote_parsed(const StringSection& text)=0;
  virtual bool other_parsed(const QChar& c)=0;
protected:
  QLocale _locale;
  bool _defaultValues;
private:
  bool error();
  bool skipQuotedText();

  QString _format;
  const QChar * _ptr;
};

bool DateTimeParser::C_parsed()
{
  _locale=QLocale("C");
  _locale.setNumberOptions(QLocale::OmitGroupSeparator);
  return true;
}

bool DateTimeParser::D_parsed()
{
  _defaultValues=true;
  return true;
}

bool DateTimeParser::error()
{
  QString errorPos(static_cast<int>(_ptr-_format.data()), ' ');
  App::log(tr("error parsing time '%1'\n"
              "                    %2^\n").arg(_format).arg(errorPos));
  return false;
}

bool DateTimeParser::parse(const QString& format)
{
  _format=format;
  _ptr=_format.data();
  while(true) {
    switch(_ptr[0].unicode()) {
    case 'C':
      if(!C_parsed()) return error();
      break;
    case 'D':
      if(!D_parsed()) return error();
      break;
    case 'd':
      if(_ptr[1].unicode()=='d') {
        if(_ptr[2].unicode()=='d') {
          if(_ptr[3].unicode()=='d') {
            if(!dddd_parsed()) return error();
            _ptr+=3;
          } else {
            if(!ddd_parsed()) return error();
            _ptr+=2;
          }
        } else {
          if(!dd_parsed()) return error();
          _ptr++;
        }
      } else {
        if(!d_parsed()) return error();
      }
      break;
    case 'M':
      if(_ptr[1].unicode()=='M') {
        if(_ptr[2].unicode()=='M') {
          if(_ptr[3].unicode()=='M') {
            if(!MMMM_parsed()) return error();
            _ptr+=3;
          } else {
            if(!MMM_parsed()) return error();
            _ptr+=2;
          }
        } else {
          if(!MM_parsed()) return error();
          _ptr++;
        }
      } else {
        if(!M_parsed()) return error();
      }
      break;
    case 'y':
      if(_ptr[1].unicode()=='y') {
        if(_ptr[2].unicode()=='y' && _ptr[3].unicode()=='y') {
          if(!yyyy_parsed()) return error();
          _ptr+=3;
        } else {
          if(!yy_parsed()) return error();
          _ptr+=1;
        }
      } else if(!other_parsed(_ptr[0])) {
        return error();
      }
      break;
    case 'h':
      if(_ptr[1].unicode()=='h') {
        if(!hh_parsed()) return error();
        _ptr++;
      } else {
        if(!h_parsed()) return error();
      }
      break;
    case 'H':
      if(_ptr[1].unicode()=='H') {
        if(!HH_parsed()) return error();
        _ptr++;
      } else {
        if(!H_parsed()) return error();
      }
      break;
    case 'm':
      if(_ptr[1].unicode()=='m') {
        if(!mm_parsed()) return error();
        _ptr++;
      } else {
        if(!m_parsed()) return error();
      }
      break;
    case 's':
      switch(_ptr[1].unicode()) {
      case 's':
        if(_ptr[2].unicode()=='z') {
          if(!ssz_parsed()) return error();
          _ptr+=2;
        } else {
          if(!ss_parsed()) return error();
          _ptr++;
        }
        break;
      case 'z':
        if(!sz_parsed()) return error();
        _ptr++;
        break;
      default:
        if(!s_parsed()) return error();
        break;
      }
      break;
    case 'z':
      if(_ptr[1].unicode()=='z' && _ptr[2].unicode()=='z') {
        if(!zzz_parsed()) return error();
        _ptr+=2;
      } else {
        if(!z_parsed()) return error();
      }
      break;
    case 'a':
      if(_ptr[1].unicode()=='p') {
        if(!ap_parsed()) return error();
        _ptr++;
      } else if(!other_parsed(_ptr[0])) {
        return error();
      }
      break;
    case 'A':
      if(_ptr[1].unicode()=='P') {
        if(!AP_parsed()) return error();
        _ptr++;
      } else if(!other_parsed(_ptr[0])) {
        return error();
      }
      break;
    case '\'': {
        StringSection str(++_ptr, 0);
        if(!skipQuotedText()) return error();
        str.setEnd(_ptr);
        if(!quote_parsed(str)) return error();
      }
      break;
    case '\0':    // END
      return true;
    default:
      if(!other_parsed(_ptr[0])) {
        return error();
      }
      break;
    }
    _ptr++;
  }
}

bool DateTimeParser::skipQuotedText()
{
  while(true) {
    switch(_ptr[0].unicode()) {
    case '\0':
      App::log(tr("unterminated quoted text\n"));
      return false;
    case '\'':
      return true;
    default:
      break;
    }
    _ptr++;
  }
}

class DateTimeFromString: public DateTimeParser
{
  TRANSLATIONS("DateTimeFromString")
public:
  DateTimeFromString(const QString& text);

  DateTimeData value(bool& ok) const;
protected:
  virtual bool d_parsed();
  virtual bool dd_parsed();
  virtual bool ddd_parsed();
  virtual bool dddd_parsed();
  virtual bool M_parsed();
  virtual bool MM_parsed();
  virtual bool MMM_parsed();
  virtual bool MMMM_parsed();
  virtual bool yy_parsed();
  virtual bool yyyy_parsed();
  virtual bool h_parsed();
  virtual bool hh_parsed();
  virtual bool H_parsed();
  virtual bool HH_parsed();
  virtual bool m_parsed();
  virtual bool mm_parsed();
  virtual bool s_parsed();
  virtual bool ss_parsed();
  virtual bool sz_parsed();
  virtual bool ssz_parsed();
  virtual bool z_parsed();
  virtual bool zzz_parsed();
  virtual bool ap_parsed();
  virtual bool AP_parsed();
  virtual bool quote_parsed(const StringSection& text);
  virtual bool other_parsed(const QChar& c);
private:
  bool truncatedError() const;

  enum AMPM {UndefinedAMPM, AM, PM};
  AMPM _ampm;
  int _year, _month, _day, _hour, _minute, _second;
  QString _text;
  StringSection _current;
  double _fraction;
  QString _dayShort, _dayLong;
};

DateTimeFromString::DateTimeFromString(const QString& text)
  : DateTimeParser()
{
  _text=text;
  _current.set(_text);
  _year=1970;
  _month=1;
  _day=1;
  _hour=0;
  _minute=0;
  _second=0;
  _fraction=0.0;
  _ampm=UndefinedAMPM;
}


bool DateTimeFromString::truncatedError() const
{
  if(_defaultValues) {
    return true;
  } else {
    App::log(tr("truncated date/time specification: '%1'\n").arg(_text));
    return false;
  }
}

DateTimeData DateTimeFromString::value(bool& ok) const
{
  DateTimeData t;
  ok=true;
  if(!_current.isEmpty()) {
    App::log(tr("unmatched characters: '%1'\n").arg(_current.toString()));
    ok=false;
    return t;
  }
  if(!t.setDate(_year, _month, _day)) {
    App::log(tr("not a valid date\n") );
    ok=false;
    return t;
  }
  if(!_dayShort.isEmpty() && _dayShort!=_locale.dayName(t.date().dayOfWeek(), QLocale::ShortFormat)) {
    App::log(tr("%1 is not on a %2").arg(t.date().toString("yyyy-MM-dd\n")).arg(_dayShort));
    ok=false;
  }
  if(!_dayLong.isEmpty() && _dayLong!=_locale.dayName(t.date().dayOfWeek(), QLocale::LongFormat)) {
    App::log(tr("%1 is not on a %2").arg(t.date().toString("yyyy-MM-dd\n")).arg(_dayLong));
    ok=false;
  }
  int h=_hour;
  switch(_ampm) {
  case UndefinedAMPM:
    break;
  case AM:
    if(_hour>12) {
      App::log(tr("not a valid hour with am/pm\n") );
      ok=false;
      return t;
    }
    break;
  case PM:
    if(_hour>12) {
      App::log(tr("not a valid hour with am/pm\n") );
      ok=false;
      return t;
    }
    h+=12;
    break;
  }
  if(!t.setTime(h, _minute, _second, _fraction)) {
    App::log(tr("not a valid time\n") );
    ok=false;
  }
  return t;
}

bool DateTimeFromString::d_parsed()
{
  if(_current.size()>=1) {
    if(_current[0].isDigit() || _current[0].isSpace()) {
      if(_current.size()>=2 && _current[1].isDigit()) {
        _day=_locale.toInt(_current.left(2).toString());
        if(_day>=1 && _day<=31) {
          _current+=2;
          return true;
        }
      } else {
        _day=_locale.toInt(_current.left(1).toString());
        if(_day>=1) {
          _current+=1;
          return true;
        }
      }
    }
    App::log(tr("expected a number between 1 and 31: '%1'\n").arg(_current.left(2).toString()));
    return false;
  } else {
    return truncatedError();
  }
}

bool DateTimeFromString::dd_parsed()
{
  if(_current.size()>=1) {
    int l=1;
    if(_current[0].isDigit()) {
      if(_current.size()>=2 && _current[1].isDigit()) {
        l=2;
      }
      _day=_locale.toInt(_current.left(l).toString());
      if(_day>=1 && _day<=31) {
        _current+=l;
        return true;
      }
    }
    App::log(tr("expected a number between 1 and 31: '%1'\n").arg(_current.left(l).toString()));
    return false;
  } else {
    return truncatedError();
  }
}

bool DateTimeFromString::ddd_parsed()
{
  if(_current.size()>0) {
    for(int i=1;i<=7; i++) {
      QString n=_locale.dayName(i, QLocale::ShortFormat);
      if(_current.beginWith(n)) {
        _dayShort=n;
        _current+=n.count();
        return true;
      }
    }
    App::log(tr("expected a valid short day name: '%1'\n").arg(_current.left(3).toString()));
    return false;
  } else {
    return truncatedError();
  }
}

bool DateTimeFromString::dddd_parsed()
{
  if(_current.size()>0) {
    for(int i=1;i<=7; i++) {
      QString n=_locale.dayName(i, QLocale::LongFormat);
      if(_current.beginWith(n)) {
        _dayLong=n;
        _current+=n.count();
        return true;
      }
    }
    App::log(tr("expected a valid long day name: '%1'\n").arg(_current.left(7).toString()));
    return false;
  } else {
    return truncatedError();
  }
}

bool DateTimeFromString::M_parsed()
{
  if(_current.size()>=1) {
    if(_current[0].isDigit() || _current[0].isSpace()) {
      if(_current.size()>=2 && _current[1].isDigit()) {
        _month=_locale.toInt(_current.left(2).toString());
        if(_month>=1 && _month<=12) {
          _current+=2;
          return true;
        }
      } else {
        _month=_locale.toInt(_current.left(1).toString());
        if(_month>=1) {
          _current+=1;
          return true;
        }
      }
    }
    App::log(tr("expected a number between 1 and 12: '%1'\n").arg(_current.left(2).toString()));
    return false;
  } else {
    return truncatedError();
  }
}

bool DateTimeFromString::MM_parsed()
{
  if(_current.size()>=1) {
    int l=1;
    if(_current[0].isDigit()) {
      if(_current.size()>=2 && _current[1].isDigit()) {
        l=2;
      }
      _month=_locale.toInt(_current.left(l).toString());
      if(_month>=1 && _month<=12) {
        _current+=l;
        return true;
      }
    }
    App::log(tr("expected a number between 1 and 12: '%1'\n").arg(_current.left(l).toString()));
    return false;
  } else {
    return truncatedError();
  }
}

bool DateTimeFromString::MMM_parsed()
{
  if(_current.size()>0) {
    for(int i=1;i<=12; i++) {
      QString n=_locale.monthName(i, QLocale::ShortFormat);
      if(_current.beginWith(n)) {
        _month=i;
        _current+=n.count();
        return true;
      }
    }
    App::log(tr("expected a valid short month name: '%1'\n").arg(_current.left(3).toString()));
    return false;
  } else {
    return truncatedError();
  }
}

bool DateTimeFromString::MMMM_parsed()
{
  if(_current.size()>0) {
    for(int i=1;i<=12; i++) {
      QString n=_locale.monthName(i, QLocale::LongFormat);
      if(_current.beginWith(n)) {
        _month=i;
        _current+=n.count();
        return true;
      }
    }
    App::log(tr("expected a valid long month name: '%1'\n").arg(_current.left(12).toString()));
    return false;
  } else {
    return truncatedError();
  }
}

bool DateTimeFromString::yy_parsed()
{
  if(_current.size()>=1) {
    int l=1;
    if(_current[0].isDigit()) {
      if(_current.size()>=2 && _current[1].isDigit()) {
        l=2;
      }
      _year=(QDate::currentDate().year()/100)*100+_locale.toInt(_current.left(l).toString());
      _current+=l;
      return true;
    }
    App::log(tr("expected a number between 0 and 99: '%1'\n").arg(_current.left(l).toString()));
    return false;
  } else {
    return truncatedError();
  }
}

bool DateTimeFromString::yyyy_parsed()
{
  if(_current.size()>=1) {
    int l=1;
    if(_current[0].isDigit()) {
      if(_current.size()>=2 && _current[1].isDigit()) {
        if(_current.size()>=3 && _current[2].isDigit()) {
          if(_current.size()>=4 && _current[3].isDigit()) {
            l=4;
          } else {
            l=3;
          }
        } else {
          l=2;
        }
      }
      _year=_locale.toInt(_current.left(l).toString());
      _current+=l;
      return true;
    }
    App::log(tr("expected a number between 0 and 9999: '%1'\n").arg(_current.left(l).toString()));
    return false;
  } else {
    return truncatedError();
  }
}

bool DateTimeFromString::h_parsed()
{
  if(_current.size()>=1) {
    if(_current[0].isDigit() || _current[0].isSpace()) {
      if(_current.size()>=2 && _current[1].isDigit()) {
        _hour=_locale.toInt(_current.left(2).toString());
        if(_hour<=23) {
          _current+=2;
          return true;
        }
      } else {
        _hour=_locale.toInt(_current.left(1).toString());
        _current+=1;
        return true;
      }
    }
    App::log(tr("expected a number between 0 and 23: '%1'\n").arg(_current.left(2).toString()) );
    return false;
  } else {
    return truncatedError();
  }
}

bool DateTimeFromString::hh_parsed()
{
  if(_current.size()>=1) {
    int l=1;
    if(_current[0].isDigit()) {
      if(_current.size()>=2 && _current[1].isDigit()) {
        l=2;
      }
      _hour=_locale.toInt(_current.left(l).toString());
      if(_hour<=23) {
        _current+=l;
        return true;
      }
    }
    App::log(tr("expected a number between 0 and 23: '%1'\n").arg(_current.left(l).toString()) );
    return false;
  } else {
    return truncatedError();
  }
}

bool DateTimeFromString::H_parsed()
{
  if(_current.size()>=1) {
    if(_current[0].isDigit() || _current[0].isSpace()) {
      if(_current.size()>=2 && _current[1].isDigit()) {
        _hour=_locale.toInt(_current.left(2).toString());
        if(_hour<=12) {
          _current+=2;
          return true;
        }
      } else {
        _hour=_locale.toInt(_current.left(1).toString());
        _current+=1;
        return true;
      }
    }
    App::log(tr("expected a number between 0 and 12: '%1'\n").arg(_current.left(2).toString()) );
    return false;
  } else {
    return truncatedError();
  }
}

bool DateTimeFromString::HH_parsed()
{
  if(_current.size()>=1) {
    int l=1;
    if(_current[0].isDigit()) {
      if(_current.size()>=2 && _current[1].isDigit()) {
        l=2;
      }
      _hour=_locale.toInt(_current.left(l).toString());
      if(_hour<=12) {
        _current+=l;
        return true;
      }
    }
    App::log(tr("expected a number between 0 and 12: '%1'\n").arg(_current.left(l).toString()) );
    return false;
  } else {
    return truncatedError();
  }
}

bool DateTimeFromString::m_parsed()
{
  if(_current.size()>=1) {
    if(_current[0].isDigit() || _current[0].isSpace()) {
      if(_current.size()>=2 && _current[1].isDigit()) {
        _minute=_locale.toInt(_current.left(2).toString());
        if(_minute<=59) {
          _current+=2;
          return true;
        }
      } else {
        _minute=_locale.toInt(_current.left(1).toString());
        _current+=1;
        return true;
      }
    }
    App::log(tr("expected a number between 0 and 59: '%1'\n").arg(_current.left(2).toString()) );
    return false;
  } else {
    return truncatedError();
  }
}

bool DateTimeFromString::mm_parsed()
{
  if(_current.size()>=1) {
    int l=1;
    if(_current[0].isDigit()) {
      if(_current.size()>=2 && _current[1].isDigit()) {
        l=2;
      }
      _minute=_locale.toInt(_current.left(l).toString());
      if(_minute<=59) {
        _current+=l;
        return true;
      }
    }
    App::log(tr("expected a number between 0 and 59: '%1'\n").arg(_current.left(l).toString()) );
    return false;
  } else {
    return truncatedError();
  }
}

bool DateTimeFromString::s_parsed()
{
  if(_current.size()>=1) {
    if(_current[0].isDigit() || _current[0].isSpace()) {
      if(_current.size()>=2 && _current[1].isDigit()) {
        _second=_locale.toInt(_current.left(2).toString());
        if(_second<=59) {
          _current+=2;
          return true;
        }
      } else {
        _second=_locale.toInt(_current.left(1).toString());
        _current+=1;
        return true;
      }
    }
    App::log(tr("expected a number between 0 and 59: '%1'\n").arg(_current.left(2).toString()) );
    return false;
  } else {
    return truncatedError();
  }
}

bool DateTimeFromString::ss_parsed()
{
  if(_current.size()>=1) {
    int l=1;
    if(_current[0].isDigit()) {
      if(_current.size()>=2 && _current[1].isDigit()) {
        l=2;
      }
      _second=_locale.toInt(_current.left(l).toString());
      if(_second<=59) {
        _current+=l;
        return true;
      }
    }
    App::log(tr("expected a number between 0 and 59: '%1'\n").arg(_current.left(l).toString()) );
    return false;
  } else {
    return truncatedError();
  }
}

bool DateTimeFromString::sz_parsed()
{
  if(_current.size()>=1) {
    if(_current[0].isDigit() || _current[0].isSpace()) {
      int i;
      if(_current.size()>=2 && _current[1].isDigit()) {
        i=2;
      } else {
        i=1;
      }
      _second=_locale.toInt(_current.left(i).toString());
      _current+=i;

      if(_current.size()>=1 && (_current[0]=='.' || _current[0]==',')) {
        for(i=1; i<_current.size() && _current[i].isDigit(); i++) {}
        _fraction=_locale.toDouble(_current.left(i).toString());
        _current+=i;
      }
      return true;
    }
    App::log(tr("expected a decimal number between 0 and 60 (excluding 60): '%1'\n").arg(_current.left(10).toString()) );
    return false;
  } else {
    return truncatedError();
  }
}

bool DateTimeFromString::ssz_parsed()
{
  if(_current.size()>=1) {
    int l=1;
    if(_current[0].isDigit()) {
      if(_current.size()>=2 && _current[1].isDigit()) {
        l=2;
      }
      _second=_locale.toInt(_current.left(l).toString());
      _current+=l;
      if(_current.size()>=1 && (_current[0]=='.' || _current[0]==',')) {
        int i;
        for(i=1; i<_current.size() && _current[i].isDigit(); i++) {}
        _fraction=_locale.toDouble(_current.left(i).toString());
        _current+=i;
      }
      return true;
    }
    App::log(tr("expected a decimal number between 0 and 60 (excluding 60): '%1'\n").arg(_current.left(10).toString()));
    return false;
  } else {
    return truncatedError();
  }
}

bool DateTimeFromString::z_parsed()
{
  if(_current.size()>=1) {
    if(_current[0].isDigit() || _current[0].isSpace()) {
      if(_current.size()>=2 && _current[1].isDigit()) {
        if(_current.size()>=3 && _current[2].isDigit()) {
          _fraction=_locale.toInt(_current.left(3).toString())*0.001;
          _current+=3;
        } else {
          _fraction=_locale.toInt(_current.left(2).toString())*0.001;
          _current+=2;
        }
      } else {
        _fraction=_locale.toInt(_current.left(1).toString())*0.001;
        _current+=1;
      }
      return true;
    }
    App::log(tr("expected a number between 0 and 999: '%1'\n").arg(_current.left(3).toString()));
    return false;
  } else {
    return truncatedError();
  }
}

bool DateTimeFromString::zzz_parsed()
{
  if(_current.size()>=3) {
    if(_current[0].isDigit() &&
       _current[1].isDigit() &&
       _current[2].isDigit()) {
      _fraction=_locale.toInt(_current.left(3).toString())*0.001;
      _current+=3;
      return true;
    }
    App::log(tr("expected a number between 000 and 999: '%1'\n").arg(_current.left(3).toString()));
    return false;
  } else {
    return truncatedError();
  }
}

bool DateTimeFromString::ap_parsed()
{
  if(_current.size()>=2) {
    if(_current.beginWith("am")) {
      _ampm=AM;
      _current+=2;
      return true;
    } else if(_current.beginWith("pm")) {
      _ampm=PM;
      _current+=2;
      return true;
    }
    App::log(tr("expected am or pm: '%1'\n").arg(_current.left(2).toString()));
    return false;
  } else {
    return truncatedError();
  }
}

bool DateTimeFromString::AP_parsed()
{
  if(_current.size()>=2) {
    if(_current.beginWith("AM")) {
      _ampm=AM;
      _current+=2;
      return true;
    } else if(_current.beginWith("PM")) {
      _ampm=PM;
      _current+=2;
      return true;
    }
    App::log(tr("expected AM or PM: '%1'\n").arg(_current.left(2).toString()));
    return false;
  } else {
    return truncatedError();
  }
}

bool DateTimeFromString::quote_parsed(const StringSection& text)
{
  QRegExp reg(text.toString());
  if(reg.indexIn(_current.toString())==0) {
    _current+=reg.matchedLength();
    return true;
  }
  App::log(tr("expected '%1'\n").arg(text.toString()));
  return false;
}

bool DateTimeFromString::other_parsed(const QChar& c)
{
  if(_current.size()>=1) {
    if(c!=_current[0]) {
      App::log(tr("wrong character: '%1'\n").arg(_current.left(1).toString()));
      return false;
    } else {
      _current+=1;
      return true;
    }
  } else {
    return truncatedError();
  }
}

class DateTimePrecision: public DateTimeParser
{
  TRANSLATIONS("DateTimePrecision")
public:
  DateTimePrecision(int precision);

  int precision() const {return _precision;}
protected:
  virtual bool C_parsed() {return true;}
  virtual bool d_parsed() {return true;}
  virtual bool dd_parsed() {return true;}
  virtual bool ddd_parsed() {return true;}
  virtual bool dddd_parsed() {return true;}
  virtual bool M_parsed() {return true;}
  virtual bool MM_parsed() {return true;}
  virtual bool MMM_parsed() {return true;}
  virtual bool MMMM_parsed() {return true;}
  virtual bool yy_parsed() {return true;}
  virtual bool yyyy_parsed() {return true;}
  virtual bool h_parsed() {return true;}
  virtual bool hh_parsed() {return true;}
  virtual bool H_parsed() {return true;}
  virtual bool HH_parsed() {return true;}
  virtual bool m_parsed() {return true;}
  virtual bool mm_parsed() {return true;}
  virtual bool s_parsed() {return true;}
  virtual bool ss_parsed() {return true;}
  virtual bool sz_parsed() {return true;}
  virtual bool ssz_parsed() {return true;}
  virtual bool z_parsed() {return setPrecision(3);}
  virtual bool zzz_parsed() {return setPrecision(3);}
  virtual bool ap_parsed() {return true;}
  virtual bool AP_parsed() {return true;}
  virtual bool quote_parsed(const StringSection&) {return true;}
  virtual bool other_parsed(const QChar&) {return true;}
private:
  inline bool setPrecision(int p);
  int _precision;
};

DateTimePrecision::DateTimePrecision(int precision)
  : DateTimeParser()
{
  _precision=precision;
}

bool DateTimePrecision::setPrecision(int p)
{
  if(_precision>p) {
    _precision=p;
  }
  return true;
}

class DateTimeToString: public DateTimeParser
{
  TRANSLATIONS("DateTimeToString")
public:
  DateTimeToString(const DateTimeData& t, int precision=3);

  bool parse(const QString& format);
  const QString& value() const {return _text;}
protected:
  virtual bool C_parsed() {_numberFormat="%1"; return DateTimeParser::C_parsed();}
  virtual bool d_parsed();
  virtual bool dd_parsed();
  virtual bool ddd_parsed();
  virtual bool dddd_parsed();
  virtual bool M_parsed();
  virtual bool MM_parsed();
  virtual bool MMM_parsed();
  virtual bool MMMM_parsed();
  virtual bool yy_parsed();
  virtual bool yyyy_parsed();
  virtual bool h_parsed();
  virtual bool hh_parsed();
  virtual bool H_parsed();
  virtual bool HH_parsed();
  virtual bool m_parsed();
  virtual bool mm_parsed();
  virtual bool s_parsed();
  virtual bool ss_parsed();
  virtual bool sz_parsed();
  virtual bool ssz_parsed();
  virtual bool z_parsed();
  virtual bool zzz_parsed();
  virtual bool ap_parsed();
  virtual bool AP_parsed();
  virtual bool quote_parsed(const StringSection& text);
  virtual bool other_parsed(const QChar& c);
private:
  QString _text;
  DateTimeData _t;
  int _precision;
  // Used for 0 padded conversion to text
  QString _numberFormat;
};

DateTimeToString::DateTimeToString(const DateTimeData& t, int precision)
  : DateTimeParser()
{
  _t=t;
  _precision=precision;
  _numberFormat="%L1";
  _t.roundSeconds(pow(10.0, -_precision));
}

bool DateTimeToString::parse(const QString& format)
{
  // Pre-parse format to check for z and zzz which require precision of 3
  //
  DateTimePrecision p(_precision);
  p.parse(format);
  _t.roundSeconds(pow(10.0, -p.precision()));
  return DateTimeParser::parse(format);
}

bool DateTimeToString::d_parsed()
{
  if(_t.date().isValid()) {
    _text+=_locale.toString(_t.date().day());
  } else {
    _text+="#";
  }
  return true;
}

bool DateTimeToString::dd_parsed()
{
  if(_t.date().isValid()) {
    _text+=_numberFormat.arg(_t.date().day(), 2, 10, QChar('0'));
  } else {
    _text+="##";
  }
  return true;
}

bool DateTimeToString::ddd_parsed()
{
  if(_t.date().isValid()) {
    _text+=_locale.dayName(_t.date().dayOfWeek(), QLocale::ShortFormat);
  } else {
    _text+="###";
  }
  return true;
}

bool DateTimeToString::dddd_parsed()
{
  if(_t.date().isValid()) {
    _text+=_locale.dayName(_t.date().dayOfWeek(), QLocale::LongFormat);
  } else {
    _text+="####";
  }
  return true;
}

bool DateTimeToString::M_parsed()
{
  if(_t.date().isValid()) {
    _text+=_locale.toString(_t.date().month());
  } else {
    _text+="#";
  }
  return true;
}

bool DateTimeToString::MM_parsed()
{
  if(_t.date().isValid()) {
    _text+=_numberFormat.arg(_t.date().month(), 2, 10, QChar('0'));
  } else {
    _text+="##";
  }
  return true;
}

bool DateTimeToString::MMM_parsed()
{
  if(_t.date().isValid()) {
    _text+=_locale.monthName(_t.date().month(), QLocale::ShortFormat);
  } else {
    _text+="###";
  }
  return true;
}

bool DateTimeToString::MMMM_parsed()
{
  if(_t.date().isValid()) {
    _text+=_locale.monthName(_t.date().month(), QLocale::LongFormat);
  } else {
    _text+="##";
  }
  return true;
}

bool DateTimeToString::yy_parsed()
{
  if(_t.date().isValid()) {
    int y=_t.date().year();
    int c=y/100*100;
    _text+=_numberFormat.arg(y-c, 2, 10, QChar('0'));
  } else {
    _text+="##";
  }
  return true;
}

bool DateTimeToString::yyyy_parsed()
{
  if(_t.date().isValid()) {
    int y=_t.date().year();
    if(y>=0 && y<=9999) {
      _text+=_numberFormat.arg(y, 4, 10, QChar('0'));
    }
  } else {
    _text+="####";
  }
  return true;
}

bool DateTimeToString::h_parsed()
{
  _text+=_locale.toString(_t.hour());
  return true;
}

bool DateTimeToString::hh_parsed()
{
  _text+=_numberFormat.arg(_t.hour(), 2, 10, QChar('0'));
  return true;
}

bool DateTimeToString::H_parsed()
{
  int h=_t.hour();
  if(h>12) h-=12;
  _text+=_locale.toString(h);
  return true;
}

bool DateTimeToString::HH_parsed()
{
  int h=_t.hour();
  if(h>12) h-=12;
  static const QString fmt="%L1";
  _text+=_numberFormat.arg(h, 2, 10, QChar('0'));
  return true;
}

bool DateTimeToString::m_parsed()
{
  _text+=_locale.toString(_t.minute());
  return true;
}

bool DateTimeToString::mm_parsed()
{
  _text+=_numberFormat.arg(_t.minute(), 2, 10, QChar('0'));
  return true;
}

bool DateTimeToString::s_parsed()
{
  _text+=_locale.toString(_t.second());
  return true;
}

bool DateTimeToString::ss_parsed()
{
  _text+=_numberFormat.arg(_t.second(), 2, 10, QChar('0'));
  return true;
}

bool DateTimeToString::sz_parsed()
{
  double s=_t.second()+_t.fractions();
  _text+=QLocale().toString(s, 'f', _precision);
  return true;
}

bool DateTimeToString::ssz_parsed()
{
  double s=_t.second()+_t.fractions();
  if(_precision>0) {
    _text+=_numberFormat.arg(s, 3+_precision, 'f', _precision, QChar('0'));
  } else {
    _text+=_numberFormat.arg(s, 2, 'f', 0, QChar('0'));
  }
  return true;
}

bool DateTimeToString::z_parsed()
{
  _text+=QLocale().toString(qRound(_t.fractions()*1000));
  return true;
}

bool DateTimeToString::zzz_parsed()
{
  _text+=_numberFormat.arg(qRound(_t.fractions()*1000), 3, 10, QChar('0'));
  return true;
}

bool DateTimeToString::ap_parsed()
{
  if(_t.hour()>12) {
    _text+="pm";
  } else {
    _text+="am";
  }
  return true;
}

bool DateTimeToString::AP_parsed()
{
  if(_t.hour()>12) {
    _text+="PM";
  } else {
    _text+="AM";
  }
  return true;
}

bool DateTimeToString::quote_parsed(const StringSection& text)
{
  _text+=text.toString();
  return true;
}

bool DateTimeToString::other_parsed(const QChar& c)
{
  _text+=c;
  return true;
}

bool DateTimeData::setTime(int hour, int minute, int second, double fractions)
{
  if(hour<0 || hour>23 ||
     minute<0 || minute>59 ||
     second<0 || second>59 ||
     fractions<0.0 || fractions>=1.0) {
    return false;
  }
  _seconds=3600*hour+60*minute+second;
  _fractions=fractions;
  return true;
}

bool DateTimeData::setTime(const QTime& t)
{
  if(!t.isValid()) {
    return false;
  }
  _seconds=3600*t.hour()+60*t.minute()+t.second();
  _fractions=1e-3*t.msec();
  return true;
}

/*!
  Add \a s seconds. \s can have have any size without any loss of precision.
*/
void DateTimeData::addSeconds(double s)
{
  double d;
  if(s<0.0) {
    if(s<=-86400.0) {
      d=ceil(s/86400.0);
      s-=d*86400.0;
    } else {
      d=0.0;
    }
    double sr=ceil(s);
    _seconds+=static_cast<int>(sr);
    s-=sr;
    _fractions+=s;
    if(_fractions<0.0) {
      _fractions+=1.0;
      _seconds--;
    }
    if(_seconds<0) {
      d--;
      _seconds+=86400;
    }
  } else {
    if(s>=86400.0) {
      d=floor(s/86400.0);
      s-=d*86400.0;
    } else {
      d=0.0;
    }
    double sr=floor(s);
    _seconds+=static_cast<int>(sr);
    s-=sr;
    _fractions+=s;
    if(_fractions>=1.0) {
      _fractions-=1.0;
      _seconds++;
    }
    if(_seconds>=86400) {
      d++;
      _seconds-=86400;
    }
  }
  _date=_date.addDays(static_cast<int>(d));
}

/*!
  Same as addSeconds() except that \a should be less than 86400 seconds.
  It saves the comparisons that prevent loss of precision.
*/
void DateTimeData::addFewSeconds(double s)
{
  _fractions+=s;
  if(s>0.0) {
    if(_fractions>=1.0) {
      _fractions-=1.0;
      _seconds++;
    }
    if(_seconds>=86400) {
      _date=_date.addDays(1);
      _seconds-=86400;
    }
  } else {
    if(_fractions<0.0) {
      _fractions+=1.0;
      _seconds--;
    }
    if(_seconds<0) {
      _date=_date.addDays(-1);
      _seconds+=86400;
    }
  }
}

void DateTimeData::addMinutes(int m)
{
  int d;
  if(m<0) {
    if(m<-60*24) {
      d=m/(60*24);
      m-=d*(60*24);
    } else {
      d=0;
    }
    _seconds+=m*60;
    if(_seconds<0) {
      d--;
      _seconds+=86400;
    }
  } else {
    if(m>60*24) {
      d=m/(60*24);
      m-=d*(60*24);
    } else {
      d=0;
    }
    _seconds+=m*60;
    if(_seconds>=86400) {
      d++;
      _seconds-=86400;
    }
  }
  _date=_date.addDays(static_cast<int>(d));
}

void DateTimeData::addHours(int h)
{
  int d;
  if(h<0) {
    if(h<-24) {
      d=h/24;
      h-=d*24;
    } else {
      d=0;
    }
    _seconds+=h*60*60;
    if(_seconds<0) {
      d--;
      _seconds+=86400;
    }
  } else {
    if(h>24) {
      d=h/24;
      h-=d*24;
    } else {
      d=0;
    }
    _seconds+=h*60*60;
    if(_seconds>=86400) {
      d++;
      _seconds-=86400;
    }
  }
  _date=_date.addDays(static_cast<int>(d));
}

void DateTimeData::fromTime_t(time_t t)
{
  QDateTime dt;
  dt.setTimeSpec(Qt::UTC);
  dt.setTime_t(t);
  _date=dt.date();
  _seconds=dt.time().hour()*3600+dt.time().minute()*60+dt.time().second();
  _fractions=0.0;
}

void DateTimeData::fromTime_t(QString t)
{
  QRegExp dec("[\\.,]");
  int decIndex=t.indexOf(dec, 0);
  if(decIndex==-1) {
    fromTime_t(t.toInt());
  } else {
    fromTime_t(t.left(decIndex).toInt());
    _fractions=t.mid(decIndex).toDouble();
  }
}

time_t DateTimeData::toTime_t() const
{
  QDateTime dt;
  dt.setDate(_date);
  int h=_seconds/3600;
  int m=_seconds/60-h*60;
  int s=_seconds-m*60-h*3600;
  dt.setTimeSpec(Qt::UTC);
  dt.setTime(QTime(h, m, s));
  return dt.toTime_t();
}

QString DateTimeData::toTime_t(int precision) const
{
  QString s;
  s+=QString::number(toTime_t());
  s+=QString::number(_fractions, 'f', precision).mid(1);
  return s;
}

bool DateTimeData::fromString(const QString & text, const QString & format)
{
  DateTimeFromString p(text);
  if(p.parse(format)) {
    bool ok;
    *this=p.value(ok);
    return ok;
  } else {
    return false;
  }
}

QString DateTimeData::toString(const QString & format, int precision) const
{
  DateTimeToString p(*this, precision);
  p.parse(format);
  return p.value();
}

double DateTimeData::secondsTo(const DateTimeData& o) const
{
  double s=0;
  s+=_date.daysTo(o._date)*86400;
  s+=o._seconds-_seconds;
  s+=o._fractions-_fractions;
  return s;
}

/*!
  Add \a secs seconds to \a dt and returns a Pitsa TIME structure.
*/
TIME DateTimeData::pitsaTime() const
{
  TRACE;
  TIME t;
  t.yr=_date.year();
  t.mo=_date.month();
  t.day=_date.day();
  t.hr=hour();
  t.mn=minute();
  t.sec=static_cast<float>(second()+_fractions);
  return t;
}

/*!
  Get the average time between this and \a o with minimal loss of precision (1e-15 seconds).
*/
void DateTimeData::average(DateTimeData& a, const DateTimeData& o) const
{
  // qint64 is large enough to contain the double range of julian days
  qint64 days2=_date.toJulianDay()+o._date.toJulianDay();
  qint64 days=days2 >> 1;
  int seconds2=86400*static_cast<int>(days2-(days << 1))+_seconds+o._seconds;
  a._seconds=seconds2 >> 1;
  a._fractions=0.5*(static_cast<double>(seconds2-(a._seconds << 1))+_fractions+o._fractions);
  if(a._fractions>=1.0) {
    a._fractions-=1.0;
    a._seconds++;
  }
  if(a._seconds>=86400) {
    a._seconds-=86400;
    days++;
  }
  a._date=QDate::fromJulianDay(days);
}

void DateTimeData::addHalfMonths(int hm)
{
  int m=hm >> 1;
  addMonths(m);
  hm-=m << 1;
  int d=_date.day();
  if(hm>0) {
    if(d<15) {
      _date=_date.addDays(14);
    } else {
      _date=_date.addDays(_date.daysInMonth()-14);
    }
  } else if(hm<0) {
    if(d<15) {
      _date=_date.addDays(14-_date.daysInMonth());
    } else {
      _date=_date.addDays(-14);
    }
  }
}

/*!
  Round to second, second being a multiple of \a s
*/
void DateTimeData::roundSeconds(double s)
{
  if(s<1.0) {
    _fractions=s*round(_fractions/s);
    if(_fractions>=1.0) {
      _fractions-=1.0;
      _seconds++;
    }
  } else {
    _seconds=static_cast<int>(s*round((_seconds+_fractions)/s));
    _fractions=0.0;
  }

}

/*!
  Move to second, second being a multiple of \a s
*/
void DateTimeData::floorSeconds(double s)
{
  if(s<1.0) {
    _fractions=s*floor(_fractions/s);
  } else {
    int si=static_cast<int>(s);
    _seconds=si*(_seconds/si);
    _fractions=0.0;
  }
  if(_seconds>=86400) {
    _seconds-=86400;
    _date=_date.addDays(1);
  }
}

/*!
  Move to next second, second being a multiple of \a s
*/
void DateTimeData::ceilSeconds(double s)
{
  if(s<1.0) {
    _fractions=s*ceil(_fractions/s);
    if(_fractions>=1.0) {
      _fractions-=1.0;
      _seconds++;
    }
  } else {
    if(_fractions>0.0) {
      _fractions=0.0;
      _seconds++;
    }
    int si=static_cast<int>(s);
    int f=si*(_seconds/si);
    if(f<_seconds) {
      _seconds=si*(_seconds/si+1);
    }
  }
  if(_seconds>=86400) {
    _seconds-=86400;
    _date=_date.addDays(1);
  }
}

/*!
  Move to next midnight
*/
void DateTimeData::ceilDay()
{
  if(!isMidnight()) {
     floorDay();
     _date=_date.addDays(1);
  }
}

/*!
  Move to last Sunday at midnight
*/
void DateTimeData::floorWeek()
{
  floorDay();
  int d=_date.dayOfWeek();
  if(d<7) {
    _date=_date.addDays(-d);
  }
}

/*!
  Move to next Sunday at midnight
*/
void DateTimeData::ceilWeek()
{
  ceilDay();
  _date=_date.addDays(7-_date.dayOfWeek());
}

/*!
  Move to 1st day of month at midnight
*/
void DateTimeData::floorHalfMonth()
{
  floorDay();
  int d=_date.day();
  if(d<15) {
    _date=_date.addDays(1-_date.day());
  } else {
    _date=_date.addDays(15-_date.day());
  }
}

/*!
  Move to 1st day of next half month at midnight
*/
void DateTimeData::ceilHalfMonth()
{
  ceilDay();
  int d=_date.day();
  if(d<15) {
    if(d>1) {
      _date=_date.addDays(15-_date.day());
    }
  } else if(d>15) {
    _date=_date.addDays(1+_date.daysInMonth()-_date.day());
  }
}

/*!
  Move to 1st day of month at midnight, month being a multiple of \a m
*/
void DateTimeData::floorMonths(int m)
{
  floorDay();
  _date=_date.addDays(1-_date.day());
  int curM=_date.month()-1;
  _date=_date.addMonths((curM/m)*m-curM);
}

/*!
  Move to 1st day of next month at midnight, month being a multiple of \a m
*/
void DateTimeData::ceilMonths(int m)
{
  ceilDay();
  int d=_date.day();
  if(d>1) {
    _date=_date.addDays(1+_date.daysInMonth()-_date.day());
  }
  int curM=_date.month()-1;
  int f=m*(curM/m);
  if(f<curM) {
    _date=_date.addMonths(f+m-curM);
  }
}

/*!
  Move to 1st day of year at midnight, year being a multiple of \a y.
*/
void DateTimeData::floorYears(int y)
{
  floorDay();
  _date=_date.addDays(1-_date.dayOfYear());
  int curY=_date.year();
  _date=_date.addYears((curY/y)*y-curY);
}

/*!
  Move to 1st day of next year at midnight, year being a multiple of \a y.
*/
void DateTimeData::ceilYears(int y)
{
  ceilDay();
  int d=_date.dayOfYear();
  if(d>1) {
    _date=_date.addDays(1+_date.daysInYear()-d);
  }
  int curY=_date.year();
  int f=y*(curY/y);
  if(f<curY) {
    _date=_date.addYears(f+y-curY);
  }
}

} // namespace QGpCoreTools
