<?php
/**
 * @copyright (c) Kufer Software GmbH, Oderstraße 7, D-84453 Mühldorf
 */


class DateTimeOperations
{
  public const DAY_IN_SECONDS = 86400; 

  public const MONDAY = 'montag';
  public const TUESDAY = 'dienstag';
  public const WEDNESDAY = 'mittwoch';
  public const THURSDAY = 'donnerstag';
  public const FRIDAY = 'freitag';
  public const SATURDAY = 'samstag';
  public const SUNDAY = 'sonntag';

  public const DATETIME_FORMAT_FULL = 'd.m.Y H:i:s';
  public const DATETIME_FORMAT_NO_SECONDS = 'd.m.Y H:i';

  
  public $dtopserver;

  
  public function __construct($pDBTyp = '--')
  {
    if ($pDBTyp === '--') {
      $connectionInfo = DBConnectionInfo::getInstance();
      $pDBTyp = $connectionInfo->db_typ;
    }
    switch ($pDBTyp) {
      case 'MYSQL':
      case 'MYSQLI':
        $this->dtopserver = new DateTimeOperationsMySQLi();
        break;
      case 'MSSQL':
        if (ini_get('mssql.datetimeconvert') == '') {
          $this->dtopserver = new DateTimeOperationsMSSQLODBC();
        } else {
          $this->dtopserver = new DateTimeOperationsMSSQL();
        }
        break;
      case 'SQLSRV':
        if (ini_get('mssql.datetimeconvert') == '') {
          $this->dtopserver = new DateTimeOperationsMSSQLODBC();
        } else {
          $this->dtopserver = new DateTimeOperationsSQLSRV();
        }
        break;
      case 'MSSQLODBC':
        $this->dtopserver = new DateTimeOperationsMSSQLODBC();
        break;
      case 'ORACLE':
        $this->dtopserver = new DateTimeOperationsOracle();
        break;
    }
  }

  
  public static function getTimestampNow()
  {
    return time(); 
  }

  
  public function getDateStr($datetimevalue)
  {
    $tmpstr = $this->dtopserver->getDateStr($datetimevalue);
    if (!(($tmpstr == '00.00.0000') or ($tmpstr == '..') or ($tmpstr == DEFAULTDATE))) {
      return $tmpstr;
    } else {
      return '';
    }
  }

  
  public function getShortDateStr($datetimevalue)
  {
    $tmpstr = $this->dtopserver->getShortDateStr($datetimevalue);
    if (!(($tmpstr == '00.00.0000') or ($tmpstr == DEFAULTDATE))) {
      return $tmpstr;
    } else {
      return '';
    }
  }

  
  public static function getShortForm4DateStr($date)
  {
    $datum = $date;
    $dat_kurz = \StringOperations::str_split($datum);
    array_splice($dat_kurz, 6, 2);

    return implode('', $dat_kurz);
  }

  
  public function getFormatDate4Server($date)
  {
    if ($date == null) {
      return $this->dtopserver->getFormatDate4Server(30, 12, 1899);
    } 

    $day = $this->getDateTimePart2Digits(intval(StringOperations::substr($date, 0, 2)));
    $month = $this->getDateTimePart2Digits(intval(StringOperations::substr($date, 3, 2)));
    $year = \StringOperations::pad(intval(StringOperations::substr($date, 6, 4)), 4, '0', STR_PAD_LEFT);

    return $this->dtopserver->getFormatDate4Server($day, $month, $year);
  }

  
  public function getFormatTime4Server($time, $day = 30, $month = 12, $year = 1899)
  {
    $hour = StringOperations::substr($time, 0, 2);
    $minute = StringOperations::substr($time, 3, 2);

    return $this->dtopserver->getFormatTime4Server($minute, $hour, $day, $month, $year);
  }

  
  public static function getTimeStr($datetimevalue, bool $seconds = false)
  {
    if ($seconds) {
      $result = \StringOperations::substr($datetimevalue, 11, 7);
      if (\StringOperations::strlen($result) === 5) {
        return $result . ':00';
      } else {
        return $result;
      }
    } else {
      return \StringOperations::substr($datetimevalue, 11, 5);
    }
  }

  
  public static function getDateFromDatetimeStr($datetimevalue)
  {
    return StringOperations::substr($datetimevalue, 0, 10);
  }

  
  public function getDateTimeStr($dateTime)
  {
    return $this->dtopserver->getDateTimeStr($dateTime);
  }

  
  public function getEmptyStringWhenDefaultDateTime($pDateTime)
  {
    if ($pDateTime === DEFAULTDATE . ' ' . DEFAULTTIME || $pDateTime === DEFAULTDATE . ' ' . DEFAULTTIME_WITH_SECONDS) {
      return '';
    } else {
      return $pDateTime;
    }
  }

  
  public function getFormatDateTime4Server($dateTimeStr)
  {
    if ($dateTimeStr == null || trim($dateTimeStr) == '') {
      return $this->dtopserver->getFormatTime4Server('00', '00', 30, 12, 1899, 0);
    }
    $day = StringOperations::substr($dateTimeStr, 0, 2);
    $month = StringOperations::substr($dateTimeStr, 3, 2);
    $year = StringOperations::substr($dateTimeStr, 6, 4);


    $hour = StringOperations::substr($dateTimeStr, 11, 2) !== '' ? StringOperations::substr($dateTimeStr, 11, 2) : '00';
    $minute = StringOperations::substr($dateTimeStr, 14, 2) !== '' ? StringOperations::substr($dateTimeStr, 14, 2) : '00';
    $second = 0;
    if (StringOperations::strlen($dateTimeStr) > 17) {
      $second = StringOperations::substr($dateTimeStr, 17, 2);
    }

    return $this->dtopserver->getFormatTime4Server($minute, $hour, $day, $month, $year, $second);
  }

  
  public function getFormateDateTime4ServerNow()
  {
    $nowtime = DateTimeOperations::getTimestampNow();
    $nowdate = getdate($nowtime);

    return $this->getFormatDateTime4Server($this->getDateTimePart2Digits($nowdate['mday']) . '.'
                    . $this->getDateTimePart2Digits($nowdate['mon']) . '.' . $nowdate['year'] . ' '
                    . $this->getDateTimePart2Digits($nowdate['hours']) . ':' . $this->getDateTimePart2Digits($nowdate['minutes']));
  }

  
  public function getFormateTime4ServerNow()
  {
    $nowtime = DateTimeOperations::getTimestampNow();
    $nowdate = getdate($nowtime);

    return $this->getFormatTime4Server($this->getDateTimePart2Digits($nowdate['hours']) . ':' . $this->getDateTimePart2Digits($nowdate['minutes']));
  }

  
  public function getFormateDate4ServerNow()
  {
    $nowtime = DateTimeOperations::getTimestampNow();
    $nowdate = getdate($nowtime);

    return $this->getFormatDate4Server($this->getDateTimePart2Digits($nowdate['mday']) . '.' . $this->getDateTimePart2Digits($nowdate['mon']) . '.' . $nowdate['year']);
  }

  
  public function getInvalidDate4Server()
  {
    return $this->dtopserver->getInvalidDate4Server();
  }

  
  public function getGueltigPart($praeffix, $pVonColumn, $pToColumn)
  {
    return ' ((' . $this->getFormateDate4ServerNow() . ' >= ' 
            . $praeffix . $pVonColumn . ' AND ' . $this->getFormateDate4ServerNow() . ' <= ' . $praeffix . $pToColumn
            . ') or (' . $this->getFormateDate4ServerNow() . ' >= ' 
            . $praeffix . $pVonColumn . ' and (' . $praeffix . $pToColumn . ' is null or '
            . $praeffix . $pToColumn . ' = '
            . $this->getFormatDate4Server(DEFAULTDATE) . ')) or (('
            . $praeffix . $pVonColumn . ' is null or ' . $praeffix . $pVonColumn . ' = ' 
            . $this->getFormatDate4Server(DEFAULTDATE) . ') and '
            . $praeffix . $pToColumn . ' >= '
            . $this->getFormateDate4ServerNow() . ') or ((' . $praeffix . $pVonColumn
            . ' is null or ' . $praeffix . $pVonColumn . ' = ' 
            . $this->getFormatDate4Server(DEFAULTDATE) . ') and ('
            . $praeffix . $pToColumn . ' is null or ' . $praeffix . $pToColumn . ' = '
            . $this->getFormatDate4Server(DEFAULTDATE) . ')))';
  }

  
  public static function convertTimeToMinutes($pTime)
  {
    $timeAry = explode(':', $pTime);

    if (isset($timeAry[1])) {
      $minutes = \intval($timeAry[0]) * 60 + \intval($timeAry[1]);
    } else {
      $minutes = 0;
    }

    return $minutes;
  }

  
  public static function convertMinutesToTime(int $pMinutes, bool $fill = true): string
  {
    $minutes = $pMinutes % 60;
    $hours = ($pMinutes - $minutes) / 60;

    if (!$fill) {
      return "$hours:$minutes";
    } else {
      $minutes = \strval($minutes);
      $hours = \strval($hours);

      return \str_repeat('0', \max(2 - \StringOperations::strlen($hours), 0)) . $hours . ':'
          . \str_repeat('0', \max(2 - \StringOperations::strlen($minutes), 0)) . $minutes;
    }
  }

  
  public static function getNextDate($eindat, $step, $pShowJahr = true)
  {
    
    $eintime = DateTimeOperations::getTimestampByDatestring($eindat);
    
    $newtime = $eintime + $step * self::DAY_IN_SECONDS;
    $newdate = getdate($newtime);
    $datestring = DateTimeOperations::getDateTimePart2Digits($newdate['mday']) . '.' . DateTimeOperations::getDateTimePart2Digits($newdate['mon']) . '.';
    if ($pShowJahr) {
      $datestring .= $newdate['year'];
    }

    return $datestring;
  }

  
  public static function getDateDaysBefore($pAusgangsdatum, $pTageZurueck)
  {
    return DateTimeOperations::getNextDate($pAusgangsdatum, -$pTageZurueck);
  }

  
  public static function getDateNow()
  {
    $nowtime = DateTimeOperations::getTimestampNow();
    $nowdate = getdate($nowtime);

    return DateTimeOperations::getDateTimePart2Digits($nowdate['mday']) . '.' . DateTimeOperations::getDateTimePart2Digits($nowdate['mon']) . '.' . $nowdate['year'];
  }

  
  public static function getTimeNow()
  {
    $nowtime = DateTimeOperations::getTimestampNow();
    $nowdate = getdate($nowtime);

    return DateTimeOperations::getDateTimePart2Digits($nowdate['hours']) . ':' . DateTimeOperations::getDateTimePart2Digits($nowdate['minutes']);
  }

  
  public static function getDateTimeNow()
  {
    return DateTimeOperations::getDateNow() . ' ' . DateTimeOperations::getTimeNow();
  }

  
  public static function getDateTimeNowExact()
  {
    $now = new DateTime();

    return $now->format(self::DATETIME_FORMAT_FULL);
  }

  
  public static function getDateTimePart2Digits($part)
  {
    if (StringOperations::strlen($part) < 2) {
      return '0' . $part;
    } else {
      return $part;
    }
  }

  
  public static function isSchaltjahr($jahr)
  {
    if ($jahr % 400 == 0) {
      return true;
    }
    if ($jahr % 4 == 0 && $jahr % 100 != 0) {
      return true;
    }

    return false;
  }

  
  public static function isDatebeforeNow($date, $daysBefore = 0)
  {
    if ($date != '') {
      $toadd = (self::DAY_IN_SECONDS * $daysBefore);
      $dateStamp = DateTimeOperations::getTimestampByDatestring($date);
      $nowStamp = DateTimeOperations::getTimestampByDatestring(DateTimeOperations::getDateNow());

      return ($dateStamp + $toadd) < $nowStamp;
    } else {
      return true;
    }
  }

  
  public static function isDateBefore($pDateEarlier, $pDateLater, $pDateEmptyAllowed = 'earlier')
  {
    $dateTmp1 = ($pDateEmptyAllowed === 'earlier' ? $pDateEarlier : $pDateLater);
    $dateTmp2 = ($pDateEmptyAllowed === 'earlier' ? $pDateLater : $pDateEarlier);

    if ($dateTmp1 == '' && DateTimeOperations::isValidDate($dateTmp2)) {
      return true;
    }
    if (!DateTimeOperations::isValidDate($dateTmp2)) {
      return false;
    }

    $earlier = DateTimeOperations::getValidDateString($pDateEarlier);
    $later = DateTimeOperations::getValidDateString($pDateLater);
    $earlierInt = self::getComparableDate($earlier);
    $laterInt = self::getComparableDate($later);

    return $laterInt > $earlierInt;
  }

  
  public static function min($date1, $date2)
  {
    $dateToTest1 = $date1 instanceof \DateTimeObj ? $date1->getDate() : $date1;
    $dateToTest2 = $date2 instanceof \DateTimeObj ? $date2->getDate() : $date2;
    if (!static::isValidDate($dateToTest2) || DateTimeOperations::isEmptyOrDefault($dateToTest2)) {
      return $date1;
    }
    if (!static::isValidDate($dateToTest1) || DateTimeOperations::isEmptyOrDefault($dateToTest1)) {
      return $date2;
    }
    if (static::isDateAfter($dateToTest2, $dateToTest1)) {
      return $date1;
    } else {
      return $date2;
    }
  }

  
  public static function max($date1, $date2)
  {
    $dateToTest1 = $date1 instanceof \DateTimeObj ? $date1->getDate() : $date1;
    $dateToTest2 = $date2 instanceof \DateTimeObj ? $date2->getDate() : $date2;
    if (!static::isValidDate($dateToTest2)) {
      return $date1;
    }
    if (!static::isValidDate($dateToTest1)) {
      return $date2;
    }
    if (static::isDateAfter($dateToTest1, $dateToTest2)) {
      return $date1;
    } else {
      return $date2;
    }
  }

  
  public static function isDateAfter($pDateLater, $pDateEarlier, $pDateEmptyAllowed = 'later')
  {
    return self::isDateBefore($pDateEarlier, $pDateLater, $pDateEmptyAllowed);
  }

