Skip to content

Commit

Permalink
Support for inclusive/exclusive date bounds
Browse files Browse the repository at this point in the history
Closes #103
  • Loading branch information
hrj committed Sep 11, 2016
1 parent 13146fb commit 775d082
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 22 deletions.
41 changes: 31 additions & 10 deletions base/src/main/scala/co/uproot/abandon/Config.scala
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ object SettingsHelper {
val accountConfigs = config.optConfigList("accounts").getOrElse(Nil)
val accounts = accountConfigs.map(makeAccountSettings)
val eodConstraints = config.optConfigList("eodConstraints").getOrElse(Nil).map(makeEodConstraints(_))
val dateConstraints = config.optConfigList("dateConstraints").getOrElse(Nil).map(makeDateConstraint(_))
val dateConstraints = config.optConfigList("dateConstraints").getOrElse(Nil).map(makeDateRangeConstraint(_))
Right(Settings(inputs, eodConstraints ++ dateConstraints, accounts, reports, ReportOptions(isRight), exports, Some(file)))
} catch {
case e: ConfigException => Left(e.getMessage)
Expand All @@ -101,23 +101,23 @@ object SettingsHelper {
}
}

private def getDate(name: String, config: Config) : Option[Date] = {
private def getDateBound(name: String, config: Config) : Option[DateBound] = {
import AbandonParser.{Success, NoSuccess}

config.optional(name) { _.getString(_) } match {
case Some(valueStr) =>
val parseResult = AbandonParser.dateExpr(ParserHelper.scanner(valueStr))
val parseResult = AbandonParser.dateBoundExpr(ParserHelper.scanner(valueStr))
parseResult match {
case Success(date, _) => Option(date)
case Success(dateBound, _) => Option(dateBound)
case NoSuccess(_, _) =>
throw new ConfigException.BadValue(config.origin, name, "expected date expression")
throw new ConfigException.BadValue(config.origin, name, "expected a date bound of the form: <date> <inclusive|exclusive>")
}
case None => None
}
}

private def makeDateConstraint(config: Config): DateConstraint = {
DateConstraint(getDate("from", config), getDate("to", config))
private def makeDateRangeConstraint(config: Config): DateRangeConstraint = {
DateRangeConstraint(getDateBound("from", config), getDateBound("to", config))
}

def makeReportSettings(config: Config) = {
Expand Down Expand Up @@ -228,16 +228,37 @@ case class NegativeConstraint(val accName: String) extends Constraint with SignC
val signStr = "negative"
}

case class DateConstraint(dateFrom: Option[Date], dateTo: Option[Date]) extends Constraint {
case class DateBound(date: Date, inclusive: Boolean) {
def isNotEarlierThan(that: Date) = {
if (inclusive) {
DateOrdering.compare(this.date, that) > 0
} else {
DateOrdering.compare(this.date, that) >= 0
}
}

def isNotLaterThan(that: Date) = {
if (inclusive) {
DateOrdering.compare(this.date, that) < 0
} else {
DateOrdering.compare(this.date, that) <= 0
}
}
}

case class DateRangeConstraint(dateFromOpt: Option[DateBound], dateToOpt: Option[DateBound]) extends Constraint {
override def check(appState: AppState): Boolean = {
appState.accState.posts.find(post => {
val date = post.date

dateFrom.map(DateOrdering.compare(_, date) > 0).getOrElse(false) || dateTo.map(DateOrdering.compare(_, date) < 0).getOrElse(false)
val fromFails = dateFromOpt.map(_.isNotEarlierThan(date)).getOrElse(false)
val toFails = dateToOpt.map(_.isNotLaterThan(date)).getOrElse(false)

fromFails || toFails
}).foreach(post => {
throw new ConstraintError(
s"${post.name} is not in date range of " +
s"[${dateFrom.getOrElse("None")}, ${dateTo.getOrElse("None")}]. " +
s"[${dateFromOpt.getOrElse("...")}, ${dateToOpt.getOrElse("...")}]. " +
s"Date is ${post.date.formatISO8601Ext}"
)
})
Expand Down
5 changes: 5 additions & 0 deletions base/src/main/scala/co/uproot/abandon/Parser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,11 @@ object AbandonParser extends StandardTokenParsers with PackratParsers {

lazy val dateExpr = dateFrag | isoDateFrag

lazy val dateBoundExpr = dateExpr ~ ("inclusive" | "exclusive") ^? {
case date ~ lusive =>
DateBound(date, lusive == "inclusive")
}

lazy val isoDateFrag = ((((integer <~ "-") ~ integer) <~ "-") ~ integer) ^? ({
case y ~ m ~ d if (isValidDate(y, m, d)) =>
Date(y, m, d)
Expand Down
57 changes: 45 additions & 12 deletions base/src/test/scala/co/uproot/abandon/DateConstraintTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,38 +16,71 @@ class DateConstraintTest extends FlatSpec with Matchers with BeforeAndAfterEach
}

it should "return true on valid posts" in {
val constraint = new DateConstraint(
Some(Date(2013, 1, 1)),
Some(Date(2013, 12, 31))
val constraint = new DateRangeConstraint(
Some(DateBound(Date(2013, 1, 1), inclusive = true)),
Some(DateBound(Date(2013, 12, 31), inclusive = false))
)

constraint.check(appState) should be(true)
}

it should "throw exception on end date violation" in {
val constraint = new DateConstraint(
Some(Date(2013, 1, 1)),
Some(Date(2013, 11, 1))
val constraint = new DateRangeConstraint(
Some(DateBound(Date(2013, 1, 1), inclusive = false)),
Some(DateBound(Date(2013, 11, 1), inclusive = false))
)
an [ConstraintError] should be thrownBy constraint.check(appState)

val constraintWithoutFrom = new DateConstraint(None, Some(Date(2013, 11, 1)))
val constraintWithoutFrom = new DateRangeConstraint(None, Some(DateBound(Date(2013, 11, 1), inclusive = true)))
an [ConstraintError] should be thrownBy constraintWithoutFrom.check(appState)
}

it should "throw exception on inclusive end date violation" in {
val constraint = new DateRangeConstraint(
Some(DateBound(Date(2013, 1, 1), inclusive = true)),
Some(DateBound(Date(2013, 11, 30), inclusive = true))
)
an [ConstraintError] should be thrownBy constraint.check(appState)

val constraintWithoutFrom = new DateRangeConstraint(None, Some(DateBound(Date(2013, 11, 30), inclusive = true)))
an [ConstraintError] should be thrownBy constraintWithoutFrom.check(appState)
}

it should "throw exception on exclusive from-date violation" in {
val constraint = new DateRangeConstraint(
Some(DateBound(Date(2013, 1, 1), inclusive = false)),
Some(DateBound(Date(2013, 12, 2), inclusive = false))
)
an [ConstraintError] should be thrownBy constraint.check(appState)

val constraintWithoutFrom = new DateRangeConstraint(Some(DateBound(Date(2013, 1, 1), inclusive = false)), None)
an [ConstraintError] should be thrownBy constraintWithoutFrom.check(appState)
}

it should "throw exception on exclusive to-date violation" in {
val constraint = new DateRangeConstraint(
Some(DateBound(Date(2013, 1, 1), inclusive = true)),
Some(DateBound(Date(2013, 12, 1), inclusive = false))
)
an [ConstraintError] should be thrownBy constraint.check(appState)

val constraintWithoutFrom = new DateRangeConstraint(None, Some(DateBound(Date(2013, 12, 1), inclusive = false)))
an [ConstraintError] should be thrownBy constraintWithoutFrom.check(appState)
}

it should "throw exception on start date violation" in {
val constraint = new DateConstraint(
Some(Date(2013, 6, 1)),
Some(Date(2013, 11, 1))
val constraint = new DateRangeConstraint(
Some(DateBound(Date(2013, 6, 1), inclusive = false)),
Some(DateBound(Date(2013, 12, 1), inclusive = false))
)
an [ConstraintError] should be thrownBy constraint.check(appState)

val constraintWithoutTo = new DateConstraint(Some(Date(2013, 11, 1)), None)
val constraintWithoutTo = new DateRangeConstraint(Some(DateBound(Date(2013, 11, 1), inclusive = false)), None)
an [ConstraintError] should be thrownBy constraintWithoutTo.check(appState)
}

it should "return true on empty dates" in {
val constraintWithoutTo = new DateConstraint(None, None)
val constraintWithoutTo = new DateRangeConstraint(None, None)
constraintWithoutTo.check(appState) should be(true)
}

Expand Down

0 comments on commit 775d082

Please sign in to comment.