Files
hakorune/src/boxes/time_box.rs
2025-08-15 04:48:10 +00:00

509 lines
14 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*! ⏰ TimeBox - 時間・日付操作Box
*
* ## 📝 概要
* 高精度な時間・日付操作を提供するBox。
* JavaScript Date、Python datetime、C# DateTimeと同等機能。
* タイムスタンプ処理、フォーマット、時差計算をサポート。
*
* ## 🛠️ 利用可能メソッド
*
* ### 📅 基本操作
* - `now()` - 現在日時取得
* - `fromTimestamp(timestamp)` - UNIXタイムスタンプから日時作成
* - `parse(date_string)` - 文字列から日時パース
* - `format(pattern)` - 指定フォーマットで文字列化
*
* ### 🔢 値取得
* - `year()` - 年取得
* - `month()` - 月取得 (1-12)
* - `day()` - 日取得 (1-31)
* - `hour()` - 時取得 (0-23)
* - `minute()` - 分取得 (0-59)
* - `second()` - 秒取得 (0-59)
* - `weekday()` - 曜日取得 (0=日曜)
*
* ### ⏱️ 計算
* - `addDays(days)` - 日数加算
* - `addHours(hours)` - 時間加算
* - `addMinutes(minutes)` - 分加算
* - `diffDays(other)` - 日数差計算
* - `diffHours(other)` - 時間差計算
*
* ## 💡 使用例
* ```nyash
* local time, now, birthday, age
* time = new TimeBox()
*
* // 現在日時
* now = time.now()
* print("現在: " + now.format("yyyy/MM/dd HH:mm:ss"))
*
* // 誕生日から年齢計算
* birthday = time.parse("1995-03-15")
* age = now.diffYears(birthday)
* print("年齢: " + age.toString() + "歳")
*
* // 1週間後
* local next_week
* next_week = now.addDays(7)
* print("1週間後: " + next_week.format("MM月dd日"))
* ```
*
* ## 🎮 実用例 - イベントスケジューラー
* ```nyash
* static box EventScheduler {
* init { time, events, current }
*
* main() {
* me.time = new TimeBox()
* me.events = []
* me.current = me.time.now()
*
* // イベント追加
* me.addEvent("会議", me.current.addHours(2))
* me.addEvent("ランチ", me.current.addHours(5))
* me.addEvent("プレゼン", me.current.addDays(1))
*
* me.showUpcomingEvents()
* }
*
* addEvent(title, datetime) {
* local event
* event = new MapBox()
* event.set("title", title)
* event.set("datetime", datetime)
* event.set("timestamp", datetime.toTimestamp())
* me.events.push(event)
* }
*
* showUpcomingEvents() {
* print("=== 今後のイベント ===")
* loop(i < me.events.length()) {
* local event, hours_until
* event = me.events.get(i)
* hours_until = event.get("datetime").diffHours(me.current)
*
* print(event.get("title") + " - " +
* hours_until.toString() + "時間後")
* }
* }
* }
* ```
*
* ## 🕐 時間計算例
* ```nyash
* local time, start, end, duration
* time = new TimeBox()
*
* // 作業時間計測
* start = time.now()
* // 何か重い処理...
* heavyCalculation()
* end = time.now()
*
* duration = end.diffSeconds(start)
* print("処理時間: " + duration.toString() + "秒")
*
* // 締切まで残り時間
* local deadline, remaining
* deadline = time.parse("2025-12-31 23:59:59")
* remaining = deadline.diffDays(time.now())
* print("締切まで" + remaining.toString() + "日")
* ```
*
* ## ⚠️ 注意
* - ローカルタイムゾーンに基づく処理
* - パース可能な日時フォーマットは限定的
* - UNIXタイムスタンプは秒単位
* - 夏時間切り替え時は計算に注意
*/
use crate::box_trait::{NyashBox, StringBox, IntegerBox, BoolBox, BoxCore, BoxBase};
use std::fmt::{Debug, Display};
use std::any::Any;
use std::time::{SystemTime, Duration};
use chrono::{DateTime, Local, TimeZone, Datelike, Timelike};
/// 時間操作を提供するBox
#[derive(Debug, Clone)]
pub struct TimeBox {
base: BoxBase,
}
impl TimeBox {
pub fn new() -> Self {
Self { base: BoxBase::new() }
}
/// 現在時刻を取得
pub fn now(&self) -> Box<dyn NyashBox> {
Box::new(DateTimeBox::now())
}
/// UNIXタイムスタンプから日時を作成
pub fn fromTimestamp(&self, timestamp: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
if let Some(int_box) = timestamp.as_any().downcast_ref::<IntegerBox>() {
Box::new(DateTimeBox::from_timestamp(int_box.value))
} else {
Box::new(StringBox::new("Error: fromTimestamp() requires integer input"))
}
}
/// 日時文字列をパース
pub fn parse(&self, date_str: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
if let Some(string_box) = date_str.as_any().downcast_ref::<StringBox>() {
match DateTimeBox::parse(&string_box.value) {
Ok(dt) => Box::new(dt),
Err(e) => Box::new(StringBox::new(&format!("Error: {}", e))),
}
} else {
Box::new(StringBox::new("Error: parse() requires string input"))
}
}
/// ミリ秒スリープ
pub fn sleep(&self, millis: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
if let Some(int_box) = millis.as_any().downcast_ref::<IntegerBox>() {
if int_box.value > 0 {
std::thread::sleep(Duration::from_millis(int_box.value as u64));
Box::new(StringBox::new("ok"))
} else {
Box::new(StringBox::new("Error: sleep() requires positive milliseconds"))
}
} else {
Box::new(StringBox::new("Error: sleep() requires integer input"))
}
}
/// 現在時刻をフォーマット
pub fn format(&self, format_str: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
if let Some(str_box) = format_str.as_any().downcast_ref::<StringBox>() {
let now = Local::now();
let formatted = now.format(&str_box.value).to_string();
Box::new(StringBox::new(formatted))
} else {
Box::new(StringBox::new("Error: format() requires string format pattern"))
}
}
}
impl NyashBox for TimeBox {
fn type_name(&self) -> &'static str {
"TimeBox"
}
fn to_string_box(&self) -> StringBox {
StringBox::new("TimeBox()")
}
fn clone_box(&self) -> Box<dyn NyashBox> {
Box::new(self.clone())
}
/// 仮実装: clone_boxと同じ後で修正
fn share_box(&self) -> Box<dyn NyashBox> {
self.clone_box()
}
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
if let Some(other_time) = other.as_any().downcast_ref::<TimeBox>() {
BoolBox::new(self.base.id == other_time.base.id)
} else {
BoolBox::new(false)
}
}
}
impl BoxCore for TimeBox {
fn box_id(&self) -> u64 {
self.base.id
}
fn parent_type_id(&self) -> Option<std::any::TypeId> {
self.base.parent_type_id
}
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "TimeBox()")
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
impl Display for TimeBox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.fmt_box(f)
}
}
/// 日時を表すBox
#[derive(Debug, Clone)]
pub struct DateTimeBox {
pub datetime: DateTime<Local>,
base: BoxBase,
}
impl DateTimeBox {
/// 現在時刻で作成
pub fn now() -> Self {
Self {
datetime: Local::now(),
base: BoxBase::new(),
}
}
/// UNIXタイムスタンプから作成
pub fn from_timestamp(timestamp: i64) -> Self {
let datetime = Local.timestamp_opt(timestamp, 0).unwrap();
Self { datetime, base: BoxBase::new() }
}
/// 文字列からパース
pub fn parse(date_str: &str) -> Result<Self, String> {
// ISO 8601形式でパース
match DateTime::parse_from_rfc3339(date_str) {
Ok(dt) => Ok(Self {
datetime: dt.with_timezone(&Local),
base: BoxBase::new(),
}),
Err(_) => {
// シンプルな形式でパース (YYYY-MM-DD HH:MM:SS)
match chrono::NaiveDateTime::parse_from_str(date_str, "%Y-%m-%d %H:%M:%S") {
Ok(naive_dt) => {
let datetime = Local.from_local_datetime(&naive_dt).unwrap();
Ok(Self { datetime, base: BoxBase::new() })
}
Err(e) => Err(format!("Failed to parse date: {}", e)),
}
}
}
}
/// 年を取得
pub fn year(&self) -> Box<dyn NyashBox> {
Box::new(IntegerBox::new(self.datetime.year() as i64))
}
/// 月を取得
pub fn month(&self) -> Box<dyn NyashBox> {
Box::new(IntegerBox::new(self.datetime.month() as i64))
}
/// 日を取得
pub fn day(&self) -> Box<dyn NyashBox> {
Box::new(IntegerBox::new(self.datetime.day() as i64))
}
/// 時を取得
pub fn hour(&self) -> Box<dyn NyashBox> {
Box::new(IntegerBox::new(self.datetime.hour() as i64))
}
/// 分を取得
pub fn minute(&self) -> Box<dyn NyashBox> {
Box::new(IntegerBox::new(self.datetime.minute() as i64))
}
/// 秒を取得
pub fn second(&self) -> Box<dyn NyashBox> {
Box::new(IntegerBox::new(self.datetime.second() as i64))
}
/// UNIXタイムスタンプを取得
pub fn timestamp(&self) -> Box<dyn NyashBox> {
Box::new(IntegerBox::new(self.datetime.timestamp()))
}
/// ISO 8601形式でフォーマット
pub fn toISOString(&self) -> Box<dyn NyashBox> {
Box::new(StringBox::new(&self.datetime.to_rfc3339()))
}
/// カスタムフォーマット
pub fn format(&self, fmt: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
if let Some(string_box) = fmt.as_any().downcast_ref::<StringBox>() {
let formatted = self.datetime.format(&string_box.value).to_string();
Box::new(StringBox::new(&formatted))
} else {
Box::new(StringBox::new("Error: format() requires string input"))
}
}
/// 日付を加算
pub fn addDays(&self, days: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
if let Some(int_box) = days.as_any().downcast_ref::<IntegerBox>() {
let new_datetime = self.datetime + chrono::Duration::days(int_box.value);
Box::new(DateTimeBox {
datetime: new_datetime,
base: BoxBase::new(),
})
} else {
Box::new(StringBox::new("Error: addDays() requires integer input"))
}
}
/// 時間を加算
pub fn addHours(&self, hours: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
if let Some(int_box) = hours.as_any().downcast_ref::<IntegerBox>() {
let new_datetime = self.datetime + chrono::Duration::hours(int_box.value);
Box::new(DateTimeBox {
datetime: new_datetime,
base: BoxBase::new(),
})
} else {
Box::new(StringBox::new("Error: addHours() requires integer input"))
}
}
}
impl NyashBox for DateTimeBox {
fn type_name(&self) -> &'static str {
"DateTimeBox"
}
fn to_string_box(&self) -> StringBox {
StringBox::new(&self.datetime.format("%Y-%m-%d %H:%M:%S").to_string())
}
fn clone_box(&self) -> Box<dyn NyashBox> {
Box::new(self.clone())
}
/// 仮実装: clone_boxと同じ後で修正
fn share_box(&self) -> Box<dyn NyashBox> {
self.clone_box()
}
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
if let Some(other_dt) = other.as_any().downcast_ref::<DateTimeBox>() {
BoolBox::new(self.datetime == other_dt.datetime)
} else {
BoolBox::new(false)
}
}
}
impl BoxCore for DateTimeBox {
fn box_id(&self) -> u64 {
self.base.id
}
fn parent_type_id(&self) -> Option<std::any::TypeId> {
self.base.parent_type_id
}
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.datetime.format("%Y-%m-%d %H:%M:%S"))
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
impl Display for DateTimeBox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.fmt_box(f)
}
}
/// タイマーを表すBox
#[derive(Debug, Clone)]
pub struct TimerBox {
start_time: SystemTime,
base: BoxBase,
}
impl TimerBox {
pub fn new() -> Self {
Self {
start_time: SystemTime::now(),
base: BoxBase::new(),
}
}
/// 経過時間をミリ秒で取得
pub fn elapsed(&self) -> Box<dyn NyashBox> {
match self.start_time.elapsed() {
Ok(duration) => {
let millis = duration.as_millis() as i64;
Box::new(IntegerBox::new(millis))
}
Err(_) => Box::new(IntegerBox::new(0)),
}
}
/// タイマーをリセット
pub fn reset(&mut self) -> Box<dyn NyashBox> {
self.start_time = SystemTime::now();
Box::new(StringBox::new("Timer reset"))
}
}
impl NyashBox for TimerBox {
fn type_name(&self) -> &'static str {
"TimerBox"
}
fn to_string_box(&self) -> StringBox {
StringBox::new("TimerBox()")
}
fn clone_box(&self) -> Box<dyn NyashBox> {
Box::new(self.clone())
}
/// 仮実装: clone_boxと同じ後で修正
fn share_box(&self) -> Box<dyn NyashBox> {
self.clone_box()
}
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
if let Some(other_timer) = other.as_any().downcast_ref::<TimerBox>() {
BoolBox::new(self.base.id == other_timer.base.id)
} else {
BoolBox::new(false)
}
}
}
impl BoxCore for TimerBox {
fn box_id(&self) -> u64 {
self.base.id
}
fn parent_type_id(&self) -> Option<std::any::TypeId> {
self.base.parent_type_id
}
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "TimerBox()")
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
impl Display for TimerBox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.fmt_box(f)
}
}