use crate::{DatabaseValue, sql_interval::SqlInterval};
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct NowBuilder {
interval: SqlInterval,
timezone: Option<String>,
}
impl NowBuilder {
#[must_use]
pub const fn new() -> Self {
Self {
interval: SqlInterval::new(),
timezone: None, }
}
#[must_use]
pub fn tz<S: Into<String>>(mut self, timezone: S) -> Self {
self.timezone = Some(timezone.into());
self
}
#[must_use]
pub fn utc(mut self) -> Self {
self.timezone = None; self
}
#[must_use]
pub fn local(mut self) -> Self {
self.timezone = Some("LOCAL".to_string());
self
}
#[must_use]
pub const fn plus_years(mut self, years: i32) -> Self {
self.interval = self.interval.add_years(years);
self
}
#[must_use]
pub const fn minus_years(mut self, years: i32) -> Self {
self.interval = self.interval.add_years(-years);
self
}
#[must_use]
pub const fn plus_months(mut self, months: i32) -> Self {
self.interval = self.interval.add_months(months);
self
}
#[must_use]
pub const fn minus_months(mut self, months: i32) -> Self {
self.interval = self.interval.add_months(-months);
self
}
#[must_use]
pub const fn plus_days(mut self, days: i32) -> Self {
self.interval = self.interval.add_days(days);
self
}
#[must_use]
pub const fn minus_days(mut self, days: i32) -> Self {
self.interval = self.interval.add_days(-days);
self
}
#[must_use]
pub const fn plus_hours(mut self, hours: i64) -> Self {
self.interval = self.interval.add_hours(hours);
self
}
#[must_use]
pub const fn minus_hours(mut self, hours: i64) -> Self {
self.interval = self.interval.add_hours(-hours);
self
}
#[must_use]
pub const fn plus_minutes(mut self, minutes: i64) -> Self {
self.interval = self.interval.add_minutes(minutes);
self
}
#[must_use]
pub const fn minus_minutes(mut self, minutes: i64) -> Self {
self.interval = self.interval.add_minutes(-minutes);
self
}
#[must_use]
pub const fn plus_seconds(mut self, seconds: i64) -> Self {
self.interval = self.interval.add_seconds(seconds);
self
}
#[must_use]
pub const fn minus_seconds(mut self, seconds: i64) -> Self {
self.interval = self.interval.add_seconds(-seconds);
self
}
#[must_use]
pub fn plus_duration(mut self, duration: Duration) -> Self {
let duration_interval = SqlInterval::from_duration(duration);
self.interval = self
.interval
.add_hours(duration_interval.hours)
.add_minutes(duration_interval.minutes)
.add_seconds(duration_interval.seconds);
self.interval.nanos = self.interval.nanos.saturating_add(duration_interval.nanos);
self.interval = self.interval.normalize();
self
}
#[must_use]
pub fn minus_duration(mut self, duration: Duration) -> Self {
let duration_interval = SqlInterval::from_duration(duration);
self.interval = self
.interval
.add_hours(-duration_interval.hours)
.add_minutes(-duration_interval.minutes)
.add_seconds(-duration_interval.seconds);
if self.interval.nanos >= duration_interval.nanos {
self.interval.nanos -= duration_interval.nanos;
} else {
self.interval = self.interval.add_seconds(-1);
self.interval.nanos = self.interval.nanos + 1_000_000_000 - duration_interval.nanos;
}
self.interval = self.interval.normalize();
self
}
#[must_use]
pub fn build(self) -> DatabaseValue {
if self.interval.is_zero() && self.timezone.is_none() {
DatabaseValue::Now
} else {
DatabaseValue::NowPlus(self.interval.normalize())
}
}
}
impl Default for NowBuilder {
fn default() -> Self {
Self::new()
}
}
impl DatabaseValue {
#[must_use]
pub const fn now() -> NowBuilder {
NowBuilder::new()
}
#[must_use]
pub const fn now_plus(interval: SqlInterval) -> Self {
Self::NowPlus(interval)
}
}
impl From<NowBuilder> for DatabaseValue {
fn from(builder: NowBuilder) -> Self {
builder.build()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test_log::test]
fn test_now_builder_basic() {
let builder = NowBuilder::new();
assert!(builder.interval.is_zero());
assert!(builder.timezone.is_none());
}
#[test_log::test]
fn test_now_builder_plus_operations() {
let result = DatabaseValue::now()
.plus_years(1)
.plus_months(2)
.plus_days(3)
.plus_hours(4)
.plus_minutes(5)
.plus_seconds(6)
.build();
if let DatabaseValue::NowPlus(interval) = result {
assert_eq!(interval.years, 1);
assert_eq!(interval.months, 2);
assert_eq!(interval.days, 3);
assert_eq!(interval.hours, 4);
assert_eq!(interval.minutes, 5);
assert_eq!(interval.seconds, 6);
} else {
panic!("Expected NowPlus variant");
}
}
#[test_log::test]
fn test_now_builder_minus_operations() {
let result = DatabaseValue::now()
.minus_years(1)
.minus_days(7)
.minus_hours(3)
.build();
if let DatabaseValue::NowPlus(interval) = result {
assert_eq!(interval.years, -1);
assert_eq!(interval.days, -7);
assert_eq!(interval.hours, -3);
} else {
panic!("Expected NowPlus variant");
}
}
#[test_log::test]
fn test_now_builder_timezone() {
let builder = DatabaseValue::now().tz("America/Los_Angeles");
let _result = builder.build();
}
#[test_log::test]
fn test_now_builder_duration() {
let duration = Duration::from_secs(3661); let result = DatabaseValue::now().plus_duration(duration).build();
if let DatabaseValue::NowPlus(interval) = result {
assert_eq!(interval.hours, 1);
assert_eq!(interval.minutes, 1);
assert_eq!(interval.seconds, 1);
} else {
panic!("Expected NowPlus variant");
}
}
#[test_log::test]
fn test_zero_interval_returns_now() {
let result = DatabaseValue::now().build();
assert_eq!(result, DatabaseValue::Now);
}
#[test_log::test]
fn test_non_zero_interval_returns_now_plus() {
let result = DatabaseValue::now().plus_days(1).build();
assert!(matches!(result, DatabaseValue::NowPlus(_)));
}
#[test_log::test]
fn test_now_plus_direct() {
let interval = SqlInterval::from_hours(24);
let result = DatabaseValue::now_plus(interval.clone());
assert_eq!(result, DatabaseValue::NowPlus(interval));
}
#[test_log::test]
fn test_from_trait() {
let builder = DatabaseValue::now().plus_days(1);
let result: DatabaseValue = builder.into();
assert!(matches!(result, DatabaseValue::NowPlus(_)));
}
#[test_log::test]
fn test_normalization_in_build() {
let result = DatabaseValue::now()
.plus_minutes(90) .build();
if let DatabaseValue::NowPlus(interval) = result {
assert_eq!(interval.hours, 1);
assert_eq!(interval.minutes, 30);
} else {
panic!("Expected NowPlus variant");
}
}
#[test_log::test]
fn test_duration_subtraction() {
let duration = Duration::from_hours(1);
let result = DatabaseValue::now()
.plus_hours(2)
.minus_duration(duration)
.build();
if let DatabaseValue::NowPlus(interval) = result {
assert_eq!(interval.hours, 1);
} else {
panic!("Expected NowPlus variant");
}
}
#[test_log::test]
fn test_duration_with_nanoseconds() {
let duration = Duration::new(1, 500_000_000); let result = DatabaseValue::now().plus_duration(duration).build();
if let DatabaseValue::NowPlus(interval) = result {
assert_eq!(interval.seconds, 1);
assert_eq!(interval.nanos, 500_000_000);
} else {
panic!("Expected NowPlus variant");
}
}
#[test_log::test]
fn test_minus_duration_with_nanos_borrow() {
let add_duration = Duration::new(2, 100_000_000); let sub_duration = Duration::new(0, 500_000_000);
let result = DatabaseValue::now()
.plus_duration(add_duration)
.minus_duration(sub_duration)
.build();
if let DatabaseValue::NowPlus(interval) = result {
assert_eq!(interval.seconds, 1);
assert_eq!(interval.nanos, 600_000_000);
} else {
panic!("Expected NowPlus variant");
}
}
#[test_log::test]
fn test_timezone_local() {
let builder = DatabaseValue::now().local();
assert_eq!(builder.timezone, Some("LOCAL".to_string()));
}
#[test_log::test]
fn test_timezone_utc() {
let builder = DatabaseValue::now().tz("America/Los_Angeles").utc();
assert_eq!(builder.timezone, None); }
#[test_log::test]
fn test_timezone_custom() {
let builder = DatabaseValue::now().tz("Europe/London");
assert_eq!(builder.timezone, Some("Europe/London".to_string()));
}
#[test_log::test]
fn test_complex_interval_combination() {
let result = DatabaseValue::now()
.plus_years(1)
.minus_months(2)
.plus_days(15)
.minus_hours(6)
.plus_minutes(30)
.minus_seconds(45)
.build();
if let DatabaseValue::NowPlus(interval) = result {
assert_eq!(interval.years, 1);
assert_eq!(interval.months, -2);
assert_eq!(interval.days, 15);
assert_eq!(interval.hours, -6);
assert_eq!(interval.minutes, 30);
assert_eq!(interval.seconds, -45);
} else {
panic!("Expected NowPlus variant");
}
}
#[test_log::test]
fn test_default_now_builder() {
let builder = NowBuilder::default();
assert!(builder.interval.is_zero());
assert!(builder.timezone.is_none());
}
#[test_log::test]
fn test_now_builder_chaining() {
let result = NowBuilder::new()
.plus_days(1)
.plus_hours(2)
.plus_minutes(3)
.build();
assert!(matches!(result, DatabaseValue::NowPlus(_)));
}
#[test_log::test]
fn test_nanos_overflow_in_plus_duration() {
let duration1 = Duration::new(0, 800_000_000); let duration2 = Duration::new(0, 700_000_000);
let result = DatabaseValue::now()
.plus_duration(duration1)
.plus_duration(duration2)
.build();
if let DatabaseValue::NowPlus(interval) = result {
assert_eq!(interval.seconds, 1);
assert_eq!(interval.nanos, 500_000_000);
} else {
panic!("Expected NowPlus variant");
}
}
}