Classe DateTime do PHP sensível à localidade

Artigo que apresenta a implementação de uma classe PHP que permite a exibição de datas de acordo com a localidade.

Um dos inconvenientes da classe DateTime é que ela consegue formatar datas apenas em inglês, diferente da função strftime, que tem seu comportamento afetado pela localidade definida na função setlocale.

Para resolver este problema, criei uma classe chamada MyDateTime, que estende a classe DateTime e oferece algumas funcionalidades adicionais como os métodos:

  • alternateFormat - Para formatar uma data com o formato aceito por strftime.
  • getWeekNames - Obtém os nomes dos dias da semana.
  • getShortWeekNames - Obtém os nomes dos dias da semana abreviados.
  • getMonthNames - Obtém os nomes dos meses do ano.
  • getShortMonthNames - Obtém os nomes dos meses do ano abreviados.


Linguagem: PHP

Copyright 2010 Rubens Takiguti Ribeiro

Licença: LGPL 3 ou superior

/**
 * Classe com funcionalidades adicionais de Data.
 * @author Rubens Takiguti Ribeiro
 */
class MyDateTime extends DateTime {

    /**
     * Formata uma data com um formato aceito por strftime.
     * Algumas informacoes sao afetadas por setlocale.     
     * @param string $format Formato aceito por strftime
     * @return string Data formatada
     */
    public function alternateFormat($format) {
        // formato de strftime / formato de date
        $conv = array(
            '%d' => 'd',
            '%a' => 'D',
            '%e' => 'j',
            '%A' => 'l',
            '%u' => 'N',
            '%w' => 'w',
            '%j' => 'z',
            '%V' => 'W',
            '%B' => 'F',
            '%m' => 'm',
            '%b' => 'M',
            '%h' => 'M',
            '%G' => 'o',
            '%Y' => 'Y',
            '%y' => 'y',
            '%P' => 'a',
            '%p' => 'A',
            '%l' => 'g',
            '%I' => 'h',
            '%H' => 'H',
            '%M' => 'i',
            '%S' => 's',
            '%z' => 'O',
            '%Z' => 'T',
            '%s' => 'U'
        );

        $semanas           = self::getWeekNames();
        $semanas_abreviado = self::getShortWeekNames(true);
        $meses             = self::getMonthNames(false, false);
        $meses_abreviado   = self::getShortMonthNames(false, true);

        preg_match_all('/(%[^%]|[^%]+|%%)/', $format, $matches);

        $valor = '';
        foreach ($matches[1] as $match) {
            if ($match == '%%') {
                $valor .= '%';
            } elseif (substr($match, 0, 1) == '%') {

                // Tratar manualmente os codigos que sofrem interferencia de setlocale
                switch ($match) {

                // Semana abreviado
                case '%a':
                    $valor .= $semanas_abreviado[$this->format('w')];
                    break;

                // Semana
                case '%A':
                    $valor .= $semanas[$this->format('w')];
                    break;

                // Mes abreviado
                case '%h':
                case '%b':
                    $valor .= $meses_abreviado[(int)$this->format('n')];
                    break;

                // Mes
                case '%B':
                    $valor .= $meses[(int)$this->format('n')];
                    break;

                default:
                    $valor .= $this->format($conv[$match]);
                    break;
                }
            } else {
                $valor .= $match;
            }
        }
        return $valor;
    }


    /**
     * Obtem os nomes dos dias da semana de acordo com a localidade
     * definida por setlocale.
     * @return array
     */
    public static function getWeekNames() {
        static $vt_semana = null;
        if ($vt_semana !== null) {
            return $vt_semana;
        }
        $vt_semana = array();
        if (function_exists('nl_langinfo')) {
            for ($i = 1; $i <= 7; $i++) {
                $vt_semana[$i - 1] = nl_langinfo(constant('DAY_'.$i));
            }
        } else {
            $dia = (int)strftime('%d');
            $mes = (int)strftime('%m');
            $ano = (int)strftime('%Y');
            for ($i = 0; $i < 7; $i++, $dia++) {
                $time = mktime(0, 0, 0, $mes, $dia, $ano);
                $vt_semana[(int)strftime('%w', $time)] = strftime('%A', $time);
            }
            ksort($vt_semana);
        }
        return $vt_semana;
    }


