packages feed

chronos-1.1.3: bench/Bench.hs

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE BangPatterns #-}

module Main where

import Control.Applicative
import Criterion.Main
import Data.Int
import Data.Word

import Control.DeepSeq (($!!), NFData(..), deepseq)
import Control.Monad (when)
import Data.Attoparsec.ByteString.Char8 (Parser, parseOnly, char)
import Data.Foldable (traverse_)
import Data.Maybe (fromMaybe)
import Data.Text.Lazy.Builder (toLazyText)
import Data.Text.Short (ShortText)
import Test.QuickCheck (arbitrary, generate)

import qualified Chronos
import qualified Data.Attoparsec.Zepto as Z
import qualified Data.ByteString.Char8 as BS8
import qualified Data.Text.Lazy as LT
import qualified Data.Text.Short as TS
import qualified Data.Thyme as Thyme
import qualified Data.Time as Time
import qualified Data.Vector.Unboxed as UVector
import qualified System.Locale as Thyme

main :: IO ()
main = do
  utcthyme <- generate arbitrary :: IO Thyme.UTCTime
  let
    renderIsoTime = Thyme.formatTime Thyme.defaultTimeLocale isoFormatString
    -- With chronos, we explicitly convert the ymdhms time to the
    -- nanoseconds since the epoch. This is done to make the benchmark
    -- compare apples to apples. Both thyme and time are computing epoch
    -- times, so we want chronos to do the same.

    dmy              = "%d:%m:%y."
    hms              = "%H:%M:%S."
    dmyhms           = isoFormatString
    timePretty       = Time.formatTime Time.defaultTimeLocale
    thymePretty      = Thyme.formatTime Thyme.defaultTimeLocale

  timeTime  <- Time.getCurrentTime
  thymeTime <- Thyme.getCurrentTime
  chronosTime <- Chronos.now

  string <- return $!! renderIsoTime utcthyme
  bytestring <- return $!! BS8.pack string
  shortText <- return $!! TS.pack string

  defaultMain
    [ bgroup "parsing"
      [ bench "Time.parseTimeM"           $ nf timeParser        string
      , bench "Thyme.parseTime"           $ nf thymeParser       string
      , bench "Thyme.timeParser"          $ nf thymeAttoparsec   bytestring
      , bench "Chronos.parserUtf8_YmdHMS" $ nf chronosAttoparsec bytestring
      , bench "Chronos.zeptoUtf8_YmdHMS"  $ nf chronosZepto      bytestring
      , bench "Chronos.decodeShortTextIso8601" $ nf chronosIso8601 shortText
      ]

    , bgroup "prettyPrint"
      [ bgroup "dmy"
        [ bench "Time.formatTime"     $ nf (timePretty dmy)  timeTime
        , bench "Thyme.formatTime"    $ nf (thymePretty dmy) thymeTime
        , bench "Chronos.builder_Dmy" $ nf chronosPrettyDmy  chronosTime
        ]
      , bgroup "HMS"
        [ bench "Time.formatTime"     $ nf (timePretty hms)  timeTime
        , bench "Thyme.formatTime"    $ nf (thymePretty hms) thymeTime
        , bench "Chronos.builder_HMS" $ nf chronosPrettyHMS  chronosTime
        ]
      , bgroup "YmdHMS"
        [ bench "Time.formatTime"        $ nf (timePretty dmyhms)  timeTime
        , bench "Thyme.formatTime"       $ nf (thymePretty dmyhms) thymeTime
        , bench "Chronos.builder_YmdHMS" $ nf chronosPrettyYmdHMS  chronosTime
        ]
      , bgroup "ISO-8601-Zulu"
        [ bench "Chronos.encodeShortTextIso8601Zulu" $ nf encodeChronosIso8601Zulu chronosTime
        ]
      ]
    ]

encodeChronosIso8601Zulu :: Chronos.Time -> ShortText
encodeChronosIso8601Zulu !t =
  Chronos.encodeShortTextIso8601Zulu (Chronos.timeToDatetime t)

chronosIso8601 :: ShortText -> Chronos.Time
{-# noinline chronosIso8601 #-}
chronosIso8601 !t = case Chronos.decodeShortTextIso8601Zoneless t of
  Just x -> Chronos.datetimeToTime x
  Nothing -> errorWithoutStackTrace "chronosIso8601: decode failure"

chronosZepto :: BS8.ByteString -> Chronos.Time
{-# noinline chronosZepto #-}
chronosZepto !bs = either error Chronos.datetimeToTime
  (Z.parse (Chronos.zeptoUtf8_YmdHMS Chronos.w3c) bs)


chronosPrettyYmdHMS :: Chronos.Time -> LT.Text
{-# noinline chronosPrettyYmdHMS #-}
chronosPrettyYmdHMS = toLazyText
  . Chronos.builder_YmdHMS Chronos.SubsecondPrecisionAuto Chronos.w3c
  . Chronos.timeToDatetime

chronosPrettyHMS :: Chronos.Time -> LT.Text
{-# noinline chronosPrettyHMS #-}
chronosPrettyHMS = toLazyText
  . Chronos.builder_HMS Chronos.SubsecondPrecisionAuto (Just ':')
  . Chronos.datetimeTime
  . Chronos.timeToDatetime

chronosPrettyDmy :: Chronos.Time -> LT.Text
{-# noinline chronosPrettyDmy #-}
chronosPrettyDmy = toLazyText
  . Chronos.builder_Dmy (Just ':')
  . Chronos.datetimeDate
  . Chronos.timeToDatetime

chronosAttoparsec :: BS8.ByteString -> Chronos.Time
{-# noinline chronosAttoparsec #-}
chronosAttoparsec =
    either error Chronos.datetimeToTime
  . parseOnly (Chronos.parserUtf8_YmdHMS Chronos.w3c)

timeParser :: String -> Time.UTCTime
{-# noinline timeParser #-}
timeParser =
  fromMaybe (error "Failed to parse in timeParser")
  . Time.parseTimeM True Time.defaultTimeLocale isoFormatString

thymeParser :: String -> Thyme.UTCTime
{-# noinline thymeParser #-}
thymeParser =
  fromMaybe (error "Failed to parse in thymeParser")
  . Thyme.parseTime Thyme.defaultTimeLocale isoFormatString

thymeAttoparsec :: BS8.ByteString -> Thyme.UTCTime
{-# noinline thymeAttoparsec #-}
thymeAttoparsec =
  Thyme.buildTime @Thyme.UTCTime
  . either error id
  . parseOnly (Thyme.timeParser Thyme.defaultTimeLocale isoFormatString)

isoFormatString :: String
{-# noinline isoFormatString #-}
isoFormatString = "%Y-%m-%dT%H:%M:%S"