PHP DateTime :: 달 더하기 및 빼기 수정
나는에서 많은 일을 해왔고 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 날짜가됩니다.
이것은 유용 할 수 있습니다.
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' );
}
}
다음은 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
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;
}
나는 이것이 반 직관적이고 실망 스럽다는 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 주.)
다음 코드를 사용하여 더 짧은 방법을 찾았습니다.
$datetime = new DateTime("2014-01-31");
$month = $datetime->format('n'); //without zeroes
$day = $datetime->format('j'); //without zeroes
if($day == 31){
$datetime->modify('last day of next month');
}else if($day == 29 || $day == 30){
if($month == 1){
$datetime->modify('last day of next month');
}else{
$datetime->modify('+1 month');
}
}else{
$datetime->modify('+1 month');
}
echo $datetime->format('Y-m-d H:i:s');
다음은 관련 질문에 대한 Juhana 답변의 개선 된 버전의 구현입니다 .
<?php
function sameDateNextMonth(DateTime $createdDate, DateTime $currentDate) {
$addMon = clone $currentDate;
$addMon->add(new DateInterval("P1M"));
$nextMon = clone $currentDate;
$nextMon->modify("last day of next month");
if ($addMon->format("n") == $nextMon->format("n")) {
$recurDay = $createdDate->format("j");
$daysInMon = $addMon->format("t");
$currentDay = $currentDate->format("j");
if ($recurDay > $currentDay && $recurDay <= $daysInMon) {
$addMon->setDate($addMon->format("Y"), $addMon->format("n"), $recurDay);
}
return $addMon;
} else {
return $nextMon;
}
}
이 버전은 $createdDate
구독과 같이 특정 날짜 (예 : 31 일)에 시작된 반복적 인 월간 기간을 처리하고 있다고 가정합니다. 항상 $createdDate
너무 늦게 "반복"날짜가 더 낮은 값으로 변경되지 않을 것입니다 (예 : 29 일, 30 일 또는 31 일 모든 반복 날짜가 통과 후 결국 28 일에 고정되지 않습니다. 윤년이 아닌 2 월까지).
다음은 알고리즘을 테스트하기위한 드라이버 코드입니다.
$createdDate = new DateTime("2015-03-31");
echo "created date = " . $createdDate->format("Y-m-d") . PHP_EOL;
$next = sameDateNextMonth($createdDate, $createdDate);
echo " next date = " . $next->format("Y-m-d") . PHP_EOL;
foreach(range(1, 12) as $i) {
$next = sameDateNextMonth($createdDate, $next);
echo " next date = " . $next->format("Y-m-d") . PHP_EOL;
}
출력되는 내용 :
created date = 2015-03-31
next date = 2015-04-30
next date = 2015-05-31
next date = 2015-06-30
next date = 2015-07-31
next date = 2015-08-31
next date = 2015-09-30
next date = 2015-10-31
next date = 2015-11-30
next date = 2015-12-31
next date = 2016-01-31
next date = 2016-02-29
next date = 2016-03-31
next date = 2016-04-30
이것은 관련 질문에서 Kasihasi의 답변 을 개선 한 버전입니다 . 이것은 날짜에 임의의 개월 수를 올바르게 더하거나 뺍니다.
public static function addMonths($monthToAdd, $date) {
$d1 = new DateTime($date);
$year = $d1->format('Y');
$month = $d1->format('n');
$day = $d1->format('d');
if ($monthToAdd > 0) {
$year += floor($monthToAdd/12);
} else {
$year += ceil($monthToAdd/12);
}
$monthToAdd = $monthToAdd%12;
$month += $monthToAdd;
if($month > 12) {
$year ++;
$month -= 12;
} elseif ($month < 1 ) {
$year --;
$month += 12;
}
if(!checkdate($month, $day, $year)) {
$d2 = DateTime::createFromFormat('Y-n-j', $year.'-'.$month.'-1');
$d2->modify('last day of');
}else {
$d2 = DateTime::createFromFormat('Y-n-d', $year.'-'.$month.'-'.$day);
}
return $d2->format('Y-m-d');
}
예를 들면 :
addMonths(-25, '2017-03-31')
다음을 출력합니다.
'2015-02-28'
If you just want to avoid skipping a month you can perform something like this to get the date out and run a loop on the next month reducing the date by one and rechecking until a valid date where $starting_calculated is a valid string for strtotime (i.e. mysql datetime or "now"). This finds the very end of the month at 1 minute to midnight instead of skipping the month.
$start_dt = $starting_calculated;
$next_month = date("m",strtotime("+1 month",strtotime($start_dt)));
$next_month_year = date("Y",strtotime("+1 month",strtotime($start_dt)));
$date_of_month = date("d",$starting_calculated);
if($date_of_month>28){
$check_date = false;
while(!$check_date){
$check_date = checkdate($next_month,$date_of_month,$next_month_year);
$date_of_month--;
}
$date_of_month++;
$next_d = $date_of_month;
}else{
$next_d = "d";
}
$end_dt = date("Y-m-$next_d 23:59:59",strtotime("+1 month"));
Extension for DateTime class which solves problem of adding or subtracting months
https://gist.github.com/66Ton99/60571ee49bf1906aaa1c
If using strtotime()
just use $date = strtotime('first day of +1 month');
you can actually do it with just date() and strtotime() as well. For example to add 1 month to todays date:
date("Y-m-d",strtotime("+1 month",time()));
if you are wanting to use the datetime class thats fine too but this is just as easy. more details here
I needed to get a date for 'this month last year' and it becomes unpleasant quite quickly when this month is February in a leap year. However, I believe this works... :-/ The trick seems to be to base your change on the 1st day of the month.
$this_month_last_year_end = new \DateTime();
$this_month_last_year_end->modify('first day of this month');
$this_month_last_year_end->modify('-1 year');
$this_month_last_year_end->modify('last day of this month');
$this_month_last_year_end->setTime(23, 59, 59);
$month = 1; $year = 2017;
echo date('n', mktime(0, 0, 0, $month + 2, -1, $year));
will output 2
(february). will work for other months too.
$current_date = new DateTime('now');
$after_3_months = $current_date->add(\DateInterval::createFromDateString('+3 months'));
For days:
$after_3_days = $current_date->add(\DateInterval::createFromDateString('+3 days'));
Important:
The method add()
of DateTime class modify the object value so after calling add()
on a DateTime Object it returns the new date object and also it modify the object it self.
$ds = new DateTime();
$ds->modify('+1 month');
$ds->modify('first day of this month');
$date = date('Y-m-d', strtotime("+1 month"));
echo $date;
참고URL : https://stackoverflow.com/questions/3602405/php-datetimemodify-adding-and-subtracting-months
'IT story' 카테고리의 다른 글
Windows에서 Jenkins 서비스 시작 / 중지 및 다시 시작 (0) | 2020.09.04 |
---|---|
REST 웹 서비스에서 클라이언트로 파일을 보내는 올바른 방법은 무엇입니까? (0) | 2020.09.03 |
스레드간에 정적 변수가 공유됩니까? (0) | 2020.09.03 |
CSS3 미디어 쿼리에 Sass 변수 사용 (0) | 2020.09.03 |
파이썬에서 json.dump ()와 json.dumps ()의 차이점은 무엇입니까? (0) | 2020.09.03 |