/ TimeTicks / src / daterange.jl
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!