2020年8月10日月曜日

タイムスタンプを得る(Rust)

Rustをインストールしたので、練習してみる。 プログラムの出力結果に、実行日時を付加するときにタイムスタンプがほしいことがある。 ここでのタイムスタンプは、"2020/08/10 09:00:35"といった形に現在日時を示す文字列である。 それを得る関数timestamp_now()を書いてみようと思った。

Pythonでは、

import datetime
dt = datetime.datetime.now()
timestamp_now = "%04d/%02d/%02d %02d:%02d:%02d" % (
    dt.year, dt.month, dt.day, 
    dt.hour, dt.minute, dt.second)
と簡単に得られる。

Rustでは、chronoという外部クレートを用いることで簡単に得られるようである。 ここでは、chronoを使わずに、std::timeを用いてタイムスタンプを得る。 std::time::SystemTime::now()を用いて、現在時刻のUNIX_EPOCHタイムからの秒数を得て、 Fairfieldの公式を使って日数を勘定すればタイムスタンプが得られるだろうということである。

現在時刻とUNIX_EPOCHタイムとの差を得る関数は以下の通りでよいだろう。

fn now_from_unix_epoch() -> f64 {
    let now = std::time::SystemTime::now();
    now.duration_since(std::time::SystemTime::UNIX_EPOCH).unwrap().as_secs_f64()
}
次に、その秒数が何日に相当するかを評価する関数である。 Rustは、Pythonのように、気軽に複数の値を返せるようであるので、 のこりの秒数から時、分、秒を返すことにしてある。 tzは、タイムゾーンによる時間のズレを補正するために入れてある。 日本時間はUTCより9時間進んでいるので、9を入れて計算すると日本時間となる。
fn counts_day_from_sec(seconds:f64, tz:f64) -> (u32, u32, u32, f64) {
    const DAY_SEC:f64 = 60.0*60.0*24.0;
    const HOUR_SEC:f64 = 60.0*60.0;
    const MIN_SEC: f64 = 60.0;
    let days = ((seconds + tz * HOUR_SEC)/DAY_SEC).floor();
    let remain_sec = (seconds + tz * HOUR_SEC) - (days*DAY_SEC) as f64;
    let hour = (remain_sec/HOUR_SEC).floor();
    let remain_sec = remain_sec - (hour*HOUR_SEC) as f64;
    let min = (remain_sec/MIN_SEC).floor();
    let sec = remain_sec - (min*MIN_SEC) as f64;
    (days as u32, hour as u32, min as u32, sec) 
}    
得られた日数から日付を得るために、日付から日数を得るFairfiledの公式を使った関数を作る。 1月、2月の時は、年を1年戻して、それぞれ13月、14月として計算する必要がある。
fn daycounts_fairfield(year:u32, month:u32, day:u32) -> u32 {
    let mut y = year;
    let mut m = month;
    if month < 3 {
        y = year -1;
        m = month +12;
    }
    let day_before_the_year = 365 * (y-1);
    let day_of_leep_year = y/4-y/100+y/400;
    let day_in_the_year = 31 +28 +306*(m+1)/10-122 +day;
    (day_before_the_year + day_of_leep_year + day_in_the_year) as u32
}    
次に、この関数を用いて日付を探せばよい。年、月、日を返す。
fn days_to_date(days:u32) -> (u32, u32, u32) {
    let unix_epoch = daycounts_fairfield(1970, 1, 1);
    let mut year = 1970;
    while daycounts_fairfield(year, 1, 1) - unix_epoch < days {
        year = year + 1;
    }
    year = year -1;
    let mut month = 1;
    while daycounts_fairfield(year, month, 1) - unix_epoch < days {
        month = month + 1;
    }
    month = month -1;
    let diff_days = daycounts_fairfield(year, month, 1)-unix_epoch;
    
    (year, month, days-diff_days+1)
}
以上の関数を用いて、タイムスタンプを生成する関数は、こうなる。
fn timestamp_now() -> String {
    let unixtime = now_from_unix_epoch();

    let (days, hour, minute, second) = counts_day_from_sec(unixtime, 9.0);
    let (year, month, day) = days_to_date(days);
    format!("{:04}/{:02}/{:02} {:02}:{:02}:{:02}", 
            year, month, day, hour, minute, second.floor() as u32)
}      
最後は、メイン関数。
fn main() {
    let s = timestamp_now();
    println!("{}", s);
}
以上をtimestamp.rsというファイルに記述したら、
rustc timestamp.rs
でコンパイルでき、timestamp.exeが生成される。

Rustは、f64とu32の除算は定義されていないなど、 どの型で計算するかをしっかり考えておかないといけない。 ただ、コンパイラのエラーメッセージが非常に親切で、メッセージを読むのが楽しい。 また、定数は大文字にしなさいや、変数名をスネークケースにしなさいなど記述についても指示が出た。

0 件のコメント: