feat: add parseISODuration for ISO 8601 duration parsing
This commit is contained in:
+43
-2
@@ -12,6 +12,7 @@ module Roux.SchemaOrg (
|
||||
parseISODuration,
|
||||
) where
|
||||
|
||||
import Control.Monad (guard)
|
||||
import Data.Aeson (FromJSON (..), (.!=), (.:), (.:?))
|
||||
import qualified Data.Aeson as A
|
||||
import qualified Data.Aeson.KeyMap as KM
|
||||
@@ -219,6 +220,46 @@ parseSchemaOrgRecipe val = do
|
||||
schemaOrgToCooklang :: SchemaOrgRecipe -> Either String Recipe
|
||||
schemaOrgToCooklang _ = Left "Not yet implemented"
|
||||
|
||||
-- | Parse an ISO 8601 duration string. (Stub — will be implemented in Task 2)
|
||||
{- | Parse an ISO 8601 duration string into a Cooklang Duration.
|
||||
Supports formats like PT20M, PT1H30M, P1DT2H, P1D.
|
||||
-}
|
||||
parseISODuration :: Text -> Maybe Duration
|
||||
parseISODuration _ = Nothing
|
||||
parseISODuration t
|
||||
| not ("P" `T.isPrefixOf` t) = Nothing
|
||||
| otherwise = do
|
||||
let rest = T.drop 1 t -- drop the P
|
||||
(datePart, afterDate) = T.break (== 'T') rest
|
||||
days = parseDateDays datePart
|
||||
timePart = T.drop 1 afterDate -- drop the T
|
||||
(hours, minutes, _seconds) = parseTimeComponent timePart
|
||||
totalMinutes = days * 24 * 60 + hours * 60 + minutes
|
||||
guard (totalMinutes > 0)
|
||||
Just (Duration (toRational totalMinutes) (Just "minutes"))
|
||||
where
|
||||
parseDateDays :: Text -> Int
|
||||
parseDateDays dp =
|
||||
let (numStr, after) = T.break (== 'D') dp
|
||||
in case T.uncons after of
|
||||
Just ('D', _) -> fromMaybe 0 (readMaybe (T.unpack numStr))
|
||||
_ -> 0
|
||||
|
||||
parseTimeComponent :: Text -> (Int, Int, Int)
|
||||
parseTimeComponent tc =
|
||||
let (hours, rest1) = parseUnit "H" tc
|
||||
(minutes, rest2) = parseUnit "M" rest1
|
||||
(seconds, _rest3) = parseUnit "S" rest2
|
||||
in (hours, minutes, seconds)
|
||||
|
||||
parseUnit :: Text -> Text -> (Int, Text)
|
||||
parseUnit unit tc =
|
||||
let (numStr, after) = T.break (\c -> c == 'H' || c == 'M' || c == 'S') tc
|
||||
in case T.uncons after of
|
||||
Just (c, rest)
|
||||
| T.singleton c == unit ->
|
||||
(fromMaybe 0 (readMaybe (T.unpack numStr)), rest)
|
||||
_ -> (0, tc)
|
||||
|
||||
readMaybe :: (Read a) => String -> Maybe a
|
||||
readMaybe s = case reads s of
|
||||
[(x, "")] -> Just x
|
||||
_ -> Nothing
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
module Roux.SchemaOrgSpec (spec) where
|
||||
|
||||
import qualified Data.Aeson as A
|
||||
import Data.CookLang (Duration (..))
|
||||
import Data.Maybe (fromJust, isJust)
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
@@ -49,3 +50,16 @@ spec = describe "SchemaOrg" $ do
|
||||
case recipe of
|
||||
Nothing -> expectationFailure "expected Just SchemaOrgRecipe, got Nothing"
|
||||
Just r -> soName r `shouldBe` "Carrot Risotto With Chile Crisp"
|
||||
|
||||
describe "parseISODuration" $ do
|
||||
let chk :: Text -> Maybe Duration -> IO ()
|
||||
chk input expected = parseISODuration input `shouldBe` expected
|
||||
|
||||
it "PT20M -> 20 minutes" $ chk "PT20M" (Just (Duration 20 (Just "minutes")))
|
||||
it "PT1H -> 60 minutes" $ chk "PT1H" (Just (Duration 60 (Just "minutes")))
|
||||
it "PT1H30M -> 90 minutes" $ chk "PT1H30M" (Just (Duration 90 (Just "minutes")))
|
||||
it "P1D -> 1440 minutes" $ chk "P1D" (Just (Duration 1440 (Just "minutes")))
|
||||
it "P1DT2H -> 1560 minutes" $ chk "P1DT2H" (Just (Duration 1560 (Just "minutes")))
|
||||
it "PT30M -> 30 minutes" $ chk "PT30M" (Just (Duration 30 (Just "minutes")))
|
||||
it "empty string -> Nothing" $ chk "" Nothing
|
||||
it "human-readable -> Nothing" $ chk "20 minutes" Nothing
|
||||
|
||||
Reference in New Issue
Block a user