/*
- * Copyright 2011 GRNET S.A. All rights reserved.
+ * Copyright 2011-2012 GRNET S.A. All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
import java.util.Date
import scala.collection.mutable
-import gr.grnet.aquarium.util.json.JsonSupport
+import annotation.tailrec
+import gr.grnet.aquarium.util.date.MutableDateCalc
/**
* A representation of a timeslot with a start and end date.
*
* @author Georgios Gousios <gousiosg@gmail.com>
*/
-case class Timeslot(from: Date, to: Date)
- extends Ordered[Timeslot] with JsonSupport {
+final case class Timeslot(from: Date, to: Date) extends Ordered[Timeslot] {
/* Preconditions to ensure correct object creations */
assert(from != null)
assert(to != null)
- assert(from.before(to))
+ assert(start <= end, "from = %s, to = %s".format(new MutableDateCalc(from), new MutableDateCalc(to)))
- def startsBefore(t: Timeslot) : Boolean = this.from.before(t.from)
+ def startsBefore(t: Timeslot) : Boolean = start < t.start
- def startsAfter(t: Timeslot) : Boolean = this.from.after(t.from)
+ def startsAfter(t: Timeslot) : Boolean = start > t.start
- def endsBefore(t: Timeslot) : Boolean = this.to.before(t.to)
+ def endsBefore(t: Timeslot) : Boolean = end < t.end
- def endsAfter(t: Timeslot) : Boolean = this.to.after(t.to)
+ def endsAfter(t: Timeslot) : Boolean = end > t.end
+
+ def after(t: Timeslot): Boolean = start > t.end
+
+ def before(t: Timeslot): Boolean = end < t.start
+
+ def start : Long = this.from.getTime
+
+ def end : Long = this.to.getTime
/**
* Check whether this time slot fully contains the provided one.
*/
- def contains(t: Timeslot) : Boolean = t.startsAfter(this) && t.endsBefore(this)
+ def contains(t: Timeslot) : Boolean = this.start <= t.start && this.end >= t.end
+
+ def weakIncludes(t: Date) : Boolean = start < t.getTime && t.getTime < end
+ def weakOverlaps(t: Timeslot) : Boolean =
+ contains(t) || t.contains(this) || this.weakIncludes(t.from) || this.weakIncludes(t.to)
+
+
+ def containsTimeInMillis(millis: Long) = start <= millis && millis <= end
+
/**
* Check whether this timeslot contains the provided time instant.
*/
- def includes(t: Date) : Boolean =
- if (from.before(t) && to.after(t)) true else false
+ def includes(t: Date) : Boolean = start <= t.getTime && t.getTime <= end
+
/**
* Check whether this timeslot overlaps with the provided one.
*/
- def overlaps(t: Timeslot) : Boolean = {
- if (contains(t) || t.contains(this))
- return true
-
- if (this.includes(t.from) || this.includes(t.to))
- return true
+ def overlaps(t: Timeslot) : Boolean =
+ contains(t) || t.contains(this) || this.includes(t.from) || this.includes(t.to)
- false
- }
/**
* Merges this timeslot with the provided one. If the timeslots overlap,
* a list with the resulting merge is returned. If the timeslots do not
- * overlap, the returned list contains both timeslots in increasing start
+ * overlap, the returned list contains both timeslots in increasing start
* date order.
*/
- def merge(t: Timeslot) : List[Timeslot] = {
- if (overlaps(t)) {
- val nfrom = if (from.before(t.from)) from else t.from
- val nto = if (to.after(t.to)) to else t.to
- List(Timeslot(nfrom, nto))
- } else
- if (this.from.before(t.from))
- List(this, t)
- else
- List(t, this)
+ def merge(t: Timeslot) : Timeslot = {
+ assert(overlaps(t),this +" has no overlap with " + t)
+ val nfrom = if (start < t.start) from else t.from
+ val nto = if (end > t.end) to else t.to
+ Timeslot(nfrom, nto)
}
/**
* Split the timeslot in two parts at the provided timestamp, if the
* timestamp falls within the timeslot boundaries.
*/
- def slice(d: Date) : List[Timeslot] =
- if (includes(d))
+ def slice(d: Date) : List[Timeslot] =
+ if (includes(d) && d.getTime != start && d.getTime != end)
List(Timeslot(from, d), Timeslot(d,to))
else
List(this)
+
/**
- * Find and return the timeslots whithin which this Timeslot overrides
+ * Find and return the timeslots within which this Timeslot overrides
* with the provided list of timeslots. For example if:
*
* - `this == Timeslot(7,20)`
*
* the result will be: `List(Timeslot(7,8), Timeslot(11,15))`
*/
- def overlappingTimeslots(list: List[Timeslot]) : List[Timeslot] = {
+ def overlappingTimeslots(list: List[Timeslot]) : List[Timeslot] =
+ list.foldLeft(List[Timeslot]()) { (ret,t) =>
+ if (t.contains(this)) this :: ret
+ else if (this.contains(t)) t :: ret
+ else if (t.overlaps(this) && t.startsBefore(this)) slice(t.to).head :: ret
+ else if (t.overlaps(this) && t.startsAfter(this)) slice(t.from).last :: ret
+ else ret
+ }.reverse
- val result = new mutable.ListBuffer[Timeslot]()
-
- list.foreach {
- t =>
- if (t.contains(this)) result += this
- else if (this.contains(t)) result += t
- else if (t.overlaps(this) && t.startsBefore(this)) result += this.slice(t.to).head
- else if (t.overlaps(this) && t.startsAfter(this)) result += this.slice(t.from).tail.head
- }
- result.toList
- }
/**
* Find and return the timeslots whithin which this Timeslot does not
*
* the result will be: `List(Timeslot(9,10), Timeslot(15,20))`
*/
- def nonOverlappingTimeslots(list: List[Timeslot]): List[Timeslot] = {
-
- val overlaps = list.filter(t => this.overlaps(t))
-
- if (overlaps.isEmpty)
- return List(this)
-
- def build(acc: List[Timeslot], listPart: List[Timeslot]): List[Timeslot] = {
+ def nonOverlappingTimeslots(list: List[Timeslot]): List[Timeslot] =
+ overlappingTimeslots(list) sortWith {_.start < _.start} match {
+ case Nil => List(this)
+ case over =>
+ val (head,last) = (over.head,over.last)
+ val hd = if (head.start > this.start) List(Timeslot(this.from, head.from)) else List()
+ val tl = if (last.end < this.end) List(Timeslot(last.to, this.to)) else List()
+ hd ++ over.tail.foldLeft((List[Timeslot](),over.head)) {
+ case ((l,x),y) => (l ++ List(Timeslot(x.to, y.from)),y)
+ }._1 ++ tl
+ }
- listPart match {
- case Nil => acc
- case x :: Nil => build(acc, List())
- case x :: y :: rest =>
- build(acc ++ List(Timeslot(x.to, y.from)), y :: rest)
- }
+ /**
+ * Align a list of consecutive timeslots to the boundaries
+ * defined by this timeslot. Elements that do not overlap
+ * with this timeslot are rejected, while elements not
+ * contained in the timeslot are trimmed to this timeslot's
+ * start and end time.
+ */
+ def align(l: List[Timeslot]): List[Timeslot] = {
+ if (l.isEmpty) return List()
+
+ val result : Option[Timeslot] =
+ if (!this.overlaps(l.head)) None
+ else if (l.head.contains(this)) Some(this)
+ else if (l.head.startsBefore(this)) Some(Timeslot(this.from, l.head.to))
+ else if (l.head.endsAfter(this)) Some(Timeslot(l.head.from, this.to))
+ else Some(this)
+
+ result match {
+ case Some(x) => x :: align(l.tail)
+ case None => align(l.tail)
}
+ }
- val head = overlaps.head
- val last = overlaps.reverse.head
+ /* align a time slot in "bound_size" boundaries so that
+ * start0 <= start and end0 >= end */
+ def align(bound_size : Long) : Timeslot = {
+ val start0 = (start / bound_size) * bound_size
+ val add_one = if (end % bound_size == 0) 0 else 1
+ val end0 = (end / bound_size + add_one) * bound_size
+ Timeslot(start0,end0)
+ }
- val start = if (head.startsAfter(this)) List(Timeslot(this.from, head.from)) else List()
- val end = if (last.endsBefore(this)) List(Timeslot(last.to, this.to)) else List()
+ /* returns true when the start and end address are
+ * multiples of bound_size*/
+ def isAligned(bound_size : Long) : Boolean =
+ start % bound_size == 0 && end % bound_size == 0
- start ++ build(List(), overlaps) ++ end
- }
/**
* Compares the starting times of two timeslots.
/**
* Converts the timeslot to the amount of hours it represents
*/
- def hours: Float = ((to.getTime - from.getTime)/1000)/60F/60F
+ def hours: Double = (to.getTime - from.getTime).toDouble / 1000.0 / 60.0 / 60.0
+
+ def deltaMillis = to.getTime - from.getTime
+
+
+ def myString : String = "Timeslot(" + this.start + "," + this.end + ")"
+ //override def toString() = myString
+ override def toString() =
+ toDateString
+
+ def toDateString = "Timeslot(%s, %s)".format(new MutableDateCalc(from), new MutableDateCalc(to))
+ def toISODateString = "Timeslot(%s, %s)".format(new MutableDateCalc(from).toISOString, new MutableDateCalc(to).toISOString)
+}
+
+object Timeslot {
+ def apply(x: Long, y: Long): Timeslot =
+ new Timeslot(new Date(x), new Date(y))
+
+ def apply(x: Int, y: Int): Timeslot =
+ new Timeslot(new Date(x), new Date(y))
+
+ def mergeOverlaps(list: List[Timeslot]): List[Timeslot] = {
+ def sorter(x: Timeslot, y: Timeslot) : Boolean = y.from after x.from
+ (list sortWith sorter).foldLeft(List[Timeslot]()) {
+ case (Nil,b) =>
+ List(b)
+ case (hd::Nil,b) =>
+ if (hd overlaps b) (hd merge b)::Nil
+ else b::hd::Nil
+ case (a @ hd::tl,b) =>
+ if(hd overlaps b) (hd merge b)::tl
+ else b :: a
+ }.reverse
+ }
}
\ No newline at end of file