package gw.financials
uses java.util.Date
uses java.util.Collection
uses java.util.ArrayList
uses java.util.List
/**
* Segments and iterates across date ranges associated with caller's objects (of type X).
* Usage:
* Instantiate a DateRangeSegmenter of appropriate type.
* Call .add for each x : X and its associated date range.
* Call segment, passing a callback block. The callback block will be called for each maximal
* date range overlapped by the same set of X, and include the set of X overlapping that period.
*
* Invariants:
* For each call to eachSegment, the end Date will be strictly later than the start Date.
* All [start, end) ranges passed to eachSegment will be non-overlapping and in calendar order.
*/
class DateRangeSegmenter {
var _edges : List> = {}
/**
* Iterate over each segment in this DateRange, calling the given block once per segment.
* The ranges will be non-overlapping, with start <= end, and segment will process the ranges
* in calendar order. For each(start, end) range, the callback will be called once, with
* the dates that bound the range, and a collection of all X that overlap that range.
*
* Before calling this function, the segmenter should have been set up by calling the
* add method for each combination of date range and X.
* @param eachSegment The block to invoke on each range
*/
function segment(eachSegment(start: Date, end: Date, active: Collection)) {
var active : ArrayList = {}
var previousDate : Date = null
for (edge in _edges.sort( \ a, b -> a._date < b._date)) {
switch (true) {
case previousDate == null:
case previousDate.daysBetween(edge._date) == 0: //don't emit zero-day segments
case active.isEmpty(): //don't emit segments not covered by any given date range
break
default: //emit a segment
eachSegment(previousDate, edge._date, active)
}
edge.update(active)
previousDate = edge._date
}
}
/**
* Add element to this date segmenter, associated with the given date range.
* @param start Start date of the range for which element is applicable
* @param end End date of the range for which element is applicable
* @param element The element which should be associated with this date range
* @return this, in order to allow chained calls
*/
function add(start : Date, end : Date, element : X) : DateRangeSegmenter {
_edges.add(new Edge(start, true, element))
_edges.add(new Edge(end, false, element))
return this
}
override function toString() : String {
return _edges.map( \ elt -> "\n" + elt.toString()).join(",")
}
private static class Edge {
var _date : Date
var _isOnset : boolean
var _element : U
construct(date : Date, isOnset : boolean, element : U) {
_date = date
_isOnset = isOnset
_element = element
}
function update(operand : Collection) {
if (_isOnset) {
operand.add(_element)
} else {
operand.remove(_element)
}
}
override function toString() : String {
var eltString = _element.toString()
return "<<" + _date + (_isOnset ? " start " : " end ") + eltString.hashCode() + " -- " + eltString + ">>"
}
}
}