use jiff::civil::{Date, DateTime, Time};
use jiff::tz::{Offset, TimeZone};
use jiff::{SignedDuration, Span, Timestamp, Zoned};
use crate::ffi::sys as hegel_c;
use crate::generators::{
BoxedGenerator, Generator, PrintableGenerator, TestCase, combine_labels, integers,
label_from_name,
};
use crate::pretty::PrettyPrinter;
const DATE_LABEL: u64 = label_from_name("hegel.jiff.dates");
const TIME_LABEL: u64 = label_from_name("hegel.jiff.times");
const DATETIME_LABEL: u64 = label_from_name("hegel.jiff.datetimes");
const TIMESTAMP_LABEL: u64 = label_from_name("hegel.jiff.timestamps");
const SPAN_LABEL: u64 = label_from_name("hegel.jiff.spans");
const SIGNED_DURATION_LABEL: u64 = label_from_name("hegel.jiff.signed_durations");
const OFFSET_LABEL: u64 = label_from_name("hegel.jiff.offsets");
const ZONED_LABEL: u64 = label_from_name("hegel.jiff.zoneds");
use crate::test_case::invalid_argument;
fn hegel_date(d: Date) -> hegel_c::hegel_date_t {
hegel_c::hegel_date_t {
year: i32::from(d.year()),
month: d.month() as u8,
day: d.day() as u8,
}
}
pub struct DateGenerator {
min_value: Date,
max_value: Date,
}
impl DateGenerator {
pub fn min_value(mut self, min: Date) -> Self {
self.min_value = min;
self
}
pub fn max_value(mut self, max: Date) -> Self {
self.max_value = max;
self
}
}
impl Generator<Date> for DateGenerator {
fn label(&self) -> u64 {
DATE_LABEL
}
fn do_draw(&self, tc: &TestCase) -> Date {
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));
Date::new(d.year as i16, d.month as i8, d.day as i8).unwrap()
}
}
impl PrintableGenerator<Date> for DateGenerator {
fn do_draw_and_print(&self, tc: &TestCase, printer: &mut PrettyPrinter) -> Date {
crate::generators::draw_and_print_value(self, tc, printer)
}
}
pub fn dates() -> DateGenerator {
DateGenerator {
min_value: Date::constant(1, 1, 1),
max_value: Date::constant(9999, 12, 31),
}
}
fn hegel_time(t: Time) -> hegel_c::hegel_time_t {
hegel_c::hegel_time_t {
hour: t.hour() as u8,
minute: t.minute() as u8,
second: t.second() as u8,
nanosecond: t.subsec_nanosecond() as u32,
}
}
pub struct TimeGenerator {
min_value: Time,
max_value: Time,
}
impl TimeGenerator {
pub fn min_value(mut self, min: Time) -> Self {
self.min_value = min;
self
}
pub fn max_value(mut self, max: Time) -> Self {
self.max_value = max;
self
}
}
impl Generator<Time> for TimeGenerator {
fn label(&self) -> u64 {
TIME_LABEL
}
fn do_draw(&self, tc: &TestCase) -> Time {
if self.min_value > self.max_value {
invalid_argument!("Cannot have max_value < min_value");
}
let t = tc.generate_time(hegel_time(self.min_value), hegel_time(self.max_value));
Time::new(
t.hour as i8,
t.minute as i8,
t.second as i8,
t.nanosecond as i32,
)
.unwrap()
}
}
impl PrintableGenerator<Time> for TimeGenerator {
fn do_draw_and_print(&self, tc: &TestCase, printer: &mut PrettyPrinter) -> Time {
crate::generators::draw_and_print_value(self, tc, printer)
}
}
pub fn times() -> TimeGenerator {
TimeGenerator {
min_value: Time::MIN,
max_value: Time::MAX,
}
}
fn datetime_to_nanos(dt: DateTime) -> i128 {
dt.to_zoned(TimeZone::UTC)
.expect("DateTime bound out of jiff::Timestamp range")
.timestamp()
.as_nanosecond()
}
fn nanos_to_datetime(n: i128) -> DateTime {
Timestamp::from_nanosecond(n)
.unwrap()
.to_zoned(TimeZone::UTC)
.datetime()
}
pub struct DateTimeGenerator {
min_value: DateTime,
max_value: DateTime,
}
impl DateTimeGenerator {
pub fn min_value(mut self, min: DateTime) -> Self {
self.min_value = min;
self
}
pub fn max_value(mut self, max: DateTime) -> Self {
self.max_value = max;
self
}
}
impl Generator<DateTime> for DateTimeGenerator {
fn label(&self) -> u64 {
DATETIME_LABEL
}
fn do_draw(&self, tc: &TestCase) -> DateTime {
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))
.max_value(datetime_to_nanos(self.max_value))
.do_draw(tc);
nanos_to_datetime(n)
}
}
impl PrintableGenerator<DateTime> for DateTimeGenerator {
fn do_draw_and_print(&self, tc: &TestCase, printer: &mut PrettyPrinter) -> DateTime {
crate::generators::draw_and_print_value(self, tc, printer)
}
}
pub fn datetimes() -> DateTimeGenerator {
DateTimeGenerator {
min_value: Timestamp::MIN.to_zoned(TimeZone::UTC).datetime(),
max_value: Timestamp::MAX.to_zoned(TimeZone::UTC).datetime(),
}
}
pub struct TimestampGenerator {
min_value: Timestamp,
max_value: Timestamp,
}
impl TimestampGenerator {
pub fn min_value(mut self, min: Timestamp) -> Self {
self.min_value = min;
self
}
pub fn max_value(mut self, max: Timestamp) -> Self {
self.max_value = max;
self
}
}
impl Generator<Timestamp> for TimestampGenerator {
fn label(&self) -> u64 {
TIMESTAMP_LABEL
}
fn do_draw(&self, tc: &TestCase) -> Timestamp {
if self.min_value > self.max_value {
invalid_argument!("Cannot have max_value < min_value");
}
let nanos = integers::<i128>()
.min_value(self.min_value.as_nanosecond())
.max_value(self.max_value.as_nanosecond())
.do_draw(tc);
Timestamp::from_nanosecond(nanos).unwrap()
}
}
impl PrintableGenerator<Timestamp> for TimestampGenerator {
fn do_draw_and_print(&self, tc: &TestCase, printer: &mut PrettyPrinter) -> Timestamp {
crate::generators::draw_and_print_value(self, tc, printer)
}
}
pub fn timestamps() -> TimestampGenerator {
TimestampGenerator {
min_value: Timestamp::MIN,
max_value: Timestamp::MAX,
}
}
const SPAN_NANOS_LIMIT: i64 = i64::MAX;
pub struct SpanGenerator {
min_nanos: i64,
max_nanos: i64,
}
impl SpanGenerator {
pub fn min_nanoseconds(mut self, min: i64) -> Self {
self.min_nanos = min;
self
}
pub fn max_nanoseconds(mut self, max: i64) -> Self {
self.max_nanos = max;
self
}
}
impl Generator<Span> for SpanGenerator {
fn label(&self) -> u64 {
SPAN_LABEL
}
fn do_draw(&self, tc: &TestCase) -> Span {
if self.min_nanos > self.max_nanos {
invalid_argument!("Cannot have max_nanoseconds < min_nanoseconds");
}
if self.min_nanos < -SPAN_NANOS_LIMIT {
invalid_argument!("min_nanoseconds must be >= -i64::MAX (the Span nanosecond limit)");
}
let nanos = integers::<i64>()
.min_value(self.min_nanos)
.max_value(self.max_nanos)
.do_draw(tc);
Span::new().try_nanoseconds(nanos).unwrap()
}
}
impl PrintableGenerator<Span> for SpanGenerator {
fn do_draw_and_print(&self, tc: &TestCase, printer: &mut PrettyPrinter) -> Span {
crate::generators::draw_and_print_value(self, tc, printer)
}
}
pub fn spans() -> SpanGenerator {
SpanGenerator {
min_nanos: -SPAN_NANOS_LIMIT,
max_nanos: SPAN_NANOS_LIMIT,
}
}
fn nanos_to_signed_duration(n: i128) -> SignedDuration {
let secs = (n / 1_000_000_000) as i64;
let subnanos = (n % 1_000_000_000) as i32;
SignedDuration::new(secs, subnanos)
}
pub struct SignedDurationGenerator {
min_value: SignedDuration,
max_value: SignedDuration,
}
impl SignedDurationGenerator {
pub fn min_value(mut self, min: SignedDuration) -> Self {
self.min_value = min;
self
}
pub fn max_value(mut self, max: SignedDuration) -> Self {
self.max_value = max;
self
}
}
impl Generator<SignedDuration> for SignedDurationGenerator {
fn label(&self) -> u64 {
SIGNED_DURATION_LABEL
}
fn do_draw(&self, tc: &TestCase) -> SignedDuration {
if self.min_value > self.max_value {
invalid_argument!("Cannot have max_value < min_value");
}
let nanos = integers::<i128>()
.min_value(self.min_value.as_nanos())
.max_value(self.max_value.as_nanos())
.do_draw(tc);
nanos_to_signed_duration(nanos)
}
}
impl PrintableGenerator<SignedDuration> for SignedDurationGenerator {
fn do_draw_and_print(&self, tc: &TestCase, printer: &mut PrettyPrinter) -> SignedDuration {
crate::generators::draw_and_print_value(self, tc, printer)
}
}
pub fn signed_durations() -> SignedDurationGenerator {
SignedDurationGenerator {
min_value: SignedDuration::MIN,
max_value: SignedDuration::MAX,
}
}
const OFFSET_MIN_SECS: i32 = -93_599;
const OFFSET_MAX_SECS: i32 = 93_599;
pub struct OffsetGenerator {
min_value: Offset,
max_value: Offset,
}
impl OffsetGenerator {
pub fn min_value(mut self, min: Offset) -> Self {
self.min_value = min;
self
}
pub fn max_value(mut self, max: Offset) -> Self {
self.max_value = max;
self
}
}
impl Generator<Offset> for OffsetGenerator {
fn label(&self) -> u64 {
OFFSET_LABEL
}
fn do_draw(&self, tc: &TestCase) -> Offset {
if self.min_value.seconds() > self.max_value.seconds() {
invalid_argument!("Cannot have max_value < min_value");
}
let secs = integers::<i32>()
.min_value(self.min_value.seconds())
.max_value(self.max_value.seconds())
.do_draw(tc);
Offset::from_seconds(secs).unwrap()
}
}
impl PrintableGenerator<Offset> for OffsetGenerator {
fn do_draw_and_print(&self, tc: &TestCase, printer: &mut PrettyPrinter) -> Offset {
crate::generators::draw_and_print_value(self, tc, printer)
}
}
pub fn offsets() -> OffsetGenerator {
OffsetGenerator {
min_value: Offset::from_seconds(OFFSET_MIN_SECS).unwrap(),
max_value: Offset::from_seconds(OFFSET_MAX_SECS).unwrap(),
}
}
pub struct ZonedGenerator<TS = TimestampGenerator, TZ = BoxedGenerator<'static, TimeZone>> {
timestamp_gen: TS,
timezone_gen: TZ,
label: u64,
}
impl<TS, TZ> ZonedGenerator<TS, TZ> {
fn new(timestamp_gen: TS, timezone_gen: TZ) -> Self
where
TS: Generator<Timestamp>,
TZ: Generator<TimeZone>,
{
ZonedGenerator {
label: combine_labels(&[ZONED_LABEL, timestamp_gen.label(), timezone_gen.label()]),
timestamp_gen,
timezone_gen,
}
}
pub fn timestamps<TS2>(self, timestamp_gen: TS2) -> ZonedGenerator<TS2, TZ>
where
TS2: Generator<Timestamp>,
TZ: Generator<TimeZone>,
{
ZonedGenerator::new(timestamp_gen, self.timezone_gen)
}
pub fn timezones<TZ2>(self, timezone_gen: TZ2) -> ZonedGenerator<TS, TZ2>
where
TS: Generator<Timestamp>,
TZ2: Generator<TimeZone>,
{
ZonedGenerator::new(self.timestamp_gen, timezone_gen)
}
}
impl<TS, TZ> Generator<Zoned> for ZonedGenerator<TS, TZ>
where
TS: Generator<Timestamp>,
TZ: Generator<TimeZone>,
{
fn label(&self) -> u64 {
self.label
}
fn do_draw(&self, tc: &TestCase) -> Zoned {
let (ts, tz) = tc.draw_silent(crate::generators::tuples2(
&self.timestamp_gen,
&self.timezone_gen,
));
Zoned::new(ts, tz)
}
}
impl<TS, TZ> PrintableGenerator<Zoned> for ZonedGenerator<TS, TZ>
where
TS: Generator<Timestamp>,
TZ: Generator<TimeZone>,
{
fn do_draw_and_print(&self, tc: &TestCase, printer: &mut PrettyPrinter) -> Zoned {
crate::generators::draw_and_print_value(self, tc, printer)
}
}
pub fn zoneds() -> ZonedGenerator {
use crate::generators::DefaultGenerator;
ZonedGenerator::new(
timestamps(),
<TimeZone as DefaultGenerator>::default_generator(),
)
}