You are here

class DatexFormatter in Datex 7

Date tools for Jalali Dates.

Hierarchy

Expanded class hierarchy of DatexFormatter

File

datex_api/datex_api_classes.inc, line 27
API and helper functions used by other datex modules.

View source
class DatexFormatter {
  protected static $daysInGregorianMonth = array(
    31,
    28,
    31,
    30,
    31,
    30,
    31,
    31,
    30,
    31,
    30,
    31,
  );
  protected static $daysInJalaliMonth = array(
    31,
    31,
    31,
    31,
    31,
    31,
    30,
    30,
    30,
    30,
    30,
    29,
  );

  /**
   * Similar to php date_format.
   *
   * @param mixed $date
   *   Date to format. For a list of accepted dates see ObjectFromDate.
   * @param DateTimeZone $tz
   *   DateTimeZone object or name (string) of timezone area.
   * @param bool $use_intl
   *   Wheter to use PHP-Intl or not, recomended: TRUE.
   * @param array $intl_args
   *   Arguments passed to PHP-Intl formatter.
   *
   * @return string
   *   formatted date.
   */
  public static function format($date, $format, $tz = NULL, $use_intl = DATEX_USE_INTL, $intl_args = array()) {
    if (self::hasINTL() && $use_intl) {
      return self::formatINTL($date, $format, $tz, $intl_args);
    }
    else {
      return self::formatPHP($date, $format, $tz);
    }
  }

  /**
   * Returns array containing names of days and monthes in persian.
   *
   * @todo replace with a translator function like Drupal's t().
   */
  public static function persianDateNames() {
    static $names = NULL;
    if (!$names) {
      $names = array(
        'months' => array(
          1 => 'فروردین',
          2 => 'اردیبهشت',
          3 => 'خرداد',
          4 => 'تیر',
          5 => 'مرداد',
          6 => 'شهریور',
          7 => 'مهر',
          8 => 'آبان',
          9 => 'آذر',
          10 => 'دی',
          11 => 'بهمن',
          12 => 'اسفند',
        ),
        'ampm' => array(
          'am' => 'ق.ظ',
          'pm' => 'ب.ظ',
        ),
        'day_abbr' => array(
          6 => 'ش.',
          7 => 'ی.',
          1 => 'د.',
          2 => 'س.',
          3 => 'چ.',
          4 => 'پ.',
          5 => 'ج.',
        ),
        'day' => array(
          6 => 'شنبه',
          7 => 'یک‌شنبه',
          1 => 'دوشنبه',
          2 => 'سه‌شنبه',
          3 => 'چهارشنبه',
          4 => 'پنج‌شنبه',
          5 => 'جمعه',
        ),
        'tz' => 'تهران',
      );
    }
    return $names;
  }

  /**
   * Converts a Gregorian date to Jalali.
   */
  public static function toJalali($gregorian_year = 0, $gregorian_month = 0, $gregorian_day = 0) {
    $now = getdate();
    $gregorian_year = ($gregorian_year ? $gregorian_year : $now['year']) - 1600;
    $gregorian_month = ($gregorian_month ? $gregorian_month : $now['mon']) - 1;
    $gregorian_day = ($gregorian_day ? $gregorian_day : $now['mday']) - 1;
    $gregorian_day_no = 365 * $gregorian_year + intval(($gregorian_year + 3) / 4) - intval(($gregorian_year + 99) / 100) + intval(($gregorian_year + 399) / 400);
    for ($i = 0; $i < $gregorian_month; ++$i) {
      $gregorian_day_no += self::$daysInGregorianMonth[$i];
    }
    if ($gregorian_month > 1 && ($gregorian_year % 4 == 0 && $gregorian_year % 100 != 0) || $gregorian_year % 400 == 0) {

      // Leap and after Feb.
      $gregorian_day_no++;
    }
    $gregorian_day_no += $gregorian_day;
    $jalali_day_no = $gregorian_day_no - 79;
    $j_np = intval($jalali_day_no / 12053);
    $jalali_day_no = $jalali_day_no % 12053;
    $j_year = 979 + 33 * $j_np + 4 * intval($jalali_day_no / 1461);
    $jalali_day_no %= 1461;
    if ($jalali_day_no >= 366) {
      $j_year += intval(($jalali_day_no - 1) / 365);
      $jalali_day_no = ($jalali_day_no - 1) % 365;
    }
    for ($i = 0; $i < 11 && $jalali_day_no >= self::$daysInJalaliMonth[$i]; ++$i) {
      $jalali_day_no -= self::$daysInJalaliMonth[$i];
    }
    $j_month = $i + 1;
    $j_day = $jalali_day_no + 1;
    return array(
      'day' => $j_day,
      'month' => $j_month,
      'year' => $j_year,
    );
  }

