YetiForceCompany/YetiForceCRM

View on GitHub
app/Fields/Date.php

Summary

Maintainability
C
7 hrs
Test Coverage
F
22%
<?php
/**
 * Tools for datetime class.
 *
 * @package App
 *
 * @copyright YetiForce S.A.
 * @license   YetiForce Public License 6.5 (licenses/LicenseEN.txt or yetiforce.com)
 * @author    Mariusz Krzaczkowski <m.krzaczkowski@yetiforce.com>
 * @author    Radosław Skrzypczak <r.skrzypczak@yetiforce.com>
 */

namespace App\Fields;

/**
 * DateTime class.
 */
class Date
{
    public static $jsDateFormat = [
        'dd-mm-yyyy' => 'd-m-Y',
        'mm-dd-yyyy' => 'm-d-Y',
        'yyyy-mm-dd' => 'Y-m-d',
        'dd.mm.yyyy' => 'd.m.Y',
        'mm.dd.yyyy' => 'm.d.Y',
        'yyyy.mm.dd' => 'Y.m.d',
        'dd/mm/yyyy' => 'd/m/Y',
        'mm/dd/yyyy' => 'm/d/Y',
        'yyyy/mm/dd' => 'Y/m/d',
    ];

    /**
     * Numeric representation of the day of the week.
     *
     * @var array
     */
    public static $dayOfWeekForJS = [
        'Monday' => 1,
        'Tuesday' => 2,
        'Wednesday' => 3,
        'Thursday' => 4,
        'Friday' => 5,
        'Saturday' => 6,
        'Sunday' => 0,
    ];
    /**
     * ISO-8601 numeric representation of the day of the week.
     *
     * @example date('N')
     *
     * @var array
     */
    public static $dayOfWeek = [
        'Monday' => 1,
        'Tuesday' => 2,
        'Wednesday' => 3,
        'Thursday' => 4,
        'Friday' => 5,
        'Saturday' => 6,
        'Sunday' => 7,
    ];

    /**
     * Short days translations.
     *
     * @var array
     */
    public static $shortDaysTranslations = [
        'Sunday' => 'LBL_SM_SUN',
        'Monday' => 'LBL_SM_MON',
        'Tuesday' => 'LBL_SM_TUE',
        'Wednesday' => 'LBL_SM_WED',
        'Thursday' => 'LBL_SM_THU',
        'Friday' => 'LBL_SM_FRI',
        'Saturday' => 'LBL_SM_SAT',
    ];

    /**
     * Current user JS date format.
     *
     * @param bool $format
     *
     * @return bool|string
     */
    public static function currentUserJSDateFormat($format = false)
    {
        if ($format) {
            return static::$jsDateFormat[$format];
        }
        return static::$jsDateFormat[\App\User::getCurrentUserModel()->getDetail('date_format')] ?? false;
    }

    /**
     * This function returns the date in user specified format.
     * limitation is that mm-dd-yyyy and dd-mm-yyyy will be considered same by this API.
     * As in the date value is on mm-dd-yyyy and user date format is dd-mm-yyyy then the mm-dd-yyyy
     * value will be return as the API will be considered as considered as in same format.
     * this due to the fact that this API tries to consider the where given date is in user date
     * format. we need a better gauge for this case.
     *
     * @param string $value the date which should a changed to user date format
     *
     * @return string
     */
    public static function formatToDisplay($value)
    {
        if (empty($value) || '0000-00-00' === $value || '0000-00-00 00:00:00' === $value) {
            return '';
        }
        if ('now' === $value) {
            $value = null;
        }
        return (new \DateTimeField($value))->getDisplayDate();
    }

    /**
     * Convert date from database format to user format.
     *
     * @param array $range ['2018-02-03','2018-02-04']
     *
     * @return array|bool ['03.02.2018','04.02.2018'] or false
     */
    public static function formatRangeToDisplay($range)
    {
        if (\is_array($range)) {
            if (!empty($range[0]) && !empty($range[1])) {
                return [
                    static::formatToDisplay($range[0]),
                    static::formatToDisplay($range[1]),
                ];
            }
            return false;
        }
        return false;
    }

