DateTime — 시간을 포함한 달력 날짜
DateTime — 시간을 포함한 달력 날짜
달력 날짜뿐 아니라 시각까지 함께 다뤄야 할 때가 있어요. 예를 들어 "2015년 11월 21일 16시 1분" 같은 값을 표현해야 하는 거죠. 그런 civil time(민간 시각)을 다루는 타입이 DateTime이에요.
본문
class DateTime does Dateish {}
민간 시각을 다루기 위해 DateTime 객체는 연, 월, 일, 시, 분(전부 Int), 초(소수일 수 있음), 그리고 타임존을 저장해요.
날짜(그레고리력)와 시간 계산을 위한 메서드를 제공해요.
DateTime 메서드는 변경 불가능(immutable)해요. 하나를 수정하고 싶다면 수정된 복사본을 만드는 게 낫죠.
타임존은 이름이 아니라 UTC로부터의 초 단위 오프셋을 뜻하는 Int로 다뤄져요.
my $dt = DateTime.new(
year => 2015,
month => 11,
day => 21,
hour => 16,
minute => 1,
);
say $dt; # OUTPUT: «2015-11-21T16:01:00Z»
say $dt.later(days => 20); # OUTPUT: «2015-12-11T16:01:00Z»
say $dt.truncated-to('hour'); # OUTPUT: «2015-11-21T16:00:00Z»
say $dt.in-timezone(-8 * 3600); # OUTPUT: «2015-11-21T08:01:00-0800»
my $now = DateTime.now(formatter => { sprintf "%02d:%02d", .hour, .minute });
say $now; # 12:45 (or something like that)
버전 6.d부터는 7̈ 같은 합성(synthetic) 코드포인트를 쓰면 오류가 발생해요.
method new
multi method new(Int :$year!, Int :$month = 1, Int :$day = 1,
Int :$hour = 0, Int :$minute = 0, :$second = 0,
Int :$timezone = 0, :&formatter)
multi method new(Date :$date!,
Int :$hour = 0, Int :$minute = 0, :$second = 0,
Int :$timezone = 0, :&formatter)
multi method new(Int() $year, Int() $month, Int() $day,
Int() $hour, Int $minute, $second,
Int() :$timezone = 0, :&formatter)
multi method new(Instant:D $i, :$timezone=0, :&formatter)
multi method new(Numeric:D $posix, :$timezone=0, :&formatter)
multi method new(Str:D $format, :$timezone=0, :&formatter)
새 DateTime 객체를 만들어요. 만드는 방법이 여러 가지예요. (year, month, day, hour, ...) 구성요소를 각각 따로 넘기거나, 날짜 부분에 Date 객체를 넘기고 시간 부분을 구성요소별로 지정하거나, Instant로부터 시간을 얻고 타임존과 formatter만 넘기거나, Instant 대신 Unix 타임스탬프로 Numeric을 넘길 수 있어요(단, 마지막 방식은 Instant와 달리 윤초를 구분할 방법이 없어요).
ISO 8601 타임스탬프 표기법이나 완전한 RFC 3339 날짜·시간 형식의 Str을 넘길 수도 있어요. 문자열은 yyyy-mm-ddThh:mm:ssZ 또는 yyyy-mm-ddThh:mm:ss+0100 형식이어야 해요. ISO 8601 표준보다는 다소 관대한 편인데, 유니코드 숫자와 압축/확장 시간 형식의 혼용을 허용하기 때문이에요.
잘못된 입력 문자열은 X::Temporal::InvalidFormat 타입의 예외를 던져요. 타임존을 포함한 문자열을 주면서 타임존 이름 있는 인자까지 준다면 X::DateTime::TimezoneClash 타입의 예외가 던져져요.
my $datetime = DateTime.new(year => 2015,
month => 1,
day => 1,
hour => 1,
minute => 1,
second => 1,
timezone => 1);
$datetime = DateTime.new(date => Date.new('2015-12-24'),
hour => 1,
minute => 1,
second => 1,
timezone => 1);
$datetime = DateTime.new(2015, 1, 1, # First January of 2015
1, 1, 1); # Hour, minute, second with default timezone
$datetime = DateTime.new(now); # Instant.
# from a Unix timestamp
say $datetime = DateTime.new(1470853583.3); # OUTPUT: «2016-08-10T18:26:23.300000Z»
$datetime = DateTime.new("2015-01-01T03:17:30+0500") # Formatted string
Rakudo 2022.03 릴리스부터는 day 매개변수에 Callable을 줄 수 있어요. *는 그 달의 마지막 날을, *-n은 마지막에서 n번째 전 날을 반환해요.
Rakudo 2022.07 릴리스부터는 주어진 날짜의 자정을 나타내도록 "YYYY-MM-DD" 문자열만 지정하는 것도 가능해요.
say DateTime.new("2023-03-04"); # OUTPUT: «2023-03-04T00:00:00Z»
method now
method now(:$timezone = $*TZ, :&formatter --> DateTime:D)
현재 시스템 시간으로 새 DateTime 객체를 만들어요. 사용자 지정 formatter와 타임존을 제공할 수 있어요. :$timezone은 GMT로부터의 초 단위 오프셋이고, 기본값은 $*TZ 변수의 값이에요.
say DateTime.now; # OUTPUT: «2018-01-08T13:05:32.703292-06:00»
아래처럼 .now 뒤에 메서드를 연결해서 현재 값을 쉽게 표현할 수도 있어요.
say DateTime.now.year; # OUTPUT: «2018»
method clone
method clone(DateTime:D: :$year, :$month, :$day, :$hour, :$minute, :$second, :$timezone, :&formatter)
invocant를 바탕으로 새 DateTime 객체를 만들되, 주어진 인자가 invocant의 값을 덮어써요.
say DateTime.new('2015-12-24T12:23:00Z').clone(hour => 0);
# OUTPUT: «2015-12-24T00:23:00Z»
이런 clone이 어떤 상황에서는 유효하지 않은 날짜를 만들 수도 있어요. 그런 경우 예외가 던져져요.
say DateTime.new("2012-02-29T12:34:56Z").clone(year => 2015);
CATCH { default { put .^name, ': ', .Str } };
# OUTPUT: «X::OutOfRange: Day out of range. Is: 29, should be in 1..28»
method hh-mm-ss
method hh-mm-ss(DateTime:D: --> Str:D)
객체가 나타내는 시간을 24시간제 HH:MM:SS 형식 문자열로 반환해요.
say DateTime.new("2052-02-29T22:34:56Z").hh-mm-ss;
# OUTPUT: «22:34:56»
method hour
method hour(DateTime:D: --> Int:D)
시(hour) 구성요소를 반환해요.
say DateTime.new('2012-02-29T12:34:56Z').hour; # OUTPUT: «12»
method minute
method minute(DateTime:D: --> Int:D)
분(minute) 구성요소를 반환해요.
say DateTime.new('2012-02-29T12:34:56Z').minute; # OUTPUT: «34»
method second
method second(DateTime:D:)
초(second) 구성요소를 반환해요. 소수 초를 포함할 수 있어요.
say DateTime.new('2012-02-29T12:34:56Z').second; # OUTPUT: «56»
say DateTime.new('2012-02-29T12:34:56.789Z').second; # OUTPUT: «56.789»
say DateTime.new('2012-02-29T12:34:56,789Z').second; # comma also ok
method whole-second
method whole-second(DateTime:D:)
초 구성요소를 Int로 내림해 반환해요.
say DateTime.new('2012-02-29T12:34:56.789Z').whole-second; # OUTPUT: «56»
method timezone
method timezone(DateTime:D: --> Int:D)
타임존을 UTC로부터의 오프셋(초)으로 반환해요.
say DateTime.new('2015-12-24T12:23:00+0200').timezone; # OUTPUT: «7200»
method offset
method offset(DateTime:D: --> Int:D)
타임존을 UTC로부터의 오프셋(초)으로 반환해요. method timezone의 별칭이에요.
say DateTime.new('2015-12-24T12:23:00+0200').offset; # OUTPUT: «7200»
method offset-in-minutes
method offset-in-minutes(DateTime:D: --> Real:D)
타임존을 UTC로부터의 오프셋(분)으로 반환해요.
say DateTime.new('2015-12-24T12:23:00+0200').offset-in-minutes; # OUTPUT: «120»
method offset-in-hours
method offset-in-hours(DateTime:D: --> Real:D)
타임존을 UTC로부터의 오프셋(시간)으로 반환해요.
say DateTime.new('2015-12-24T12:23:00+0200').offset-in-hours; # OUTPUT: «2»
method Str
method Str(DateTime:D: --> Str:D)
invocant를 formatter가 하는 방식대로 문자열로 표현해 반환해요. formatter를 지정하지 않았다면 ISO 8601 타임스탬프가 반환돼요.
say DateTime.new('2015-12-24T12:23:00+0200').Str;
# OUTPUT: «2015-12-24T12:23:00+02:00»
method Instant
method Instant(DateTime:D: --> Instant:D)
invocant를 바탕으로 Instant 객체를 반환해요.
say DateTime.new('2015-12-24T12:23:00+0200').Instant; # OUTPUT: «Instant:1450952616»
method Real
multi method Real(DateTime:D: --> Instant:D)
invocant를 Instant로 변환해요. Instant 메서드로도 같은 값을 얻을 수 있어요.
Rakudo 컴파일러 2023.02 릴리스부터 사용할 수 있어요.
method Numeric
multi method Numeric(DateTime:D: --> Instant:D)
Rakudo 컴파일러 2021.09 릴리스부터 사용할 수 있어요.
invocant를 Instant로 변환해요. Instant 메서드로도 같은 값을 얻을 수 있어요. 이 덕분에 DateTime 객체를 산술 연산에 바로 쓸 수 있어요.
method day-fraction
method day-fraction(DateTime:D: --> Real:D)
인스턴스의 시간을 24시간 하루의 분수로 반환해요.
say DateTime.new('2021-12-24T12:23:00.43Z').day-fraction; # OUTPUT: «0.5159772»
day-fraction 값은 같은 인스턴스의 수정 줄리안 일(modified-julian-date)의 소수 부분과 같아요.
그리고 윤초 때문에 기대값과 약간 달라질 수 있다는 점도 주의하세요.
for 30, 31 { say DateTime.new(2016,12,$_,12,0,0).day-fraction }
# OUTPUT: «0.50.499994»
Rakudo 컴파일러 2021.04 릴리스부터 사용할 수 있어요.
method julian-date
method julian-date(DateTime:D: --> Real:D)
UTC 날짜·시간에 대한 줄리안 일(JD)을 반환해요.
say DateTime.new('2021-12-24T12:23:00.43Z').julian-date; # OUTPUT: «2459573.0159772»
줄리안 일은 proleptic 그레고리력 기준 기원전 4714년 11월 24일 정오(UTC)의 epoch에서 0으로 시작해요(세계 대부분과 국제 상업·여행에서 쓰는 달력이죠). JD는 천문학에서 천체가 지구 본초자오선을 통과하는 시각을 정의하는 데 쓰여요. 어떤 순간에 대해서든 그 epoch부터 그 순간까지의 전체 일 수와 하루의 분수의 합이에요.
Rakudo 2025.08 릴리스 이전에는 julian-date 메서드가 타임존 정보를 전혀 쓰지 않고 DateTime 객체에서 그것을 조용히 무시했어요.
Rakudo 컴파일러 2021.04 릴리스부터 사용할 수 있어요.
method modified-julian-date
method modified-julian-date(DateTime:D: --> Real:D)
UTC 날짜·시간에 대한 수정 줄리안 일(MJD)을 반환해요.
say DateTime.new('2021-12-24T12:23:00.43Z').modified-julian-date; # OUTPUT: «59572.5159772»
수정 줄리안 일의 소수 부분은 같은 인스턴스의 day-fraction과 같은 값이고, 마찬가지로 윤초의 영향을 받아요. 똑같이, MJD의 정수 부분은 같은 순간의 daycount와 같은 값인데, 둘이 같은 epoch(1858년 11월 17일)를 참조하기 때문이에요. MJD는 줄리안 일에서 상수 2_400_000.5를 빼서 얻으며, 민간 시간 체계와 천문 시간 체계 사이의 변환을 단순화하는 데 쓰여요.
Rakudo 2025.08 릴리스 이전에는 modified-julian-date 메서드가 타임존 정보를 전혀 쓰지 않고 DateTime 객체에서 그것을 조용히 무시했어요.
Rakudo 컴파일러 2021.04 릴리스부터 사용할 수 있어요.
method posix
method posix(Bool:D: $ignore-timezone = False --> Int:D)
날짜와 시간을 POSIX/Unix 타임스탬프(POSIX epoch, 1970-01-01T00:00:00Z 이후의 non-leap 초)로 반환해요.
$ignore-timezone이 True면 DateTime 객체가 타임존 오프셋이 0인 것처럼 취급돼요.
method posix(Bool:D: $ignore-timezone = False, :$real --> Num:D)
POSIX 시간은 윤초를 무시하므로, 이 메서드는 윤초와 그 바로 다음 초를 같은 타임스탬프로 합친다는 점을 주의하세요. 더 정밀한 게 필요하면 관련 메서드에 대해 Instant를 보세요.
Rakudo 컴파일러 2022.06 릴리스부터 :real 이름 있는 인자를 지정할 수도 있어요. 참으로 지정하면 Num이 반환되어 POSIX epoch 이후의 초 수를 소수 초까지 정밀하게 얻을 수 있어요.
say DateTime.new('2015-12-24T12:23:00Z').posix; # OUTPUT: «1450959780»
say DateTime.new('2022-06-21T12:23:00.5Z').posix; # OUTPUT: «1655814180»
say DateTime.new('2022-06-21T12:23:00.5Z').posix(:real); # OUTPUT: «1655814180.5»
method truncated-to
method truncated-to(DateTime:D: Cool $unit)
지정된 단위보다 작은 모든 것을 가능한 가장 작은 값으로 잘라낸 invocant의 복사본을 반환해요.
my $d = DateTime.new("2012-02-29T12:34:56.946314Z");
say $d.truncated-to('second'); # OUTPUT: «2012-02-29T12:34:56Z»
say $d.truncated-to('minute'); # OUTPUT: «2012-02-29T12:34:00Z»
say $d.truncated-to('hour'); # OUTPUT: «2012-02-29T12:00:00Z»
say $d.truncated-to('day'); # OUTPUT: «2012-02-29T00:00:00Z»
say $d.truncated-to('month'); # OUTPUT: «2012-02-01T00:00:00Z»
say $d.truncated-to('year'); # OUTPUT: «2012-01-01T00:00:00Z»
소수 초가 있는 DateTime은 .truncated-to('second')로 정수 초까지 잘라낼 수 있어요.
method Date
multi method Date(DateTime:U --> Date:U)
multi method Date(DateTime:D --> Date:D)
invocant를 Date로 변환해요.
say DateTime.new("2012-02-29T12:34:56.946314Z").Date; # OUTPUT: «2012-02-29»
say DateTime.Date; # OUTPUT: «(Date)»
method DateTime
method DateTime(--> DateTime)
invocant 자신을 반환해요.
say DateTime.new("2012-02-29T12:34:56.946314Z").DateTime;
# OUTPUT: «2012-02-29T12:34:56.946314Z»
say DateTime.DateTime;
# OUTPUT: «(DateTime)»
method utc
method utc(DateTime:D: --> DateTime:D)
같은 시간이지만 UTC 타임존에 있는 DateTime 객체를 반환해요.
say DateTime.new('2015-12-24T12:23:00+0200').utc;
# OUTPUT: «2015-12-24T10:23:00Z»
method in-timezone
method in-timezone(DateTime:D: Int(Cool) $timezone = 0 --> DateTime:D)
같은 시간이지만 지정된 $timezone(GMT로부터의 초 단위 오프셋)에 있는 DateTime 객체를 반환해요.
say DateTime.new('2015-12-24T12:23:00Z').in-timezone(3600 + 1800); # OUTPUT: «2015-12-24T13:53:00+0130»
RFC 7164에 따르면 윤초는 지역 시간을 따르지 않고 항상 UTC 하루의 끝에 발생해요.
say DateTime.new: '2017-01-01T00:59:60+01:00'
# OUTPUT: «2017-01-01T00:59:60+01:00»
method local
method local(DateTime:D: --> DateTime:D)
같은 시간이지만 로컬 타임존($*TZ)에 있는 DateTime 객체를 반환해요.
my $*TZ = -3600;
say DateTime.new('2015-12-24T12:23:00+0200').local; # OUTPUT: «2015-12-24T09:23:00-0100»
sub infix:<->
multi infix:<-> (DateTime:D, Duration:D --> DateTime:D)
multi infix:<-> (DateTime:D, DateTime:D --> Duration:D)
뺄 대상 DateTime과, 뺄 Duration 또는 다른 DateTime 객체를 받아요. 각각 새 DateTime 객체 또는 두 날짜 사이의 Duration을 반환해요. Duration을 뺄 때는 원래 DateTime의 타임존이 반환된 DateTime 객체에 보존돼요.
say raku DateTime.new(:2016year) - DateTime.new(:2015year):;
# OUTPUT: «Duration.new(31536001.0)»
say DateTime.new(:2016year, :3600timezone) - Duration.new(31536001.0);
# OUTPUT: «2015-01-01T00:00:00+01:00»
sub infix:<+>
multi infix:<+> (DateTime:D, Duration:D --> DateTime:D)
multi infix:<+> (Duration:D, DateTime:D --> DateTime:D)
DateTime를 받아 주어진 Duration만큼 늘리되 타임존을 보존해요.
say DateTime.new(:2015year) + Duration.new(31536001.0);
# OUTPUT: «2016-01-01T00:00:00Z»
say Duration.new(42) + DateTime.new(:2015year, :3600timezone);
# OUTPUT: «2015-01-01T00:00:42+01:00»
sub infix:
multi infix:<cmp>(DateTime:D \a, DateTime:D \b --> Order:D)
동등한 순간을 비교해 Order를 반환해요.