diff --git a/src/Persefone.jl b/src/Persefone.jl index 093a63a2878cc8d795f76eefa7f447f0af407225..3ff4da76b27c49126cecf8a6309535134678f814 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 0000000000000000000000000000000000000000..f15df79f09d6d893bbd2478d7655404fb4c77d7b --- /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)