  public static function isDateBetween($pDateEarlier, $pDateLater, $pDateBetween)
  {
    return self::isDateBefore($pDateEarlier, self::addDays($pDateBetween, 1))
            && self::isDateBefore($pDateBetween, self::addDays($pDateLater, 1));
  }

  
  public static function isDateTimeBefore($pDateTimeEarlier, $pDateTimeLater, $pDateTimeEmptyAllowed = 'earlier', $pFormat = self::DATETIME_FORMAT_NO_SECONDS)
  {
    $dateTmp1 = ($pDateTimeEmptyAllowed === 'earlier' ? $pDateTimeEarlier : $pDateTimeLater);
    $dateTmp2 = ($pDateTimeEmptyAllowed === 'earlier' ? $pDateTimeLater : $pDateTimeEarlier);

    if ($dateTmp1 == '' && DateTimeOperations::isValidDateTime($dateTmp2, $pFormat)) {
      return true;
    }
    if (!DateTimeOperations::isValidDateTime($dateTmp2, $pFormat)) {
      return false;
    }

    $earlier = DateTimeOperations::getValidDateTimeString($pDateTimeEarlier);
    $later = DateTimeOperations::getValidDateTimeString($pDateTimeLater);
    $earlierInt = self::getComparableDateTime($earlier);
    $laterInt = self::getComparableDateTime($later);

    return $laterInt > $earlierInt;
  }

  
  public static function isDateTimeAfter($pDateTimeLater, $pDateTimeEarlier, $pDateTimeEmptyAllowed = 'later', $pFormat = self::DATETIME_FORMAT_NO_SECONDS)
  {
    return self::isDateTimeBefore($pDateTimeEarlier, $pDateTimeLater, $pDateTimeEmptyAllowed, $pFormat);
  }

  
  public static function getDay(string $pDate): string
  {
    return \StringOperations::substr($pDate, 0, 2);
  }

  
  public static function getMonth(string $pDate): string
  {
    return \StringOperations::substr($pDate, 3, 2);
  }

  
  public static function getYear(string $pDate): string
  {
    return \StringOperations::substr($pDate, 6, 4);
  }

  public static function getTwoDigitYear(string $pDate): string
  {
    return \StringOperations::substr($pDate, 8, 2);
  }

  
  public static function getHours(string $pDateTime): string
  {
    return \StringOperations::substr($pDateTime, 11, 2);
  }

  
  public static function getMinutes(string $pDateTime): string
  {
    return \StringOperations::substr($pDateTime, 14, 2);
  }

  
  public static function getSeconds(string $pDateTime): string
  {
    return \StringOperations::substr($pDateTime, 17, 2);
  }

  
  public static function getComparableDate(string $pDate, string $pSeparator = ''): string
  {
    $dateTimeParts = array();
    $dateTimeParts[] = self::getYear($pDate);
    $dateTimeParts[] = self::getMonth($pDate);
    $dateTimeParts[] = self::getDay($pDate);

    return self::combineDateTimeParts($dateTimeParts, $pSeparator);
  }

  
  public static function getComparableDateTime(string $pDateTime, string $pSeparator = '', string $pSeparatorDateAndTime = ''): string
  {
    $dateTimeParts = array();
    $dateTimeParts[] = self::getComparableDate($pDateTime, $pSeparator);
    $dateTimeParts[] = self::getHours($pDateTime);
    if (!empty($pSeparatorDateAndTime)) {
      $tmp = self::combineDateTimeParts($dateTimeParts, $pSeparatorDateAndTime);
      $dateTimeParts = array($tmp);
    }
    $dateTimeParts[] = self::getMinutes($pDateTime);

    return self::combineDateTimeParts($dateTimeParts, $pSeparator);
  }

  
  public static function getComparableDateTimeExact(string $pDateTime, string $pSeparator = '', string $pSeparatorDateAndTime = ''): string
  {
    $dateTimeParts = array();
    $dateTimeParts[] = self::getComparableDateTime($pDateTime, $pSeparator, $pSeparatorDateAndTime);
    $dateTimeParts[] = self::getSeconds($pDateTime);

    return self::combineDateTimeParts($dateTimeParts, $pSeparator);
  }

  
  private static function combineDateTimeParts(array $pDateTimeParts, string $pSeparator = ''): string
  {
    $result = '';
    foreach ($pDateTimeParts as $part) {
      $result .= $part . $pSeparator;
    }
    if (\StringOperations::strlen($pSeparator) > 0) {
      $result = \StringOperations::substr($result, 0, -1);
    }

    return $result;
  }

  
  
  
  public static function getFloatasTimeString($time, $pLong = false)
  {
    return static::getFloatasTimeStringParts($time, 2, $pLong);
  }

  
  public static function getFirstDayOfCurrentWeek()
  {
    $datum = DateTimeOperations::getDateNow();

    return DateTimeOperations::getFirstDayOfWeek($datum);
  }

  
  public static function getFirstDayOfWeek($pDatum)
  {
    $wochentag = DateTimeOperations::getWeekDayFromDate($pDatum);

    return DateTimeOperations::getDateDaysBefore($pDatum, $wochentag - 1);
  }

  
  public static function getLastDayOfWeek($pDatum)
  {
    $datum = DateTimeOperations::getFirstDayOfWeek($pDatum);

    return DateTimeOperations::addDays($datum, 6);
  }

  
  public static function getFloatasTimeStringQuarter($time, $pLong = false)
  {
    return self::getFloatasTimeStringParts($time, 4, $pLong);
  }

  
  public static function getFloatasTimeStringParts($time, int $partsOfHour, $pLong = false)
  {
    assert($partsOfHour > 1);
    $minutesPerPart = 60 / $partsOfHour;

    $timeParts = round($time * $partsOfHour);
    $hour = (int)floor($time);
    $partIdx = ((int)$timeParts) % $partsOfHour;
    if ($partIdx === 0 && ((int)round($time)) !== $hour) {
      
      
      $hour += 1;
      $minuteStr = '00';
    } else {
      $minute = (int)floor($partIdx * $minutesPerPart);
      if ($minute === 60) {
        $minute = 59;
      }
      $minuteStr = \StringOperations::lpad($minute, 2, '0');
    }

    if ($pLong && $hour < 10) { 
      return '0' . $hour . ':' . $minuteStr;
    }

    return $hour . ':' . $minuteStr;
  }

  
  public static function getTimeStringasFloat($time)
  {
    $hour = StringOperations::substr($time, 0, 2);
    $minute = StringOperations::substr($time, 3, 2);
    
    
    
    
    $minute2 = 0.0;
    if ($minute >= 15) {
      $minute2 = 0.25;
    }
    if ($minute >= 30) {
      $minute2 = 0.5;
    }
    if ($minute >= 45) {
      $minute2 = 0.75;
    }

    return floatval($hour) + $minute2;
  }

  
  public static function getTimeStringAsFloatExact($time)
  {
    return \DateTimeOperations::convertTimeToMinutes($time) / 60;
  }

  
  public static function getTimeStringasFloatUpperBound($time)
  {
    $hour = StringOperations::substr($time, 0, 2);
    $minute = StringOperations::substr($time, 3, 2);
    if ($minute < 30) {
      $minute2 = 0.0;
    } else {
      $minute2 = 0.5;
    }

    return $hour + $minute2;
  }

