【问题标题】:Haskell date parsing and formattingHaskell 日期解析和格式化
【发布时间】:2011-05-09 15:08:25
【问题描述】:

我一直在使用 Haskell 的 Date.Time 模块来解析像 12-4-1999 或 1-31-1999 这样的日期。我试过了:

parseDay :: String -> Day
parseDay s = readTime defaultTimeLocale "%m%d%Y" s

而且我认为它希望我的月份和日期正好有两位数,而不是 1 或 2...

这样做的正确方法是什么?

另外,我想以这种格式打印出我的 Day:12/4/1999 Haskell 方法是什么?

感谢您的帮助。

【问题讨论】:

  • 在 %m、%d 和 %Y 之间不需要破折号吗?
  • @AlexBaranosky 你可能想考虑接受这个问题的不同答案:)

标签: haskell formatting date


【解决方案1】:

这是一些旧代码,其中包含两种类型的自制日期,只有 YMD 的日期,没有时间或时区等。

它展示了如何使用readDec 将字符串解析为日期。请参阅parseDate 函数。使用readDec,读取数字,前导空格(因为filter)或前导零无关紧要,并且解析在第一个非数字处停止。然后使用tail(跳过非数字)进入日期的下一个数字字段。

它显示了几种格式化输出的方式,但最灵活的方式是使用Text.printf。见instance Show LtDate。有了 printf,一切皆有可能!

import Char
import Numeric
import Data.Time.Calendar
import Data.Time.Clock
import Text.Printf
-- ================================================================
--                        LtDate
-- ================================================================
type Date=(Int,Int,Int)
data LtDate = LtDate 
  { ltYear :: Int,
    ltMonth:: Int,
    ltDay  :: Int
  } 
instance Show LtDate 
  where show d = printf "%4d-%02d-%02d" (ltYear d) (ltMonth d) (ltDay d)

toLtDate :: Date -> LtDate
toLtDate (y,m,d)= LtDate y m d

-- =============================================================
--                         Date
-- =============================================================
-- | Parse a String mm/dd/yy into tuple (y,m,d)
-- accepted formats
--
-- @
-- 12\/01\/2004
-- 12\/ 1\' 4
-- 12-01-99
-- @
parseDate :: String -> Date
parseDate s = (y,m,d)
    where [(m,rest) ] = readDec (filter (not . isSpace) s)
          [(d,rest1)] = readDec (tail rest)
          [(y, _)   ] = parseDate' rest1

-- | parse the various year formats used by Quicken dates
parseDate':: String -> [(Int,String)]
parseDate' (y:ys) =
  let [(iy,rest)] = readDec ys
      year=case y of '\''      -> iy + 2000
                     _  ->
                       if iy < 1900 then  iy + 1900 else iy
   in [(year,rest)]

