Duration/RU: For non-int times, use the coarsest grain

Summary:
This commit fixes #111, which was an open issue that any non-integer
multiple of any unit of time was being converted to seconds.

My solution is to write a recursive function `Duration.Helpers.inCoarsestGrain`
which, given a grain `g` and double value `v` finds the coarses grain `g'` such that
`v * g` - rounded to the nearest seconds - has integral units.

We call this function only in the case of non-integer multiples, and we start our
search from the given grain because nothing coarser would make sense. The code could
actually be slightly more efficient if we started at the next-smallest grain, but
in the interest of clarity I think this is probably better.

Reviewed By: chessai

Differential Revision: D27891439

fbshipit-source-id: b048310963eb71337fd91ab4ef3c840134a76e73
This commit is contained in:
Steven Troxler 2021-04-21 13:27:14 -07:00 committed by Facebook GitHub Bot
parent f73277c2b0
commit 23ec021b07
4 changed files with 23 additions and 5 deletions

View File

@ -13,6 +13,7 @@
* Duration: Diminutives for minutes and hours
* Duration: quarters of an hour
* Duration: added 'сутки' (24-hour period)
* Duration: use coarsest reasonable grain for \<positive-non-integer\> \<time-grain\>
### Server

View File

@ -16,6 +16,7 @@ module Duckling.Duration.Helpers
, secondsFromHourMixedFraction
, timesOneQuarter
, timesThreeQuarter
, inCoarsestGrain
) where
import Prelude
@ -25,6 +26,7 @@ import Duckling.Duration.Types (DurationData (DurationData))
import Duckling.Numeral.Helpers (isNatural)
import Duckling.Types
import qualified Duckling.Duration.Types as TDuration
import qualified Duckling.Numeral.Types as TNumeral
import qualified Duckling.TimeGrain.Types as TG
-- -----------------------------------------------------------------
@ -74,3 +76,15 @@ timesThreeQuarter grain = case grain of
TG.Month -> Just . duration TG.Day . (21+) . (30*)
TG.Year -> Just . duration TG.Month . (9+) . (12*)
_ -> const Nothing
inCoarsestGrain :: TG.Grain -> Double -> DurationData
inCoarsestGrain g v =
go g
where
seconds = fromIntegral (floor $ TG.inSeconds g v :: Int)
go grain =
-- recursion terminates because `seconds` is an Integer
let inGrain = seconds / TG.inSeconds grain 1
in case TNumeral.getIntValue inGrain of
Just n -> duration grain n
Nothing -> go $ TG.lower grain

View File

@ -58,10 +58,12 @@ allExamples = concat
, "3 четверти часа"
, "три четверти часа"
]
, examples (DurationData 5400 Second)
, examples (DurationData 90 Minute)
[ "полтора часа"
, "1.5 часа"
, "5400 секунд"
]
, examples (DurationData 5400 Second)
[ "5400 секунд"
]
, examples (DurationData 8 Hour)
[ "8 часов"
@ -88,7 +90,7 @@ allExamples = concat
, "полгода"
, "пол года"
]
, examples (DurationData 9072000 Second)
, examples (DurationData 105 Day)
[ "3.5 месяца"
, "три с половиной месяца"
, "приблизительно 3.5 месяца"

View File

@ -97,7 +97,7 @@ ruleGrainAsDuration = Rule
rulePositiveDuration :: Rule
rulePositiveDuration = Rule
{ name = "<positive-numeral> <time-grain>"
{ name = "<positive-non-integer> <time-grain>"
, pattern =
[ numberWith TNumeral.value $ and . sequence [not . isInteger, (>0)]
, dimension TimeGrain
@ -105,7 +105,8 @@ rulePositiveDuration = Rule
, prod = \case
(Token Numeral NumeralData{TNumeral.value = v}:
Token TimeGrain grain:
_) -> Just $ Token Duration $ duration Second $ floor $ inSeconds grain v
_) ->
Just $ Token Duration $ inCoarsestGrain grain v
_ -> Nothing
}