module Darcs.Patch.OldDate ( readUTCDate, showIsoDateTime ) where
import Prelude ( (^) )
import Darcs.Prelude
import Text.ParserCombinators.Parsec
import System.Time
import Data.Char ( toUpper, isDigit )
import Control.Monad ( liftM, liftM2 )
import qualified Data.ByteString.Char8 as B
import Data.Maybe ( fromMaybe )
readUTCDate :: String -> CalendarTime
readUTCDate = readDate 0
readDate :: Int -> String -> CalendarTime
readDate tz d =
case parseDate tz d of
Left e -> error e
Right ct -> ct
parseDate :: Int -> String -> Either String CalendarTime
parseDate tz d =
if length d >= 14 && B.all isDigit bd
then Right $
CalendarTime (readI $ B.take 4 bd)
(toEnum $ (+ (1)) $ readI $ B.take 2 $ B.drop 4 bd)
(readI $ B.take 2 $ B.drop 6 bd)
(readI $ B.take 2 $ B.drop 8 bd)
(readI $ B.take 2 $ B.drop 10 bd)
(readI $ B.take 2 $ B.drop 12 bd)
0 Sunday 0
"GMT" 0 False
else let dt = do { x <- dateTime tz; eof; return x }
in case parse dt "" d of
Left e -> Left $ "bad date: "++d++" - "++show e
Right ct -> Right ct
where bd = B.pack (take 14 d)
readI s = fst $ fromMaybe (error "parseDate: invalid date") (B.readInt s)
showIsoDateTime :: CalendarTime -> String
showIsoDateTime ct = concat [ show $ ctYear ct
, twoDigit . show . (+1) . fromEnum $ ctMonth ct
, twoDigit . show $ ctDay ct
, twoDigit . show $ ctHour ct
, twoDigit . show $ ctMin ct
, twoDigit . show $ ctSec ct
]
where twoDigit [] = undefined
twoDigit x@(_:[]) = '0' : x
twoDigit x@(_:_:[]) = x
twoDigit _ = undefined
caseChar :: Char -> GenParser Char a Char
caseChar c = satisfy (\x -> toUpper x == toUpper c)
caseString :: String -> GenParser Char a ()
caseString cs = mapM_ caseChar cs <?> cs
manyN :: Int -> GenParser a b c -> GenParser a b [c]
manyN n p
| n <= 0 = return []
| otherwise = liftM2 (++) (count n p) (many p)
manyNtoM :: Int -> Int -> GenParser a b c -> GenParser a b [c]
manyNtoM n m p
| n < 0 = return []
| n > m = return []
| n == m = count n p
| n == 0 = foldr ((<|>) . (\x -> try $ count x p)) (return []) $ reverse [1..m]
| otherwise = liftM2 (++) (count n p) (manyNtoM 0 (mn) p)
dateTime :: Int -> CharParser a CalendarTime
dateTime tz =
choice [try $ cvsDateTime tz,
try $ iso8601DateTime tz,
oldDateTime]
dayAndHMSTime :: CharParser a (Int, Int, Int, Int)
dayAndHMSTime = do d <- day
_ <- mySpaces
h <- hour
_ <- char ':'
m <- minute
_ <- char ':'
s <- second
return (d, h, m, s)
cvsDateTime :: Int -> CharParser a CalendarTime
cvsDateTime tz =
do y <- year
_ <- char '/'
mon <- monthNum
_ <- char '/'
(d, h, m, s) <- dayAndHMSTime
z <- option tz $ mySpaces >> zone
return (CalendarTime y mon d h m s 0 Monday 0 "" z False)
oldDateTime :: CharParser a CalendarTime
oldDateTime = do wd <- dayName
_ <- mySpaces
mon <- monthName
_ <- mySpaces
(d, h, m, s) <- dayAndHMSTime
_ <- mySpaces
z <- zone
_ <- mySpaces
y <- year
return (CalendarTime y mon d h m s 0 wd 0 "" z False)
iso8601DateTime :: Int -> CharParser a CalendarTime
iso8601DateTime localTz = try $
do d <- iso8601Date
t <- option id $ try $ do optional $ oneOf " T"
iso8601Time
return $ t $ d { ctTZ = localTz }
iso8601Date :: CharParser a CalendarTime
iso8601Date =
do d <- calendar_date <|> week_date <|> ordinal_date
return $ foldr ($) nullCalendar d
where
calendar_date =
try $ do d <- optchain year_ [ (dash, month_), (dash, day_) ]
notFollowedBy (digit <|> char 'W')
return d
week_date = --yyyy-Www-dd
try $ do yfn <- year_
optional dash
_ <- char 'W'
w' <- (\x -> x1) `liftM` twoDigits
wd <- option 1 $ do { optional dash; nDigits 1 }
let y = yfn nullCalendar
firstDay = ctWDay y
let afterThursday = firstDay == Sunday || firstDay > Thursday
w = if afterThursday then w'+1 else w'
diff c = c { ctDay = (7 * w) + wd fromEnum firstDay }
return [toUTCTime.toClockTime.diff.yfn]
ordinal_date =
try $ optchain year_ [ (dash, yearDay_) ]
year_ = try $ do y <- fourDigits <?> "year (0000-9999)"
return $ \c -> c { ctYear = y }
month_ = try $ do m <- twoDigits <?> "month (1 to 12)"
return $ \c -> c { ctMonth = intToMonth m, ctPicosec = 0 }
day_ = try $ do d <- twoDigits <?> "day in month (1 to 31)"
return $ \c -> c { ctDay = d }
yearDay_ = try $ do d <- nDigits 3 <?> "day in year (1 to 366)"
return $ \c -> c { ctYDay = d }
dash = char '-'
iso8601Time :: CharParser a (CalendarTime -> CalendarTime)
iso8601Time = try $
do ts <- optchain hour_ [ (colon , min_)
, (colon , sec_)
, (oneOf ",.", pico_) ]
z <- option id $ choice [ zulu , offset ]
return $ foldr (.) id (z:ts)
where
hour_ = do h <- twoDigits
return $ \c -> c { ctHour = h }
min_ = do m <- twoDigits
return $ \c -> c { ctMin = m }
sec_ = do s <- twoDigits
return $ \c -> c { ctSec = s }
pico_ = do digs <- many digit
let picoExp = 12
digsExp = length digs
let frac | null digs = 0
| digsExp > picoExp = read $ take picoExp digs
| otherwise = 10 ^ (picoExp digsExp) * read digs
return $ \c -> c { ctPicosec = frac }
zulu = do { _ <- char 'Z'; return (\c -> c { ctTZ = 0 }) }
offset = do sign <- choice [ char '+' >> return 1
, char '-' >> return (1) ]
h <- twoDigits
m <- option 0 $ do { optional colon; twoDigits }
return $ \c -> c { ctTZ = sign * 60 * ((h*60)+m) }
colon = char ':'
optchain :: CharParser a b -> [(CharParser a c, CharParser a b)] -> CharParser a [b]
optchain p next = try $
do r1 <- p
r2 <- case next of
[] -> return []
((sep,p2):next2) -> option [] $ do { optional sep; optchain p2 next2 }
return (r1:r2)
nDigits :: Int -> CharParser a Int
nDigits n = read `liftM` count n digit
twoDigits, fourDigits :: CharParser a Int
twoDigits = nDigits 2
fourDigits = nDigits 4
mySpaces :: CharParser a String
mySpaces = manyN 1 $ char ' '
dayName :: CharParser a Day
dayName = choice
[ caseString "Mon" >> return Monday
, try (caseString "Tue") >> return Tuesday
, caseString "Wed" >> return Wednesday
, caseString "Thu" >> return Thursday
, caseString "Fri" >> return Friday
, try (caseString "Sat") >> return Saturday
, caseString "Sun" >> return Sunday
]
year :: CharParser a Int
year = fourDigits
monthNum :: CharParser a Month
monthNum = do mn <- manyNtoM 1 2 digit
return $ intToMonth (read mn :: Int)
intToMonth :: Int -> Month
intToMonth 1 = January
intToMonth 2 = February
intToMonth 3 = March
intToMonth 4 = April
intToMonth 5 = May
intToMonth 6 = June
intToMonth 7 = July
intToMonth 8 = August
intToMonth 9 = September
intToMonth 10 = October
intToMonth 11 = November
intToMonth 12 = December
intToMonth _ = error "invalid month!"
monthName :: CharParser a Month
monthName = choice
[ try (caseString "Jan") >> return January
, caseString "Feb" >> return February
, try (caseString "Mar") >> return March
, try (caseString "Apr") >> return April
, caseString "May" >> return May
, try (caseString "Jun") >> return June
, caseString "Jul" >> return July
, caseString "Aug" >> return August
, caseString "Sep" >> return September
, caseString "Oct" >> return October
, caseString "Nov" >> return November
, caseString "Dec" >> return December
]
day :: CharParser a Int
day = do d <- manyNtoM 1 2 digit
return (read d :: Int)
hour :: CharParser a Int
hour = twoDigits
minute :: CharParser a Int
minute = twoDigits
second :: CharParser a Int
second = twoDigits
zone :: CharParser a Int
zone = choice
[ do { _ <- char '+'; h <- hour; m <- minute; return (((h*60)+m)*60) }
, do { _ <- char '-'; h <- hour; m <- minute; return (((h*60)+m)*60) }
, mkZone "UTC" 0
, mkZone "UT" 0
, mkZone "GMT" 0
, mkZone "EST" (5)
, mkZone "EDT" (4)
, mkZone "CST" (6)
, mkZone "CDT" (5)
, mkZone "MST" (7)
, mkZone "MDT" (6)
, mkZone "PST" (8)
, mkZone "PDT" (7)
, mkZone "CEST" 2
, mkZone "EEST" 3
, do { _ <- manyTill (oneOf $ ['a'..'z']++['A'..'Z']++" ")
(lookAhead space_digit);
return 0 }
]
where mkZone n o = try $ do { caseString n; return (o*60*60) }
space_digit = try $ do { _ <- char ' '; oneOf ['0'..'9'] }
nullCalendar :: CalendarTime
nullCalendar = CalendarTime 0 January 0 0 0 0 1 Sunday 0 "" 0 False