PHP 개발자라면 strtotime() 함수로 날짜 문자열을 Unix 타임스탬프로 변환하려다 예상과 다른 결과를 얻은 경험이 있을 것이다. "2024-01-15 14:30:00"을 파싱했는데 엉뚱한 값이 나오거나, 특정 날짜 형식에서는 아예 false가 반환되기도 한다. 다만 대부분의 개발자들은 왜 이런 일이 발생하는지 정확히 모른 채 그냥 date() 함수로 다시 포맷팅하거나 DateTime 클래스로 우회해버린다. 이번에는 strtotime()이 정확히 어떻게 동작하는지, 왜 실패하는지, 그리고 어떻게 안정적으로 사용하는지 완벽하게 정리해서 소개하겠다.

 

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()은 자신의 패턴에 맞춰 해석하려 하는데, 이 과정에서 오류가 생긴다. 또한 타임존 설정이 맞지 않으면 같은 문자열도 다른 타임스탐프를 반환한다.

 

strtotime() 실패하는 흔한 케이스

 

케이스 1. 기본 타임존 미설정

PHP는 기본 타임존이 UTC로 설정되어 있다. strtotime()은 타임존 정보가 없는 날짜 문자열을 이 기본 타임존으로 해석한다. 만약 당신이 한국(KST, UTC+9)에서 "2024-01-15 14:30:00"을 파싱하면, PHP는 이걸 UTC로 취급해버린다. 그 결과 예상한 한국 시간과 9시간 차이가 나는 타임스탬프를 얻게 된다.

 

케이스 2. 날짜 형식이 strtotime() 패턴 밖

"2024-01-15" (ISO 8601)는 잘 인식하지만, "15.01.2024" (유럽식 점 구분자)나 "15-Jan-2024" (축약 월 이름)는 제대로 파싱되지 않을 수 있다. 특히 일-월-연도 순서가 고정된 입력은 strtotime()이 월-일-연도로 잘못 읽는 경우가 많다.

 

케이스 3. 상대 날짜 표현의 모호함

"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"이 되어버린다. 또는 역으로 계산되기도 한다.

 

✓ 올바른 코드: date_default_timezone_set() 사용
<?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()은 더 엄격하지만 안정적인 대안이다. 특히 입력 형식을 정확히 알 때는 이 방법이 훨씬 낫다.

 

케이스: 입력 형식이 "DD-MM-YYYY HH:MM:SS" 고정
<?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를 반환하므로 오류 처리도 명확하다.

 

strtotime()을 써야 할 때와 쓰면 안 될 때

 

strtotime()이 좋은 경우

상대 날짜 표현을 사용할 때다. "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() 메서드를 써야 해서 오히려 복잡해진다.

 

strtotime()을 피해야 할 경우

고정된 형식의 날짜 문자열을 파싱할 때, 특히 API 응답이나 CSV 데이터처럼 입력 형식이 명확할 때는 DateTime::createFromFormat()을 쓰자. 또한 마이크로초 정밀도가 필요하거나 복잡한 날짜 연산을 해야 할 때도 DateTime이 낫다.

 

실무 패턴: API 응답 날짜 안전하게 파싱하기

외부 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()을 선택하자. 이 글에서 다룬 타임존 설정과 형식 지정 두 가지 원칙을 지키면, 날짜 파싱으로 인한 예기치 않은 버그는 거의 발생하지 않을 것이다.