神話のプログラム言語 Odin 日付操作とログ操作編
アイキャッチを変更しました。Odin言語の記載を行う時には、これからアイキャッチに、神のマークを入れるようにします。(これが神かどうかは微妙ですが…😭)
それでは、Odinでの日付取得やログ表示などは、どうするのかについて、今回は記載していきます。
1.時間操作
Odin言語での時間関数は、まだ完璧ではないと思います。
何故なら、タイムゾーンの導入も先月2024年11月に導入されたばかりですので、これから基本ライブラリは、もっと良くなっていくと思いますが、今現時点の時間操作方法を説明します。
1-1.タイマー操作:sleep関数
まず、sleep関数
ですが、sleepなどの時間関数を扱う場合は、inport "core:time"
をインポートして利用します。また、時間の単位については、time.Microsecondなどで単位表示して使う事が出来ます。
以下のようにtime01ディレクトリにmain.odinを作成して実行してみます。
package main
import "core:fmt"
import "core:time"
main :: proc() {
fmt.println("start:", time.now()) // 開始時間 time.now()で現日付を表示
timer := 100 * time.Microsecond // time.Microsecondでミリ秒設定 100ミリ秒を設定している
// time.Nanosecondでナノ秒設定
// time.Microsecondでマイクロ秒設定
// time.Secondで秒設定
// time.Minuteで分設定
// time.Hourで時間設定
time.sleep(timer)
fmt.println("end:", time.now()) // 終了時間 time.now()で現日付を表示
}
以下のように実行すると、time.now関数は、UTC時間で表示されます。
$ odin run .\time01\
start: 2024-12-18 13:00:37.837520100 +0000 UTC
end: 2024-12-18 13:00:37.837814100 +0000 UTC
1-2.タイマー操作:tick関数
tick関数
はラップ表示機能と増分表示機能があります。
tick_lap_time
関数がランプタイムの表示で、tick_since
関数は増分量を表示してくれます。
以下のようにtime02ディレクトリにmain.odinを作成して実行してみます。
package main
import "core:fmt"
import "core:time"
main :: proc() {
start, end: time.Tick
fmt.printfln("--- 1度だけのtick時間表示")
start = time.tick_now() // 開始時間取得
time.sleep(100 * time.Millisecond)
end = time.tick_now() // 終了時間取得
duration := time.tick_diff(start, end)
fmt.printfln("%v", duration) // 105.2744ms
fmt.printfln("--- ラップ時間表示")
start = time.tick_now()
for i in 0 ..= 10 {
time.sleep(200 * time.Millisecond)
duration = time.tick_lap_time(&start)
fmt.printfln("%v", duration) // ラップタイムが表示される 201.2114ms 202.5384ms ...
}
fmt.printfln("--- 増加時間表示")
start = time.tick_now()
for i in 0 ..= 10 {
time.sleep(200 * time.Millisecond)
duration = time.tick_since(start)
fmt.printfln("%v", duration) // 増加分でタイムが表示 201.1117ms 402.7548ms ...
}
}
実行すると以下のように表示されます。
$ odin run .\time02\
--- 1度だけのtick時間表示
102.4409ms
--- ラップ時間表示
214.8984ms
201.6516ms
203.5381ms
203.0259ms
204.2812ms
203.9555ms
209.7021ms
202.5051ms
202.9365ms
203.8408ms
201.1618ms
--- 増加時間表示
206.2357ms
410.8632ms
615.1059ms
818.4887ms
1.0206505s
1.2268074s
1.4289198s
1.6305082s
1.8312992s
2.0336494s
2.2380513s
1-3.タイマー操作:stopwatch関数
stopwatch関数
は開始時から終了時までの時間を表示します。
tick関数
のような増分やラップ時間を計測する事は出来ません。
以下のようにtime03ディレクトリにmain.odinを作成して実行してみます。
package main
import "core:fmt"
import "core:time"
main :: proc() {
sw: time.Stopwatch
// stopwatch操作
time.stopwatch_reset(&sw) // ストップウォッチのリセット
time.stopwatch_start(&sw) // ストップウォッチ開始
// 200ミリ秒待機
time.sleep(200 * time.Millisecond)
time.stopwatch_stop(&sw) // ストップウォッチ停止
// ストップウォッチの結果表示
fmt.println(sw) // Stopwatch{running = false, _start_time = Tick{_nsec = 10413855746100}, _accumulation = 201.8388ms}
}
$ odin run .\time03\
Stopwatch{running = false, _start_time = Tick{_nsec = 45411339840400}, _accumulation = 212.2903ms}
1-4.日付操作
time.now()関数
は現在の日付をUTC表記で表示します。
time.time_to_datetime
関数で、年月日などの数字に変換してくれます。
以下のようにdate01ディレクトリにmain.odinを作成して実行してみます。
package main
import "core:fmt"
import "core:time"
import "core:time/datetime"
main :: proc() {
fmt.println("現在の日付:", time.now()) // time.now()で現日付を表示するがUTC時間であるため、日本時間と9時間のズレがある
today := time.now()
// Time型を日付に変換
year, month, day := time.date( today ) // 年月日をそれぞれ求める
hour, minute, second := time.clock_from_time( today ) // 時分秒をそれぞれ求める
fmt.printfln("%v/%v/%v %v:%v:%v",
year, month, day, hour, minute, second) // これだと月が文字表記になる
// Time型を日付に変換
datetime, _ := time.time_to_datetime( today ) // 但し、日付はUTC時間で表示される
fmt.printfln("%v/%v/%v %v:%v:%v",
datetime.year, datetime.month, datetime.day, datetime.hour, datetime.minute, datetime.second)
}
実行すると、time.date関数を使った時のみ、月の表示が英字で表示されます。
$ odin run .\date01\
現在の日付: 2024-12-18 13:05:55.185431600 +0000 UTC
2024/December/18 13:5:55
2024/12/18 13:5:55
1-5.日付操作
datetime.add_days_to_date
関数で、現在の日付から加算日付を求める事が出来ます。
※あくまで日付だけです。
以下のようにdate02ディレクトリにmain.odinを作成して実行してみます。
package main
import "core:fmt"
import "core:time"
import "core:time/datetime"
main :: proc() {
dt, _ := time.time_to_datetime( time.now() )
// 日付加算
fmt.printfln("before: %04v/%02v/%02v %02v:%02v:%02v", dt.year, dt.month, dt.day, dt.time.hour, dt.time.minute, dt.time.second)
add_dt, _ := datetime.add_days_to_date(dt.date, -10) // 10日前を求める
fmt.printfln("after: %04v/%02v/%02v %02v:%02v:%02v", add_dt.year, add_dt.month, add_dt.day, dt.time.hour, dt.time.minute, dt.time.second)
}
$ odin run .\date02\
before: 2024/12/18 13:07:20
after: 2024/12/08 13:07:20
1-6.タイムゾーン関数
time.now()
関数で、現在の日付をUTC時間で表示されますが、timezone
を使う事で、各国の現在時間を算出する事が出来ます。
以下のようにdate03ディレクトリにmain.odinを作成して実行してみます。
package main
import "core:fmt"
import "core:time"
import "core:time/datetime"
import "core:time/timezone"
main :: proc() {
tz, _ := timezone.region_load("Asia/Tokyo") // 日本のタイムゾーンを求める
defer timezone.region_destroy(tz)
dt_utc, _ := time.time_to_datetime( time.now() ) // 現日付を求める UTC時間
dt, _ := timezone.datetime_to_tz(dt_utc, tz) // 現日付をタイムゾーンから求める
fmt.printfln("%04v/%02v/%02v %02v:%02v:%02v", dt.date.year, dt.date.month, dt.date.day, dt.time.hour, dt.time.minute, dt.time.second)
}
実行すると、UTC表示では無く、日本時間が正常に表示されます。
$ odin run .\date03\
2024/12/18 22:07:49
2.ログ制御
2-1.コンソールログ
以下にコンソールログ出力制御のプログラムを記載します。
log01ディレクトリに以下のファイルを記載して実行します。
contextエリアのloggerに、ログ出力情報(ログレベル、出力形式)を設定し、ログを出力します。
ログのレベルは、INFOレベルから出力させるようにしています。
出力形式は、出力ヘッダーと記載された物しか出力できません。
また、出力時間もUTC時間で表示されてしまいます。
package main
import "core:log"
// 出力ヘッダー
my_Console_Logger_Opts :: log.Options{
.Level, // INFO, WARN, ERROR などのタイトル
.Terminal_Color, // 出力カラー
.Date, // 日付
.Time, // 時間
// .Line, // 出力行
// .Procedure, // 出力した関数名
}
main :: proc() {
// コンソールログの設定
logger := log.create_console_logger(.Info, my_Console_Logger_Opts, "")
context.logger = logger // contextエリアにコンソールロガーを設定
log.debug("debug Program started") // contextエリアにロガーを設定すれば、後はどこでも出力される
log.info("info Program started")
log.warn("warn Program started")
log.error("error Program started")
log.destroy_console_logger(logger) // ロガーの削除
}
実行すると、コンソール上に、INFO、WARN、ERRORのみが表示されます。
DEBUGは表示されません。また、日付はUTC時間で表示されます。
$ odin run .\log01\
[INFO ] --- [2024-12-18 13:10:07] info Program started
[WARN ] --- [2024-12-18 13:10:07] warn Program started
[ERROR] --- [2024-12-18 13:10:07] error Program started
標準ログライブラリを変更してlocaltime時間を表示させる
ログ出力を、UTC時間からlocaltime時間に変更する方法は、二通りあり、1つは、自分で一からログを作成する方法と、2つ目は、現在のOdin標準ライブラリのログを書き換える方法です。
余り好ましくはありませんが、現在のOdin言語では、まだログ制御も完璧ではありませんので、以下のように書き換えましょう。
import "core:c/libc" // c.libcをヘッダーに追加
// do_time_header関数を強制的に変更する
do_time_header :: proc(opts: Options, buf: ^strings.Builder, t: time.Time) {
when time.IS_SUPPORTED {
if Full_Timestamp_Opts & opts != nil {
fmt.sbprint(buf, "[")
// clibcからlocaltimeを抽出
tc := libc.time(nil)
tm := libc.localtime(&tc)
y, m, d := tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday
h, min, s := tm.tm_hour, tm.tm_min, tm.tm_sec
// 下記2行をコメントアウトにする
// y, m, d := time.date(t)
// h, min, s := time.clock(t)
上記の/odin/core/log/file_console_logger.odinを改変する事で、ログがlocaltimeで表示されます。
$ odin run .\log01\
[INFO ] --- [2024-12-18 22:12:34] info Program started
[WARN ] --- [2024-12-18 22:12:34] warn Program started
[ERROR] --- [2024-12-18 22:12:34] error Program started
2-2.ファイルログ
ファイルログは、ファイルハンドラーを取得後に、ファイルログを作成します。
また、ファイルログの出力形式も、コンソールと同じで、出力ヘッダーに記載された物しか出力できません。それと、ログ出力も、ログローテーションの機能はありません、指定したファイルにのみログファイルを作るだけです。
package main
import "core:os"
import "core:log"
import "core:fmt"
my_File_Logger_Opts :: log.Options{
.Level, // INFO, WARN, ERROR などのタイトル
// .Terminal_Color, // 出力カラー
.Date, // 日付
.Time, // 時間
// .Line, // 出力行
// .Procedure, // 出力した関数名
}
main :: proc() {
log_file, err := os.open("log22.txt", os.O_APPEND)
defer os.close(log_file)
logger : log.Logger = log.create_file_logger(log_file, .Info, my_File_Logger_Opts)
defer log.destroy_file_logger(logger)
context.logger = logger // contextエリアにファイルロガーを設定
log.debug("debug Program started")
log.info("info Program started")
log.warn("warn Program started")
log.error("error Program started")
fmt.println("end")
}
2-3.ミックスログ
コンソールとファイルログを一緒に扱いたい場合は、以下のように行います。
context.loggerが一つしかないため、ミックスログを生成し、その中にコンソールとファイルのロガーを設定する方法です。
この方法も、余りいい方法とは思えないですが、現時点のOdinでのログ出力は下記の通り行います。
package main
import "core:os"
import "core:log"
import "core:fmt"
my_Console_Logger_Opts :: log.Options{
.Level, // INFO, WARN, ERROR などのタイトル
.Terminal_Color, // 出力カラー
.Date, // 日付
.Time, // 時間
// .Line, // 出力行
// .Procedure, // 出力した関数名
}
my_File_Logger_Opts :: log.Options{
.Level, // INFO, WARN, ERROR などのタイトル
// .Terminal_Color, // 出力カラー
.Date, // 日付
.Time, // 時間
// .Line, // 出力行
// .Procedure, // 出力した関数名
}
main :: proc() {
log_file, err := os.open("log05.txt", os.O_CREATE | os.O_WRONLY | os.O_APPEND)
fmt.println("errNo:", err)
defer os.close(log_file)
logger_file : log.Logger = log.create_file_logger(log_file, .Info, my_Console_Logger_Opts)
defer log.destroy_file_logger(logger_file)
logger_console := log.create_console_logger(.Info, my_File_Logger_Opts, "")
defer log.destroy_console_logger(logger_console)
// 複数のロガーを結合
logger_multi := log.create_multi_logger(logger_file, logger_console)
defer log.destroy_multi_logger(logger_multi)
context.logger = logger_multi // contextエリアにコンソールとファイルのロガーを設定
log.debug("debug Program started")
log.info("info Program started")
log.warn("warn Program started")
log.error("error Program started")
fmt.println("end")
}
おわりに
ログ制御については、現時点UTC時間しか表示されないし、ファイルログはログローテーション出来ないし、ミックスログの記載が面倒だったりと、まだまだ、修正が必要かと思います。
次回は、Dear ImGuiの導入とプログラム方法について、記載していきたいと思います。
Discussion