use crate::datatypes::values::Value;
pub(super) const SPATIAL_RESOLUTION_HELP: &str =
"spatial argument did not resolve to a geometry or point. \
Either pass column_types={'<col>': 'geometry'} (or 'location.lat'/'location.lon') during \
add_nodes(), or store the data under a conventional property name (wkt_geometry, geometry, \
geom, or wkt for WKT; latitude+longitude or lat+lon for points).";
pub(super) fn json_to_value(j: &serde_json::Value) -> Value {
match j {
serde_json::Value::Null => Value::Null,
serde_json::Value::Bool(b) => Value::Boolean(*b),
serde_json::Value::Number(n) => {
if let Some(i) = n.as_i64() {
Value::Int64(i)
} else {
Value::Float64(n.as_f64().unwrap_or(f64::NAN))
}
}
serde_json::Value::String(s) => Value::String(s.clone()),
serde_json::Value::Array(a) => Value::List(a.iter().map(json_to_value).collect()),
serde_json::Value::Object(o) => Value::Map(
o.iter()
.map(|(k, v)| (k.clone(), json_to_value(v)))
.collect(),
),
}
}
pub(super) struct ParsedIsoDateTime {
pub local: chrono::NaiveDateTime,
pub offset: Option<chrono::FixedOffset>,
}
impl ParsedIsoDateTime {
pub fn utc(&self) -> chrono::NaiveDateTime {
match self.offset {
Some(offset) => self.local - offset,
None => self.local,
}
}
}
pub(super) fn parse_iso_datetime(s: &str) -> Option<ParsedIsoDateTime> {
use chrono::Timelike;
let trimmed = s.trim();
let truncate = |dt: chrono::NaiveDateTime| dt.with_nanosecond(0).unwrap_or(dt);
if let Ok(zoned) = chrono::DateTime::parse_from_rfc3339(trimmed) {
return Some(ParsedIsoDateTime {
local: truncate(zoned.naive_local()),
offset: Some(*zoned.offset()),
});
}
let signed;
let widened = match trimmed.split_once('-') {
Some((year, _)) if year.len() > 4 && year.chars().all(|c| c.is_ascii_digit()) => {
signed = format!("+{trimmed}");
Some(signed.as_str())
}
_ => None,
};
for candidate in [Some(trimmed), widened].into_iter().flatten() {
for format in ["%Y-%m-%dT%H:%M:%S%.f", "%Y-%m-%dT%H:%M"] {
if let Ok(naive) = chrono::NaiveDateTime::parse_from_str(candidate, format) {
return Some(ParsedIsoDateTime {
local: truncate(naive),
offset: None,
});
}
}
}
if trimmed.contains('T') {
return None;
}
crate::graph::features::timeseries::parse_date_query(trimmed)
.ok()
.and_then(|(date, _)| date.and_hms_opt(0, 0, 0))
.map(|local| ParsedIsoDateTime {
local,
offset: None,
})
}
#[derive(Clone, Copy)]
pub(super) enum LocalTemporalKind {
DateTime,
Time,
}
pub(super) fn next_random_u64() -> u64 {
use std::cell::Cell;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::SystemTime;
static THREAD_COUNTER: AtomicU64 = AtomicU64::new(0);
thread_local! {
static XORSHIFT_STATE: Cell<u64> = {
let nanos = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64;
let counter = THREAD_COUNTER.fetch_add(1, Ordering::Relaxed);
let mut seed = nanos.wrapping_add(counter.wrapping_mul(0x9E37_79B9_7F4A_7C15));
seed ^= seed >> 30;
seed = seed.wrapping_mul(0xBF58_476D_1CE4_E5B9);
seed ^= seed >> 27;
seed = seed.wrapping_mul(0x94D0_49BB_1331_11EB);
seed ^= seed >> 31;
Cell::new(seed | 1)
};
}
XORSHIFT_STATE.with(|state| {
let mut x = state.get();
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
state.set(x);
x
})
}
pub(super) fn next_random_u128_halves() -> (u64, u64) {
(next_random_u64(), next_random_u64())
}
pub(super) fn coerce_naive_datetime(v: &Value) -> Option<chrono::NaiveDateTime> {
match v {
Value::Timestamp(dt) => Some(*dt),
Value::DateTime(d) => d.and_hms_opt(0, 0, 0),
_ => None,
}
}