  public static function getTimeStringasFloatLowerBound($time)
  {
    $hour = StringOperations::substr($time, 0, 2);
    $minute = StringOperations::substr($time, 3, 2);
    if ($minute == 0) {
      $minute2 = 0.0;
    } elseif ($minute <= 30) {
      $minute2 = 0.5;
    } else {
      $minute2 = 1;
    }

    return $hour + $minute2;
  }

  
  public static function getDaysofMonth($month, $year)
  {
    $days = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
    if ($month != 2) {
      return $days[$month - 1];
    } else {
      if (checkdate($month, 29, $year)) {
        return 29;
      } else {
        return 28;
      }
    }
  }

  
  public static function getDateOneMonthBefore($pTag, $pMonat, $pJahr)
  {
    if ($pMonat > 1) {
      $pMonat--;
    } else {
      $pMonat = 12;
      $pJahr--;
    }
    if (checkdate($pMonat, $pTag, $pJahr)) {
      return \DateTimeOperations::datefmt('dd.MM.yyyy', mktime(0, 0, 0, $pMonat, $pTag, $pJahr));
    } else {
      for ($i = 0; $i < 10; $i++) {
        $pTag--;
        if (checkdate($pMonat, $pTag, $pJahr)) {
          return \DateTimeOperations::datefmt('dd.MM.yyyy', mktime(0, 0, 0, $pMonat, $pTag, $pJahr));
        }
      }
    }
  }

  
  public static function getNameofMonth($pMonth)
  {
    $months = LanguageOperations::getNameofMonths();
    $monthOhneFuehrendeNull = intval($pMonth);

    return $months[$monthOhneFuehrendeNull];
  }

  public static function getNameofMonthByDate($pDate)
  {
    if (!DateTimeOperations::isValidDate($pDate)) {
      return '';
    }
    $month = StringOperations::substr($pDate, 3, 2);
    if (StringOperations::substr($month, 0, 1) === '0') {
      $month = StringOperations::substr($month, 1, 1);
    }

    return DateTimeOperations::getNameofMonth($month);
  }

  public static function getMonthByDate($pDate)
  {
    $month = StringOperations::substr($pDate, 3, 2);
    if (StringOperations::substr($month, 0, 1) === '0') {
      $month = StringOperations::substr($month, 1, 1);
    }

    return $month;
  }

  public static function getNameofMonthShortByDate($pDate)
  {
    if (!DateTimeOperations::isValidDate($pDate)) {
      return '';
    }
    $month = DateTimeOperations::getMonthByDate($pDate);

    return DateTimeOperations::getNameofMonthShort($month);
  }

  
  public static function getNameofMonthShort($pMonth)
  {
    $months = LanguageOperations::getNameofMonthsShort();

    return $months[$pMonth];
  }

  
  public static function getAllMonth()
  {
    return LanguageOperations::getNameofMonths();
  }

  
  public static function getWeekDayShort($date)
  {
    if (($date == '') || ($date == '..') || ($date == DEFAULTDATE)) {
      return '';
    } 

    
    $day = intval(StringOperations::substr($date, 0, 2));
    $month = intval(StringOperations::substr($date, 3, 2));
    $year = intval(StringOperations::substr($date, 6, 4));
    $abc = StringOperations::strtoupper(date('D', mktime(0, 0, 0, $month, $day, $year)));
    $woindary = array('MON' => '1', 'TUE' => '2', 'WED' => '3', 'THU' => '4', 'FRI' => '5', 'SAT' => '6', 'SUN' => '7');

    return DateTimeOperations::getWeekDayFromInt($woindary[$abc], 1);
  }

  
  public static function getShortWeekDayArray()
  {
    $days_short = LanguageOperations::getNameofDaysShort();
    $language_en = new LanguageOperationsEN();
    $days_short_en = array_map('StringOperations::strtoupper', $language_en->getNameofDaysShort());

    return array_combine($days_short_en, $days_short);
  }

  
  public static function getLongWeekDayFromShortForm($pParam)
  {
    if ($pParam == '') {
      return '';
    }
    $days_short = LanguageOperations::getNameOfDaysShort();
    $days = LanguageOperations::getNameofDays();
    $result = array_combine($days_short, $days);

    return $result[$pParam];
  }

  
  public static function getWeekDay($date)
  {
    $int = static::getWeekDayFromDate($date);
    if ($int !== '') {
      return DateTimeOperations::getWeekDayFromInt($int, false);
    } else {
      return '';
    }
  }

  
  public static function getWeekDayFromDate($date)
  {
    $woindary = array('MON' => '1', 'TUE' => '2', 'WED' => '3', 'THU' => '4', 'FRI' => '5', 'SAT' => '6', 'SUN' => '7');
    if (($date == '') || ($date == '..') || ($date == DEFAULTDATE)) {
      return '';
    } 
    $day = StringOperations::substr($date, 0, 2);
    $month = StringOperations::substr($date, 3, 2);
    $year = StringOperations::substr($date, 6, 4);
    $abc = StringOperations::strtoupper(date('D', mktime(0, 0, 0, $month, $day, $year)));

    return $woindary[$abc];
  }

  
  public static function isWorkday($pDate)
  {
    $dayInt = static::getWeekDayFromDate($pDate);

    return $dayInt !== '7' && $dayInt !== '';
  }

  
  public static function getNextWorkdays($pNumber, $pFromDate = '')
  {
    if (!is_numeric($pNumber)) {
      throw new InvalidArgumentException('$pNumber ist kein Integer');
    }
    if (!static::isValidDate($pFromDate)) {
      $date = static::getDateNow();
    } else {
      $date = $pFromDate;
    }
    $result = array();
    $i = 1;
    do {
      $candidate = static::addDays($date, $i++);
      if (static::isWorkday($candidate)) {
        $result[] = $candidate;
      }
    } while (count($result) < $pNumber);

    return $result;
  }

  
  public static function isWorkdayMonToFri($pDate, $pSettings)
  {
    $dayInt = self::getWeekDayFromDate($pDate);
    $result = $dayInt !== '' && $dayInt <= 5;
    if ($result) {
      $result = !self::isHoliday($pDate, $pSettings);
    }

    return $result;
  }

  
  public static function isHoliday($pDate, $pSettings)
  {
    $year = self::getYear($pDate);
    $feiertage = self::getHolidays($year, $pSettings);

    return in_array($pDate, $feiertage);
  }

  
  public static function getWeekDayFromDateDelphiStyle($pDate)
  {
    $tmp = DateTimeOperations::getWeekDayFromDate($pDate);
    
    $tmp++;
    if ($tmp == 8) {
      $tmp = 1;
    }

    return $tmp;
  }

  
  public static function getWeekDayFromInt($day, $formshort)
  {
    if ($formshort) { 
      $wotagary = LanguageOperations::getNameofDaysShort();
    } else {
      $wotagary = LanguageOperations::getNameofDays();
    }

    return $wotagary[$day];
  }

  
  public static function invertWotageString($pWotage)
  {
    $result = array();
    for ($i = 1; $i <= 7; $i++) {
      $index = StringOperations::strpos($pWotage, (string)$i);
      if ($index === false) {
        $result[] = $i;
      }
    }

    return $result;
  }

  
  public static function getWeekDays($daystring)
  {
    $wotagary = LanguageOperations::getNameofDaysShort();
    $daystring = StringOperations::substr($daystring, 0, 7);
    $daystringneu = '';
    $strlength = StringOperations::strlen($daystring);
    for ($i = 0; $i < $strlength; $i++) {
      if (StringOperations::substr($daystring, $i, 1) == 'X') {
        $daystringneu .= $wotagary[$i + 1] . ', ';
      }
    }

    return StringOperations::substr($daystringneu, 0, StringOperations::strlen($daystringneu) - 3);
  }

