Pergunta

Como você realmente executa operações de data e hora, como adicionar data, encontrar diferença, descobrir quantos dias, excluindo fins de semana, em um intervalo?Eu pessoalmente comecei a passar algumas dessas operações para meu banco de dados postgresql, pois normalmente eu só precisaria emitir uma instrução sql para obter uma resposta, no entanto, para fazê-lo em PHP eu teria que escrever muito mais código, o que significa mais chances para que erros ocorram...

Existe alguma biblioteca em PHP que execute operações de data e hora de uma forma que não exija muito código?isso supera o sql em uma situação em que 'Dadas duas datas, quantos dias úteis existem entre as duas datas?'Implementar em SQL ou $pet_lang' que é resolvido fazendo esta consulta?

SELECT  COUNT(*) AS total_days
FROM    (SELECT date '2008-8-26' + generate_series(0,
          (date '2008-9-1' - date '2008-8-26')) AS all_days) AS calendar
WHERE   EXTRACT(isodow FROM all_days) < 6;
Foi útil?

Solução

O objeto DateTime DateTime do PHP5+é útil porque é ciente da economia do tempo e do horário de verão, mas precisa de alguma extensão para realmente resolver o problema.Escrevi o seguinte para resolver um problema semelhante.O método find_WeekdaysFromThisTo() é de força bruta, mas funciona razoavelmente rápido se o intervalo de tempo for inferior a 2 anos.

$tryme = new Extended_DateTime('2007-8-26');
$newer = new Extended_DateTime('2008-9-1');

print 'Weekdays From '.$tryme->format('Y-m-d').' To '.$newer->format('Y-m-d').': '.$tryme -> find_WeekdaysFromThisTo($newer) ."\n";
/*  Output:  Weekdays From 2007-08-26 To 2008-09-01: 265  */
print 'All Days From '.$tryme->format('Y-m-d').' To '.$newer->format('Y-m-d').': '.$tryme -> find_AllDaysFromThisTo($newer) ."\n";
/*  Output:  All Days From 2007-08-26 To 2008-09-01: 371   */
$timefrom = $tryme->find_TimeFromThisTo($newer);
print 'Between '.$tryme->format('Y-m-d').' and '.$newer->format('Y-m-d').' there are '.
      $timefrom['years'].' years, '.$timefrom['months'].' months, and '.$timefrom['days'].
      ' days.'."\n";
/*  Output: Between 2007-08-26 and 2008-09-01 there are 1 years, 0 months, and 5 days. */

class Extended_DateTime extends DateTime {

    public function find_TimeFromThisTo($newer) {
        $timefrom = array('years'=>0,'months'=>0,'days'=>0);

        // Clone because we're using modify(), which will destroy the object that was passed in by reference
        $testnewer = clone $newer;

        $timefrom['years'] = $this->find_YearsFromThisTo($testnewer);
        $mod = '-'.$timefrom['years'].' years';
        $testnewer -> modify($mod);

        $timefrom['months'] = $this->find_MonthsFromThisTo($testnewer);
        $mod = '-'.$timefrom['months'].' months';
        $testnewer -> modify($mod);

        $timefrom['days'] = $this->find_AllDaysFromThisTo($testnewer);
        return $timefrom;
    } // end function find_TimeFromThisTo


    public function find_YearsFromThisTo($newer) {
        /*
        If the passed is:
        not an object, not of class DateTime or one of its children,
        or not larger (after) $this
        return false
        */
        if (!is_object($newer) || !($newer instanceof DateTime) || $newer->format('U') < $this->format('U'))
            return FALSE;
        $count = 0;

        // Clone because we're using modify(), which will destroy the object that was passed in by reference
        $testnewer = clone $newer;

        $testnewer -> modify ('-1 year');
        while ( $this->format('U') < $testnewer->format('U')) {
            $count ++;
            $testnewer -> modify ('-1 year');
        }
        return $count;
    } // end function find_YearsFromThisTo


