서버

Node.js 에서 Date 객체 Time Zone 다루기

행복한 시지프 - 서버 2026. 2. 9. 21:05

들어가며

블로그 성향 분석 서비스를 만들고 싶습니다. 하나의 항목이, “아침형 vs 밤형” 입니다. 글을 주로 언제 쓰느냐에 따라서 결정합니다. RSS 의 pubDate 를 파싱해서 계산합니다. 이때 버그가 있었습니다. test CI 를 등록하고나서, 이 테스트가 통과하지 않았습니다. 분명 Local 에서는 모든 테스트가 동작했는데, 왜 CI 에서는 통과하지 않을 걸까요?

 

에러 로그를 보니, 테스트 코드 결과에서 hour 가 예상과 달랐던 것입니다. 원인을 분석해보니 Time Zone 문제라는 것을 깨달았고, Node.js 환경에서 어떻게 Time Zone 을 다루어야 하는지 학습하고 적용해보았습니다.

  [
    {
-     "hour": 21,
+     "hour": 12,
      "minute": 49,
    },
  ]

 

문제상황 구체화

코드는 이러했습니다. 단순한 로직이라 문제가 없다고 판단했습니다. 테스트 코드도 정상적으로 통과했고요.

export function parseTimeFromPubDate(pubDate: string): ParsedTime {
  const date = new Date(pubDate);
  return { hour: date.getHours(), minute: date.getMinutes() };
}

RSS 에 포함된 pubDate 는 RFC 2822 Data Format 형식을 갖습니다. 이런 형식이죠.

Sun, 18 Jan 2026 21:49:08 +0900

 

 

배포 날짜에서, 시/분을 추출하고 평균을 내어, 평균 글 배포 시간을 보여주려고 했습니다.

문제는 getHours 메서드에서 발생했습니다. getHours 는 프로세스의 Time Zone 에 따라서 다른 응답을 냅니다.

 

테스트 코드로 이를 검증해봅시다. 2026-01-01T00:00:00Z 라는 같은 UTC 에서, Date 객체를 만들고 getHours 를 호출하면, 지역마다 다른 응답을 냅니다.

import { describe, expect, it } from 'vitest';

process.env.TZ = 'UTC';
it('TZ=UTC일 때, UTC 자정의 getHours()는 0을 반환한다', () => {
  const date = new Date('2026-01-01T00:00:00Z');
  expect(date.getHours()).toBe(0);
});

process.env.TZ = 'Asia/Seoul';
it('TZ=Asia/Seoul일 때, UTC 자정의 getHours()는 9시를 반환한다', () => {
  const date = new Date('2026-01-01T00:00:00Z');
  expect(date.getHours()).toBe(9);
});

process.env.TZ = 'America/New_York';
it('TZ=America/New_York일 때, UTC 자정의 getHours()는 19시를 반환한다', () => {
  const date = new Date('2026-01-01T00:00:00Z');
  expect(date.getHours()).toBe(19);
});

 

이건 익히 들어 아는 사실이었지만, 프론트엔드 개발을 하면서 크게 신경을 썼던 적은 없습니다. 이유를 생각해보니 이랬습니다.

  1. 서버에서 내려준 값을 그대로 사용
  2. 날짜/시간 검증은 서버 API 로 검증
  3. 한국에서만 서비스하기 때문에, getHour 그대로 사용

서버를 개발하는 것은 달랐습니다. 서버 자체가 다양한 위치에서 뜰 수 있습니다. 특히 저는 Cloudflare Workers 라는 Edge Runtime 을 활용해서 배포하기 때문에, 매 호출마다 다른 지역에서 API 가 호출될 수 있습니다.

 

그만큼 서버에서 TIme Zone 을 고려하는 것은 매우 중요한 태스크였습니다. 이 문제를 해소해보며, Time Zone 에 대한 이해를 높여봅시다.

요구사항 정리

  • pubDate 를 파싱할 때 Time Zone 에 따라서 다른 결과가 나오면 안 된다.
  • RSS 의 pubDate 의 time offset 에 맞게 결과가 나와야 한다.

 

TDD/Red - 테스트 코드 작성

명확한 요구사항이 있는 경우, 테스트 코드를 먼저 작성하는 것이 좋습니다. Red 를 먼저 만들고, 그걸 충족하게 만드는 구현체를 만들면 됩니다.

 

무엇을 테스트해야 할까요?

다른 Time Zone 에서도 pubDate 파싱 결과가 동일하게 나온다는 것을 테스트하면 됩니다.

 

다른 Time Zone 을 어떻게 테스트 할 수 있을까요? Node.js 에서 Time Zone 을 강제로 설정하는 방법이 있습니다. process.env 의 TZ 변수를 조정하는 것입니다.

 

