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 + ">>" } } }