如何在Haskell中将时间戳转换为公历日期时间

5

假设我有一个表示Epoch时间的整数,例如epoch = 1499055085,我想将它转换为Haskell中的UTCTime。如何做到这一点?

在其他语言中,这是一个非常简单的任务,为什么在Haskell中却这么困难呢?


这个问题的答案在很大程度上取决于这些秒是什么。它们是忽略闰秒的Unix时间吗?还是UTC中的秒数,如果是,从哪个时期开始算起? - Cirdec
2个回答

5

这是一个不错的问题,尽管有些人对它进行了负面评价。那么在这里,相当标准的“strptime”和“strftime”的等效物是什么呢?

你可能会觉得这很复杂,我同意,因为我也花了很多时间来弄清楚这个问题(我是新手),所以这里是我的一些发现。这并不简单,因为:

  1. 处理时间是复杂的。
  2. Data.Time库专注于类型安全(与Haskell一致),这也意味着没有自动类型转换。因此,您必须对此有所了解。
  3. 如何读取纪元取决于它究竟代表什么以及它是如何存储的。因此,如果标准库中没有实现通用函数epoch -> utc,那么这不是遗漏,而是因为它不存在。

在Python中,这更简单,因为datetime可以从您的环境中推断出“非常好的默认值”。对于Haskell来说,“大多数情况下都足够好的默认参数值”的概念并不适用。

如果纪元值来自某个任意整数,则Willem Van Onsem的epochToUTC函数可以工作。如果它是来自文件的时间戳(EpochTime),则我们需要另一个函数,因为它不是整数(它看起来像整数,行为像整数,但它既不是Int也不是Integer——这是一些非常深奥的问题)。

至于import语句,您可以将其简化为仅使用import Data.Time,这将从该模块中获取所有所需内容,但不包括posixSecondsToUTCTime,因为……我想它被认为是某种专业化的东西。

import System.Posix.Types(EpochTime)
import Data.Time
import Data.Time.Clock.POSIX(posixSecondsToUTCTime)

epochToUTC :: EpochTime -> UTCTime
epochToUTC = posixSecondsToUTCTime . realToFrac

在ghci中的示例:(当前工作目录的时间戳)
import System.Posix.Files

fStat <- getFileStatus "."
let someTime = epochToUTC . modificationTime $ fStat

我们可以很好地操作这个时间值。请注意,EpochTime 是特定于 System.Posix 模块的。如果它是从数据库或网页中检索到的时间戳,则可能会有另一个故事(类型安全并非免费)。

就像这个 "strptime" 操作并不是非常熟悉一样,类似的情况也出现在类似 "strftime" 的操作中。这可以通过使用 formatTime 来完成。例如:

formatTime defaultTimeLocale "%Y-%m-%d" someTime

--equivalent
formatTime defaultTimeLocale ( iso8601DateFormat Nothing ) someTime

--whichever dateFmt defined in that TimeLocale
formatTime defaultTimeLocale ( dateFmt defaultTimeLocale ) someTime

--"%a, %_d %b %Y %H:%M:%S %Z"
formatTime defaultTimeLocale rfc822DateFormat someTime

如果始终使用defaultTimeLocale,则可以优雅地缩写为:
formatTime' = formatTime defaultTimeLocale

阅读材料:

Haskell时间库教程(我们需要更多这样的内容!)

时间 - HaskellWiki


非常感谢您提供的信息丰富的答案,我认为这些负评可能是Haskell社区中新手敌意导致的。经过这么长时间,您提供的链接仍然非常有用,再次感谢。 - milad zahedi
1
很高兴能帮忙。我不认为这是新手敌意。Haskellers 对他们的语言非常有信仰,这是一把双刃剑。问问题就应该得到答案。如果在问问题的同时表达了强烈的观点,那么就应该预期会得到一个带有强烈观点的回答。 - pbarill
也许你是对的。我猜我在极度沮丧的时候问了这个问题。 - milad zahedi

5

谁说这很困难呢?

你可以简单地使用fromIntegral :: (Integral a, Num b) => a -> b将纪元(一个整数)转换为POSIXTime

接下来,我们可以使用posixSecondsToUTCTime :: POSIXTime -> UTCTime获取UTCTime,最后我们使用utctDay :: UTCTime -> Day获取UTCTime的日期部分。

如果您想要一个(年,月,日)元组,我们可以使用toGregorian :: Day -> (Integer,Int,Int)方法。

所以我们可以使用以下方法:

import Data.Time.Calendar(toGregorian)
import Data.Time.Clock(utctDay,UTCTime)
import Data.Time.Clock.POSIX(posixSecondsToUTCTime)

epochToUTC :: Integral a => a -> UTCTime
epochToUTC = posixSecondsToUTCTime . fromIntegral

epochToGregorian :: Integral a => a -> (Integer,Int,Int)
epochToGregorian = toGregorian . utctDay . epochToUTC

接下来举个例子:

Main> epochToGregorian 1234567
(1970,1,15)
Main> epochToGregorian 123456789
(1973,11,29)
Main> epochToGregorian 1234567890
(2009,2,13)

2
这很困难,因为我必须导入3个模块并且确切知道每个模块包含什么,对于像我这样的新手来说非常繁琐。 - milad zahedi
你知道还有哪些编程语言可以默认提供日期转换操作,而且你不需要了解它们就能使用吗? - Cubic
导入三个模块只需要三行代码,我无法想象这是多么繁琐。此外,如果你真的认为一行代码和两行代码相比有所改进,你可以使用 import Data.Time 来获取前两个模块。最后,你不必“知道”每个模块包含什么 - 你只需要能够阅读文档(这些文档在本答案中提供了链接!)。 - user2407038
我冷静下来后,在这个问题上找到了这个有用的教程:https://two-wrongs.com/haskell-time-library-tutorial - pbarill

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接