fortranでカレンダ処理

# カレンダ処理もFortranで
この記事は、計算時間の測定方法ではなく、カレンダ処理をFortranで簡単に実行するためのものです。カレンダ処理関数を自作する場合、うるう年や2038年問題などを気にする必要があるので割と頭が痛くなります。そこでライブラリを使うわけですが、C/C++/Pythonと同じように、Fortranにもカレンダ処理を扱うことのできるライブラリがあります。[datetime-fortran](https://github.com/wavebitscientific/datetime-fortran?tab=readme-ov-file#timedelta)です。

|言語|ライブラリ|
| ---|---|
| C|time.h |
| C++ | chrono|
|python| datetime|
|fortran| datetime-fortran|

# datetime-fortranの利用例
このライブラリはpythonの[`datetime`ライブラリ](https://docs.python.org/ja/3/library/datetime.html)によく似た利用方法となっています。(よく似たというより、ほぼそのまま。)
まず、サンプルのメインプログラムと実行結果を示します。

```fortran:main.f90
program main
  use mydatetime
  implicit none
  call separator(say_hello)
  call separator(use_epoch)
  call separator(use_timedelta)
end program main
```
- say_hello: 実行時の日時を取得しコンソールに表示。その後文字列から日時データを読み取る事例。
- use_epoch: エポック秒、タイムゾーン、ローカルタイム、世界標準時を扱う事例。C言語の`tiem.h`ライブラリに寄せた書き方をしてみた。
- use_timedelta: 日時の差分計算の事例。


```bash:実行結果
 !-----
 now             :2024/11/29 23:32:37 (Friday)... ==  1732890757
 read from text  :2024/12/25 20:00:00 (Wednesday)
 
 !-----
 timezone@mymachine:   9.0000000000000000     
 localtime :1970/01/01 09:00:00 (Thursday)
 gmtime    :1970/01/01 00:00:00 (Thursday)
 
 !-----
 2024/12/25 00:00:00 (Wednesday)
 2024/12/25 00:00:00 (Wednesday)
 one_day < delta : T
```
メインプログラムから呼び出されるサブルーチンの内部は次のコードです。
```fortran:mydatetime.f90
module mydatetime
  use iso_fortran_env
  use datetime_module !datetime-fortraライブラリ
  implicit none
  character(22),parameter :: strfmt =  "%Y/%m/%d %H:%M:%S (%A)" ! 表示形式を指定する文字列
  private

  public :: separator, say_hello, use_epoch, use_timedelta
contains
  subroutine say_hello
    use stdlib_string_type ! stdlibライブラリのstring_type()を使う
    type(datetime) :: day   ! 日時情報を扱う型
    integer(int64) :: epoch ! エポック秒
    day = day%now()  !現在日時を取得
    epoch = day%secondsSinceEpoch() !取得した現在日時をエポック秒に変換
    print *, "now             :",day%strftime(strfmt), "... == ",string_type(epoch)
    day = strptime("2024/12/25 20:00:00",strfmt) ! 引数の文字列からdatetime型を生成
    print *, "read from text  :",day%strftime(strfmt)
  end subroutine say_hello

  subroutine use_epoch
    type(datetime) :: day
    real(real64) :: tz !タイムゾーン（東京は+9hour）
    integer(int64) :: epoch
    tz = machinetimezone()
    epoch = 0
    print*, "timezone@mymachine:",tz
    day = localtime(epoch, tz) !実行マシンの日時（標準時+9h)
    print *, "localtime :",day%strftime(strfmt)
    day = gmtime(epoch) !世界標準時
    print *, "gmtime    :",day%strftime(strfmt)
  end subroutine

  subroutine use_timedelta
    type(datetime) :: day_a, day_b ,day_ans
    type(timedelta) :: one_day, delta
    integer :: i
    one_day = timedelta(days=1) ! "1日分"の時間
    day_a = datetime(2024,12,1)
    day_b = datetime(2024,12,25)
    delta = day_b - day_a !2024/12/1 ~ 2024/12/25の時間差 

    day_ans = day_a
    do i = 1,24 !1日ずつ足し合わせ（カレンダを進めながら、勤務時間をチェックしたり。。とか）
      day_ans = day_ans + one_day
    end do
    print*, day_ans%strftime(strfmt)

    day_ans = day_a + delta !一気にdelta分の時間差を足すと..
    print*, day_ans%strftime(strfmt)

    print*, "one_day < delta :", one_day < delta !時間間隔の大小も比較できる
  end subroutine

  subroutine separator(fun)
    ! コンソール表示用ユーティリティ
    external fun
    print *, "!-----"
    call fun()
    print *, ""
  end subroutine
end module mydatetime
```
力尽きたので詳しくは解説しませんが、上記のコードを見てください。ほぼpythonのdatetimeライブラリの使用感と同じです。

# 計算速度について
pythonのdatetimeライブラリと比較した結果、断然`datetime-fortran`が早いです。
また、datetime-fortranを使うにしても、より速度を上げたいのであれば、ボトルネック部分はなるべく`type(timedelta)`を使わずにエポック秒の演算をする方が高速になります。エポック秒は整数演算なので高速になるのだと勝手に思っています。
このへんの事情はC言語も同様で、[`tm構造体`](https://ja.wikipedia.org/wiki/Time.h)をループ内で多用するとあまり速度が出ませんでした。

# 並列計算について
Cの`time.h`は、スレッドセーフでない関数があったりするので要注意です。
`time.h`にはスレッドセーフ版の関数も見つかりますが、OSによって関数名が違ったりしてちょっと使い辛く感じました。（スレッド並列版はコンパイラ拡張的な扱い？）

datetime-fortranは基本的に並列計算で利用できます。
Cへのインターフェイスとして書かれている関数は、将来的にCの影響を受ける可能性はありますが、Fortranコードを眺めると、基本的に並列計算可能なことを意図していることが明白です。

# fpmを使うとビルドが簡単
`pip install fpm`としたあと、
`fpm new mydatetime --lib --app`などとすれば、mydatetimeというフォルダにテンプレが出来上がります。`fpm.toml`の下の方に、

```toml
[dependencies]
stdlib = "*"
datetime.git ="https://github.com/wavebitscientific/datetime-fortran"
```
と書けば、
```bash
fpm run
```
でビルド&実行してくれます。（色々端折ってますので、フォルダ名やファイル名など、各自の環境に合わせてください。）

# おわり
pythonでカレンダ処理を高速化する必要に迫られて、色々と調べた結果のご紹介でした。