본문 바로가기
빌드노트 검색

빌드노트

PostgreSQL에서 같은 01/02/2026이 다른 날짜로 저장되는 이유

PostgreSQL의 DateStyle 입력 순서에 따라 같은 01/02/2026이 1월 2일 또는 2월 1일로 저장된다. PostgreSQL 18.6에서 MDY·DMY, ISO 입력, 표시만 달라지는 날짜와 잘못된 날짜를 비교하고 입력 계약을 명확히 하는 방법을 설명한다.

PostgreSQL에서 01/02/2026을 date로 바꿨는데 한 연결에서는 1월 2일, 다른 연결에서는 2월 1일이 될 수 있다. 두 숫자가 모두 월과 일로 유효해서, 문자열을 해석하는 연결의 DateStyle에 따라 서로 다른 날짜가 된다. 먼저 그 연결의 입력 순서를 확인하고, 전달할 날짜를 2026-02-01처럼 연도가 앞에 오는 ISO 형식으로 정하는 것이 해결의 출발점이다.

날짜를 변환하는 연결에서 확인한다

문제가 나는 SQL을 실행하는 연결에서 아래 두 조회를 순서대로 실행한다. 별도로 연 관리자 콘솔의 설정이 애플리케이션 연결과 같다고 가정하면 안 된다.

SHOW DateStyle;
SELECT '01/02/2026'::date AS slash_date,
       '2026-02-01'::date AS iso_date;

이번 실습의 초기 설정은 ISO, MDY였고, 두 열은 각각 2026-01-02, 2026-02-01이었다. 여기서 ISO는 출력 형식이고 MDY는 모호한 입력을 월·일·년 순서로 읽는 규칙이다. ISO라는 글자가 있다고 슬래시 입력까지 연·월·일로 해석하는 것은 아니다. DMY라면 일·월·년 순서로 읽는다. 두 요소를 따로 지정할 수 있다는 점은 PostgreSQL 18의 DateStyle 설명 (새 탭에서 열림)에 명시돼 있다.

이 조회는 현재 연결이 지금 문자열을 어떻게 해석하는지 보여 준다. 이미 저장된 날짜가 과거에 어떤 설정으로 들어왔는지까지 알려 주지는 않는다. 현재 조회 결과가 같은데 저장된 값만 다르다면, 입력 당시 설정과 DB에 전달되기 전 문자열 변환도 확인해야 한다.

출력은 같게 두고 입력 순서만 바꿔 본다

아래는 PostgreSQL 18.6의 별도 실습 DB에서 실행한 비교다. psql로 연결하고, 진행 중인 트랜잭션이 없는 상태에서 문장을 순서대로 실행한다. 테이블을 만들지 않고 변환 결과만 조회한다.

BEGIN;
SET LOCAL DateStyle = 'ISO, MDY';
SELECT '01/02/2026'::date AS slash_date,
       '2026-02-01'::date AS iso_date;
ROLLBACK;

BEGIN;
SET LOCAL DateStyle = 'ISO, DMY';
SELECT '01/02/2026'::date AS slash_date,
       '2026-02-01'::date AS iso_date;
ROLLBACK;

두 SELECT에서 읽을 부분은 다음과 같다.

  • ISO, MDY: slash_date는 2026-01-02, iso_date는 2026-02-01이었다.
  • ISO, DMY: slash_date는 2026-02-01, iso_date는 2026-02-01이었다.

출력 형식을 둘 다 ISO로 맞췄는데 슬래시 입력의 결과가 달라졌다. 반면 2026-02-01은 같은 날짜가 됐다. 공식 날짜 입력 설명 (새 탭에서 열림)은 이런 모호한 입력의 순서 규칙과 연·월·일 ISO 형식을 구분해 설명한다.

별도 저장 비교도 수행했다. 서로 다른 두 연결에서 같은 슬래시 문자열을 date 열에 한 행씩 넣고 커밋한 뒤, 새 연결에서 함께 읽었다. MDY로 넣은 행의 월·일은 1, 2, DMY로 넣은 행은 2, 1이었다. ISO 문자열로 넣은 대조 열은 두 행 모두 2월 1일이었다. 두 연결의 TimeZone은 모두 Etc/UTC로 유지했다. 이 비교에서는 문자열을 날짜로 읽는 단계에서 값이 갈렸다.

실습의 SET LOCAL은 해당 트랜잭션에서만 적용된다. 커밋이나 롤백 뒤에는 원래 연결 설정으로 돌아오며, 트랜잭션 밖에서 실행하면 경고와 함께 효과가 없다. 비교할 때 BEGIN을 포함해야 하는 이유다. 실습에서도 종료 뒤 ISO, MDY 복귀를 확인했다. SET의 적용 범위 (새 탭에서 열림)

