【问题标题】:Parse an ISO formatted date string into its components将 ISO 格式的日期字符串解析为其组件
【发布时间】:2016-04-23 19:27:37
【问题描述】:

我是 haskell 的新手,想通过尝试一些真实世界的应用程序来学习 learninggin。其中一个组件是能够将 ISO 格式字符串中的日期解析到它的组件中。 This Stack Overflow post 帮助我开始了,但这还不够,我很困惑。

我有以下代码:

import System.Locale
import Data.Time
import Data.Time.Format

data IsoDate  = IsoDate {
    year :: Int
    , month :: Int
    , day :: Int
} deriving (Show)

parseIsoDate :: String -> IsoDate
parseIsoDate dateString = 
    IsoDate year month day
    where 
        timeFromString = readTime defaultTimeLocale "%Y %m %d" dateString :: UTCTime
        year = 2013
        month = 10
        day = 31

对于 2013 年的万圣节来说,这很好。我已经尝试将年份重写为:

year = formatTime defaultTimeLocale "%y" timeFromString

我知道会失败(不能用String 构造我的IsoDate 类型)。然后尝试将字符串读入 Int。

year = read (formatTime defaultTimeLocale "%y" timeFromString)

回复如下:

 parseIsoDate "2012-12-23"
 IsoDate {year = *** Exception: readsTime: bad input "2012-12-23"

还有一些其他的尝试可以让这个转换 - 但我发布的是最合理的尝试,o 我不会发布其他尝试。

我想弄清楚如何使用我当前的代码(因为我正在尝试学习结构),此外(因为日期解析是必不可少的)我想知道更好的方法(也许是最惯用的)在 Haskell 中处理这个。

【问题讨论】:

    标签: date parsing datetime haskell


    【解决方案1】:

    我认为你需要做的是

    parseIsoDate :: String -> Maybe IsoDate
    

    因为并非您提供的每个String 都是有效日期。实施它,您已经掌握了大部分正确的成分,但我认为您不想解析 UTCTime,而是解析可以转换为您的数据结构的 Day

    import Data.Time
    
    data IsoDate = ...
    
    parseIsoDate :: String -> Maybe IsoDate
    parseIsoDate str = do julianDay <- parse str
                          let (y, m, d) = toGregorian julianDay
                          return $ IsoDate (fromIntegral y) m d
    
      where parse:: String -> Maybe Day
            parse = parseTimeM True defaultTimeLocale "%F"
    

    现在有点解释和建议:

    1. 我会将数据类型 IsoDate 更改为使用 Integer 多年 - 因为它们可能很大(至少比 Int 大 - 看看我们宇宙的年龄)。这也是toGregorian 的结果的选择,它将转换Day -&gt; (Integer, Int, Int),如果不是,您必须在fromIntegral 的帮助下将其生成的Integer 转换为Int,如您在我的示例中看到的那样.

    2. 我使用的语法称为 do-syntax for Maybe,这在第一行中很方便,我在 monad 中提取了一个值并 bind 给它一个名字 - julianDay。 然后我将值转换为公历日。 然后return 再次进入Maybe。如果第一步失败并产生 Nothing,即 String 只是 gobbledygook,那么其他任何操作都不会完成,您的程序会在不做任何工作的情况下完成(这就是惰性求值的力量)。

    更新

    如果您使用的是RecordWildCards 扩展名,并且可能是Functor,您可以执行以下操作

    {-# LANGUAGE Record
    module MyLib
    import Data.Time
    
    data IsoDate = IsoDate { year :: Integer
                           , month :: Int
                           , day :: Int}
                  deriving (Show)
    
    parseIsoDate :: String -> Maybe IsoDate
    parseIsoDate str = do (year, month, day) <- toGregorian <$> parse str
                          return IsoDate{..}
    
      where parse:: String -> Maybe Day
            parse = parseTimeM True defaultTimeLocale "%F"
    

    【讨论】:

    • 酷,我选择了 UtcTime,因为我发现的示例从那里开始。天更有意义(我们的数据没有时间成分)。 %F 很好 (hackage.haskell.org/package/time-1.6/docs/…)。尽管我已经看过它,但我还没有掌握“do”语法。它只是启用了单子的合成糖吗? (我将不得不跳过),
    • 另外 - 我同意也许 - 只是(不是双关语)专注于让其他语法正确。
    • "Do-syntax" 通常只用在 monad 的上下文中——但不要害怕它没有你想象的那么可怕。 ghc 中有一个名为 ApplicativeDo 的扩展,它使您能够在 Applicative 上下文中使用此语法,它稍微更通用但功能较弱 - 而且也没有听起来那么可怕。
    • 谢谢 - 在过去的 4 年里,我一直只使用 JS 进行编码,所以在长时间的停工后重新开始使用静态语言。之前我在 F# 中编码并且一直在 JS 中以函数式风格工作 - 但我觉得我需要学习 Haskell(好奇心)。我最喜欢的项目是为我的业务领域构建一个 DSL,这将非常适合 F#,我认为 Haskell 中有许多类似的构造。无论如何,首先要关注抽象语法树的启动和运行,然后从语法中解析它是最后一点
    • 我想我可能遗漏了一些东西 - 我粘贴了您的代码并收到以下错误:“输入 `let' 时出现解析错误” - 我认为间距看起来不错...
    【解决方案2】:

    这是一个答案:

    data IsoDate  = IsoDate {
        year :: Int
        , month :: Int
        , day :: Int
    } deriving (Show)
    
    parseIsoDate :: String -> IsoDate
    parseIsoDate dateString = 
        IsoDate year month day
        where 
            timeFromString = readTime defaultTimeLocale "%Y %m %d" dateString :: UTCTime
            year = read (formatTime defaultTimeLocale "%0Y" timeFromString) :: Int
            month = read (formatTime defaultTimeLocale "%m" timeFromString) :: Int
            day = read (formatTime defaultTimeLocale "%d" timeFromString) :: Int
    

    哪些重构为:

    data DatePart = Year | Month | Day deriving(Enum, Show)
    
    datePart :: DatePart ->  UTCTime -> Int
    datePart Year utcTime = read (formatTime defaultTimeLocale "%0Y" utcTime)
    datePart Month utcTime = read (formatTime defaultTimeLocale "%m" utcTime)
    datePart Day utcTime = read (formatTime defaultTimeLocale "%d" utcTime)
    
    parseIsoDate :: String -> IsoDate
    parseIsoDate dateString = 
        IsoDate year month day
        where 
            timeFromString = readTime defaultTimeLocale "%Y %m %d" dateString :: UTCTime
            year = datePart Year timeFromString
            month = datePart Month timeFromString
            day = datePart Day timeFromString
    

    使用中

     parseIsoDate "2012 12 02"
    

    该数据不是 ISO 格式,仍在努力使其读取“2012-12-01”。还在寻找让它在该语言中工作的首选方法。


    更新破折号是微不足道的,将“%Y %m %d”更改为“%Y-%m-%d”我以为我已经尝试过那个,但它一定是其他代码出错了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-17
      • 2016-05-17
      • 1970-01-01
      • 2019-09-14
      • 2015-04-27
      • 2017-06-10
      相关资源
      最近更新 更多