    public function find_MonthsFromThisTo($newer) {
        /*
        If the passed is:
        not an object, not of class DateTime or one of its children,
        or not larger (after) $this
        return false
        */
        if (!is_object($newer) || !($newer instanceof DateTime) || $newer->format('U') < $this->format('U'))
            return FALSE;

        $count = 0;
        // Clone because we're using modify(), which will destroy the object that was passed in by reference
        $testnewer = clone $newer;
        $testnewer -> modify ('-1 month');

        while ( $this->format('U') < $testnewer->format('U')) {
            $count ++;
            $testnewer -> modify ('-1 month');
        }
        return $count;
    } // end function find_MonthsFromThisTo


    public function find_AllDaysFromThisTo($newer) {
        /*
        If the passed is:
        not an object, not of class DateTime or one of its children,
        or not larger (after) $this
        return false
        */
        if (!is_object($newer) || !($newer instanceof DateTime) || $newer->format('U') < $this->format('U'))
            return FALSE;

        $count = 0;
        // Clone because we're using modify(), which will destroy the object that was passed in by reference
        $testnewer = clone $newer;
        $testnewer -> modify ('-1 day');

        while ( $this->format('U') < $testnewer->format('U')) {
            $count ++;
            $testnewer -> modify ('-1 day');
        }
        return $count;
    } // end function find_AllDaysFromThisTo


    public function find_WeekdaysFromThisTo($newer) {
        /*
        If the passed is:
        not an object, not of class DateTime or one of its children,
        or not larger (after) $this
        return false
        */
        if (!is_object($newer) || !($newer instanceof DateTime) || $newer->format('U') < $this->format('U'))
            return FALSE;

        $count = 0;

        // Clone because we're using modify(), which will destroy the object that was passed in by reference
        $testnewer = clone $newer;
        $testnewer -> modify ('-1 day');

        while ( $this->format('U') < $testnewer->format('U')) {
            // If the calculated day is not Sunday or Saturday, count this day
            if ($testnewer->format('w') != '0' && $testnewer->format('w') != '6')
                $count ++;
            $testnewer -> modify ('-1 day');
        }
        return $count;
    } // end function find_WeekdaysFromThisTo

    public function set_Day($newday) {
        if (is_int($newday) && $newday > 0 && $newday < 32 && checkdate($this->format('m'),$newday,$this->format('Y')))
            $this->setDate($this->format('Y'),$this->format('m'),$newday);
    } // end function set_Day


    public function set_Month($newmonth) {
        if (is_int($newmonth) && $newmonth > 0 && $newmonth < 13)
            $this->setDate($this->format('Y'),$newmonth,$this->format('d'));
    } // end function set_Month


    public function set_Year($newyear) {
        if (is_int($newyear) && $newyear > 0)
            $this->setDate($newyear,$this->format('m'),$this->format('d'));
    } // end function set_Year
} // end class Extended_DateTime

Outras dicas

Enquanto para a maioria das operações de data e hora eu normalmente converteria para Unixtime e realizaria subtração de adição, etc.no número inteiro Unixtime, você pode querer dar uma olhada na classe Zend_Date da estrutura Zend.

Isso tem muitas das funcionalidades que você descreve.Embora o Zend seja considerado uma "estrutura", ele funciona excepcionalmente bem como uma biblioteca de classes para selecionar elementos.Rotineiramente, incluímos isso em projetos e, em seguida, apenas adicionamos pedaços quando precisamos deles.

strtotime() é útil, mas possui alguns comportamentos estranhos que podem aparecer de vez em quando se você não o estiver usando apenas para converter uma string formatada de data/hora.

coisas como "+1 mês" ou "-3 dias" às vezes podem não fornecer o que você espera.

Para adicionar uma data, você pode usar o método DataHora::adicionar (Adiciona uma quantidade de dias, meses, anos, horas, minutos e segundos a um objeto DateTime), disponível a partir do php 5.3.0.

Para encontrar a diferença entre duas datas, existe o DataHora::diferença método;mas não parece haver um método para contar os dias úteis entre duas datas.

PEAR::Date parece que pode ter alguma funcionalidade útil.

PEAR::Calendar também pode ser útil.

O método mais fácil é usar um carimbo de data/hora, representando o número de segundos desde 1º de janeiro de 2008.Com um tipo de carimbo de data/hora, você pode fazer coisas como...

now = time();
tomorrow = now + 24 * 60 * 60; // 24 hours * 60 minutes * 60 seconds

Confira a documentação para tempo(), data() e mktime() nas páginas da web php.Esses são os três métodos que costumo usar com mais frequência.

Você pode usar uma combinação de strtotime, mktime e data fazer a aritmética

Aqui está um exemplo que usa um combo para fazer alguma aritmética http://rushi.wordpress.com/2008/04/13/php-print-out-age-of-date-in-words/ Vou reproduzir o código aqui para simplificar

if ($timestamp_diff < (60*60*24*7)) {
   echo floor($timestamp_diff/60/60/24)." Days";
} elseif ($timestamp_diff > (60*60*24*7*4)) {
   echo floor($timestamp_diff/60/60/24/7)." Weeks";
} else {
   $total_months = $months = floor($timestamp_diff/60/60/24/30);
   if($months >= 12) {
      $months = ($total_months % 12);
      $years&nbsp; = ($total_months - $months)/12;
      echo $years . " Years ";
   }
   if($months > 0)
      echo $months . " Months";
}
?>

@Rushi eu não gosto de strtotime() pessoalmente.não sei por que, mas descobri esta manhã que passar uma string como esta '2008-09-11 9:5 AM' para strtotime retorna um falso ...

Não creio que o código que você forneceu resolva o problema de exemplo 'Dadas duas datas, quantos dias úteis existem entre as duas datas?Implemente em SQL ou $pet_lang' e não considero se tenho uma lista de feriados ...

Você pode obter o número de dias entre duas datas como esta:

$days = (strtotime("2008-09-10") - strtotime("2008-09-12")) / (60 * 60 * 24);

E você pode fazer funcionar algo assim (não tenho o php instalado no meu computador de trabalho, então não posso garantir que a sintaxe esteja 100% correta)

function isWorkDay($date)
{
 // check if workday and return true if so
}

function numberOfWorkDays($startdate, $enddate)
{
  $workdays = 0;
  $tmp = strtotime($startdate);
  $end = strtotime($enddate);
  while($tmp <= $end)
  {
    if ( isWorkDay( date("Y-m-d",$tmp) ) ) $workdays++;
    $tmp += 60*60*24;
  }
  return $workdays;
}

Se você não gosta de strtotime e sempre tem a data no mesmo formato, você pode usar a função explodir como

list($year, $month, day) = explode("-", $date);

Eu recomendaria fortemente o uso do PHP 5.2 Objetos DateTime, em vez de usar carimbos de data/hora UNIX, ao fazer cálculos de data.Ao usar as funções de data do PHP que retornam carimbos de data/hora UNIX, você tem um intervalo muito limitado para trabalhar (por exemplo,nada antes de 1970).

Se você der uma olhada http://php.net/date , você encontrará alguns exemplos de uso mktime() para realizar operações.

Um exemplo simples seria descobrir qual seria a data de amanhã.Você pode fazer isso simplesmente adicionando 1 ao valor do dia em mktime() do seguinte modo:

$tomorrow = date("Y-m-d", mktime(0, 0, 0, date("m"), date("d") + 1, date("Y")));

Então aqui você receberá uma data no formato AAAA-MM-DD contendo a data de amanhã.Você também pode subtrair dias simplesmente substituindo '+' por '-'. mktime() torna a vida muito mais fácil e evita que você tenha que fazer instruções if aninhadas e outras codificações problemáticas.

para obter dias úteis/feriados, postgresql CTE ftw - consulte http://osssmb.wordpress.com/2009/12/02/business-days-working-days-sql-for-postgres-2/

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top