PHP 개발자라면 strtotime() 함수로 날짜 문자열을 Unix 타임스탬프로 변환하려다 예상과 다른 결과를 얻은 경험이 있을 것이다. "2024-01-15 14:30:00"을 파싱했는데 엉뚱한 값이 나오거나, 특정 날짜 형식에서는 아예 false가 반환되기도 한다. 다만 대부분의 개발자들은 왜 이런 일이 발생하는지 정확히 모른 채 그냥 date() 함수로 다시 포맷팅하거나 DateTime 클래스로 우회해버린다. 이번에는 strtotime()이 정확히 어떻게 동작하는지, 왜 실패하는지, 그리고 어떻게 안정적으로 사용하는지 완벽하게 정리해서 소개하겠다.
strtotime()은 영어로 쓰인 날짜 설명(description)을 Unix 타임스탬프로 변환하는 함수다. 핵심 단어는 "영어"와 "설명"이다. 즉, "2024-01-15"처럼 엄격한 ISO 형식뿐만 아니라 "next Monday", "last day of February"처럼 자연어로 표현된 상대적 날짜도 이해한다. 그런데 이 유연함이 동시에 문제의 원인이 된다.
strtotime()은 내부적으로 정규표현식 패턴 매칭을 통해 입력 문자열을 해석한다. 문제는 패턴이 고정되어 있고, 모든 가능한 날짜 형식을 다 커버하지 못한다는 점이다. 예를 들어 "15-01-2024" (유럽식 DD-MM-YYYY)나 "01/15/2024" (미국식 MM/DD/YYYY)처럼 보이는 문자열도 strtotime()은 자신의 패턴에 맞춰 해석하려 하는데, 이 과정에서 오류가 생긴다. 또한 타임존 설정이 맞지 않으면 같은 문자열도 다른 타임스탐프를 반환한다.
PHP는 기본 타임존이 UTC로 설정되어 있다. strtotime()은 타임존 정보가 없는 날짜 문자열을 이 기본 타임존으로 해석한다. 만약 당신이 한국(KST, UTC+9)에서 "2024-01-15 14:30:00"을 파싱하면, PHP는 이걸 UTC로 취급해버린다. 그 결과 예상한 한국 시간과 9시간 차이가 나는 타임스탬프를 얻게 된다.
"2024-01-15" (ISO 8601)는 잘 인식하지만, "15.01.2024" (유럽식 점 구분자)나 "15-Jan-2024" (축약 월 이름)는 제대로 파싱되지 않을 수 있다. 특히 일-월-연도 순서가 고정된 입력은 strtotime()이 월-일-연도로 잘못 읽는 경우가 많다.
"2024-01-15"를 "next day"로 해석하려고 했는데, strtotime()이 여러 패턴과 매치되면서 엉뚱한 결과가 나올 수 있다. 특히 마이너한 날짜 표현이나 언어 조합은 false를 반환하기 쉽다.
<?php
$str = "2024-01-15 14:30:00";
$timestamp = strtotime($str);
echo date('Y-m-d H:i:s', $timestamp); // UTC로 해석되어 예상과 다른 결과
?>
한국에서 실행하면 "2024-01-15 14:30:00"을 UTC로 읽어서, 한국 시간으로는 "2024-01-15 23:30:00"이 되어버린다. 또는 역으로 계산되기도 한다.
<?php
date_default_timezone_set('Asia/Seoul'); // 반드시 스크립트 맨 앞에
$str = "2024-01-15 14:30:00";
$timestamp = strtotime($str);
echo date('Y-m-d H:i:s', $timestamp); // 2024-01-15 14:30:00
echo $timestamp; // 1705316400
?>
date_default_timezone_set('Asia/Seoul')을 호출하면 이후 모든 시간 관련 함수가 KST 기준으로 동작한다. strtotime()도 이 타임존을 기준으로 문자열을 파싱한다.
strtotime()의 유연성이 양날의 검이라면, DateTime 클래스의 createFromFormat()은 더 엄격하지만 안정적인 대안이다. 특히 입력 형식을 정확히 알 때는 이 방법이 훨씬 낫다.
<?php
date_default_timezone_set('Asia/Seoul');
$str = "15-01-2024 14:30:00";
// ✗ strtotime()으로 시도 - 실패 가능
$timestamp1 = strtotime($str);
var_dump($timestamp1); // false 또는 잘못된 값
// ✓ DateTime::createFromFormat() - 안전
$date = DateTime::createFromFormat('d-m-Y H:i:s', $str);
if ($date === false) {
echo "파싱 실패";
} else {
$timestamp2 = $date->getTimestamp();
echo date('Y-m-d H:i:s', $timestamp2); // 2024-01-15 14:30:00
}
?>
DateTime::createFromFormat()은 정확한 형식 문자열을 받아서 그에 맞춰 엄격하게 파싱한다. 형식이 맞지 않으면 false를 반환하므로 오류 처리도 명확하다.
상대 날짜 표현을 사용할 때다. "now", "next Monday", "last day of February", "+7 days", "-1 month" 같은 표현은 strtotime()의 본래 목적이고, 이 경우 DateTime보다 코드가 간단하다.
<?php
date_default_timezone_set('Asia/Seoul');
// 다음 월요일 자정
$nextMonday = strtotime('next Monday');
echo date('Y-m-d H:i:s', $nextMonday);
// 현재로부터 7일 뒤
$nextWeek = strtotime('+7 days');
echo date('Y-m-d H:i:s', $nextWeek);
// 2월의 마지막 날
$lastOfFeb = strtotime('last day of February 2024');
echo date('Y-m-d H:i:s', $lastOfFeb);
?>
이런 경우들은 DateTime으로 구현하려면 interval과 modify() 메서드를 써야 해서 오히려 복잡해진다.
고정된 형식의 날짜 문자열을 파싱할 때, 특히 API 응답이나 CSV 데이터처럼 입력 형식이 명확할 때는 DateTime::createFromFormat()을 쓰자. 또한 마이크로초 정밀도가 필요하거나 복잡한 날짜 연산을 해야 할 때도 DateTime이 낫다.
외부 API에서 "2024-01-15T14:30:00Z"(ISO 8601 with Z suffix) 같은 UTC 타임스탬프를 받으면 어떻게 할까? strtotime()으로는 위험하다.
<?php
date_default_timezone_set('Asia/Seoul');
$apiResponse = '2024-01-15T14:30:00Z';
// ✗ strtotime()은 Z를 제대로 해석 못할 수 있음
// $ts = strtotime($apiResponse); // 불안정
// ✓ DateTime을 사용해서 안전하게
$date = DateTime::createFromFormat('Y-m-d\\TH:i:sZ', $apiResponse, new DateTimeZone('UTC'));
if ($date === false) {
// ISO 8601 표준 방식으로 다시 시도
$date = new DateTime($apiResponse, new DateTimeZone('UTC'));
}
// UTC에서 한국 시간으로 변환
$date->setTimezone(new DateTimeZone('Asia/Seoul'));
$timestamp = $date->getTimestamp();
echo $date->format('Y-m-d H:i:s'); // 2024-01-15 23:30:00 (UTC+9)
?>
DateTime 생성자는 ISO 8601 문자열을 자동으로 인식하므로, 미리 정해진 API 형식이라면 이 방법이 가장 안전하다.
| 상황 | 실수 | 해결책 |
|---|---|---|
| 타임존 미설정 | strtotime()이 UTC로만 동작해서 시간 차이 발생 | date_default_timezone_set() 호출 |
| 고정 형식 입력 | strtotime()의 패턴 매칭 오류로 false 반환 | DateTime::createFromFormat() 사용 |
| API 응답 파싱 | Z suffix나 ISO 8601 미인식 | new DateTime($str) 또는 DateTimeImmutable 사용 |
| 데이터베이스 시간값 | MySQL의 datetime 컬럼을 strtotime()으로 파싱 | DateTime::createFromFormat('Y-m-d H:i:s', $dbTime) 사용 |
| 사용자 입력 날짜 | 예상치 못한 형식으로 오류 발생 | 폼 입력 시 date type 사용, 백엔드에서 DateTime::createFromFormat() 검증 |
strtotime()은 상대 날짜 표현("next Monday", "+7 days")에는 최고의 도구지만, 절대 날짜 문자열 파싱에는 DateTime::createFromFormat()이 더 안전하다. 무엇보다 date_default_timezone_set()을 스크립트 맨 앞에 한 줄 추가하는 습관이 시간대 관련 버그의 80%를 미리 방지한다. API 응답이나 데이터베이스 값을 다룰 때는 입력 형식을 먼저 파악하고, 그에 맞춰 DateTime이나 createFromFormat()을 선택하자. 이 글에서 다룬 타임존 설정과 형식 지정 두 가지 원칙을 지키면, 날짜 파싱으로 인한 예기치 않은 버그는 거의 발생하지 않을 것이다.