🦀

Rust製タイムゾーン変換ツール「Timezone Translator」をリリースしました 🎉

2024/07/08に公開

仕事や趣味でGoogle Cloudを使っている時に,スケジュールや日付範囲をTimezoneをUTCで入力する機会がありました.時間の指定のミスは,間違っていたとしても動いてしまうが故に検知しにくく,できるだけ避けたいです.そこで,CLIでタイムゾーンを変換するツールを作成しました.

GitHub

モチベーションにつながるので,⭐️ 押していってくださいー!Pull Requestも大歓迎です!

https://github.com/shunsock/timezone_translator

インストール

Install (Binary)

sudo curl -L -o /usr/local/bin/tzt https://github.com/shunsock/timezone_translator/releases/download/v0.2.0/timezone_translator && sudo chmod +x /usr/local/bin/tzt

Install (Source)

ソースからビルドする場合、以下のコマンドを使用します。この場合、 CargoMake が必要です。

git clone https://github.com/shunsock/timezone_translator.git
cd timezone_translator
make install

使い方

Timezone Translatorのオプションは非常にシンプルです.

$ tzt -T "2024-01-01 12:00:00" -f "America/New_York" -t "UTC"
2024-01-01 17:00:00 UTC

詳細は以下になります.

$tzt --help
Converts time between time zones

Usage: tzt [OPTIONS] --time <TIME>

Options:
  -T, --time <TIME>
          Time in the format YYYY-MM-DD HH:MM:SS (you can omit HH:MM:SS) or YYYY-MM-DDTHH:MM:SS
  -f, --from <FROM_TIMEZONE>
          The original timezone (e.g. America/New_York) @see https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html [default: Your_Local_Timezone]
  -t, --to <TO_TIMEZONE>
          The target timezone (e.g. Asia/Tokyo) @see https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html# [default: Your_Local_Timezone]
  -a, --ambiguous-time-strategy <STRATEGY>
          Strategy to use for ambiguous times (earliest, latest) [default: earliest]
  -h, --help
          Print help
  -V, --version
          Print version

使用技術

今回は,管理を簡単にするために1つのバイナリを出力する言語で開発することにしました.コンパイルをしてバイナリを出力するタイプの言語だと,C#とRustを使っているのですが,今回はClapを触ってみたかったのでRustを選択しました.

Rustであれば鉄板の構成です.

  • 言語:
    • Rust : 高パフォーマンスで安全なシステムプログラミング言語.
  • クレート (ライブラリ):
    • clap : コマンドライン引数を簡単に解析するためのクレート.複雑なコマンドラインツールを迅速に開発できます.
    • chrono : 日付と時刻を扱うためのクレート.Rustにおける標準的な日時操作を提供し、タイムゾーンを考慮した日時操作もサポートします.
    • chrono-tz : chrono クレートと連携して,タイムゾーン情報を扱うためのクレート.
    • regex : 正規表現を使った文字列操作を可能にするクレート.
    • thiserror : カスタムエラー型を簡単に定義するためのマクロベースのクレート.エラーハンドリングをシンプルかつ強力にします.Webサイトや書籍などでよく紹介されているCrateでRustaceanなら知っている方も多いと思います.
  • CI:
    • GitHub Actions : 継続的インテグレーションと継続的デリバリー(CI/CD)を実現するためのGitHubの機能.クレートの更新やCVEの監視は,dependabotを使っています.

Architecture

同じようなソフトウェアを作りたい方向けの章です.

Cargo ModulesでCrateの構成図しています.Cargo Modulesの詳細は下記のリンクをご参照下さい.

https://github.com/regexident/cargo-modules

