<?php
/**
 * @copyright (c) Kufer Software GmbH, Loisachstrasse 4, D-84453 Mühldorf
 */


class DateObj
{
  protected $_timeStamp;
  private $_year;
  private $_month;
  private $_day;
  protected $_hour = 0; 
  protected $_minute = 0; 
  protected $_second = 0; 

  
  public function __construct($pYear, $pMonth = 0, $pDay = 0)
  {
    $this->setDate($pYear, $pMonth, $pDay);
  }

  public function __toString()
  {
    return $this->getDate();
  }

  
  public function __clone()
  {
    return new DateObj($this->getYear(), $this->getMonth(), $this->getDay());
  }

  
  protected function _timeStampToDate()
  {
    $date = date('d-m-Y-H-i-s', $this->_timeStamp);
    $dateAry = explode('-', $date);

    $this->_day = (int)$dateAry[0];
    $this->_month = (int)$dateAry[1];
    $this->_year = (int)$dateAry[2];
    $this->_hour = (int)$dateAry[3];
    $this->_minute = (int)$dateAry[4];
    $this->_second = (int)$dateAry[5];
  }

  
  protected function _dateToTimeStamp()
  {
    $this->_timeStamp = mktime($this->_hour, $this->_minute, $this->_second, $this->_month, $this->_day, $this->_year);
  }

  
  protected function _lpad($pStr, $pStellen = 2)
  {
    if ($pStellen > 1) {
      for ($i = StringOperations::strlen($pStr); $i < $pStellen; $i++) {
        $pStr = '0' . $pStr;
      }
    }

    return $pStr;
  }

  
  private static $_MonthDaysMap = array(1 => 31, 2 => 28, 3 => 31, 4 => 30, 5 => 31, 6 => 30, 7 => 31,
                                         8 => 31, 9 => 30, 10 => 31, 11 => 30, 12 => 31);

  
  protected function _getDaysOfMonth($pMonth = 0, $pYear = 0)
  {
    if (empty($pMonth)) {
      $pMonth = $this->_month;
    }
    if (empty($pYear)) {
      $pYear = $this->_year;
    }

    if ($pMonth == 2 && $this->isLeapYear($pYear)) {
      return 29;
    }

    return self::$_MonthDaysMap[(int)$pMonth];
  }

  
  protected function _isDateValid($pYear, $pMonth, $pDay)
  {
    if ((! ($pYear >= 1000 && $pYear <= 9999))
       || (! ($pMonth >= 1 && $pMonth <= 12))
       || (! ($pDay >= 1 && $pDay <= $this->_getDaysOfMonth($pMonth, $pYear)))
    ) {
      return false;
    }

    return true;
  }

  

  
  public function setYear($pYear)
  {
    if ($this->_day == 29 && $this->_month == 2 && !$this->isLeapYear($pYear)) {
      throw new InvalidArgumentExceptionExtended('pYear', $pYear, 'ungültiges Datum: Schalttag an einem Nicht-Schaltjahr');
    }
    if ($pYear <= 999 || $pYear >= 9999) {
      throw new InvalidArgumentExceptionExtended('pYear', $pYear, 'ungültiges Datum: Jahr muss vierstellig sein');
    }
    $this->_year = (int)$pYear;
    $this->_dateToTimeStamp();

    return $this;
  }

  
  public function setMonth($pMonth)
  {
    if ($pMonth < 1 || $pMonth > 12) {
      throw new InvalidArgumentExceptionExtended('pMonth', $pMonth, 'ungültiges Datum: Monate außerhalb vom Gültigkeitsbereich');
    }
    if ($this->_day > $this->_getDaysOfMonth($pMonth)) {
      throw new InvalidArgumentExceptionExtended('pMonth', $pMonth, 'ungültiges Datum: Tage außerhalb vom Gültigkeitsbereich');
    }
    $this->_month = (int)$pMonth;
    $this->_dateToTimeStamp();

    return $this;
  }

  
  public function setDay($pDay)
  {
    if ($pDay < 1 || $pDay > $this->_getDaysOfMonth()) {
      throw new InvalidArgumentExceptionExtended('pDay', $pDay, 'ungültiges Datum: Tage außerhalb vom Gültigkeitsbereich');
    }
    $this->_day = (int)$pDay;
    $this->_dateToTimeStamp();

    return $this;
  }

  
  public function setDayToLastDayInMonth()
  {
    $this->addMonths(1);
    $this->setDay(1);
    $this->addDays(-1);

    return $this;
  }

  
  public function setTimeStamp($timeStamp)
  {
    $this->_timeStamp = $timeStamp;
    $this->_timeStampToDate();

    
    $this->_hour = 0;
    $this->_minute = 0;
    $this->_second = 0;
    $this->_dateToTimeStamp();

    return $this;
  }

  
  public function setDate($pYear, $pMonth = 0, $pDay = 0)
  {
    $tmpDay = $tmpMonth = $tmpYear = 0;

    
    
    if (StringOperations::strpos($pYear, '.') !== false) {
      
      if (preg_match('/([0-9]+)\\.([0-9]+)\\.([0-9]*)/', $pYear, $match)) {
        $tmpDay = (int)$match[1];
        $tmpMonth = (int)$match[2];
        $tmpYear = (int)$match[3];

        if (empty($tmpYear)) {
          if (empty($this->_year)) {
            $tmpYear = StringOperations::substr(DateTimeOperations::getDateNow(), 6, 4);
          } else {
            $tmpYear = $this->_year;
          }
        } elseif ($tmpYear <= 999) {
          if (empty($this->_year)) {
            $tmpYear += 2000;
          } else {
            $tmpYear += StringOperations::substr($this->_year, 0, 2) * 100;
          }
        }
      }
    } elseif ($pYear > 9999) {
      
      $this->setTimeStamp($pYear);

      return $this;
    } else {
      $tmpYear = $pYear;
      $tmpMonth = $pMonth;
      $tmpDay = $pDay;
    }

    if ($this->_isDateValid($tmpYear, $tmpMonth, $tmpDay)) {
      $this->_year = (int)$tmpYear;
      $this->_month = (int)$tmpMonth;
      $this->_day = (int)$tmpDay;
      $this->_dateToTimeStamp();
    } else {
      throw new Exception('ungültiges Datum: Datum nicht korrekt gesetzt.');
    }

    return $this;
  }

  
  public function addYears($pYear)
  {
    $this->_year += $pYear;
    if ($this->_day == 29 && $this->_month && !$this->isLeapYear()) {
      $this->_day = 28;
    }

    $this->_dateToTimeStamp();

    return $this;
  }

  
  public function addMonths($pMonth)
  {
    
    

    $tmpMonth = $this->_month + $pMonth;
    $this->_month = ($tmpMonth - 1) % 12 + 1;
    
    if ($this->_month < 1) {
      $this->_month = 12 - $this->_month;
    }

    if ($tmpMonth > 12 || $tmpMonth < 1) {
      if ($tmpMonth < 1) {
        
        
        $tmpMonth -= 11;
      }
      $this->_year += floor(($tmpMonth - 1) / 12);

      
      $daysOfMonth = $this->_getDaysOfMonth();
      if ($this->_day > $daysOfMonth) {
        $this->_day = $daysOfMonth;
      }
    }
    $this->_dateToTimeStamp();

    return $this;
  }

  
  public function addDays($pDay)
  {
    $this->_day += $pDay;
    $this->_dateToTimeStamp();
    $this->_timeStampToDate(); 

    return $this;
  }

  

