sqlx와 PostgreSQL 타임존 완벽 정복!
안녕하세요!
오늘은 Go 언어에서 sqlx와 Postgres를 사용해 타임존을 다루는 방법에 대해 알아보겠습니다.
사실 거의 database/sql 패키지 이야기가 될 것 같긴 한데요.
UTC 기준으로 데이터를 저장하는 PostgreSQL에서, Go 쪽에서 특정 타임존을 기준으로 PostgreSQL의 timestamp 데이터를 가져오고 싶습니다.
원본 데이터
sample-db=# \d test_timezone
테이블 "public.test_timezone"
컬럼 | 타입 | 콜Collation | Nullable | 기본값
-----------+--------------------------+-----------+----------+---------
test_time | timestamp with time zone | | |
sample-db=# SELECT * from test_timezone;
test_time
------------------------
2025-01-12 15:00:00+00
(1 row)
얻고 싶은 결과
2025-01-13 00:00:00 +0900 KST
그대로 가져오기
결과를 그대로 가져와 봤는데요.
var examTime time.Time
db.Get(&examTime, "SELECT test_time from test_timezone")
fmt.Println(examTime)
// 출력
// 2025-01-12 15:00:00 +0000 UTC
time.Now()
같은 경우에는 Go를 실행하는 서버의 타임존에 맞춰지기도 하는데요.
이 경우에는 서버 자체의 타임존은 영향을 주지 않는 것 같아요.
timestamp 타입 OR timestamptz 타입
데이터 타입이 timestamptz
타입이어야만 타임존 정보를 얻을 수 있는데요.
var examTime time.Time
db.Get(&examTime, "SELECT test_time::timestamp from test_timezone")
fmt.Println(examTime)
// 출력
// 2025-01-12 15:00:00 +0000 +0000
PostgreSQL의 timezone
함수를 사용하면 다음과 같은데요.
시간은 원하는 대로 나왔지만, timezone
함수를 사용했음에도 불구하고 반환되는 값의 타입이 timestamp
이기 때문에 타임존 정보를 얻을 수는 없네요.
😥
[참고 자료: PostgreSQL 8.4.4 문서 9.9.
날짜/시간 함수와 연산자](https://www.postgresql.org/docs/8.4/functions-datetime.html)
var examTime time.Time
db.Get(&examTime, "SELECT timezone('KST', test_time::timestamptz) FROM test_timezone;")
fmt.Println(examTime)
// 출력
// 2025-01-13 00:00:00 +0000 +0000
해결 방법
세션의 타임존을 원하는 값으로 설정해주면 예상했던 값을 얻을 수 있습니다.
다만, 한번 변경하면 명시적으로 되돌리기 전까지는 해당 연결에서는 계속 타임존이 변경된 상태로 유지되니 주의해야 합니다.
var examTime time.Time
db.Exec("SET SESSION timezone TO 'Asia/Tokyo';")
db.Get(&examTime, "SELECT test_time from test_timezone")
fmt.Println(examTime)
// 출력
// 2025-01-13 00:00:00 +0900 JST
다른 해결 방법
이 방법은 단순한 PostgreSQL 팁이 되어버리는데요.
실용성은 떨어지지만, Go에서 타임존을 제어하는 방법을 알아볼게요.
이 내용을 참고해서 Scan
함수를 구현하면 데이터를 읽어올 때 자체적으로 처리 로직을 작성할 수 있습니다.
type MyTime time.Time
const TZ = "Asia/Seoul"
// println을 위한 함수입니다.
func (m MyTime) String() string {
return time.Time(m).String()
}
// 읽기 함수를 구현합니다.
func (m *MyTime) Scan(value any) error {
if value == nil {
m = &MyTime{}
return nil
}
t, ok := value.(time.Time)
if !ok {
return fmt.Errorf("타입이 맞지 않는데요.")
}
tz, _ := time.LoadLocation(TZ)
mt := MyTime(t.In(tz))
*m = mt
return nil
}
func createMyType(db *sqlx.DB) {
var examTime MyTime
db.Get(&examTime, "SELECT test_time from test_timezone")
fmt.Println(examTime)
}
// 출력
// 2025-01-13 00:00:00 +0900 KST
직접 정의한 타입을 사용하는 것은 번거로울 수도 있어서 저는 timestamp
타입 그대로 사용할 것 같은데요.
하지만 JSON 타입이나 배열 타입을 SELECT해서 바로 구조체에 넣고 싶을 때 Scan
메서드 내에서 Unmarshal
하는 식으로 활용하면 꽤 유용한 팁이 될 수 있습니다.
그럼.
'Go' 카테고리의 다른 글
Go 언어로 블록체인 직접 만들기: 개념부터 구현까지 쉽게 파헤치기 (0) | 2025.02.15 |
---|---|
Steam(스팀)이 Go(고) 런타임을 망가뜨린다고? 게임 개발, 이제 Go로도 가능한가? (0) | 2025.02.09 |
예상치 못한 untyped int의 동작, 왜 이런 일이? (0) | 2025.01.12 |
Go에서 구조체 패킹(structure packing) 이해하기 (0) | 2024.12.28 |
Go 언어의 reflect 패키지: 첫걸음부터 실전 활용까지 (1) | 2024.11.08 |