refactoring date parsing, FuzzyDate
This commit is contained in:
		
							parent
							
								
									49a84957a9
								
							
						
					
					
						commit
						8c56c3c4b3
					
				| @ -38,6 +38,12 @@ instance Show Date where | |||||||
| instance Show DateTime where  | instance Show DateTime where  | ||||||
|    show (DateTime t) = formatTime defaultTimeLocale "%Y/%m/%d %H:%M:%S" t |    show (DateTime t) = formatTime defaultTimeLocale "%Y/%m/%d %H:%M:%S" t | ||||||
| 
 | 
 | ||||||
|  | -- | A fuzzy date is either a partially-specified or a relative date. | ||||||
|  | -- We represent it as a triple of strings such as | ||||||
|  | -- ("2008","01","01") or ("2008","","") or ("","","tomorrow") or  | ||||||
|  | -- ("","last|this|next","day|week|month|quarter|year"). | ||||||
|  | type FuzzyDate = (String,String,String) | ||||||
|  | 
 | ||||||
| mkDate :: Day -> Date | mkDate :: Day -> Date | ||||||
| mkDate day = Date (localTimeToUTC utc (LocalTime day midnight)) | mkDate day = Date (localTimeToUTC utc (LocalTime day midnight)) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -482,6 +482,7 @@ ledgerfromtimelog = do | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| -- misc parsing | -- misc parsing | ||||||
|  | 
 | ||||||
| {-|  | {-|  | ||||||
| Parse a date in any of the formats allowed in ledger's period expressions, | Parse a date in any of the formats allowed in ledger's period expressions, | ||||||
| and maybe some others: | and maybe some others: | ||||||
| @ -495,11 +496,10 @@ and maybe some others: | |||||||
| > yesterday, today, tomorrow | > yesterday, today, tomorrow | ||||||
| > (not yet) this/next/last week/day/month/quarter/year | > (not yet) this/next/last week/day/month/quarter/year | ||||||
| 
 | 
 | ||||||
| Returns a triple of possibly empty strings for year, month and day | Returns a FuzzyDate, to be converted to a full date later, in the IO | ||||||
| (defaults are supplied later in the IO layer.) | layer.  Note: assumes any text in the parse stream has been lowercased. | ||||||
| Note: only recognises month names in lowercase. |  | ||||||
| -} | -} | ||||||
| smartdate :: Parser (String,String,String) | smartdate :: Parser FuzzyDate | ||||||
| smartdate = do | smartdate = do | ||||||
|   let dateparsers = [ymd, ym, md, y, d, month, mon, today', yesterday, tomorrow] |   let dateparsers = [ymd, ym, md, y, d, month, mon, today', yesterday, tomorrow] | ||||||
|   (y,m,d) <- choice $ map try dateparsers |   (y,m,d) <- choice $ map try dateparsers | ||||||
| @ -507,7 +507,7 @@ smartdate = do | |||||||
| 
 | 
 | ||||||
| datesepchar = oneOf "/-." | datesepchar = oneOf "/-." | ||||||
| 
 | 
 | ||||||
| ymd :: Parser (String,String,String) | ymd :: Parser FuzzyDate | ||||||
| ymd = do | ymd = do | ||||||
|   y <- many1 digit |   y <- many1 digit | ||||||
|   datesepchar |   datesepchar | ||||||
| @ -518,7 +518,7 @@ ymd = do | |||||||
|   guard (read d <= 31) |   guard (read d <= 31) | ||||||
|   return (y,m,d) |   return (y,m,d) | ||||||
| 
 | 
 | ||||||
| ym :: Parser (String,String,String) | ym :: Parser FuzzyDate | ||||||
| ym = do | ym = do | ||||||
|   y <- many1 digit |   y <- many1 digit | ||||||
|   guard (read y > 12) |   guard (read y > 12) | ||||||
| @ -527,19 +527,19 @@ ym = do | |||||||
|   guard (read m <= 12) |   guard (read m <= 12) | ||||||
|   return (y,m,"1") |   return (y,m,"1") | ||||||
| 
 | 
 | ||||||
| y :: Parser (String,String,String) | y :: Parser FuzzyDate | ||||||
| y = do | y = do | ||||||
|   y <- many1 digit |   y <- many1 digit | ||||||
|   guard (read y >= 1000) |   guard (read y >= 1000) | ||||||
|   return (y,"1","1") |   return (y,"1","1") | ||||||
| 
 | 
 | ||||||
