Timespecs should be working ok now!
authorProdromos Gerakios <pgerakios@grnet.gr>
Mon, 2 Jul 2012 13:18:16 +0000 (16:18 +0300)
committerProdromos Gerakios <pgerakios@grnet.gr>
Mon, 2 Jul 2012 13:18:16 +0000 (16:18 +0300)
13 files changed:
pom.xml
src/main/scala/gr/grnet/aquarium/logic/accounting/dsl/DSL.scala
src/main/scala/gr/grnet/aquarium/logic/accounting/dsl/DSLCronSpec.scala [new file with mode: 0644]
src/main/scala/gr/grnet/aquarium/logic/accounting/dsl/DSLSemanticChecks.scala
src/main/scala/gr/grnet/aquarium/logic/accounting/dsl/DSLTimeFrame.scala
src/main/scala/gr/grnet/aquarium/logic/accounting/dsl/DSLTimeFrameRepeat.scala
src/main/scala/gr/grnet/aquarium/logic/accounting/dsl/DSLTimeSpec.scala
src/main/scala/gr/grnet/aquarium/logic/accounting/dsl/DSLUtils.scala
src/main/scala/gr/grnet/aquarium/logic/accounting/dsl/Timeslot.scala
src/test/resources/policy.yaml
src/test/scala/gr/grnet/aquarium/logic/test/DSLUtilsTest.scala
src/test/scala/gr/grnet/aquarium/logic/test/PerfTest.scala
src/test/scala/gr/grnet/aquarium/logic/test/TimeslotTest.scala

diff --git a/pom.xml b/pom.xml
index fc3b07b..9e50302 100644 (file)
--- a/pom.xml
+++ b/pom.xml
       <artifactId>joda-convert</artifactId>
       <version>1.1</version>
     </dependency>
+
+      <dependency> <!-- quartz stuff  -->
+          <groupId>org.quartz-scheduler</groupId>
+          <artifactId>quartz</artifactId>
+          <version>2.1.5</version>
+      </dependency>
+      <dependency>
+          <groupId>org.quartz-scheduler</groupId>
+          <artifactId>quartz-oracle</artifactId>
+          <version>2.1.5</version>
+      </dependency>
+      <dependency>
+          <groupId>org.quartz-scheduler</groupId>
+          <artifactId>quartz-weblogic</artifactId>
+          <version>2.1.5</version>
+      </dependency>
+      <dependency>
+          <groupId>org.quartz-scheduler</groupId>
+          <artifactId>quartz-jboss</artifactId>
+          <version>2.1.5</version>
+      </dependency>
   </dependencies>
 
   <!--Build configuration-->
index 2e5c3c0..ea882d8 100644 (file)
@@ -439,20 +439,33 @@ trait DSL {
       case YAMLEmptyNode => None
     }
 
-    val effective = timeframe / Vocabulary.repeat match {
-      case x: YAMLListNode => parseTimeFrameRepeat(x)
-      case YAMLEmptyNode => List()
+    val repeat =
+     timeframe / Vocabulary.repeat match {
+       case x: YAMLListNode => parseTimeFrameRepeat(x)
+      case YAMLEmptyNode => Nil
     }
 
-    DSLTimeFrame(from, to, effective)
+    /*val cronRepeat = timeframe / "cron" match {
+      case x: YAMLListNode =>  parseCronSpecs(x)
+      case _ => Nil
+    }*/
+
+    DSLTimeFrame(from, to,repeat)
   }
 
