module Todos.Dates
(parseDate, getCurrentDateTime,
pSpecDates)
where
import Prelude.Unicode
import Data.Char (toUpper)
import Data.List
import Data.Time.Calendar
import Data.Time.LocalTime
import Text.ParserCombinators.Parsec
import Todos.Types
getCurrentDateTime ∷ IO DateTime
getCurrentDateTime = do
zt ← getZonedTime
let lt = zonedTimeToLocalTime zt
ld = localDay lt
ltod = localTimeOfDay lt
(y,m,d) = toGregorian ld
h = todHour ltod
mins = todMin ltod
s = round $ todSec ltod
return $ DateTime (fromIntegral y) m d h mins s
uppercase ∷ String → String
uppercase = map toUpper
isPrefixOfI ∷ String → String → Bool
p `isPrefixOfI` s = (uppercase p) `isPrefixOf` (uppercase s)
lookupS ∷ String → [(String,a)] → Maybe a
lookupS _ [] = Nothing
lookupS k ((k',v):other) | k `isPrefixOfI` k' = Just v
| otherwise = lookupS k other
monthsN ∷ [(String,Int)]
monthsN = zip months [1..]
lookupMonth ∷ String → Maybe Int
lookupMonth n = lookupS n monthsN
date ∷ Int → Int → Int → DateTime
date y m d = DateTime y m d 0 0 0
addTime ∷ DateTime → Time → DateTime
addTime dt t = dt {
hour = tHour t + hour dt,
minute = tMinute t + minute dt,
second = tSecond t + second dt }
times ∷ Int → CharParser st t → CharParser st [t]
times 0 _ = return []
times n p = do
ts ← times (n1) p
t ← optionMaybe p
case t of
Just t' → return (ts ++ [t'])
Nothing → return ts
number ∷ Int → Int → CharParser st Int
number n m = do
t ← read `fmap` (n `times` digit)
if t > m
then fail "number too large"
else return t
pYear ∷ CharParser st Int
pYear = do
y ← number 4 10000
if y < 2000
then return (y+2000)
else return y
pMonth ∷ CharParser st Int
pMonth = number 2 12
pDay ∷ CharParser st Int
pDay = number 2 31
euroNumDate ∷ CharParser st DateTime
euroNumDate = do
d ← pDay
char '.'
m ← pMonth
char '.'
y ← pYear
return $ date y m d
americanDate ∷ CharParser st DateTime
americanDate = do
y ← pYear
char '/'
m ← pMonth
char '/'
d ← pDay
return $ date y m d
euroNumDate' ∷ Int → CharParser st DateTime
euroNumDate' year = do
d ← pDay
char '.'
m ← pMonth
return $ date year m d
americanDate' ∷ Int → CharParser st DateTime
americanDate' year = do
m ← pMonth
char '/'
d ← pDay
return $ date year m d
strDate ∷ CharParser st DateTime
strDate = do
d ← pDay
space
ms ← many1 letter
case lookupMonth ms of
Nothing → fail $ "unknown month: "++ms
Just m → do
space
y ← pYear
notFollowedBy $ char ':'
return $ date y m d
strDate' ∷ Int → CharParser st DateTime
strDate' year = do
d ← pDay
space
ms ← many1 letter
case lookupMonth ms of
Nothing → fail $ "unknown month: "++ms
Just m → return $ date year m d
time24 ∷ CharParser st Time
time24 = do
h ← number 2 23
char ':'
m ← number 2 59
x ← optionMaybe $ char ':'
case x of
Nothing → return $ Time h m 0
Just _ → do
s ← number 2 59
notFollowedBy letter
return $ Time h m s
ampm ∷ CharParser st Int
ampm = do
s ← many1 letter
case map toUpper s of
"AM" → return 0
"PM" → return 12
_ → fail "AM/PM expected"
time12 ∷ CharParser st Time
time12 = do
h ← number 2 12
char ':'
m ← number 2 59
x ← optionMaybe $ char ':'
s ← case x of
Nothing → return 0
Just _ → number 2 59
optional space
hd ← ampm
return $ Time (h+hd) m s
pAbsDate ∷ Int → CharParser st DateTime
pAbsDate year = do
date ← choice $ map try $ map ($ year) $ [
const euroNumDate,
const americanDate,
const strDate,
strDate',
euroNumDate',
americanDate']
optional $ char ','
s ← optionMaybe space
case s of
Nothing → return date
Just _ → do
t ← choice $ map try [time12,time24]
return $ date `addTime` t
data DateIntervalType = Day | Week | Month | Year
deriving (Eq,Show,Read)
data DateInterval = Days ℤ
| Weeks ℤ
| Months ℤ
| Years ℤ
deriving (Eq,Show)
convertTo ∷ DateTime → Day
convertTo dt = fromGregorian (fromIntegral $ year dt) (month dt) (day dt)
convertFrom ∷ Day → DateTime
convertFrom dt =
let (y,m,d) = toGregorian dt
in date (fromIntegral y) m d
modifyDate ∷ (t → Day → Day) → t → DateTime → DateTime
modifyDate fn x dt = convertFrom $ fn x $ convertTo dt
addInterval ∷ DateTime → DateInterval → DateTime
addInterval dt (Days ds) = modifyDate addDays ds dt
addInterval dt (Weeks ws) = modifyDate addDays (ws*7) dt
addInterval dt (Months ms) = modifyDate addGregorianMonthsClip ms dt
addInterval dt (Years ys) = modifyDate addGregorianYearsClip ys dt
maybePlural ∷ String → CharParser st String
maybePlural str = do
r ← string str
optional $ char 's'
return (capitalize r)
pDateInterval ∷ CharParser st DateIntervalType
pDateInterval = do
s ← choice $ map maybePlural ["day", "week", "month", "year"]
return $ read s
pRelDate ∷ DateTime → CharParser st DateTime
pRelDate date = do
offs ← (try futureDate) <|> (try passDate) <|> (try today) <|> (try tomorrow) <|> yesterday
return $ date `addInterval` offs
futureDate ∷ CharParser st DateInterval
futureDate = do
string "in "
n ← many1 digit
char ' '
tp ← pDateInterval
case tp of
Day → return $ Days (read n)
Week → return $ Weeks (read n)
Month → return $ Months (read n)
Year → return $ Years (read n)
passDate ∷ CharParser st DateInterval
passDate = do
n ← many1 digit
char ' '
tp ← pDateInterval
string " ago"
case tp of
Day → return $ Days $ (read n)
Week → return $ Weeks $ (read n)
Month → return $ Months $ (read n)
Year → return $ Years $ (read n)
today ∷ CharParser st DateInterval
today = do
string "today"
return $ Days 0
tomorrow ∷ CharParser st DateInterval
tomorrow = do
string "tomorrow"
return $ Days 1
yesterday ∷ CharParser st DateInterval
yesterday = do
string "yesterday"
return $ Days (1)
pDate ∷ DateTime → CharParser st DateTime
pDate date = (try $ pRelDate date) <|> (try $ pAbsDate $ year date)
dateType ∷ String → DateType
dateType "start" = StartDate
dateType "end" = EndDate
dateType "deadline" = Deadline
dateType _ = error "unknown date type"
pSpecDate ∷ DateTime → CharParser st (DateType, DateTime)
pSpecDate date = do
tp ← choice $ map string ["start","end","deadline"]
string ": "
dt ← pDate date
return (dateType tp, dt)
pSpecDates ∷ DateTime → CharParser st [(DateType, DateTime)]
pSpecDates date = do
char '('
pairs ← (pSpecDate date) `sepBy1` (string "; ")
string ") "
return pairs
parseDate ∷ DateTime
→ String
→ Either ParseError DateTime
parseDate date s = runParser (pDate date) () "" s