Skip to content

Instantly share code, notes, and snippets.

@YangSiJun528
Last active June 18, 2025 06:48
Show Gist options
  • Select an option

  • Save YangSiJun528/f7095391f7ace37f5198c82dc50fc2cd to your computer and use it in GitHub Desktop.

Select an option

Save YangSiJun528/f7095391f7ace37f5198c82dc50fc2cd to your computer and use it in GitHub Desktop.
`@JsonFormat` LocalDateTimed와 timezone 설정하는법 & FE/BE에서 타임존 포함 시간 관리 관리하는 법

코드 결과 설명

LocalDateTime의 경우 타임존 정보가 없는 시간 데이터라서, @JsonFormat의 timezone 설정이 적용되지 않는다.

적용하기 위해선 다음 옵션을 사용할 수 있다.

  • OffsetDateTime, ZonedDateTime으로 변경
  • 커스텀 어노테이션/Converter 이나 커스텀 Serializer/Deserializer
  • Instant 사용 (UTC 고정인 경우)
  • 타입존 값 'Z'+09:00 같은 값 하드코딩

시간 관리를 어떻게 하는게 좋은가?

개인적으로는 시간과 관련한 구체적인 요구사항이 없다면 아래 조건을 따르는 것을 선호한다.
(한 국가 or 시차를 대상으로 하는 경우에도 적용한다.)


내부 로직(FE/BE)에서 시간을 관리/저장할 때는 UTC로 통일하여 관리한다.
대부분에 언어에선 UTC 기준으로 시간 정보를 저장하는 타입이 존재한다. 자바에선 Instant로 관리할 수 있다.

외부 사용자에게 시차 처리가 필요한 경우에만 변환해서 제공해주고, 내부 시간 관리에선 통일하여 관리할 수 있다.
시차 처리에는 FE가 책임을 가진다. 브라우저나 상대방의 디바이스 설정에 맞게 시차를 변환하여 UI에 보여준다.

다음 장점이 있다.

  • 서버 간 시간 비교, 로그 분석, 이벤트 정렬 등에 이점
  • 하나의 통일관 시차를 사용하므로 모호함을 제거
  • DB, 시스템 로그와의 연계 용이
  • 관리/UI 변경에 잘 대응
package com.example.datetime;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.junit.jupiter.api.Test;
import java.time.*;
import java.util.Map;
/**
* Test class demonstrating Jackson JSON serialization of various Java date/time types.
* This test shows how different @JsonFormat annotations affect the serialization output
* for LocalDateTime, OffsetDateTime, ZonedDateTime, and Instant objects.
*/
public class DateTimeSerializationTest {
/**
* Sample DTO class containing various date/time fields with different JsonFormat configurations.
* Each field demonstrates a different serialization pattern and timezone handling approach.
*/
static class SampleDto {
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'")
public LocalDateTime localWithZ;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss", timezone = "UTC")
public LocalDateTime localWithTimezone;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", timezone = "UTC")
public LocalDateTime localWithHardcodedZ;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS", timezone = "UTC")
public LocalDateTime localWithIsoFormatUtc;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS", timezone = "Asia/Seoul")
public LocalDateTime localWithIsoFormatKst;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", timezone = "UTC")
public OffsetDateTime offsetDateTime;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", timezone = "UTC")
public ZonedDateTime zonedDateTime;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", timezone = "UTC")
public Instant instant;
}
@Test
public void testDateTimeSerialization() throws Exception {
// Create ObjectMapper with JavaTimeModule for Java 8 time support
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
// Create a specific date/time for consistent test results
LocalDateTime now = LocalDateTime.of(2025, 6, 18, 15, 30, 45);
// Create and populate the sample DTO
SampleDto dto = new SampleDto();
dto.localWithZ = now;
dto.localWithTimezone = now;
dto.localWithHardcodedZ = now;
dto.localWithIsoFormatUtc = now;
dto.localWithIsoFormatKst = now;
dto.offsetDateTime = now.atOffset(ZoneOffset.UTC);
dto.zonedDateTime = now.atZone(ZoneId.of("UTC"));
dto.instant = now.atZone(ZoneId.of("UTC")).toInstant();
// Serialize the DTO to JSON string
String json = mapper.writeValueAsString(dto);
System.out.println("Serialized JSON:");
System.out.println(json);
System.out.println();
// Parse the JSON back to a Map for detailed field inspection
Map<String, String> result = mapper.readValue(json, Map.class);
System.out.println("Individual field serialization results:");
result.forEach((fieldName, serializedValue) ->
System.out.printf("%-25s → %s%n", fieldName, serializedValue));
System.out.println();
System.out.println("Field descriptions:");
System.out.println("localWithZ → LocalDateTime with hardcoded 'Z' suffix (no timezone conversion)");
System.out.println("localWithTimezone → LocalDateTime converted to UTC timezone (no Z suffix)");
System.out.println("localWithHardcodedZ → LocalDateTime converted to UTC with hardcoded 'Z' suffix");
System.out.println("localWithIsoFormatUtc → LocalDateTime with milliseconds, UTC timezone (shows Z)");
System.out.println("localWithIsoFormatKst → LocalDateTime with milliseconds, Seoul timezone (converted time)");
System.out.println("offsetDateTime → OffsetDateTime with timezone offset (shows Z)");
System.out.println("zonedDateTime → ZonedDateTime converted to UTC with offset (shows Z)");
System.out.println("instant → Instant (always UTC) with 'Z' suffix");
System.out.println();
System.out.println("Expected output:");
System.out.println("localWithZ → 2025-06-18T15:30:45Z");
System.out.println("localWithTimezone → 2025-06-18T15:30:45");
System.out.println("localWithHardcodedZ → 2025-06-18T15:30:45Z");
System.out.println("localWithIsoFormatUtc → 2025-06-18T15:30:45.000");
// Note: localWithIsoFormatKst will be not converted to Seoul time. Becasuse type is LocalDateTime.
System.out.println("localWithIsoFormatKst → 2025-06-18T15:30:45.000");
System.out.println("offsetDateTime → 2025-06-18T15:30:45Z");
System.out.println("zonedDateTime → 2025-06-18T15:30:45Z");
System.out.println("instant → 2025-06-18T15:30:45Z");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment