将ISO格式的日期字符串解析为其组件

时间:2016-04-23 19:27:37

标签: date parsing datetime haskell

我是haskell的新手,想通过尝试真实世界的应用程序来接近learngin。其中一个组件是能够将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

我知道会失败(无法使用IsoDate构建我的String类型。然后尝试将字符串读入Int。

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

以下回复:

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

还有其他一些尝试让这种转变 - 但我发布的是最理性的尝试,o我不打算发布其他尝试。

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

2 个答案:

答案 0 :(得分:1)

这是一个答案:

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”我以为我曾尝试过这个预告片,但它必须与其他代码一起出错。

答案 1 :(得分: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 -> (Integer, Int, Int)的结果的选择,如果不是,您必须在Integer的帮助下将其生成的Int转换为fromIntegral正如您在我的示例中看到的那样{1}}。

  2. 我使用的语法称为Maybe do-syntax ,这在第一行中是一个方便的东西我在monad中提取一个值并且绑定它的名称 - julianDay。 然后我将值转换为格里高利日。 然后return再次进入Maybe。如果第一步失败并产生Nothing,即String只是gobbledygook,那么其他任何操作都没有完成,你的程序完成而没有做任何工作(那就是懒惰的评价)。

  3. 更新

    如果您使用的是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"