| d :: Parser (String,String,String) | d :: Parser FuzzyDate | ||||||
| d = do | d = do | ||||||
|   d <- many1 digit |   d <- many1 digit | ||||||
|   guard (read d <= 31) |   guard (read d <= 31) | ||||||
|   return ("","",d) |   return ("","",d) | ||||||
| 
 | 
 | ||||||
| md :: Parser (String,String,String) | md :: Parser FuzzyDate | ||||||
| md = do | md = do | ||||||
|   m <- many1 digit |   m <- many1 digit | ||||||
|   guard (read m <= 12) |   guard (read m <= 12) | ||||||
| @ -553,22 +553,40 @@ months = ["january","february","march","april","may","june", | |||||||
| 
 | 
 | ||||||
| mons = ["jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"] | mons = ["jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"] | ||||||
| 
 | 
 | ||||||
| month :: Parser (String,String,String) | month :: Parser FuzzyDate | ||||||
| month = do | month = do | ||||||
|   m <- choice $ map string months |   m <- choice $ map string months | ||||||
|   let i = maybe 0 (+1) $ (map toLower m) `elemIndex` months |   let i = maybe 0 (+1) $ (map toLower m) `elemIndex` months | ||||||
|   return ("",show i,"1") |   return ("",show i,"1") | ||||||
| 
 | 
 | ||||||
| mon :: Parser (String,String,String) | mon :: Parser FuzzyDate | ||||||
| mon = do | mon = do | ||||||
|   m <- choice $ map string mons |   m <- choice $ map string mons | ||||||
|   let i = maybe 0 (+1) $ (map toLower m) `elemIndex` mons |   let i = maybe 0 (+1) $ (map toLower m) `elemIndex` mons | ||||||
|   return ("",show i,"1") |   return ("",show i,"1") | ||||||
| 
 | 
 | ||||||
|  | today',yesterday,tomorrow :: Parser FuzzyDate | ||||||
| today'    = string "today"     >> return ("","","today") | today'    = string "today"     >> return ("","","today") | ||||||
| yesterday = string "yesterday" >> return ("","","yesterday") | yesterday = string "yesterday" >> return ("","","yesterday") | ||||||
| tomorrow  = string "tomorrow"  >> return ("","","tomorrow") | tomorrow  = string "tomorrow"  >> return ("","","tomorrow") | ||||||
| 
 | 
 | ||||||
|  | lastthisnextthing :: Parser FuzzyDate | ||||||
|  | lastthisnextthing = do | ||||||
|  |   r <- choice [ | ||||||
|  |         string "last" | ||||||
|  |        ,string "this" | ||||||
|  |        ,string "next" | ||||||
|  |       ] | ||||||
|  |   many1 spacenonewline | ||||||
|  |   p <- choice [ | ||||||
|  |         string "day" | ||||||
|  |        ,string "week" | ||||||
|  |        ,string "month" | ||||||
|  |        ,string "quarter" | ||||||
|  |        ,string "year" | ||||||
|  |       ] | ||||||
|  |   return ("",r,p) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| type TransactionMatcher = Transaction -> Bool | type TransactionMatcher = Transaction -> Bool | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										52
									
								
								Options.hs
									
									
									
									
									
								
							
							
						
						
									
										52
									
								
								Options.hs
									
									
									
									
									
								
							| @ -88,36 +88,46 @@ parseArguments = do | |||||||
|     (opts,[],[])       -> do {opts' <- fixDates opts; return (opts',[],[])} |     (opts,[],[])       -> do {opts' <- fixDates opts; return (opts',[],[])} | ||||||
|     (opts,_,errs)      -> ioError (userError (concat errs ++ usage)) |     (opts,_,errs)      -> ioError (userError (concat errs ++ usage)) | ||||||
| 
 | 
 | ||||||
| -- | Convert any fuzzy/relative dates within these option values to | -- | Convert any fuzzy dates within these option values to explicit ones, | ||||||
| -- explicit ones, based on today's date. | -- based on today's date. | ||||||
| fixDates :: [Opt] -> IO [Opt] | fixDates :: [Opt] -> IO [Opt] | ||||||
| fixDates opts = do | fixDates opts = do | ||||||
|   t <- today |   t <- today | ||||||
|   return $ map (fixopt t) opts |   return $ map (fixopt t) opts | ||||||
|   where |   where | ||||||
|     fixopt t (Begin s)   = Begin $ fixdate t s |     fixopt t (Begin s)   = Begin $ fixdatestr t s | ||||||
|     fixopt t (End s)     = End $ fixdate t s |     fixopt t (End s)     = End $ fixdatestr t s | ||||||
|     fixopt t (Display s) = -- hacky |     fixopt t (Display s) = -- hacky | ||||||
|         Display $ gsubRegexPRBy "\\[.+?\\]" fixbracketeddate s |         Display $ gsubRegexPRBy "\\[.+?\\]" fixbracketeddatestr s | ||||||
|         where fixbracketeddate s = "[" ++ (fixdate t $ init $ tail s) ++ "]" |         where fixbracketeddatestr s = "[" ++ (fixdatestr t $ init $ tail s) ++ "]" | ||||||
|     fixopt _ o            = o |     fixopt _ o            = o | ||||||
| 
 | 
 | ||||||
