diff --git a/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/Helpers.scala b/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/Helpers.scala index fd0dff6b..616385ac 100644 --- a/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/Helpers.scala +++ b/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/Helpers.scala @@ -16,6 +16,8 @@ package com.xiaomi.duckling.dimension.time +import java.time.temporal.ChronoUnit + import com.xiaomi.duckling.Types._ import com.xiaomi.duckling.dimension.implicits._ import com.xiaomi.duckling.dimension.time.enums.Grain @@ -23,6 +25,7 @@ import com.xiaomi.duckling.dimension.time.enums.Grain._ import com.xiaomi.duckling.dimension.time.form.{TimeOfDay, Month => _} import com.xiaomi.duckling.dimension.time.helper.TimeDataHelpers.hour import com.xiaomi.duckling.dimension.time.predicates.{TimeDatePredicate, TimePredicate} +import com.xiaomi.duckling.dimension.time.Types.InstantValue object Helpers { @@ -66,4 +69,20 @@ object Helpers { } else td2 } + def countGrains(start: InstantValue, end: InstantValue): Int = { + val a = start.datetime.toLocalDatetime + val b = end.datetime.toLocalDatetime + val n = start.grain match { + case NoGrain | Second => ChronoUnit.SECONDS.between(a, b) + case Minute => ChronoUnit.MINUTES.between(a, b) + case Hour => ChronoUnit.HOURS.between(a, b) + case Day => ChronoUnit.DAYS.between(a, b) + case Week => ChronoUnit.WEEKS.between(a, b) + case Month => ChronoUnit.MONTHS.between(a, b) + case Quarter => ChronoUnit.MONTHS.between(a, b) / 3 + case Year => ChronoUnit.YEARS.between(a, b) + } + n.intValue() + } + } diff --git a/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/Rules.scala b/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/Rules.scala index bba0a406..0fedefb9 100644 --- a/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/Rules.scala +++ b/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/Rules.scala @@ -376,8 +376,7 @@ trait Rules extends DimRules { case TimeOfDay(_, _) => Day case _ => NoGrain } - } else if (g == Year && td.timeGrain == Month) Year - else NoGrain + } else g val coarseDate = cycleNth(g, sign * v, roundGrain) tt(intersect(coarseDate, td)) } else None diff --git a/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/date/Rules.scala b/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/date/Rules.scala index b29ed1e7..8eca9936 100644 --- a/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/date/Rules.scala +++ b/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/date/Rules.scala @@ -18,7 +18,9 @@ package com.xiaomi.duckling.dimension.time.date import scalaz.std.string.parseInt import java.time.LocalDate +import java.util.regex.Pattern +import scala.collection.mutable import scala.util.Try import com.xiaomi.duckling.Types._ @@ -33,7 +35,7 @@ import com.xiaomi.duckling.dimension.time.duration.{isADecade, Duration, Duratio import com.xiaomi.duckling.dimension.time.enums.Grain._ import com.xiaomi.duckling.dimension.time.enums.Hint.{NoHint, RecentNominal, YearMonth} import com.xiaomi.duckling.dimension.time.enums.IntervalType.{Closed, Open} -import com.xiaomi.duckling.dimension.time.enums.{Grain, Hint} +import com.xiaomi.duckling.dimension.time.enums.{Grain, Hint, IntervalType} import com.xiaomi.duckling.dimension.time.helper.TimeDataHelpers._ import com.xiaomi.duckling.dimension.time.predicates.{EndOfGrainPredicate, SequencePredicate, TimeDatePredicate, _} import com.xiaomi.duckling.dimension.time.{GrainWrapper, TimeData} @@ -398,4 +400,38 @@ trait Rules extends DimRules { } } ) + + private val WeekX = Pattern.compile("(周|星期)([一二三四五六日天])") + private val WeekDayStr = "12345671234567" + + val ruleWeekXyz = Rule( + name = "周一周二", + pattern = List("((周|星期)[一二三四五六日天]){2,}".regex), + prod = regexMatch {case text :: _ => + val m = WeekX.matcher(text) + var pos = 0 + val days = mutable.Buffer[Int]() + while (m.find(pos)) { + val d = m.group(2) match { + case "一" => 1 + case "二" => 2 + case "三" => 3 + case "四" => 4 + case "五" => 5 + case "六" => 6 + case "日" | "天" => 7 + } + days += d + pos = m.end() + } + WeekDayStr.indexOf(days.mkString("")) match { + case -1 => None + case _ => + val td = TimeData( + TimeIntervalsPredicate(IntervalType.Closed, TimeDatePredicate(dayOfWeek = days.head), TimeDatePredicate(dayOfWeek = days.last), beforeEndOfInterval = true), + timeGrain = Day) + Token(Date, td) + } + } + ) } diff --git a/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/predicates.scala b/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/predicates.scala index 055d0a9e..4332ba5d 100644 --- a/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/predicates.scala +++ b/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/predicates.scala @@ -187,6 +187,11 @@ object predicates { case _ => false } + val isInterval: Predicate = { + case Token(Time, td: TimeData) => td.timePred.isInstanceOf[TimeIntervalsPredicate] + case _ => false + } + val isIntervalOfDay: Predicate = { case Token(Time, td: TimeData) => td.form match { diff --git a/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/repeat/Repeat.scala b/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/repeat/Repeat.scala index 022a9950..1ad9df2d 100644 --- a/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/repeat/Repeat.scala +++ b/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/repeat/Repeat.scala @@ -25,6 +25,7 @@ import com.xiaomi.duckling.dimension.time.grain.TimeGrain import com.xiaomi.duckling.dimension.time.Types._ import com.xiaomi.duckling.dimension.time.enums.Grain import com.xiaomi.duckling.dimension.time.form.Form +import com.xiaomi.duckling.dimension.time.Helpers.countGrains case object Repeat extends Dimension with Rules { override val name: String = "Repeat" @@ -32,10 +33,12 @@ case object Repeat extends Dimension with Rules { override val dimDependents: List[Dimension] = List(TimeGrain, Duration, Time) } -case class RepeatData(interval: Option[DurationData] = None, +case class RepeatData(interval: Option[DurationData] = None, // 间隔,如果与其它的配合,表示外层间隔 n: Option[Int] = None, start: Option[TimeData] = None, - workdayType: Option[WorkdayType] = None) + workdayType: Option[WorkdayType] = None, + repeatGrain: Option[Grain] = None, + repeatNFromInterval: Option[TimeData] = None) extends Resolvable { override def resolve(context: Context, @@ -48,7 +51,19 @@ case class RepeatData(interval: Option[DurationData] = None, } case None => (None, true) } - if (success) Some(RepeatValue(interval, n, instant, workdayType), false) + val repeatN = repeatNFromInterval match { + case Some(intervalTimeData) => + intervalTimeData.resolve(context, options) match { + case Some((tv: TimeValue, _)) => + tv.timeValue match { + case IntervalValue(start, end) => Some(countGrains(start, end)) + case _ => None + } + case _ => None + } + case _ => None + } + if (success) Some(RepeatValue(interval, n.orElse(repeatN), instant, repeatGrain = repeatNFromInterval.map(_.timeGrain), workdayType), false) else None } } @@ -63,6 +78,7 @@ case class RepeatData(interval: Option[DurationData] = None, case class RepeatValue(interval: Option[DurationData] = None, n: Option[Int] = None, start: Option[(TimeValue, Option[Form])] = None, + repeatGrain: Option[Grain] = None, workdayType: Option[WorkdayType] = None) extends ResolvedValue { diff --git a/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/repeat/Rules.scala b/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/repeat/Rules.scala index ff4aaea1..5c0ff131 100644 --- a/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/repeat/Rules.scala +++ b/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/repeat/Rules.scala @@ -22,11 +22,12 @@ import com.xiaomi.duckling.Types._ import com.xiaomi.duckling.dimension.DimRules import com.xiaomi.duckling.dimension.implicits._ import com.xiaomi.duckling.dimension.matcher.GroupMatch -import com.xiaomi.duckling.dimension.matcher.Prods.{regexMatch, singleRegexMatch} -import com.xiaomi.duckling.dimension.time.{form, Time, TimeData} +import com.xiaomi.duckling.dimension.matcher.Prods.regexMatch +import com.xiaomi.duckling.dimension.time.{form, GrainWrapper, Time, TimeData} import com.xiaomi.duckling.dimension.time.duration.{Duration, DurationData} import com.xiaomi.duckling.dimension.time.enums.{Grain, Hint} -import com.xiaomi.duckling.dimension.time.predicates.{isAPartOfDay, isATimeOfDay, isHint, isNotLatent, isTimeDatePredicate, IntersectTimePredicate, TimeDatePredicate, TimeIntervalsPredicate} +import com.xiaomi.duckling.dimension.time.helper.TimeDataHelpers.intersect +import com.xiaomi.duckling.dimension.time.predicates._ trait Rules extends DimRules with LazyLogging { /** @@ -90,10 +91,12 @@ trait Rules extends DimRules with LazyLogging { } ) + private val predicateEveryGrain = "每(一个?|个)?(年度?|月|周|星期|天|小时|分钟)的?".regex + val ruleEveryGrainDatetime = Rule( name = " ", pattern = List( - "每(一个?|个)?(年度?|月|周|星期|天|小时|分钟)的?".regex, + predicateEveryGrain, and(isDimension(Time), isNotLatent).predicate), prod = tokens { case Token(_, GroupMatch(_ :: _ :: grainToken :: _)) :: Token(_, td: TimeData) :: _ @@ -155,4 +158,54 @@ trait Rules extends DimRules with LazyLogging { workdaysTime(rd, td) } ) + + // 周一到周五早上八点 + val ruleIntervalTime = Rule( + name = "