    /**
     * Obtem os nomes dos dias da semana abreviados de acordo 
     * com a localidade definida por setlocale.
     * @return array
     */
    public static function getShortWeekNames() {
        static $vt_semana = null;
        if ($vt_semana !== null) {
            return $vt_semana;
        }
        $vt_semana = array();
        if (function_exists('nl_langinfo')) {
            for ($i = 1; $i <= 7; $i++) {
                $vt_semana[$i - 1] = nl_langinfo(constant('ABDAY_'.$i));
            }
        } else {
            $dia = (int)strftime('%d');
            $mes = (int)strftime('%m');
            $ano = (int)strftime('%Y');
            for ($i = 0; $i < 7; $i++, $dia++) {
                $time = mktime(0, 0, 0, $mes, $dia, $ano);
                $vt_semana[(int)strftime('%w', $time)] = strftime('%a', $time);
            }
            ksort($vt_semana);
        }
        return $vt_semana;
    }


    /**
     * Obtem os nomes dos meses de acordo com a localidade definida
     * pela funcao setlocale.
     * @return array
     */
    public static function getMonthNames() {
        static $vt_meses = null;
        if ($vt_meses !== null) {
            return $vt_meses;
        }
        $vt_meses = array();
        if (function_exists('nl_langinfo')) {
            for ($i = 1; $i <= 12; $i++) {
                $vt_meses[$i] = nl_langinfo(constant('MON_'.$i));
            }
        } else {
            $ano = (int)strftime('%Y');
            for ($mes = 1; $mes <= 12; $mes++) {
                $time = mktime(0, 0, 0, $mes, 1, $ano);
                $vt_meses[$mes] = strftime('%B', $time);
            }
        }
        return $vt_meses;
    }

    /**
     * Obtem os nomes dos meses abreviados de acordo com a
     * localidade definida pela funcao setlocale.
     * @return array
     */
    public static function getShortMonthNames() {
        static $vt_meses = null;
        if ($vt_meses !== null) {
            return $vt_meses;
        }
        $vt_meses = array();
        if (function_exists('nl_langinfo')) {
            for ($i = 1; $i <= 12; $i++) {
                $vt_meses[$i] = nl_langinfo(constant('ABMON_'.$i));
            }
        } else {
            $ano = (int)strftime('%Y');
            for ($mes = 1; $mes <= 12; $mes++) {
                $time = mktime(0, 0, 0, $mes, 1, $ano);
                $vt_meses[$mes] = strftime('%b', $time);
            }
        }
        return $vt_meses;
    }


    /**
     * Cria uma data MyDateTime a partir de um formato.
     * @param string $format Formato aceito por date
     * @param string $time Data formatada
     * @param TimeZone $timezone Timezone desejado
     * @return MyDateTime
     */
    public static function createFromFormat($format, $time, $timezone = null) { 
        if ($timezone) { 
            $data = parent::createFromFormat($format, $time, $timezone);
        } else { 
            $data = parent::createFromFormat($format, $time);
        }
        if (!$data) { 
            return false;
        }

        list($Y, $n, $j, $G, $i, $s) = explode(';', $data->format('Y;n;j;G;i;s'));

        $data2 = new self();
        $data2->setDate((int)$Y, (int)$n, (int)$j);
        $data2->setTime((int)$G, (int)$i, (int)$s);
        $data2->setTimezone($data->getTimezone());
        return $data2;
    }

}

Exemplo de uso:

$d = new MyDateTime();

setlocale(LC_TIME, 'pt_BR.UTF-8', 'Portuguese_Brazil.1252');
var_dump(MyDateTime::getWeekNames());
var_dump(MyDateTime::getShortWeekNames());
var_dump(MyDateTime::getMonthNames());
var_dump(MyDateTime::getShortMonthNames());
echo $d->alternateFormat('%A, %d de %B de %Y');

Observação: uma forma muito mais simples de se implementar o método alternateFormat, seria assim:

    public function alternateFormat($format) {
        return strftime($format, $this->getTimestamp());
    }

Porém, há um inconveniente. O timestamp inteiro em computadores de arquitetura 32bits, tem um intervalo bastante restrito e não conseguiria mostrar uma data muito distante, por exemplo 01/01/1560. No entanto, a classe DateTime consegue trabalhar com intervalos muito maiores e exibe a data corretamente no exemplo abaixo:

$d = new MyDateTime('01-01-1560');
echo $d->alternateFormat('%A, %d de %B de %Y');

0 comentários