DateTime
1. 날짜를 더하거나 빼기
애초에 달력이란 게 달마다 날 수가 다르고 윤년에 서머타임에 여러 요소들이 섞여 있어서, 이게 뜻밖의 경우를 당하기 쉽다.
문서에 있는 [How DateTime Math Works 절]에서 눈에 들어오는 부분 위주로 옮겨보면:
DateTime은 덧셈이나 뺄셈을 할 때 일, 월, 분, 초, 나노초 순서로 연산한다. 오버플로우가 날 경우는 각 단계에서 조정한다. 따라서 2월 28일에 "1개월 1일"를 더하면 3월 29일이 되는 게 아니라 4월 1일이 된다. 반면에 1개월을 먼저 더한 후에 1일을 더하면 3월 29일이 된다.
my $dt = DateTime->new( year => 2003, month => 2, day => 28 );
$dt->add( months => 1, days => 1 );
$dt->add( months => 1 )->add( days => 1 );
서머 타임의 경우도 유사한 문제가 발생한다.
my $dt = DateTime->new(
year => 2003,
month => 4,
day => 5,
hour => 1,
minute => 58,
time_zone => "America/Chicago",
);
$dt->add( days => 1, minutes => 3 );
$dt->add( minutes => 3 )->add( days => 1 );
이런 경우는 datetime 객체를 UTC 타임존으로 변환한 후 연산하면 결과를 미리 예상하기 쉽다.
DateTime::Duration 모듈에는, 개월 단위로 더할 때 "달의 마지막 날"을 계산하는 세 가지 알고리즘을 제공한다. 이 알고리즘은 덧셈의 결과가 그 달의 마지막 날을 넘어갈 때(1월 30일에 1개월을 더하는 경우처럼) 영향을 미친다.
$dt->add( months => 1, end_of_month => 'wrap' );
$dt->add( months => 1, end_of_month => 'limit' );
$dt->add( months => 1, end_of_month => 'preserve' );
- 월초, 월말의 여러 날짜에 대해 각각 테스트한 결과 : [gist]
디폴트 동작은 양의 duration 값에 대해서는 "wrap"을, 음의 duration 값에 대해서는 "preserve"를 적용한다고 한다.
저 gist에 있는 "다음 달 10일"을 찾는 코드는 주인장이 실제로 업무용 코드를 작성하며 문제가 되었던 코드이다. 1월 31일을 기준으로 다음 달 10일을 찾기 위해 "다음 달(1개월 더하기)"를 먼저 했더니 3월 3일이 되고 그 다음 "10일"로 설정하면 결과적으로 3월 10일이 되어 버린다. 이런 경우는 "10일"로 설정하는 걸 먼저 해주어야 한다.
2. Comments
컴퓨터분류