1. 글을 작성하게 된 계기
진우, 동균님과 스터디를 진행하며 알게 된 내용을 정리하기 위해 글을 작성하게 되었습니다.
2. 개념
시간에 대해 알기 위해서는 기본적으로 알고 있어야 하는 개념들이 있는데, 이에 대해 살펴보겠습니다.
- ISO 8601
- Unix Time
- TimeZone
- UTC
- Zulu Time
- Offset
2-1. ISO 8601
ISO 8601은 날짜와 시간과 관련된 데이터를 전 세계적으로 교환하고 소통하는 데 사용되는 국제 표준 입니다. 이는 그레고리력 날짜를 사용하며, 표준 24시간 기반의 시간 표시를 따릅니다.
ISO 8601 is an international standard covering the worldwide exchange and communication of date and time-related data.
ISO 8601은 선택적으로 UTC 오프셋을 포함할 수 있어, 현지 시간과 UTC 시간 차이를 명확히 할 수 있습니다.
1
2
3
4
5
6
7
8
9
public class LocalDateTimeExample {
public static void main(String[] args) {
final LocalDateTime localDateTime = LocalDateTime.parse("2024-09-02T14:30:00");
final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 2024-09-02 14:30:00
final LocalDateTime result = localDateTime.format(formatter);
}
}
이는 각 날짜와 시간 요소의 의미를 맥락에 따라 해석하도록 하며, 표준 내에서 숫자가 아닌 January, Thursday 등의 요소는 사용할 수 없습니다. 표준은 지정된 숫자와 기호 외에 다른 양식의 사용을 금지하며, 모든 표현은 명확한 숫자적 의미 를 가져야 합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Main {
public static LocalDateTime parseISODateTime(String isoDateTime) {
DateTimeFormatter isoFormatter = DateTimeFormatter.ISO_DATE_TIME;
try {
return LocalDateTime.parse(isoDateTime, isoFormatter);
} catch (DateTimeParseException e) {
throw new IllegalArgumentException("Ex");
}
}
public static void main(String[] args) {
// 2024-09-02T14:30
final LocalDateTime dateTime = parseISODateTime.parseISODateTime("2024-09-02T14:30:00");
}
}
날짜와 시간은 가장 큰 시간 단위부터 가장 작은 시간 단위 순으로 배열하며, 아라비아 숫자와 특정한 컴퓨터 문장 부호(-, :, T, W, Z 등)를 사용합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {
public static void main(String[] args) {
// 2024-09-02T14:30Z
final ZonedDateTime zonedDateTime = handler.parseISOZonedDateTime("2024-09-02T14:30:00Z");
}
public static ZonedDateTime parseISOZonedDateTime(String isoDateTimeWithZone) {
DateTimeFormatter isoFormatter = DateTimeFormatter.ISO_ZONED_DATE_TIME;
try {
return ZonedDateTime.parse(isoDateTimeWithZone, isoFormatter);
} catch (DateTimeParseException e) {
throw new IllegalArgumentException("Ex");
}
}
}
2-2. Unix Time
Unix Time은 1970년 1월 1일 00:00:00 UTC부터 현재까지의 경과 시간을 초 단위로 환산한 시간 을 말합니다. 윤초는 무시하며, 유닉스 계열 운영체제 및 다른 운영 체제와 파일 형식에서 사용됩니다.
date +%s 명령어로 대부분의 유닉스 운영 체제에서 확인 가능하며, 2038년 문제(year 2038 problem)를 안고 있습니다.
1
2
$ date
2024년 9월 2일 월요일 20시 02분 54초 KST
2038년 문제는 32bit 로 표현된 Unix 시간은 2038년 1월 19일에 산술 오버플로 문제를 일으킬 수 있는 문제를 말합니다. 즉, 2038년은 int가 나타낼 수 있는 최대 수를 초과하기 때문에, 이를 정수 형태로 표현할 경우, 정확한 시간이 나오지 않는 것입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Main {
public static void main(String[] args) {
// 32비트 정수의 최대값 (Unix Time 최대값)
final int maxUnixTime = Integer.MAX_VALUE; // 2147483647
// 1초 후 overflow 발생
final int overflowUnixTime = maxUnixTime + 1; // -2147483648
}
public static String convertUnixTimeToDate(final int unixTime) {
final Date date = new Date((long) unixTime * 1000);
final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(date);
}
}
이를 해결하는 방법으로, 64bit의 시간 형식으로 전환을 할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {
public static void main(String[] args) {
// 64비트 Unix Time (Long.MAX_VALUE). 대략 292,277,026,596년까지 표현 가능
final long maxUnixTime = Long.MAX_VALUE / 1000000000L;
// maxUnixTime에 해당하는 날짜 결과
String dateAtMaxUnixTime = convertUnixTimeToDate(maxUnixTime);
}
public static String convertUnixTimeToDate(final long unixTime) {
final Date date = new Date(unixTime * 1000);
final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(date);
}
}
혹은 타임스탬프와 날짜 객체를 사용할 수도 있습니다.
1
2
3
4
5
6
7
public class Main {
public static void main(String[] args) {
// 최대 Instant 시간 (대략 10^19초 후까지 표현 가능)
final Instant maxInstant = Instant.ofEpochSecond(Long.MAX_VALUE);
final String dateAtMaxInstant = maxInstant.atZone(ZoneId.of("UTC")).format(DateTimeFormatter.ISO_INSTANT);
}
}
2-3. Timezone
타임존(Timezone)은 주로 국가 나 그 하위 구역(주, 도, 시 등) 의 경계를 따라 설정됩니다. 이를 통해 서로 다른 지역 간의 시간 차이로 인해 발생할 수 있는 혼란을 줄일 수 있습니다.
예를 들어, 미팅 때문에 한국에서 미국으로 갈 때, 13시간 비행을 한다고 가정해보겠습니다. 이 경우, 미국 시간을 기준으로 계산해야 하며, 아래와 같은 과정을 통해 미국 시간 기준 도착 시간을 알 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Main {
public static void main(String[] args) {
// 출발 시간 설정
final LocalDateTime startTimeInSeoul = LocalDateTime.of(2024, 9, 2, 14, 0);
// 출발 시간의 타임존을 서울로 설정
final ZonedDateTime departureTime = ZonedDateTime.of(startTimeInSeoul, ZoneId.of("Asia/Seoul"));
// 비행 시간 설정 (예: 13시간 비행)
final ZonedDateTime arrivalTimeInNewYork = departureTime.plusHours(13);
// 도착 시간의 타임존을 뉴욕으로 변환
final ZonedDateTime arrivalTime = arrivalTimeInNewYork.withZoneSameInstant(ZoneId.of("America/New_York"));
// 2024-09-02 15:00:00 EDT
final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");
final String formattedArrivalTime = arrivalTime.format(formatter);
}
}
지구는 자전하기 때문에, 지리적 위치에 따라 시간이 달라집니다. 이론상으로는 타임존이 경도에 따라 설정되어야 하지만, 현실적으로는 경도보다는 국가와 같은 행정 구역의 경계를 따라 타임존이 설정되는 경우가 많습니다. 예를 들어, 미국의 여러 주가 같은 시간대를 공유하거나, 유럽의 여러 국가들이 같은 시간대(CET)를 사용하고 있습니다.
2-4. UTC
UTC(Coordinated Universal Time)는 협정세계시를 의미하며, 국제적으로 합의된 시간 기준 입니다. UTC는 0° 경도(현재의 IERS 기준 자오선)에 위치한 평균 태양시(UT1)와 약 1초 이내의 차이를 유지하며, 이는 지구의 자전과 일치하도록 조정된 시간입니다. UTC는 일광 절약 시간제(Daylight Saving Time)를 따르지 않으며, 전 세계적으로 표준 시간대를 설정하는 기준이 됩니다.
이는 1972년 1월 1일 부터 시행되었으며, 이전의 국제 표준시는 GMT였습니다. GMT는 영국 런던 근처의 그리니치 천문대를 기준으로 한 시간대이며, 둘의 실질적 시간 차이는 없습니다. 단, UTC가 원자 시계를 사용해, 천문학적 관측에 기반한 시간으로 한 GMT 보다 정확한 시간 측정 방식을 사용합니다.
원자 시계: UTC는 원자 시계를 사용하여 더욱 정확한 시간 측정이 가능합니다.윤초: 윤초 조정을 통해 지구의 자전 속도 변화에 맞춰 시간을 조절합니다.
2-5. TransitionRule
TransitionRule은 시간대 관련 프로그래밍이나 시간 관리 시스템에서 시간대 변경이나 일광 절약 시간(Daylight Saving Time, DST)과 같은 시간 전환 규칙을 정의하는 데 사용되는 개념입니다. 이는 특정 날짜와 시간에 시간대 설정을 변경하는 방법을 지정합니다. 주요 구성 요소로는 규칙이 적용되기 시작하는 정확한 날짜와 시간을 정의하는 시작 날짜와 시간, 규칙이 끝나는 종료 날짜와 시간, 시간을 앞당기거나 뒤로 늦추는 시간 전환의 종류, 그리고 규칙이 적용되는 적용 시간대가 포함됩니다.
TransitionRule은 전 세계의 다양한 시간대와 그 변화를 추적하는 시간대 데이터베이스 관리, 많은 국가에서 채택하고 있는 일광 절약 시간(DST) 관리, 비즈니스 미팅, 항공편 일정, 국제 컨퍼런스 등의 계획을 세울 때 해당 지역의 시간대 변화를 고려하는 일정 관리 및 계획에 응용됩니다. 또한, 프로그래밍 언어와 라이브러리는 시간대 관리를 위한 API를 제공하며, Java의 java.time 패키지는 이러한 시간대 및 일광 절약 시간 변경을 관리하기 위한 여러 클래스와 메소드를 포함하고 있습니다. TransitionRule은 복잡한 글로벌 시간 체계를 관리하고, 다양한 지역 간의 시간 차이를 정확하게 계산하는 데 필수적인 요소입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Main {
public static void main(String[] args) {
// 뉴욕 타임존을 사용
final ZoneId zoneId = ZoneId.of("America/New_York");
// 현재 시간 기준으로 뉴욕의 타임존 규칙 가져오기
final ZoneRules zoneRules = zoneId.getRules();
// 2024년의 특정 날짜(3월 1일) 기준으로 타임존 규칙 가져오기
final LocalDateTime dateTime = LocalDateTime.of(2024, 3, 1, 0, 0);
final ZonedDateTime zonedDateTime = dateTime.atZone(zoneId);
// 해당 연도의 모든 타임존 전환(예: 일광 절약 시간 시작과 끝) 가져오기
final List<ZoneOffsetTransition> transitions = zoneRules.getTransitions();
for (ZoneOffsetTransition transition : transitions) {
System.out.println("Transition occurs on: " + transition.getDateTimeBefore());
System.out.println("Offset before transition: " + transition.getOffsetBefore());
System.out.println("Offset after transition: " + transition.getOffsetAfter());
System.out.println("Transition occurs on: " + transition.getDateTimeAfter());
System.out.println("===================================");
}
}
}
1
2
3
4
5
6
Transition occurs on: 2024-03-10T01:59:59
Offset before transition: -05:00
Offset after transition: -04:00
Transition occurs on: 2024-11-03T01:59:59
Offset before transition: -04:00
Offset after transition: -05:00
Zulu Time
Zulu Time은 군사 및 항공에서 자주 사용되는 용어로, 세계 시간의 기준이 되는 UTC(Coordinated Universal Time, 협정 세계시)를 의미합니다. 이는 군사 및 항공 분야에서 특히 중요한 역할을 하며, 전 세계적으로 통일된 시간 기준을 사용해, 서로 다른 시간대에 있는 여러 국가 간의 협력을 원활하게 할 수 있습니다.
Offset
Offset은 UTC를 기준으로 각 지역의 표준 시간대는 UTC에 대해 특정 시간만큼 더하거나 빼는 방식으로 설정됩니다.
예를 들어, KST(Korea Standard Time)는 한국 표준시를 의미하며, KST는 GMT+9 입니다. 이는 한국이 동경 135°에 위치해 있으며, 런던(GMT 기준)보다 약 135도 동쪽에 위치해 있기 때문입니다. 지구는 동쪽으로 자전하기 때문에, 동쪽에 위치한 지역은 시간이 더 빠르게 흐르며, 따라서 한국 표준시는 GMT보다 9시간 앞선, 135도를 15로 나눈 값인 9가 됩니다.
- UTC+0: 런던 (일광 절약 시간제가 적용되지 않는 겨울철)
- UTC+1: 파리, 베를린 (일광 절약 시간제 적용 전의 중앙유럽 시간)
- UTC-5: 뉴욕 (동부 표준시)
- UTC+9: 서울 (한국 표준시)
Summer Time
서머 타임(Summer Time), 또는 일반적으로 알려진 Daylight Saving Time (DST) 는 특정 기간 동안 표준시간에서 시간을 1시간 앞당기는 제도입니다. 이는 주로 봄부터 가을까지 적용되며, 여름철에 낮 시간을 더 효율적으로 활용하기 위해 고안되었습니다.
시간 간격 처리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Main {
public static void main(String[] args) {
final Duration interval = calculateInterval("2024-01-01T12:00:00", "2024-01-01T14:00:00");
// PT2H30M
final Duration duration = parseDuration("PT2H30M");
}
public static Duration calculateInterval(
final String startDateTime,
final String endDateTime
) {
try {
final LocalDateTime start = LocalDateTime.parse(startDateTime);
final LocalDateTime end = LocalDateTime.parse(endDateTime);
return Duration.between(start, end);
} catch (DateTimeParseException e) {
throw new IllegalArgumentException("Ex");
}
}
public static Duration parseDuration(final String duration) {
try {
return Duration.parse(duration);
} catch (DateTimeParseException e) {
throw new IllegalArgumentException("Ex");
}
}
}
반복 시간 처리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Main {
public static void main(String[] args) {
// recurringTimes = ["2024-01-01T12:00:00", "2024-01-02T12:00:00", "2024-01-03T12:00:00"]
final List<LocalDateTime> recurringTimes = calculateTimes("2024-01-01T12:00:00", "PT1D", 3);
}
public List<LocalDateTime> calculateTimes(
final String startDateTime,
final String duration,
final int repeatCount
) {
final List<LocalDateTime> times = new ArrayList<>();
try {
final LocalDateTime start = LocalDateTime.parse(startDateTime);
final Duration interval = Duration.parse(duration);
for (int index = 0; index < repeatCount; index++) {
times.add(start.plus(interval.multipliedBy(index)));
}
} catch (DateTimeParseException ex) {
throw new IllegalArgumentException("Ex");
}
return times;
}
}