Tenho trabalhado muito com o DateTime class
e recentemente encontrei o que pensei ser um bug ao adicionar meses. Depois de um pouco de pesquisa, parece que não era um bug, mas funcionava conforme o esperado. De acordo com a documentação encontrada aqui :
Exemplo # 2 Cuidado ao adicionar ou subtrair meses
<?php
$date = new DateTime('2000-12-31');
$date->modify('+1 month');
echo $date->format('Y-m-d') . "\n";
$date->modify('+1 month');
echo $date->format('Y-m-d') . "\n";
?>
The above example will output: 2001-01-31 2001-03-03
Alguém pode justificar por que isso não é considerado um bug?
Além disso, alguém tem alguma solução elegante para corrigir o problema e fazer com que o +1 mês funcione conforme o esperado, em vez de como pretendido?
strtotime()
stackoverflow.com/questions/7119777/…Respostas:
Por que não é um bug:
O comportamento atual está correto. O seguinte acontece internamente:
+1 month
aumenta o número do mês (originalmente 1) em um. Isso marca o encontro2010-02-31
.O segundo mês (fevereiro) tem apenas 28 dias em 2010, então o PHP corrige isso automaticamente continuando a contar os dias a partir de 1º de fevereiro. Você então termina em 3 de março.
Como conseguir o que deseja:
Para obter o que deseja: verifique manualmente o próximo mês. Em seguida, adicione o número de dias que o próximo mês tem.
Espero que você mesmo consiga codificar isso. Estou apenas dando o que fazer.
Maneira do PHP 5.3:
Para obter o comportamento correto, você pode usar uma das novas funcionalidades do PHP 5.3 que apresenta a estrofe de tempo relativo
first day of
. Esta estrofe pode ser usado em combinação comnext month
,fifth month
ou+8 months
para ir para o primeiro dia do mês especificado. Em vez+1 month
do que você está fazendo, você pode usar este código para obter o primeiro dia do próximo mês assim:Este script será gerado corretamente
February
. As seguintes coisas acontecem quando o PHP processa estafirst day of next month
estrofe:next month
aumenta o número do mês (originalmente 1) em um. Isso torna a data 2010-02-31.first day of
define o número do dia como1
, resultando na data 01-02-2010.fonte
Aqui está outra solução compacta totalmente usando métodos DateTime, modificando o objeto no local sem criar clones.
Ele produz:
fonte
$dt->modify()->modify()
. Funciona tão bem.Isso pode ser útil:
fonte
$dateTime->modify('first day of next month')->modify('-1day')
Minha solução para o problema:
fonte
Eu concordo com o sentimento do OP de que isso é contra-intuitivo e frustrante, mas também determina o que
+1 month
significa nos cenários em que isso ocorre. Considere estes exemplos:Você começa com 31/01/2015 e deseja adicionar um mês 6 vezes para obter um ciclo de agendamento para envio de um boletim informativo por e-mail. Com as expectativas iniciais do OP em mente, isso retornaria:
De imediato, observe que esperamos
+1 month
significarlast day of month
ou, alternativamente, adicionar 1 mês por iteração, mas sempre em referência ao ponto inicial. Em vez de interpretar isso como "último dia do mês", poderíamos lê-lo como "31º dia do próximo mês ou o último disponível dentro desse mês". Isso significa que saltamos de 30 de abril para 31 de maio em vez de 30 de maio. Observe que isso não ocorre porque é o "último dia do mês", mas porque queremos "o mais próximo disponível para a data do mês de início".Então, suponha que um de nossos usuários assine outro boletim informativo para começar em 30/01/2015. Qual é a data intuitiva para
+1 month
? Uma interpretação seria "30º dia do próximo mês ou o mais próximo disponível", que retornaria:Isso seria bom, exceto quando nosso usuário recebesse os dois boletins no mesmo dia. Vamos supor que este é um problema do lado da oferta em vez do lado da demanda. Não estamos preocupados que o usuário fique incomodado em receber 2 newsletters no mesmo dia, mas em vez disso, nossos servidores de e-mail não podem pagar pela largura de banda para enviar duas vezes mais muitos boletins informativos. Com isso em mente, voltamos à outra interpretação de "+1 mês" como "enviar do penúltimo dia de cada mês" que retornaria:
Agora evitamos qualquer sobreposição com o primeiro conjunto, mas também terminamos com 29 de abril e 29 de junho, o que certamente corresponde às nossas intuições originais que
+1 month
simplesmente deveriam retornarm/$d/Y
ou o atraente e simplesm/30/Y
para todos os meses possíveis. Portanto, agora vamos considerar uma terceira interpretação do+1 month
uso de ambas as datas:31 de janeiro
30 de janeiro
A descrição acima tem alguns problemas. Fevereiro é ignorado, o que pode ser um problema tanto no fim do fornecimento (digamos, se houver uma alocação mensal de largura de banda e fevereiro for desperdiçado e março for dobrado) quanto no final da demanda (os usuários se sentem enganados em fevereiro e percebem o março extra como tentativa de corrigir o erro). Por outro lado, observe que os dois conjuntos de datas:
Dados os dois últimos conjuntos, não seria difícil simplesmente reverter uma das datas se ela caísse fora do mês seguinte real (então volte para 28 de fevereiro e 30 de abril no primeiro conjunto) e não perder o sono durante o sobreposição ocasional e divergência do padrão "último dia do mês" vs "penúltimo dia do mês". Mas esperar que a biblioteca escolha entre "mais bonito / natural", "interpretação matemática de 31/02 e outros estouros do mês" e "em relação ao primeiro dia do mês ou mês passado" sempre vai acabar com as expectativas de alguém não sendo atendidas e alguma programação precisa ajustar a data "errada" para evitar o problema do mundo real que a interpretação "errada" apresenta.
Então, novamente, embora eu também espere
+1 month
retornar uma data que na verdade é no mês seguinte, não é tão simples quanto a intuição e, dadas as escolhas, ir com a matemática acima das expectativas dos desenvolvedores da web é provavelmente a escolha segura.Aqui está uma solução alternativa que ainda é tão desajeitada quanto qualquer outra, mas acho que tem bons resultados:
Não é o ideal, mas a lógica subjacente é: Se adicionar 1 mês resultar em uma data diferente do mês seguinte esperado, descarte essa data e adicione 4 semanas. Aqui estão os resultados com as duas datas de teste:
31 de janeiro
30 de janeiro
(Meu código é uma bagunça e não funcionaria em um cenário de vários anos. Agradeço a qualquer pessoa que reescreva a solução com um código mais elegante, desde que a premissa subjacente seja mantida intacta, ou seja, se +1 mês retorna uma data funky, use +4 semanas em vez disso.)
fonte
Fiz uma função que retorna um DateInterval para garantir que a adição de um mês mostre o mês seguinte e remove os dias depois disso.
fonte
Em conjunto com a resposta de shamittomar, poderia ser isso adicionando meses "com segurança":
fonte
Eu descobri uma maneira mais rápida de contornar isso usando o seguinte código:
fonte
Aqui está uma implementação de uma versão aprimorada da resposta de Juhana em uma pergunta relacionada:
Essa versão assume
$createdDate
que você está lidando com um período mensal recorrente, como uma assinatura, que começou em uma data específica, como o dia 31. Sempre demora$createdDate
muito para que as datas "recorrentes" não mudem para valores mais baixos à medida que são empurradas para os meses de menor valor (por exemplo, todas as datas recorrentes 29, 30 ou 31 não ficarão presas no dia 28 após passar até um ano não bissexto de fevereiro).Aqui estão alguns códigos de driver para testar o algoritmo:
Quais saídas:
fonte
Esta é uma versão aprimorada da resposta de Kasihasi a uma pergunta relacionada. Isso adicionará ou subtrairá corretamente um número arbitrário de meses a uma data.
Por exemplo:
irá produzir:
fonte
Se você quiser apenas evitar pular um mês, você pode realizar algo assim para tirar a data e executar um loop no próximo mês reduzindo a data em um e verificando novamente até uma data válida onde $ started_calculated é uma string válida para strtotime (ex. mysql datetime ou "now"). Isso indica que o final do mês está entre 1 minuto e meia-noite, em vez de pular o mês.
fonte
Extensão para a classe DateTime que resolve o problema de adição ou subtração de meses
https://gist.github.com/66Ton99/60571ee49bf1906aaa1c
fonte
Se usar
strtotime()
apenas use$date = strtotime('first day of +1 month');
fonte
Eu precisava conseguir uma data para 'este mês do ano passado' e fica desagradável rapidamente quando este mês é fevereiro em um ano bissexto. No entanto, acredito que isso funcione ...: - / O truque parece ser basear sua alteração no primeiro dia do mês.
fonte
fonte
irá imprimir
2
(fevereiro). funcionará por outros meses também.fonte
Por dias:
Importante:
O método
add()
da classe DateTime modifica o valor do objeto para que após chamaradd()
em um objeto DateTime ele retorne o novo objeto de data e também modifique o próprio objeto.fonte
você também pode fazer isso apenas com date () e strtotime (). Por exemplo, para adicionar 1 mês à data de hoje:
se você deseja usar a classe datetime, tudo bem também, mas é tão fácil quanto. mais detalhes aqui
fonte
A resposta aceita já explica porque isso não é um mas, e algumas outras respostas apresentam uma solução legal com expressões php como
first day of the +2 months
. O problema com essas expressões é que não são preenchidas automaticamente.A solução é bastante simples. Primeiro, você deve encontrar abstrações úteis que refletem seu espaço de problema. Nesse caso, é um
ISO8601DateTime
. Em segundo lugar, deve haver várias implementações que podem trazer uma representação textual desejada. Por exemplo,Today
,Tomorrow
,The first day of this month
,Future
- todos representam uma implementação específica doISO8601DateTime
conceito.Portanto, no seu caso, uma implementação de que você precisa é
TheFirstDayOfNMonthsLater
. É fácil encontrar apenas olhando as subclasses llist em qualquer IDE. Aqui está o código:A mesma coisa com os últimos dias de um mês. Para obter mais exemplos dessa abordagem, leia isto .
fonte
fonte