time-exts-2.1.0: src/Data/Time/Exts/Parser.hs
--------------------------------------------------------------------------------
-- Copyright (c) 2014, Enzo Haussecker, Steve Severance. All rights reserved. --
--------------------------------------------------------------------------------
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UnicodeSyntax #-}
{-# OPTIONS -Wall #-}
-- | Timestamp parsers and related utilities.
module Data.Time.Exts.Parser (
-- ** Utilities
FormatText
, ParseError(..)
-- ** Parse Unix Timestamps
, parseUnixDate
, parseUnixTime
, parseUnixTimeMillis
, parseUnixTimeMicros
, parseUnixTimeNanos
, parseUnixTimePicos
, parseUnixDateTime
, parseUnixDateTimeMillis
, parseUnixDateTimeMicros
, parseUnixDateTimeNanos
, parseUnixDateTimePicos
-- ** Parse UTC and Local Timestamps
, parseLocalDate
, parseLocalDateTime
, parseLocalDateTimeMillis
, parseLocalDateTimeMicros
, parseLocalDateTimeNanos
, parseLocalDateTimePicos
-- ** Parse Unix Timestamps With Parameters
, parseUnixDate'
, parseUnixTime'
, parseUnixTimeMillis'
, parseUnixTimeMicros'
, parseUnixTimeNanos'
, parseUnixTimePicos'
, parseUnixDateTime'
, parseUnixDateTimeMillis'
, parseUnixDateTimeMicros'
, parseUnixDateTimeNanos'
, parseUnixDateTimePicos'
-- ** Parse UTC and Local Timestamps With Parameters
, parseLocalDate'
, parseLocalDateTime'
, parseLocalDateTimeMillis'
, parseLocalDateTimeMicros'
, parseLocalDateTimeNanos'
, parseLocalDateTimePicos'
) where
import Control.Applicative ((<|>), (<$>), (*>))
import Control.Arrow ((***))
import Control.Exception (Exception)
import Control.Monad
import Control.Monad.State.Strict (execState, State)
import Data.Attoparsec.Text as P hiding (decimal)
import Data.Convertible (Convertible(..), prettyConvertError)
import Data.Char (isAlpha)
import Data.Default (def)
import Data.Label ((:->), mkLabels)
import Data.Label.Monadic (puts, modify)
import Data.List as L (foldl', foldl1, map, zip)
import Data.String (IsString(..))
import Data.Text as T
import Data.Time.Exts.Base hiding (TimeZone)
import Data.Time.Exts.Local
import Data.Time.Exts.Unix
import Data.Time.Exts.Zone
import Data.Typeable (Typeable)
import System.Locale (TimeLocale(..))
-- | The format string is composed of various %-codes, each
-- representing time-related information described below.
--
-- [@%%@] A literal '%' character.
--
-- [@%A@] The full weekday name according to the current locale.
--
-- [@%a@] The abbreviated weekday name according to the current locale.
--
-- [@%B@] The full month name according to the current locale.
--
-- [@%b@] The abbreviated month name according to the current locale.
--
-- [@%D@] Equivalent to %m\/%d\/%y.
--
-- [@%d@] The day of the month as a decimal number (range 01 to 31).
--
-- [@%e@] Like %d, the day of the month as a decimal number, but a leading zero is replaced by a space.
--
-- [@%F@] Equivalent to %Y-%m-%d (the ISO 8601 date format).
--
-- [@%H@] The hour as a decimal number using a 24-hour clock (range 00 to 23).
--
-- [@%h@] Equivalent to %b.
--
-- [@%I@] The hour as a decimal number using a 12-hour clock (range 01 to 12).
--
-- [@%l@] Like %I, the hour as a decimal number using a 12-hour clock, but a leading zero is replaced by a space.
--
-- [@%M@] The minute as a decimal number (range 00 to 59).
--
-- [@%m@] The month as a decimal number (range 01 to 12).
--
-- [@%P@] Like %p, the period of the day according to the current locale, but lowercase.
--
-- [@%p@] The period of the day according to the current locale.
--
-- [@%Q@] The fraction of the second as a decimal number (range 0 to 999999999999).
--
-- [@%R@] Equivalent to %H:%M.
--
-- [@%r@] Equivalent to %I:%M:%S %p.
--
-- [@%S@] The second as a decimal number (range 00 to 60).
--
-- [@%T@] Equivalent to %H:%M:%S.
--
-- [@%Y@] The year as a decimal number (range 1970 to 9999).
--
-- [@%y@] The year as a decimal number without a century (range 00 to 99).
--
-- [@%Z@] The timezone abbreviation.
type FormatText = Text
-- | Error handling type.
newtype ParseError = ParseError String deriving (Show,Typeable)
instance Exception ParseError
instance IsString ParseError where
fromString = ParseError
-- | A struct with date, time, and time zone
-- components, plus component modifiers.
data TZ = TZ {
_set_year :: Year
, _set_mon :: Month
, _set_mday :: Day
, _set_wday :: DayOfWeek
, _set_hour :: Hour
, _set_min :: Minute
, _set_sec :: Double
, _set_frac :: Double -> Double
, _set_ampm :: Hour -> Hour
, _set_zone :: TimeZone
}
mkLabels [''TZ]
-- | Parse a Unix date.
--
-- > >>> parseUnixDate "%A, %B %e, %Y" "Tuesday, March 4, 2014"
-- > Right 2014-03-04
--
parseUnixDate :: FormatText -> Text -> Either ParseError UnixDate
parseUnixDate = parseUnixDate' def
-- | Same as @parseUnixDate@, except takes an additional locale parameter.
--
-- > >>> let german = defaultTimeLocale { wDays = [("Sonntag","So"),("Montag","Mo")...
-- > >>> parseUnixDate' german "%A, %B %e, %Y" "Dienstag, März 4, 2014"
-- > Right 2014-03-04
--
parseUnixDate' :: TimeLocale -> FormatText -> Text -> Either ParseError UnixDate
parseUnixDate' locale format text = fun <$> parseTimestamp locale Universal format text
where fun TZ{..} = createUnixDate _set_year _set_mon _set_mday
-- | Parse a Unix time.
--
-- > >>> parseUnixTime "%T" "15:32:19"
-- > Right 15:32:19
--
parseUnixTime :: FormatText -> Text -> Either ParseError UnixTime
parseUnixTime = parseUnixTime' def
-- | Same as @parseUnixTime@, except takes an additional locale parameter.
--
-- > >>> let albanian = defaultTimeLocale { wDays = [("e diel","Die"),("e hënë ","Hën")...
-- > >>> parseUnixTime' albanian "%l:%M:%S %p" "12:28:47 PD"
-- > Right 00:28:47
--
parseUnixTime' :: TimeLocale -> FormatText -> Text -> Either ParseError UnixTime
parseUnixTime' locale format text = fun <$> parseTimestamp locale Universal format text
where fun TZ{..} = createUnixTime hour _set_min sec
where hour = _set_ampm _set_hour
sec = truncate _set_sec
-- | Parse a Unix time with millisecond granularity.
--
-- > >>> parseUnixTimeMillis "%I:%M:%S.%Q %p" "09:41:09.313 PM"
-- > Right 21:41:09.313
--
parseUnixTimeMillis :: FormatText -> Text -> Either ParseError UnixTimeMillis
parseUnixTimeMillis = parseUnixTimeMillis' def
-- | Same as @parseUnixTimeMillis@, except takes an additional locale parameter.
--
-- > >>> let urdu = defaultTimeLocale { wDays = [("پير","پير"),("اتوار","اتوار")...
-- > >>> parseUnixTimeMillis' urdu "%l:%M:%S.%Q %p" " 3:12:47.624 ش"
-- > Right 15:12:47.624
--
parseUnixTimeMillis' :: TimeLocale -> FormatText -> Text -> Either ParseError UnixTimeMillis
parseUnixTimeMillis' locale format text = fun <$> parseTimestamp locale Universal format text
where fun TZ{..} = createUnixTimeMillis hour _set_min sec mil
where hour = _set_ampm _set_hour
(,) sec mil = properFracMillis $ _set_frac _set_sec
-- | Parse a Unix time with microsecond granularity.
--
-- > >>> parseUnixTimeMicros "%R:%S.%Q" "03:15:50.513439"
-- > Right 03:15:50.513439
--
parseUnixTimeMicros :: FormatText -> Text -> Either ParseError UnixTimeMicros
parseUnixTimeMicros = parseUnixTimeMicros' def
-- | Same as @parseUnixTimeMicros@, except takes an additional locale parameter.
--
-- > >>> let chinese = defaultTimeLocale { wDays = [("星期日","日"),("星期一","一")...
-- > >>> parseUnixTimeMicros' chinese "%p%I:%M:%S.%Q" "下午11:46:18.130561"
-- > Right 23:46:18.130561
--
parseUnixTimeMicros' :: TimeLocale -> FormatText -> Text -> Either ParseError UnixTimeMicros
parseUnixTimeMicros' locale format text = fun <$> parseTimestamp locale Universal format text
where fun TZ{..} = createUnixTimeMicros hour _set_min sec mic
where hour = _set_ampm _set_hour
(,) sec mic = properFracMicros $ _set_frac _set_sec
-- | Parse a Unix time with nanosecond granularity.
--
-- > >>> parseUnixTimeNanos "%l:%M:%S.%Q %P" " 1:27:44.001256754 pm"
-- > Right 13:27:44.001256754
--
parseUnixTimeNanos :: FormatText -> Text -> Either ParseError UnixTimeNanos
parseUnixTimeNanos = parseUnixTimeNanos' def
-- | Same as @parseUnixTimeNanos@, except takes an additional locale parameter.
--
-- > >>> let swahili = defaultTimeLocale { wDays = [("Jumapili","J2"),("Jumatatu","J3")...
-- > >>> parseUnixTimeNanos' swahili "%H:%M:%S.%Q %p" "12:05:50.547621324 asubuhi"
-- > Right 00:05:50.547621324
--
parseUnixTimeNanos' :: TimeLocale -> FormatText -> Text -> Either ParseError UnixTimeNanos
parseUnixTimeNanos' locale format text = fun <$> parseTimestamp locale Universal format text
where fun TZ{..} = createUnixTimeNanos hour _set_min sec nan
where hour = _set_ampm _set_hour
(,) sec nan = properFracNanos $ _set_frac _set_sec
-- | Parse a Unix time with picosecond granularity.
--
-- > >>> parseUnixTimePicos "%T.%Q" "13:09:23.247795919586"
-- > Right 13:09:23.247795919586
--
parseUnixTimePicos :: FormatText -> Text -> Either ParseError UnixTimePicos
parseUnixTimePicos = parseUnixTimePicos' def
-- | Same as @parseUnixTimePicos@, except takes an additional locale parameter.
--
-- > >>> let japanese = defaultTimeLocale { wDays = [("日曜日","日"),("月曜日","月")...
-- > >>> parseUnixTimePicos' japanese "%I:%M:%S.%Q %p" "04:20:15.340563315063 午前"
-- > Right 04:20:15.340563315063
--
parseUnixTimePicos' :: TimeLocale -> FormatText -> Text -> Either ParseError UnixTimePicos
parseUnixTimePicos' locale format text = fun <$> parseTimestamp locale Universal format text
where fun TZ{..} = createUnixTimePicos hour _set_min sec pic
where hour = _set_ampm _set_hour
(,) sec pic = properFracPicos $ _set_frac _set_sec
-- | Parse a Unix date and time.
--
-- > >>> parseUnixDateTime "%FT%TZ" "2014-02-27T11:31:20Z"
-- > Right 2014-02-27 11:31:20
--
parseUnixDateTime :: FormatText -> Text -> Either ParseError UnixDateTime
parseUnixDateTime = parseUnixDateTime' def
-- | Same as @parseUnixDateTime@, except takes an additional locale parameter.
--
-- > >>> let somali = defaultTimeLocale { wDays = [("Axad","Axa"),("Isniin","Isn")...
-- > >>> parseUnixDateTime' somali "%A, %B %e, %r %Y" "Salaaso, Bisha Saddexaad 11, 03:41:33 galabnimo 2014"
-- > Right 2014-03-11 15:41:33
--
parseUnixDateTime' :: TimeLocale -> FormatText -> Text -> Either ParseError UnixDateTime
parseUnixDateTime' locale format text = fun <$> parseTimestamp locale Universal format text
where fun TZ{..} = createUnixDateTime _set_year _set_mon _set_mday hour _set_min sec
where hour = _set_ampm _set_hour
sec = truncate _set_sec
-- | Parse a Unix date and time with millisecond granularity.
--
-- > >>> parseUnixDateTimeMillis "%a %B %e %I:%M:%S.%Q %p %Y" "Wed March 5 06:53:04.475 PM 2014"
-- > Right 2014-03-05 18:53:04.475
--
parseUnixDateTimeMillis :: FormatText -> Text -> Either ParseError UnixDateTimeMillis
parseUnixDateTimeMillis = parseUnixDateTimeMillis' def
-- | Same as @parseUnixDateTimeMillis@, except takes an additional locale parameter.
--
-- > >>> let turkish = defaultTimeLocale { wDays = [("Pazar","Paz"),("Pazartesi","Pzt")...
-- > >>> parseUnixDateTimeMillis' turkish "%a %B %e %I:%M:%S.%Q %p %Y" "Prş Mart 13 07:22:54.324 ÖS 2014"
-- > Right 2014-03-13 19:22:54.324
--
parseUnixDateTimeMillis' :: TimeLocale -> FormatText -> Text -> Either ParseError UnixDateTimeMillis
parseUnixDateTimeMillis' locale format text = fun <$> parseTimestamp locale Universal format text
where fun TZ{..} = createUnixDateTimeMillis _set_year _set_mon _set_mday hour _set_min sec mil
where hour = _set_ampm _set_hour
(,) sec mil = properFracMillis $ _set_frac _set_sec
-- | Parse a Unix date and time with microsecond granularity.
--
-- > >>> parseUnixDateTimeMicros "%D %T.%Q" "03/06/14 17:26:55.148415"
-- > Right 2014-03-06 17:26:55.148415
--
parseUnixDateTimeMicros :: FormatText -> Text -> Either ParseError UnixDateTimeMicros
parseUnixDateTimeMicros = parseUnixDateTimeMicros' def
-- | Same as @parseUnixDateTimeMicros@, except takes an additional locale parameter.
--
-- > >>> let angika = defaultTimeLocale { wDays = [("रविवार","रवि"),("सोमवार","सोम")...
-- > >>> parseUnixDateTimeMicros' angika "%A %d %B %Y %I:%M:%S.%Q %p" "शुक्रवार 07 मार्च 2014 07:10:50.283025 अपराह्न"
-- > Right 2014-03-07 19:10:50.283025
--
parseUnixDateTimeMicros' :: TimeLocale -> FormatText -> Text -> Either ParseError UnixDateTimeMicros
parseUnixDateTimeMicros' locale format text = fun <$> parseTimestamp locale Universal format text
where fun TZ{..} = createUnixDateTimeMicros _set_year _set_mon _set_mday hour _set_min sec mic
where hour = _set_ampm _set_hour
(,) sec mic = properFracMicros $ _set_frac _set_sec
-- | Parse a Unix date and time with nanosecond granularity.
--
-- > >>> parseUnixDateTimeNanos "%d.%m.%Y %I:%M:%S.%Q %p" "18.03.2014 07:06:43.774295132 PM"
-- > Right 2014-03-18 19:06:43.774295132
--
parseUnixDateTimeNanos :: FormatText -> Text -> Either ParseError UnixDateTimeNanos
parseUnixDateTimeNanos = parseUnixDateTimeNanos' def
-- | Same as @parseUnixDateTimeNanos@, except takes an additional locale parameter.
--
-- > >>> let russian = defaultTimeLocale { wDays = [("Воскресенье","Вс"),("Понедельник","Пн")...
-- > >>> parseUnixDateTimeNanos' russian "%a %d %b %Y %T.%Q" "Ср 11 дек 2013 22:17:42.146648836"
-- > Right 2013-12-11 22:17:42.146648836
--
parseUnixDateTimeNanos' :: TimeLocale -> FormatText -> Text -> Either ParseError UnixDateTimeNanos
parseUnixDateTimeNanos' locale format text = fun <$> parseTimestamp locale Universal format text
where fun TZ{..} = createUnixDateTimeNanos _set_year _set_mon _set_mday hour _set_min sec nan
where hour = _set_ampm _set_hour
(,) sec nan = properFracNanos $ _set_frac _set_sec
-- | Parse a Unix date and time with picosecond granularity.
--
-- > >>> parseUnixDateTimePicos "%FT%T.%QZ" "2014-03-03T17:58:15.916795765305Z"
-- > Right 2014-03-03 17:58:15.916795765305
--
parseUnixDateTimePicos :: FormatText -> Text -> Either ParseError UnixDateTimePicos
parseUnixDateTimePicos = parseUnixDateTimePicos' def
-- | Same as @parseUnixDateTimePicos@, except takes an additional locale parameter.
--
-- > >>> let norwegian = defaultTimeLocale { wDays = [("søndag","sø."),("mandag","ma.")...
-- > >>> parseUnixDateTimePicos' norwegian "%a %d. %b %Y kl. %H.%M.%S.%Q" "fr. 07. mars 2014 kl. 21.11.55.837472109433"
-- > Right 2014-03-07 21:11:55.837472109433
--
parseUnixDateTimePicos' :: TimeLocale -> FormatText -> Text -> Either ParseError UnixDateTimePicos
parseUnixDateTimePicos' locale format text = fun <$> parseTimestamp locale Universal format text
where fun TZ{..} = createUnixDateTimePicos _set_year _set_mon _set_mday hour _set_min sec pic
where hour = _set_ampm _set_hour
(,) sec pic = properFracPicos $ _set_frac _set_sec
-- | Parse a local date.
--
-- > >>> parseLocalDate "%A, %B %e, %Y (%Z)" "Monday, March 17, 2014 (PST)"
-- > Right 2014-03-17 PST
--
parseLocalDate :: FormatText -> Text -> Either ParseError LocalDate
parseLocalDate = parseLocalDate' def Universal
-- | Same as @parseLocalDate@, except takes an additional locale and city parameter.
--
-- > >>> parseLocalDate' defaultTimeLocale Kolkata "%A, %B %e, %Y (%Z)" "Monday, March 17, 2014 (IST)"
-- > Right 2014-03-17 IST
--
-- Note that the city parameter is required to distinguish between the India and Israel.
parseLocalDate' :: TimeLocale -> City -> FormatText -> Text -> Either ParseError LocalDate
parseLocalDate' locale city format text = fun <$> parseTimestamp locale city format text
where fun TZ{..} = createLocalDate _set_year _set_mon _set_mday _set_zone
-- | Parse a local date and time.
--
-- > >>> parseLocalDateTime "%a %b %e %H:%M:%S %Z %Y" "Fri Mar 14 09:29:53 EST 2014"
-- > Right 2014-03-14 09:29:53 EST
--
parseLocalDateTime :: FormatText -> Text -> Either ParseError LocalDateTime
parseLocalDateTime = parseLocalDateTime' def Universal
-- | Same as @parseLocalDateTime@, except takes an additional locale and city parameter.
--
-- > >>> let french = defaultTimeLocale { wDays = [("dimanche","dim."),("lundi","lun.")...
-- > >>> parseLocalDateTime' french Paris "%a %d %b %T %Z %Y" "ven. 07 mars 22:49:03 UTC 2014"
-- > Right 2014-03-07 22:49:03 UTC
--
parseLocalDateTime' :: TimeLocale -> City -> FormatText -> Text -> Either ParseError LocalDateTime
parseLocalDateTime' locale city format text = fun <$> parseTimestamp locale city format text
where fun TZ{..} = createLocalDateTime _set_year _set_mon _set_mday hour _set_min sec _set_zone
where hour = _set_ampm _set_hour
sec = truncate _set_sec
-- | Parse a local date and time with millisecond granularity.
--
-- > >>> parseLocalDateTimeMillis "%B %e %Y %I:%M:%S.%Q %p %Z" "July 1 2012 01:59:60.215 AM EET"
-- > Right 2012-07-01 01:59:60.215 EET
--
-- Note that the timestamp in the example above corresponds to a leap second.
parseLocalDateTimeMillis :: FormatText -> Text -> Either ParseError LocalDateTimeMillis
parseLocalDateTimeMillis = parseLocalDateTimeMillis' def Universal
-- | Same as @parseLocalDateTimeMillis@, except takes an additional locale and city parameter.
--
-- > >>> parseLocalDateTimeMillis' defaultTimeLocale Chicago "%B %e %Y %I:%M:%S.%Q %p %Z" "July 13 2013 12:15:30.985 AM CDT"
-- > Right 2013-07-13 00:15:30.985 CDT
--
-- Note that the city parameter is required to distinguish between the United States and China.
parseLocalDateTimeMillis' :: TimeLocale -> City -> FormatText -> Text -> Either ParseError LocalDateTimeMillis
parseLocalDateTimeMillis' locale city format text = fun <$> parseTimestamp locale city format text
where fun TZ{..} = createLocalDateTimeMillis _set_year _set_mon _set_mday hour _set_min sec mil _set_zone
where hour = _set_ampm _set_hour
(,) sec mil = properFracMillis $ _set_frac _set_sec
-- | Parse a local date and time with microsecond granularity.
--
-- > >>> parseLocalDateTimeMicros "%F %T.%Q (%Z)" "2014-03-04 02:45:42.827495 (HKT)"
-- > Right 2014-03-04 02:45:42.827495 HKT
--
parseLocalDateTimeMicros :: FormatText -> Text -> Either ParseError LocalDateTimeMicros
parseLocalDateTimeMicros = parseLocalDateTimeMicros' def Universal
-- | Same as @parseLocalDateTimeMicros@, except takes an additional locale and city parameter.
--
-- > >>> let spanish = defaultTimeLocale { wDays = [("domingo","dom"),("lunes","lun")...
-- > >>> parseLocalDateTimeMicros' spanish Paris "%a %d %b %I:%M:%S.%Q %P %Y %Z" "dom 26 ene 04:27:16.743312 pm 2014 CET"
-- > Right 2014-01-26 16:27:16.743312 CET
--
parseLocalDateTimeMicros' :: TimeLocale -> City -> FormatText -> Text -> Either ParseError LocalDateTimeMicros
parseLocalDateTimeMicros' locale city format text = fun <$> parseTimestamp locale city format text
where fun TZ{..} = createLocalDateTimeMicros _set_year _set_mon _set_mday hour _set_min sec mic _set_zone
where hour = _set_ampm _set_hour
(,) sec mic = properFracMicros $ _set_frac _set_sec
-- | Parse a local date and time with nanosecond granularity.
--
-- > >>> parseLocalDateTimeNanos "%b. %d, %T.%Q %Z %Y" "Mar. 09, 18:53:55.856423459 UTC 2014"
-- > Right 2014-03-09 18:53:55.856423459 UTC
--
parseLocalDateTimeNanos :: FormatText -> Text -> Either ParseError LocalDateTimeNanos
parseLocalDateTimeNanos = parseLocalDateTimeNanos' def Universal
-- | Same as @parseLocalDateTimeNanos@, except takes an additional locale and city parameter.
--
-- > >>> let italian = defaultTimeLocale { wDays = [("domenica","dom"),("lunedì","lun")...
-- > >>> parseLocalDateTimeNanos' italian Paris "%a %e %b %Y %T.%Q %Z" "sab 12 apr 2014 04:59:21.528207540 CEST"
-- > Right 2014-04-12 04:59:21.528207540 CEST
--
parseLocalDateTimeNanos' :: TimeLocale -> City -> FormatText -> Text -> Either ParseError LocalDateTimeNanos
parseLocalDateTimeNanos' locale city format text = fun <$> parseTimestamp locale city format text
where fun TZ{..} = createLocalDateTimeNanos _set_year _set_mon _set_mday hour _set_min sec nan _set_zone
where hour = _set_ampm _set_hour
(,) sec nan = properFracNanos $ _set_frac _set_sec
-- | Parse a local date and time with picosecond granularity.
--
-- > >>> parseLocalDateTimePicos "%d.%m.%Y %T.%Q %Z" "09.04.2014 05:22:56.587234905781 SGT"
-- > Right 2014-04-09 05:22:56.587234905781 SGT
--
parseLocalDateTimePicos :: FormatText -> Text -> Either ParseError LocalDateTimePicos
parseLocalDateTimePicos = parseLocalDateTimePicos' def Universal
-- | Same as @parseLocalDateTimePicos@, except takes an additional locale and city parameter.
--
-- > >>> parseLocalDateTimePicos' defaultTimeLocale Shanghai "%a %b %d %Y %T.%Q %Z" "Sat Mar 08 2014 22:51:47.264356423524 CST"
-- > Right 2014-03-08 22:51:47.264356423524 CST
--
-- Note that the city parameter is required to distinguish between the United States and China.
parseLocalDateTimePicos' :: TimeLocale -> City -> FormatText -> Text -> Either ParseError LocalDateTimePicos
parseLocalDateTimePicos' locale city format text = fun <$> parseTimestamp locale city format text
where fun TZ{..} = createLocalDateTimePicos _set_year _set_mon _set_mday hour _set_min sec pic _set_zone
where hour = _set_ampm _set_hour
(,) sec pic = properFracPicos $ _set_frac _set_sec
-- | Initialize timestamp components.
initTZ :: TZ
initTZ = TZ 1970 January 1 Thursday 0 0 0.0 id id utc
-- | Parse timestamp components.
parseTimestamp
:: TimeLocale
-> City
-> FormatText
-> Text
-> Either ParseError TZ
parseTimestamp locale city format text =
either left Right $ do
parser <- parseFormat locale city format
parseOnly parser text
where left = Left . ParseError
-- | Parse format text.
parseFormat
:: TimeLocale
-> City
-> FormatText
-> Either String (Parser TZ)
parseFormat locale city =
fmap exec . parseOnly parser
where parser = many' $ createParser locale city
exec x = flip execState initTZ <$> sequence <$> sequence x
-- | Create a format text parser.
createParser
:: TimeLocale
-> City
-> Parser (Parser (State TZ ()))
createParser locale city =
matchLit "%%"
<|> matchSet "%A" set_wday (weekLong locale)
<|> matchSet "%a" set_wday (weekShort locale)
<|> matchSet "%B" set_mon (monthLong locale)
<|> matchSet "%b" set_mon (monthShort locale)
<|> matchMDY "%D" set_year set_mon set_mday
<|> matchSet "%d" set_mday (fixInt 2)
<|> matchSet "%e" set_mday padIntTwo
<|> matchYMD "%F" set_year set_mon set_mday
<|> matchSet "%H" set_hour (fixInt 2)
<|> matchSet "%h" set_mon (monthShort locale)
<|> matchSet "%I" set_hour (fixInt 2)
<|> matchSet "%l" set_hour padIntTwo
<|> matchSet "%M" set_min (fixInt 2)
<|> matchSet "%m" set_mon monthInt
<|> matchSet "%P" set_ampm (period locale toLower)
<|> matchSet "%p" set_ampm (period locale id)
<|> matchSet "%Q" set_frac fraction
<|> matchHM "%R" set_hour set_min
<|> matchT12 "%r" set_hour set_min set_sec locale
<|> matchSet "%S" set_sec second
<|> matchHMS "%T" set_hour set_min set_sec
<|> matchSet "%Y" set_year (fixInt 4)
<|> matchSet "%y" set_year yearTwo
<|> matchSet "%Z" set_zone (timezone city)
<|> matchTxt
-- | Match a percent literal.
matchLit
:: Text
-> Parser (Parser (State TZ ()))
matchLit code =
string code *>
return (char '%' *> return (return ()))
-- | Match a percent code and update the field
-- with the value returned by the parser.
matchSet
:: Text
-> (TZ :-> a)
-> Parser a
-> Parser (Parser (State TZ ()))
matchSet code field parser =
string code *> return (puts field <$> parser)
-- | Match a year-month-day percent code and update
-- the fields with the values returned by the parser.
matchYMD
:: Text
-> (TZ :-> Year )
-> (TZ :-> Month)
-> (TZ :-> Day )
-> Parser (Parser (State TZ ()))
matchYMD code _year _mon _day =
string code *> return parser where
parser = do
y <- fixInt 4; _ <- char '-'
m <- monthInt; _ <- char '-'
d <- fixInt 2
return $!
puts _year y *>
puts _mon m *>
puts _day d
-- | Match a month-day-year percent code and update
-- the fields with the values returned by the parser.
matchMDY
:: Text
-> (TZ :-> Year )
-> (TZ :-> Month)
-> (TZ :-> Day )
-> Parser (Parser (State TZ ()))
matchMDY code _year _mon _day =
string code *> return parser where
parser = do
m <- monthInt; _ <- char '/'
d <- fixInt 2; _ <- char '/'
y <- yearTwo
return $!
puts _year y *>
puts _mon m *>
puts _day d
-- | Match a hour-minute percent code and update the
-- fields with the values returned by the parser.
matchHM
:: Text
-> (TZ :-> Hour )
-> (TZ :-> Minute)
-> Parser (Parser (State TZ ()))
matchHM code _hour _min =
string code *> return parser where
parser = do
h <- fixInt 2; _ <- char ':'
m <- fixInt 2
return $!
puts _hour h *>
puts _min m
-- | Match a hour-minute-second percent code and update
-- the fields with the values returned by the parser.
matchHMS
:: Text
-> (TZ :-> Hour )
-> (TZ :-> Minute)
-> (TZ :-> Double)
-> Parser (Parser (State TZ ()))
matchHMS code _hour _min _sec =
string code *> return parser where
parser = do
h <- fixInt 2; _ <- char ':'
m <- fixInt 2; _ <- char ':'
s <- second
return $!
puts _hour h *>
puts _min m *>
puts _sec s
-- | Match a hour-minute-second-period percent code and
-- update the fields with the values returned by the parser.
matchT12
:: Text
-> (TZ :-> Hour )
-> (TZ :-> Minute)
-> (TZ :-> Double)
-> TimeLocale
-> Parser (Parser (State TZ ()))
matchT12 code _hour _min _sec locale =
string code *> return parser where
parser = do
h <- fixInt 2; _ <- char ':'
m <- fixInt 2; _ <- char ':'
s <- second ; _ <- char ' '
f <- period locale id
return $!
puts _hour h *>
puts _min m *>
puts _sec s *>
modify _hour f
-- | Match any other character sequence.
matchTxt :: Parser (Parser (State TZ ()))
matchTxt = takeWhile1 (/='%') >>= return . \ src -> do
trg <- P.take $ T.length src
if src == trg then return (return ())
else fail "matchTxt: mismatch"
-- | Parse an integral type of exactly @n@ digits.
fixInt :: Integral a => Int -> Parser a
fixInt n = do
s <- replicateM n digit
return $! fromIntegral $ L.foldl' step 0 s
where step a c = a * 10 + fromEnum c - 48
-- | Parse an integral type of two digits
-- or one digit preceded by a space.
padIntTwo :: Integral a => Parser a
padIntTwo = do
let f a b = a * 10 + b
liftM2 f getDigit getDigit
<|> do char ' ' >> getDigit
where getDigit = do
d <- digit
return $! fromIntegral $ fromEnum d - 48
-- | Parse a year in two digit format.
yearTwo :: Parser Year
yearTwo = f <$> fixInt 2
where f y = if y <= 69 then 2000 + y else 1900 + y
-- | Parse a month in two digit format.
monthInt :: Parser Month
monthInt = do
m <- fixInt 2
if 1 <= m && m <= 12
then return $! toEnum (m-1)
else fail $ "monthInt: out of bounds"
-- | Parse a month in short text format.
monthShort :: TimeLocale -> Parser Month
monthShort = fromList . flip L.zip monthList . L.map (pack . snd) . months
-- | Parse a month in long text format.
monthLong :: TimeLocale -> Parser Month
monthLong = fromList . flip L.zip monthList . L.map (pack . fst) . months
-- | Parse a day of week in short text format.
weekShort :: TimeLocale -> Parser DayOfWeek
weekShort = fromList . flip L.zip weekList . L.map (pack . snd) . wDays
-- | Parse a day of week in long text format.
weekLong :: TimeLocale -> Parser DayOfWeek
weekLong = fromList . flip L.zip weekList . L.map (pack . fst) . wDays
-- | Parse a second in two digit format.
second :: Parser Double
second = (realToFrac :: Int -> Double) <$> fixInt 2
-- | Parse a decimal in zero to twelve digit format.
fraction :: Parser (Double -> Double)
fraction = do
(,) n l <- foldM step (0,0) [1..12]
return $! (+ realToFrac n * 10 ** (- realToFrac l))
where step :: (Int, Int) -> Int -> Parser (Int, Int)
step acc@(n,_) l = option acc . try $ do
c <- digit
let n' = n * 10 + fromEnum c - 48
return $! (n', l)
-- | Parse period symbols.
period :: TimeLocale -> (Text -> Text) -> Parser (Hour -> Hour)
period TimeLocale{amPm = (am, pm)} casify = fromList
[(toText am, \ case 12 -> 00; x -> x )
,(toText pm, \ case 12 -> 12; x -> x + 12)]
where toText = casify . pack
-- | Parse a time zone.
timezone :: City -> Parser TimeZone
timezone city = do
t <- takeWhile1 isAlpha
case safeConvert . TimeZoneAbbr city $ unpack t of
Left err -> fail $ prettyConvertError err
Right zone -> return $! zone
-- | Create a parser from a list of key-value pairs.
fromList :: [(Text, a)] -> Parser a
fromList = L.foldl1 (<|>) . L.map (uncurry (*>) . (string *** return))
-- | List of days of the week.
weekList :: [DayOfWeek]
weekList = [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]
-- | List of months of the year.
monthList :: [Month]
monthList = [January, February, March, April, May, June, July, August, September, October, November, December]