-- | Note some functions sort by this format
-- | So be careful when changing it.
showDate::(Int, Int, Int) -> String
showDate (y,m,d)= yy ++ '-':mm ++ '-':dd
    where dd=zpad (show d)
          mm = zpad (show m)
          yy = show y
          zpad ds@(_:ds')
           | ds'==[] = '0':ds
           | otherwise = ds


-- | from LtDate to Date
fromLtDate :: LtDate -> Date
fromLtDate  lt = (ltYear lt, ltMonth lt, ltDay lt)

一旦有了 (Y,M,D),就很容易转换为 Haskell 库类型以进行数据操作。完成 HS 库后,Text.printf 可用于格式化显示日期。

【讨论】:

  • 这看起来非常复杂!一定有更简单的方法,上帝帮助我
  • 请仔细看一下,代码只是一个例子,并非全部需要:解析 dateparseDate 只需要 4 行;拨打readDec 3 次。要显示日期,请使用 Text.printf` 一次。加载文件并在您的日期运行它以查看它是否有效,然后查看那些parseDate 和printf。此 parseDate 忽略空格并且不关心日期中的分隔符“-”或“/”。它将 '55 更改为 1955 等。它是 10 行(包括 parseDate')ShowDate 和 fromLtDate,并且不需要 data 语句。时钟和日历也不包括在内。
  • 删除除parseDate 和parseDate' 之外的所有内容,这样看起来更简单。保留 Data.Char 和 Numeric 的包含年份解析器,parseDate' 可以替换为对 readDec 的另一个调用。
【解决方案2】:

从最近开始,我建议您使用 strptime 包来满足您的所有日期/时间解析需求。

【讨论】:

    【解决方案3】:

    您可以使用 Data.Time.Format 中的函数来读取日期。我在下面包含了一个简单的程序,它以一种格式读取日期并以两种不同的格式写回该日期。要以个位数的月份或天数读取,请在 % 和格式说明符之间放置一个连字符 (-)。换句话说,要解析格式为 9-9-2012 的日期,然后在 % 和格式字符之间包含一个连字符。因此,要解析“9-9-2012”,您需要格式字符串“%-d-%-m-%Y”。

    注意:考虑到 Haskell 包的发展速度,这个答案有点老套了。可能是时候寻找更好的解决方案了。我不再写 Haskell,所以如果其他人创建另一个答案会很好,因为这个问题在 Google 搜索结果中排名很高。

    自 2017 年 7 月起,我们鼓励您使用 parseTimeOrError。代码变为:

    import Data.Time
    
    main =
      do
        let dateString = "26 Jan 2012 10:54 AM"
        let timeFromString = parseTimeOrError True defaultTimeLocale "%d %b %Y %l:%M %p" dateString :: UTCTime
        -- Format YYYY/MM/DD HH:MM
        print $ formatTime defaultTimeLocale "%Y/%m/%d %H:%M" timeFromString
        -- Format MM/DD/YYYY hh:MM AM/PM
        print $ formatTime defaultTimeLocale "%m/%d/%Y %I:%M %p" timeFromString
    
        -- now for a string with single digit months and days:
        let dateString = "9-8-2012 10:54 AM"
        let timeFromString = parseTimeOrError True defaultTimeLocale "%-d-%-m-%Y %l:%M %p" dateString :: UTCTime
        -- Format YYYY/MM/DD HH:MM
        print $ formatTime defaultTimeLocale "%Y/%m/%d %H:%M" timeFromString
    

    .cabal 文件中的版本: 构建依赖:base >=4.9 && = 1.6.0.1

    截至 2014 年 8 月,最好从“System.Locale”包而不是 Haskell 1998“Locale”包中获取语言环境。考虑到这一点,上面的示例代码现在显示为:

    import System.Locale
    import Data.Time
    import Data.Time.Format
    
    main =
      do
        let dateString = "26 Jan 2012 10:54 AM"
        let timeFromString = readTime defaultTimeLocale "%d %b %Y %l:%M %p" dateString :: UTCTime
        -- Format YYYY/MM/DD HH:MM
        print $ formatTime defaultTimeLocale "%Y/%m/%d %H:%M" timeFromString
        -- Format MM/DD/YYYY hh:MM AM/PM
        print $ formatTime defaultTimeLocale "%m/%d/%Y %I:%M %p" timeFromString
    
        -- now for a string with single digit months and days:
        let dateString = "9-8-2012 10:54 AM"
        let timeFromString = readTime defaultTimeLocale "%-d-%-m-%Y %l:%M %p" dateString :: UTCTime
        -- Format YYYY/MM/DD HH:MM
        print $ formatTime defaultTimeLocale "%Y/%m/%d %H:%M" timeFromString
    

    现在的输出如下所示:

    "2012/01/26 10:54"
    "01/26/2012 10:54 AM"
    "2012/08/09 10:54"
    

    **原文,2012 年 1 月 ** 答案:

    import Locale
    import Data.Time
    import Data.Time.Format
    
    main =
      do
        let dateString = "26 Jan 2012 10:54 AM"
        let timeFromString = readTime defaultTimeLocale "%d %b %Y %l:%M %p" dateString :: UTCTime
        -- Format YYYY/MM/DD HH:MM
        print $ formatTime defaultTimeLocale "%Y/%m/%d %H:%M" timeFromString
        -- Format MM/DD/YYYY hh:MM AM/PM
        print $ formatTime defaultTimeLocale "%m/%d/%Y %I:%M %p" timeFromString
    

    输出如下:

    "2012/01/26 10:54"
    "01/26/2012 10:54 AM"
    

    Data.Time.Format 可从“时间”包中获得。如果您需要解析一位数的月份或日期,即像 9-9-2012 这样的日期,则在 % 和格式字符之间包含一个连字符。因此,要解析“9-9-2012”,您需要格式字符串“%-d-%-m-%Y”。

    【讨论】:

    • 对于懒人来说,这是在“时间”包中。
    • 我得到Ambiguous occurrence ‘defaultTimeLocale’ 是Data.Time.defaultTimeLocale 还是System.Locale.defaultTimeLocale?
    • 查看我的编辑。这个答案开始显示它的年龄,但它适用于带有 cabal-install 1.24.0.2 和 ghc 8.0.2 的 OS X。
    • 我认为在这种情况下,最好使用更新的代码发布另一个答案,甚至可能更新原始答案并使用“旧”答案发布新答案。现在最古老的“解决方案”出现在问题的顶部。
    • @ChrisStryczynski:我明白你的意思。我重新排序了这些部分,所以最近的部分是第一个。为了清楚起见,请随意编辑它。
    【解决方案4】:

    使用 %-d 和 %-m 代替 %d 和 %m 表示单个数字日/月是可以的,即

    parseDay :: String -> Day
    parseDay s = readTime defaultTimeLocale "%-m%-d%Y" s
    

    这可能是 sclv 的意思,但他的评论对我来说有点太神秘了。

    【讨论】:

    猜你喜欢
    • 2014-11-10
    • 2017-12-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-23
    相关资源
    最近更新 更多