나는에서 많은 일을 해왔고 DateTime class
최근 몇 달을 추가 할 때 내가 버그라고 생각했던 것을 발견했습니다. 약간의 조사 끝에 버그가 아니라 의도 한대로 작동하는 것으로 나타났습니다. 여기 에있는 문서에 따르면 :
Example # 2 월을 더하거나 뺄 때주의하십시오
<?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
이것이 버그로 간주되지 않는 이유를 누구든지 정당화 할 수 있습니까?
또한 누구든지 문제를 해결하고 의도 한대로 +1 개월이 예상대로 작동하도록 할 수있는 우아한 해결책이 있습니까?
답변
버그가 아닌 이유 :
현재 동작이 정확합니다. 다음은 내부적으로 발생합니다.
-
+1 month
월 번호 (원래 1)를 1 씩 증가시킵니다. 이것은 날짜를 만듭니다2010-02-31
. -
두 번째 달 (2 월)은 2010 년에 28 일 밖에 없기 때문에 PHP는 2 월 1 일부터 계속해서 일을 계산하여이를 자동 수정합니다. 그런 다음 3 월 3 일에 끝납니다.
원하는 것을 얻는 방법 :
원하는 것을 얻으려면 다음 달을 수동으로 확인하십시오. 그런 다음 다음 달의 일 수를 추가하십시오.
이 코드를 직접 작성할 수 있기를 바랍니다. 나는 단지 할 일을주고있다.
PHP 5.3 방식 :
올바른 동작을 얻으려면 상대 시간 스탠자를 도입하는 PHP 5.3의 새로운 기능 중 하나를 사용할 수 있습니다 first day of
. 이 스 D와 함께 사용 할 수 있습니다 next month
, fifth month
또는 +8 months
지정된 달의 첫날로 이동합니다. +1 month
당신이하는 일 대신에 , 다음과 같이 다음 달의 첫날을 얻기 위해이 코드를 사용할 수 있습니다 :
<?php
$d = new DateTime( '2010-01-31' );
$d->modify( 'first day of next month' );
echo $d->format( 'F' ), "\n";
?>
이 스크립트는 February
. PHP가이 first day of next month
스탠자를 처리 할 때 다음과 같은 일이 발생합니다 .
-
next month
월 번호 (원래 1)를 1 씩 증가시킵니다. 이렇게하면 날짜가 2010-02-31이됩니다. -
first day of
날짜 번호를로 설정하여1
2010-02-01 날짜가됩니다.
답변
다음은 DateTime 메서드를 전적으로 사용하여 복제본을 만들지 않고 개체를 제자리에서 수정하는 또 다른 압축 솔루션입니다.
$dt = new DateTime('2012-01-31');
echo $dt->format('Y-m-d'), PHP_EOL;
$day = $dt->format('j');
$dt->modify('first day of +1 month');
$dt->modify('+' . (min($day, $dt->format('t')) - 1) . ' days');
echo $dt->format('Y-m-d'), PHP_EOL;
다음을 출력합니다.
2012-01-31
2012-02-29
답변
이것은 유용 할 수 있습니다.
echo Date("Y-m-d", strtotime("2013-01-01 +1 Month -1 Day"));
// 2013-01-31
echo Date("Y-m-d", strtotime("2013-02-01 +1 Month -1 Day"));
// 2013-02-28
echo Date("Y-m-d", strtotime("2013-03-01 +1 Month -1 Day"));
// 2013-03-31
echo Date("Y-m-d", strtotime("2013-04-01 +1 Month -1 Day"));
// 2013-04-30
echo Date("Y-m-d", strtotime("2013-05-01 +1 Month -1 Day"));
// 2013-05-31
echo Date("Y-m-d", strtotime("2013-06-01 +1 Month -1 Day"));
// 2013-06-30
echo Date("Y-m-d", strtotime("2013-07-01 +1 Month -1 Day"));
// 2013-07-31
echo Date("Y-m-d", strtotime("2013-08-01 +1 Month -1 Day"));
// 2013-08-31
echo Date("Y-m-d", strtotime("2013-09-01 +1 Month -1 Day"));
// 2013-09-30
echo Date("Y-m-d", strtotime("2013-10-01 +1 Month -1 Day"));
// 2013-10-31
echo Date("Y-m-d", strtotime("2013-11-01 +1 Month -1 Day"));
// 2013-11-30
echo Date("Y-m-d", strtotime("2013-12-01 +1 Month -1 Day"));
// 2013-12-31
답변
문제에 대한 나의 해결책 :
$startDate = new \DateTime( '2015-08-30' );
$endDate = clone $startDate;
$billing_count = '6';
$billing_unit = 'm';
$endDate->add( new \DateInterval( 'P' . $billing_count . strtoupper( $billing_unit ) ) );
if ( intval( $endDate->format( 'n' ) ) > ( intval( $startDate->format( 'n' ) ) + intval( $billing_count ) ) % 12 )
{
if ( intval( $startDate->format( 'n' ) ) + intval( $billing_count ) != 12 )
{
$endDate->modify( 'last day of -1 month' );
}
}
답변
나는 이것이 반 직관적이고 실망 스럽다는 OP의 감정에 동의하지만 무엇을 +1 month
, 이것이 발생하는 시나리오에서 의미 기도합니다. 다음 예를 고려하십시오.
2015-01-31로 시작하여 한 달을 6 번 추가하여 이메일 뉴스 레터를 보내기위한 일정주기를 얻으려고합니다. OP의 초기 기대치를 염두에두고 다음을 반환합니다.
- 2015-01-31
- 2015-02-28
- 2015-03-31
- 2015-04-30
- 2015-05-31
- 2015-06-30
바로 시작점을 기준으로 반복 당 1 개월을 추가하거나 +1 month
의미 할 것으로 예상 됩니다 last day of month
. 이것을 “월의 마지막 날”로 해석하는 대신 “다음 달의 31 일 또는 해당 달의 마지막 사용 가능 날짜”로 읽을 수 있습니다. 즉, 5 월 30 일이 아니라 4 월 30 일에서 5 월 31 일로 점프합니다. 이것은 “마지막 날”이기 때문이 아니라 “시작 월의 날짜에 가장 가까운 날짜”를 원하기 때문입니다.
따라서 사용자 중 한 명이 2015-01-30에 시작하기 위해 다른 뉴스 레터를 구독한다고 가정합니다. 직관적 인 날짜는 무엇입니까 +1 month
? 한 가지 해석은 “다음 달의 30 일 또는 가장 가까운 사용 가능 날짜”이며 다음을 반환합니다.
- 2015-01-30
- 2015-02-28
- 2015-03-30
- 2015-04-30
- 2015-05-30
- 2015-06-30
사용자가 같은 날 두 뉴스 레터를받는 경우를 제외하고는 괜찮습니다. 이것이 수요 측이 아닌 공급측 문제라고 가정 해 보겠습니다. 사용자가 같은 날 뉴스 레터 2 개를받는 데 짜증이 날 것이라 걱정하지 않지만 대신 메일 서버가 두 번 전송하는 대역폭을 감당할 수 없습니다. 많은 뉴스 레터. 이를 염두에두고 “+1 개월”을 “매월 두 번째에서 마지막 날까지 보내기”라는 다른 해석으로 돌아가서 다음을 반환합니다.
- 2015-01-30
- 2015-02-27
- 2015-03-30
- 2015-04-29
- 2015-05-30
- 2015-06-29
이제 우리는 첫 번째 세트와의 겹침을 피했지만 4 월과 6 월 29 일로 끝납니다. 이것은 +1 month
단순히 돌아와야 하는 원래의 직관 m/$d/Y
이나 m/30/Y
가능한 모든 달 동안 매력적이고 단순한 것과 일치 합니다. 이제 +1 month
두 날짜 를 사용 하는 세 번째 해석을 고려해 보겠습니다 .
1 월 31 일
- 2015-01-31
- 2015-03-03
- 2015-03-31
- 2015-05-01
- 2015-05-31
- 2015-07-01
1 월 30 일
- 2015-01-30
- 2015-03-02
- 2015-03-30
- 2015-04-30
- 2015-05-30
- 2015-06-30
위는 몇 가지 문제가 있습니다. 2 월은 건너 뛰기 때문에 공급 단 (예 : 월별 대역폭 할당이 있고 2 월이 낭비되고 3 월이 두 배가되는 경우)과 수요 단 (사용자가 2 월에 속임을 느끼고 추가 3 월을 인식하는 경우) 모두 문제가 될 수 있습니다. 실수를 수정하려는 시도). 반면에 두 개의 날짜 세트는 다음과 같습니다.
- 겹치지 않음
- 그 달에 날짜가있는 날짜는 항상 같은 날짜입니다 (1 월 30 일 세트는 꽤 깔끔해 보입니다)
- 모두 “올바른”날짜로 간주 될 수있는 날짜로부터 3 일 (대부분의 경우 1 일) 이내입니다.
- 후임자 및 전임자로부터 모두 최소 28 일 (음력)이므로 매우 고르게 분포되어 있습니다.
마지막 두 세트를 감안할 때 실제 다음 달이 아닌 경우 날짜 중 하나를 롤백하고 (첫 번째 세트에서 2 월 28 일 및 4 월 30 일로 롤백) “월 말일”과 “월의 두 번째 날에서 말일”패턴이 가끔씩 겹치고 발산됩니다. 그러나 도서관이 “가장 예쁘고 자연스러운”, “02/31 및 다른 달의 수학적 해석 넘침”, “월 1 일 또는 지난달에 상대적”중에서 선택하기를 기대하는 것은 항상 누군가의 기대를 충족하지 못하고 끝날 것입니다. “잘못된”해석으로 인해 발생하는 실제 문제를 피하기 위해 “잘못된”날짜를 조정해야하는 일정.
다시 말하지만 +1 month
실제로 다음 달의 날짜를 반환 할 것으로 예상 하지만 직관만큼 간단하지 않고 선택 사항이 주어지면 웹 개발자의 기대를 뛰어 넘는 수학을 사용하는 것이 안전한 선택 일 것입니다.
다음은 여전히 어색하지만 좋은 결과가 있다고 생각하는 대체 솔루션입니다.
foreach(range(0,5) as $count) {
$new_date = clone $date;
$new_date->modify("+$count month");
$expected_month = $count + 1;
$actual_month = $new_date->format("m");
if($expected_month != $actual_month) {
$new_date = clone $date;
$new_date->modify("+". ($count - 1) . " month");
$new_date->modify("+4 weeks");
}
echo "* " . nl2br($new_date->format("Y-m-d") . PHP_EOL);
}
최적은 아니지만 기본 논리는 다음과 같습니다. 1 개월을 추가 한 결과 다음 달 예상 날짜가 아닌 경우 해당 날짜를 스크랩하고 대신 4 주를 추가합니다. 두 테스트 날짜의 결과는 다음과 같습니다.
1 월 31 일
- 2015-01-31
- 2015-02-28
- 2015-03-31
- 2015-04-28
- 2015-05-31
- 2015-06-28
1 월 30 일
- 2015-01-30
- 2015-02-27
- 2015-03-30
- 2015-04-30
- 2015-05-30
- 2015-06-30
(내 코드는 엉망이고 다년간의 시나리오에서는 작동하지 않습니다. 기본 전제가 그대로 유지되는 한, 즉 +1 개월이 펑키 한 날짜를 반환하는 한, 더 우아한 코드로 솔루션을 다시 작성하는 사람을 환영합니다. 대신 +4 주.)
답변
DateInterval을 반환하는 함수를 만들어 월을 추가하면 다음 달이 표시되고 그 이후의 날짜는 제거됩니다.
$time = new DateTime('2014-01-31');
echo $time->format('d-m-Y H:i') . '<br/>';
$time->add( add_months(1, $time));
echo $time->format('d-m-Y H:i') . '<br/>';
function add_months( $months, \DateTime $object ) {
$next = new DateTime($object->format('d-m-Y H:i:s'));
$next->modify('last day of +'.$months.' month');
if( $object->format('d') > $next->format('d') ) {
return $object->diff($next);
} else {
return new DateInterval('P'.$months.'M');
}
}
답변
shamittomar의 대답과 함께 다음과 같이 “안전하게”개월을 추가 할 수 있습니다.
/**
* Adds months without jumping over last days of months
*
* @param \DateTime $date
* @param int $monthsToAdd
* @return \DateTime
*/
public function addMonths($date, $monthsToAdd) {
$tmpDate = clone $date;
$tmpDate->modify('first day of +'.(int) $monthsToAdd.' month');
if($date->format('j') > $tmpDate->format('t')) {
$daysToAdd = $tmpDate->format('t') - 1;
}else{
$daysToAdd = $date->format('j') - 1;
}
$tmpDate->modify('+ '. $daysToAdd .' days');
return $tmpDate;
}