    /**
     * Function to get date value for db format.
     *
     * @param string $value        Date
     * @param bool   $leadingZeros
     *
     * @return string
     */
    public static function formatToDb($value, $leadingZeros = false)
    {
        if ($leadingZeros) {
            $delim = ['/', '.'];
            foreach ($delim as $delimiter) {
                $x = strpos($value, $delimiter);
                if (false !== $x) {
                    $value = str_replace($delimiter, '-', $value);
                    break;
                }
            }
            [$y, $m, $d] = explode('-', $value);
            if (1 == \strlen($y)) {
                $y = '0' . $y;
            }
            if (1 == \strlen($m)) {
                $m = '0' . $m;
            }
            if (1 == \strlen($d)) {
                $d = '0' . $d;
            }
            $value = implode('-', [$y, $m, $d]);
        }
        return (new \DateTimeField($value))->getDBInsertDateValue();
    }

    /**
     * Convert date to single items.
     *
     * @param string      $date
     * @param bool|string $format Date format
     *
     * @return array Array date list($y, $m, $d)
     */
    public static function explode($date, $format = false)
    {
        if (empty($format)) {
            $format = 'yyyy-mm-dd';
        }
        switch ($format) {
            case 'dd-mm-yyyy':
                [$d, $m, $y] = array_pad(explode('-', $date, 3), 3, null);
                break;
            case 'mm-dd-yyyy':
                [$m, $d, $y] = array_pad(explode('-', $date, 3), 3, null);
                break;
            case 'yyyy-mm-dd':
                [$y, $m, $d] = array_pad(explode('-', $date, 3), 3, null);
                break;
            case 'dd.mm.yyyy':
                [$d, $m, $y] = array_pad(explode('.', $date, 3), 3, null);
                break;
            case 'mm.dd.yyyy':
                [$m, $d, $y] = array_pad(explode('.', $date, 3), 3, null);
                break;
            case 'yyyy.mm.dd':
                [$y, $m, $d] = array_pad(explode('.', $date, 3), 3, null);
                break;
            case 'dd/mm/yyyy':
                [$d, $m, $y] = array_pad(explode('/', $date, 3), 3, null);
                break;
            case 'mm/dd/yyyy':
                [$m, $d, $y] = array_pad(explode('/', $date, 3), 3, null);
                break;
            case 'yyyy/mm/dd':
                [$y, $m, $d] = array_pad(explode('/', $date, 3), 3, null);
                break;
            default:
                break;
        }
        return [$y, $m, $d];
    }

    /**
     * Get day from date or datetime.
     *
     * @param string $date
     * @param bool   $shortName
     * @param mixed  $translated
     *
     * @return string
     */
    public static function getDayFromDate($date, $shortName = false, $translated = false)
    {
        if ($translated) {
            return \App\Language::translate(date($shortName ? 'D' : 'l', strtotime($date)), $shortName ? 'Vtiger' : 'Calendar');
        }
        return date($shortName ? 'D' : 'l', strtotime($date));
    }

    /**
     * Get short days of week.
     *
     * @param bool $byId associative array by day id
     *
     * @return array
     */
    public static function getShortDaysOfWeek(bool $byId = true)
    {
        $days = [];
        foreach (static::$dayOfWeek as $day => $id) {
            if ($byId) {
                $days[$id] = static::$shortDaysTranslations[$day];
            } else {
                $days[static::$shortDaysTranslations[$day]] = $id;
            }
        }
        return $days;
    }

    /**
     * Gets list of holidays.
     *
     * @param string $start
     * @param string $end
     *
     * @return array
     */
    public static function getHolidays(string $start = '', string $end = ''): array
    {
        if (\App\Cache::has('Date::getHolidays', $start . $end)) {
            return \App\Cache::get('Date::getHolidays', $start . $end);
        }
        $query = (new \App\Db\Query())->from('vtiger_publicholiday');
        if ($start && $end) {
            $query->where(['between', 'holidaydate', $start, $end]);
        }
        $query->orderBy(['holidaydate' => SORT_ASC]);
        $holidays = [];
        $dataReader = $query->createCommand()->query();
        while ($row = $dataReader->read()) {
            $holidays[$row['holidaydate']] = [
                'id' => $row['publicholidayid'],
                'date' => $row['holidaydate'],
                'name' => $row['holidayname'],
                'type' => $row['holidaytype'],
                'day' => \App\Language::translate(date('l', strtotime($row['holidaydate'])), 'PublicHoliday'),
            ];
        }
        $dataReader->close();
        \App\Cache::save('Date::getHolidays', $start . $end, $holidays);
        return $holidays;
    }