+  /*def parseCronSpecs(l: YAMLListNode) : List[DSLCronSpec] =
+      l.listValue map  {
+        case x:YAMLStringNode => new DSLCronSpec(x.string.trim)
+        case YAMLEmptyNode => throw new DSLParseException("Bad cron expression")
+      }*/
+
   /** Parse a resource frame repeat block */
   def parseTimeFrameRepeat(tmr: YAMLListNode): List[DSLTimeFrameRepeat] = {
 
     if (tmr.isEmpty)
       return List()
 
+
     /** Parse a resource frame entry (start, end tags) */
     def findInMap(repeat: YAMLMapNode,
                   tag: String) : (String, List[DSLTimeSpec]) = {
@@ -464,7 +477,7 @@ trait DSL {
 
     val start = findInMap(tmr.head.asInstanceOf[YAMLMapNode], Vocabulary.start)
     val end = findInMap(tmr.head.asInstanceOf[YAMLMapNode], Vocabulary.end)
-
+    assert(start._2.size == end._2.size)
     DSLTimeFrameRepeat(start._2, end._2, start._1,end._1) :: parseTimeFrameRepeat(tmr.tail)
   }
 
@@ -491,6 +504,7 @@ trait DSL {
       val input = cron.get(ii).toString
       if (input.equals("*"))
         (-1).until(0).toList
+       //TODO:  DO NOT ACCEPT "-" and ","
       else if (input.contains('-')) {
         val ints = input.split('-')
         ints(0).toInt.until(ints(1).toInt + 1).toList
@@ -503,8 +517,9 @@ trait DSL {
           b <- splitMultiVals(1)
           c <- splitMultiVals(2)
           d <- splitMultiVals(3)
-          e <- splitMultiVals(4)  } yield
-      DSLTimeSpec(a,b,c,d,e)
+          e <- splitMultiVals(4)}
+    yield
+       DSLTimeSpec(a,b,c,d,e)
   }
 }
 
diff --git a/src/main/scala/gr/grnet/aquarium/logic/accounting/dsl/DSLCronSpec.scala b/src/main/scala/gr/grnet/aquarium/logic/accounting/dsl/DSLCronSpec.scala
new file mode 100644 (file)
index 0000000..8cf6cfa
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * 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
+ * conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above
+ *      copyright notice, this list of conditions and the following
+ *      disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above
+ *      copyright notice, this list of conditions and the following
+ *      disclaimer in the documentation and/or other materials
+ *      provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and
+ * documentation are those of the authors and should not be
+ * interpreted as representing official policies, either expressed
+ * or implied, of GRNET S.A.
+ */
+
+package gr.grnet.aquarium.logic.accounting.dsl
+
+import org.quartz.CronExpression
+import java.util.Date
+
+/**
+ * @author Prodromos Gerakios <pgerakios@grnet.gr>
+ */
+
+case class DSLCronSpec(cronSpec: String) extends DSLItem {
+
+    private val cronExpr = {
+      val e = "00 " + cronSpec.trim //IMPORTANT: WE DO NOT CARE ABOUT SECONDS!!!
+      val l = e.split(" ")
+      assert(l.size == 6,"Invalid cron specification")
+      //for {ll <- l } Console.err.println(ll)
+      (l(3),l(5))  match {
+        case ("?",_) | (_,"?") => ()
+        case (_,"*") => l.update(5,"?")
+        case ("*",_) => l.update(3,"?")
+      }
+      val e1 = l.foldLeft("") { (s,elt) => s + " " + elt}
+      //Console.err.println("e = " + e + " and e1 = " + e1)
+      new CronExpression(e1)
+    }
+
+    def includes(d:Date) : Boolean =
+      cronExpr isSatisfiedBy d
+
+  /* the next valid date cannot outlive (min,max)*/
+  def nextValidDate(min0:Date,max0:Date,d:Date) : Option[Date] =
+    (cronExpr getNextValidTimeAfter d) match {
+      case null =>
+        None
+      case d1 =>
+        val (min,max,e) = (min0.getTime,max0.getTime,d1.getTime)
+        if(e < min || e>max)
+          None
+        else
+          Some({assert(d1.getTime>=d.getTime);d1})
+    }
+
+  /*def nextValidDate(d:Date) : Date = {
+   val ret = cronExpr getNextValidTimeAfter d
+   ret
+ } */
+
+  /*def nextContinuousValidDate(d:Date) : Date = {
+      val d1 = cronExpr getNextInvalidTimeAfter d
+      if (d1 != null)
+        d1.setTime(d1.getTime - 1000) /* 1 sec before was valid so return this*/
+      d1
+    }
+
+
+    def getMaxValidBeforeDate(min0:Date , max0:Date,d:Date) : Option[Date] ={
+      val (min,max,e) = (min0.getTime,max0.getTime,d.getTime)
+      if(min > e || max < e) None
+      else if(includes(d)) Some(d)
+      else {
+        var end = e
+        var start = min //0L
+        var best:Date = null
+        var iterations=0L
+        val tmp = new Date(0L)
+        val step = 10000L * 60L // 1 minute
+        while(start < end) {
+          iterations += 1
+          val pivot = (end - start) / 2 + start
+          tmp.setTime(pivot)
+          val next = nextValidDate(tmp)
+          if(next == null){ /* no valid time after pivot*/
+            end = pivot-step; // pivot minus one
+          } else {
+            val p = next.getTime()
+            if(p < e) { /* next date occurs before e*/
+              val post =  next.getTime < d.getTime
+              assert(post,"BUG!!!")
+              best = next
+              start = p + step
+            } else if( p > e) { /* next date occurs after e*/
+              end = pivot-step
+            }
+            else assert(false,"This should not happen")
+          }
+        //  System.err.println("Start: " + new Date(start) + " end: " + new Date(end));
+        }
+        System.err.println("Iterations " + iterations);
+        if(best!=null) Some(best) else None
+      }
+    }
+    */
+}
+
+object DSLCronSpec {
+  val emptyCronSpec = new DSLCronSpec("? ? ? ? ?")
+}
\ No newline at end of file
index bb5389b..2fc60c6 100644 (file)
@@ -37,13 +37,13 @@ package gr.grnet.aquarium.logic.accounting.dsl
 
 import java.util.Date
 import scala.collection._
-
+//TODO: REMOVE THIS CLASS
 /**
  * A semantic checker for the Aquarium accounting DSL. 
  *
  * @author Georgios Gousios <gousiosg@gmail.com>
  */
