greg 0.1.2

WIP: datetime library
Documentation
use crate::{
	Point,
	Span,
	real::Scale,
	Frame,
	utc,
};
use crate::calendar::{
	Calendar,
	Utc,
	Weekday
};
use crate::calendar::zone::{
	Shift,
	Unsteady
};


struct FakeUnsteady;
impl Shift for FakeUnsteady {
	fn apply(&self, point: Point) -> Point {point}
}
impl Unsteady for FakeUnsteady {
	fn revert_to_earliest(&self, point: Point) -> Point {point}
}


fn floor_check(case: Point, expected: Point, scale: Scale) {
	let received = Calendar(Utc).date_floor(scale, case);
	assert_eq!(
		expected,
		received,
		"\n{} by {:?} should round down to {} but received {}",
		Utc::lookup(case),
		scale,
		Utc::lookup(expected),
		Utc::lookup(received)
	)
}

fn fmt_frame<Z: Shift>(cal: &Calendar<Z>, frame: Frame) -> String {
	let start = cal.lookup(frame.start);
	let stop = cal.lookup(frame.stop);
	format!("{start}{stop}")
}

#[test]
fn date_floor() {
	use crate::Span;

	const JAN_1_UTC: Point = utc!(2020-01-01);
	const JAN_2_UTC: Point = utc!(2020-01-02);
	const JAN_31_UTC: Point = utc!(2020-01-31);
	const DEC_31_UTC: Point = utc!(2020-12-31);



	for scale in [Scale::Years, Scale::Months] {
		floor_check(JAN_1_UTC, JAN_1_UTC, scale);
		floor_check(JAN_2_UTC, JAN_1_UTC, scale);
		floor_check(JAN_31_UTC, JAN_1_UTC, scale);
	}
	floor_check(DEC_31_UTC, JAN_1_UTC, Scale::Years);

	let all_scales = [
		Scale::Years,
		Scale::Months,
		Scale::Weeks,
		Scale::Days,
		Scale::Hours,
		Scale::Minutes,
		Scale::Seconds
	];
	/// This was a Monday
	const JAN_1_UTC_2018: Point = utc!(2018-01-01);

	for scale in all_scales {
		floor_check(JAN_1_UTC_2018, JAN_1_UTC_2018, scale);
	}

	/*
	 *	WEEK ROUNDING (SPECIAL)
	 */
	/// Monday
	const JAN_3_UTC: Point = utc!(2022-01-03);

	for days in 0..6 {
		let weekday = JAN_3_UTC + Span::from(Scale::Days) * days;
		floor_check(weekday, JAN_3_UTC, Scale::Weeks);
	}
	/// Another Monday
	const SEP_12_UTC: Point = utc!(2022-09-12);

	for days in 0..6 {
		let weekday = SEP_12_UTC + Span::from(Scale::Days) * days;
		floor_check(weekday, SEP_12_UTC, Scale::Weeks);
	}
}



#[test]
fn weekday() {
	use Weekday::*;
	let cases = [
		(utc!(1969-12-29), Monday),
		(utc!(1969-12-30), Tuesday),
		(utc!(1969-12-31), Wednesday),
		(utc!(1970-01-01), Thursday),
		(utc!(1970-01-02), Friday),
		(utc!(1970-01-03), Saturday),
		(utc!(1970-01-04), Sunday),

		(utc!(2018-01-01), Monday),
		(utc!(2018-01-02), Tuesday),
		(utc!(2018-01-03), Wednesday),
		(utc!(2018-01-04), Thursday),
		(utc!(2018-01-05), Friday),
		(utc!(2018-01-06), Saturday),
		(utc!(2018-01-07), Sunday),
		(utc!(2018-01-08), Monday),
	];
	for (point, day) in cases {
		assert_eq!(day, Calendar(Utc).weekday(point));
	}
}