  /**
   * Converts a Jalali date to Gregorian.
   */
  public static function toGregorian($jalali_year = 0, $jalali_month = 0, $jalali_day = 0) {
    $now = self::toJalali();
    $jalali_year = ($jalali_year ? $jalali_year : $now['year']) - 979;
    $jalali_month = ($jalali_month ? $jalali_month : $now['month']) - 1;
    $jalali_day = ($jalali_day ? $jalali_day : $now['day']) - 1;
    $jalali_day_no = 365 * $jalali_year + intval($jalali_year / 33) * 8 + intval(($jalali_year % 33 + 3) / 4);
    for ($i = 0; $i < $jalali_month; ++$i) {
      $jalali_day_no += self::$daysInJalaliMonth[$i];
    }
    $jalali_day_no += $jalali_day;
    $gregorian_day_no = $jalali_day_no + 79;
    $g_year = 1600 + 400 * intval($gregorian_day_no / 146097);
    $gregorian_day_no = $gregorian_day_no % 146097;
    $leap = TRUE;
    if ($gregorian_day_no >= 36525) {
      $gregorian_day_no--;
      $g_year += 100 * intval($gregorian_day_no / 36524);
      $gregorian_day_no = $gregorian_day_no % 36524;
      if ($gregorian_day_no >= 365) {
        $gregorian_day_no++;
      }
      else {
        $leap = FALSE;
      }
    }
    $g_year += 4 * intval($gregorian_day_no / 1461);
    $gregorian_day_no %= 1461;
    if ($gregorian_day_no >= 366) {
      $leap = FALSE;
      $gregorian_day_no--;
      $g_year += intval($gregorian_day_no / 365);
      $gregorian_day_no = $gregorian_day_no % 365;
    }
    for ($i = 0; $gregorian_day_no >= self::$daysInGregorianMonth[$i] + ($i == 1 && $leap); $i++) {
      $gregorian_day_no -= self::$daysInGregorianMonth[$i] + ($i == 1 && $leap);
    }
    $g_month = $i + 1;
    $g_day = $gregorian_day_no + 1;
    return array(
      'year' => $g_year,
      'month' => $g_month,
      'day' => $g_day,
    );
  }

  /**
   * Converts date format string (like 'Y-m-d') to it's PHP-Intl equivilant.
   *
   * @param string $format
   *   Format accepted by php date_format.
   *
   * @return string
   *   Format accepted by PHP-Intl date formatter (ICU).
   */
  public static function phpToIntl($format) {
    static $format_map = NULL;
    if (!$format_map) {
      $format_map = array(
        'd' => 'dd',
        'D' => 'EEE',
        'j' => 'd',
        'l' => 'EEEE',
        'N' => 'e',
        'S' => 'LLLL',
        'w' => '',
        'z' => 'D',
        'W' => 'w',
        'm' => 'MM',
        'M' => 'MMM',
        'F' => 'MMMM',
        'n' => 'M',
        't' => '',
        'L' => '',
        'o' => 'yyyy',
        'y' => 'yy',
        'Y' => 'YYYY',
        'a' => 'a',
        'A' => 'a',
        'B' => '',
        'g' => 'h',
        'G' => 'H',
        'h' => 'hh',
        'H' => 'HH',
        'i' => 'mm',
        's' => 'ss',
        'u' => 'SSSSSS',
        'e' => 'z',
        'I' => '',
        'O' => 'Z',
        'P' => 'ZZZZ',
        'T' => 'v',
        'Z' => '',
        'c' => '',
        'r' => '',
        'U' => '',
        ' ' => ' ',
        '-' => '-',
        '.' => '.',
        '-' => '-',
        ':' => ':',
      );
    }
    $replace_pattern = '/[^ \\:\\-\\/\\.\\\\dDjlNSwzWmMFntLoyYaABgGhHisueIOPTZcrU]/';
    return strtr(preg_replace($replace_pattern, '', $format), $format_map);
  }

