DateTime

DateTime

시간대(time zone)를 가진 날짜·시간(datetime) 구현을 제공하는 모듈이에요. 이 datetime은 어떤 시간대에서의 날짜·시간을 찍은 **스냅샷(snapshot)**으로 볼 수 있어요. 그래서 UTC 오프셋과 Standard 오프셋을 모두 포함하고, 포맷팅 전용으로 쓰이는 존 약어(zone abbreviation) 필드도 함께 가집니다. 다만 미래의 datetime이 반드시 존재한다고 보장되지는 않아요 — 지정학적 이유 때문에 시간대 규칙이 언제든 바뀔 수 있으니까요. 자세한 내용은 "스냅샷으로서의 datetime" 섹션을 참고하세요.

Elixir의 ==/2, >/2, </2 같은 비교는 구조적(structural)이며 DateTime struct 필드를 기준으로 해요. datetime을 제대로 비교하려면 compare/2, after?/2, before?/2 함수를 사용하세요. 이 모듈의 compare/2 덕분에 Enum.min/2Enum.max/2Enum의 최소·최대 datetime도 구할 수 있어요. 예:

iex> Enum.min([~U[2022-01-12 00:01:00.00Z], ~U[2021-01-12 00:01:00.00Z]], DateTime)
~U[2021-01-12 00:01:00.00Z]

개발자라면 DateTime struct를 직접 만들기보다, 이 모듈이 제공하는 함수나 서드파티 달력 라이브러리의 함수를 활용하는 걸 권장해요.

출처: DateTime

본문

시간대 데이터베이스(Time zone database)

이 모듈의 많은 함수는 시간대 데이터베이스를 필요로 해요. 시간대 데이터베이스는 각 지역(locale)이 과거·현재·미래에 사용했거나 사용할 것으로 예상되는 UTC 오프셋의 기록이에요. 그 계획은 달라질 수 있으므로 주기적으로 갱신해야 합니다.

기본적으로 DateTimeCalendar.get_time_zone_database/0이 반환하는 기본 시간대 데이터베이스를 사용해요. 기본값은 Calendar.UTCOnlyTimeZoneDatabase인데, 이는 오직 "Etc/UTC" datetime만 처리하고 다른 시간대에 대해서는 {:error, :utc_only_time_zone_database}를 반환합니다.

다른 시간대 데이터베이스도 설정할 수 있어요. 사용 가능한 옵션과 라이브러리는 다음과 같아요.

이 중 하나를 쓰려면 먼저 mix.exs에 의존성으로 추가해야 해요. 그다음 설정을 통해 구성하거나,

config :elixir, :time_zone_database, Tz.TimeZoneDatabase

Calendar.put_time_zone_database/1을 호출해서 구성할 수 있어요.

Calendar.put_time_zone_database(Tz.TimeZoneDatabase)

정확한 이름은 각 라이브러리 설치 안내를 참고하세요.

스냅샷으로서의 datetime(Datetimes as snapshots)

첫 번째 섹션에서 datetime을 "어떤 시간대에서의 날짜·시간 스냅샷"이라고 했어요. 정확히 무슨 뜻인지 예를 통해 살펴볼게요.

폴란드에 사는 누군가가 내년에 브라질에 있는 사람과 회의를 잡으려 한다고 상상해 봅시다. 회의는 폴란드 시간대에서 새벽 2:30에 열릴 거예요. 그럼 브라질에서는 몇 시에 회의가 열릴까요?

오늘, 즉 1년 전 시점에서 시간대 데이터베이스를 조회하면 이 모듈의 API가 지금 당장은 유효한 답을 알려줄 거예요. 하지만 이 답은 미래에는 유효하지 않을 수 있어요. 왜냐하면 브라질과 폴란드 모두 시간대 규칙을 바꿀 수 있고, 그게 결과에 영향을 주기 때문이에요. 예를 들어 어떤 나라는 "서머타임(Daylight Saving Time)"에 들어가거나 그만둘 수도 있는데, 이는 매년 한 번 시계를 한 시간 앞으로 또는 한 시간 뒤로 조정하는 과정이에요. 규칙이 바뀔 때마다 폴란드 시간 새벽 2:30이 브라질에서 정확히 언제인지가 달라질 수 있습니다.

다시 말해 미래의 DateTime을 다룰 때는, 이벤트가 실제로 일어나기 전까지는 얻은 결과가 항상 정확하다는 보장이 없어요. 그래서 미래 시간을 물어보면, 그 답은 현재 시점의 시간대 규칙 상태를 반영한 스냅샷인 거예요. 과거의 datetime은 시간대 규칙이 과거 사건에 대해 바뀌지 않으므로 이런 문제가 없어요.

설상가상으로 폴란드 시간 새벽 2:30이 실제로는 존재하지 않거나 애매할 수도 있어요. 어떤 시간대가 "서머타임"을 시행하면 매년 시계를 한 번 앞으로 옮깁니다. 이때 존재하지 않는 시간대가 통째로 한 시간 생기죠. 그리고 시계를 뒤로 옮길 때는 어떤 한 시간이 두 번 일어납니다. 그래서 이 시프트 백(shifting back) 시점에 회의를 잡고 싶다면, 새벽 2:30 중 어느 쪽을 말하는 건지 명확히 밝혀야 해요 — 시프트 이전에 오는 "Summer Time", 아니면 시프트 이후에 오는 "Standard Time" 말이죠. 날짜·시간에 민감한 애플리케이션은 이런 시나리오를 고려해서 사용자에게 정확히 전달해야 합니다.

