From 778d6e37929c5c43835e814e3c1ed4b66b5ddb75 Mon Sep 17 00:00:00 2001
From: Daniel Vedder <daniel.vedder@idiv.de>
Date: Fri, 2 Aug 2024 14:10:44 +0200
Subject: [PATCH] Added automatic conversion of Tuple{Int64,Int64} to
 AnnualDate

---
 docs/src/developing.md |  7 +++++++
 docs/src/simulation.md |  2 +-
 src/core/utils.jl      | 24 ++++++++++++++++++++----
 src/nature/macros.jl   | 15 +++++++++++----
 4 files changed, 39 insertions(+), 9 deletions(-)

diff --git a/docs/src/developing.md b/docs/src/developing.md
index 48ff489..8049e8a 100644
--- a/docs/src/developing.md
+++ b/docs/src/developing.md
@@ -149,3 +149,10 @@ julia> 2km / 10m
 
 Within Persefone, the following units and dimensions have been imported for direct usage:
 `cm`, `m`, `km`, `m²`, `ha`, `km²`, `mg`, `g`, `kg`, `Length`, `Area`, `Mass`.
+
+### Dates
+
+Persefone expands the default [Dates](https://docs.julialang.org/en/v1/stdlib/Dates/) library
+with the [`AnnualDate`](@ref) type, which can be used to store dates that recur every year
+(e.g. migration or harvest). `AnnualDates` can be compared and added/subtracted just as normal
+dates. Use [`thisyear()`](@ref) to convert an `AnnualDate` to a `Date`.
diff --git a/docs/src/simulation.md b/docs/src/simulation.md
index 11ccea7..6bd8e0b 100644
--- a/docs/src/simulation.md
+++ b/docs/src/simulation.md
@@ -9,7 +9,7 @@ This file defines the module, including all exported symbols and two high-level
 
 ```@autodocs
 Modules = [Persefone]
-Pages = ["Persefone.jl"]
+Pages = ["Persefone.jl", "core/utils.jl"]
 ```
 
 ## simulation.jl
diff --git a/src/core/utils.jl b/src/core/utils.jl
index f15df79..d8077c3 100644
--- a/src/core/utils.jl
+++ b/src/core/utils.jl
@@ -8,10 +8,11 @@
 import Unitful: cm, m, km, ha, mg, g, kg, Length, Area, Mass
 const m² = m^2
 const km² = km^2
-import Base./ # enable division with different length/area unit types
-/(x::S,y::T) where {S<:Length, T<:Length} = (upreferred(x)/m) / (upreferred(y)/m)
-/(x::S,y::T) where {S<:Area, T<:Area} = (upreferred(x)/m²) / (upreferred(y)/m²)
-/(x::S,y::T) where {S<:Mass, T<:Mass} = (upreferred(x)/g) / (upreferred(y)/g)
+
+# enable division with different length/area unit types
+Base.:(/)(x::S,y::T) where {S<:Length, T<:Length} = (upreferred(x)/m) / (upreferred(y)/m)
+Base.:(/)(x::S,y::T) where {S<:Area, T<:Area} = (upreferred(x)/m²) / (upreferred(y)/m²)
+Base.:(/)(x::S,y::T) where {S<:Mass, T<:Mass} = (upreferred(x)/g) / (upreferred(y)/g)
 
 ## Utility type and function for working wth recurring dates
 
@@ -20,6 +21,8 @@ import Base./ # enable division with different length/area unit types
 
 A type to handle recurring dates (e.g. migration, harvest).
 Stores a month and a day, and can be compared against normal dates.
+To save typing, a Tuple{Int64,Int64} is automatically converted to an
+AnnualDate, allowing this syntax: `nestingend::AnnualDate = (August, 15)`.
 """
 mutable struct AnnualDate
     month::Int64
@@ -30,18 +33,31 @@ mutable struct AnnualDate
         if !(0 < month <= 12)
             Base.error("AnnualDate: month $month is out of range.") #TODO replace with exception
         elseif !(0 < day <= 31)
+            # not strictly accurate (AnnualDate(February, 30) is possible), but good enough
             Base.error("AnnualDate: day $day is out of range.") #TODO replace with exception
         else
             new(month, day)
         end
 end
 
+# (automatically) convert integer tuples to AnnualDate.
+# Allows writing `(August, 2)` instead of `AnnualDate(August, 2)` where an AnnualDate is expected.
+AnnualDate(ad::Tuple{Int64,Int64}) = AnnualDate(ad...)
+Base.convert(::Type{AnnualDate}, ad::Tuple{Int64,Int64}) = AnnualDate(ad)
+
 # Interface with Dates
 AnnualDate(date::Date) = AnnualDate(month(date), day(date))
 Dates.month(ad::AnnualDate) = ad.month
 Dates.day(ad::AnnualDate) = ad.day
 
 # Instantiate a recurring date for a given year
+"""
+    thisyear(annualdate, model)
+    nextyear(annualdate, model)
+    lastyear(annualdate, model)
+
+Convert an AnnualDate to a Date, using the current/next/previous year of the simulation run.
+"""
 thisyear(ad::AnnualDate, model::SimulationModel) = Date(year(model.date), ad.month, ad.day)
 nextyear(ad::AnnualDate, model::SimulationModel) = Date(year(model.date)+1, ad.month, ad.day)
 lastyear(ad::AnnualDate, model::SimulationModel) = Date(year(model.date)-1, ad.month, ad.day)
diff --git a/src/nature/macros.jl b/src/nature/macros.jl
index 54e3bef..14a81d3 100644
--- a/src/nature/macros.jl
+++ b/src/nature/macros.jl
@@ -495,8 +495,7 @@ end
 """
     @thisyear(annualdate)
 
-Construct a date object referring to the current model year from an AnnualDate
-(== Tuple{Int64,Int64}).
+Construct a date object referring to the current model year from an AnnualDate.
 """
 macro thisyear(annualdate)
     :(thisyear($(esc(annualdate)), $(esc(:model))))
@@ -505,9 +504,17 @@ end
 """
     @nextyear(annualdate)
 
-Construct a date object referring to the next year in the model from an AnnualDate
-(== Tuple{Int64,Int64}).
+Construct a date object referring to the next year in the model from an AnnualDate.
 """
 macro nextyear(annualdate)
     :(nextyear($(esc(annualdate)), $(esc(:model))))
 end
+
+"""
+    @lastyear(annualdate)
+
+Construct a date object referring to the last year in the model from an AnnualDate.
+"""
+macro lastyear(annualdate)
+    :(lastyear($(esc(annualdate)), $(esc(:model))))
+end
-- 
GitLab