  public static function getNow()
  {
    return new DateObj(DateTimeOperations::getDateNow());
  }

  public static function getDefault()
  {
    return new DateObj(DEFAULTDATE);
  }

  
  public function getDateFmt($pFormat)
  {
    return \DateTimeOperations::datefmt($pFormat, $this->_timeStamp);
  }

  
  public function getYear($p2Stellig = false)
  {
    if ($p2Stellig) {
      return StringOperations::substr($this->_year, -2);
    }

    return $this->_year;
  }

  
  public function getMonth($p2Stellig = true)
  {
    if ($p2Stellig) {
      return $this->_lpad($this->_month, 2);
    }

    return $this->_month;
  }

  
  public static $_monthMap = array(1 => 'Januar', 2 => 'Februar', 3 => 'März', 4 => 'April', 5 => 'Mai', 6 => 'Juni',
                                       7 => 'Juli', 8 => 'August', 9 => 'September', 10 => 'Oktober', 11 => 'November', 12 => 'Dezember');
  
  public static $_monthMapShort = array(1 => 'Jan.', 2 => 'Feb.', 3 => 'Mär.', 4 => 'Apr.', 5 => 'Mai', 6 => 'Jun.',
                                            7 => 'Jul.', 8 => 'Aug.', 9 => 'Sep.', 10 => 'Okt.', 11 => 'Nov.', 12 => 'Dez.');