좋은 소식은 Elixir에 이런 문제를 해결하는 데 필요한 모든 구성 요소가 있다는 거예요. Elixir가 기본으로 쓰는 시간대 데이터베이스인 Calendar.UTCOnlyTimeZoneDatabase는 UTC만 처리하므로 이런 문제가 없어요. 제대로 된 시간대 데이터베이스를 들여오면 이 모듈의 함수들이 데이터베이스를 조회해 관련 정보를 반환합니다. 예를 들어 DateTime.new/4가 이 섹션에서 설명한 시나리오에 따라 서로 다른 결과를 반환하는 걸 보면 알 수 있어요.

시간대 사이 변환(Converting between timezones)

위 주의사항을 염두에 두고, 완전한 시간대 데이터베이스를 들여왔다고 가정하면 시간대 사이의 일반적인 시프트 예시가 몇 가지 있어요.

# Local time to UTC
new_york = DateTime.from_naive!(~N[2023-06-26T09:30:00], "America/New_York")
#=> #DateTime<2023-06-26 09:30:00-04:00 EDT America/New_York>

utc = DateTime.shift_zone!(new_york, "Etc/UTC")
#=> ~U[2023-06-26 13:30:00Z]

# UTC to local time
DateTime.shift_zone!(utc, "Europe/Paris")
#=> #DateTime<2023-06-26 15:30:00+02:00 CEST Europe/Paris>

Summary(요약)

Types

  • t()

Functions

  • add(datetime, amount_to_add, unit \\\\ :second, time_zone_database \\\\ Calendar.get_time_zone_database())DateTime에 지정된 시간을 더해요.
  • after?(datetime1, datetime2) — 첫 datetime이 둘째보다 엄격히 나중이면 true를 반환해요.
  • before?(datetime1, datetime2) — 첫 datetime이 둘째보다 엄격히 이르면 true를 반환해요.
  • compare(datetime1, datetime2) — 두 datetime struct를 비교해요.
  • convert(datetime, calendar) — 주어진 datetime을 한 달력에서 다른 달력으로 변환해요.
  • convert!(datetime, calendar) — 주어진 datetime을 한 달력에서 다른 달력으로 변환해요.
  • diff(datetime1, datetime2, unit \\\\ :second)datetime2datetime1에서 뺍니다.
  • from_gregorian_seconds(seconds, arg \\\\ {0, 0}, calendar \\\\ Calendar.ISO) — 그레고리오 초를 DateTime struct로 변환해요.
  • from_iso8601(string, format_or_calendar \\\\ Calendar.ISO)ISO 8601:2019의 확장 "Date and time of day" 형식을 파싱해요.
  • from_iso8601(string, calendar, format) — 달력과 모드(mode)를 모두 지정해 ISO8601에서 변환해요.
  • from_naive(naive_datetime, time_zone, time_zone_database \\\\ Calendar.get_time_zone_database()) — 주어진 NaiveDateTimeDateTime으로 변환해요.
  • from_naive!(naive_datetime, time_zone, time_zone_database \\\\ Calendar.get_time_zone_database()) — 주어진 NaiveDateTimeDateTime으로 변환해요(오류 시 예외).
  • from_unix(integer, unit \\\\ :second, calendar \\\\ Calendar.ISO) — 주어진 Unix 시간을 DateTime으로 변환해요.
  • from_unix!(integer, unit \\\\ :second, calendar \\\\ Calendar.ISO) — 주어진 Unix 시간을 DateTime으로 변환해요(오류 시 예외).
  • new(date, time, time_zone \\\\ "Etc/UTC", time_zone_database \\\\ Calendar.get_time_zone_database()) — date와 time struct로 datetime을 만들어요.
  • new!(date, time, time_zone \\\\ "Etc/UTC", time_zone_database \\\\ Calendar.get_time_zone_database()) — date와 time struct로 datetime을 만들되, 오류 시 예외를 던져요.
  • now(time_zone, time_zone_database \\\\ Calendar.get_time_zone_database()) — 주어진 시간대의 현재 datetime을 반환해요.
  • now!(time_zone, time_zone_database \\\\ Calendar.get_time_zone_database()) — 주어진 시간대의 현재 datetime을 반환하거나 오류 시 예외를 던져요.
  • shift(datetime, duration, time_zone_database \\\\ Calendar.get_time_zone_database()) — 주어진 datetime을 해당 달력에 따라 duration만큼 이동시켜요.
  • shift_zone(datetime, time_zone, time_zone_database \\\\ Calendar.get_time_zone_database())DateTime의 시간대를 바꿔요.
  • shift_zone!(datetime, time_zone, time_zone_database \\\\ Calendar.get_time_zone_database())DateTime의 시간대를 바꾸거나 오류 시 예외를 던져요.
  • to_date(datetime)DateTimeDate로 변환해요.
  • to_gregorian_seconds(datetime)DateTime struct를 그레고리오 초와 마이크로초로 변환해요.
  • to_iso8601(datetime, format \\\\ :extended, offset \\\\ nil) — 주어진 datetime을 ISO 8601:2019 형식으로 변환해요.
  • to_naive(datetime) — 주어진 datetimeNaiveDateTime으로 변환해요.
  • to_string(datetime) — 주어진 datetime을 해당 달력에 따라 문자열로 변환해요.
  • to_time(datetime)DateTimeTime으로 변환해요.
  • to_unix(datetime, unit \\\\ :second) — 주어진 datetime을 Unix 시간으로 변환해요.
  • truncate(datetime, precision) — microsecond 필드를 주어진 정밀도(:microsecond, :millisecond, :second)로 잘라낸 datetime을 반환해요.
  • utc_now(calendar_or_time_unit \\\\ Calendar.ISO) — 현재 UTC datetime을 반환해요.
  • utc_now(time_unit, calendar) — 특정 달력과 정밀도를 지원하는 현재 UTC datetime을 반환해요.

더 알아보기