| -- | Convert a fuzzy date string to an explicit yyyy/mm/dd date, using the | -- | Convert a fuzzy date string to an explicit yyyy/mm/dd string using | ||||||
| -- provided today's date for defaults. | -- the provided date as reference point. | ||||||
| fixdate :: Date -> String -> String | fixdatestr :: Date -> String -> String | ||||||
| fixdate t s = printf "%04s/%02s/%02s" y' m' d' | fixdatestr t s = printf "%04d/%02d/%02d" y m d | ||||||
|     where |     where | ||||||
|       (ty,tm,td) = dateComponents t |       pdate = fromparse $ parsewith smartdate $ map toLower s | ||||||
|       (y,m,d) = fromparse $ parsewith smartdate $ map toLower s |       (y,m,d) = dateComponents $ fixFuzzyDate t pdate | ||||||
|       (y',m',d') = case (y,m,d) of  | 
 | ||||||
|                      ("","","today") -> (show ty,show tm,show td) | -- | Convert a FuzzyDate to an absolute date using the provided date as | ||||||
|                      ("","","yesterday") -> (show y, show m, show d)  | -- reference point. | ||||||
|                          where (y,m,d) = toGregorian $ addDays (-1) $ fromGregorian ty tm td | fixFuzzyDate :: Date -> FuzzyDate -> Date | ||||||
|                      ("","","tomorrow") -> (show y, show m, show d)  | fixFuzzyDate refdate pdate = mkDate $ fromGregorian y m d | ||||||
|                          where (y,m,d) = toGregorian $ addDays 1 $ fromGregorian ty tm td |     where | ||||||
|                      ("","",d)       -> (show ty,show tm,d) |       (y,m,d) = fix pdate | ||||||
|                      ("",m,d)        -> (show ty,m,d) |       fix :: FuzzyDate -> (Integer,Int,Int) | ||||||
|                      otherwise       -> (y,m,d) |       fix ("","","today")     = (ry, rm, rd) | ||||||
|  |       fix ("","","yesterday") = dateComponents $ lastday refdate | ||||||
|  |       fix ("","","tomorrow")  = dateComponents $ nextday refdate | ||||||
|  |       fix ("","",d)           = (ry, rm, read d) | ||||||
|  |       fix ("",m,d)            = (ry, read m, read d) | ||||||
|  |       fix (y,m,d)             = (read y, read m, read d) | ||||||
|  |       (ry,rm,rd) = dateComponents refdate | ||||||
|  | 
 | ||||||
|  | lastday, nextday :: Date -> Date | ||||||
|  | lastday = mkDate . (addDays (-1)) . utctDay . dateToUTC | ||||||
|  | nextday = mkDate . (addDays 1) . utctDay . dateToUTC | ||||||
| 
 | 
 | ||||||
| -- | Get the ledger file path from options, an environment variable, or a default | -- | Get the ledger file path from options, an environment variable, or a default | ||||||
| ledgerFilePathFromOpts :: [Opt] -> IO String | ledgerFilePathFromOpts :: [Opt] -> IO String | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								Tests.hs
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Tests.hs
									
									
									
									
									
								
							| @ -100,7 +100,7 @@ misc_tests = TestList [ | |||||||
|   "smartparsedate"     ~: do |   "smartparsedate"     ~: do | ||||||
|     t <- today |     t <- today | ||||||
|     let (ty,tm,td) = dateComponents t |     let (ty,tm,td) = dateComponents t | ||||||
|     let str `gives` datestr = assertequal datestr (fixdate t str) |     let str `gives` datestr = assertequal datestr (fixdatestr t str) | ||||||
|     "1999-12-02" `gives` "1999/12/02" |     "1999-12-02" `gives` "1999/12/02" | ||||||
|     "1999.12.02" `gives` "1999/12/02" |     "1999.12.02" `gives` "1999/12/02" | ||||||
|     "1999/3/2"   `gives` "1999/03/02" |     "1999/3/2"   `gives` "1999/03/02" | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user