  public static $monthMapAustria = array(1 => 'Jänner', 2 => 'Februar', 3 => 'März', 4 => 'April', 5 => 'Mai', 6 => 'Juni',
    7 => 'Juli', 8 => 'August', 9 => 'September', 10 => 'Oktober', 11 => 'November', 12 => 'Dezember');

  
  public function getMonthName($pShortForm = false)
  {
    if ($pShortForm) {
      return self::$_monthMapShort[$this->_month];
    } else {
      return self::$_monthMap[$this->_month];
    }
  }

  
  public function getMonthNameAustria()
  {
    return self::$monthMapAustria[$this->_month];
  }

  
  public function getDay($p2Stellig = true)
  {
    if ($p2Stellig) {
      return $this->_lpad($this->_day, 2);
    }

    return $this->_day;
  }

  
  public function getQuartal()
  {
    return 1 + (int)floor(($this->_month - 1) / 3);
  }

  
  public static $_weekDayMap = array(1 => 'Montag', 2 => 'Dienstag', 3 => 'Mittwoch', 4 => 'Donnerstag', 5 => 'Freitag', 6 => 'Samstag', 7 => 'Sonntag');
  
  public static $_weekDayMapShort = array(1 => 'Mo.', 2 => 'Di.', 3 => 'Mi.', 4 => 'Do.', 5 => 'Fr.', 6 => 'Sa.', 7 => 'So.');

  
  public function getWeekDayName($pShortForm = false)
  {
    if ($pShortForm) {
      return self::$_weekDayMapShort[$this->getWeekDay()];
    } else {
      return self::$_weekDayMap[$this->getWeekDay()];
    }
  }

  
  public function getWeekDay()
  {
    return (int)date('N', $this->_timeStamp);
  }

  
  public function getTimeStamp()
  {
    return $this->_timeStamp;
  }

  
  public function getDateWithoutYear()
  {
    return $this->getDay(true) . '.' . $this->getMonth(true) . '.';
  }

  
  public function getDate($pJahr2Stellig = false)
  {
    return $this->getDateWithoutYear() . $this->getYear($pJahr2Stellig);
  }

  
  public function getDateWithWeekDay($pJahr2Stellig = false, $pWochtagShortFormat = false)
  {
    return $this->getWeekDayName($pWochtagShortFormat) . ', ' . $this->getDate($pJahr2Stellig);
  }

  
  public function getKalenderWoche()
  {
    return (int)date('W', $this->_timeStamp);
  }

  

  
  public function isLeapYear($pYear = 0)
  {
    
    if (empty($pYear)) {
      $pYear = $this->_year;
    }

    if ($pYear < 1583) {
      return false;
    }
    if ($pYear % 400 == 0) {
      return true;
    }
    if ($pYear % 100 == 0) {
      return false;
    }
    if ($pYear % 4 == 0) {
      return true;
    }

    return false;
  }

  
  public function compare($pDate)
  {
    $date1 = new DateTime();
    $date1->setDate($this->getYear(), $this->getMonth(), $this->getDay());
    if ($this instanceof \DateTimeObj) {
      $date1->setTime($this->getHour(), $this->getMinute(), $this->getSecond());
    }
    $date2 = new DateTime();
    $date2->setDate($pDate->getYear(), $pDate->getMonth(), $pDate->getDay());
    if ($pDate instanceof \DateTimeObj) {
      $date2->setTime($pDate->getHour(), $pDate->getMinute(), $pDate->getSecond());
    }

    if ($date1 == $date2) {
      return 0;
    } elseif ($date1 < $date2) {
      return -1;
    } else {
      return 1;
    }
  }

  public function isDefaultDate()
  {
    return $this->_year == 1899 && $this->_month == 12 && $this->_day == 30;
  }

  public function isAfterDate(\DateObj $before): bool
  {
    if ($before instanceof \DateTimeObj) {
      return $this->isAfterDate($before->toDateObj());
    } elseif ($this instanceof \DateTimeObj) {
      return $this->toDateObj()->isAfterDate($before);
    }

    return $this->getTimeStamp() >= $before->getTimeStamp();
  }

  public function isBeforeDate(\DateObj $after): bool
  {
    return $after->isAfterDate($this);
  }

