diff --git a/src/main.rs b/src/main.rs
index b112c11..16f2c3a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -17,13 +17,13 @@
*/
use chrono::NaiveDate;
-use libdrcr::reporting::{
- builders::register_dynamic_builders,
- calculator::solve_for,
- steps::{
- register_lookup_fns, AllTransactionsExceptRetainedEarnings,
- AllTransactionsIncludingRetainedEarnings, CalculateIncomeTax,
- },
+use libdrcr::reporting::builders::register_dynamic_builders;
+use libdrcr::reporting::calculator::steps_for_targets;
+use libdrcr::reporting::steps::{
+ register_lookup_fns, AllTransactionsExceptRetainedEarnings,
+ AllTransactionsIncludingRetainedEarnings, CalculateIncomeTax,
+};
+use libdrcr::reporting::types::{
DateArgs, DateStartDateEndArgs, ReportingContext, ReportingProductKind, ReportingStep,
};
@@ -44,7 +44,7 @@ fn main() {
];
println!("For income statement:");
- match solve_for(targets, context) {
+ match steps_for_targets(targets, context) {
Ok(steps) => {
for step in steps {
println!("- {}", step);
@@ -67,7 +67,7 @@ fn main() {
];
println!("For balance sheet:");
- match solve_for(targets, context) {
+ match steps_for_targets(targets, context) {
Ok(steps) => {
for step in steps {
println!("- {}", step);
diff --git a/src/reporting/builders.rs b/src/reporting/builders.rs
index 614f749..15bb630 100644
--- a/src/reporting/builders.rs
+++ b/src/reporting/builders.rs
@@ -18,8 +18,8 @@
use std::fmt::Display;
-use super::{
- calculator::{has_step_or_can_build, HasStepOrCanBuild, ReportingGraphDependencies},
+use super::calculator::{has_step_or_can_build, HasStepOrCanBuild, ReportingGraphDependencies};
+use super::types::{
DateArgs, DateStartDateEndArgs, ReportingContext, ReportingProductId, ReportingProductKind,
ReportingStep, ReportingStepArgs, ReportingStepDynamicBuilder, ReportingStepId,
};
diff --git a/src/reporting/calculator.rs b/src/reporting/calculator.rs
index a06369b..47f9c0e 100644
--- a/src/reporting/calculator.rs
+++ b/src/reporting/calculator.rs
@@ -16,50 +16,37 @@
along with this program. If not, see .
*/
-use super::{
+//! This module implements the dependency resolution for [ReportingStep]s
+
+use super::types::{
ReportingContext, ReportingProductId, ReportingStep, ReportingStepDynamicBuilder,
ReportingStepFromArgsFn, ReportingStepId,
};
+/// List of dependencies between [ReportingStep]s and [ReportingProduct]s
#[derive(Debug)]
pub struct ReportingGraphDependencies {
vec: Vec,
}
impl ReportingGraphDependencies {
+ /// Get the list of [Dependency]s
pub fn vec(&self) -> &Vec {
&self.vec
}
- pub fn add_dependency(&mut self, step: ReportingStepId, dependency: ReportingProductId) {
+ /// Record that the [ReportingStep] depends on the [ReportingProduct]
+ pub fn add_dependency(&mut self, step: ReportingStepId, product: ReportingProductId) {
if !self
.vec
.iter()
- .any(|d| d.step == step && d.product == dependency)
+ .any(|d| d.step == step && d.product == product)
{
- self.vec.push(Dependency { step, product: dependency });
+ self.vec.push(Dependency { step, product });
}
}
- /*pub fn add_target_dependency(&mut self, target: ReportingStepId, dependency: ReportingStepId) {
- for kind in target.product_kinds {
- match kind {
- ReportingProductKind::Transactions | ReportingProductKind::BalancesBetween => {
- self.add_dependency(
- target.clone(),
- ReportingProductId {
- name: dependency.name,
- kind: *kind,
- args: target.args.clone(),
- },
- );
- }
- ReportingProductKind::BalancesAt => todo!(),
- ReportingProductKind::Generic => todo!(),
- }
- }
- }*/
-
+ /// Get the [Dependency]s for the given [ReportingStep]
pub fn dependencies_for_step(&self, step: &ReportingStepId) -> Vec<&Dependency> {
return self.vec.iter().filter(|d| d.step == *step).collect();
}
@@ -72,6 +59,7 @@ pub struct Dependency {
pub product: ReportingProductId,
}
+/// Indicates an error during dependency resolution in [steps_for_targets]
#[derive(Debug)]
pub enum ReportingCalculationError {
UnknownStep { message: String },
@@ -86,6 +74,7 @@ pub enum HasStepOrCanBuild<'a, 'b> {
None,
}
+/// Determines whether the [ReportingProduct] is generated by a known step, or can be generated by a lookup function or dynamic builder
pub fn has_step_or_can_build<'a, 'b>(
product: &ReportingProductId,
steps: &'a Vec>,
@@ -129,23 +118,15 @@ pub fn has_step_or_can_build<'a, 'b>(
return HasStepOrCanBuild::None;
}
+/// Check whether the [ReportingStep] would be ready to execute, if the given previous steps have already completed
fn would_be_ready_to_execute(
step: &Box,
steps: &Vec>,
dependencies: &ReportingGraphDependencies,
previous_steps: &Vec,
) -> bool {
- //println!(
- // "- would_be_ready_to_execute: {}, {:?}",
- // step.id(),
- // previous_steps
- //);
-
- // Check whether the step would be ready to execute, if the previous steps have already completed
'check_each_dependency: for dependency in dependencies.vec.iter() {
if dependency.step == step.id() {
- //println!("-- {}", dependency.dependency);
-
// Check if the dependency has been produced by a previous step
for previous_step in previous_steps {
if steps[*previous_step].id().name == dependency.product.name
@@ -166,7 +147,8 @@ fn would_be_ready_to_execute(
true
}
-pub fn solve_for(
+/// Recursively resolve the dependencies of the target [ReportingStep]s and return a sorted [Vec] of [ReportingStep]s
+pub fn steps_for_targets(
targets: Vec>,
context: ReportingContext,
) -> Result>, ReportingCalculationError> {
@@ -180,12 +162,15 @@ pub fn solve_for(
for dependency in target.requires(&context) {
dependencies.add_dependency(target.id(), dependency);
}
- target.as_ref().init_graph(&steps, &mut dependencies, &context);
+ target
+ .as_ref()
+ .init_graph(&steps, &mut dependencies, &context);
}
// Call after_init_graph on targets
for step in steps.iter() {
- step.as_ref().after_init_graph(&steps, &mut dependencies, &context);
+ step.as_ref()
+ .after_init_graph(&steps, &mut dependencies, &context);
}
// Process dependencies
@@ -204,8 +189,7 @@ pub fn solve_for(
}) {
// Try lookup function
if let Some(lookup_key) = context.step_lookup_fn.keys().find(|(name, kinds)| {
- *name == dependency.product.name
- && kinds.contains(&dependency.product.kind)
+ *name == dependency.product.name && kinds.contains(&dependency.product.kind)
}) {
let (takes_args_fn, from_args_fn) =
context.step_lookup_fn.get(lookup_key).unwrap();
@@ -269,12 +253,15 @@ pub fn solve_for(
for dependency in new_step.requires(&context) {
dependencies.add_dependency(new_step.id(), dependency);
}
- new_step.as_ref().init_graph(&steps, &mut dependencies, &context);
+ new_step
+ .as_ref()
+ .init_graph(&steps, &mut dependencies, &context);
}
// Call after_init_graph on all steps
for step in steps.iter() {
- step.as_ref().after_init_graph(&steps, &mut dependencies, &context);
+ step.as_ref()
+ .after_init_graph(&steps, &mut dependencies, &context);
}
}
diff --git a/src/reporting/mod.rs b/src/reporting/mod.rs
index 5a1ae40..bc34ccb 100644
--- a/src/reporting/mod.rs
+++ b/src/reporting/mod.rs
@@ -16,206 +16,7 @@
along with this program. If not, see .
*/
-use std::fmt::Debug;
-use std::{collections::HashMap, fmt::Display};
-
-use calculator::ReportingGraphDependencies;
-use chrono::NaiveDate;
-use downcast_rs::Downcast;
-use dyn_clone::DynClone;
-use dyn_eq::DynEq;
-
pub mod builders;
pub mod calculator;
pub mod steps;
-
-pub struct ReportingContext {
- eofy_date: NaiveDate,
- step_lookup_fn: HashMap<
- (&'static str, &'static [ReportingProductKind]),
- (ReportingStepTakesArgsFn, ReportingStepFromArgsFn),
- >,
- step_dynamic_builders: Vec,
-}
-
-impl ReportingContext {
- pub fn new(eofy_date: NaiveDate) -> Self {
- Self {
- eofy_date: eofy_date,
- step_lookup_fn: HashMap::new(),
- step_dynamic_builders: Vec::new(),
- }
- }
-
- fn register_lookup_fn(
- &mut self,
- name: &'static str,
- product_kinds: &'static [ReportingProductKind],
- takes_args_fn: ReportingStepTakesArgsFn,
- from_args_fn: ReportingStepFromArgsFn,
- ) {
- self.step_lookup_fn
- .insert((name, product_kinds), (takes_args_fn, from_args_fn));
- }
-
- fn register_dynamic_builder(&mut self, builder: ReportingStepDynamicBuilder) {
- if !self
- .step_dynamic_builders
- .iter()
- .any(|b| b.name == builder.name)
- {
- self.step_dynamic_builders.push(builder);
- }
- }
-}
-
-#[derive(Debug, Eq, PartialEq)]
-pub struct ReportingProductId {
- name: &'static str,
- kind: ReportingProductKind,
- args: Box,
-}
-
-impl Display for ReportingProductId {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- f.write_fmt(format_args!("{}.{:?}({})", self.name, self.kind, self.args))
- }
-}
-
-#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
-pub enum ReportingProductKind {
- Transactions,
- BalancesAt,
- BalancesBetween,
- Generic,
-}
-
-//enum ReportingProduct {
-// Transactions(Transactions),
-// BalancesAt(BalancesAt),
-// BalancesBetween(BalancesBetween),
-// Generic(Box),
-//}
-
-//struct Transactions {}
-//struct BalancesAt {}
-//struct BalancesBetween {}
-
-//trait GenericReportingProduct {}
-
-//type ReportingProducts = HashMap;
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct ReportingStepId {
- pub name: &'static str,
- pub product_kinds: &'static [ReportingProductKind],
- pub args: Box,
-}
-
-impl Display for ReportingStepId {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- f.write_fmt(format_args!(
- "{}{:?}({})",
- self.name, self.product_kinds, self.args
- ))
- }
-}
-
-pub trait ReportingStep: Debug + Display + Downcast {
- // Info
- fn id(&self) -> ReportingStepId;
-
- // Methods
- fn requires(&self, _context: &ReportingContext) -> Vec {
- vec![]
- }
-
- fn init_graph(
- &self,
- _steps: &Vec>,
- _dependencies: &mut ReportingGraphDependencies,
- _context: &ReportingContext,
- ) {
- }
-
- fn after_init_graph(
- &self,
- _steps: &Vec>,
- _dependencies: &mut ReportingGraphDependencies,
- _context: &ReportingContext,
- ) {
- }
-
- //fn execute(&self, _context: &ReportingContext, _products: &mut ReportingProducts) {
- // todo!();
- //}
-}
-
-downcast_rs::impl_downcast!(ReportingStep);
-
-pub trait ReportingStepArgs: Debug + Display + Downcast + DynClone + DynEq {}
-
-downcast_rs::impl_downcast!(ReportingStepArgs);
-dyn_clone::clone_trait_object!(ReportingStepArgs);
-dyn_eq::eq_trait_object!(ReportingStepArgs);
-
-pub type ReportingStepTakesArgsFn = fn(args: &Box) -> bool;
-pub type ReportingStepFromArgsFn = fn(args: Box) -> Box;
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct VoidArgs {}
-
-impl ReportingStepArgs for VoidArgs {}
-
-impl Display for VoidArgs {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- f.write_fmt(format_args!(""))
- }
-}
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct DateArgs {
- pub date: NaiveDate,
-}
-
-impl ReportingStepArgs for DateArgs {}
-
-impl Display for DateArgs {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- f.write_fmt(format_args!("{}", self.date))
- }
-}
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct DateStartDateEndArgs {
- pub date_start: NaiveDate,
- pub date_end: NaiveDate,
-}
-
-impl ReportingStepArgs for DateStartDateEndArgs {}
-
-impl Display for DateStartDateEndArgs {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- f.write_fmt(format_args!("{}, {}", self.date_start, self.date_end))
- }
-}
-
-pub struct ReportingStepDynamicBuilder {
- name: &'static str,
- can_build: fn(
- name: &'static str,
- kind: ReportingProductKind,
- args: &Box,
- steps: &Vec>,
- dependencies: &ReportingGraphDependencies,
- context: &ReportingContext,
- ) -> bool,
- build: fn(
- name: &'static str,
- kind: ReportingProductKind,
- args: Box,
- steps: &Vec>,
- dependencies: &ReportingGraphDependencies,
- context: &ReportingContext,
- ) -> Box,
-}
+pub mod types;
diff --git a/src/reporting/steps.rs b/src/reporting/steps.rs
index 85ab6a7..15482cd 100644
--- a/src/reporting/steps.rs
+++ b/src/reporting/steps.rs
@@ -20,13 +20,11 @@ use std::fmt::Display;
use chrono::Datelike;
+use crate::reporting::types::{BalancesAt, DateStartDateEndArgs, ReportingProduct, ReportingProductId};
use crate::util::sofy_from_eofy;
-use super::{
- calculator::ReportingGraphDependencies, DateArgs, DateStartDateEndArgs, ReportingContext,
- ReportingProductId, ReportingProductKind, ReportingStep, ReportingStepArgs, ReportingStepId,
- VoidArgs,
-};
+use super:: calculator::ReportingGraphDependencies;
+use super::types::{DateArgs, ReportingContext, ReportingProductKind, ReportingProducts, ReportingStep, ReportingStepArgs, ReportingStepId, VoidArgs};
pub fn register_lookup_fns(context: &mut ReportingContext) {
context.register_lookup_fn(
diff --git a/src/reporting/types.rs b/src/reporting/types.rs
new file mode 100644
index 0000000..e6a25bd
--- /dev/null
+++ b/src/reporting/types.rs
@@ -0,0 +1,282 @@
+/*
+ DrCr: Web-based double-entry bookkeeping framework
+ Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+use std::collections::HashMap;
+use std::fmt::{Debug, Display};
+
+use chrono::NaiveDate;
+use downcast_rs::Downcast;
+use dyn_clone::DynClone;
+use dyn_eq::DynEq;
+use dyn_hash::DynHash;
+
+use crate::QuantityInt;
+
+use super::calculator::ReportingGraphDependencies;
+
+// -----------------
+// REPORTING CONTEXT
+
+/// Records the context for a single reporting job
+pub struct ReportingContext {
+ pub eofy_date: NaiveDate,
+ pub(crate) step_lookup_fn: HashMap<
+ (&'static str, &'static [ReportingProductKind]),
+ (ReportingStepTakesArgsFn, ReportingStepFromArgsFn),
+ >,
+ pub(crate) step_dynamic_builders: Vec,
+}
+
+impl ReportingContext {
+ /// Initialise a new [ReportingContext]
+ pub fn new(eofy_date: NaiveDate) -> Self {
+ Self {
+ eofy_date: eofy_date,
+ step_lookup_fn: HashMap::new(),
+ step_dynamic_builders: Vec::new(),
+ }
+ }
+
+ /// Register a lookup function
+ ///
+ /// A lookup function generates concrete [ReportingStep]s from a [ReportingStepId].
+ pub fn register_lookup_fn(
+ &mut self,
+ name: &'static str,
+ product_kinds: &'static [ReportingProductKind],
+ takes_args_fn: ReportingStepTakesArgsFn,
+ from_args_fn: ReportingStepFromArgsFn,
+ ) {
+ self.step_lookup_fn
+ .insert((name, product_kinds), (takes_args_fn, from_args_fn));
+ }
+
+ /// Register a dynamic builder
+ ///
+ /// Dynamic builders are called when no concrete [ReportingStep] is implemented, and can dynamically generate a [ReportingStep]. Dynamic builders are implemented in [super::builders].
+ pub fn register_dynamic_builder(&mut self, builder: ReportingStepDynamicBuilder) {
+ if !self
+ .step_dynamic_builders
+ .iter()
+ .any(|b| b.name == builder.name)
+ {
+ self.step_dynamic_builders.push(builder);
+ }
+ }
+}
+
+/// Function which determines whether the [ReportingStepArgs] are valid arguments for a given [ReportingStep]
+///
+/// See [ReportingContext::register_lookup_fn].
+pub type ReportingStepTakesArgsFn = fn(args: &Box) -> bool;
+
+/// Function which builds a concrete [ReportingStep] from the given [ReportingStepArgs]
+///
+/// See [ReportingContext::register_lookup_fn].
+pub type ReportingStepFromArgsFn = fn(args: Box) -> Box;
+
+// -------------------------------
+// REPORTING STEP DYNAMIC BUILDERS
+
+/// Represents a reporting step dynamic builder
+///
+/// See [ReportingContext::register_dynamic_builder].
+pub struct ReportingStepDynamicBuilder {
+ pub name: &'static str,
+ pub can_build: fn(
+ name: &'static str,
+ kind: ReportingProductKind,
+ args: &Box,
+ steps: &Vec>,
+ dependencies: &ReportingGraphDependencies,
+ context: &ReportingContext,
+ ) -> bool,
+ pub build: fn(
+ name: &'static str,
+ kind: ReportingProductKind,
+ args: Box,
+ steps: &Vec>,
+ dependencies: &ReportingGraphDependencies,
+ context: &ReportingContext,
+ ) -> Box,
+}
+
+// ------------------
+// REPORTING PRODUCTS
+
+/// Identifies a [ReportingProduct]
+#[derive(Debug, Eq, Hash, PartialEq)]
+pub struct ReportingProductId {
+ pub name: &'static str,
+ pub kind: ReportingProductKind,
+ pub args: Box,
+}
+
+impl Display for ReportingProductId {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_fmt(format_args!("{}.{:?}({})", self.name, self.kind, self.args))
+ }
+}
+
+/// Identifies a type of [ReportingProduct]
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
+pub enum ReportingProductKind {
+ Transactions,
+ BalancesAt,
+ BalancesBetween,
+ Generic,
+}
+
+/// Represents the result of a [ReportingStep]
+pub enum ReportingProduct {
+ Transactions(Transactions),
+ BalancesAt(BalancesAt),
+ BalancesBetween(BalancesBetween),
+ Generic(Box),
+}
+
+/// Records a list of transactions generated by a [ReportingStep]
+pub struct Transactions {}
+
+/// Records cumulative account balances at a particular point in time
+pub struct BalancesAt {
+ pub balances: HashMap,
+}
+
+/// Records the total value of transactions in each account between two points in time
+pub struct BalancesBetween {}
+
+/// Represents a custom [ReportingProduct] generated by a [ReportingStep]
+pub trait GenericReportingProduct {}
+
+/// Convenience type mapping [ReportingProductId] to [ReportingProduct]
+pub type ReportingProducts = HashMap;
+
+// ---------------
+// REPORTING STEPS
+
+/// Identifies a [ReportingStep]
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct ReportingStepId {
+ pub name: &'static str,
+ pub product_kinds: &'static [ReportingProductKind],
+ pub args: Box,
+}
+
+impl Display for ReportingStepId {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_fmt(format_args!(
+ "{}{:?}({})",
+ self.name, self.product_kinds, self.args
+ ))
+ }
+}
+
+/// Represents a step in a reporting job
+pub trait ReportingStep: Debug + Display + Downcast {
+ /// Get the [ReportingStepId] for this [ReportingStep]
+ fn id(&self) -> ReportingStepId;
+
+ /// Return a list of statically defined dependencies for this [ReportingStep]
+ #[allow(unused_variables)]
+ fn requires(&self, context: &ReportingContext) -> Vec {
+ vec![]
+ }
+
+ /// Called when the [ReportingStep] is initialised in [super::calculator::steps_for_targets]
+ #[allow(unused_variables)]
+ fn init_graph(
+ &self,
+ steps: &Vec>,
+ dependencies: &mut ReportingGraphDependencies,
+ context: &ReportingContext,
+ ) {
+ }
+
+ /// Called when new [ReportingStep]s are initialised in [super::calculator::steps_for_targets]
+ ///
+ /// This callback can be used to dynamically declare dependencies between [ReportingStep]s that are not known at initialisation.
+ #[allow(unused_variables)]
+ fn after_init_graph(
+ &self,
+ steps: &Vec>,
+ dependencies: &mut ReportingGraphDependencies,
+ context: &ReportingContext,
+ ) {
+ }
+
+ /// Called to generate the [ReportingProduct] for this [ReportingStep]
+ #[allow(unused_variables)]
+ fn execute(&self, context: &ReportingContext, products: &mut ReportingProducts) {
+ todo!();
+ }
+}
+
+downcast_rs::impl_downcast!(ReportingStep);
+
+// ------------------------
+// REPORTING STEP ARGUMENTS
+
+/// Represents arguments to a [ReportingStep]
+pub trait ReportingStepArgs: Debug + Display + Downcast + DynClone + DynEq + DynHash {}
+
+downcast_rs::impl_downcast!(ReportingStepArgs);
+dyn_clone::clone_trait_object!(ReportingStepArgs);
+dyn_eq::eq_trait_object!(ReportingStepArgs);
+dyn_hash::hash_trait_object!(ReportingStepArgs);
+
+/// [ReportingStepArgs] implementation which takes no arguments
+#[derive(Clone, Debug, Eq, Hash, PartialEq)]
+pub struct VoidArgs {}
+
+impl ReportingStepArgs for VoidArgs {}
+
+impl Display for VoidArgs {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_fmt(format_args!(""))
+ }
+}
+
+/// [ReportingStepArgs] implementation which takes a single date
+#[derive(Clone, Debug, Eq, Hash, PartialEq)]
+pub struct DateArgs {
+ pub date: NaiveDate,
+}
+
+impl ReportingStepArgs for DateArgs {}
+
+impl Display for DateArgs {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_fmt(format_args!("{}", self.date))
+ }
+}
+
+/// [ReportingStepArgs] implementation which takes a date range
+#[derive(Clone, Debug, Eq, Hash, PartialEq)]
+pub struct DateStartDateEndArgs {
+ pub date_start: NaiveDate,
+ pub date_end: NaiveDate,
+}
+
+impl ReportingStepArgs for DateStartDateEndArgs {}
+
+impl Display for DateStartDateEndArgs {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_fmt(format_args!("{}, {}", self.date_start, self.date_end))
+ }
+}