use crate::cbor_utils::{cbor_array, cbor_map};
use crate::generators::{BasicGenerator, DefaultGenerator, Generator, TestCase};
use chrono::{
DateTime, Datelike, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, NaiveWeek, TimeDelta,
TimeZone, Timelike, Utc, Weekday, WeekdaySet,
};
use ciborium::Value;
use std::marker::PhantomData;
fn time_to_total_nanos(t: NaiveTime) -> i64 {
let secs = i64::from(t.num_seconds_from_midnight());
let nanos = i64::from(t.nanosecond());
secs * 1_000_000_000 + nanos
}
fn total_nanos_to_time(total: i64) -> NaiveTime {
let (secs, nanos) = if total >= 86_400 * 1_000_000_000 {
(86_399, (total - 86_399 * 1_000_000_000) as u32)
} else {
(
(total / 1_000_000_000) as u32,
(total % 1_000_000_000) as u32,
)
};
NaiveTime::from_num_seconds_from_midnight_opt(secs, nanos).unwrap()
}
fn naive_time_default_max() -> NaiveTime {
NaiveTime::from_hms_nano_opt(23, 59, 59, 999_999_999).unwrap()
}
pub struct WeekdaySetGenerator;
impl WeekdaySetGenerator {
fn build_schema(&self) -> Value {
cbor_map! {
"type" => "list",
"unique" => true,
"elements" => cbor_map! {
"type" => "integer",
"min_value" => 0u64,
"max_value" => 6u64,
},
"min_size" => 0u64,
"max_size" => 7u64,
}
}
}
impl Generator<WeekdaySet> for WeekdaySetGenerator {
fn as_basic(&self) -> Option<BasicGenerator<'_, WeekdaySet>> {
Some(BasicGenerator::new(self.build_schema(), |raw| {
let arr = raw.into_array().unwrap();
let mut set = WeekdaySet::EMPTY;
for v in arr {
let n: u8 = crate::generators::deserialize_value(v);
set.insert(Weekday::try_from(n).unwrap());
}
set
}))
}
}
pub fn weekday_sets() -> WeekdaySetGenerator {
WeekdaySetGenerator
}
pub struct FixedOffsetGenerator {
min_value: FixedOffset,
max_value: FixedOffset,
}
impl FixedOffsetGenerator {
pub fn min_value(mut self, min: FixedOffset) -> Self {
self.min_value = min;
self
}
pub fn max_value(mut self, max: FixedOffset) -> Self {
self.max_value = max;
self
}
fn build_schema(&self) -> Value {
let min_secs = self.min_value.local_minus_utc();
let max_secs = self.max_value.local_minus_utc();
assert!(min_secs <= max_secs, "Cannot have max_value < min_value");
cbor_map! {
"type" => "integer",
"min_value" => i64::from(min_secs),
"max_value" => i64::from(max_secs),
}
}
}
impl Generator<FixedOffset> for FixedOffsetGenerator {
fn as_basic(&self) -> Option<BasicGenerator<'_, FixedOffset>> {
Some(BasicGenerator::new(self.build_schema(), |raw| {
let secs: i32 = crate::generators::deserialize_value(raw);
FixedOffset::east_opt(secs).unwrap()
}))
}
}
pub fn fixed_offsets() -> FixedOffsetGenerator {
FixedOffsetGenerator {
min_value: FixedOffset::west_opt(86_399).unwrap(),
max_value: FixedOffset::east_opt(86_399).unwrap(),
}
}
fn timedelta_to_nanos(d: TimeDelta) -> i128 {
i128::from(d.num_seconds()) * 1_000_000_000 + i128::from(d.subsec_nanos())
}
fn nanos_to_timedelta(n: i128) -> TimeDelta {
let secs = n.div_euclid(1_000_000_000) as i64;
let nanos = n.rem_euclid(1_000_000_000) as u32;
TimeDelta::new(secs, nanos).unwrap()
}
pub struct TimeDeltaGenerator {
min_value: TimeDelta,
max_value: TimeDelta,
}
impl TimeDeltaGenerator {
pub fn min_value(mut self, min: TimeDelta) -> Self {
self.min_value = min;
self
}
pub fn max_value(mut self, max: TimeDelta) -> Self {
self.max_value = max;
self
}
fn build_schema(&self) -> Value {
assert!(
self.min_value <= self.max_value,
"Cannot have max_value < min_value"
);
cbor_map! {
"type" => "integer",
"min_value" => timedelta_to_nanos(self.min_value),
"max_value" => timedelta_to_nanos(self.max_value),
}
}
}
impl Generator<TimeDelta> for TimeDeltaGenerator {
fn as_basic(&self) -> Option<BasicGenerator<'_, TimeDelta>> {
Some(BasicGenerator::new(self.build_schema(), |raw| {
let nanos: i128 = crate::generators::deserialize_value(raw);
nanos_to_timedelta(nanos)
}))
}
}
pub fn time_deltas() -> TimeDeltaGenerator {
TimeDeltaGenerator {
min_value: TimeDelta::MIN,
max_value: TimeDelta::MAX,
}
}
pub struct NaiveDateGenerator {
min_value: NaiveDate,
max_value: NaiveDate,
}
impl NaiveDateGenerator {
pub fn min_value(mut self, min: NaiveDate) -> Self {
self.min_value = min;
self
}
pub fn max_value(mut self, max: NaiveDate) -> Self {
self.max_value = max;
self
}
fn build_schema(&self) -> Value {
assert!(
self.min_value <= self.max_value,
"Cannot have max_value < min_value"
);
cbor_map! {
"type" => "integer",
"min_value" => self.min_value.num_days_from_ce(),
"max_value" => self.max_value.num_days_from_ce(),
}
}
}
impl Generator<NaiveDate> for NaiveDateGenerator {
fn as_basic(&self) -> Option<BasicGenerator<'_, NaiveDate>> {
Some(BasicGenerator::new(self.build_schema(), |raw| {
let n: i32 = crate::generators::deserialize_value(raw);
NaiveDate::from_num_days_from_ce_opt(n).unwrap()
}))
}
}
pub fn naive_dates() -> NaiveDateGenerator {
NaiveDateGenerator {
min_value: NaiveDate::MIN,
max_value: NaiveDate::MAX,
}
}
pub struct NaiveTimeGenerator {
min_value: NaiveTime,
max_value: NaiveTime,
}
impl NaiveTimeGenerator {
pub fn min_value(mut self, min: NaiveTime) -> Self {
self.min_value = min;
self
}
pub fn max_value(mut self, max: NaiveTime) -> Self {
self.max_value = max;
self
}
fn build_schema(&self) -> Value {
let min_nanos = time_to_total_nanos(self.min_value);
let max_nanos = time_to_total_nanos(self.max_value);
assert!(min_nanos <= max_nanos, "Cannot have max_value < min_value");
cbor_map! {
"type" => "integer",
"min_value" => min_nanos,
"max_value" => max_nanos,
}
}
}
impl Generator<NaiveTime> for NaiveTimeGenerator {
fn as_basic(&self) -> Option<BasicGenerator<'_, NaiveTime>> {
Some(BasicGenerator::new(self.build_schema(), |raw| {
let n: i64 = crate::generators::deserialize_value(raw);
total_nanos_to_time(n)
}))
}
}
pub fn naive_times() -> NaiveTimeGenerator {
NaiveTimeGenerator {
min_value: NaiveTime::MIN,
max_value: naive_time_default_max(),
}
}
pub struct NaiveDateTimeGenerator {
min_value: NaiveDateTime,
max_value: NaiveDateTime,
}
impl NaiveDateTimeGenerator {
pub fn min_value(mut self, min: NaiveDateTime) -> Self {
self.min_value = min;
self
}
pub fn max_value(mut self, max: NaiveDateTime) -> Self {
self.max_value = max;
self
}
}
impl Generator<NaiveDateTime> for NaiveDateTimeGenerator {
fn as_basic(&self) -> Option<BasicGenerator<'_, NaiveDateTime>> {
assert!(
self.min_value <= self.max_value,
"Cannot have max_value < min_value"
);
let schema = cbor_map! {
"type" => "integer",
"min_value" => datetime_to_nanos(&self.min_value.and_utc()),
"max_value" => datetime_to_nanos(&self.max_value.and_utc()),
};
Some(BasicGenerator::new(schema, |raw| {
let n: i128 = crate::generators::deserialize_value(raw);
nanos_to_utc_datetime(n).naive_utc()
}))
}
}
pub fn naive_datetimes() -> NaiveDateTimeGenerator {
NaiveDateTimeGenerator {
min_value: DateTime::<Utc>::MIN_UTC.naive_utc(),
max_value: DateTime::<Utc>::MAX_UTC.naive_utc(),
}
}
pub struct NaiveWeekGenerator<S = <Weekday as DefaultGenerator>::Generator> {
date_gen: NaiveDateGenerator,
start_gen: S,
}
impl<S> NaiveWeekGenerator<S> {
pub fn min_date(mut self, min: NaiveDate) -> Self {
self.date_gen = self.date_gen.min_value(min);
self
}
pub fn max_date(mut self, max: NaiveDate) -> Self {
self.date_gen = self.date_gen.max_value(max);
self
}
pub fn weekday_starts<S2>(self, start_gen: S2) -> NaiveWeekGenerator<S2>
where
S2: Generator<Weekday>,
{
NaiveWeekGenerator {
date_gen: self.date_gen,
start_gen,
}
}
}
impl<S: Generator<Weekday>> Generator<NaiveWeek> for NaiveWeekGenerator<S> {
fn as_basic(&self) -> Option<BasicGenerator<'_, NaiveWeek>> {
let date_basic = self.date_gen.as_basic()?;
let start_basic = self.start_gen.as_basic()?;
let schema = cbor_map! {
"type" => "tuple",
"elements" => cbor_array![
date_basic.schema().clone(),
start_basic.schema().clone(),
],
};
Some(BasicGenerator::new(schema, move |raw| {
let [d_raw, s_raw]: [Value; 2] = raw.into_array().unwrap().try_into().unwrap();
let date = date_basic.parse_raw(d_raw);
let start = start_basic.parse_raw(s_raw);
date.week(start)
}))
}
}
pub fn naive_weeks() -> NaiveWeekGenerator {
NaiveWeekGenerator {
date_gen: naive_dates(),
start_gen: Weekday::default_generator(),
}
}
fn datetime_to_nanos<Tz: TimeZone>(dt: &DateTime<Tz>) -> i128 {
i128::from(dt.timestamp()) * 1_000_000_000 + i128::from(dt.timestamp_subsec_nanos())
}
fn nanos_to_utc_datetime(n: i128) -> DateTime<Utc> {
let secs = n.div_euclid(1_000_000_000) as i64;
let nsecs = n.rem_euclid(1_000_000_000) as u32;
DateTime::<Utc>::from_timestamp(secs, nsecs).unwrap()
}
pub struct DateTimeGenerator<G = FixedOffsetGenerator, Tz: TimeZone = FixedOffset> {
tz_gen: G,
min_value: NaiveDateTime,
max_value: NaiveDateTime,
_phantom: PhantomData<fn() -> Tz>,
}
impl<G, Tz: TimeZone> DateTimeGenerator<G, Tz> {
pub fn min_value(mut self, min: NaiveDateTime) -> Self {
self.min_value = min;
self
}
pub fn max_value(mut self, max: NaiveDateTime) -> Self {
self.max_value = max;
self
}
pub fn timezones<G2, Tz2>(self, tz_gen: G2) -> DateTimeGenerator<G2, Tz2>
where
G2: Generator<Tz2>,
Tz2: TimeZone,
{
DateTimeGenerator {
tz_gen,
min_value: self.min_value,
max_value: self.max_value,
_phantom: PhantomData,
}
}
}
impl<G, Tz> Generator<DateTime<Tz>> for DateTimeGenerator<G, Tz>
where
G: Generator<Tz>,
Tz: TimeZone + Send + Sync + 'static,
{
fn do_draw(&self, tc: &TestCase) -> DateTime<Tz> {
let naive = naive_datetimes()
.min_value(self.min_value)
.max_value(self.max_value)
.do_draw(tc);
let tz = self.tz_gen.do_draw(tc);
match tz.from_local_datetime(&naive).earliest() {
Some(dt) => dt,
None => {
tc.assume(false);
unreachable!()
}
}
}
}
pub fn datetimes() -> DateTimeGenerator<FixedOffsetGenerator, FixedOffset> {
DateTimeGenerator {
tz_gen: fixed_offsets(),
min_value: DateTime::<Utc>::MIN_UTC.naive_utc(),
max_value: DateTime::<Utc>::MAX_UTC.naive_utc(),
_phantom: PhantomData,
}
}