$ cargo modules structure
crate timezone_translator
├── mod command: pub(crate)
│   ├── mod arguments: pub(self)
│   │   ├── mod ambiguous_time_strategy: pub(crate)
│   │   │   └── fn ambiguous_time_strategy: pub(crate)
│   │   ├── mod from: pub(crate)
│   │   │   └── fn from: pub(crate)
│   │   ├── mod time: pub(crate)
│   │   │   └── fn time: pub(crate)
│   │   └── mod to: pub(crate)
│   │       └── fn to: pub(crate)
│   ├── mod command_definition: pub(self)
│   │   └── fn command_provider: pub(crate)
│   ├── mod helper: pub(self)
│   │   └── mod local_timezone_string_provider: pub(super)
│   │       └── fn provide_local_timezone_string: pub(crate)
│   ├── mod receiver: pub(crate)
│   │   └── fn receive_user_input: pub(crate)
│   └── mod validated_options: pub(crate)
│       ├── mod ambiguous_time_strategy: pub(crate)
│       │   └── enum AmbiguousTimeStrategy: pub(crate)
│       └── mod validated_user_inputs: pub(crate)
│           └── struct ValidatedCommandOptions: pub(crate)
│               ├── fn ambiguous_time_strategy: pub(crate)
│               ├── fn from_tz: pub(crate)
│               ├── fn new: pub(crate)
│               ├── fn time: pub(crate)
│               └── fn to_tz: pub(crate)
├── fn main: pub(crate)
├── mod translator: pub(crate)
│   ├── mod translation_error: pub(crate)
│   │   └── enum TranslationError: pub(crate)
│   └── mod translator: pub(crate)
│       ├── struct TimezoneTranslator: pub(crate)
│       │   ├── fn convert: pub(crate)
│       │   └── fn new: pub(crate)
│       └── fn select_time_with_ambiguous_time_strategy: pub(self)
└── mod validator: pub(crate)
    ├── mod ambiguous_time_strategy_validator: pub(crate)
    │   └── fn validate_string_for_ambiguous_time_strategy: pub(super)
    ├── mod command_options_validator: pub(crate)
    │   └── fn validate_command_options: pub(crate)
    ├── mod native_datetime_validator: pub(self)
    │   └── fn validate_string_for_native_datetime: pub(super)
    ├── mod regex_matcher: pub(self)
    │   ├── mod ymd_hms_matcher: pub(super)
    │   │   └── fn ymd_hms_matcher: pub(crate)
    │   ├── mod ymd_matcher: pub(super)
    │   │   └── fn ymd_matcher: pub(crate)
    │   └── mod ymd_t_hms_matcher: pub(super)
    │       └── fn ymd_t_hms_matcher: pub(crate)
    ├── mod timezone_validator: pub(self)
    │   └── fn validate_string_for_timezone: pub(super)
    └── mod validation_error: pub(self)
        └── enum ValidationError: pub(crate)

Timezone Translatorは大きく分けて三つの部分に分けられます.

  • Command: CLIの設定や型などのコマンドそのものの知識を記載しています.
  • Validator: ユーザーの入力したテキスト情報のフォーマットが正しいか判断します.正しい場合には型を付与します.
  • Translator: timezoneの変換を行う.

上記の三つのComponentをmain関数が動かしています.

fn main() {
    let user_input_options: ArgMatches = receive_user_input();

    let validated_options: ValidatedCommandOptions = match validate_command_options(&user_input_options) {
        Ok(v) => v,
        Err(e) => {
            eprintln!("{}", e);
            exit(1);
        }
    };

    let date_time_mapped: Result<DateTime<Tz>, TranslationError> = TimezoneTranslator::new(
        validated_options.time(),
        validated_options.from_tz(),
        validated_options.to_tz(),
        validated_options.ambiguous_time_strategy(),
    )
    .convert();

    match date_time_mapped {
        Ok(mapped) => println!("{}", mapped),
        Err(e) => eprintln!("{}", e),
    }
}

作ってみた感想など

Clapが良い

よく言われていることなのですが,Clapが優秀でした.素早く動くものを作れて,ソースコードが大きくなってきたら,設定する内容ごとに分割するといったことができ,非常に体験が良かったです.

RustRoverが良い

私はJetBrainsのAll in Packを購入しているので,どの言語でもリファクタリングは楽にできる環境ではあるのですが,使っている言語の中でもトップクラスにリファクタリングしやすいと感じました.名前を変えたり,移動させた時にコンパイルやテストが失敗するということが無く,非常に快適です.RustRoverは個人かつ非商用であれば,Freeなので,Rust書いている方は是非触ってみて欲しいです.

RustRoverでの開発画面

https://blog.jetbrains.com/rust/2024/05/21/rustrover-is-released-and-includes-a-free-non-commercial-option/

Redditで公開すると良い

ざっくりと実装ができた時点でRedditで公開したのですが,すぐに意見やバグ報告をもらうことができて,大変助かりました.フィードバックが欲しい方におすすめです.

https://www.reddit.com/r/rust/comments/1dr9x58/tzt_timezone_translator/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button

Discussion