이런 식으로 사용 가능합니다.

  • process.env.TZ = 'UTC'
  • process.env.TZ = 'America/New_York'
  • process.env.TZ = 'Asia/Seoul'

 

저는 3개의 TZ 에서, 동일한 결과가 나오는지 테스트할 것입니다. 이때 3개의 파일을 별개로 작성해야 합니다. 왜냐하면, 프로세스 env 를 파일 실행 중간에 변경할 수 없기 때문입니다. 파일마다 다른 워커로 실행이 되기 때문에, 별개 파일 간에는 영향을 주지 않습니다.

 

1. 뉴욕 TZ 를 테스트 합니다.

process.env.TZ = 'America/New_York';

import { describe, expect, it } from 'vitest';
import { parseTimeFromPubDate } from '../../utils/time-analyzer';

describe('parseLocalTime (TZ=America/New_York)', () => {
  it('+0900 오프셋: pubDate 의 Time Zone 시간을 그대로 추출한다', () => {
    expect(parseTimeFromPubDate('Sun, 18 Jan 2026 21:49:08 +0900')).toEqual({
      hour: 21,
      minute: 49,
    });
  });
});

 

2. 서울 TZ 를 테스트 합니다.

process.env.TZ = 'Asia/Seoul';

import { describe, expect, it } from 'vitest';
import { parseTimeFromPubDate } from '../../utils/time-analyzer';

describe('parseLocalTime (TZ=Asia/Seoul)', () => {
  it('+0900 오프셋: pubDate 의 Time Zone 시간을 그대로 추출한다', () => {
    expect(parseTimeFromPubDate('Sun, 18 Jan 2026 21:49:08 +0900')).toEqual({
      hour: 21,
      minute: 49,
    });
  });
});

 

 

3. UTC TZ 를 테스트 합니다.

process.env.TZ = 'UTC';

import { describe, expect, it } from 'vitest';
import { parseTimeFromPubDate } from '../../utils/time-analyzer';

describe('parseLocalTime (TZ=UTC)', () => {
  it('+0900 오프셋: pubDate 의 Time Zone 시간을 그대로 추출한다', () => {
    expect(parseTimeFromPubDate('Sun, 18 Jan 2026 21:49:08 +0900')).toEqual({
      hour: 21,
      minute: 49,
    });
  });
});

 

테스트 코드를 먼저 작성했습니다. 현재 구현체는 아래와 같으므로, Red 가 발생할 것입니다.

export function parseTimeFromPubDate(pubDate: string): ParsedTime {
  const date = new Date(pubDate);
  return { hour: date.getHours(), minute: date.getMinutes() };
}

 

보시는 바와 같이, +09:00 offset 과 일치하는 Seoul 만 성공하고, 나머지 TZ 에서는 실패합니다.

 

TDD/Green - 구현체 작성

구현체는 간단합니다. pubDate 가 이미 작성자의 Local Time Zone 을 지키고 있는 형식이므로, pubDate 의 시간과 분을 그대로 추출해주면 됩니다.

export function parseTimeFromPubDate(pubDate: string): ParsedTime {
  const match = pubDate.match(/(\\d{2}):(\\d{2}):\\d{2}/);
  return { hour: parseInt(match[1]), minute: parseInt(match[2]) };
}

 

정규표현식으로 HH:MM:SS 형식을 찾아서, 시간과 분을 추출해주면 끝입니다.

 

 

테스트가 정상적으로 통과합니다.

 

Cloudflare Workers CI 에서는 UTC 를 활용하는데요. CI 도 정상적으로 통과하는 것을 확인했습니다. 장애는 정상 해소되었습니다.

 

마치며

Time Zone 문제로 장애가 발생하고, UTC, GMT, KST, ISO 8601, Date 객체의 동작 등 여러 키워드를 공부했습니다. 프론트엔드 개발 할 때도, 이 개념들이 무엇인지 대강 알고는 있었지만, 다룰 일이 많지 않아 명확하게 알 지는 못 했습니다. 드디어 머릿속에 엉켜있던 실을 가지런히 풀어낼 수 있었습니다. 이제는 Time Zone 을 다룰 때, Smell 을 맡을 수 있을 정도가 되었습니다. 서버 코드를 만질 때는, getHours, getMinutes 같은 로컬 시간 메서드 사용을 항상 주의해야 합니다. 문자열을 직접 만지거나, UTC 와 오프셋을 고려하여 처리해주는 습관을 들여야 합니다. 사이드 프로젝트의 작은 장애로, 값싸게 치르고 성장한 것 같습니다.