  public const DATE_PARAM_TODAY = 0;
  public const DATE_PARAM_TOMORROW = 1;
  public const DATE_PARAM_CURRENT_WEEK_MONDAY = 2;
  public const DATE_PARAM_CURRENT_WEEK_SUNDAY = 3;
  public const DATE_PARAM_NEXT_WEEK_MONDAY = 4;
  public const DATE_PARAM_NEXT_WEEK_SUNDAY = 5;
  public const DATE_PARAM_CURRENT_MONTH_FIRST_DAY = 6;
  public const DATE_PARAM_CURRENT_MONTH_LAST_DAY = 7;
  public const DATE_PARAM_NEXT_MONTH_FIRST_DAY = 8;
  public const DATE_PARAM_NEXT_MONTH_LAST_DAY = 9;

  
  public static function getDateByParam($p_param)
  {
    switch ($p_param) {
      case 0:
        $dat = \DateTimeOperations::datefmt('dd.MM.yyyy', DateTimeOperations::getTimestampNow());
        break;
      case 1:
        $dat = \DateTimeOperations::datefmt('dd.MM.yyyy', (DateTimeOperations::getTimestampNow() + self::DAY_IN_SECONDS));
        break;
      case 2:
        $dat = \DateTimeOperations::datefmt('dd.MM.yyyy', strtotime('last Monday', DateTimeOperations::getTimestampNow() + self::DAY_IN_SECONDS));
        break;
      case 3:
        $dat = \DateTimeOperations::datefmt('dd.MM.yyyy', strtotime('Sunday', DateTimeOperations::getTimestampNow()));
        break;
      case 4:
        $dat = \DateTimeOperations::datefmt('dd.MM.yyyy', strtotime('Monday', DateTimeOperations::getTimestampNow() + self::DAY_IN_SECONDS));
        break;
      case 5:
        
        
        $dat = \DateTimeOperations::datefmt('dd.MM.yyyy', strtotime('Sunday', DateTimeOperations::getTimestampNow() + 604800));
        break;
      case 6: 
        $dat = \DateTimeOperations::datefmt('dd.MM.yyyy', (DateTimeOperations::getTimestampNow() - self::DAY_IN_SECONDS * (\DateTimeOperations::datefmt('dd', DateTimeOperations::getTimestampNow()) - 1)));
        break;
      case 7: 
        $dat = \DateTimeOperations::datefmt('dd.MM.yyyy', mktime(0, 0, 0, \DateTimeOperations::datefmt('MM', DateTimeOperations::getTimestampNow()) + 1, 0, \DateTimeOperations::datefmt('yyyy', DateTimeOperations::getTimestampNow())));
        break;
      case 8:
        $dat = \DateTimeOperations::datefmt('dd.MM.yyyy', mktime(0, 0, 0, \DateTimeOperations::datefmt('MM', DateTimeOperations::getTimestampNow()) + 1, 1, \DateTimeOperations::datefmt('yyyy', DateTimeOperations::getTimestampNow())));
        break;
      case 9:
        $dat = \DateTimeOperations::datefmt('dd.MM.yyyy', mktime(0, 0, 0, \DateTimeOperations::datefmt('MM', DateTimeOperations::getTimestampNow()) + 2, 0, \DateTimeOperations::datefmt('yyyy', DateTimeOperations::getTimestampNow())));
        break;
      case DATE_2_MO_BEGINN: 
        $month = \DateTimeOperations::datefmt('MM', DateTimeOperations::getTimestampNow()) + 2;
        $year = \DateTimeOperations::datefmt('yyyy', DateTimeOperations::getTimestampNow());
        $dat = \DateTimeOperations::datefmt('dd.MM.yyyy', mktime(0, 0, 0, $month, 1, $year));
        break;
      case DATE_2_MO_ENDE: 
        $month = \DateTimeOperations::datefmt('MM', DateTimeOperations::getTimestampNow()) + 2;
        $year = \DateTimeOperations::datefmt('yyyy', DateTimeOperations::getTimestampNow());
        $dat = \DateTimeOperations::datefmt('dd.MM.yyyy', mktime(0, 0, 0, $month + 1, 0, $year));
        break;
      case DATE_3_MO_BEGINN: 
        $month = \DateTimeOperations::datefmt('MM', DateTimeOperations::getTimestampNow()) + 3;
        $year = \DateTimeOperations::datefmt('yyyy', DateTimeOperations::getTimestampNow());
        $dat = \DateTimeOperations::datefmt('dd.MM.yyyy', mktime(0, 0, 0, $month, 1, $year));
        break;
      case DATE_3_MO_ENDE: 
        $month = \DateTimeOperations::datefmt('MM', DateTimeOperations::getTimestampNow()) + 3;
        $year = \DateTimeOperations::datefmt('yyyy', DateTimeOperations::getTimestampNow());
        $dat = \DateTimeOperations::datefmt('dd.MM.yyyy', mktime(0, 0, 0, $month + 1, 0, $year));
        break;
      case DATE_NAE_QUART_BEGINN: 
        $month = \DateTimeOperations::datefmt('MM', DateTimeOperations::getTimestampNow()) + 3;
        $day = \DateTimeOperations::datefmt('dd', DateTimeOperations::getTimestampNow());
        $year = \DateTimeOperations::datefmt('yyyy', DateTimeOperations::getTimestampNow());
        $date = \DateTimeOperations::datefmt('dd.MM.yyyy', mktime(0, 0, 0, $month, $day, $year));
        $dat = self::getQuartalerstTag($date);
        break;
      case DATE_NAE_QUART_ENDE: 
        $month = \DateTimeOperations::datefmt('MM', DateTimeOperations::getTimestampNow()) + 3;
        $day = \DateTimeOperations::datefmt('dd', DateTimeOperations::getTimestampNow());
        $year = \DateTimeOperations::datefmt('yyyy', DateTimeOperations::getTimestampNow());
        $date = \DateTimeOperations::datefmt('dd.MM.yyyy', mktime(0, 0, 0, $month, $day, $year));
        $dat = self::getQuartalletztTag($date);
        break;
      case DATE_2_WO_BEGINN: 
        $dat = \DateTimeOperations::datefmt('dd.MM.yyyy', strtotime('next Monday', DateTimeOperations::getTimestampNow() + self::DAY_IN_SECONDS + 604800));
        break;
      case DATE_2_WO_ENDE: 
        
        
        $dat = \DateTimeOperations::datefmt('dd.MM.yyyy', strtotime('next Sunday', DateTimeOperations::getTimestampNow() + 2 * 604800));
        break;
      case DATE_3_WO_BEGINN: 
        $dat = \DateTimeOperations::datefmt('dd.MM.yyyy', strtotime('next Monday', DateTimeOperations::getTimestampNow() + self::DAY_IN_SECONDS + 2 * 604800));
        break;
      case DATE_3_WO_ENDE: 
        
        
        $dat = \DateTimeOperations::datefmt('dd.MM.yyyy', strtotime('next Sunday', DateTimeOperations::getTimestampNow() + 3 * 604800));
        break;
      default:
        $dat = \DateTimeOperations::datefmt('dd.MM.yyyy', DateTimeOperations::getTimestampNow());
        break;
    }

    return $dat;
  }

  
  public static function getQuartalerstTag($date = '')
  {
    
    
    if (($date == '') || (!self::isValidDate($date))) {
      
      $month = \DateTimeOperations::datefmt('MM', self::getTimestampNow());
      $year = \DateTimeOperations::datefmt('yyyy', self::getTimestampNow());
    } else {
      
      
      $month = \DateTimeOperations::datefmt('MM', mktime(0, 0, 0, StringOperations::substr($date, 3, 2), StringOperations::substr($date, 0, 2), StringOperations::substr($date, 6, 4)));
      $year = \DateTimeOperations::datefmt('yyyy', mktime(0, 0, 0, StringOperations::substr($date, 3, 2), StringOperations::substr($date, 0, 2), StringOperations::substr($date, 6, 4)));
    }

    switch ($month % 3) {
      case 0:  
        $month -= 2;
        break;
      case 1: 
        break;
      case 2: 
        $month -= 1;
        break;
    }

    return \DateTimeOperations::datefmt('dd.MM.yyyy', mktime(0, 0, 0, $month, 1, $year));
  }

  
  public static function getQuartalletztTag($date = '')
  {
    
    
    if (($date == '') || (!self::isValidDate($date))) {
      
      $month = \DateTimeOperations::datefmt('MM', self::getTimestampNow());
      $year = \DateTimeOperations::datefmt('yyyy', self::getTimestampNow());
    } else {
      
      
      $month = \DateTimeOperations::datefmt('MM', mktime(0, 0, 0, StringOperations::substr($date, 3, 2), StringOperations::substr($date, 0, 2), StringOperations::substr($date, 6, 4)));
      $year = \DateTimeOperations::datefmt('yyyy', mktime(0, 0, 0, StringOperations::substr($date, 3, 2), StringOperations::substr($date, 0, 2), StringOperations::substr($date, 6, 4)));
    }
    switch ($month % 3) {
      case 0:  
        break;
      case 1: 
        $month += 2;
        break;
      case 2: 
        $month += 1;
        break;
    }
    
    return \DateTimeOperations::datefmt('dd.MM.yyyy', mktime(0, 0, 0, $month + 1, 0, $year));
  }

  
  public static function getQuartal($pDate)
  {
    if (!(self::isValidDate($pDate))) {
      return false;
    }

    $month = self::getMonth($pDate);

    if ($month <= 3) {
      return 1;
    } elseif ($month <= 6) {
      return 2;
    } elseif ($month <= 9) {
      return 3;
    } else { 
      return 4;
    }
  }