-trait DSLSemanticChecks {
+/*trait DSLSemanticChecks {
 
   /**
    * Functions to apply by default when checking consistency for
@@ -147,4 +147,4 @@ trait DSLSemanticChecks {
 
 sealed trait DSLConsistencyMsg
 case class DSLConsistencyWarn(warn: String) extends DSLConsistencyMsg
-case class DSLConsistencyError(err: String) extends DSLConsistencyMsg
+case class DSLConsistencyError(err: String) extends DSLConsistencyMsg*/
index 4f4d2ca..5ca271c 100644 (file)
@@ -37,8 +37,11 @@ package gr.grnet.aquarium.logic.accounting.dsl
 
 import gr.grnet.aquarium.util.shortNameOfClass
 
-import java.util.Date
+import java.util.{GregorianCalendar, Date, Calendar}
 import gr.grnet.aquarium.util.date.MutableDateCalc
+import collection.mutable
+import java.util
+import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
 
 /**
  * Represents an effectivity timeframe.
@@ -48,7 +51,8 @@ import gr.grnet.aquarium.util.date.MutableDateCalc
 case class DSLTimeFrame (
   from: Date,
   to: Option[Date],
-  repeat: List[DSLTimeFrameRepeat]
+  repeat: List[DSLTimeFrameRepeat] /*,
+  cronRepeat: List[DSLCronSpec]*/
 ) extends DSLItem {
 
   to match {
@@ -58,6 +62,137 @@ case class DSLTimeFrame (
     case None =>
   }
 
+  private val start = from.getTime
+  private val infinity = new Date(Long.MaxValue)
+  private val end = to.getOrElse(infinity).getTime
+
+  /*
+   *  Given the timeslot "t" and the timeslot of this frame (start,end)
+   *  compute the intersection of the two timeslots (return a list of common intervals)
+   *  which satisfy the cron expressions. If no expressions are present which just
+   *  take the intersection. If there exist cron specs (cron,start) we compute the
+   *  intersection between "t" and the fine-grained subtimeslots (of this time frame).
+   *
+   *  */
+  def intervalsOf(t:Timeslot) : List[Timeslot]=
+    if(repeat.isEmpty)
+      t.overlappingTimeslots(List(Timeslot(start,end)))
+    else {
+      val result = new mutable.ListBuffer[Timeslot]()
+      var offset = t.from
+      firstValidTimeslot(t.from) match {
+        case None => ()
+        case Some(t) =>
+          result += t
+          offset = t.to
+      }
+      val step = 1000L*60L // +1 minute step
+      var continue = true
+      while(continue)
+        minValidAfter(t,offset) match {
+          case None =>
+            continue = false
+          case Some(next_timeslot) =>
+            result += next_timeslot
+            offset =  next_timeslot.to
+        }
+      result.toList
+    }
+    /*nextValidAfter(t.from) match {
+      case None =>
+        val ret = addRest(t.from)
+        ret
+      case Some(d_next) =>
+        if(d_next.after(t.to)){
+          if(repeat.isEmpty)
+            List(t)
+          else
+            Nil
+        }
+        else {
+          val ret =  Timeslot(t.from,d_next) :: addRest(d_next)
+          ret
+        }
+    } */
+
+  /*
+   * Find the first smallest (smallest here means with the least "end" timestamp) timeslot
+   * from beginning "offset" and ending at "d_end".
+   *
+   * Return None if "offset" is not bounded in this time frame and is not bounded by
+   * a valid "start" / "end" cron spec.
+   *
+   * */
+   private def firstValidTimeslot(offset:Date) : Option[Timeslot] = {
+    def minimum (min:Option[Timeslot],d_start:Date,d_end:Date) : Option[Timeslot] = {
+      min match {
+        case None =>
+          Some(Timeslot(d_start,d_end))
+        case Some(Timeslot(_,d_min)) =>
+          if(d_min.getTime < d_start.getTime)
+            min
+          else
+            Some(Timeslot(d_start,d_end))
+      }
+    }
+    val to = this.to.getOrElse(infinity)
+    repeat.foldLeft (None:Option[Timeslot]) {
+      (min,r)  =>
+        r.getEnd.nextValidDate(from,to,offset) match {
+          case None =>
+            min
+          case Some(d_end) =>
+            r.getStart.nextValidDate(from,to,from) match {
+              case None => /* there is no valid starting date in the entire interval!*/
+                  min
+              case Some(_) =>  /**/
+                r.getStart.nextValidDate(from,to,offset) match {
+                  case None => /* so there is at least one starting date before offset
+                                  and there are no starting dates after. So we are inside
+                                  an interval*/
+                    if(from.getTime <= offset.getTime && offset.getTime <= to.getTime)
+                       minimum(min,offset,d_end)
+                    else
+                       min
+                  case Some(d_start) =>
+                     if(d_start.getTime > d_end.getTime) {
+                        /* there is another starting date which occurs after the ending date.
+                        *  So we are within the interval*/
+                       minimum(min,offset,d_end)
+                     } else { /* we are outside the interval*/
+                       min
+                     }
+                }
+            }
+     }
+   }
+  }
+
+
+  private def minValidAfter(t:Timeslot,offset:Date) : Option[Timeslot] =
+       repeat.foldLeft  (None:Option[Timeslot]) {
+        (min,r)  =>
+          r.getStart.nextValidDate(t.from,t.to,offset) match {
+            case None =>
+              min
+            case Some(d_start) =>
+              r.getEnd.nextValidDate(t.from,t.to,d_start) match {
+                case None =>
+                  min
+                case Some(d_end) =>
+                  min match {
+                    case None =>
+                      Some(Timeslot(d_start,d_end))
+                    case Some(Timeslot(d_min,_)) =>
+                      if(d_min.getTime < d_start.getTime)
+                        min
+                      else
+                        Some(Timeslot(d_start,d_end))
+                  }
+              }
+          }
+     }
+
   override def toMap(): Map[String, Any] = {
     val toTS = to match {
       case Some(x) => Map(Vocabulary.to -> x.getTime)
@@ -71,18 +206,100 @@ case class DSLTimeFrame (
     }
 
     toTS ++
-    Map(Vocabulary.from -> from.getTime) ++
-    repeatMap
+      Map(Vocabulary.from -> from.getTime) ++
+      repeatMap
   }
 
-  override def toString = "%s(%s, %s, %s)".format(
+  override def toString =
+    //"%s(%s, %s,\n %s\n || cron: %s)".format(
+    "%s(%s, %s,%s)".format(
     shortNameOfClass(classOf[DSLTimeFrame]),
     new MutableDateCalc(from).toString,
     to.map(t => new MutableDateCalc(t)),
-    repeat
+    repeat /*,
+    cronRepeat*/
   )
+
+  /*def includes(d :Date) : Boolean = {
+    val now = d.getTime
+    start <= now && now <= end && (cronRepeat.isEmpty || (cronRepeat.exists {_ occurs d}))
+  } */
+
+  /*/*  Return the next VALID, MAXIMUM and CONTINUOUS date after "d" for this time frame.
+   *  The *returned time* is the end of the time frame if there exist no cron specs.
+   *  Otherwise, the returned time is maximum of the dates that
+   *  1. are continuous (no interruptions) from "d"
+   *  2. occur after "d"
+   *  3. belong in the time frame
+   *
+   *  The input parameter "d" must occur within the time frame. If there exist
+   *  cron specs "d" must belong in the time spec specified by cron.
+   */
+  def nextValidAfter1(d: Date) : Option[Date] = {
+    val now = d.getTime
+    if (now < start || now > end) None /* undefined valid is date not contained here*/
+    else if (cronRepeat.isEmpty) Some(to.getOrElse(infinity)) /* No specs! The next valid date is the end of time frame*/
+    else {
+      /* filter out cron specs that DO NOT contain d */
+      val includes_d = cronRepeat filter { _ includes d }
+    /* get the next CONTINUOUS valid date from "d" using the DSLCronSpec class */
+      val next_cont_d =  includes_d  map {_ nextContinuousValidDate d}
+      /* filter the resulting dates "d1" so that they occur after "d" and are within the timeframe bounds*/
+      val filter_invalid = next_cont_d filter {d1:Date =>  d1 != null &&
+                                                           d1.getTime > now &&
+                                                           start <= d1.getTime &&
+                                                           d1.getTime <= end}
+      /* sort the next dates and select the MAXIMUM one*/
+      val sorted = filter_invalid  sortWith {_.getTime > _.getTime}
+      sorted match {
+        case Nil => None /* Nothing interesting was found*/
+        case hd::_ => if(hd!=null) Some(hd) else None  /* the MAXIMAL next date */
+      }
+    }
+  }
+  var time_var : List[Long] = Nil
+  private def time[R](block: => R): R = {
+    val t0 = System.nanoTime()
+    val result = block    // call-by-name
+    val t1 = System.nanoTime()
+    time_var =  ((t1-t0)/1000000L) :: time_var
+    //Console.err.println("Time__xxxx: " + time_var)
+    result
+  } */
+  /*/* the nextValidDate cannot be outside the end limit of this time frame
+     Description: given date "d" get the nextValidDate after "d"
+   * */
+  def nextValidAfter(d:Date) : Option[Date] = {
+     def nextDate(r:DSLTimeFrameRepeat) : Option[Date] =
+       time(r.getStart.getMaxValidBeforeDate(from,to.getOrElse(infinity),d)) match {
+         case None =>
+           None
+         case Some(min) =>
+           r.getEnd.nextValidDate(from,to.getOrElse(infinity),min) match {
+             case None =>
+               None
+             case Some(d_max) =>
+               if(d_max.getTime < d.getTime)
+                 None
+               else
+                 Some(d_max)
+           }
+
+       }
+    if(repeat.isEmpty){
+      val d_time = d.getTime
+      if(d_time < start || d_time > end) None
+      else Some(to.getOrElse(infinity))
+    }
+    else {
+      var tmpDate : Option[Date] = null
+      repeat.collectFirst {
+        case r  if({tmpDate=nextDate(r);tmpDate} != None) => tmpDate.get
+      }
+    }
+  }*/
 }
 
 object DSLTimeFrame{
-  val emptyTimeFrame = DSLTimeFrame(new Date(0), None, List())
+  val emptyTimeFrame = DSLTimeFrame(new Date(0), None, List())//,List())
 }
\ No newline at end of file
index 3495913..3d592d4 100644 (file)
@@ -35,6 +35,9 @@
 
 package gr.grnet.aquarium.logic.accounting.dsl
 
+import org.quartz.CronExpression
+import java.util.Date
+
 /**
  * Encapsulates a repeating item
  *
@@ -47,6 +50,20 @@ case class DSLTimeFrameRepeat (
   endCron: String
 ) extends DSLItem {
 
+  private def makeCronExpression(s: String) : CronExpression  = {
+    val e = "0 " + s.trim
+    val l = e.split(" ")
+    (l(3),l(5))  match {
+      case ("?",_) | (_,"?") => ()
+      case (_,"*") => l.update(5,"?")
+      case ("*",_) => l.update(3,"?")
+    }
+    val e1 = l.foldLeft("") { (s,elt) => s + " " + elt}
+    new CronExpression(e1)
+  }
+  val getStart = DSLCronSpec(startCron)
+  val getEnd =  DSLCronSpec(endCron)
+
   assert(start.size == end.size,
     ("start (%s) and end (%s) cron-like specs do not expand to equal" +
       " number of repetition definitions").format(startCron, endCron))
index b8b56c2..a1cf615 100644 (file)
@@ -68,37 +68,36 @@ import collection.mutable
   assert(-1 <= mon && 12 > mon && mon != 0)
   assert(-1 <= dow && 7 > dow)
 
+  /* calendar-related methods*/
+  private val cal = new GregorianCalendar()
+  private def initDay(init:Long) {
+    cal.setTimeInMillis(init)
+    cal.set(Calendar.MINUTE,min)
+    cal.set(Calendar.HOUR_OF_DAY,hour)
+    cal.set(Calendar.SECOND,0)
+  }
+  private def nextValidDay(end:Long) (): Date = {
+    var ret : Date = null
+    while (cal.getTimeInMillis <= end && ret == null) {
+      val valid : Boolean = (mon < 0  || cal.get(Calendar.MONTH) == getCalendarMonth()) &&
+                            (dom < 0  || cal.get(Calendar.DAY_OF_MONTH) == dom) &&
+                            (dow < 0  || cal.get(Calendar.DAY_OF_WEEK) == getCalendarDow())
+      if(valid)
+         ret = cal.getTime
+      cal.add(Calendar.DAY_OF_YEAR, 1)
+    }
+    ret
+  }
 
   def expandTimeSpec(from: Date,  to: Date) : List[Date] = {
-    val c = new GregorianCalendar()
-    c.setTime { /*adjust time given from "from" variable  */
-      c.setTime(from)
-      c.set(Calendar.MINUTE,this.min)
-      c.set(Calendar.HOUR_OF_DAY,this.hour)
-      /*if(c.getTimeInMillis < from.getTime) {
-        //c.add(Calendar.DAY_OF_YEAR, 1)
-       // assert(c.getTimeInMillis >= from.getTime,c.getTime + " >=" + from) /* sanity check */
-      }*/
-      c.getTime
-   }
-   def equals () : Boolean =
-      (this.mon < 0  || c.get(Calendar.MONTH) == this.getCalendarMonth()) &&
-      (this.dom < 0  || c.get(Calendar.DAY_OF_MONTH) == this.dom) &&
-      (this.dow < 0  || c.get(Calendar.DAY_OF_WEEK) == this.getCalendarDow())
-
     val result = new mutable.ListBuffer[Date]()
-    //Console.err.println("\n\nBEGIN\n\n")
-    while (c.getTimeInMillis <= to.getTime) {
-      val b : Boolean = equals
-      //Console.err.println(c.getTime +  "\t\t"  + to + " ==>" + (if(b) "YES" else "NO"))
-      if (b) result += new Date(c.getTime.getTime)
-      c.add(Calendar.DAY_OF_YEAR, 1)
-    }
-    //Console.err.println("\n\nEND\n\n")
+    val nextDay = this.nextValidDay (to.getTime) _
+    var d : Date = null
+    initDay(from.getTime)
+    while({d=nextDay();d}!=null) result += d
     result.toList
   }
 
-
   /** Day of week conversions to stay compatible with [[java.util.Calendar]] */
   private def getCalendarDow(): Int = dow match {
     case 0 => Calendar.SUNDAY
@@ -129,5 +128,22 @@ import collection.mutable
 }
 
 object DSLTimeSpec {
-  val emtpyTimeSpec = DSLTimeSpec(0, 0, 0, 0, 0)
+  val emtpyTimeSpec = DSLTimeSpec(0,0, -1, -1, -1)
+
+  def expandTimeSpec(d0:DSLTimeSpec,d1:DSLTimeSpec, from: Date,  to: Date) : List[(Date,Date)] = {
+    assert(d0!=null)
+    assert(d1!=null)
+    assert(from!=null)
+    assert(to!=null)
+    val result = new mutable.ListBuffer[(Date,Date)]()
+    val d0_nextDay = d0.nextValidDay (to.getTime) _ /* iterator for valid dates of d0*/
+    val d1_nextDay = d1.nextValidDay (to.getTime) _ /* iterator for valid dates of d1*/
+    var d0_d : Date = null
+    d0.initDay(from.getTime)
+    while({d0_d=d0_nextDay();d0_d}!=null){
+      val d1_d : Date = {d1.initDay(d0_d.getTime);d1_nextDay()}
+      if(d1_d!=null) result += ((d0_d,d1_d))
+    }
+    result.toList
+  }
 }
\ No newline at end of file
index f71bbbf..c2ce109 100644 (file)
@@ -68,13 +68,95 @@ trait DSLUtils extends DateUtils {
   immutable.SortedMap[Timeslot, DSLPriceList] =
     resolveEffective[DSLPriceList](timeslot,agr.pricelist)
 
+  /*private def printPolicy[T <: DSLTimeBoundedItem[T]](t : T) : Unit = {
+    Console.err.println("Policy " + t.name + " " + t.toTimeslot + " DETAIL : " + t.effective)
+    t.overrides match {
+      case None => Console.println
+      case Some(t) => printPolicy(t)
+    }
+  }
+
+  private def printMap[T <: DSLTimeBoundedItem[T]](m: immutable.SortedMap[Timeslot, T]) = {
+    Console.err.println("BEGIN MAP: ")
+    for { (t,p) <- m.toList } Console.err.println("Timeslot " + t + "\t\t" + p.name)
+    Console.err.println("END MAP")
+  } */
+
+  def resolveEffective[T <: DSLTimeBoundedItem[T]](timeslot0: Timeslot,policy: T):
+  immutable.SortedMap[Timeslot, T] = {
+    //Console.err.println("\n\nInput timeslot: " + timeslot0 + "\n\n")
+    ///printPolicy(policy)
+    val ret =  resolveEffective3(timeslot0,policy) //HERE
+    //printMap(ret)
+    ret
+  }
+
+  def resolveEffective3[T <: DSLTimeBoundedItem[T]](timeslot0: Timeslot,policy: T):
+  immutable.SortedMap[Timeslot, T] = {
+    assert(policy.toTimeslot contains timeslot0,"Policy does not contain timeslot")
+    val timeslot = timeslot0 //TODO: timeslot0.align(5000)
+    val subtimeslots_of_this_policy = Timeslot.mergeOverlaps(policy.effective intervalsOf timeslot)
+    val subtimeslots_NOT_IN_this_policy = Timeslot.mergeOverlaps(timeslot.nonOverlappingTimeslots
+                                                                                          (subtimeslots_of_this_policy))
+    val policy_map =  subtimeslots_of_this_policy.foldLeft  (immutable.SortedMap[Timeslot, T]())
+                                                            {(map,t) =>
+                                                                  //Console.err.println("Adding timeslot" + t + " for policy " + policy.name)
+                                                                  map + ((t,policy))
+                                                            }
+    val other_policy_map = policy.overrides match {
+                                              case None =>
+                                                 immutable.SortedMap[Timeslot, T]()
+                                              case Some(parent_policy)=>
+                                                  subtimeslots_NOT_IN_this_policy.foldLeft (
+                                                      (immutable.SortedMap[Timeslot, T]()))
+                                                      {(map,t) =>
+                                                        //Console.err.println("Residual timeslot: " + t)
+                                                        map ++ resolveEffective3(t,parent_policy)
+                                                      }
+                                            }
+    val final_map = policy_map ++ other_policy_map
+    final_map
+  }
+
+  /*def resolveEffective2[T <: DSLTimeBoundedItem[T]](timeslot0: Timeslot,policy: T):
+  immutable.SortedMap[Timeslot, T] = {
+    assert(policy.toTimeslot contains timeslot0,"Policy does not contain timeslot")
+
+    /* generate mappings from timeslots -> policies
+     * Algorithm: find next valid date (starting from timeslot.start) in this policy
+     */
+    val timeslot = timeslot0 //TODO: timeslot0.align(5000)
+    def nextDate(d:Date,p:T) : Option[(Date,T,Boolean)] =
+      (p.effective nextValidAfter d,p.overrides) match {
+        case (None,None) => None
+        case (None,Some(parent_policy)) =>
+          val d1 = nextDate(d,parent_policy)
+          d1
+        case (Some(d1),_) => /* the next valid date cannot occur after the end of timeslot*/
+           if (d1.before(timeslot.to)) Some((d1,p,true)) else Some((timeslot.to,p,false))
+      }
+    def genMap(map: immutable.SortedMap[Timeslot, T],d:Date) : immutable.SortedMap[Timeslot, T] = {
+        val step = 1000L
+        nextDate(d,policy) match {
+        case None => map
+        case Some((d1,policy,cont)) =>
+          val t = Timeslot(d,d1)
+          val map1 = map + (t -> policy)
+          if(cont) genMap(map1,new Date(d1.getTime + step)) // 1 second after d1
+          else map1 /* done */
+      }
+    }
+    val map = genMap(immutable.SortedMap[Timeslot, T](),timeslot.from)
+    map
+  }
+
   /**
    * Splits the provided timeslot into chunks according to the validity
    * timeslots specified by the provided time bounded item. It
    * returns a map whose keys are the timeslot chunks and the values
    * correspond to the effective time bounded item (algorithm or pricelist).
    */
-  def resolveEffective[T <: DSLTimeBoundedItem[T]](timeslot: Timeslot,policy: T):
+  def resolveEffective1[T <: DSLTimeBoundedItem[T]](timeslot: Timeslot,policy: T):
   immutable.SortedMap[Timeslot, T] = {
       assert(policy.toTimeslot contains timeslot,"Policy does not contain timeslot")
 
@@ -87,26 +169,23 @@ trait DSLUtils extends DateUtils {
      val timeslots_IN_policy = timeslot.overlappingTimeslots(all_timeslots)
      val timeslots_NOT_IN_policy = timeslot.nonOverlappingTimeslots(all_timeslots)
 
-      immutable.SortedMap[Timeslot, T]() ++
+     val ret =  immutable.SortedMap[Timeslot, T]() ++
       /*add [timeslots -> policy] covered by this policy*/
       timeslots_IN_policy.flatMap {
-         effective_timeslot => Map(effective_timeslot -> policy)
+         effective_timeslot =>  Map(effective_timeslot -> policy)
       } ++
       /*add [timeslots -> policy] covered by parent policies */
       timeslots_NOT_IN_policy.flatMap { /* search the policy hierarchy for effective timeslots not covered by this policy.*/
         not_effective_timeslot => policy.overrides match {
           case None => immutable.SortedMap[Timeslot, T]() /*Nothing to do. TODO: throw exception ?*/
-          case Some(parent_policy) => resolveEffective(not_effective_timeslot,parent_policy) /* search the policy hierarchy*/
+          case Some(parent_policy) => resolveEffective1(not_effective_timeslot,parent_policy) /* search the policy hierarchy*/
         }
       }
-  }
-
-  /**
-   * Utility function to put timeslots in increasing start timestamp order
-   */
-  private def sorter(x: Timeslot, y: Timeslot) : Boolean =   y.from after x.from
+    ret
+  }*/
 
-  /**
+   /*
+  /*
    * Get a list of all timeslots within which a timeframe
    * is effective, whithin the provided time bounds.
    */
@@ -117,34 +196,30 @@ trait DSLUtils extends DateUtils {
       val toDate = if (spec.to.getOrElse(t.to).after(t.to)) t.to else spec.to.getOrElse(t.to)
       List(Timeslot(fromDate, toDate))
     } /* otherwise for all repetitions determine timeslots*/
-    else mergeOverlaps(for { r <- spec.repeat
-                            ts <- effectiveTimeslots(r, t.from, t.to) }
-                       yield ts)
+    else {
+        val all =  for { r <- spec.repeat
+                         ts <- effectiveTimeslots(r, t.from, t.to)
+                      }  yield ts
+      //for{ i <- all} Console.err.println(i)
+        mergeOverlaps(all)
+    }
+  */
+
+
 
   /**
    * Merges overlapping timeslots.
    */
-  private[logic] def mergeOverlaps(list: List[Timeslot]): List[Timeslot] =
-    (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
 
-
-  /**
+  /*/**
    * Get a list of all timeslots within which a time frame is active.
      The result is returned sorted by timeframe start date.
    */
   def effectiveTimeslots(spec: DSLTimeFrameRepeat, from: Date, to : Date):
     List[Timeslot] =
       for { (h1,h2) <- spec.start zip spec.end
-            (d1,d2) <- h1.expandTimeSpec(from, to) zip h2.expandTimeSpec(from, to)
+            (d1,d2) <- DSLTimeSpec.expandTimeSpec(h1,h2,from,to)
           }
       yield Timeslot(d1,d2)
+      */
 }
index bc6860c..9ac8590 100644 (file)
@@ -52,7 +52,7 @@ final case class Timeslot(from: Date, to: Date)
   /* Preconditions to ensure correct object creations */
   assert(from != null)
   assert(to != null)
-  assert(from.before(to), "from = %s, to = %s".format(new MutableDateCalc(from), new MutableDateCalc(to)))
+  assert(start <= end, "from = %s, to = %s".format(new MutableDateCalc(from), new MutableDateCalc(to)))
 
   def startsBefore(t: Timeslot) : Boolean =  start < t.start
 
@@ -82,7 +82,7 @@ final case class Timeslot(from: Date, to: Date)
   /**
    * Check whether this timeslot contains the provided time instant.
    */
-  private def includes(t: Date) : Boolean = start <= t.getTime &&  t.getTime <= end
+  private[dsl] def includes(t: Date) : Boolean = start <= t.getTime &&  t.getTime <= end
 
 
   /**
@@ -179,6 +179,21 @@ final case class Timeslot(from: Date, to: Date)
     }
   }
 
+  /* 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)
+  }
+
+  /* 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
+
+
   /**
    * Compares the starting times of two timeslots.
    */
@@ -198,7 +213,8 @@ final case class Timeslot(from: Date, to: Date)
 
   def myString : String = "Timeslot(" + this.start + "," + this.end + ")"
   //override def toString() = myString
-  override def toString() = toDateString
+  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)
@@ -210,4 +226,18 @@ object Timeslot {
 
   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
index 2c1a435..b504e5a 100644 (file)
@@ -52,6 +52,7 @@ aquariumpolicy:
       overrides: default
       diskspace: 0
       effective:
+        #cron: ["00 00 12-14,18-20 * *"] #5 fields!!
         repeat:
           - start: "00 00 12 * *"
             end:   "00 00 14 * *"
@@ -79,6 +80,7 @@ aquariumpolicy:
       vmtime: 0.1
       diskspace: 0.05
       effective:
+        #cron: ["* 02 * * Tue,Wed"]
         repeat:
           - start: "00 02 * * Tue"
             end:   "00 02 * * Wed"
@@ -91,6 +93,7 @@ aquariumpolicy:
       vmtime: 0.6
       diskspace: 0.05
       effective:
+        #cron : ["* 12-14,18-20 * * *"] 
         repeat:
           - start: "00 12 * * *"
             end:   "00 14 * * *"
index 976dd68..e42425d 100644 (file)
@@ -73,37 +73,48 @@ class DSLUtilsTest extends DSLTestBase with DSLUtils with TestMethods {
     val to =  new Date(1324214719000L)   //Sun Dec 18 15:25:19 +0200 2011
 
     var a = DSLTimeSpec(33, 12, -1, -1, 3)
-    var result = a.expandTimeSpec(from, to)
+    var b = DSLTimeSpec(34, 12, -1, -1, 3)
+    var result = DSLTimeSpec.expandTimeSpec(a,b,from, to)
     assertEquals(4, result.size)
 
     a = DSLTimeSpec(33, 12, -1, 10, 3)   // Timespec falling outside from-to
-    result = a.expandTimeSpec(from, to)
+    b = DSLTimeSpec(34, 12, -1, -1, 3)
+    result = DSLTimeSpec.expandTimeSpec(a,b,from, to)
     assertEquals(0, result.size)
 
     // Would only return an entry if the 1rst of Dec 2011 is Thursday
     a = DSLTimeSpec(33, 12, 1, -1, 3)
-    result = a.expandTimeSpec(from, to)
+    b = DSLTimeSpec(34, 12, 1, -1, 3)
+    result = DSLTimeSpec.expandTimeSpec(a,b,from, to)
     assertEquals(0, result.size)
 
     // The 9th of Dec 2011 is Friday
     //Console.err.println("\n\nBEGIN CALCULATION\t\t" + from + "\t\t" + to +  "\n\n")
     a = DSLTimeSpec(33, 12, 9, -1, 5)
-    result = a.expandTimeSpec(from, to)
+    b = DSLTimeSpec(34, 12, 9, -1, 5)
+    result = DSLTimeSpec.expandTimeSpec(a,b,from, to)
     //Console.err.println("\n\nEND CALCULATION: " + result +"\n\n")
     assertEquals(1, result.size)
 
     // Every day
     a = DSLTimeSpec(33, 12, -1, -1, -1)
-    result = a.expandTimeSpec(from, to)
+    b = DSLTimeSpec(34, 12, -1, -1, -1)
+    result = DSLTimeSpec.expandTimeSpec(a,b,from, to)
     assertEquals(31, result.size)
 
-
-    //Console.err.println("\n\n@BEGIN CALCULATION\t\t" + from + "\t\t" + to +  "\n\n")
-    a = DSLTimeSpec(33, 12, -1, -1, 5)
-    result = a.expandTimeSpec(from, to)
-    //Console.err.println("\n\n@END CALCULATION: " + result +"\n\n")
-//    assertEquals(1, result.size)
-
+    from.setTime(1340614800000L) //  06/25/2012 12:00:00
+    to.setTime(1340982000000L)   // 06/29/2012 18:00:00
+    a = DSLTimeSpec(00, 12, -1, -1, 1 ) // monday at 12:00
+    b = DSLTimeSpec(00, 19, -1, -1, 5) //  friday at 19:00
+    result = DSLTimeSpec.expandTimeSpec(a,b,from, to)
+    assert(result.size==0)
+
+    from.setTime(1340614800000L) //  06/25/2012 12:00:00
+    to.setTime(1341068400000L)   // 06/30/2012 18:00:00
+    a = DSLTimeSpec(00, 12, -1, -1, 1 ) // monday at 12:00
+    b = DSLTimeSpec(00, 19, -1, -1, 5) //  friday at 19:00
+    result = DSLTimeSpec.expandTimeSpec(a,b,from, to)
+    assert(result.size==1)
   }
 
   /*@Test
@@ -122,32 +133,7 @@ class DSLUtilsTest extends DSLTestBase with DSLUtils with TestMethods {
     assertEquals(34, result.size)
   }*/
 
-  @Test
-  def testMergeOverlaps = {
-    var l = List(Timeslot(new Date(3), new Date(5)),Timeslot(new Date(1), new Date(3)))
-
-    var result = mergeOverlaps(l)
-    assertEquals(1, result.size)
-    assertEquals(Timeslot(new Date(1), new Date(5)), result.head)
-
-    l = l ++ List(Timeslot(new Date(4), new Date(6)))
-    result = mergeOverlaps(l)
-    assertEquals(1, result.size)
-    assertEquals(Timeslot(new Date(1), new Date(6)), result.head)
-
-    l = l ++ List(Timeslot(new Date(7), new Date(8)))
-    result = mergeOverlaps(l)
-    assertEquals(2, result.size)
-    assertEquals(Timeslot(new Date(1), new Date(6)), result.head)
-    assertEquals(Timeslot(new Date(7), new Date(8)), result.tail.head)
-
-    l = l ++ List(Timeslot(new Date(2), new Date(20)))
-    result = mergeOverlaps(l)
-    assertEquals(1, result.size)
-    assertEquals(Timeslot(new Date(1), new Date(20)), result.head)
-  }
-
-  @Test
+  /*@Test
   def testEffectiveTimeslots = {
     val from =  new Date(1321621969000L) //Fri Nov 18 15:12:49 +0200 2011
     val to =  new Date(1324214719000L)   //Sun Dec 18 15:25:19 +0200 2011
@@ -200,9 +186,9 @@ class DSLUtilsTest extends DSLTestBase with DSLUtils with TestMethods {
     result = effectiveTimeslots(repeat, new Date(1304121600000L),
       new Date(1319932800000L))
     assertNotEmpty(result)
-  }
+  } */
 
-  @Test
+  /*@Test
   def testAllEffectiveTimeslots = {
     var from = new Date(1321621969000L) //Fri Nov 18 15:12:49 +0200 2011
     val to =  new Date(1324214719000L)   //Sun Dec 18 15:25:19 +0200 2011
@@ -217,16 +203,16 @@ class DSLUtilsTest extends DSLTestBase with DSLUtils with TestMethods {
       parseCronString("00 20 * * 5"),
       "00 18 * * 5",
       "00 20 * * 5")
-    val tf = DSLTimeFrame(from, None, List(repeat1, repeat2))
+   // val tf = DSLTimeFrame(from, None, List(repeat1, repeat2),Nil)
 
-    var result = allEffectiveTimeslots(tf, Timeslot(from, to))
+    /*var result = allEffectiveTimeslots(tf, Timeslot(from, to))
     assertEquals(36, result.size)
     testSuccessiveTimeslots(result)
 
-    result = allEffectiveTimeslots(DSLTimeFrame(new Date(0), None, List()),
+    result = allEffectiveTimeslots(DSLTimeFrame(new Date(0), None, List(),Nil),
       Timeslot(new Date(14), new Date(40)))
-    assertEquals(1, result.size)
-  }
+    assertEquals(1, result.size)*/
+  } */
 
  /* @Test
   def testNonEffectiveTimeslots = {
@@ -265,6 +251,26 @@ class DSLUtilsTest extends DSLTestBase with DSLUtils with TestMethods {
 
     return
   } */
+    private def times[R](times:Int,block: => R): R = {
+     val count = times
+     val (ret,s) =  time(block)
+     var sum = s
+     for { i <- 2 to times} {
+       val (_,t) = time(block)
+       sum += t
+       //System.err.println("time" + t +  " count " + count)
+     }
+     //Console.err.println("Elapsed time: " + (sum/count)/1000000L + "\tms")
+     ret
+   }
+
+    private def time[R](block: => R): (R,Long) = {
+     val t0 = System.nanoTime()
+     val result = block    // call-by-name
+     val t1 = System.nanoTime()
+     (result,t1-t0)
+    }
+
 
   @Test
   def testFindEffective = {
@@ -278,9 +284,12 @@ class DSLUtilsTest extends DSLTestBase with DSLUtils with TestMethods {
     val ts5 = 1322689082000L //Wed, 30 Nov 2011 23:38:02 EET
     val ts6 = 1322555880000L //Tue, 29 Nov 2011 10:38:00 EET
 
-    var pricelists = resolveEffectivePricelistsForTimeslot(Timeslot(new Date(ts1), new Date(ts2)), agr)
+    var check = new Timeslot(new Date(1322649482000L), new Date(1322654400000L))
+    //Console.err.println("Check " + check)
+    var pricelists = times(1,resolveEffectivePricelistsForTimeslot(Timeslot(new Date(ts1), new Date(ts2)), agr))
+
     assertEquals(2, pricelists.keySet.size)
-    assertNotNone(pricelists.get(new Timeslot(new Date(1322654402000L), new Date(1322656682000L))))
+    assertNotNone(pricelists.get(check))
     assertEquals("foobar", pricelists.head._2.name)
 
     pricelists = resolveEffectivePricelistsForTimeslot(Timeslot(new Date(ts2), new Date(ts3)), agr)
@@ -295,7 +304,7 @@ class DSLUtilsTest extends DSLTestBase with DSLUtils with TestMethods {
     pricelists = resolveEffectivePricelistsForTimeslot(Timeslot(new Date(ts1), new Date(ts5)), agr)
     assertEquals(4, pricelists.keySet.size)
 
-    pricelists = resolveEffectivePricelistsForTimeslot(Timeslot(new Date(ts6), new Date(ts5)), agr)
+    pricelists = times(1,resolveEffectivePricelistsForTimeslot(Timeslot(new Date(ts6), new Date(ts5)), agr))
     assertEquals(9, pricelists.keySet.size)
   }
 
index 2224136..9bc9632 100644 (file)
@@ -81,9 +81,10 @@ class PerfTest extends DSLUtils with DSL {
         var rndEnd = new Date((min + (scala.math.random * (max - min) + 1)).toLong)
 
         while (rndEnd.before(rndStart)) rndEnd = new Date((min + (scala.math.random * (max - min) + 1)).toLong)
-        val tf = DSLTimeFrame(rndStart, Some(rndEnd), List(repeat1, repeat2))
+        val tf = DSLTimeFrame(rndStart, Some(rndEnd), List(repeat1, repeat2))//,Nil)
 
-        numResolved += allEffectiveTimeslots(tf, Timeslot(new Date(min), new Date(max))).size
+        numResolved += tf.intervalsOf(Timeslot(new Date(min), new Date(max))).size
+          //allEffectiveTimeslots(tf, Timeslot(new Date(min), new Date(max))).size
     }
 
     var total = TimeHelpers.nowMillis() - start
index b184eef..9a259f9 100644 (file)
@@ -109,4 +109,31 @@ class TimeslotTest extends TestMethods {
     assertEquals(1, result.size)
     assertEquals(t, result.head)
   }
+
+  @Test
+  def testMergeOverlaps = {
+    var l = List(Timeslot(new Date(3), new Date(5)),Timeslot(new Date(1), new Date(3)))
+
+    var result = Timeslot.mergeOverlaps(l)
+    assertEquals(1, result.size)
+    assertEquals(Timeslot(new Date(1), new Date(5)), result.head)
+
+    l = l ++ List(Timeslot(new Date(4), new Date(6)))
+    result = Timeslot.mergeOverlaps(l)
+    assertEquals(1, result.size)
+    assertEquals(Timeslot(new Date(1), new Date(6)), result.head)
+
+    l = l ++ List(Timeslot(new Date(7), new Date(8)))
+    result = Timeslot.mergeOverlaps(l)
+    assertEquals(2, result.size)
+    assertEquals(Timeslot(new Date(1), new Date(6)), result.head)
+    assertEquals(Timeslot(new Date(7), new Date(8)), result.tail.head)
+
+    l = l ++ List(Timeslot(new Date(2), new Date(20)))
+    result = Timeslot.mergeOverlaps(l)
+    assertEquals(1, result.size)
+    assertEquals(Timeslot(new Date(1), new Date(20)), result.head)
+  }
+
+
 }
\ No newline at end of file