날짜와 시간

날짜와 시간 (temporal)

Raku는 시간 정보를 다루는 여러 클래스를 제공해요. Date, DateTime, Instant, 그리고 Duration이 그것이죠. 처음 두 클래스는 dateish라서 Dateish 역할을 섞고 있어요. 그 역할은 날짜를 다루는 클래스가 가져야 할 모든 메서드와 속성을 정의해요. 여기에 X::Temporal을 뿌리로 둔 예외 클래스 계층도 함께 있어요.

출처: Raku Docs - Date and time functions

이 클래스들을 조금 길게 뻗은 예시로 살펴볼게요. 특정 확장자(기본 .raku)를 가진 파일을 디렉터리(기본 .)에서 모두 처리하고, 나이에 따라 정렬한 뒤, 달별로 생성된 파일 수와 특정 기간(월 범위로 표현)에 수정된 파일 수를 계산하는 프로그램이에요.

sub MAIN( $path = ".", $extension = "raku" ) {
    my DateTime $right = DateTime.now;
    my %metadata;
    my %files-month;
    my %files-period;
    for dir($path).grep( / \.$extension $/ ) -> $file {
        CATCH {
            when X::Temporal { say "Date-related problem", .payload }
            when X::IO { say "File-related problem", .payload }
            default { .payload.say }
        }
        my Instant $modified = $file.modified;
        my Instant $accessed = $file.accessed;
        my Duration $duration = $accessed - $modified;
        my $age = $right - DateTime($accessed);
        my $time-of-day = $file.changed.DateTime.hh-mm-ss but Dateish;
        my $file-changed-date =  $file.changed.Date;
        %metadata{$file} = %( modified => $modified,
                              accessed => $accessed,
                              age => $age,
                              difference => $duration,
                              changed-tod => $time-of-day,
                              changed-date => $file-changed-date);
        %files-month{$file-changed-date.month}++;
        given $file-changed-date {
            when Date.new("2018-01-01")..^Date.new("2018-04-01") { %files-period<pre-grant>++}
            when Date.new("2018-04-01")..Date.new("2018-05-31") { %files-period<grant>++}
            default { %files-period<post-grant>++};
        }
    }

    %metadata.sort( { $^a.value<age> <=> $^b.value<age> } ).map: {
        say $^x.key, ", ",
        $^x.value<accessed modified age difference changed-tod changed-date>.join(", ");
    };
    %files-month.keys.sort.map: {
        say "Month $^x → %files-month{$^x}"
    };

    %files-period.keys.map: {
        say "Period $^x → %files-period{$^x}"
    };
}

두 번째 줄에서 DateTimenow가 돌려준 현재 날짜와 시간을 담는 데 쓰여요.

7~11번째 줄에는 CATCH phaser가 선언되어 있어요. 이 녀석의 주된 임무는 DateTime 관련 예외와 그 밖의 예외 유형을 구분하는 거예요. 이런 종류의 예외는 잘못된 형식이나 시간대 충돌에서 생길 수 있죠. 파일 속성이 손상되지 않는 한 둘 다 사실상 불가능하지만, 어쨌든 잡아서 다른 예외와 분리해 두는 게 좋아요.

12~13번째 줄에서는 파일이 접근되고 수정된 시점을 나타내는 데 Instant를 써요. Instant는 원자적 초(atomic seconds) 단위로 측정되는, 시간 사건의 아주 저수준 표현이에요. 반면 14번째 줄에 선언된 Duration은 두 Instant 사이에 흐른 시간을 나타내요. 여기서는 나이(age)를 표현하는 데 쓰죠.

어떤 변수들은 dateish 특성을 붙여 다루고 싶을 수 있어요. $time-of-day에는 파일이 변경된 시각(시간대)이 담겨요. changed는 Instant를 돌려주지만, 이를 Date(즉 Dateish)로 변환한 뒤 그렇게 해서 시각을 추출해요. (Instant 자체는 Dateish가 아니에요.) 그래서 $time-of-day«Str+{Dateish}␤» 타입을 가지게 돼요.

파일이 언제 변경됐는지로 기간을 알아내는 데는 이 변수의 날짜를 써요.

Date.new("2018-01-01")..^Date.new("2018-04-01")

이 코드는 날짜 Range를 만들고 $file-changed-date가 여기에 스마트매치돼요. 날짜는 이렇게 쓸 수 있어요. 위 예는 마지막 원소를 제외하는(..^) Range를 만드는 경우죠.

바로 이 변수로 파일이 수정된 연도의 달(month)도 계산해요. Date는 당연히 Dateish라서, 그 속성을 꺼내는 month 메서드를 가지고 있어요.

Duration 객체끼리는 비교가 가능해요. 아래 코드는 그것을 이용해 파일을 나이 순으로 정렬해요.

%metadata.sort({
    $^a.value<age> <=> $^b.value<age>
});