  public static function isDateInCurrentQuater(string $date): bool
  {
    if (\DateTimeOperations::isEmptyOrDefault($date)) {
      return false;
    }

    $now = \DateTimeOperations::getDateNow();

    return \DateTimeOperations::isDateBetween(
      \DateTimeOperations::getQuartalerstTag($now),
      \DateTimeOperations::getQuartalletztTag($now),
      $date
    );
  }

  
  public static function getTimestampByDatestring($pDate, $pTime = '', $pLocal = false)
  {
    $stunde = 0;
    $sekunde = 0;
    $minute = 0;
    if (!empty($pTime)) {
      $stunde = StringOperations::substr($pTime, 0, 2);
      $minute = StringOperations::substr($pTime, 3, 2);
      $sekunde = StringOperations::substr($pTime, 6, 2);
    }
    if (empty($sekunde)) {
      $sekunde = 0;
    }
    $month = (int)StringOperations::substr($pDate, 3, 2);
    $day = (int)StringOperations::substr($pDate, 0, 2);
    $year = (int)StringOperations::substr($pDate, 6, 4);
    if ($pLocal) {
      return mktime($stunde, $minute, $sekunde, $month, $day, $year);
    }

    return gmmktime($stunde, $minute, $sekunde, $month, $day, $year);
  }

  
  public static function getTimestampByDateTimeString(string $datetime, bool $pLocal = false): int
  {
    list($date, $time) = explode(' ', $datetime);

    return static::getTimestampByDatestring($date, $time, $pLocal);
  }

  
  public static function getDatestringByTimestamp($pTimestamp)
  {
    return date(self::DATETIME_FORMAT_FULL, $pTimestamp);
  }

  
  public static function getTimeDistanceToNow($pDatum, $pZeit)
  {
    
    
    $akttime = DateTimeOperations::getTimestampByDatestring(DateTimeOperations::getDateNow(), DateTimeOperations::getTimeNow());

    return $akttime - DateTimeOperations::getTimestampByDatestring($pDatum, $pZeit);
    
    
  }

  public static function getUTCTimeString($pDate = '', $pTime = '')
  {
    if ($pDate == '' && $pTime = '') {
      return date('Ymd\\THis');
    } else {
      return date('Ymd\\THis', mktime(intval(StringOperations::substr($pTime, 0, 2)), intval(StringOperations::substr($pTime, 3, 2)), 0, intval(StringOperations::substr($pDate, 3, 2)), intval(StringOperations::substr($pDate, 0, 2)), intval(StringOperations::substr($pDate, 6, 4))));
    }
  }

  
  public static function getValidDateString($value, $thisyear = true)
  {
    $datenow = self::getDateNow();
    list($value) = explode(' ', $value ?? '', 2);
    if (!self::isValidDate($value)) {
      if ($thisyear) {
        $currentYear = self::getYear($datenow);
        $date = StringOperations::ensureTrailing($value, '.');

        return self::getValidDateString($date . $currentYear, false);
      }

      return DEFAULTDATE;
    }
    $matches = array();
    preg_match('/^(\\d{1,2})\\.(\\d{1,2})\\.(\\d{2}|\\d{4})$/', $value, $matches);
    $day = StringOperations::lpad($matches[1], 2, '0');
    $month = StringOperations::lpad($matches[2], 2, '0');
    $year = intval($matches[3]);
    if ($year < 100) {
      if ($year > self::getTwoDigitYear($datenow) + 15) {
        $year = $year + 1900;
      } else {
        $year = $year + 2000;
      }
    }

    return $day . '.' . $month . '.' . $year;
  }

  
  public static function getValidDateTimeString($value, $thisyear = true)
  {
    $parts = explode(' ', $value ?? '');
    $date = DateTimeOperations::getValidDateString($parts[0], $thisyear);
    if ($date == '') {
      return '';
    }

    if (isset($parts[1])) {
      $timeAry = explode(':', $parts[1]);
      $time = StringOperations::getInt2Digits((int)$timeAry[0]) . ':'
              . StringOperations::getInt2Digits((int)$timeAry[1]);
      if (isset($timeAry[2])) {
        $time .= ':' . StringOperations::getInt2Digits((int)$timeAry[2]);
      } else {
        $time .= ':00';
      }
    } else {
      $time = DEFAULTTIME_WITH_SECONDS;
    }

    return $date . ' ' . $time;
  }

  
  public static function reformatDateString($pDate)
  {
    $retour = StringOperations::substr($pDate, 6) . '-';
    $retour .= StringOperations::substr($pDate, 3, 2) . '-';
    $retour .= StringOperations::substr($pDate, 0, 2);

    return $retour;
  }

  
  public static function getWbDateStringFromISO8601($pDate)
  {
    $parts = explode('-', $pDate);

    return implode('.', array_reverse($parts));
  }

  
  public static function getISO8601FromDateTime($dateTime)
  {
    return $dateTime->format('P%yY%mM%dDT%hH%iM%sS');
  }

  
  
  public static function getWbDateStringFromRFC3339($dtStr)
  {
    $date = date_create_from_format(DateTimeInterface::RFC3339, $dtStr);
    $date->setTimezone(new DateTimeZone('Europe/Berlin'));

    return $date->format(self::DATETIME_FORMAT_NO_SECONDS);
  }

  
  public function getValidDate($value)
  {
    $value = $value ?? '';
    if (StringOperations::strlen($value) == 4) { 
      if ((is_numeric(StringOperations::substr($value, 0, 4))) && (StringOperations::strlen($value) == 4)) {
        return $this->getFormatDate4Server('01.01.' . $value);
      }
    } else {
      if (StringOperations::strlen($value) == 10) { 
        if ((is_numeric(StringOperations::substr($value, 0, 2))) && (is_numeric(StringOperations::substr($value, 3, 2))) && (is_numeric(StringOperations::substr($value, 6, 4))) && ((StringOperations::strlen($value) >= 8) && (StringOperations::strlen($value) <= 10))) {
          if (checkdate((StringOperations::substr($value, 3, 2)), (StringOperations::substr($value, 0, 2)), (StringOperations::substr($value, 6, 4))) == true) {
            return $this->getFormatDate4Server($value);
          }
        }
      }
    }

    return $this->getFormatDate4Server(DEFAULTDATE);
  }

  
  public static function getValidTimeString($pValue, $pTimeString = '')
  {
    $zeitbezeichnung = 'Uhrzeit';
    if ($pTimeString !== 'Zeitspanne') {
      if ($pTimeString !== '') {
        $zeitbezeichnung .= ' ( ' . $pTimeString . ' )';
      }
    } else {
      $zeitbezeichnung = 'Zeitspanne';
    }
    $excMessage = 'Ungültiger Wert / ungültiges Format der ' . $zeitbezeichnung . ': ' . $pValue . '.';

    
    if ($pTimeString === 'Zeitspanne') {
      if ($pValue === 0 || $pValue === '00:00' || $pValue === '0:00' || empty($pValue)) {
        throw new Exception($excMessage);
      }
    }
    
    if ($pValue === '' || $pValue === 0) {
      return '00:00';
    }


    if (!static::isValidTimeString($pValue)) {
      throw new Exception($excMessage);
    }
    $timeAry = explode(':', $pValue);

    return StringOperations::getInt2Digits((int)$timeAry[0]) . ':' . StringOperations::getInt2Digits((int)$timeAry[1]);
  }

