はじめに
Universal Date Parserは、あらゆる形式の日付を自動的に解析し、標準化された出力に変換する高性能なRustライブラリです。パフォーマンス、信頼性、使いやすさを重視して設計されており、自動フォーマット検出、タイムゾーン対応、多言語バインディングを提供します。
ライブラリの特徴
主要な機能
- 自動フォーマット検出: 複数の日付フォーマットを自動で認識
- 高性能: 1回の解析あたり300-600ナノ秒の超高速処理
- タイムゾーン対応: 柔軟なタイムゾーン処理オプション
- 多言語対応: C FFI、WebAssembly、Python対応
- ゼロコピー: 可能な限りメモリコピーを避けた効率的な実装
- 曖昧性解決: 地域固有のフォーマット(MM/DD vs DD/MM)の賢い判定
サポートする日付フォーマット
ISO 8601標準
// 完全なISO 8601サポート
"2023-12-25T10:30:00Z" // UTC日時
"2023-12-25T10:30:00+09:00" // タイムゾーン付き
"2023-12-25" // 日付のみ
"20231225T103000Z" // コンパクト形式
地域別フォーマット
// アメリカ式(月/日/年)
"12/25/2023" // MM/DD/YYYY
// ヨーロッパ式(日/月/年)
"25/12/2023" // DD/MM/YYYY
// 日本式
"2023年12月25日"
"2023/12/25"
Unixタイムスタンプ
// 秒精度
"1703520645" // エポックからの秒数
// ミリ秒精度
"1703520645000" // エポックからのミリ秒数
アーキテクチャ概要
コアコンポーネント
ParsedDate構造体
解析成功後の日付データを表現する基本構造体:
#[derive(Debug, Clone, PartialEq)]
pub struct ParsedDate {
pub year: i32,
pub month: u32,
pub day: u32,
pub hour: Option<u32>,
pub minute: Option<u32>,
pub second: Option<u32>,
pub timezone_offset: Option<i32>,
pub detected_format: String,
}
UniversalDateParser
設定可能なオプションを持つメイン解析エンジン:
pub struct UniversalDateParser {
timezone_mode: TimezoneMode,
ambiguity_resolver: AmbiguityResolver,
}
フォーマット検出エンジン
自動パターンマッチングによる賢いフォーマット認識:
pub fn detect_format(&self, input: &str) -> Option<&'static str> {
// ISO 8601フォーマット(最優先)
if ISO_DATETIME_REGEX.is_match(input) {
return Some("ISO 8601 DateTime");
}
// Unixタイムスタンプ
if UNIX_TIMESTAMP_REGEX.is_match(input) {
return if input.len() > 10 {
Some("Unix Timestamp (ms)")
} else {
Some("Unix Timestamp")
};
}
// 地域フォーマットと曖昧性解決
// ... 高度なパターンマッチング
}
設計原則
パフォーマンス最優先
- 可能な限りゼロコピー解析
-
lazy_static
を使用した効率的な正規表現コンパイル - 早期リターンによる効率的なパターンマッチング
- ベンチマーク結果:1回の解析あたり300-600ns
インテリジェント検出
解析エンジンは以下の優先順位でフォーマットを検出:
- ISO 8601標準(最高優先度)
- Unixタイムスタンプ
- 明確な地域フォーマット
- 曖昧なフォーマットの文脈解決
タイムゾーン対応
柔軟なタイムゾーン処理オプション:
- AssumeUtc: すべての日付をUTCとして処理(最高速)
- AssumeLocal: システムタイムゾーンを使用
- PreserveOffset: 元のタイムゾーン情報を保持
- ConvertToUtc: すべての日付をUTCに変換
インストールと基本的な使用方法
Cargoプロジェクトへの追加
[dependencies]
universal-date-parser = "0.1.0"
基本的な使用例
use universal_date_parser::{UniversalDateParser, TimezoneMode};
fn main() {
let parser = UniversalDateParser::new(TimezoneMode::AssumeUtc);
// 様々なフォーマットの解析
let inputs = vec![
"2023-12-25T10:30:00Z",
"12/25/2023",
"25/12/2023",
"1703520645",
"2023年12月25日",
];
for input in inputs {
match parser.parse(input) {
Ok(parsed) => {
println!("入力: {} -> 解析結果: {:?}", input, parsed);
println!("検出されたフォーマット: {}", parsed.detected_format);
},
Err(e) => println!("解析エラー: {}", e),
}
}
}
高度な設定例
use universal_date_parser::{
UniversalDateParser,
TimezoneMode,
AmbiguityResolver
};
fn main() {
// カスタム設定でパーサーを作成
let parser = UniversalDateParser::builder()
.timezone_mode(TimezoneMode::PreserveOffset)
.ambiguity_resolver(AmbiguityResolver::PreferEuropean)
.build();
// 曖昧な日付の解析
let ambiguous_date = "01/02/2023";
match parser.parse(ambiguous_date) {
Ok(parsed) => {
// PreferEuropeanの設定により、01/02/2023は2月1日として解釈
println!("解析結果: {}-{:02}-{:02}",
parsed.year, parsed.month, parsed.day);
},
Err(e) => println!("エラー: {}", e),
}
}
パフォーマンス特性
ベンチマーク結果
Criterionを使用した包括的なベンチマークに基づく結果:
フォーマットタイプ | 平均時間 | スループット |
---|---|---|
ISO 8601 DateTime | 388ns | 2.6M ops/sec |
ISO 8601 Date | 344ns | 2.9M ops/sec |
アメリカ式フォーマット | 392ns | 2.6M ops/sec |
ヨーロッパ式フォーマット | 618ns | 1.6M ops/sec |
Unixタイムスタンプ | 483ns | 2.1M ops/sec |
Unixタイムスタンプ(ms) | 598ns | 1.7M ops/sec |
メモリ使用量
- 最小限のヒープアロケーション
- 静的正規表現パターンの使用
- スタックベースの解析でメモリ効率を最大化
最適化手法
ゼロコピー解析
// 文字列のコピーを避けた効率的な解析
pub fn parse_iso_date(&self, input: &str) -> Result<ParsedDate, ParseError> {
// 直接文字列スライスを操作してパフォーマンス向上
let year: i32 = input[0..4].parse()?;
let month: u32 = input[5..7].parse()?;
let day: u32 = input[8..10].parse()?;
// ...
}
遅延正規表現コンパイル
lazy_static! {
static ref ISO_DATETIME_REGEX: Regex =
Regex::new(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$")
.unwrap();
}
多言語バインディング
C FFI
C言語からの使用例:
#include "universal_date_parser.h"
int main() {
UniversalDateParser* parser = universal_parser_new();
ParsedDate result;
int status = universal_parser_parse(parser, "2023-12-25", &result);
if (status == 0) {
printf("解析成功: %d-%d-%d\n", result.year, result.month, result.day);
}
universal_parser_free(parser);
return 0;
}
WebAssembly
JavaScript/TypeScriptからの使用:
import { UniversalDateParser } from './pkg/universal_date_parser.js';
const parser = new UniversalDateParser();
const result = parser.parse("2023-12-25T10:30:00Z");
console.log(`解析結果: ${result.year}-${result.month}-${result.day}`);
console.log(`検出フォーマット: ${result.detected_format}`);
Python バインディング
PyO3を使用したPython対応:
import universal_date_parser
parser = universal_date_parser.UniversalDateParser()
result = parser.parse("2023-12-25T10:30:00Z")
print(f"解析結果: {result.year}-{result.month}-{result.day}")
print(f"検出フォーマット: {result.detected_format}")
実用的な使用例
ログ解析システム
use universal_date_parser::{UniversalDateParser, TimezoneMode};
use std::collections::HashMap;
struct LogAnalyzer {
parser: UniversalDateParser,
stats: HashMap<String, u32>,
}
impl LogAnalyzer {
fn new() -> Self {
Self {
parser: UniversalDateParser::new(TimezoneMode::AssumeUtc),
stats: HashMap::new(),
}
}
fn analyze_log_entry(&mut self, log_line: &str) {
// ログエントリから日付部分を抽出
if let Some(date_part) = self.extract_date_from_log(log_line) {
match self.parser.parse(&date_part) {
Ok(parsed) => {
let format = parsed.detected_format.clone();
*self.stats.entry(format).or_insert(0) += 1;
},
Err(_) => {
*self.stats.entry("unknown".to_string()).or_insert(0) += 1;
}
}
}
}
fn extract_date_from_log(&self, log_line: &str) -> Option<String> {
// ログ形式に応じた日付抽出ロジック
// 実際の実装では正規表現などを使用
None
}
}
データベース移行ツール
use universal_date_parser::{UniversalDateParser, TimezoneMode};
struct DataMigrator {
parser: UniversalDateParser,
}
impl DataMigrator {
fn new() -> Self {
Self {
parser: UniversalDateParser::new(TimezoneMode::ConvertToUtc),
}
}
fn migrate_date_column(&self, values: Vec<String>) -> Vec<String> {
values.into_iter()
.map(|date_str| {
match self.parser.parse(&date_str) {
Ok(parsed) => {
// ISO 8601 UTC形式に統一
format!("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
parsed.year,
parsed.month,
parsed.day,
parsed.hour.unwrap_or(0),
parsed.minute.unwrap_or(0),
parsed.second.unwrap_or(0)
)
},
Err(_) => date_str, // 解析失敗時は元の値を保持
}
})
.collect()
}
}
API レスポンス正規化
use universal_date_parser::{UniversalDateParser, TimezoneMode};
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize)]
struct ApiResponse {
id: u64,
#[serde(deserialize_with = "parse_flexible_date")]
created_at: String,
data: serde_json::Value,
}
fn parse_flexible_date<'de, D>(deserializer: D) -> Result<String, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
let parser = UniversalDateParser::new(TimezoneMode::ConvertToUtc);
match parser.parse(&s) {
Ok(parsed) => Ok(format!("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
parsed.year,
parsed.month,
parsed.day,
parsed.hour.unwrap_or(0),
parsed.minute.unwrap_or(0),
parsed.second.unwrap_or(0)
)),
Err(_) => Err(serde::de::Error::custom("無効な日付フォーマット")),
}
}
エラーハンドリングとデバッグ
エラータイプ
#[derive(Debug, Clone)]
pub enum ParseError {
InvalidFormat(String),
InvalidDate(String),
AmbiguousFormat(String),
TimezoneParseError(String),
NumericParseError(String),
}
impl std::fmt::Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
ParseError::InvalidFormat(msg) => write!(f, "無効なフォーマット: {}", msg),
ParseError::InvalidDate(msg) => write!(f, "無効な日付: {}", msg),
ParseError::AmbiguousFormat(msg) => write!(f, "曖昧なフォーマット: {}", msg),
ParseError::TimezoneParseError(msg) => write!(f, "タイムゾーンエラー: {}", msg),
ParseError::NumericParseError(msg) => write!(f, "数値変換エラー: {}", msg),
}
}
}
デバッグモード
let parser = UniversalDateParser::builder()
.debug_mode(true)
.build();
match parser.parse("ambiguous/date/format") {
Ok(result) => println!("成功: {:?}", result),
Err(e) => {
println!("エラー: {}", e);
// デバッグモードでは詳細な解析ステップが出力される
}
}
テストとベンチマーク
単体テスト例
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_iso_8601_parsing() {
let parser = UniversalDateParser::new(TimezoneMode::AssumeUtc);
let result = parser.parse("2023-12-25T10:30:00Z").unwrap();
assert_eq!(result.year, 2023);
assert_eq!(result.month, 12);
assert_eq!(result.day, 25);
assert_eq!(result.hour, Some(10));
assert_eq!(result.minute, Some(30));
assert_eq!(result.second, Some(0));
}
#[test]
fn test_ambiguous_format_resolution() {
let parser = UniversalDateParser::builder()
.ambiguity_resolver(AmbiguityResolver::PreferEuropean)
.build();
let result = parser.parse("01/02/2023").unwrap();
assert_eq!(result.month, 2); // ヨーロッパ式:1日2月
assert_eq!(result.day, 1);
}
}
パフォーマンステスト
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn benchmark_parsing(c: &mut Criterion) {
let parser = UniversalDateParser::new(TimezoneMode::AssumeUtc);
c.bench_function("ISO 8601 parsing", |b| {
b.iter(|| parser.parse(black_box("2023-12-25T10:30:00Z")))
});
c.bench_function("Unix timestamp parsing", |b| {
b.iter(|| parser.parse(black_box("1703520645")))
});
}
criterion_group!(benches, benchmark_parsing);
criterion_main!(benches);
よくある質問とトラブルシューティング
Q: 曖昧な日付フォーマットはどのように処理されますか?
A: ライブラリは以下の戦略で曖昧性を解決します:
- 文脈分析: 同じ入力内の他の日付パターンから推測
-
設定可能な解決方法:
AmbiguityResolver
で地域設定を指定 - エラー報告: 解決不可能な場合は明確なエラーメッセージ
// 曖昧性解決の設定例
let parser = UniversalDateParser::builder()
.ambiguity_resolver(AmbiguityResolver::PreferUS) // MM/DD/YYYY優先
.build();
Q: カスタム日付フォーマットを追加できますか?
A: 現在のバージョンではカスタムフォーマットの直接追加はサポートしていませんが、将来のバージョンで実装予定です。回避策として前処理で標準フォーマットに変換してください。
Q: パフォーマンスを最大化するには?
A: 以下の最適化を推奨します:
-
適切なタイムゾーンモード:
AssumeUtc
が最高速 - パーサーの再利用: インスタンス作成コストを削減
- バッチ処理: 大量データの一括処理
// 最適化された使用例
let parser = UniversalDateParser::new(TimezoneMode::AssumeUtc);
let dates: Vec<&str> = vec![/* 大量の日付文字列 */];
let results: Vec<_> = dates.par_iter() // rayon使用で並列処理
.map(|date| parser.parse(date))
.collect();
Q: メモリ使用量を削減するには?
A: ライブラリは既に最小限のメモリ使用量で設計されていますが、さらに削減するには:
- 不要な文字列クローンを避ける
- パース結果の即座の処理
- 大量データの場合はストリーミング処理
今後の開発予定
バージョン 0.2.0 (予定)
- 自然言語解析: "yesterday", "next week"などの相対日付
- カスタムフォーマット: ユーザー定義パターンのサポート
- ローカライゼーション: 多言語の月名・曜日名対応
バージョン 0.3.0 (予定)
- 時間範囲解析: "from 2023-01-01 to 2023-12-31"
- ファジーマッチング: 軽微な入力エラーの自動修正
- パフォーマンス向上: SIMD命令の活用
長期計画
- 機械学習: AIベースのフォーマット推測
- プラグインシステム: サードパーティ拡張のサポート
- ビジュアルツール: 解析ルールの可視化
コントリビューション
開発に参加するには
- リポジトリのフォーク
-
機能ブランチの作成:
git checkout -b feature/amazing-feature
-
変更のコミット:
git commit -m 'Add amazing feature'
-
ブランチのプッシュ:
git push origin feature/amazing-feature
- プルリクエストの作成
コーディングガイドライン
-
Rustfmt:
cargo fmt
でフォーマット -
Clippy:
cargo clippy
で静的解析 - テスト: すべての新機能にテストを追加
- ドキュメント: パブリックAPIにはドキュメントコメント必須
バグレポート
バグを発見した場合は、以下の情報を含めてIssueを作成してください:
- Rustバージョン
- ライブラリバージョン
- 入力データの例
- 期待される結果
- 実際の結果
- エラーメッセージ
まとめ
Universal Date Parserは、Rustエコシステムにおける包括的な日付解析ソリューションです。高性能、柔軟性、使いやすさを兼ね備え、様々なアプリケーションでの日付処理を大幅に簡素化します。
主な利点
- 統一されたAPI: 複数フォーマットを単一インターフェースで処理
- 高性能: 本番環境で実証済みの速度
- 信頼性: 包括的なテストスイートによる品質保証
- 拡張性: 将来の機能追加に対応したアーキテクチャ
適用分野
- ログ解析システム
- データ移行ツール
- API統合
- リアルタイム処理
- バッチ処理アプリケーション
Rustの安全性とパフォーマンスを活用し、日付解析における複雑さを隠蔽しながら、開発者に強力で使いやすいツールを提供します。
プロジェクトリンク:
- GitHub: https://github.com/rust-core-libs/universal-date-parser
- Crates.io: https://crates.io/crates/universal-date-parser
- ドキュメント: https://docs.rs/universal-date-parser
ライセンス: MIT / Apache 2.0 デュアルライセンス