[PHP/Carbon] subMonth, subRealMonth, subMonthNoOverflow의 결과는 월말에 다르다

30105 단어 LaravelPHPtech

월말에 발생한 착오


3월 말이 되자 갑자기 PHPUnit을 시작한 CI에 오류가 발생했다.원인을 규명하기는 어렵지만, PHP 일자라이브러리 카본의subMonth,subRealMonth,subMonthNover flow의 행동이 다르다는 결론이 나왔다.

카본 버전.

  • 2.43.0
  • subMonth의 동작 확인[3월 말]


    PHPUnit을 사용해 2001년 3월 말로 가정한 날짜에 테스트 코드를 적어 동작 확인을 했다.
        /**
         * When subtract one month from the date at the end of March, you get 28 days before, 30 days before, and exactly one month before, respectively.
         */
        public function test_3月末の日付から1ヶ月引くとそれぞれ28日前、30日前、正確な1月前になる()
        {
            Carbon::setTestNow(Carbon::create(2001, 3, 31, 10));
            self::assertEquals(
                Carbon::create(2001, 3, 3, 10),
                Carbon::now()->subMonth(),
            );
            self::assertEquals(
                Carbon::now()->subDays(30),
                Carbon::now()->subRealMonth(),
            );
            self::assertEquals(
                Carbon::create(2001, 2, 28, 10),
                Carbon::now()->subMonthNoOverflow(),
            );
        }
    
    subMonth의 결과는 2월 말인 28일부터 사흘 뒤인 3월 3일을 의미한다.왜 이러지?그것은 3월부터 31일까지 subMonth가 결과에 따라 2월 31일로 판단했기 때문이다.하지만 2월 31일은 사실상 3월 3일이다.이에 따라 최종 결과는 3월 3일이다.
    subRealMonth는 항상 30일 전에 반환됩니다.
    마지막으로subMonthNoOverflow는 아까처럼 2월 31일의 계산 결과 이후 그 달의 일수를 초과하지 않기 위해 차단했다.이 방법의 결과가 가장 직관적이다.
    결론적으로 subMonth와subRealMonth가 달을 인용했지만 결과적으로 날짜와 시간의 달은 3월을 유지했다.
    이렇게 같은 방법으로 보아도 계산 결과가 다르다.예를 들어'지난달 15일'처리에 필요한 요건을 실시하는 경우 이러한 방법 중 어느 것을 사용하느냐에 따라 논리적 실현 방법이 다르다는 점에 주의해야 한다.

    subMonth의 동작 확인[윤년 3월 말]


    이어 윤년과 관련해서도 동작 확인이 이뤄졌다.
        /**
         * When subtract one month from the end of March in a leap year, you get 29 days before, 30 days before, and exactly one month before, respectively.
         */
        public function test_うるう年の3月末から1ヶ月引くとそれぞれ29日前、30日前、正確な1月前になる()
        {
            Carbon::setTestNow(Carbon::create(2000, 3, 31, 10));
            self::assertEquals(
                Carbon::create(2001, 3, 2, 10),
                Carbon::now()->subMonth(),
            );
            self::assertEquals(
                Carbon::now()->subDays(30),
                Carbon::now()->subRealMonth(),
            );
            self::assertEquals(
                Carbon::create(2000, 2, 29, 10),
                Carbon::now()->subMonthNoOverflow(),
            );
        }
    
    이번subMonth의 결과는 3월 2일이다.방금 3월 3일의 결과와 다르다.
    왜 이러지?그것은 윤년이기 때문에 2월부터 29일까지.2월 29일까지 존재하기 때문에 2월 31일은 사실상 3월 2일이다.
    subRealMonth는 30일 전에도 표시했다.
    마지막으로subMonthNoOverflow는 직감적인 결과를 표시합니다.
    여기까지의 결론으로 기본적으로subMonthNoOverflow를 사용하면 직관적인 결과를 얻을 수 있기 때문에subMonthNoOverflow를 사용해야 한다.
    매번 30일 전 subRealMonth가 뜨는데 일정 일수 유료 서비스로 이용하지 않을까요.

    addMonth의 동작 확인[1월 말/윤년 1월 말]


    가설을 확인하기 위해addMonth/addRealMonth/addMonthNoOverflow도 같은 확인을 진행했다.이번에는 1월 말 일자를 이용한 실험이다.
        /**
         * If you add one month from the end of January, it will be 31 days, 30 days, and one month from now, respectively.
         */
        public function test_1月末から1ヶ月足すとそれぞれ31日後、30日後、1ヶ月後になる()
        {
            Carbon::setTestNow(Carbon::create(2001, 1, 31, 10));
            self::assertEquals(
                Carbon::create(2001, 3, 3, 10),
                Carbon::now()->addMonth(),
            );
            self::assertEquals(
                Carbon::now()->addDays(30),
                Carbon::now()->addRealMonth(),
            );
            self::assertEquals(
                Carbon::create(2001, 2, 28, 10),
                Carbon::now()->addMonthNoOverflow(),
            );
        }
    
        /**
         * If you add one month from the end of January in a leap year, it will be 31 days later, 30 days later, and one month later, respectively.
         */
        public function test_うるう年の1月末から1ヶ月足すとそれぞれ31日後、30日後、1ヶ月後になる()
        {
            Carbon::setTestNow(Carbon::create(2000, 1, 31, 10));
            self::assertEquals(
                Carbon::create(2000, 3, 2, 10),
                Carbon::now()->addMonth(),
            );
            self::assertEquals(
                Carbon::now()->addDays(30),
                Carbon::now()->addRealMonth(),
            );
            self::assertEquals(
                Carbon::create(2000, 2, 29, 10),
                Carbon::now()->addMonthNoOverflow(),
            );
        }
    
    방금 해설한 내용과 같아서 중단되었지만addmonth에서도 같은 논리로 이동하는 것을 알 수 있다.1월 31일addMonth와 2월 31일이기 때문에 최종 날짜는 3월 2일 또는 3일입니다.

    지난달 15일


    마지막으로 지난달 15일을 요구하는 날짜와 시간을 문장에서 서술한 상황에서 다시 설명한다.
        /**
         * When the requirement is to calculate 0:00:00 on the 15th of the previous month, care should be taken to execute startOfMonth() first, or subMonthNoOverflow() should be used.
         */
        public function test_前月の1500分を算出するという要件の時は先にstartOfMonthを実行するように気をつけるか、subMonthNoOverflowを使うべき()
        {
            Carbon::setTestNow(Carbon::create(2001, 3, 31));
            self::assertEquals(
                Carbon::create(2001, 2, 15),
                Carbon::now()->startOfMonth()->subMonth()->addDays(14),
            );
    
            // 先にsubMonthすると日付が当月になってしまう
            self::assertEquals(
                Carbon::create(2001, 3, 15),
                Carbon::now()->subMonth()->startOfMonth()->addDays(14),
            );
    
            self::assertEquals(
                Carbon::create(2001, 2, 15),
                Carbon::now()->subMonthNoOverflow()->startOfMonth()->addDays(14),
            );
    
            self::assertEquals(
                Carbon::create(2001, 2, 15),
                Carbon::now()->startOfMonth()->subMonthNoOverflow()->addDays(14),
            );
        }
    
    startOfMonth를 실행한 후subMonth를 사용하면 이상한 행동으로 인한 오류를 피할 수 있지만subMonthNoverflow를 사용하면 순서에 의존하지 않는 기대 결과를 얻을 수 있다.
    결론은subMonth와addMonth는 달을 더하거나 당기는 처리처럼 보이지만 실제로는 앞뒤 달의 일수 차이로 의도하지 않은 결과를 초래할 때가 있다.따라서 기본적으로subMonthNoOverflow나addMonthNoOverflow를 이용하면 상황에 따라 다른 방법을 사용하면 버그가 발생하기 더욱 어렵다.

    좋은 웹페이지 즐겨찾기