1.20.2012

Perl: Recognizing time from text with a specified format

Perl: 時刻が表記された文字列の認識

任意の正規表現パターンと、そのパターンに沿って記述された時刻表記を与えられたとき、
その文字列を読み取り午前0:00からの経過秒数を取得したい。

ただし Perl 5.6 でも動作するものとしたいため、名前付きバッファ(Named Captures)は使えない。
車輪の再発明のような気もするが実装。

実行例

"%H:%M:%S", "01:02:03" ⇒ 1時2分3秒 = 1 * 3600 + 2 * 60 + 3 = 3723
"^\d{8}%H%M%S", "20120119010203456789" ⇒ 1時2分3秒 = 1 * 3600 + 2 * 60 + 3 = 3723
"%H : %M (:? : %S )?, "01:02" ⇒ 1時2分 = 1 * 3600 + 2 * 60 = 3720
"%S [ ] %H – %M", "03 01-02" ⇒ 1時2分3秒 = 1 * 3600 + 2 * 60 + 3 = 3723

コード

i番目に出現したフォーマット識別子に対して、単位あたりの秒数 Wiを設定してゆく。
読み込んだ数値をviとすれば、求める値はΣwi × vi である。

#!/usr/bin/env perl
use strict;
use warnings;

#-----------------------------------------------------------------------------
# Time Reader class
#-----------------------------------------------------------------------------
package TimeReader;

# Define format specifiers.
# hash to a list of regular expression pattern and how many seconds in a spec.
my %_SPEC_FOR = (
    '%H' => [ '\\d{2}', 60 * 60 ],
    '%M' => [ '\\d{2}',      60 ],
    '%S' => [ '\\d{2}',       1 ],
);

##############################################################################
# Constructor: new()
#
sub new {
    my ($class, $format) = @_;  # format pattern
    my @weights;

    SEARCH_SPEC:
    for my $i (0 .. length($format) - 2) {
        my $str = substr($format, $i, 2);
        next SEARCH_SPEC if !exists $_SPEC_FOR{$str};
        push @weights, $_SPEC_FOR{$str}->[1];
    }

    while (my ($key, $value) = each %_SPEC_FOR) {
        $format =~ s/ $key / ( $value->[0] ) /xms;
    }

    # Create instance.
    my $new_object = bless {}, $class;
    $new_object->{reg_exp} = $format;
    $new_object->{weights} = \@weights;
    return $new_object;
}

##############################################################################
# Method read()
#
# Read text, then returns elapsed seconds from 0:00 am.
#
sub read {
    my ($self, $text) = @_;  # text includes time representation

    my @ret = $text =~ m/ $self->{reg_exp} /xms or return;

    my $seconds = 0;
    for my $i (0 .. $#{ $self->{weights} }) {
        if ($ret[$i]) {
            $seconds += $ret[$i] * $self->{weights}[$i];
        }
    }
    return $seconds;
}

package main;

print TimeReader->new('%H:%M:%S')->read('01:02:03'), "\n";
print TimeReader->new('^\d{8}%H%M%S')->read('20120119010203456789'), "\n";
print TimeReader->new('%H : %M (:? : %S )?')->read('01:02'), "\n";
print TimeReader->new('%S [ ] %H - %M')->read('03 01-02'), "\n";

# unexpected result?
print TimeReader->new('%H:%H')->read('01:02'), "\n";
print TimeReader->new('%H*')->read('0102'), "\n";

制約事項

・捕捉すべき同種のフォーマット識別子が複数回マッチしないようにすること

 (a) パターンの中に捕捉すべき同種のフォーマット識別子が複数回使われた場合、
  捕捉文字列に置き換わるのは最初の一回のみである。

 "%H:%H", "01:02" ⇒ ( \d{2} ) :%H として捕捉 ⇒ マッチしないため undef を返す

 (b) 補足すべきフォーマット識別子に対して2回以上の量指定子を利用した場合、
  最後にマッチした部分の値が評価される。

 "%H*", "0102" ⇒  ( \d{2} )* として捕捉 ⇒ 最後にマッチするのは「02」 ⇒ 2時として評価 ⇒ 2 * 3600 = 7200

・フォーマット識別子の定義は2文字であること(通常は '%' と他1文字とする)

0 件のコメント:

コメントを投稿