{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-} {- time-quote: quasi-quotes for dates and times Copyright (C) 2018 Thomas Tuegel This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . -} {-| Module : Data.Time.Quote Description : Quasi-quotes for dates and times Copyright : (C) 2018 Thomas Tuegel License : GPL-3 Maintainer : Thomas Tuegel This module provides quasi-quoters to parse dates and times according to ISO 8601 formats. By using quasi-quotes for literals, parse errors are found by the compiler. To use quasi-quotes, place the @LANGUAGE QuasiQuotes@ pragma at the top of the source file or use the GHC command-line option @-XQuasiQuotes@. If using GHCi, enter at the prompt >>> :set -XQuasiQuotes Quasi-quoters are provided for each type that defines an 'ISO8601' instance. Use the quasi-quoters to quote a literal date or time of the corresponding type; for example > [utcTime| 2018-05-25T15:36:27.416462897Z |] -- :: UTCTime All quasi-quoters accept optional whitespace before and after the quoted value. Refer to the list below for details about the expected format for each type. -} module Data.Time.Quote ( utcTime , day , timeOfDay , localTime , timeZone , zonedTime , calendarDiffDays , calendarDiffTime ) where import qualified Data.Char as Char import qualified Data.List as List import Data.Time ( CalendarDiffDays(..), CalendarDiffTime(..), Day, LocalTime, TimeOfDay , TimeZone, UTCTime, ZonedTime ) import Data.Time.Format.ISO8601 (ISO8601) import qualified Data.Time.Format.ISO8601 as Time import Language.Haskell.TH.Lib import Language.Haskell.TH.Syntax import Language.Haskell.TH.Quote (QuasiQuoter(..)) -- $setup -- >>> :set -XQuasiQuotes -- | Quote a value of type 'UTCTime' in @yyyy-mm-ddThh:mm:ss[.sss]Z@ format. -- -- >>> [utcTime| 2018-05-25T15:36:27.416462897Z |] -- 2018-05-25 15:36:27.416462897 UTC -- utcTime :: QuasiQuoter utcTime = QuasiQuoter { quoteExp = \str -> parse @UTCTime str >>= liftData , quotePat = \_ -> fail "utcTime: cannot quote pattern!" , quoteType = \_ -> fail "utcTime: cannot quote type!" , quoteDec = \_ -> fail "utcTime: cannot quote declaration!" } -- | Quote a value of type 'Day' in @yyyy-mm-dd@ format. -- -- >>> [day| 2018-05-25 |] -- 2018-05-25 -- day :: QuasiQuoter day = QuasiQuoter { quoteExp = \str -> parse @Day str >>= liftData , quotePat = \_ -> fail "day: cannot quote pattern!" , quoteType = \_ -> fail "day: cannot quote type!" , quoteDec = \_ -> fail "day: cannot quote declaration!" } -- | Quote a value of type 'TimeOfDay' in @hh:mm:ss[.sss]@ format. -- -- >>> [timeOfDay| 15:36:27.416462897 |] -- 15:36:27.416462897 -- timeOfDay :: QuasiQuoter timeOfDay = QuasiQuoter { quoteExp = \str -> parse @TimeOfDay str >>= liftData , quotePat = \_ -> fail "timeOfDay: cannot quote pattern!" , quoteType = \_ -> fail "timeOfDay: cannot quote type!" , quoteDec = \_ -> fail "timeOfDay: cannot quote declaration!" } -- | Quote a value of type 'LocalTime' in @yyyy-mm-ddThh:mm:ss[.sss]@ format. -- -- >>> [localTime| 2018-05-25T10:36:27.416462897 |] -- 2018-05-25 10:36:27.416462897 -- localTime :: QuasiQuoter localTime = QuasiQuoter { quoteExp = \str -> parse @LocalTime str >>= liftData , quotePat = \_ -> fail "localTime: cannot quote pattern!" , quoteType = \_ -> fail "localTime: cannot quote type!" , quoteDec = \_ -> fail "localTime: cannot quote declaration!" } -- | Quote a value of type 'TimeZone' in @±hh:mm@ format. -- -- >>> [timeZone| -05:00 |] -- -0500 -- timeZone :: QuasiQuoter timeZone = QuasiQuoter { quoteExp = \str -> parse @TimeZone str >>= liftData , quotePat = \_ -> fail "timeZone: cannot quote pattern!" , quoteType = \_ -> fail "timeZone: cannot quote type!" , quoteDec = \_ -> fail "timeZone: cannot quote declaration!" } -- | Quote a value of type 'ZonedTime' in @yyyy-mm-ddThh:mm:ss[.sss]±hh:mm@ -- format. -- -- >>> [zonedTime| 2018-05-25T10:36:27.416462897-05:00 |] -- 2018-05-25 10:36:27.416462897 -0500 -- zonedTime :: QuasiQuoter zonedTime = QuasiQuoter { quoteExp = \str -> parse @ZonedTime str >>= liftData , quotePat = \_ -> fail "zonedTime: cannot quote pattern!" , quoteType = \_ -> fail "zonedTime: cannot quote type!" , quoteDec = \_ -> fail "zonedTime: cannot quote declaration!" } -- | Quote a value of type 'CalendarDiffDays' in @PyYmMdD@ format. -- -- >>> [calendarDiffDays| P2018Y5M25D |] -- P24221M25D -- calendarDiffDays :: QuasiQuoter calendarDiffDays = QuasiQuoter { quoteExp = \str -> parse @CalendarDiffDays str >>= lift , quotePat = \_ -> fail "calendarDiffDays: cannot quote pattern!" , quoteType = \_ -> fail "calendarDiffDays: cannot quote type!" , quoteDec = \_ -> fail "calendarDiffDays: cannot quote declaration!" } where lift CalendarDiffDays {..} = [| CalendarDiffDays { cdMonths = $(liftData cdMonths) , cdDays = $(liftData cdDays) } |] -- | Quote a value of type 'CalendarDiffTime' in @PyYmMdDThHmMs[.sss]S@ format. -- -- >>> [calendarDiffTime| P2018Y5M25DT15H36M27.416462897S |] -- P24221MT2216187.416462897S -- calendarDiffTime :: QuasiQuoter calendarDiffTime = QuasiQuoter { quoteExp = \str -> parse @CalendarDiffTime str >>= lift , quotePat = \_ -> fail "calendarDiffTime: cannot quote pattern!" , quoteType = \_ -> fail "calendarDiffTime: cannot quote type!" , quoteDec = \_ -> fail "calendarDiffTime: cannot quote declaration!" } where lift CalendarDiffTime {..} = [| CalendarDiffTime { ctMonths = $(liftData ctMonths) , ctTime = $(liftData ctTime) } |] parse :: ISO8601 t => String -> Q t parse = Time.iso8601ParseM . strip where strip = List.dropWhileEnd Char.isSpace . List.dropWhile Char.isSpace