Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
c55839e
add basic fdb reader
mathleur Mar 6, 2026
5f3ea70
float coordinates
mathleur Mar 6, 2026
6d43769
add datetime coord
mathleur Mar 6, 2026
2030ce5
fix fdb reader on db
mathleur Mar 6, 2026
a2258c0
add coord types to json serde
mathleur Mar 9, 2026
6ed69ac
fix when empty child
mathleur Mar 9, 2026
d845666
add qube examples
mathleur Mar 9, 2026
57c07e0
fix qa
mathleur Mar 9, 2026
7ea0373
add version to json
mathleur Mar 9, 2026
e4048a1
ignore files
mathleur Mar 9, 2026
e6d3d5c
rename vars
mathleur Mar 17, 2026
93bb49c
Potential fix for pull request finding
mathleur Mar 17, 2026
3158a4a
Potential fix for pull request finding
mathleur Mar 17, 2026
31ba6be
Potential fix for pull request finding
mathleur Mar 17, 2026
00e267c
Potential fix for pull request finding
mathleur Mar 17, 2026
215f0ce
Potential fix for pull request finding
mathleur Mar 17, 2026
386ee7e
Potential fix for pull request finding
mathleur Mar 17, 2026
117553f
Potential fix for pull request finding
mathleur Mar 17, 2026
dcf400c
Change datetime format to ISO-like without timezone
mathleur Mar 17, 2026
edc393f
format
mathleur Mar 17, 2026
ae558c5
fix compile errors
mathleur Mar 17, 2026
1c89da2
Merge branch 'feat/more_coord_types' of https://github.com/ecmwf/qube…
mathleur Mar 17, 2026
f4a776d
Potential fix for pull request finding
mathleur Mar 17, 2026
a7769f7
Potential fix for pull request finding
mathleur Mar 17, 2026
301590f
Potential fix for pull request finding
mathleur Mar 17, 2026
e41e7ce
Potential fix for pull request finding
mathleur Mar 17, 2026
4816222
Initial plan
Copilot Mar 17, 2026
c64d98f
Changes before error encountered
Copilot Mar 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ __pycache__
.DS_Store
.venv
*.json
*.bak
raw_list
*.egg-info/
deps/
Expand Down
1 change: 1 addition & 0 deletions qubed/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ slotmap = "1.0.7"
smallbitvec = "2.6.0"
tiny-str = "0.10.0"
tiny-vec = "0.10.0"
chrono = "0.4"
rayon = "1.7"

[lib]
Expand Down
282 changes: 282 additions & 0 deletions qubed/src/coordinates/datetime.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
use std::hash::Hash;

use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc};
use tiny_vec::TinyVec;

use crate::coordinates::{Coordinates, IntersectionResult};

#[derive(Debug, Clone, PartialEq)]
pub enum DateTimeCoordinates {
List(TinyVec<NaiveDateTime, 4>),
}

impl DateTimeCoordinates {
pub(crate) fn extend(&mut self, new_coords: &DateTimeCoordinates) {
match (self, new_coords) {
(DateTimeCoordinates::List(list), DateTimeCoordinates::List(new_list)) => {
for v in new_list.iter() {
list.push(*v);
}
}
}
}

pub(crate) fn append(&mut self, new_coord: NaiveDateTime) {
match self {
DateTimeCoordinates::List(list) => list.push(new_coord),
}
}

pub(crate) fn len(&self) -> usize {
match self {
DateTimeCoordinates::List(list) => list.len(),
}
}

pub(crate) fn contains(&self, value: NaiveDateTime) -> bool {
match self {
DateTimeCoordinates::List(list) => list.iter().any(|&v| v == value),
}
}

pub(crate) fn to_string(&self) -> String {
match self {
DateTimeCoordinates::List(list) => list
.iter()
.map(|dt| dt.format("%Y-%m-%dT%H:%M:%S").to_string())
.collect::<Vec<String>>()
.join("/"),
}
}

pub(crate) fn hash(&self, hasher: &mut std::collections::hash_map::DefaultHasher) {
"datetime".hash(hasher);
match self {
DateTimeCoordinates::List(list) => {
for dt in list.iter() {
// use seconds and nanoseconds for stable hashing
dt.and_utc().timestamp().hash(hasher);
dt.and_utc().timestamp_subsec_nanos().hash(hasher);
}
}
}
}

pub(crate) fn intersect(
&self,
other: &DateTimeCoordinates,
) -> IntersectionResult<DateTimeCoordinates> {
match (self, other) {
(DateTimeCoordinates::List(list_a), DateTimeCoordinates::List(list_b)) => {
use std::collections::HashSet;

let mut set_b: HashSet<NaiveDateTime> = HashSet::new();
for v in list_b.iter() {
set_b.insert(*v);
}

let mut set_a: HashSet<NaiveDateTime> = HashSet::new();
for v in list_a.iter() {
set_a.insert(*v);
}

let mut intersection = TinyVec::new();
let mut only_a = TinyVec::new();

let mut added: HashSet<NaiveDateTime> = HashSet::new();
for v in list_a.iter() {
if set_b.contains(v) {
if !added.contains(v) {
intersection.push(*v);
added.insert(*v);
}
} else {
only_a.push(*v);
}
}

let mut only_b = TinyVec::new();
for v in list_b.iter() {
if !set_a.contains(v) {
only_b.push(*v);
}
}

IntersectionResult {
intersection: DateTimeCoordinates::List(intersection),
only_a: DateTimeCoordinates::List(only_a),
only_b: DateTimeCoordinates::List(only_b),
}
}
}
}

/// Try to parse a string into `NaiveDateTime` using common formats.
pub(crate) fn parse_from_str(s: &str) -> Option<NaiveDateTime> {
// Try RFC3339 / ISO 8601
if let Ok(dt) = DateTime::parse_from_rfc3339(s) {
return Some(dt.with_timezone(&Utc).naive_utc());
}

// Try YYYY-MM-DD HH:MM:SS
if let Ok(ndt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S") {
return Some(ndt);
}

// Try YYYY-MM-DD
if let Ok(d) = NaiveDate::parse_from_str(s, "%Y-%m-%d") {
return Some(NaiveDateTime::new(d, NaiveTime::from_hms_opt(0, 0, 0).unwrap()));
}

// Try YYYYMMDD
if s.len() == 8 {
if let Ok(d) = NaiveDate::parse_from_str(s, "%Y%m%d") {
return Some(NaiveDateTime::new(d, NaiveTime::from_hms_opt(0, 0, 0).unwrap()));
}
}

// Try compact datetime YYYYMMDDTHHMM
if s.len() == 13 {
if let Ok(ndt) = NaiveDateTime::parse_from_str(s, "%Y%m%dT%H%M") {
return Some(ndt);
}
}

None
}
}

