daterange.jl
1 import Base: length, iterate, collect 2 3 @doc """A type representing a date range. 4 5 $(FIELDS) 6 7 This type is used to store information about a range of dates, including the current date within the range, the start and stop dates, and the step size between dates. 8 9 """ 10 mutable struct DateRange 11 current_date::OptDate 12 const start::OptDate 13 stop::OptDate 14 const step::Union{Nothing,Period} 15 function DateRange(start::OptDate=nothing, stop::OptDate=nothing, step=nothing) 16 new(start, start, stop, step) 17 end 18 function DateRange(start::OptDate, stop::OptDate, tf::TimeFrame) 19 new(start, start, stop, tf.period) 20 end 21 end 22 23 @doc """Convert a DateRange object d to a DateTuple object. 24 25 $(TYPEDSIGNATURES) 26 27 Example: 28 29 ```julia 30 d = DateRange(Date(2022, 1, 1), Date(2022, 12, 31)) 31 date_tuple = convert(DateTuple, d) # returns a DateTuple with the start and stop dates of the DateRange 32 ``` 33 """ 34 function Base.convert(::Type{DateTuple}, d::DateRange) 35 DateTuple(( 36 @something(d.start, typemin(DateTime)), @something(d.stop, typemax(DateTime)) 37 )) 38 end 39 40 Base.similar(dr::DateRange) = begin 41 DateRange(dr.start, dr.stop, dr.step) 42 end 43 44 function Base.print(io::IO, dr::DateRange) 45 print(io, "start: ", dr.start, "\nstop: ", dr.stop, "\nstep: ", dr.step, "\n") 46 end 47 Base.display(dr::DateRange) = Base.print(dr) 48 iterate(dr::DateRange) = begin 49 @assert !isnothing(dr.start) && !isnothing(dr.stop) 50 this = @something dr.current_date dr.start 51 dr.current_date = this + dr.step 52 (this, dr) 53 end 54 55 iterate(dr::DateRange, ::DateRange) = begin 56 now = dr.current_date 57 dr.current_date += dr.step 58 dr.current_date > dr.stop && return nothing 59 (now, dr) 60 end 61 62 length(dr::DateRange) = begin 63 (dr.stop - dr.start) รท dr.step 64 end 65 66 collect(dr::DateRange) = begin 67 out = [] 68 for d in dr 69 push!(out, d) 70 end 71 out 72 end 73 74 @doc "Starts the current date of the DateRange (defaults to `start` value.)" 75 current!(dr::DateRange, d=dr.start) = dr.current_date = d 76 function Base.isequal(dr1::DateRange, dr2::DateRange) 77 dr1.start === dr2.start && dr1.stop === dr2.stop 78 end 79 80 function Base.isapprox(dr1::DateRange, dr2::DateRange) 81 dr1.start >= dr2.start && dr1.stop <= dr2.stop 82 end 83 84 function Base.parse(::Type{DateRange}, s::AbstractString) 85 local to = step = "" 86 (from, tostep) = split(s, "..") 87 if !isempty(tostep) 88 try 89 (to, step) = split(tostep, ";") 90 catch error 91 if error isa BoundsError 92 to = tostep 93 step = "" 94 else 95 rethrow(error) 96 end 97 end 98 end 99 args::Vector{Any} = [isempty(v) ? nothing : todatetime(v) for v in (from, to)] 100 if !isempty(step) 101 push!(args, convert(TimeFrame, step)) 102 end 103 DateRange(args...) 104 end 105 106 @doc """Create a `DateRange` using notation `FROM..TO;STEP`. 107 108 example: 109 1999-..2000-;1d 110 1999-12-01..2000-02-01;1d 111 1999-12-01T12..2000-02-01T10;1d 112 """ 113 macro dtr_str(s::String) 114 :($(Base.parse(DateRange, s))) 115 end 116 117 export DateRange, DateTuple, @dtr_str, current!