Files
turso/core/time/internal.rs
2025-06-30 10:01:03 +03:00

567 lines
17 KiB
Rust

use std::ops::{Deref, Sub};
use chrono::{self, DateTime, Timelike, Utc};
use chrono::{prelude::*, DurationRound};
use turso_ext::Value;
use crate::time::{Result, TimeError};
const DAYS_BEFORE_EPOCH: i64 = 719162;
const TIME_BLOB_SIZE: usize = 13;
const VERSION: u8 = 1;
#[derive(Debug, PartialEq, PartialOrd, Eq)]
pub struct Time {
inner: DateTime<Utc>,
}
#[derive(Debug, PartialEq, Eq, PartialOrd)]
pub struct Duration {
inner: chrono::Duration,
}
#[derive(strum_macros::Display, strum_macros::EnumString)]
pub enum TimeField {
#[strum(to_string = "millennium")]
Millennium,
#[strum(to_string = "century")]
Century,
#[strum(to_string = "decade")]
Decade,
#[strum(to_string = "year")]
Year,
#[strum(to_string = "quarter")]
Quarter,
#[strum(to_string = "month")]
Month,
#[strum(to_string = "day")]
Day,
#[strum(to_string = "hour")]
Hour,
#[strum(to_string = "minute")]
Minute,
#[strum(to_string = "second")]
Second,
#[strum(to_string = "millisecond")]
MilliSecond,
#[strum(to_string = "milli")]
Milli,
#[strum(to_string = "microsecond")]
MicroSecond,
#[strum(to_string = "micro")]
Micro,
#[strum(to_string = "nanosecond")]
NanoSecond,
#[strum(to_string = "nano")]
Nano,
#[strum(to_string = "isoyear")]
IsoYear,
#[strum(to_string = "isoweek")]
IsoWeek,
#[strum(to_string = "isodow")]
IsoDow,
#[strum(to_string = "yearday")]
YearDay,
#[strum(to_string = "weekday")]
WeekDay,
#[strum(to_string = "epoch")]
Epoch,
}
#[derive(strum_macros::Display, strum_macros::EnumString)]
pub enum TimeRoundField {
#[strum(to_string = "millennium")]
Millennium,
#[strum(to_string = "century")]
Century,
#[strum(to_string = "decade")]
Decade,
#[strum(to_string = "year")]
Year,
#[strum(to_string = "quarter")]
Quarter,
#[strum(to_string = "month")]
Month,
#[strum(to_string = "week")]
Week,
#[strum(to_string = "day")]
Day,
#[strum(to_string = "hour")]
Hour,
#[strum(to_string = "minute")]
Minute,
#[strum(to_string = "second")]
Second,
#[strum(to_string = "millisecond")]
MilliSecond,
#[strum(to_string = "milli")]
Milli,
#[strum(to_string = "microsecond")]
MicroSecond,
#[strum(to_string = "micro")]
Micro,
}
impl Default for Time {
fn default() -> Self {
Self::new()
}
}
impl Time {
/// Returns a new instance of Time with tracking UTC::now
pub fn new() -> Self {
Self { inner: Utc::now() }
}
pub fn into_blob(self) -> Value {
let blob: [u8; 13] = self.into();
Value::from_blob(blob.to_vec())
}
pub fn fmt_iso(&self, offset_sec: i32) -> Result<String> {
if offset_sec == 0 {
if self.inner.nanosecond() == 0 {
return Ok(self.inner.format("%FT%TZ").to_string());
} else {
return Ok(self.inner.format("%FT%T%.9fZ").to_string());
}
}
// I do not see how this can error
let offset = &FixedOffset::east_opt(offset_sec).ok_or(TimeError::InvalidFormat)?;
let timezone_date = self.inner.with_timezone(offset);
if timezone_date.nanosecond() == 0 {
Ok(timezone_date.format("%FT%T%:z").to_string())
} else {
Ok(timezone_date.format("%FT%T%.9f%:z").to_string())
}
}
pub fn fmt_datetime(&self, offset_sec: i32) -> Result<String> {
let fmt = "%F %T";
if offset_sec == 0 {
return Ok(self.inner.format(fmt).to_string());
}
// I do not see how this can error
let offset = &FixedOffset::east_opt(offset_sec).ok_or(TimeError::InvalidFormat)?;
let timezone_date = self.inner.with_timezone(offset);
Ok(timezone_date.format(fmt).to_string())
}
pub fn fmt_date(&self, offset_sec: i32) -> Result<String> {
let fmt = "%F";
if offset_sec == 0 {
return Ok(self.inner.format(fmt).to_string());
}
// I do not see how this can error
let offset = &FixedOffset::east_opt(offset_sec).ok_or(TimeError::InvalidFormat)?;
let timezone_date = self.inner.with_timezone(offset);
Ok(timezone_date.format(fmt).to_string())
}
pub fn fmt_time(&self, offset_sec: i32) -> Result<String> {
let fmt = "%T";
if offset_sec == 0 {
return Ok(self.inner.format(fmt).to_string());
}
// I do not see how this can error
let offset = &FixedOffset::east_opt(offset_sec).ok_or(TimeError::InvalidFormat)?;
let timezone_date = self.inner.with_timezone(offset);
Ok(timezone_date.format(fmt).to_string())
}
/// Adjust the datetime to the offset
pub fn from_datetime(dt: DateTime<Utc>) -> Self {
Self { inner: dt }
}
//
#[allow(clippy::too_many_arguments)]
pub fn time_date(
year: i32,
month: i32,
day: i64,
hour: i64,
minutes: i64,
seconds: i64,
nano_secs: i64,
offset: FixedOffset,
) -> Result<Self> {
let mut dt: NaiveDateTime = NaiveDate::from_ymd_opt(1, 1, 1)
.unwrap()
.and_hms_opt(0, 0, 0)
.unwrap();
match year.cmp(&0) {
std::cmp::Ordering::Greater => {
dt = dt
.checked_add_months(chrono::Months::new((year - 1).unsigned_abs() * 12))
.ok_or(TimeError::CreationError)?
}
std::cmp::Ordering::Less => {
dt = dt
.checked_sub_months(chrono::Months::new((year - 1).unsigned_abs() * 12))
.ok_or(TimeError::CreationError)?
}
std::cmp::Ordering::Equal => (),
};
match month.cmp(&0) {
std::cmp::Ordering::Greater => {
dt = dt
.checked_add_months(chrono::Months::new((month - 1).unsigned_abs()))
.ok_or(TimeError::CreationError)?
}
std::cmp::Ordering::Less => {
dt = dt
.checked_sub_months(chrono::Months::new((month - 1).unsigned_abs()))
.ok_or(TimeError::CreationError)?
}
std::cmp::Ordering::Equal => (),
};
dt += chrono::Duration::try_days(day - 1).ok_or(TimeError::CreationError)?;
dt += chrono::Duration::try_hours(hour).ok_or(TimeError::CreationError)?;
dt += chrono::Duration::try_minutes(minutes).ok_or(TimeError::CreationError)?;
dt += chrono::Duration::try_seconds(seconds).ok_or(TimeError::CreationError)?;
dt += chrono::Duration::nanoseconds(nano_secs);
dt = dt
.and_local_timezone(offset)
.single()
.ok_or(TimeError::CreationError)?
.naive_utc();
Ok(dt.into())
}
pub fn time_add_date(self, years: i32, months: i32, days: i64) -> Result<Self> {
let mut dt: NaiveDateTime = self.into();
match years.cmp(&0) {
std::cmp::Ordering::Greater => {
dt = dt
.checked_add_months(chrono::Months::new(years.unsigned_abs() * 12))
.ok_or(TimeError::CreationError)?;
}
std::cmp::Ordering::Less => {
dt = dt
.checked_sub_months(chrono::Months::new(years.unsigned_abs() * 12))
.ok_or(TimeError::CreationError)?;
}
std::cmp::Ordering::Equal => (),
};
match months.cmp(&0) {
std::cmp::Ordering::Greater => {
dt = dt
.checked_add_months(chrono::Months::new(months.unsigned_abs()))
.ok_or(TimeError::CreationError)?
}
std::cmp::Ordering::Less => {
dt = dt
.checked_sub_months(chrono::Months::new(months.unsigned_abs()))
.ok_or(TimeError::CreationError)?
}
std::cmp::Ordering::Equal => (),
};
dt += chrono::Duration::try_days(days).ok_or(TimeError::CreationError)?;
Ok(dt.into())
}
pub fn get_second(&self) -> i64 {
self.inner.second() as i64
}
pub fn get_nanosecond(&self) -> i64 {
self.inner.timestamp_subsec_nanos() as i64
}
pub fn to_unix(&self) -> i64 {
self.inner.timestamp()
}
pub fn to_unix_milli(&self) -> i64 {
self.inner.timestamp_millis()
}
pub fn to_unix_micro(&self) -> i64 {
self.inner.timestamp_micros()
}
pub fn to_unix_nano(&self) -> Option<i64> {
self.inner.timestamp_nanos_opt()
}
pub fn add_duration(&self, d: Duration) -> Self {
Self {
inner: self.inner + d.inner,
}
}
pub fn sub_duration(&self, d: Duration) -> Self {
Self {
inner: self.inner - d.inner,
}
}
pub fn trunc_duration(&self, d: Duration) -> Result<Self> {
Ok(Self {
inner: self.inner.duration_trunc(d.inner)?,
})
}
pub fn trunc_field(&self, field: TimeRoundField) -> Result<Self> {
use TimeRoundField::*;
let year: i32;
let mut month: i32 = 1;
let mut week: i32 = 0;
let mut day: i64 = 1;
let mut hour: i64 = 0;
let mut minutes: i64 = 0;
let mut seconds: i64 = 0;
let mut nano_secs: i64 = 0;
let offset = FixedOffset::east_opt(0).unwrap(); // UTC
match field {
Millennium => {
let millennium = (self.inner.year() / 1000) * 1000;
year = millennium;
}
Century => {
let century = (self.inner.year() / 100) * 100;
year = century;
}
Decade => {
let decade = (self.inner.year() / 10) * 10;
year = decade;
}
Year => {
year = self.inner.year();
}
Quarter => {
let quarter = ((self.inner.month() - 1) / 3) as i32;
year = self.inner.year();
month = (quarter * 3) + 1;
}
Month => {
year = self.inner.year();
month = self.inner.month() as i32;
}
Week => {
let isoweek = self.inner.iso_week();
year = isoweek.year();
week = isoweek.week() as i32;
}
Day => {
year = self.inner.year();
month = self.inner.month() as i32;
day = self.inner.day() as i64;
}
Hour => {
year = self.inner.year();
month = self.inner.month() as i32;
day = self.inner.day() as i64;
hour = self.inner.hour() as i64;
}
Minute => {
year = self.inner.year();
month = self.inner.month() as i32;
day = self.inner.day() as i64;
hour = self.inner.hour() as i64;
minutes = self.inner.minute() as i64;
}
Second => {
year = self.inner.year();
month = self.inner.month() as i32;
day = self.inner.day() as i64;
hour = self.inner.hour() as i64;
minutes = self.inner.minute() as i64;
seconds = self.inner.second() as i64;
}
MilliSecond | Milli => {
year = self.inner.year();
month = self.inner.month() as i32;
day = self.inner.day() as i64;
hour = self.inner.hour() as i64;
minutes = self.inner.minute() as i64;
seconds = self.inner.second() as i64;
nano_secs = (self.inner.nanosecond() / 1_000_000 * 1_000_000) as i64;
}
MicroSecond | Micro => {
year = self.inner.year();
month = self.inner.month() as i32;
day = self.inner.day() as i64;
hour = self.inner.hour() as i64;
minutes = self.inner.minute() as i64;
seconds = self.inner.second() as i64;
nano_secs = (self.inner.nanosecond() / 1_000 * 1_000) as i64;
}
};
let mut ret = Self::time_date(year, month, day, hour, minutes, seconds, nano_secs, offset)?;
// Means we have to adjust for the week
if week != 0 {
ret = ret.time_add_date(0, 0, ((week - 1) * 7) as i64)?;
}
Ok(ret)
}
pub fn round_duration(&self, d: Duration) -> Result<Self> {
Ok(Self {
inner: self.inner.duration_round(d.inner)?,
})
}
pub fn time_get(&self, field: TimeField) -> Value {
use TimeField::*;
match field {
Millennium => Value::from_integer((self.inner.year() / 1000) as i64),
Century => Value::from_integer((self.inner.year() / 100) as i64),
Decade => Value::from_integer((self.inner.year() / 10) as i64),
Year => Value::from_integer(self.inner.year() as i64),
Quarter => Value::from_integer(self.inner.month().div_ceil(3) as i64),
Month => Value::from_integer(self.inner.month() as i64),
Day => Value::from_integer(self.inner.day() as i64),
Hour => Value::from_integer(self.inner.hour() as i64),
Minute => Value::from_integer(self.inner.minute() as i64),
Second => Value::from_float(
self.inner.second() as f64 + (self.inner.nanosecond() as f64) / (1_000_000_000_f64),
),
MilliSecond | Milli => {
Value::from_integer((self.inner.nanosecond() / 1_000_000 % 1_000) as i64)
}
MicroSecond | Micro => {
Value::from_integer((self.inner.nanosecond() / 1_000 % 1_000_000) as i64)
}
NanoSecond | Nano => {
Value::from_integer((self.inner.nanosecond() % 1_000_000_000) as i64)
}
IsoYear => Value::from_integer(self.inner.iso_week().year() as i64),
IsoWeek => Value::from_integer(self.inner.iso_week().week() as i64),
IsoDow => Value::from_integer(self.inner.weekday().days_since(Weekday::Sun) as i64),
YearDay => Value::from_integer(self.inner.ordinal() as i64),
WeekDay => Value::from_integer(self.inner.weekday().num_days_from_sunday() as i64),
Epoch => Value::from_float(
self.inner.timestamp() as f64 + self.inner.nanosecond() as f64 / 1_000_000_000_f64,
),
}
}
}
impl From<Time> for [u8; TIME_BLOB_SIZE] {
fn from(value: Time) -> Self {
let mut blob = [0u8; 13];
let seconds = value.inner.timestamp() + (3600 * 24 * DAYS_BEFORE_EPOCH);
let nanoseconds = value.inner.timestamp_subsec_nanos();
blob[0] = VERSION;
blob[1] = (seconds >> 56) as u8; // bytes 1-8: seconds
blob[2] = (seconds >> 48) as u8;
blob[3] = (seconds >> 40) as u8;
blob[4] = (seconds >> 32) as u8;
blob[5] = (seconds >> 24) as u8;
blob[6] = (seconds >> 16) as u8;
blob[7] = (seconds >> 8) as u8;
blob[8] = seconds as u8;
blob[9] = (nanoseconds >> 24) as u8; // bytes 9-12: nanoseconds
blob[10] = (nanoseconds >> 16) as u8;
blob[11] = (nanoseconds >> 8) as u8;
blob[12] = (nanoseconds) as u8;
blob
}
}
impl TryFrom<Vec<u8>> for Time {
type Error = TimeError;
fn try_from(value: Vec<u8>) -> Result<Time> {
if value.len() != TIME_BLOB_SIZE {
return Err(TimeError::InvalidSize);
}
if value[0] != VERSION {
return Err(TimeError::MismatchVersion);
}
let seconds = value[8] as i64
| (value[7] as i64) << 8
| (value[6] as i64) << 16
| (value[5] as i64) << 24
| (value[4] as i64) << 32
| (value[3] as i64) << 40
| (value[2] as i64) << 48
| (value[1] as i64) << 56;
let nanoseconds = value[12] as u32
| (value[11] as u32) << 8
| (value[10] as u32) << 16
| (value[9] as u32) << 24;
Ok(Self {
inner: DateTime::from_timestamp(seconds - (3600 * 24 * DAYS_BEFORE_EPOCH), nanoseconds)
.ok_or(TimeError::InvalidFormat)?
.to_utc(),
})
}
}
impl From<NaiveDateTime> for Time {
fn from(value: NaiveDateTime) -> Self {
Self {
inner: value.and_utc(),
}
}
}
impl From<Time> for NaiveDateTime {
fn from(value: Time) -> Self {
value.inner.naive_utc()
}
}
impl Sub for Time {
type Output = Duration;
fn sub(self, rhs: Self) -> Self::Output {
Duration {
inner: self.inner - rhs.inner,
}
}
}
impl From<i64> for Duration {
fn from(value: i64) -> Self {
Self {
inner: chrono::Duration::nanoseconds(value),
}
}
}
impl Deref for Duration {
type Target = chrono::Duration;
fn deref(&self) -> &Self::Target {
&self.inner
}
}