From 2fccb9f8737a9fdf1cadfa9116c66078ba1af387 Mon Sep 17 00:00:00 2001
From: Daniel Vedder <daniel.vedder@idiv.de>
Date: Fri, 2 Aug 2024 12:08:51 +0200
Subject: [PATCH] Moved date and unit functions to core/util.jl

---
 src/Persefone.jl  | 15 +-----------
 src/core/utils.jl | 60 +++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 61 insertions(+), 14 deletions(-)
 create mode 100644 src/core/utils.jl

diff --git a/src/Persefone.jl b/src/Persefone.jl
index 093a63a..3ff4da7 100644
--- a/src/Persefone.jl
+++ b/src/Persefone.jl
@@ -107,20 +107,6 @@ export
     cropcover,
     cropyield
 
-## Import and define units and dimensions
-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)
-
-## Utility type and function for working wth recurring dates
-const AnnualDate = Tuple{Int64,Int64}
-function thisyear(ad::AnnualDate, model::SimulationModel) = Date(year(model.date), ad...)
-function nextyear(ad::AnnualDate, model::SimulationModel) = Date(year(model.date)+Year(1), ad...)
-
 """
     SimulationModel
 
@@ -141,6 +127,7 @@ function stepagent! end
 
 ## include all module files (note that the order matters - if file
 ## b references something from file a, it must be included later)
+include("core/utils.jl")
 include("core/input.jl")
 include("core/output.jl")
 include("analysis/makieplots.jl")
diff --git a/src/core/utils.jl b/src/core/utils.jl
new file mode 100644
index 0000000..f15df79
--- /dev/null
+++ b/src/core/utils.jl
@@ -0,0 +1,60 @@
+### Persefone.jl - a model of agricultural landscapes and ecosystems in Europe.
+###
+### This file holds various utility types and functions, especially for dealing with
+### units and dates.
+###
+
+## Import and define units and dimensions
+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)
+
+## Utility type and function for working wth recurring dates
+
+"""
+    AnnualDate
+
+A type to handle recurring dates (e.g. migration, harvest).
+Stores a month and a day, and can be compared against normal dates.
+"""
+mutable struct AnnualDate
+    month::Int64
+    day::Int64
+
+    # inner constructor to make sure inputs are in range
+    AnnualDate(month::Int64, day::Int64) =
+        if !(0 < month <= 12)
+            Base.error("AnnualDate: month $month is out of range.") #TODO replace with exception
+        elseif !(0 < day <= 31)
+            Base.error("AnnualDate: day $day is out of range.") #TODO replace with exception
+        else
+            new(month, day)
+        end
+end
+
+# 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(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)
+
+# Comparison between AnnualDates and with Dates
+Base.:(==)(ad1::AnnualDate, ad2::AnnualDate) = (month(ad1) == month(ad2) && day(ad1) == day(ad2))
+Base.:(==)(ad::AnnualDate, d::Date) = (month(d) == month(ad) && day(d) == day(ad))
+Base.:(==)(d::Date, ad::AnnualDate) = (ad == d)
+Base.:(<)(ad1::AnnualDate, ad2::AnnualDate) = (month(ad1) < month(ad2) ||
+                                               (month(ad1) == month(ad2) && day(ad1) < day(ad2)))
+Base.:(<)(ad::AnnualDate, d::Date) = (thisyear(ad) < d)
+Base.:(<)(d::Date, ad::AnnualDate) = (d < thisyear(ad))
+
+# Addition and subtraction of date periods
+Base.:(+)(ad::AnnualDate, time::DatePeriod) = AnnualDate(thisyear(ad) + time)
+Base.:(-)(ad::AnnualDate, time::DatePeriod) = AnnualDate(thisyear(ad) - time)
-- 
GitLab