  public static function isValidTimeString($pToCheck)
  {
    return preg_match('/^([0-9]|0[0-9]|1[0-9]|2[0-3]):([0-9]|[0-5][0-9])$/', $pToCheck) === 1;
  }

  
  public static function isValidDate($string, $pAllowEmptyDate = false)
  {
    if ($pAllowEmptyDate && empty($string)) {
      return true;
    }

    $matches = array();
    preg_match('/^(\\d{1,2})\\.(\\d{1,2})\\.(\\d{2}|\\d{4})$/', $string ?? '', $matches);
    if (count($matches) != 4) {
      return false;
    }
    $day = intval($matches[1]);
    $month = intval($matches[2]);
    $year = intval($matches[3]);
    if (strlen($matches[3]) === 2) {
      $year = intval($matches[3]) + 2000;
    }

    if ($year > 2100 || $year < 1899) {
      return false;
    }

    return checkdate($month, $day, $year);
  }

  
  public static function isValidDateTime(?string $dateTime, string $format = self::DATETIME_FORMAT_NO_SECONDS): bool
  {
    if (is_null($dateTime)) {
      return false;
    }

    $d = DateTime::createFromFormat($format, $dateTime);

    return $d && $d->format($format) == $dateTime;
  }

  
  public static function getYearFromValidDate($pDate)
  {
    $firstdot = StringOperations::strpos($pDate, '.'); 
    $seconddot = StringOperations::strpos($pDate, '.', $firstdot) + $firstdot; 

    return StringOperations::substr($pDate, $seconddot + 2);
  }

  
  public static function getDaysFromTo($pDate1, $pDate2)
  {
    $date1 = DateTimeOperations::getValidDateString($pDate1);
    $date2 = DateTimeOperations::getValidDateString($pDate2);

    return ((DateTimeOperations::getTimestampByDateString($date2)
            - DateTimeOperations::getTimestampByDateString($date1)) / self::DAY_IN_SECONDS) + 1; 
  }

  
  public static function getNightsBetween($pDate1, $pDate2)
  {
    return (DateTimeOperations::getTimestampByDateString($pDate2)
            - DateTimeOperations::getTimestampByDateString($pDate1)) / self::DAY_IN_SECONDS; 
  }

  
  public static function computeDate($pDate, $pTimeSpan)
  {
    $date = DateTimeOperations::getValidDateString($pDate, true);

    return \DateTimeOperations::datefmt('dd.MM.yyyy', strtotime($pTimeSpan, DateTimeOperations::getTimestampByDateString($date)));
  }

  
  public static function computeDateTime($pDate, $pTimeSpan, bool $local = true)
  {
    $date = DateTimeOperations::getValidDateTimeString($pDate, true);

    return \DateTimeOperations::datefmt(
      'dd.MM.yyyy HH:mm:ss',
      strtotime(
        $pTimeSpan,
        DateTimeOperations::getTimestampByDateTimeString($date, $local)
      )
    );
  }

  
  public static function addHours($pDate, $pHours)
  {
    if ($pHours == 0) {
      return $pDate;
    }

    $hoursStr = '+' . $pHours;
    if ($pHours < 0) {
      $hoursStr = (string)$pHours;
    }

    return DateTimeOperations::computeDateTime($pDate, $hoursStr . ' hour' . ($pHours != '1' ? 's' : ''));
  }

  
  public static function addDays($pDate, $pDays)
  {
    $daysStr = '+' . $pDays;
    if ($pDays < 0) {
      $daysStr = (string)$pDays;
    }

    return DateTimeOperations::computeDate($pDate, $daysStr . ' day' . ($pDays != '1' ? 's' : ''));
  }

  
  public static function addMonths($pDate, $pMonths, $doNotSkipMonths = true)
  {
    $monthStr = '+' . $pMonths;
    if ($pMonths < 0) {
      $monthStr = (string)$pMonths;
    }

    $date = DateTimeOperations::computeDate($pDate, $monthStr . ' month' . ($pMonths != '1' ? 's' : ''));
    if ($doNotSkipMonths && StringOperations::substr($pDate, 0, 1) > 0 && StringOperations::startsWith($date, '0')) {
      $date = DateTimeOperations::computeDate($date, 'last day of -1 month');
    }

    return $date;
  }

  
  public static function shiftMonth($pMonat, $pForward = true)
  {
    if ($pForward) {
      $monthshift_nachher = array(1 => 2, 2 => 3, 3 => 4, 4 => 5, 5 => 6, 6 => 7, 7 => 8, 8 => 9, 9 => 10, 10 => 11, 11 => 12, 12 => 1);

      return $monthshift_nachher[$pMonat];
    } else {
      $monthshift_vorher = array(1 => 12, 2 => 1, 3 => 2, 4 => 3, 5 => 4, 6 => 5, 7 => 6, 8 => 7, 9 => 8, 10 => 9, 11 => 10, 12 => 11);

      return $monthshift_vorher[$pMonat];
    }
  }

  public function computeAge($pDate)
  {
    $today = $this->getDateNow();

    return $this->computeAgeAtDate($pDate, $today);
  }

  public static function computeAgeAtDate($pBirthdate, $pDate)
  {
    $date = StringOperations::formatDateString($pDate, 'jjjj')
            . StringOperations::formatDateString($pDate, 'mm')
            . StringOperations::formatDateString($pDate, 'tt');
    $birthdate = StringOperations::formatDateString($pBirthdate, 'jjjj')
            . StringOperations::formatDateString($pBirthdate, 'mm')
            . StringOperations::formatDateString($pBirthdate, 'tt');

    return floor(($date - $birthdate) / 10000);
  }

  public static function computeAgeAtDateInMonths($pBirthdate, $pDate)
  {
    $birthdate = DateTimeObj::createFromString($pBirthdate);
    $date = DateTimeObj::createFromString($pDate);
    $age = ($date->getYear() - $birthdate->getYear()) * 12;
    if ($date->getMonth() > $birthdate->getMonth()) {
      $age = $age + ($date->getMonth() - $birthdate->getMonth());
    } else {
      $age = $age - ($birthdate->getMonth() - $date->getMonth());
      if ($date->getDay() < $birthdate->getDay()) {
        $age--;
      }
    }

    return $age;
  }

  public static function getTimeStampForAbgleich()
  {
    return date(self::DATETIME_FORMAT_FULL);
  }

  
  public static function getAnzTage($pVon, $pBis)
  {
    $fromtime = DateTimeOperations::getTimestampByDatestring($pVon);
    $totime = DateTimeOperations::getTimestampByDatestring($pBis);

    return ($totime - $fromtime) / self::DAY_IN_SECONDS + 1;
  }

  
  public static function getAnzTageImMonat($pMonat, $pJahr)
  {
    $datum = '01.' . \StringOperations::pad($pMonat, 2, '0', STR_PAD_LEFT) . '.' . $pJahr;

    return date('t', strtotime($datum));
  }

  
  public static function getAnzWochen($pVon, $pBis)
  {
    $days = DateTimeOperations::getAnzTage($pVon, $pBis);
    $wochen = (int)($days / 7);
    if ($days % 7 != 0) {
      $wochen++;
    }

    return $wochen;
  }

  
  public static function zeitSpanne($zeitbegin, $zeitende, $zeitspanne = '01:00')
  {
    $overflow = 0;
    
    $amin = (int)StringOperations::substr($zeitbegin, 3, 2);
    $astd = (int)StringOperations::substr($zeitbegin, 0, 2);
    
    $emin = (int)StringOperations::substr($zeitende, 3, 2);
    $estd = (int)StringOperations::substr($zeitende, 0, 2);
    
    $smin = (int)StringOperations::substr($zeitspanne, 3, 2);
    $sstd = (int)StringOperations::substr($zeitspanne, 0, 2);
    

    $cmin = $emin - $amin;
    if ($cmin < 0) {
      $overflow = 1;
      $cmin += 60;
    }

    $cstd = $estd - $astd - $overflow;

    $cminges = $cmin + 60 * $cstd;
    $sminges = $smin + 60 * $sstd;

    return (float)$cminges / (float)$sminges;
  }

  
  public static function add_months(string $months, \DateTime $dateObject): \DateInterval
  {
    $nextInstance = new \DateTime($dateObject->format('d.m.Y'));
    $next = clone $nextInstance;
    $next->modify('last day of +' . ($months === '' ? $months .= 0 : $months) . ' month');
    if ($dateObject->format('d') > $next->format('d')) {
      return $dateObject->diff($next);
    } else {
      return new \DateInterval('P' . $months . 'M');
    }
  }

  
  public static function changeMonth(string $pDate, string $pMonths, ?bool $pSetToFirstDayOfMonth = null): string
  {
    $datum = new \DateTime($pDate);
    if (ctype_digit($pMonths)) {
      $neuesDatum = $datum->add(self::add_months($pMonths, $datum));
    } else {
      if ($pMonths == '-1' && $datum->format('m') == ('03')) {
        $neuesDatum = $datum->modify('first day of last month');
      } else {
        $neuesDatum = $datum->sub(self::add_months(str_replace('-', '', $pMonths), $datum));
      }
    }
    if (isset($pSetToFirstDayOfMonth)) {
      if ($pSetToFirstDayOfMonth) {
        $neuesDatum->modify('first day of this month');
      } elseif (!$pSetToFirstDayOfMonth) {
        $neuesDatum->modify('last day of this month');
      }
    }

    return $neuesDatum->format('d.m.Y');
  }

  
  public static function addDaysMonthsYears($pDatum, $pDays, $pMonths, $pYears)
  {
    $day = self::getDay($pDatum);
    $month = self::getMonth($pDatum);
    $year = self::getYear($pDatum);

    if ($pYears > 0) {
      $year = $year + $pYears;
    } else {
      $year = $year - $pYears;
    }

    if ($pMonths > 0) {
      for ($i = 1; $i <= $pMonths; $i++) {
        $month = $month + 1;
        if ($month > 12) {
          $year = $year + 1;
          $month = 1;
        }
      }
    } else {
      for ($i = 1; $i <= $pMonths * -1; $i++) {
        $month = $month - 1;
        if ($month < 1) {
          $year = $year - 1;
          $month = 12;
        }
      }
    }

    if ($pDays > 0) {
      for ($i = 1; $i <= $pDays; $i++) {
        $day = $day + 1;
        if ($day > DateTimeOperations::getDaysofMonth($month, $year)) {
          $month = $month + 1;
          if ($month > 12) {
            $year = $year + 1;
            $month = 1;
          }
          $day = 1;
        }
      }
    } else {
      for ($i = 1; $i <= $pDays * -1; $i++) {
        $day = $day - 1;
        if ($day < 1) {
          $month = $month - 1;
          if ($month < 1) {
            $year = $year - 1;
            $month = 12;
          }
          $day = DateTimeOperations::getDaysofMonth($month, $year);
        }
      }
    }

    if ($day > DateTimeOperations::getDaysofMonth($month, $year)) {
      $day = DateTimeOperations::getDaysofMonth($month, $year);
    }

    return \StringOperations::pad($day, 2, '0', STR_PAD_LEFT)
      . '.' . \StringOperations::pad($month, 2, '0', STR_PAD_LEFT)
      . '.' . \StringOperations::pad($year, 4, '0', STR_PAD_LEFT);
  }

  
  public static function getFirstAndLastDatum($pDatumAry)
  {
    $datumAryComparable = array();
    foreach ($pDatumAry as $datum) {
      $datumAryComparable[$datum] = self::getComparableDate($datum);
    }
    asort($datumAryComparable);
    $first = key($datumAryComparable);
    end($datumAryComparable);
    $last = key($datumAryComparable);

    return array('first' => $first, 'last' => $last);
  }