표시만 바뀐 날짜는 월·일이 그대로다

화면에 01/02/2026이 보인다는 사실만으로 입력 오류를 판단할 수는 없다. 이미 만들어진 2월 1일을 다른 순서로 표시했을 수도 있다. 이를 구별하려면 같은 날짜 값에서 월과 일을 따로 읽는다. 아래 예제는 실습 DB에서 임시 테이블을 만들 수 있는 계정으로 실행한다.

BEGIN;
CREATE TEMP TABLE date_probe (day date);
INSERT INTO date_probe VALUES (DATE '2026-02-01');

SET LOCAL DateStyle = 'SQL, MDY';
SELECT day, extract(month FROM day) AS month,
       extract(day FROM day) AS day_of_month FROM date_probe;

SET LOCAL DateStyle = 'SQL, DMY';
SELECT day, extract(month FROM day) AS month,
       extract(day FROM day) AS day_of_month FROM date_probe;
ROLLBACK;

두 조회의 결과 행은 아래와 같았다. 열 순서는 day, month, day_of_month이며, psql의 열 맞춤과 명령 태그는 생략했다.

02/01/2026|2|1
01/02/2026|2|1

첫 열의 표기는 바뀌었지만 월은 계속 2, 일은 계속 1이다. 중간에 날짜를 다시 넣거나 수정하지도 않았다. 날짜 출력 설명 (새 탭에서 열림)에서 설명하는 SQL 스타일의 출력 순서 차이다. 자신의 열에 같은 extract 조회를 적용했을 때 월·일까지 다르다면 표시 설정만 바꿔 해결할 수 없다.

이미 잘못 해석해 저장한 날짜도 DateStyle을 바꾼다고 복원되지 않는다. 원래 문자열과 입력 계약이 없다면 2026-01-02가 올바른 1월 2일인지, 잘못 저장된 2월 1일인지 날짜 값만으로는 구별할 수 없다. 모든 행의 월·일을 일괄 교환하기 전에 원본 자료와 대조해야 한다.

받는 형식에 순서를 명시한다

새 입력 계약은 네 자리 연도가 앞에 오는 YYYY-MM-DD, 예를 들어 2026-02-01로 정할 수 있다. 슬래시를 하이픈으로 바꾼 01-02-2026만으로는 연·월·일 계약이 되지 않는다. 또한 ::date는 여러 형식을 받아들이므로, 캐스트 성공 자체가 입력이 ISO 형식을 지켰다는 검사는 아니다. 받는 쪽에서 약속한 형식과 실제 존재하는 날짜인지 확인해야 한다.

기존 파일을 계속 받아야 하고 순서가 일·월·년이라고 합의돼 있다면, 세션 기본값에 맡기지 않고 그 순서를 지정해 변환할 수 있다. 아래 조회는 같은 문자열에 서로 다른 계약을 적용한 비교다.

SELECT to_date('01/02/2026', 'DD/MM/YYYY') AS day_first,
       to_date('01/02/2026', 'MM/DD/YYYY') AS month_first;

ISO 출력으로 확인했을 때 day_first는 2026-02-01, month_first는 2026-01-02였다. 이 결과는 MDY·DMY 두 연결에서 같았다. 어느 쪽을 사용할지는 문자열 내용이 아니라 보내는 쪽과 합의한 순서로 결정한다.

다만 to_date에 형식을 지정하는 것과 정확한 문자 모양을 검증하는 것은 다르다. 같은 실습에서 to_date('01-02-2026', 'DD/MM/YYYY')도 2월 1일로 변환됐다. 슬래시만 허용하는 계약이라면 별도 형식 검증이 필요하다. 구분자에 관대한 동작은 형식 변환 함수 문서 (새 탭에서 열림)에 설명돼 있다.

입력 순서와 날짜의 유효성도 나눠 봐야 한다. 13/02/2026은 MDY에서 SQLSTATE 22008로 실패했고 DMY에서는 2026-02-13으로 변환됐다. 반면 없는 날짜인 2026-02-30은 MDY·DMY 모두 같은 오류로 거부됐다. 오류가 없다는 것만으로는 월과 일을 의도대로 읽었다고 판단할 수 없다. 01/02/2026처럼 양쪽 해석이 모두 유효한 입력이 그 예다.

직접 확인한 범위는 PostgreSQL 18.6과 psql의 문자열 캐스트, 날짜 저장, 표시 형식 및 명시적 형식 변환이다. 애플리케이션 드라이버의 날짜 바인딩과 커넥션풀 초기화는 실험하지 않았다. 적용할 때는 실제 입력이 문자열로 DB에 도착하는지, 그 전에 날짜형으로 변환되는지부터 확인한다.

← 전체 글 보기