#[test]
fn frames() {
	let cal = Calendar(FakeUnsteady);

	let frames = cal.frames(Scale::Weeks, Point::now());
	for f @ Frame {start, stop} in frames.take(10) {
		assert_eq!(cal.weekday(start), Weekday::Monday);
		assert_eq!(cal.weekday(stop), Weekday::Monday);
		assert_eq!(f.span(), Scale::Weeks.into());
		let start = cal.lookup(start);
		let stop = cal.lookup(stop);
		assert_eq!(start.1.as_seconds(), 0);
		assert_eq!(stop.1.as_seconds(), 0);
		println!("{start}{stop}");
	}

	let all_scales_and_spans = [
		(Scale::Years, &[Span::DAY * 365, Span::DAY * 366][..]),
		(
			Scale::Months,
			&[
				Span::DAY * 28,
				Span::DAY * 29,
				Span::DAY * 30,
				Span::DAY * 31
			]
		),
		(Scale::Weeks, &[Span::WEEK]),
		(Scale::Days, &[Span::DAY]),
		(Scale::Hours, &[Span::HOUR]),
		(Scale::Minutes, &[Span::MINUTE]),
		(Scale::Seconds, &[Span::SECOND])
	];

	for (scale, valid_spans) in all_scales_and_spans {
		println!("\nScale: {scale:12?} | Start: {}", cal.lookup(Point::now()));
		let mut frames = cal.frames(scale, Point::now());

		assert_eq!(
			frames.next().unwrap(),
			frames.prev(),
			"next != prev for Scale::{scale:?}"
		);
		let _ = frames.next();
		assert_eq!(
			frames.next().unwrap(),
			frames.prev(),
			"next != prev for Scale::{scale:?}"
		);
		let _ = frames.prev();

		for (i, f @ Frame {start, stop}) in frames.take(10).enumerate() {
			println!("{i} | {}", fmt_frame(&cal, f));
			assert!(
				valid_spans.contains(&f.span()),
				"Incorrect frame duration {}",
				f.span()
			);
			assert_eq!(
				cal.date_floor_earliest(scale, start),
				start,
				"\nstart isn't date_floored: got {}, date_floor {}",
				cal.lookup(start),
				cal.lookup(cal.date_floor_earliest(scale, start))
			);
			assert_eq!(
				cal.date_floor_earliest(scale, stop),
				stop,
				"\nstop isn't date_floored: got {}, date_floor {}",
				cal.lookup(stop),
				cal.lookup(cal.date_floor_earliest(scale, stop))
			);
		}
	}
}

#[test]
fn frames_rev() {
	let cal = Calendar(FakeUnsteady);

	let frames = cal.frames(Scale::Weeks, Point::now()).rev();
	for f @ Frame {start, stop} in frames.take(10) {
		assert_eq!(cal.weekday(start), Weekday::Monday);
		assert_eq!(cal.weekday(stop), Weekday::Monday);
		assert_eq!(f.span(), Scale::Weeks.into());
		let start = cal.lookup(start);
		let stop = cal.lookup(stop);
		assert_eq!(start.1.as_seconds(), 0);
		assert_eq!(stop.1.as_seconds(), 0);
		println!("{start}{stop}");
	}

	let all_scales_and_spans = [
		(Scale::Years, &[Span::DAY * 365, Span::DAY * 366][..]),
		(
			Scale::Months,
			&[
				Span::DAY * 28,
				Span::DAY * 29,
				Span::DAY * 30,
				Span::DAY * 31
			]
		),
		(Scale::Weeks, &[Span::WEEK]),
		(Scale::Days, &[Span::DAY]),
		(Scale::Hours, &[Span::HOUR]),
		(Scale::Minutes, &[Span::MINUTE]),
		(Scale::Seconds, &[Span::SECOND])
	];

	for (scale, valid_spans) in all_scales_and_spans {
		println!("\nScale: {scale:12?} | Start: {}", cal.lookup(Point::now()));
		let frames = cal.frames(scale, Point::now()).rev();

		for (i, f @ Frame {start, stop}) in frames.take(10).enumerate() {
			println!("{i} | {}", fmt_frame(&cal, f));
			assert!(
				valid_spans.contains(&f.span()),
				"Incorrect frame duration {}",
				f.span()
			);
			assert_eq!(
				cal.date_floor_earliest(scale, start),
				start,
				"\nstart isn't date_floored: got {}, date_floor {}",
				cal.lookup(start),
				cal.lookup(cal.date_floor_earliest(scale, start))
			);
			assert_eq!(
				cal.date_floor_earliest(scale, stop),
				stop,
				"\nstop isn't date_floored: got {}, date_floor {}",
				cal.lookup(stop),
				cal.lookup(cal.date_floor_earliest(scale, stop))
			);
		}
	}
}