Calculate hours, not minutes
[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
41 /**
42  * A representation of a timeslot with a start and end date.
43  *
44  * @author Georgios Gousios <gousiosg@gmail.com>
45  */
46 case class Timeslot(from: Date, to: Date) extends Ordered[Timeslot] {
47
48   /* Preconditions to ensure correct object creations */
49   assert(from != null)
50   assert(to != null)
51   assert(from.before(to))
52
53   def startsBefore(t: Timeslot) : Boolean = this.from.before(t.from)
54
55   def startsAfter(t: Timeslot) : Boolean = this.from.after(t.from)
56
57   def endsBefore(t: Timeslot) : Boolean = this.to.before(t.to)
58
59   def endsAfter(t: Timeslot) : Boolean = this.to.after(t.to)
60
61   /**
62    * Check whether this time slot fully contains the provided one.
63    */
64   def contains(t: Timeslot) : Boolean = t.startsAfter(this) && t.endsBefore(this)
65
66   /**
67    * Check whether this timeslot contains the provided time instant.
68    */
69   def includes(t: Date) : Boolean =
70     if (from.before(t) && to.after(t)) true else false
71
72   /**
73    * Check whether this timeslot overlaps with the provided one.
74    */
75   def overlaps(t: Timeslot) : Boolean = {
76     if (contains(t) || t.contains(this))
77       return true
78
79     if (this.includes(t.from) || this.includes(t.to))
80       return true
81
82     false
83   }
84
85   /**
86    * Merges this timeslot with the provided one. If the timeslots overlap,
87    * a list with the resulting merge is returned. If the timeslots do not
88    * overlap, the returned list contains both timeslots in increasing start
89    * date order.
90    */
91   def merge(t: Timeslot) : List[Timeslot] = {
92     if (overlaps(t)) {
93       val nfrom = if (from.before(t.from)) from else t.from
94       val nto   = if (to.after(t.to)) to else t.to
95       List(Timeslot(nfrom, nto))
96     } else
97       if (this.from.before(t.from))
98         List(this, t)
99       else
100         List(t, this)
101   }
102
103   /**
104    * Split the timeslot in two parts at the provided timestamp, if the
105    * timestamp falls within the timeslot boundaries.
106    */
107   def slice(d: Date) : List[Timeslot] =
108     if (includes(d))
109       List(Timeslot(from, d), Timeslot(d,to))
110     else
111       List(this)
112
113   /**
114    * Find and return the timeslots whithin which this Timeslot overrides
115    * with the provided list of timeslots. For example if:
116    * 
117    *  - `this == Timeslot(7,20)`
118    *  - `list == List(Timeslot(1,3), Timeslot(6,8), Timeslot(11,15))`
119    *
120    * the result will be: `List(Timeslot(7,8), Timeslot(11,15))`
121    */
122   def overlappingTimeslots(list: List[Timeslot]) : List[Timeslot] = {
123
124     val result = new mutable.ListBuffer[Timeslot]()
125
126     list.foreach {
127       t =>
128         if (t.contains(this)) result += this
129         else if (this.contains(t)) result += t
130         else if (t.overlaps(this) && t.startsBefore(this)) result += this.slice(t.to).head
131         else if (t.overlaps(this) && t.startsAfter(this)) result += this.slice(t.from).tail.head
132     }
133     result.toList
134   }
135
136   /**
137    * Find and return the timeslots whithin which this Timeslot does not
138    * override with the provided list of timeslots. For example if:
139    *
140    *  - `this == Timeslot(7,20)`
141    *  - `list == List(Timeslot(1,3), Timeslot(6,8), Timeslot(11,15))`
142    *
143    * the result will be: `List(Timeslot(9,10), Timeslot(15,20))`
144    */
145   def nonOverlappingTimeslots(list: List[Timeslot]): List[Timeslot] = {
146
147     val overlaps = list.filter(t => this.overlaps(t))
148
149     if (overlaps.isEmpty)
150       return List(this)
151
152     def build(acc: List[Timeslot], listPart: List[Timeslot]): List[Timeslot] = {
153
154       listPart match {
155         case Nil => acc
156         case x :: Nil => build(acc, List())
157         case x :: y :: rest =>
158           build(acc ++ List(Timeslot(x.to,  y.from)), y :: rest)
159       }
160     }
161
162     val head = overlaps.head
163     val last = overlaps.reverse.head
164
165     val start = if (head.startsAfter(this)) List(Timeslot(this.from, head.from)) else List()
166     val end = if (last.endsBefore(this)) List(Timeslot(last.to, this.to)) else List()
167
168     start ++ build(List(), overlaps) ++ end
169   }
170
171   /**
172    * Compares the starting times of two timeslots.
173    */
174   def compare(that: Timeslot): Int = {
175     if (this.startsBefore(that)) -1
176     else if (this.startsAfter(that)) 1
177     else 0
178   }
179
180   /**
181    * Converts the timeslot to the amount of hours it represents
182    */
183   def hours: Float = ((to.getTime - from.getTime)/1000)/60F/60F
184 }