  /**
   * Formats a date according to format given.
   *
   * This function uses internal
   * methods for converting, See DatexFormatter::formatINTL is suggested
   * instead.
   */
  public static function formatPHP($date, $format, $tz) {
    $persian_date_names = self::persianDateNames();

    // For anyone reading this comment: Passing a DateTimeZone to datetime
    // constructor has no effect on it! You MUST use setTimezone to set a
    // tz on the stupid object.
    // Tested on PHP 5.4.15 (built: May 12 2013 13:11:23) Archlinux.
    $date = self::ObjectFromDate($date);
    if ($tz) {
      $tz = self::getTzObject($tz);
      $date
        ->setTimezone($tz);
    }
    $gregorian_date = array(
      'd' => intval($date
        ->format('j')),
      'm' => intval($date
        ->format('n')),
      'Y' => intval($date
        ->format('Y')),
    );
    $jalali_date = self::toJalali($gregorian_date['Y'], $gregorian_date['m'], $gregorian_date['d']);
    $z = $jalali_date['month'] <= 6 ? $jalali_date['month'] * 31 + $jalali_date['day'] : 186 + (($jalali_date['month'] - 6) * 30 + $jalali_date['day']);
    $format = preg_replace('/[\\\\][a-z]/', '', $format);
    $is_leap = self::isLeap($jalali_date['year']) ? 1 : 0;
    $replacements = array(
      'd' => sprintf('%02d', $jalali_date['day']),
      'D' => $persian_date_names['day_abbr'][$date
        ->format('N')],
      'j' => $jalali_date['day'],
      'l' => $persian_date_names['day'][$date
        ->format('N')],
      'S' => $persian_date_names['day_abbr'][$date
        ->format('N')],
      'F' => $persian_date_names['months'][$jalali_date['month']],
      'm' => sprintf('%02d', $jalali_date['month']),
      'n' => $jalali_date['month'],
      'L' => str_replace('L', $is_leap, $format),
      'Y' => $jalali_date['year'],
      'y' => $jalali_date['year'],
      'o' => $jalali_date['year'],
      'a' => $persian_date_names['ampm'][$date
        ->format('a')],
      'A' => $persian_date_names['ampm'][$date
        ->format('a')],
      'B' => $persian_date_names['ampm'][$date
        ->format('a')],
      'c' => $jalali_date['year'] . '-' . $jalali_date['month'] . '-' . $jalali_date['day'] . 'T' . $date
        ->format('H:i:sP'),
      'g' => $date
        ->format('g'),
      'G' => $date
        ->format('G'),
      'h' => $date
        ->format('h'),
      'H' => $date
        ->format('H'),
      'i' => $date
        ->format('i'),
      's' => $date
        ->format('s'),
      'u' => $date
        ->format('u'),
      'I' => $date
        ->format('I'),
      'O' => $date
        ->format('O'),
      'P' => $date
        ->format('P'),
      'T' => $date
        ->format('T'),
      'Z' => $date
        ->format('Z'),
      'U' => $date
        ->format('U'),
      'w' => $date
        ->format('w'),
      'N' => $date
        ->format('N'),
      'e' => $date
        ->format('e'),
      'z' => $z,
      'W' => ceil($z / 7),
      't' => self::$daysInJalaliMonth[$jalali_date['month'] - 1],
    );
    $replacements['r'] = $persian_date_names['day_abbr'][$date
      ->format('N')] . ', ' . $jalali_date['day'] . ' ' . $persian_date_names['months'][$jalali_date['month']] . ' ' . $jalali_date['year'] . $date
      ->format('H:i:s P');
    if ($is_leap && $jalali_date['month'] == 12) {
      $replacements['t'] = 30;
    }
    else {
      $replacements['t'] = self::$daysInJalaliMonth[$jalali_date['month'] - 1];
    }
    return strtr($format, $replacements);
  }