  public function isBetweenDates(\DateObj $before, \DateObj $after): bool
  {
    return $before->isBeforeDate($this) && $after->isAfterDate($this);
  }
}


class DateTimeObj extends DateObj
{
  
  public function __construct($pYear, $pMonth = 0, $pDay = 0, $pHour = 0, $pMinute = 0, $pSecond = 0)
  {
    $this->setDateTime($pYear, $pMonth, $pDay, $pHour, $pMinute, $pSecond);
  }

  public function __toString()
  {
    return $this->getDateTime();
  }

  
  public static function createFromString($pString)
  {
    if (trim($pString ?? '') == '') {
      return self::getDefault();
    }

    $regexDate = '/(\\d{2})\\.(\\d{2})\\.(\\d{4})/'; 
    $regexDateTime = '/(\\d{2})\\.(\\d{2})\\.(\\d{4}) (\\d{2}):(\\d{2}):(\\d{2})/'; 
    $regexDateTime2 = '/(\\d{2})\\.(\\d{2})\\.(\\d{4}) (\\d{2}):(\\d{2})/'; 
    $matches = array();

    $status = preg_match($regexDateTime, $pString, $matches);
    if ($status === false) {
      throw new Exception('Beim Regexmatch von ' . $pString . ' mit dem Regexpattern ' . $regexDateTime . ' ist ein Fehler aufgetreten.', 55, null);
    }
    if ($status === 1) {
      
      return new DateTimeObj($matches[3], $matches[2], $matches[1], $matches[4], $matches[5], $matches[6]);
    }

    $status = preg_match($regexDateTime2, $pString, $matches);
    if ($status === false) {
      throw new Exception('Beim Regexmatch von ' . $pString . ' mit dem Regexpattern ' . $regexDateTime2 . ' ist ein Fehler aufgetreten.', 55, null);
    }
    if ($status === 1) {
      
      return new DateTimeObj($matches[3], $matches[2], $matches[1], $matches[4], $matches[5], 0);
    }

    $status2 = preg_match($regexDate, $pString, $matches);
    if ($status2 === false) {
      throw new Exception('Beim Regexmatch von ' . $pString . ' mit dem Regexpattern ' . $regexDate . ' ist ein Fehler aufgetreten.', 55, null);
    }
    if ($status2 === 1) {
      
      return new DateTimeObj($matches[3], $matches[2], $matches[1]);
    }

    throw new Exception('Es konnte kein DateTimeObj aus dem string ' . $pString . ' erzeugt werden.', 55, null);
  }

  
  public function __clone()
  {
    return new DateTimeObj($this->getYear(), $this->getMonth(), $this->getDay(), $this->getHour(), $this->getMinute(), $this->getSecond());
  }

  

  
  public function setHour($pHour)
  {
    if ($pHour < 0 || $pHour > 23) {
      throw new InvalidArgumentExceptionExtended('pHour', $pHour, 'ungültiges Datum: Stunden außerhalb vom Gültigkeitsbereich');
    }
    $this->_hour = (int)$pHour;
    $this->_dateToTimeStamp();

    return $this;
  }

  
  public function setMinute($pMinute)
  {
    if ($pMinute < 0 || $pMinute > 59) {
      throw new InvalidArgumentExceptionExtended('pMinute', $pMinute, 'ungültiges Datum: Minuten außerhalb vom Gültigkeitsbereich');
    }
    $this->_minute = (int)$pMinute;
    $this->_dateToTimeStamp();

    return $this;
  }

  
  public function setSecond($pSecond)
  {
    if ($pSecond < 0 || $pSecond > 59) {
      throw new InvalidArgumentExceptionExtended('pSecond', $pSecond, 'ungültiges Datum: Sekunden außerhalb vom Gültigkeitsbereich');
    }
    $this->_second = (int)$pSecond;
    $this->_dateToTimeStamp();

    return $this;
  }

  
  public function setDateTime($pYear, $pMonth = 0, $pDay = 0, $pHour = 0, $pMinute = 0, $pSecond = 0)
  {
    if (StringOperations::strpos($pYear, '.')) {
      if (preg_match('/([0-9]+)\\.([0-9]+)\\.([0-9]*)( ([0-9]*):?([0-9]*):?([0-9]*))?/', $pYear, $match)) {
        $pHour = (isset($match[5])) ? (int)$match[5] : 0;
        $pMinute = (isset($match[6])) ? (int)$match[6] : 0;
        $pSecond = (isset($match[7])) ? (int)$match[7] : 0;
      }
    }
    
    $this->setTime($pHour, $pMinute, $pSecond);
    $this->setDate($pYear, $pMonth, $pDay);

    return $this;
  }

  
  public function setTime($pHour, $pMinute = 0, $pSecond = 0)
  {
    $tmpHour = $tmpMinute = $tmpSecond = 0;

    if (StringOperations::strpos($pHour, ':') !== false) {
      
      if (preg_match('/([0-9]*):?([0-9]*):?([0-9]*)/', $pHour, $match)) {
        $tmpHour = (int)$match[1];
        $tmpMinute = (int)$match[2];
        $tmpSecond = (int)$match[3];
      }
    } else {
      $tmpHour = $pHour;
      $tmpMinute = $pMinute;
      $tmpSecond = $pSecond;
    }
    if (($tmpHour >= 0 && $tmpHour < 24)
      && ($tmpMinute >= 0 && $tmpMinute < 60)
      && ($tmpSecond >= 0 && $tmpSecond < 60)
    ) {
      $this->_hour = (int)$tmpHour;
      $this->_minute = (int)$tmpMinute;
      $this->_second = (int)$tmpSecond;
      $this->_dateToTimeStamp();
    } else {
      throw new Exception('ungültiges Datum: ungültige Zeitangaben');
    }

    return $this;
  }

  
  public function setTimeStamp($timeStamp)
  {
    $this->_timeStamp = $timeStamp;
    $this->_timeStampToDate();

    return $this;
  }

  

  
  public function getHour($p2Stellig = true)
  {
    if ($p2Stellig) {
      return $this->_lpad($this->_hour, 2);
    }

    return $this->_hour;
  }

  
  public function getMinute($p2Stellig = true)
  {
    if ($p2Stellig) {
      return $this->_lpad($this->_minute, 2);
    }

    return $this->_minute;
  }

  
  public function getSecond($p2Stellig = true)
  {
    if ($p2Stellig) {
      return $this->_lpad($this->_second, 2);
    }

    return $this->_second;
  }

  
  public function addHours($pHour)
  {
    $this->_timeStamp += $pHour * 3600;
    $this->_timeStampToDate();

    return $this;
  }

  
  public function addMinutes($pMinute)
  {
    $this->_timeStamp += $pMinute * 60;
    $this->_timeStampToDate();

    return $this;
  }

  
  public function addSeconds($pSecond)
  {
    $this->_timeStamp += $pSecond;
    $this->_timeStampToDate();

    return $this;
  }

  

