Instant — 시간의 특정 순간
Instant — 시간의 특정 순간
Instant는 원자초(atomic second) 단위로 측정된, 분수(fraction)까지 포함하는 시간의 특정 순간이에요. 어떤 특정 시대(epoch)에 묶여 있거나 그것을 인식하지도 않죠.
class Instant is Cool does Real { }
Instant를 사용하면 그 순간에 맞춰진 DateTime 객체를 만들 수 있어요. 의사 상수(pseudo-constant) now는 현재 시간을 Instant로 반환해요.
Instant(와 Duration)에는 기본 산술 연산이 정의돼 있어요. Duration에 Instant를 더하면 다른 Instant가 되고, 두 Instant를 빼면 Duration이 돼요. 두 Instant를 더하는 것은 명시적으로 금지되고, 그 외의 연산은 모두 정의되지 않았어요.
참고: 흔히 쓰이는 시간 표준인 POSIX 시간(Unix time)은 윤초(leap second)를 다루지 않지만,
Instant객체는 윤초를 다뤄요. 그래서 변환 메서드에는 이를 처리하는 별도의 장치가 있어요.from-posix,to-posix메서드와 아래의 윤초 논의를 함께 보면 이해가 쉬워요.
본문
메서드 (Methods)
method from-posix
method from-posix($posix, Bool $prefer-leap-second = False)
POSIX 타임스탬프 $posix를 Instant로 변환해요. $prefer-leap-second가 True라면, 윤초가 있는 경우 가능한 두 초 중 첫 번째 초를 반환값으로 가져요.
say DateTime.new(Instant.from-posix(1483228800, True)); # OUTPUT: «2016-12-31T23:59:60Z»
say DateTime.new(Instant.from-posix(1483228800)); # OUTPUT: «2017-01-01T00:00:00Z»
method to-posix
method to-posix()
호출 객체(invocant)를 POSIX 타임스탬프로 변환하고, POSIX 타임스탬프와 Bool을 담은 두 요소 목록을 반환해요. from-posix의 역연산인 셈인데, 두 번째 반환값이 True가 되는 것은 이 Instant가 윤초에 있을 때 그때뿐이에요.
say DateTime.new('2017-01-01T00:00:00Z').Instant.to-posix; # OUTPUT: «(1483228800 False)»
say DateTime.new('2016-12-31T23:59:60Z').Instant.to-posix; # OUTPUT: «(1483228800 True)»
method Date
method Date(Instant:D: --> Date:D)
호출 객체를 Date로 강제 변환해요.
my $i = "/etc/passwd".IO.modified;
say $i; # OUTPUT: «Instant:1771879757.95139447»
say $i.Date; # OUTPUT: «2026-02-23»
method DateTime
method DateTime(Instant:D: --> DateTime:D)
호출 객체를 DateTime으로 강제 변환해요.
say now.DateTime; # OUTPUT: «2026-03-07T13:56:42.930951Z»
윤초 (Leap Seconds)
POSIX 시간(흔히 "Unix time"이라 불러요)은 윤초를 다루지 않아요. 다시 말해 지구의 자전 주기가 고정 상수라고 가정하죠. 그런데 실제로는 정오와 다음 정오 사이의 시간이 매일 조금씩 달라지기 때문에, 어떤 시간의 60번째 분에 가끔 61번째 초가 추가돼야 해요. 이를 윤초라고 부르고, 세계 모든 곳에서 동시에 발생하기 때문에 시간대에 따라 하루 중 서로 다른 시각에 발생해요. POSIX는 서로 다른 두 연속 초에 같은 정수 라벨을 붙이는 방식으로 이를 처리해요. 즉 윤초가 발생하면 DateTime.now.posix는 두 초 내내 변하지 않죠.
my Instant $i .= from-posix(1483228799);
my Duration $s = DateTime.new(1) - DateTime.new(0);
sub demo { say [$_, .posix] with DateTime.new($i + $^n * $s) }
demo(0); # OUTPUT: «[2016-12-31T23:59:59Z 1483228799]»
demo(1); # OUTPUT: «[2016-12-31T23:59:60Z 1483228800]»
demo(2); # OUTPUT: «[2017-01-01T00:00:00Z 1483228800]»
demo(3); # OUTPUT: «[2017-01-01T00:00:01Z 1483228801]»
원자시계도 지구의 자전을 무시하므로, 1958년 처음 보정됐을 때부터 1972년 윤초가 도입되기 전까지 UTC는 원자시계 시간보다 10초 뒤처져 있었어요. Instant의 수치값은 원자시계 시간을 따르는데, 2026년 기준으로 기록된 윤초는 27번이에요. 모두 더하면 37초가 되고, 그래서 2026년 기준으로 다음이 성립해요.
with now { say .Int - DateTime.new($_).posix } # OUTPUT: «37»
미래의 윤초 (Future Leap Seconds)
윤초에 대한 지식을 활용하는 메서드들은, 구현이 알고 있는 마지막 윤초 이후에는 더 이상 윤초가 없을 거라고 항상 가정해요. 그 마지막 윤초가 실제로 예정된 마지막 윤초가 아닐 수도 있죠. 그래서 사용하는 컴파일러 버전에 따라 다른 결과를 얻을 수 있어요. 예를 들어 2016년 12월 31일의 윤초는 7월에 발표돼 Rakudo 2016.07에 포함됐으므로, 2016.06 이하 릴리스는 그것을 알지 못해요.
$ perl6-2016.06 -e 'say Instant.from-posix: 1485726595'
Instant:1485726631
$ perl6-2016.07 -e 'say Instant.from-posix: 1485726595'
Instant:1485726632
Rakudo 컴파일러는 알지 못하는 미래의 윤초에 대해 항상 0을 반환하므로, 새 윤초가 발표되면 오래된 코드에 패치를 가할 수 있어요. 그러면 어떤 컴파일러 버전에서 실행하든 올바른 결과를 얻을 수 있어요.
$ perl6-2016.06 -e 'say ($*VM.version before v2016.07 ?? 1 !! 0) + Instant.from-posix: 1485726595'
Instant:1485726632
$ perl6-2016.07 -e 'say ($*VM.version before v2016.07 ?? 1 !! 0) + Instant.from-posix: 1485726595'
Instant:1485726632
이 예시들은 이름 변경 이전의 컴파일러를 요구해 여전히
perl6을 참조해요. 2016년 말 이후로 지구의 자전이 안정적이라 그 뒤로 새 윤초가 없었기 때문이에요.