    /**
     * Get closest working day from given data.
     *
     * @param \DateTime $date
     * @param string    $modify
     * @param int       $id
     *
     * @return string
     */
    public static function getWorkingDayFromDate(\DateTime $date, string $modify, int $id = \App\Utils\BusinessHours::DEFAULT_BUSINESS_HOURS_ID): string
    {
        $value = $date->modify($modify)->format('Y-m-d');
        $businessHours = \App\Utils\BusinessHours::getBusinessHoursById($id);
        $workingDays = explode(',', $businessHours['working_days'] ?? '1,2,3,4,5');
        $holidays = [];
        if ($businessHours['holidays'] ?? 1) {
            $holidays = self::getHolidays();
        }
        $iterator = 31;
        while (isset($holidays[$value]) || !\in_array($date->format('N'), $workingDays)) {
            $value = $date->modify($modify[0] . '1 day')->format('Y-m-d');
            if (0 === --$iterator) {
                throw new \App\Exceptions\AppException('Exceeded the recursive limit, a loop might have been created.');
            }
        }
        return $value;
    }

    /**
     * Method to return date counted only using working days.
     *
     * @param \DateTime $date
     * @param int       $counter
     * @param bool      $increase
     * @param int       $id
     *
     * @return string
     */
    public static function getOnlyWorkingDayFromDate(\DateTime $date, int $counter, bool $increase = true, int $id = \App\Utils\BusinessHours::DEFAULT_BUSINESS_HOURS_ID): string
    {
        $value = $date->format('Y-m-d');
        while ($counter-- > 0) {
            $value = self::getWorkingDayFromDate($date, ($increase ? '+' : '-') . '1 day', $id);
        }
        return $value;
    }

    /**
     * Function changes the date format to the database format without changing the time zone.
     *
     * @param string $date
     * @param string $fromFormat
     *
     * @return string
     */
    public static function sanitizeDbFormat(string $date, string $fromFormat)
    {
        $dbDate = '';
        if ($date) {
            [$y, $m, $d] = self::explode($date, $fromFormat);
            if (!$y || !$m || !$d) {
                if (false !== strpos($date, '-')) {
                    $separator = '-';
                } elseif (false !== strpos($date, '.')) {
                    $separator = '.';
                } elseif (false !== strpos($date, '/')) {
                    $separator = '/';
                }
                $formatToConvert = str_replace(['/', '.'], '-', $fromFormat);
                $dateToConvert = str_replace($separator, '-', $date);
                switch ($formatToConvert) {
                case 'dd-mm-yyyy':
                    [$d, $m, $y] = explode('-', $dateToConvert, 3);
                    break;
                case 'mm-dd-yyyy':
                    [$m, $d, $y] = explode('-', $dateToConvert, 3);
                    break;
                case 'yyyy-mm-dd':
                    [$y, $m, $d] = explode('-', $dateToConvert, 3);
                    break;
                default:
                    break;
            }
                $dbDate = $y . '-' . $m . '-' . $d;
            } else {
                $dbDate = $y . '-' . $m . '-' . $d;
            }
        }

        return $dbDate;
    }

    /**
     * Check if the date is correct.
     *
     * @param string      $date
     * @param string|null $format
     *
     * @return bool
     */
    public static function isValid(string $date, ?string $format = null): bool
    {
        return (false !== strpos($date, '-') || false !== strpos($date, '.') || false !== strpos($date, '/'))
        && ([$y, $m, $d] = self::explode($date, $format))
        && is_numeric($m) && is_numeric($d) && is_numeric($y)
        && checkdate($m, $d, $y) && strtotime("{$y}-{$m}-{$d}");
    }
}