impl Default for DateTimeCoordinates {
fn default() -> Self {
DateTimeCoordinates::List(TinyVec::new())
}
}

impl From<NaiveDateTime> for Coordinates {
fn from(value: NaiveDateTime) -> Self {
let mut vec = TinyVec::new();
vec.push(value);
Coordinates::DateTimes(DateTimeCoordinates::List(vec))
}
}

impl From<DateTimeCoordinates> for Coordinates {
fn from(value: DateTimeCoordinates) -> Self {
Coordinates::DateTimes(value)
}
}

impl From<&str> for DateTimeCoordinates {
fn from(value: &str) -> Self {
if let Some(ndt) = DateTimeCoordinates::parse_from_str(value) {
let mut vec = TinyVec::new();
vec.push(ndt);
DateTimeCoordinates::List(vec)
} else {
DateTimeCoordinates::default()
}
}
}

impl From<&[NaiveDateTime]> for Coordinates {
fn from(value: &[NaiveDateTime]) -> Self {
let mut vec = TinyVec::new();
for &v in value {
vec.push(v);
}
Coordinates::DateTimes(DateTimeCoordinates::List(vec))
}
}

impl<const N: usize> From<&[NaiveDateTime; N]> for Coordinates {
fn from(value: &[NaiveDateTime; N]) -> Self {
let mut vec = TinyVec::new();
for &v in value.iter() {
vec.push(v);
}
Coordinates::DateTimes(DateTimeCoordinates::List(vec))
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_datetime_append_and_len() {
let mut coords = DateTimeCoordinates::default();
let d1 = NaiveDate::from_ymd(2020, 1, 1).and_hms(0, 0, 0);
let d2 = NaiveDate::from_ymd(2020, 1, 2).and_hms(12, 30, 0);
coords.append(d1);
coords.append(d2);

match coords {
DateTimeCoordinates::List(list) => {
assert_eq!(list.len(), 2);
assert_eq!(list[0], d1);
assert_eq!(list[1], d2);
}
}
}

#[test]
fn test_datetime_extend() {
let mut a = DateTimeCoordinates::default();
let d1 = NaiveDate::from_ymd(2020, 1, 1).and_hms(0, 0, 0);
let d2 = NaiveDate::from_ymd(2020, 1, 2).and_hms(0, 0, 0);
a.append(d1);

let mut b = DateTimeCoordinates::default();
b.append(d2);

a.extend(&b);

match a {
DateTimeCoordinates::List(list) => {
assert_eq!(list.len(), 2);
assert_eq!(list[0], d1);
assert_eq!(list[1], d2);
}
}
}

#[test]
fn test_datetime_to_string_and_parse() {
let d = NaiveDate::from_ymd(2021, 5, 4).and_hms(6, 7, 8);
let mut c = DateTimeCoordinates::default();
c.append(d);
let s = c.to_string();
assert!(s.contains("2021-05-04T06:07:08"));

// parse from iso string
let parsed = DateTimeCoordinates::parse_from_str("2021-05-04T06:07:08Z");
assert!(parsed.is_some());
assert_eq!(parsed.unwrap(), d);
}

#[test]
fn test_datetime_intersect() {
let mut a = DateTimeCoordinates::default();
let d1 = NaiveDate::from_ymd(2020, 1, 1).and_hms(0, 0, 0);
let d2 = NaiveDate::from_ymd(2020, 1, 2).and_hms(0, 0, 0);
let d3 = NaiveDate::from_ymd(2020, 1, 3).and_hms(0, 0, 0);
a.append(d1);
a.append(d2);
a.append(d3);

let mut b = DateTimeCoordinates::default();
b.append(d2);
b.append(d3);
b.append(NaiveDate::from_ymd(2020, 1, 4).and_hms(0, 0, 0));

let res = a.intersect(&b);

match res.intersection {
DateTimeCoordinates::List(list) => {
assert_eq!(list.len(), 2);
assert_eq!(list[0], d2);
assert_eq!(list[1], d3);
}
}
}
}
Loading