2008-11-11 15:34:05 +03:00
|
|
|
{-|
|
|
|
|
|
2008-11-27 03:35:00 +03:00
|
|
|
A 'SmartDate' is a date which may be partially-specified or relative.
|
|
|
|
Eg 2008/12/31, but also 2008/12, 12/31, tomorrow, last week, next year.
|
|
|
|
We represent these as a triple of strings like ("2008","12",""),
|
|
|
|
("","","tomorrow"), ("","last","week").
|
2008-11-11 15:34:05 +03:00
|
|
|
|
|
|
|
-}
|
|
|
|
|
2008-11-22 15:18:19 +03:00
|
|
|
module Ledger.Dates
|
|
|
|
where
|
2008-11-11 15:34:05 +03:00
|
|
|
|
|
|
|
import Data.Time.Clock
|
|
|
|
import Data.Time.Format
|
|
|
|
import Data.Time.Calendar
|
2008-11-27 04:49:13 +03:00
|
|
|
import Data.Time.Calendar.MonthDay
|
|
|
|
import Data.Time.Calendar.OrdinalDate
|
|
|
|
import Data.Time.Calendar.WeekDate
|
2008-11-11 15:34:05 +03:00
|
|
|
import Data.Time.LocalTime
|
|
|
|
import System.Locale (defaultTimeLocale)
|
|
|
|
import Text.Printf
|
|
|
|
import Data.Maybe
|
2008-11-27 03:35:00 +03:00
|
|
|
import Text.ParserCombinators.Parsec
|
|
|
|
import Text.ParserCombinators.Parsec.Char
|
|
|
|
import Text.ParserCombinators.Parsec.Combinator
|
|
|
|
import Ledger.Types
|
|
|
|
import Ledger.Utils
|
2008-11-11 15:34:05 +03:00
|
|
|
|
|
|
|
|
2008-11-27 07:01:07 +03:00
|
|
|
showDate :: Day -> String
|
|
|
|
showDate d = formatTime defaultTimeLocale "%Y/%m/%d" d
|
2008-11-11 15:34:05 +03:00
|
|
|
|
2008-11-27 07:01:07 +03:00
|
|
|
mkUTCTime :: Day -> TimeOfDay -> UTCTime
|
|
|
|
mkUTCTime day tod = localTimeToUTC utc (LocalTime day tod)
|
2008-11-11 15:34:05 +03:00
|
|
|
|
2008-11-27 07:01:07 +03:00
|
|
|
today :: IO Day
|
2008-11-26 00:30:21 +03:00
|
|
|
today = do
|
|
|
|
t <- getZonedTime
|
2008-11-27 07:01:07 +03:00
|
|
|
return $ localDay (zonedTimeToLocalTime t)
|
2008-11-27 03:35:00 +03:00
|
|
|
|
2008-11-27 07:01:07 +03:00
|
|
|
now :: IO UTCTime
|
|
|
|
now = getCurrentTime
|
2008-11-27 03:35:00 +03:00
|
|
|
|
2008-11-27 07:01:07 +03:00
|
|
|
elapsedSeconds :: Fractional a => UTCTime -> UTCTime -> a
|
|
|
|
elapsedSeconds t1 t2 = realToFrac $ diffUTCTime t1 t2
|
2008-11-27 03:35:00 +03:00
|
|
|
|
2008-11-27 07:01:07 +03:00
|
|
|
dayToUTC :: Day -> UTCTime
|
|
|
|
dayToUTC d = localTimeToUTC utc (LocalTime d midnight)
|
2008-11-27 03:35:00 +03:00
|
|
|
|
|
|
|
-- | Convert a fuzzy date string to an explicit yyyy/mm/dd string using
|
|
|
|
-- the provided date as reference point.
|
2008-11-27 07:01:07 +03:00
|
|
|
fixSmartDateStr :: Day -> String -> String
|
2008-11-27 03:35:00 +03:00
|
|
|
fixSmartDateStr t s = printf "%04d/%02d/%02d" y m d
|
|
|
|
where
|
2008-11-27 07:01:07 +03:00
|
|
|
(y,m,d) = toGregorian $ fixSmartDate t sdate
|
|
|
|
sdate = fromparse $ parsewith smartdate $ map toLower s
|
2008-11-27 03:35:00 +03:00
|
|
|
|
|
|
|
-- | Convert a SmartDate to an absolute date using the provided date as
|
|
|
|
-- reference point.
|
2008-11-27 07:01:07 +03:00
|
|
|
fixSmartDate :: Day -> SmartDate -> Day
|
|
|
|
fixSmartDate refdate sdate = fix sdate
|
2008-11-27 03:35:00 +03:00
|
|
|
where
|
2008-11-27 07:01:07 +03:00
|
|
|
fix :: SmartDate -> Day
|
|
|
|
fix ("","","today") = fromGregorian ry rm rd
|
|
|
|
fix ("","this","day") = fromGregorian ry rm rd
|
|
|
|
fix ("","","yesterday") = prevday refdate
|
|
|
|
fix ("","last","day") = prevday refdate
|
|
|
|
fix ("","","tomorrow") = nextday refdate
|
|
|
|
fix ("","next","day") = nextday refdate
|
|
|
|
fix ("","last","week") = prevweek refdate
|
|
|
|
fix ("","this","week") = thisweek refdate
|
|
|
|
fix ("","next","week") = nextweek refdate
|
|
|
|
fix ("","last","month") = prevmonth refdate
|
|
|
|
fix ("","this","month") = thismonth refdate
|
|
|
|
fix ("","next","month") = nextmonth refdate
|
|
|
|
fix ("","last","quarter") = prevquarter refdate
|
|
|
|
fix ("","this","quarter") = thisquarter refdate
|
|
|
|
fix ("","next","quarter") = nextquarter refdate
|
|
|
|
fix ("","last","year") = prevyear refdate
|
|
|
|
fix ("","this","year") = thisyear refdate
|
|
|
|
fix ("","next","year") = nextyear refdate
|
|
|
|
fix ("","",d) = fromGregorian ry rm (read d)
|
|
|
|
fix ("",m,d) = fromGregorian ry (read m) (read d)
|
|
|
|
fix (y,m,d) = fromGregorian (read y) (read m) (read d)
|
|
|
|
(ry,rm,rd) = toGregorian refdate
|
2008-11-27 03:35:00 +03:00
|
|
|
|
2008-11-27 05:49:22 +03:00
|
|
|
prevday :: Day -> Day
|
|
|
|
prevday = addDays (-1)
|
|
|
|
nextday = addDays 1
|
2008-11-27 04:49:13 +03:00
|
|
|
|
2008-11-27 05:49:22 +03:00
|
|
|
thisweek = startofweek
|
|
|
|
prevweek = startofweek . addDays (-7)
|
|
|
|
nextweek = startofweek . addDays 7
|
|
|
|
startofweek day = fromMondayStartWeek y w 1
|
2008-11-27 04:49:13 +03:00
|
|
|
where
|
2008-11-27 05:49:22 +03:00
|
|
|
(y,_,_) = toGregorian day
|
2008-11-27 04:49:13 +03:00
|
|
|
(w,_) = mondayStartWeek day
|
|
|
|
|
2008-11-27 05:49:22 +03:00
|
|
|
thismonth = startofmonth
|
|
|
|
prevmonth = startofmonth . addGregorianMonthsClip (-1)
|
|
|
|
nextmonth = startofmonth . addGregorianMonthsClip 1
|
|
|
|
startofmonth day = fromGregorian y m 1 where (y,m,_) = toGregorian day
|
|
|
|
|
|
|
|
thisquarter = startofquarter
|
|
|
|
prevquarter = startofquarter . addGregorianMonthsClip (-3)
|
|
|
|
nextquarter = startofquarter . addGregorianMonthsClip 3
|
|
|
|
startofquarter day = fromGregorian y (firstmonthofquarter m) 1
|
|
|
|
where
|
|
|
|
(y,m,_) = toGregorian day
|
|
|
|
firstmonthofquarter m = ((m-1) `div` 3) * 3 + 1
|
|
|
|
|
|
|
|
thisyear = startofyear
|
|
|
|
prevyear = startofyear . addGregorianYearsClip (-1)
|
|
|
|
nextyear = startofyear . addGregorianYearsClip 1
|
|
|
|
startofyear day = fromGregorian y 1 1 where (y,_,_) = toGregorian day
|
2008-11-27 03:35:00 +03:00
|
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
-- parsing
|
|
|
|
|
2008-11-11 15:34:05 +03:00
|
|
|
-- | Parse a date-time string to a time type, or raise an error.
|
2008-11-27 07:01:07 +03:00
|
|
|
parsedatetime :: String -> UTCTime
|
|
|
|
parsedatetime s =
|
2008-11-11 15:34:05 +03:00
|
|
|
parsetimewith "%Y/%m/%d %H:%M:%S" s $
|
|
|
|
error $ printf "could not parse timestamp \"%s\"" s
|
|
|
|
|
|
|
|
-- | Parse a date string to a time type, or raise an error.
|
2008-11-27 07:01:07 +03:00
|
|
|
parsedate :: String -> Day
|
|
|
|
parsedate s =
|
2008-11-11 15:34:05 +03:00
|
|
|
parsetimewith "%Y/%m/%d" s $
|
|
|
|
error $ printf "could not parse date \"%s\"" s
|
|
|
|
|
|
|
|
-- | Parse a time string to a time type using the provided pattern, or
|
|
|
|
-- return the default.
|
|
|
|
parsetimewith :: ParseTime t => String -> String -> t -> t
|
|
|
|
parsetimewith pat s def = fromMaybe def $ parseTime defaultTimeLocale pat s
|
|
|
|
|
2008-11-27 03:35:00 +03:00
|
|
|
{-|
|
|
|
|
Parse a date in any of the formats allowed in ledger's period expressions,
|
|
|
|
and maybe some others:
|
|
|
|
|
|
|
|
> 2004
|
|
|
|
> 2004/10
|
|
|
|
> 2004/10/1
|
|
|
|
> 10/1
|
|
|
|
> 21
|
|
|
|
> october, oct
|
|
|
|
> yesterday, today, tomorrow
|
|
|
|
> (not yet) this/next/last week/day/month/quarter/year
|
|
|
|
|
|
|
|
Returns a SmartDate, to be converted to a full date later (see fixSmartDate).
|
|
|
|
Assumes any text in the parse stream has been lowercased.
|
|
|
|
-}
|
|
|
|
smartdate :: Parser SmartDate
|
|
|
|
smartdate = do
|
2008-11-27 04:49:13 +03:00
|
|
|
let dateparsers = [ymd, ym, md, y, d, month, mon, today', yesterday, tomorrow,
|
|
|
|
lastthisnextthing
|
2008-11-27 03:35:00 +03:00
|
|
|
]
|
|
|
|
(y,m,d) <- choice $ map try dateparsers
|
|
|
|
return $ (y,m,d)
|
|
|
|
|
|
|
|
datesepchar = oneOf "/-."
|
|
|
|
|
|
|
|
ymd :: Parser SmartDate
|
|
|
|
ymd = do
|
|
|
|
y <- many1 digit
|
|
|
|
datesepchar
|
|
|
|
m <- many1 digit
|
|
|
|
guard (read m <= 12)
|
|
|
|
datesepchar
|
|
|
|
d <- many1 digit
|
|
|
|
guard (read d <= 31)
|
|
|
|
return (y,m,d)
|
|
|
|
|
|
|
|
ym :: Parser SmartDate
|
|
|
|
ym = do
|
|
|
|
y <- many1 digit
|
|
|
|
guard (read y > 12)
|
|
|
|
datesepchar
|
|
|
|
m <- many1 digit
|
|
|
|
guard (read m <= 12)
|
|
|
|
return (y,m,"1")
|
|
|
|
|
|
|
|
y :: Parser SmartDate
|
|
|
|
y = do
|
|
|
|
y <- many1 digit
|
|
|
|
guard (read y >= 1000)
|
|
|
|
return (y,"1","1")
|
|
|
|
|
|
|
|
d :: Parser SmartDate
|
|
|
|
d = do
|
|
|
|
d <- many1 digit
|
|
|
|
guard (read d <= 31)
|
|
|
|
return ("","",d)
|
|
|
|
|
|
|
|
md :: Parser SmartDate
|
|
|
|
md = do
|
|
|
|
m <- many1 digit
|
|
|
|
guard (read m <= 12)
|
|
|
|
datesepchar
|
|
|
|
d <- many1 digit
|
|
|
|
guard (read d <= 31)
|
|
|
|
return ("",m,d)
|
|
|
|
|
|
|
|
months = ["january","february","march","april","may","june",
|
|
|
|
"july","august","september","october","november","december"]
|
|
|
|
|
|
|
|
mons = ["jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"]
|
|
|
|
|
|
|
|
month :: Parser SmartDate
|
|
|
|
month = do
|
|
|
|
m <- choice $ map string months
|
|
|
|
let i = maybe 0 (+1) $ (map toLower m) `elemIndex` months
|
|
|
|
return ("",show i,"1")
|
|
|
|
|
|
|
|
mon :: Parser SmartDate
|
|
|
|
mon = do
|
|
|
|
m <- choice $ map string mons
|
|
|
|
let i = maybe 0 (+1) $ (map toLower m) `elemIndex` mons
|
|
|
|
return ("",show i,"1")
|
|
|
|
|
|
|
|
today',yesterday,tomorrow :: Parser SmartDate
|
|
|
|
today' = string "today" >> return ("","","today")
|
|
|
|
yesterday = string "yesterday" >> return ("","","yesterday")
|
|
|
|
tomorrow = string "tomorrow" >> return ("","","tomorrow")
|
|
|
|
|
|
|
|
lastthisnextthing :: Parser SmartDate
|
|
|
|
lastthisnextthing = do
|
|
|
|
r <- choice [
|
|
|
|
string "last"
|
|
|
|
,string "this"
|
|
|
|
,string "next"
|
|
|
|
]
|
2008-11-27 04:49:13 +03:00
|
|
|
--many1 spacenonewline
|
2008-11-27 05:49:22 +03:00
|
|
|
many spacenonewline -- allow the space to be omitted for easier scripting
|
2008-11-27 03:35:00 +03:00
|
|
|
p <- choice [
|
|
|
|
string "day"
|
|
|
|
,string "week"
|
|
|
|
,string "month"
|
|
|
|
,string "quarter"
|
|
|
|
,string "year"
|
|
|
|
]
|
|
|
|
return ("",r,p)
|
2008-11-22 15:18:19 +03:00
|
|
|
|