A human-friendlier toString for timeslot
[aquarium] / src / main / scala / gr / grnet / aquarium / logic / accounting / dsl / Timeslot.scala
1 /*
2  * Copyright 2011 GRNET S.A. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or
5  * without modification, are permitted provided that the following
6  * conditions are met:
7  *
8  *   1. Redistributions of source code must retain the above
9  *      copyright notice, this list of conditions and the following
10  *      disclaimer.
11  *
12  *   2. Redistributions in binary form must reproduce the above
13  *      copyright notice, this list of conditions and the following
14  *      disclaimer in the documentation and/or other materials
15  *      provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
18  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
21  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
24  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
25  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGE.
29  *
30  * The views and conclusions contained in the software and
31  * documentation are those of the authors and should not be
32  * interpreted as representing official policies, either expressed
33  * or implied, of GRNET S.A.
34  */
35
36 package gr.grnet.aquarium.logic.accounting.dsl
37
38 import java.util.Date
39 import scala.collection.mutable
40 import annotation.tailrec
41 import gr.grnet.aquarium.util.date.MutableDateCalc
42
43 /**
44  * A representation of a timeslot with a start and end date.
45  *
46  * @author Georgios Gousios <gousiosg@gmail.com>
47  */
48 final case class Timeslot(from: Date, to: Date)
49   extends DSLItem with Ordered[Timeslot] {
50
51   /* Preconditions to ensure correct object creations */
52   assert(from != null)
53   assert(to != null)
54   assert(from.before(to), "from = %s, to = %s".format(new MutableDateCalc(from), new MutableDateCalc(to)))
55
56   def startsBefore(t: Timeslot) : Boolean = this.from.before(t.from)
57
58   def startsAfter(t: Timeslot) : Boolean = this.from.after(t.from)
59
60   def endsBefore(t: Timeslot) : Boolean = this.to.before(t.to)
61
62   def endsAfter(t: Timeslot) : Boolean = this.to.after(t.to)
63
64   def after(t: Timeslot): Boolean = if (this.from.after(t.to)) true else false
65
66   def before(t: Timeslot): Boolean = if (this.to.before(t.from)) true else false
67
68   /**
69    * Check whether this time slot fully contains the provided one.
70    */
71   def contains(t: Timeslot) : Boolean = {
72     if (this.from.getTime <= t.from.getTime &&
73       this.to.getTime >= t.to.getTime)
74       return true
75     return false
76   }
77
78   /**
79    * Check whether this timeslot contains the provided time instant.
80    */
81   def includes(t: Date) : Boolean =
82     if (from.before(t) && to.after(t)) true else false
83
84   /**
85    * Check whether this timeslot overlaps with the provided one.
86    */
87   def overlaps(t: Timeslot) : Boolean = {
88     if (contains(t) || t.contains(this))
89       return true
90
91     if (this.includes(t.from) || this.includes(t.to))
92       return true
93
94     false
95   }
96
97   /**
98    * Merges this timeslot with the provided one. If the timeslots overlap,
99    * a list with the resulting merge is returned. If the timeslots do not
100      * overlap, the returned list contains both timeslots in increasing start
101    * date order.
102    */
103   def merge(t: Timeslot) : List[Timeslot] = {
104     if (overlaps(t)) {
105       val nfrom = if (from.before(t.from)) from else t.from
106       val nto   = if (to.after(t.to)) to else t.to
107       List(Timeslot(nfrom, nto))
108     } else
109       if (this.from.before(t.from))
110         List(this, t)
111       else
112         List(t, this)
113   }
114
115   /**
116    * Split the timeslot in two parts at the provided timestamp, if the
117    * timestamp falls within the timeslot boundaries.
118    */
119   def slice(d: Date) : List[Timeslot] =
120     if (includes(d))
121       List(Timeslot(from, d), Timeslot(d,to))
122     else
123       List(this)
124
125   /**
126    * Find and return the timeslots within which this Timeslot overrides
127    * with the provided list of timeslots. For example if:
128    * 
129    *  - `this == Timeslot(7,20)`
130    *  - `list == List(Timeslot(1,3), Timeslot(6,8), Timeslot(11,15))`
131    *
132    * the result will be: `List(Timeslot(7,8), Timeslot(11,15))`
133    */
134   def overlappingTimeslots(list: List[Timeslot]) : List[Timeslot] = {
135
136     val result = new mutable.ListBuffer[Timeslot]()
137
138     list.foreach {
139       t =>
140         if (t.contains(this)) result += this
141         else if (this.contains(t)) result += t
142         else if (t.overlaps(this) && t.startsBefore(this)) result += this.slice(t.to).head
143         else if (t.overlaps(this) && t.startsAfter(this)) result += this.slice(t.from).last
144     }
145     result.toList
146   }
147
148   /**
149    * Find and return the timeslots whithin which this Timeslot does not
150    * override with the provided list of timeslots. For example if:
151    *
152    *  - `this == Timeslot(7,20)`
153    *  - `list == List(Timeslot(1,3), Timeslot(6,8), Timeslot(11,15))`
154    *
155    * the result will be: `List(Timeslot(9,10), Timeslot(15,20))`
156    */
157   def nonOverlappingTimeslots(list: List[Timeslot]): List[Timeslot] = {
158
159     val overlaps = list.filter(t => this.overlaps(t))
160
161     if (overlaps.isEmpty)
162       return List(this)
163
164     def build(acc: List[Timeslot], listPart: List[Timeslot]): List[Timeslot] = {
165
166       listPart match {
167         case Nil => acc
168         case x :: Nil => build(acc, List())
169         case x :: y :: rest =>
170           build(acc ++ List(Timeslot(x.to,  y.from)), y :: rest)
171       }
172     }
173
174     val head = overlaps.head
175     val last = overlaps.reverse.head
176
177     val start = if (head.startsAfter(this)) List(Timeslot(this.from, head.from)) else List()
178     val end = if (last.endsBefore(this)) List(Timeslot(last.to, this.to)) else List()
179
180     start ++ build(List(), overlaps) ++ end
181   }
182
183   /**
184    * Align a list of consecutive timeslots to the boundaries
185    * defined by this timeslot. Elements that do not overlap
186    * with this timeslot are rejected, while elements not
187    * contained in the timeslot are trimmed to this timeslot's
188    * start and end time.
189    */
190   def align(l: List[Timeslot]): List[Timeslot] = {
191     if (l.isEmpty) return List()
192
193     val result =
194       if (!this.overlaps(l.head)) List()
195       else if (this.contains(l.head)) List(l.head)
196       else if (l.head.startsBefore(this)) List(Timeslot(this.from, l.head.to))
197       else if (l.head.endsAfter(this)) List(Timeslot(l.head.from, this.to))
198       else List(this)
199
200     if (!result.isEmpty)
201       result.head :: align(l.tail)
202     else
203       align(l.tail)
204   }
205
206   /**
207    * Compares the starting times of two timeslots.
208    */
209   def compare(that: Timeslot): Int = {
210     if (this.startsBefore(that)) -1
211     else if (this.startsAfter(that)) 1
212     else 0
213   }
214
215   /**
216    * Converts the timeslot to the amount of hours it represents
217    */
218   def hours: Double = (to.getTime - from.getTime).toDouble / 1000.0 / 60.0 / 60.0
219
220   def deltaMillis = to.getTime - from.getTime
221
222   override def toString() = toDateString
223
224   def toDateString = "Timeslot(%s, %s)".format(new MutableDateCalc(from), new MutableDateCalc(to))
225   def toISODateString = "Timeslot(%s, %s)".format(new MutableDateCalc(from).toISOString, new MutableDateCalc(to).toISOString)
226 }