문제: Asia/Seoul 시간대 설정에도 예약 작업이 하루 밀림
node-cron 라이브러리를 사용하여 특정 시간대(예: Asia/Seoul)에 맞춰 예약 작업을 설정했지만, 실제로 작업이 실행될 때 처리되는 날짜가 예상보다 하루 늦게 계산되는 문제가 발생할 수 있습니다. 예를 들어, Asia/Seoul 기준으로 자정(0 0 * * *)에 실행되도록 설정했음에도, 작업 내부에서 오늘 날짜를 계산하면 실제로는 '어제' 날짜로 인식되는 상황입니다.
원인: node-cron의 시간대 해석과 애플리케이션 로직의 불일치
이 문제는 node-cron의 timezone 옵션과 Node.js 환경의 Date 객체 동작 방식, 그리고 서버의 기본 시간대가 복합적으로 작용하여 발생합니다.
node-cron의timezone옵션:node-cron은timezone: 'Asia/Seoul'옵션을 통해 Cron 표현식(예:0 0 * * *)을 지정된 시간대(Asia/Seoul, 즉 KST) 기준으로 정확히 해석하여 실행 시점을 결정합니다. 즉, 서버의 시간대가 UTC이더라도 KST 자정(00:00)에 맞춰 작업 콜백 함수를 실행합니다.Date객체의 기본 동작: 하지만 작업 콜백 함수 내부에서new Date()와 같이 시간대를 명시하지 않고Date객체를 생성하면, 이Date객체는 서버의 기본 시간대를 따르게 됩니다. 대부분의 서버는 기본 시간대가 UTC로 설정되어 있습니다.- 시간대 시차로 인한 날짜 불일치:
Asia/Seoul(KST)은 UTC보다 9시간 빠릅니다(UTC+9). 따라서node-cron이 KST 10월 26일 00:00에 작업을 실행하더라도, 이때new Date()를 호출하면 UTC 기준으로는 10월 25일 15:00가 됩니다. 만약 작업 로직이new Date().getDate()와 같이Date객체의 로컬 날짜를 사용하여오늘을 판단한다면, KST 기준 '오늘'이 아닌 UTC 기준 '어제' 날짜를 얻게 되어 결과적으로 KST 기준으로는 '하루 밀리는' 것처럼 보이게 됩니다.
해결 방법: 작업 로직 내에서 명시적인 시간대 처리
node-cron이 스케줄을 정확히 Asia/Seoul 자정으로 맞춘다 하더라도, 작업 함수 내부에서는 luxon 또는 date-fns-tz와 같은 시간대 라이브러리를 사용하여 모든 날짜/시간 계산을 Asia/Seoul 기준으로 명확하게 수행해야 합니다. 이를 통해 서버의 기본 시간대와 관계없이 항상 Asia/Seoul 기준의 정확한 날짜를 얻을 수 있습니다.
코드 예시 (Luxon 사용)
luxon 라이브러리를 사용하면 지정된 시간대를 기준으로 날짜와 시간을 쉽게 다룰 수 있습니다. 먼저 luxon을 설치합니다:
pnpm add luxon
# 또는 npm install luxon
# 또는 yarn add luxon
다음은 node-cron 작업 내에서 luxon을 사용하여 Asia/Seoul 시간대를 기준으로 날짜를 처리하는 예시입니다.
import cron from 'node-cron';
import { DateTime } from 'luxon';
// node-cron 스케줄링
cron.schedule('0 0 * * *', () => {
// 이 콜백 함수는 'Asia/Seoul' 시간대 기준으로 매일 자정(00:00)에 실행됩니다.
// 예를 들어, KST 2023년 10월 26일 00:00에 실행될 경우,
// 이는 UTC 2023년 10월 25일 15:00에 해당합니다.
console.log('--- Cron job started ---');
// 1. 서버의 기본 Date 객체를 사용하는 경우 (주의 필요)
// new Date()는 서버의 기본 시간대 (대부분 UTC)를 따릅니다.
const serverDefaultDate = new Date();
console.log(`[문제의 원인] 서버 기본 Date 객체 (UTC): ${serverDefaultDate.toISOString()}`);
console.log(`[문제의 원인] 서버 기본 Date 기준 날짜: ${serverDefaultDate.getDate()}`); // UTC 기준의 날짜 (예: 25)
// 2. Luxon을 사용하여 Asia/Seoul 시간대로 현재 시각을 가져오는 경우
// DateTime.now().setZone('Asia/Seoul')을 통해 정확히 KST 기준의 현재 시각을 얻습니다.
const nowInSeoul = DateTime.now().setZone('Asia/Seoul');
console.log(`[해결 방법] Luxon으로 얻은 Asia/Seoul 시각: ${nowInSeoul.toISO()}`);
console.log(`[해결 방법] Luxon Asia/Seoul 기준 날짜: ${nowInSeoul.day}`); // KST 기준의 날짜 (예: 26)
// 3. 작업에 사용할 최종 날짜 문자열 생성
// 이제 nowInSeoul 객체를 사용하여 원하는 형식으로 날짜를 추출할 수 있습니다.
const targetDateForProcessing = nowInSeoul.toFormat('yyyy-MM-dd');
console.log(`[최종 결과] Asia/Seoul 기준으로 처리할 날짜: ${targetDateForProcessing}`);
// 여기에서 targetDateForProcessing 변수를 사용하여 데이터베이스 쿼리, 파일 생성 등
// '오늘' 날짜를 기준으로 하는 모든 작업을 수행합니다.
// 예: `SELECT * FROM daily_reports WHERE report_date = '${targetDateForProcessing}'`
console.log('--- Cron job finished ---');
}, {
scheduled: true,
timezone: 'Asia/Seoul' // node-cron이 Cron 표현식을 Asia/Seoul 기준으로 해석하도록 지시
});
console.log('node-cron 스케줄러가 시작되었습니다.');
검증 방법
- 서버의 기본 시간대 확인: 서버에 접속하여
timedatectl(Linux) 또는date -u명령어를 통해 서버의 기본 시간대가 무엇인지 확인합니다. 대부분의 클라우드 서버는 기본적으로 UTC로 설정되어 있습니다. - 예시 코드 실행 및 로그 분석: 위
node-cron예시 코드를 Node.js 환경에서 실행하고 콘솔 로그를 주의 깊게 확인합니다.[문제의 원인]으로 표시된서버 기본 Date 기준 날짜와[해결 방법]으로 표시된Luxon Asia/Seoul 기준 날짜가 어떻게 다른지 비교하세요.[최종 결과]로 출력되는Asia/Seoul 기준으로 처리할 날짜가 예상한 KST 기준 '오늘' 날짜와 일치하는지 확인하면 됩니다.
흔히 하는 실수 및 주의점
node-cron의timezone옵션의 범위:node-cron의timezone옵션은 오직 Cron 표현식의 해석에만 영향을 미칩니다. 즉, 언제 콜백 함수를 실행할지를 결정하는 데 사용될 뿐, 콜백 함수 내부에서 생성되는Date객체의 시간대까지 변경하지는 않습니다.- 서버 시간대 변경의 위험성:
/etc/timezone파일 수정이나TZ환경 변수 설정을 통해 서버의 기본 시간대를 변경하는 것은 근본적인 해결책이 될 수 있지만, 서버 내 다른 애플리케이션이나 시스템 기능에 예기치 않은 영향을 미칠 수 있습니다. 특정 Node.js 프로세스에만TZ환경 변수를 설정하는 방법은 고려해 볼 수 있으나, 가장 안전하고 명확한 방법은 애플리케이션 코드 내에서 시간대를 명시적으로 처리하는 것입니다. - 일광 절약 시간(DST):
Asia/Seoul은 일광 절약 시간을 사용하지 않지만, 만약 DST를 사용하는 다른 시간대를 다룬다면luxon과 같은 라이브러리는 DST 전환을 자동으로 처리해주므로 더욱 견고한 코드를 작성할 수 있습니다. Date객체 대신 타임스탬프 사용: 날짜/시간 계산에서 시간대 문제를 완전히 회피하고 싶다면, 모든 시각을 UTC 타임스탬프(Epoch Milliseconds)로 저장하고 처리하는 것을 고려할 수 있습니다. 하지만 이는 사용자에게 보여줄 때 다시 특정 시간대로 변환하는 과정이 필요합니다.