  public static function getNow()
  {
    return new DateTimeObj(DateTimeOperations::getDateTimeNow());
  }

  public static function getDefault()
  {
    return new DateTimeObj(DEFAULTDATE);
  }

  
  public function getTime($pMitSekunden = true, $pStunde2Stellig = true)
  {
    if ($pStunde2Stellig) {
      $hour = $this->_lpad($this->_hour, 2);
    } else {
      $hour = $this->_hour;
    }

    $time = $hour . ':' . $this->_lpad($this->_minute);

    if ($pMitSekunden) {
      $time .= ':' . $this->_lpad($this->_second);
    }

    return $time;
  }

  
  public function getDateTime($pMitSekunden = true, $pStunde2Stellig = true)
  {
    return $this->getDate() . ' ' . $this->getTime($pMitSekunden, $pStunde2Stellig);
  }

  
  public function stringFormatDateTime($pFormat)
  {
    $dateTime = new DateTime();
    $dateTime->setTimestamp($this->_timeStamp);

    return $dateTime->format($pFormat);
  }

  public function isDefaultDate()
  {
    return parent::isDefaultDate() && $this->_hour == 0 && $this->_minute == 0 && $this->_second == 0;
  }

  public function isAfterDateTime(\DateTimeObj $before): bool
  {
    return $this->getTimeStamp() >= $before->getTimeStamp();
  }

  public function isBeforeDateTime(\DateTimeObj $after): bool
  {
    return $after->isAfterDateTime($this);
  }

  public function isBetweenDateTimes(\DateTimeObj $before, \DateTimeObj $after): bool
  {
    return $before->isBeforeDateTime($this) && $after->isAfterDateTime($this);
  }

  public function toDateObj(): \DateObj
  {
    return new \DateObj($this->getYear(), $this->getMonth(), $this->getDay());
  }
}