  public static function getGebDatForGebjahr($pGebjahr, $pDefault)
  {
    $result = $pDefault;
    if (StringOperations::strlen($pGebjahr) == 4 && preg_match('/^[0-9]+$/', (string)$pGebjahr)) {
      $result = '01.01.' . $pGebjahr;
    }

    return $result;
  }

  public static function getAlter($pGeburtstag)
  {
    $jahrGebDat = self::getYear($pGeburtstag);
    $monatGebDat = self::getMonth($pGeburtstag);
    $tagGebDat = self::getDay($pGeburtstag);

    $heute = self::getDateNow();
    $jahrHeute = self::getYear($heute);
    $monatHeute = self::getMonth($heute);
    $tagHeute = self::getDay($heute);

    
    $keinVollesJahr = 0;
    if ($monatHeute < $monatGebDat
       || ($monatHeute == $monatGebDat && $tagHeute < $tagGebDat)) {
      $keinVollesJahr = 1;
    }

    return ($jahrHeute - $jahrGebDat) - $keinVollesJahr;
  }

  
  private static function getDatumOstersonntag($pJahr)
  {
    static $cache = array();

    if (!isset($cache[$pJahr])) {
      $k = intdiv($pJahr, 100);                                                   
      $m = 15 + intdiv(3 * $k + 3, 4) - intdiv(8 * $k + 13, 25);                  
      $s = 2 - intdiv(3 * $k + 3, 4);                                             
      $a = $pJahr % 19;                                                           
      $d = (19 * $a + $m) % 30;                                                   
      $r = intdiv($d, 29) + (intdiv($d, 28) - intdiv($d, 29)) * intdiv($a, 11);   
      $grenze = 21 + $d - $r;                                                     
      $ersterSonntagMaerz = 7 - (($pJahr + intdiv($pJahr, 4) + $s) % 7);          
      $tag = $grenze + 7 - (($grenze - $ersterSonntagMaerz) % 7);                 
      $monat = 3;                                                                 
      if ($tag > 31) {
        $tag = $tag - 31;
        $monat = 4;
      }

      $cache[$pJahr] = \StringOperations::pad($tag, 2, '0', STR_PAD_LEFT)
        . '.' . \StringOperations::pad($monat, 2, '0', STR_PAD_LEFT)
        . '.' . $pJahr;
    }

    return $cache[$pJahr];
  }

  
  private static function getHolidays($pJahr, $pSettings)
  {
    $land = ConstantOperations::getLand($pSettings);

    switch ($land) {
      case 'A':
        $result = self::getHolidaysAT($pJahr);
        break;
      case 'CH':
        $result = self::getHolidaysCH($pJahr);
        break;
      case 'I':
        $result = self::getHolidaysIT($pJahr);
        break;
      default:
        $result = self::getHolidaysDE($pJahr, $pSettings);
        break;
    }

    return $result;
  }

  
  private static function getHolidaysDE($pYear, $pSettings)
  {
    static $cache = array();
    if (!isset($cache[$pYear . $pSettings->bundesland])) {
      $bundesland = $pSettings->bundesland;
      if (StringOperations::startsWith($bundesland, 'D-')) {
        $bundesland = StringOperations::substr($bundesland, 2, strlen($bundesland));
      }

      $datumOstersonntag = self::getDatumOstersonntag($pYear);

      $holidays = array(
          '01.01.' . $pYear, 
          '01.05.' . $pYear, 
          '03.10.' . $pYear, 
          '25.12.' . $pYear, 
          '26.12.' . $pYear  
          );

      $holidays[] = DateTimeOperations::addDays($datumOstersonntag, -2); 
      $holidays[] = DateTimeOperations::addDays($datumOstersonntag, 1); 
      $holidays[] = DateTimeOperations::addDays($datumOstersonntag, 39); 
      $holidays[] = DateTimeOperations::addDays($datumOstersonntag, 50); 

      if (in_array($bundesland, array(BUNDESLAND_BADEN_WUERTTEMBERG, BUNDESLAND_BAYERN,
          BUNDESLAND_SACHSEN_ANHALT))) {
        $holidays[] = '06.01.' . $pYear; 
      }
      if (in_array($bundesland, array(BUNDESLAND_BAYERN, BUNDESLAND_SAARLAND))) {
        $holidays[] = '15.08.' . $pYear; 
      }
      if ($pYear >= '2019' && in_array($bundesland, array(BUNDESLAND_THUERINGEN))) {
        $holidays[] = '20.09.' . $pYear; 
      }
      if (in_array($bundesland, array(BUNDESLAND_BRANDENBURG, BUNDESLAND_MECKLENBURG_VORPOMMERN,
          BUNDESLAND_SACHSEN, BUNDESLAND_SACHSEN_ANHALT, BUNDESLAND_THUERINGEN))) {
        $holidays[] = '31.10.' . $pYear; 
      }
      if (in_array($bundesland, array(BUNDESLAND_BAYERN, BUNDESLAND_BADEN_WUERTTEMBERG,
          BUNDESLAND_NORDRHEIN_WESTFALEN, BUNDESLAND_RHEINLAND_PFALZ, BUNDESLAND_SAARLAND))) {
        $holidays[] = '01.11.' . $pYear; 
      }
      if (in_array($bundesland, array(BUNDESLAND_BAYERN, BUNDESLAND_BADEN_WUERTTEMBERG,
          BUNDESLAND_HESSEN, BUNDESLAND_NORDRHEIN_WESTFALEN, BUNDESLAND_RHEINLAND_PFALZ,
          BUNDESLAND_SAARLAND))) {
        $holidays[] = DateTimeOperations::addDays($datumOstersonntag, 60); 
      }

      $cache[$pYear . $pSettings->bundesland] = $holidays;
    }

    return $cache[$pYear . $pSettings->bundesland];
  }

  
  private static function getHolidaysAT($pYear)
  {
    static $cache = array();
    if (!isset($cache[$pYear])) {
      $datumOstersonntag = self::getDatumOstersonntag($pYear);

      $holidays = array(
          '01.01.' . $pYear, 
          '06.01.' . $pYear, 
          '01.05.' . $pYear, 
          '15.08.' . $pYear, 
          '26.10.' . $pYear, 
          '01.11.' . $pYear, 
          '08.12.' . $pYear, 
          '25.12.' . $pYear, 
          '26.12.' . $pYear  
          );

      $holidays[] = DateTimeOperations::addDays($datumOstersonntag, 1);  
      $holidays[] = DateTimeOperations::addDays($datumOstersonntag, 39); 
      $holidays[] = DateTimeOperations::addDays($datumOstersonntag, 50); 
      $holidays[] = DateTimeOperations::addDays($datumOstersonntag, 60); 

      $cache[$pYear] = $holidays;
    }

    return $cache[$pYear];
  }

  
  private static function getHolidaysCH($pYear)
  {
    static $cache = array();
    if (!isset($cache[$pYear])) {
      $datumOstersonntag = self::getDatumOstersonntag($pYear);

      $holidays = array(
          '01.01.' . $pYear, 
          '01.08.' . $pYear, 
          '25.12.' . $pYear  
          );

      $holidays[] = DateTimeOperations::addDays($datumOstersonntag, 39); 

      $cache[$pYear] = $holidays;
    }

    return $cache[$pYear];
  }

  
  private static function getHolidaysIT($pYear)
  {
    static $cache = array();
    if (!isset($cache[$pYear])) {
      $datumOstersonntag = self::getDatumOstersonntag($pYear);

      $holidays = array(
          '01.01.' . $pYear, 
          '06.01.' . $pYear, 
          '25.04.' . $pYear, 
          '01.05.' . $pYear, 
          '02.06.' . $pYear, 
          '15.08.' . $pYear, 
          '01.11.' . $pYear, 
          '08.12.' . $pYear, 
          '25.12.' . $pYear, 
          '26.12.' . $pYear  
          );

      $holidays[] = DateTimeOperations::addDays($datumOstersonntag, 1); 

      $cache[$pYear] = $holidays;
    }

    return $cache[$pYear];
  }

  
  public static function checkTimeCollisions($pBegin, $pEnd, $pBegin2, $pEnd2)
  {
    if (($pBegin <= $pBegin2 && $pEnd > $pBegin2)
            || ($pBegin < $pEnd2 && $pEnd >= $pEnd2)
            || ($pBegin >= $pBegin2 && $pEnd <= $pEnd2)) {
      return true;
    }

    return false;
  }

  
  public static function isDateTimeDefault(?string $param): bool
  {
    if ($param === DEFAULTDATE) {
      return true;
    }
    if ($param === DEFAULTDATE . ' ' . DEFAULTTIME) {
      return true;
    }
    if ($param === DEFAULTDATE . ' ' . DEFAULTTIME_WITH_SECONDS) {
      return true;
    }

    return false;
  }

  
  public static function isDateTimeEmptyOrDefault(?string $param): bool
  {
    if (empty($param)) {
      return true;
    }
    if (self::isDateTimeDefault($param)) {
      return true;
    }

    return false;
  }

  
  public static function isTimeDefault(?string $param): bool
  {
    if ($param === DEFAULTTIME) {
      return true;
    }
    if ($param === DEFAULTTIME_WITH_SECONDS) {
      return true;
    }

    return false;
  }

  
  public static function isTimeEmptyOrDefault(?string $param): bool
  {
    if (empty($param)) {
      return true;
    }
    if (self::isTimeDefault($param)) {
      return true;
    }

    return false;
  }

