`Calendar.ISO`
Calendar.ISO
ISO 8601을 따르는 그레고리력(Gregorian calendar)인, Elixir의 기본 달력 구현이에요.
이 달력은 프롤렙틱(proleptic) 그레고리력을 구현하므로 오늘날 대부분의 국가에서 쓰는 달력과 호환됩니다. "프롤렙틱"이 의미하는 바는 윤년 규칙이 모든 시대에 적용된다는 것이에요. 그래서 그레고리력이 채택되기 이전인 1583년 이전의 날짜는 조금 다른 결과를 줍니다.
출처:
Calendar.ISO
본문
ISO 8601 준수(compliance)
ISO 8601 명세는 기능이 풍부하지만, 애플리케이션이 대부분의 부분을 선택적으로 구현하게 허용해요. Elixir가 내린 선택들은 아래에 정리되어 있습니다.
지원 기능
표준 라이브러리는 가능한 ISO 8601 기능 중 최소한의 집합을 지원합니다. 구체적으로 파서는 달력 날짜(calendar date)만 지원하고 순번(ordinal)과 주(week) 형식은 지원하지 않아요. 게다가 ISO 8601 형식의 기간(duration) 파싱(음수 시간 단위와 분수 초 포함)을 지원합니다.
기본적으로 Elixir는 확장 형식(extended-format)의 날짜·시간만 파싱합니다. 기본 형식(basic-format)을 파싱하도록 선택할 수도 있어요.
NaiveDateTime.to_iso8601/2와 DateTime.to_iso8601/2는 기본 형식 또는 확장 형식 문자열을 생성하게 해주고, Calendar.strftime/2는 원하는 대로 datetime을 포매팅하게 해줍니다.
Elixir는 축소 정확도 형식(예: 일(day) 구성 요소가 없는 날짜)도, 최하위 구성 요소의 십진 정밀도(예: 10:01:25,5)도 지원하지 않습니다.
예시를 볼게요. Elixir는 기본적으로 파싱 시 확장 형식을 기대합니다:
iex> Calendar.ISO.parse_naive_datetime("2015-01-23T23:50:07")
{:ok, {2015, 1, 23, 23, 50, 7, {0, 0}}}
iex> Calendar.ISO.parse_naive_datetime("20150123T235007")
{:error, :invalid_format}
원하면 파싱을 기본 형식으로 제한할 수 있어요:
iex> Calendar.ISO.parse_naive_datetime("20150123T235007Z", :basic)
{:ok, {2015, 1, 23, 23, 50, 7, {0, 0}}}
iex> Calendar.ISO.parse_naive_datetime("20150123T235007Z", :extended)
{:error, :invalid_format}
파싱에서는 달력 날짜만 지원합니다. 순번 날짜와 주 날짜는 지원하지 않아요:
iex> Calendar.ISO.parse_date("2015-04-15")
{:ok, {2015, 4, 15}}
iex> Calendar.ISO.parse_date("2015-105")
{:error, :invalid_format}
iex> Calendar.ISO.parse_date("2015-W16")
{:error, :invalid_format}
연, 월, 일, 시, 분, 초는 완전히 지정되어야 합니다:
iex> Calendar.ISO.parse_date("2015-04-15")
{:ok, {2015, 4, 15}}
iex> Calendar.ISO.parse_date("2015-04")
{:error, :invalid_format}
iex> Calendar.ISO.parse_date("2015")
{:error, :invalid_format}
iex> Calendar.ISO.parse_time("23:50:07.0123456")
{:ok, {23, 50, 7, {12345, 6}}}
iex> Calendar.ISO.parse_time("23:50:07")
{:ok, {23, 50, 7, {0, 0}}}
iex> Calendar.ISO.parse_time("23:50")
{:error, :invalid_format}
확장(Extensions)
파서와 포매터는 하나의 ISO 8601 확장을 채택합니다. 바로 확장 연도 표기(extended year notation)예요.
이를 통해 날짜에 + 또는 - 부호를 접두어로 붙일 수 있어, 표현 가능한 연도 범위가 기본(0000..9999)에서 -9999..9999로 확장됩니다. Elixir는 여전히 이 형식의 연도를 네 자리로 제한해요.
iex> Calendar.ISO.parse_date("-2015-01-23")
{:ok, {-2015, 1, 23}}
iex> Calendar.ISO.parse_date("+2015-01-23")
{:ok, {2015, 1, 23}}
iex> Calendar.ISO.parse_naive_datetime("-2015-01-23 23:50:07")
{:ok, {-2015, 1, 23, 23, 50, 7, {0, 0}}}
iex> Calendar.ISO.parse_utc_datetime("+2015-01-23 23:50:07Z")
{:ok, {2015, 1, 23, 23, 50, 7, {0, 0}}, 0}
추가(Additions)
ISO 8601은 파싱과 포매팅 모두에서 날짜와 시간 사이의 구분자로 T 대신 공백을 허용하지 않아요. 그런데 공백 구분은 충분히 흔한 표현이어서, Elixir는 파싱 중에는 그것을 허용합니다.
NaiveDateTime.to_iso8601/1과 DateTime.to_iso8601/1의 날짜 포매팅은 T 구분자를 사용하는 명세 준수 문자열 표현을 만들어 냅니다.
iex> Calendar.ISO.parse_naive_datetime("2015-01-23 23:50:07.0123456")
{:ok, {2015, 1, 23, 23, 50, 7, {12345, 6}}}
iex> Calendar.ISO.parse_naive_datetime("2015-01-23T23:50:07.0123456")
{:ok, {2015, 1, 23, 23, 50, 7, {12345, 6}}}
주요 함수들
Calendar.ISO는 Calendar behaviour의 모든 콜백을 구현하면서, 각 함수에 대한 직접 호출도 제공해요. 몇 가지 대표 함수를 살펴볼게요.
문자열 변환: date_to_string/4는 날짜를 문자열로 변환합니다. 기본적으로 사람이 읽기 좋은 "확장(extended)" 형식으로 반환하고, :basic 옵션을 넘기면 "기본(basic)" 형식도 지원해요.
iex> Calendar.ISO.date_to_string(2015, 2, 28)
"2015-02-28"
iex> Calendar.ISO.date_to_string(-99, 1, 31)
"-0099-01-31"
iex> Calendar.ISO.date_to_string(2015, 2, 28, :basic)
"20150228"
datetime_to_string/12는 타임존을 포함한 datetime을 문자열로 변환합니다.
iex> time_zone = "Etc/UTC"
iex> Calendar.ISO.datetime_to_string(2017, 8, 1, 1, 2, 3, {4, 5}, time_zone, "UTC", 0, 0)
"2017-08-01 01:02:03.00000Z"
iex> time_zone = "Europe/Berlin"
iex> Calendar.ISO.datetime_to_string(2017, 8, 1, 1, 2, 3, {4, 5}, time_zone, "CDT", 3600, 3600)
"2017-08-01 01:02:03.00000+02:00 CDT Europe/Berlin"
date_to_iodata/4와 datetime_to_iodata/12(둘 다 v1.19.0부터)는 문자열 대신 iodata를 반환한다는 점을 빼고 위와 동일해요.
파싱: parse_date/1, parse_time/1, parse_naive_datetime/1,2, parse_utc_datetime/1,2, parse_duration/1이 있습니다. 모두 문자열을 받아 {:ok, ...} 또는 {:error, ...}를 반환해요. 위에서 본 것처럼 ISO 8601 준수 규칙이 그대로 적용됩니다.
요일·날짜 계산: day_of_week/4는 주어진 연·월·일로 요일을 계산합니다. 값은 1부터 7까지의 정수인데, 여기서 1은 주어진 starting_on 요일을 뜻해요. 예를 들어 starting_on이 :monday면 1은 월요일, 7은 일요일입니다. :default는 :monday와 동등해요.
iex> Calendar.ISO.day_of_week(2016, 10, 31, :monday)
{1, 1, 7}
iex> Calendar.ISO.day_of_week(2016, 10, 31, :sunday)
{2, 1, 7}
iex> Calendar.ISO.day_of_week(2016, 10, 31, :saturday)
{3, 1, 7}
day_of_year/3는 연중 일수(1~366)를, day_of_era/3는 일과 시대(era) 튜플을, year_of_era/3는 연과 시대를 계산해요. quarter_of_year/3는 분기를 계산합니다.
월·윤년: days_in_month/2는 해당 연·월의 일 수를 반환합니다.
iex> Calendar.ISO.days_in_month(1900, 2)
28
iex> Calendar.ISO.days_in_month(2000, 2)
29
iex> Calendar.ISO.days_in_month(2004, 4)
30
leap_year?/1은 윤년 여부를, months_in_year/1은 그 해의 월 수(이 달력에서는 12)를 반환합니다.
기타 콜백: day_rollover_relative_to_midnight_utc/0은 {0, 1}을 반환합니다(이 달력에서 새 하루는 자정에 시작). iso_days_to_beginning_of_day/1·iso_days_to_end_of_day/1(v1.15.0부터)은 iso_days를 하루의 첫/마지막 순간으로 변환하고, naive_datetime_to_iso_days/7 등 변환 함수들이 있습니다. valid_date?/3(:default 값 지원)와 valid_time?/4는 유효성 검사를 담당해요. time_unit_to_precision/1은 시간 단위를 정밀도로 변환합니다.
shift_date/4, shift_time/5, shift_naive_datetime/8(v1.17.0부터)은 Duration.t()만큼 해당 값을 이동시킵니다.
이 모듈은 bce(값 0), ce, era, weekday 같은 몇 가지 내부 타입도 정의합니다. 예를 들어 t:weekday/0은 :monday부터 :sunday까지의 요일 atom들을 담고, t:era/0은 c:Calendar.year_of_era/3과 함께 사용되는 Calendar.era()의 부분 집합입니다. parse_date/2 같은 함수에서 :extended와 :basic 형식 지정에 쓰이는 t:format/0 타입도 있어요.
더 알아보기
- 이 달력이 구현하는 규약은
Calendar모듈에서 다룹니다. - 실제 date, time, datetime 구조체는
Date,Time,NaiveDateTime,DateTime을 참고하세요. - ISO 8601 명세 전체는 공식 ISO 문서에서 확인할 수 있어요.