use crate::generators::{DefaultGenerator, Generator, TestCase, hashsets, integers};
use crate::test_case::invalid_argument;
use chrono::{
DateTime, Datelike, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, NaiveWeek, TimeDelta,
TimeZone, Timelike, Utc, Weekday, WeekdaySet,
};
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 is_mid_day_leap(t: NaiveTime) -> bool {
t.nanosecond() >= 1_000_000_000 && t.num_seconds_from_midnight() != 86_399
}
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 Generator<WeekdaySet> for WeekdaySetGenerator {
fn do_draw(&self, tc: &TestCase) -> WeekdaySet {
let days = hashsets(integers::<u8>().min_value(0).max_value(6))
.max_size(7)
.do_draw(tc);
let mut set = WeekdaySet::EMPTY;
for n in days {
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
}
}
impl Generator<FixedOffset> for FixedOffsetGenerator {
fn do_draw(&self, tc: &TestCase) -> FixedOffset {
let min_secs = self.min_value.local_minus_utc();
let max_secs = self.max_value.local_minus_utc();
if min_secs > max_secs {
invalid_argument!("Cannot have max_value < min_value");
}
let secs = integers::<i32>()
.min_value(min_secs)
.max_value(max_secs)
.do_draw(tc);
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
}
}
impl Generator<TimeDelta> for TimeDeltaGenerator {
fn do_draw(&self, tc: &TestCase) -> TimeDelta {
if self.min_value > self.max_value {
invalid_argument!("Cannot have max_value < min_value");
}
let nanos = integers::<i128>()
.min_value(timedelta_to_nanos(self.min_value))
.max_value(timedelta_to_nanos(self.max_value))
.do_draw(tc);
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
}
}
impl Generator<NaiveDate> for NaiveDateGenerator {
fn do_draw(&self, tc: &TestCase) -> NaiveDate {
if self.min_value > self.max_value {
invalid_argument!("Cannot have max_value < min_value");
}
let d = tc.generate_date(hegel_date(self.min_value), hegel_date(self.max_value));
NaiveDate::from_ymd_opt(d.year, u32::from(d.month), u32::from(d.day)).unwrap()
}
}
fn hegel_date(d: NaiveDate) -> hegel_c::hegel_date_t {
hegel_c::hegel_date_t {
year: d.year(),
month: d.month() as u8,
day: d.day() as u8,
}
}
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
}
}
impl Generator<NaiveTime> for NaiveTimeGenerator {
fn do_draw(&self, tc: &TestCase) -> NaiveTime {
if is_mid_day_leap(self.min_value) || is_mid_day_leap(self.max_value) {
invalid_argument!(
"naive_times bounds using a leap second are only supported at 23:59:59"
);
}
let min_nanos = time_to_total_nanos(self.min_value);
let max_nanos = time_to_total_nanos(self.max_value);
if min_nanos > max_nanos {
invalid_argument!("Cannot have max_value < min_value");
}
let n = integers::<i64>()
.min_value(min_nanos)
.max_value(max_nanos)
.do_draw(tc);
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 do_draw(&self, tc: &TestCase) -> NaiveDateTime {
if self.min_value.time().nanosecond() >= 1_000_000_000
|| self.max_value.time().nanosecond() >= 1_000_000_000
{
invalid_argument!(
"naive_datetimes bounds cannot use a leap second: the epoch-nanosecond \
encoding maps a leap second into the following second"
);
}
if self.min_value > self.max_value {
invalid_argument!("Cannot have max_value < min_value");
}
let n = integers::<i128>()
.min_value(datetime_to_nanos(&self.min_value.and_utc()))
.max_value(datetime_to_nanos(&self.max_value.and_utc()))
.do_draw(tc);
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 do_draw(&self, tc: &TestCase) -> NaiveWeek {
let (date, start) = crate::generators::tuples2(&self.date_gen, &self.start_gen).do_draw(tc);
date.week(start)
}
}
pub fn naive_weeks() -> NaiveWeekGenerator {
NaiveWeekGenerator {
date_gen: naive_dates()
.min_value(
NaiveDate::MIN
.checked_add_days(chrono::Days::new(7))
.unwrap(),
)
.max_value(
NaiveDate::MAX
.checked_sub_days(chrono::Days::new(7))
.unwrap(),
),
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,
}
}