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