  public static function isEmptyOrDefault($param)
  {
    if (is_null($param)) {
      return true;
    }
    if ($param instanceof DateTimeObj) {
      return $param->isDefaultDate();
    }
    if ($param instanceof DateObj) {
      return $param->isDefaultDate();
    }
    if (is_string($param)) {
      if ($param === '') {
        return true;
      }
      try {
        $dt = DateTimeObj::createFromString($param);

        return $dt->isDefaultDate();
      } catch (Exception $e) {
        return false;
      }
    }

    return false;
  }

  
  public static function equals($dt1, $dt2)
  {
    if (DateTimeOperations::isEmptyOrDefault($dt1)) {
      return DateTimeOperations::isEmptyOrDefault($dt2);
    }
    if (DateTimeOperations::isEmptyOrDefault($dt2)) {
      return DateTimeOperations::isEmptyOrDefault($dt1);
    }

    $dto1 = static::toDateTimeObj($dt1);
    if ($dto1 === false) {
      return false;
    }
    $dto2 = static::toDateTimeObj($dt2);
    if ($dto2 === false) {
      return false;
    }

    return (string)$dto1 === (string)$dto2;
  }

  public static function toDateTimeObj($dt)
  {
    if ($dt instanceof DateTimeObj) {
      return $dt;
    }
    try {
      return DateTimeObj::createFromString($dt);
    } catch (Exception $ex) {
      return false;
    }
  }

  
  public static function getMonatName(int $zeitraum): string
  {
    $aktmonat = \DateTimeOperations::datefmt('MM', self::getTimestampNow());

    switch ($zeitraum) {
      case DATE_DIE_MO:
        $zielmonat = $aktmonat;
        break; 
      case DATE_NAE_MO:
        $zielmonat = ($aktmonat + 1) % 12;
        break; 
      case DATE_2_MO:
        $zielmonat = ($aktmonat + 2) % 12;
        break; 
      case DATE_3_MO:
        $zielmonat = ($aktmonat + 3) % 12;
        break; 
      default:
        return '';
    }
    
    
    
    if ($zielmonat == 0) {
      $zielmonat = 12;
    }

    return self::getNameofMonth($zielmonat);
  }

  
  public static function removeTimeIfEmptyInDateTime(string $datetime): string
  {
    $dtvalue = static::getValidDateTimeString($datetime);
    if ($dtvalue === DEFAULTDATE . ' ' . DEFAULTTIME_WITH_SECONDS
        || \DateTimeOperations::getTimeStr($dtvalue) !== DEFAULTTIME
        || \DateTimeOperations::getTimeStr($dtvalue, true) !== DEFAULTTIME_WITH_SECONDS) {
      return $datetime;
    } else {
      return \DateTimeOperations::getDateFromDatetimeStr($dtvalue);
    }
  }

  private static function hasIntlDateFormater(string $locale): bool
  {
    static $cache = array();
    if (!isset($cache[$locale])) {
      $result = extension_loaded('intl') && class_exists('\\IntlDateFormatter');
      if ($result) {
        
        try {
          
          $langName = \Locale::getDisplayLanguage($locale);
          $cache[$locale] = $langName !== '' && $langName !== $locale;
        } catch (\IntlException $ex) {
          $cache[$locale] = false;
        }
      } else {
        $cache[$locale] = false;
      }
    }

    return $cache[$locale];
  }

  
  public static function datefmt(string $format, $timestamp = null, string $locale = 'de-DE', ?string $timezone = null): string
  {
    $timezone = new \DateTimeZone($timezone ?? date_default_timezone_get());

    if (is_null($timestamp)) {
      $timestamp = new \DateTime('now', $timezone);
    } elseif (is_numeric($timestamp)) {
      $timestamp = intval($timestamp);
      if (static::hasIntlDateFormater($locale)) {
        $timestamp = date_create('@' . $timestamp, $timezone);
      } else {
        
        
        
        
        $timestamp = date_create('@' . $timestamp);
        $timestamp->setTimezone($timezone);
      }
    } elseif (is_string($timestamp)) {
      $timestamp = date_create($timestamp, $timezone);
    }

    if (!static::hasIntlDateFormater($locale)) {
      $format = \StringOperations::str_fast_parallel_replace(
        $format,
        array(
          'yyyy' => 'Y', 'yy' => 'y', 'y' => 'Y',
          'MMMM' => 'F', 'MMM' => 'M', 'MM' => 'm', 'M' => 'n',
          'dd' => 'd', 'd' => 'j',
          'EEEE' => 'l', 'EEE' => 'D', 'EE' => 'D', 'E' => 'D',
          'eeee' => 'l', 'eee' => 'D', 'e' => 'N',
          'hh' => 'h', 'h' => 'g',
          'HH' => 'H', 'H' => 'G',
          'kk' => 'H', 'k' => 'G',
          'mm' => 'i', 'm' => 'i' ,
          'ss' => 's', 's' => 'i' ,
          'aaa' => 'A', 'aa' => 'A', 'a' => 'A',
          'zzzz' => 'TP', 'zzz' => 'O', 'zz' => 'O', 'z' => 'O')
      );

      $result = $timestamp->format($format);
      $hasWeekdayInText = \StringOperations::contains($format, 'l');
      $hasMonthInText = \StringOperations::contains($format, 'F');
      
      
      $isGerman = in_array($locale, array('de-DE', 'de_DE', 'de'));
      if ($hasWeekdayInText && $isGerman) {
        $result = str_replace(
          array('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'),
          array('Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'),
          $result
        );
      }
      if ($hasMonthInText && $isGerman) {
        $result = str_replace(
          array('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'),
          array('Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'),
          $result
        );
      }

      return $result;
    }

    $fmt = new \IntlDateFormatter(
      $locale,
      IntlDateFormatter::FULL,
      IntlDateFormatter::FULL,
      $timezone,
      IntlDateFormatter::GREGORIAN,
      $format
    );

    return $fmt->format($timestamp);
  }

  public function areDateTimeEqual(string $dateTime0, string $dateTime1): bool
  {
    return static::getValidDateTimeString($dateTime0) === static::getValidDateTimeString($dateTime1);
  }

  
  public static function getValidWoTagWithAlleString($wotag): string
  {
    if (is_string($wotag)) {
      if ($wotag === '' || $wotag === 'alle') {
        return $wotag;
      }

      $wotag = \array_map('\\trim', explode(',', $wotag));
    }

    $order = array('Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So');
    $wotag = array_unique(array_filter($wotag, static function(string $str) use ($order): bool {
      return in_array($str, $order);
    }));
    $flipped_order = array_flip($order);

    usort($wotag, static function($lhs, $rhs) use ($flipped_order): int {
      $leftPos = $flipped_order[$lhs];
      $rightPos = $flipped_order[$rhs];

      return $leftPos <=> $rightPos;
    });

    $wotag = implode(', ', $wotag);
    if ($wotag === 'Mo, Di, Mi, Do, Fr, Sa, So') { 
      return 'alle';
    }

    return $wotag;
  }

  
  public static function getWoTageAsArray(string $wotag): array
  {
    if ($wotag === 'alle') {
      $monday = true;
      $tuesday = true;
      $wednesday = true;
      $thursday = true;
      $friday = true;
      $saturday = true;
      $sunday = true;
    } else {
      $wotage = array_map('\\trim', explode(',', $wotag));
      $monday = in_array('Mo', $wotage);
      $tuesday = in_array('Di', $wotage);
      $wednesday = in_array('Mi', $wotage);
      $thursday = in_array('Do', $wotage);
      $friday = in_array('Fr', $wotage);
      $saturday = in_array('Sa', $wotage);
      $sunday = in_array('So', $wotage);
    }

    return array(
      self::MONDAY => $monday,
      self::TUESDAY => $tuesday,
      self::WEDNESDAY => $wednesday,
      self::THURSDAY => $thursday,
      self::FRIDAY => $friday,
      self::SATURDAY => $saturday,
      self::SUNDAY => $sunday
    );
  }

  
  public static function getDatesBetweenGenerator(string $von, string $bis)
  {
    assert(!static::isEmptyOrDefault($von));
    assert(!static::isEmptyOrDefault($bis));
    if (static::isDateBefore($bis, $von)) { 
      $tmp = $bis;
      $bis = $von;
      $von = $tmp;
      unset($tmp);
    }

    $current = $von;

    while ($current !== $bis) {
      yield $current;
      $current = \DateTimeOperations::addDays($current, 1);
    }

    yield $current;
  }

  
  public static function compareDate(?string $a, ?string $b): int
  {
    $aIsDefault = \DateTimeOperations::isEmptyOrDefault($a);
    $bIsDefault = \DateTimeOperations::isEmptyOrDefault($b);

    if ($aIsDefault || $bIsDefault) {
      return (int)(!$aIsDefault) - (int)(!$bIsDefault);
    }

    return \DateTimeOperations::getTimestampByDatestring($a, '', true) - \DateTimeOperations::getTimestampByDatestring($b, '', true);
  }

  
  public static function compareDateTime(string $a, string $b): int
  {
    $aIsDefault = \DateTimeOperations::isDateTimeEmptyOrDefault($a);
    $bIsDefault = \DateTimeOperations::isDateTimeEmptyOrDefault($b);

    if ($aIsDefault || $bIsDefault) {
      return (int)(!$aIsDefault) - (int)(!$bIsDefault);
    }

    return \DateTimeOperations::getTimestampByDateTimeString($a, true) - \DateTimeOperations::getTimestampByDateTimeString($b, true);
  }

  
  public static function findEarliestDate(iterable $dates): ?string
  {
    return (new \Kufer\Web\Classes\Stream\IterableStream($dates))
      ->filter(static function(?string $date): bool {
        return !\DateTimeOperations::isEmptyOrDefault($date);
      })
      ->min(static function(string $a, string $b): int {
        return \DateTimeOperations::compareDate($a, $b);
      });
  }
}