  /**
   * Formats a date according to format given.
   *
   * This function uses php-intl methods for converting. PECL package php-intl
   * must be enabled.
   */
  public static function formatINTL($date, $format, $tz, $formatter_args) {
    $intl_formatter = new IntlDateFormatter("fa_IR@calendar=persian", IntlDateFormatter::FULL, IntlDateFormatter::FULL, self::getTzString($tz), IntlDateFormatter::TRADITIONAL, self::phpToIntl($format));
    $date = $intl_formatter
      ->format(self::getTimestamp($date));

    // By default undecorates decorated date by intl.
    return isset($formatter_args['decor']) ? $date : self::decor($date, FALSE);
  }

  /**
   * Created a DateTime object containing date from $date.
   *
   * Accepted date formats are an integer (as timestamp), A DatexObject object,
   * A DateTime or DateObject object or an array.
   *
   * If an array is given and $gregorian is set to FALSE, Then Object is
   * extracted from a Jalali date. This is the only way of getting date from
   * jalali date.
   */
  public static function ObjectFromDate($date = NULL, $tz = NULL, $gregorian = TRUE) {

    // If is a number or a timestamp string: like 432432 or '432432', it
    // will become like '@432432'. This case covers both integer and string.
    if (is_numeric($date)) {
      $date = '@' . $date;
    }
    elseif (is_object($date)) {
      $date = '@' . $date
        ->format('U');
    }
    elseif (is_array($date)) {
      if (!$gregorian) {
        $greg_date = self::toGregorian(@$date['year'], @$date['month'], @$date['day']);
        $year = $greg_date['year'];
        $month = $greg_date['month'];
        $day = $greg_date['day'];
      }
      else {
        $year = isset($date['year']) ? intval($date['year']) : intval(date('Y'));
        $month = isset($date['month']) ? intval($date['month']) : intval(date('n'));
        $day = isset($date['day']) ? intval($date['day']) : intval(date('j'));
      }
      $hour = isset($date['hour']) ? intval($date['hour']) : intval(date('G'));
      $minute = isset($date['minute']) ? intval($date['minute']) : intval(date('i'));
      $second = isset($date['second']) ? intval($date['second']) : intval(date('s'));
      $date = '@' . mktime($hour, $minute, $second, $month, $day, $year);
    }
    elseif ($date == NULL) {
      $date = 'now';
    }

    // Why doesn't the freaking DateTime accept NULL az tz?!!
    return isset($tz) ? new DateTime($date, $tz) : new DateTime($date);
  }

  /**
   * Generates timestamp from given date.
   */
  public static function getTimestamp($date) {
    $date = self::ObjectFromDate($date);
    return intval($date
      ->format('U'));
  }

  /**
   * Determines wether PECL package php-intl is available or not.
   */
  public static function hasINTL() {
    return class_exists('IntlDateFormatter');
  }

  /**
   * Returns non zero if given year is a leap year.
   *
   * Algorithm author Amin Saeedi <amin.w3dev@gmail.com>,
   * Find him on github! @link http://github.org/amsa
   */
  public static function isLeap($year_value) {
    return array_search(($year_value + 2346) % 2820 % 128, array(
      5,
      9,
      13,
      17,
      21,
      25,
      29,
      34,
      38,
      42,
      46,
      50,
      54,
      58,
      62,
      67,
      71,
      75,
      79,
      83,
      87,
      91,
      95,
      100,
      104,
      108,
      112,
      116,
      120,
      124,
      0,
    ));
  }

  /**
   * Returns a valid timezone object from given timezone.
   *
   * Returns $tz itself if it is already a DateTimeZone object.
   */
  public static function getTzObject($tz = NULL) {
    if ($tz) {
      if (is_string($tz)) {
        $tz = new DateTimeZone($tz);
      }
      elseif (!$tz instanceof DateTimeZone) {
        $tz = new DateTimeZone(date_default_timezone_get());
      }
    }
    return $tz;
  }

  /**
   * Returns a valid timezone string from given timezone.
   */
  public static function getTzString($tz) {
    $tz = self::getTzObject($tz);
    return method_exists($tz, 'getName') ? $tz
      ->getName() : NULL;
  }

  /**
   * Translates English numbers to Persian numbers and vice versa.
   *
   * @param bool $decorate
   *   If true, Returned string contains Persian numbers and if false, will
   *   contain English numbers.
   */
  public static function decor($value, $decorate = FALSE) {
    static $to_en = array(
      '۱' => 1,
      '۲' => 2,
      '۳' => 3,
      '۴' => 4,
      '۵' => 5,
      '۶' => 6,
      '۷' => 7,
      '۸' => 8,
      '۹' => 9,
      '۰' => 0,
    );
    static $to_fa = array(
      1 => '۱',
      2 => '۲',
      3 => '۳',
      4 => '۴',
      5 => '۵',
      6 => '۶',
      7 => '۷',
      8 => '۸',
      9 => '۹',
      0 => '۰',
    );
    return strtr($value, $decorate ? $to_fa : $to_en);
  }

  /**
   * For extracting date from formatted date strings.
   */
  public static function parser($date, $format) {
    if (!(is_string($date) && is_string($format))) {
      throw new Exception('Datex parser: date or format not valid.');
    }
    $granularities = array();
    $j = $i = 0;
    $format_len = strlen($format);
    do {
      while (!ctype_alpha($format[$i]) && $i < $format_len) {
        $i++;
      }
      while (!ctype_digit($date[$j])) {
        $j++;
      }
      switch ($format[$i]) {
        case 'd':
          $granularities['day'] = $date[$j] . $date[++$j];
          break;
        case 'j':
          if (ctype_digit($date[$j + 1])) {
            $granularities['day'] = $date[$j] . $date[++$j];
          }
          else {
            $granularities['day'] = $date[$j];
          }
          break;
        case 'm':
          $granularities['month'] = $date[$j] . $date[++$j];
          break;
        case 'n':
          if (ctype_digit($date[$j + 1])) {
            $granularities['month'] = $date[$j] . $date[++$j];
          }
          else {
            $granularities['month'] = $date[$j];
          }
          break;
        case 'y':
        case 'Y':
          $granularities['year'] = $date[$j] . $date[++$j] . $date[++$j] . $date[++$j];
        case 'h':
          $granularities['hour'] = $date[$j] . $date[++$j];
          break;
        case 'g':
          if (ctype_digit($date[$j + 1])) {
            $granularities['hour'] = $date[$j] . $date[++$j];
          }
          else {
            $granularities['hour'] = $date[$j];
          }
          break;
        case 'H':
          $granularities['hour'] = $date[$j] . $date[++$j];
          break;
        case 'G':
          if (ctype_digit($date[$j + 1])) {
            $granularities['hour'] = $date[$j] . $date[++$j];
          }
          else {
            $granularities['hour'] = $date[$j];
          }
          break;
        case 'i':
          $granularities['minute'] = $date[$j] . $date[++$j];
          break;
        case 's':
          $granularities['second'] = $date[$j] . $date[++$j];
          break;
      }
      $j++;
    } while (++$i < $format_len);
    return $granularities;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
DatexFormatter::$daysInGregorianMonth protected static property
DatexFormatter::$daysInJalaliMonth protected static property
DatexFormatter::decor public static function Translates English numbers to Persian numbers and vice versa.
DatexFormatter::format public static function Similar to php date_format.
DatexFormatter::formatINTL public static function Formats a date according to format given.
DatexFormatter::formatPHP public static function Formats a date according to format given.
DatexFormatter::getTimestamp public static function Generates timestamp from given date.
DatexFormatter::getTzObject public static function Returns a valid timezone object from given timezone.
DatexFormatter::getTzString public static function Returns a valid timezone string from given timezone.
DatexFormatter::hasINTL public static function Determines wether PECL package php-intl is available or not.
DatexFormatter::isLeap public static function Returns non zero if given year is a leap year.
DatexFormatter::ObjectFromDate public static function Created a DateTime object containing date from $date.
DatexFormatter::parser public static function For extracting date from formatted date strings.
DatexFormatter::persianDateNames public static function Returns array containing names of days and monthes in persian.
DatexFormatter::phpToIntl public static function Converts date format string (like 'Y-m-d') to it's PHP-Intl equivilant.
DatexFormatter::toGregorian public static function Converts a Jalali date to Gregorian.
DatexFormatter::toJalali public static function Converts a Gregorian date to Jalali.