diff --git a/CHANGES b/CHANGES
--- a/CHANGES
+++ b/CHANGES
@@ -1,13 +1,44 @@
 API-ish changes in the hledger-lib package.
 See also the hledger and project change logs (for user-visible changes).
 
-# 1.3.2 (2017/9/6)
+# 1.4 (2017/9/30)
 
-* fix missing parsec dep breaking doctests with ghc 8.2 (fpco/stackage#2835)
+* add readJournalFile[s]WithOpts, with simpler arguments and support
+for detecting new transactions since the last read.
 
+* query: add payee: and note: query terms, improve description/payee/note docs (Jakub Zárybnický, Simon Michael, #598, #608)
+
+* journal, cli: make trailing whitespace significant in regex account aliases
+Trailing whitespace in the replacement part of a regular expression
+account alias is now significant. Eg, converting a parent account to
+just an account name prefix: --alias '/:acct:/=:acct '
+
+* timedot: allow a quantity of seconds, minutes, days, weeks, months
+  or years to be logged as Ns, Nm, Nd, Nw, Nmo, Ny
+
+* csv: switch the order of generated postings, so account1 is first.
+This simplifies things and facilitates future improvements.
+
+* csv: show the "creating/using rules file" message only with --debug
+
+* csv: fix multiple includes in one rules file
+
+* csv: add "newest-first" rule for more robust same-day ordering
+
+* deps: allow ansi-terminal 0.7
+
+* deps: add missing parsec lower bound, possibly related to #596, fpco/stackage#2835
+
+* deps: drop oldtime flag, require time 1.5+
+
+* deps: remove ghc < 7.6 support, remove obsolete CPP conditionals
+
+* deps: fix test suite with ghc 8.2
+
+
 # 1.3.1 (2017/8/25)
 
-* Fix a bug with -H showing nothing for empty periods (Nicholas Niro)
+* Fix a bug with -H showing nothing for empty periods (#583, Nicholas Niro)
 This patch fixes a bug that happened when using the -H option on
 a period without any transaction. Previously, the behavior was no
 output at all even though it should have shown the previous ending balances
diff --git a/Hledger/Data/Account.hs b/Hledger/Data/Account.hs
--- a/Hledger/Data/Account.hs
+++ b/Hledger/Data/Account.hs
@@ -11,7 +11,9 @@
 where
 import Data.List
 import Data.Maybe
+import Data.Ord
 import qualified Data.Map as M
+import Data.Text (pack,unpack)
 import Safe (headMay, lookupJustDef)
 import Test.HUnit
 import Text.Printf
@@ -26,7 +28,7 @@
 -- deriving instance Show Account
 instance Show Account where
     show Account{..} = printf "Account %s (boring:%s, postings:%d, ebalance:%s, ibalance:%s)"
-                       aname
+                       (pack $ regexReplace ":" "_" $ unpack aname)  -- hide : so pretty-show doesn't break line
                        (if aboring then "y" else "n" :: String)
                        anumpostings
                        (showMixedAmount aebalance)
@@ -182,6 +184,20 @@
 filterAccounts p a
     | p a       = a : concatMap (filterAccounts p) (asubs a)
     | otherwise = concatMap (filterAccounts p) (asubs a)
+
+-- | Sort each level of an account tree by inclusive amount,
+-- so that the accounts with largest normal balances are listed first.  
+-- The provided normal balance sign determines whether normal balances
+-- are negative or positive.
+sortAccountTreeByAmount :: NormalBalance -> Account -> Account
+sortAccountTreeByAmount normalsign a
+  | null $ asubs a = a
+  | otherwise      = a{asubs=
+                        sortBy (maybeflip $ comparing aibalance) $ 
+                        map (sortAccountTreeByAmount normalsign) $ asubs a}
+  where
+    maybeflip | normalsign==NormalNegative = id
+              | otherwise                  = flip
 
 -- | Search an account list by name.
 lookupAccount :: AccountName -> [Account] -> Maybe Account
diff --git a/Hledger/Data/Journal.hs b/Hledger/Data/Journal.hs
--- a/Hledger/Data/Journal.hs
+++ b/Hledger/Data/Journal.hs
@@ -20,6 +20,7 @@
   commodityStylesFromAmounts,
   journalConvertAmountsToCost,
   journalFinalise,
+  journalPivot,
   -- * Filtering
   filterJournalTransactions,
   filterJournalPostings,
@@ -293,9 +294,9 @@
 -- | A query for Cash (-equivalent) accounts in this journal (ie,
 -- accounts which appear on the cashflow statement.)  This is currently
 -- hard-coded to be all the Asset accounts except for those containing the
--- case-insensitive regex @(receivable|A/R)@.
+-- case-insensitive regex @(receivable|:A/R|:fixed)@.
 journalCashAccountQuery  :: Journal -> Query
-journalCashAccountQuery j = And [journalAssetAccountQuery j, Not $ Acct "(receivable|A/R)"]
+journalCashAccountQuery j = And [journalAssetAccountQuery j, Not $ Acct "(receivable|:A/R|:fixed)"]
 
 -- Various kinds of filtering on journals. We do it differently depending
 -- on the command.
@@ -885,6 +886,32 @@
                             ]}
 -- #endif
 
+-- | Apply the pivot transformation to all postings in a journal,
+-- replacing their account name by their value for the given field or tag.
+journalPivot :: Text -> Journal -> Journal
+journalPivot fieldortagname j = j{jtxns = map (transactionPivot fieldortagname) . jtxns $ j}
+
+-- | Replace this transaction's postings' account names with the value
+-- of the given field or tag, if any.
+transactionPivot :: Text -> Transaction -> Transaction         
+transactionPivot fieldortagname t = t{tpostings = map (postingPivot fieldortagname) . tpostings $ t}
+
+-- | Replace this posting's account name with the value
+-- of the given field or tag, if any, otherwise the empty string.
+postingPivot :: Text -> Posting -> Posting         
+postingPivot fieldortagname p = p{paccount = pivotedacct, porigin = Just $ originalPosting p}
+  where
+    pivotedacct
+      | Just t <- ptransaction p, fieldortagname == "code"        = tcode t  
+      | Just t <- ptransaction p, fieldortagname == "description" = tdescription t  
+      | Just t <- ptransaction p, fieldortagname == "payee"       = transactionPayee t  
+      | Just t <- ptransaction p, fieldortagname == "note"        = transactionNote t  
+      | Just (_, value) <- postingFindTag fieldortagname p        = value
+      | otherwise                                                 = ""
+
+postingFindTag :: TagName -> Posting -> Maybe (TagName, TagValue)         
+postingFindTag tagname p = find ((tagname==) . fst) $ postingAllTags p
+
 -- Misc helpers
 
 -- | Check if a set of hledger account/description filter patterns matches the
@@ -929,6 +956,10 @@
 --     expenses:supplies  $1
 --     assets:cash
 --
+-- 2008/10/01 take a loan
+--     assets:bank:checking $1
+--     liabilities:debts    $-1
+--
 -- 2008/12/31 * pay off
 --     liabilities:debts  $1
 --     assets:bank:checking
@@ -1000,6 +1031,22 @@
              tpostings=["expenses:food" `post` usd 1
                        ,"expenses:supplies" `post` usd 1
                        ,"assets:cash" `post` missingamt
+                       ],
+             tpreceding_comment_lines=""
+           }
+          ,
+           txnTieKnot $ Transaction {
+             tindex=0,
+             tsourcepos=nullsourcepos,
+             tdate=parsedate "2008/10/01",
+             tdate2=Nothing,
+             tstatus=Unmarked,
+             tcode="",
+             tdescription="take a loan",
+             tcomment="",
+             ttags=[],
+             tpostings=["assets:bank:checking" `post` usd 1
+                       ,"liabilities:debts" `post` usd (-1)
                        ],
              tpreceding_comment_lines=""
            }
diff --git a/Hledger/Data/Ledger.hs b/Hledger/Data/Ledger.hs
--- a/Hledger/Data/Ledger.hs
+++ b/Hledger/Data/Ledger.hs
@@ -93,8 +93,8 @@
 tests_ledgerFromJournal = [
  "ledgerFromJournal" ~: do
   assertEqual "" (0) (length $ ledgerPostings $ ledgerFromJournal Any nulljournal)
-  assertEqual "" (11) (length $ ledgerPostings $ ledgerFromJournal Any samplejournal)
-  assertEqual "" (6) (length $ ledgerPostings $ ledgerFromJournal (Depth 2) samplejournal)
+  assertEqual "" (13) (length $ ledgerPostings $ ledgerFromJournal Any samplejournal)
+  assertEqual "" (7) (length $ ledgerPostings $ ledgerFromJournal (Depth 2) samplejournal)
  ]
 
 tests_Hledger_Data_Ledger = TestList $
diff --git a/Hledger/Data/Posting.hs b/Hledger/Data/Posting.hs
--- a/Hledger/Data/Posting.hs
+++ b/Hledger/Data/Posting.hs
@@ -25,7 +25,6 @@
   hasAmount,
   postingAllTags,
   transactionAllTags,
-  postingAllImplicitTags,
   relatedPostings,
   removePrices,
   -- * date operations
@@ -175,33 +174,22 @@
                                Nothing -> Unmarked
   | otherwise = s
 
--- | Implicit tags for this transaction.
-transactionImplicitTags :: Transaction -> [Tag]
-transactionImplicitTags t = filter (not . T.null . snd) [("code", tcode t)
-                                                        ,("description", tdescription t)
-                                                        ,("payee", transactionPayee t)
-                                                        ,("note", transactionNote t)
-                                                        ]
-
 transactionPayee :: Transaction -> Text
 transactionPayee = fst . payeeAndNoteFromDescription . tdescription
 
 transactionNote :: Transaction -> Text
-transactionNote = fst . payeeAndNoteFromDescription . tdescription
+transactionNote = snd . payeeAndNoteFromDescription . tdescription
 
 -- | Parse a transaction's description into payee and note (aka narration) fields,
 -- assuming a convention of separating these with | (like Beancount).
 -- Ie, everything up to the first | is the payee, everything after it is the note.
 -- When there's no |, payee == note == description.
 payeeAndNoteFromDescription :: Text -> (Text,Text)
-payeeAndNoteFromDescription t = (textstrip p, textstrip $ T.tail n)
+payeeAndNoteFromDescription t
+  | T.null n = (t, t)
+  | otherwise = (textstrip p, textstrip $ T.drop 1 n)
   where
-    (p,n) = T.breakOn "|" t
-
--- | Tags for this posting including implicit and any inherited from its parent transaction.
-postingAllImplicitTags :: Posting -> [Tag]
-postingAllImplicitTags p = ptags p ++ maybe [] transactionTags (ptransaction p)
-    where transactionTags t = ttags t ++ transactionImplicitTags t
+    (p, n) = T.span (/= '|') t
 
 -- | Tags for this posting including any inherited from its parent transaction.
 postingAllTags :: Posting -> [Tag]
diff --git a/Hledger/Data/StringFormat.hs b/Hledger/Data/StringFormat.hs
--- a/Hledger/Data/StringFormat.hs
+++ b/Hledger/Data/StringFormat.hs
@@ -10,7 +10,7 @@
         , StringFormat(..)
         , StringFormatComponent(..)
         , ReportItemField(..)
-        , tests
+        , tests_Hledger_Data_StringFormat
         ) where
 
 import Prelude ()
@@ -147,7 +147,7 @@
     Left  error -> assertFailure $ show error
     Right actual -> assertEqual ("Input: " ++ s) expected actual
 
-tests = test [ formattingTests ++ parserTests ]
+tests_Hledger_Data_StringFormat = test [ formattingTests ++ parserTests ]
 
 formattingTests = [
       testFormat (FormatLiteral " ")                                ""            " "
diff --git a/Hledger/Data/Types.hs b/Hledger/Data/Types.hs
--- a/Hledger/Data/Types.hs
+++ b/Hledger/Data/Types.hs
@@ -356,6 +356,15 @@
   aboring                   :: Bool           -- ^ used in the accounts report to label elidable parents
   } deriving (Typeable, Data, Generic)
 
+-- | Whether an account's balance is normally a positive number (in accounting terms,
+-- normally a debit balance), as for asset and expense accounts, or a negative number
+-- (in accounting terms, normally a credit balance), as for liability, equity and 
+-- income accounts. Cf https://en.wikipedia.org/wiki/Normal_balance .
+data NormalBalance = 
+    NormalPositive -- ^ normally debit - assets, expenses...
+  | NormalNegative -- ^ normally credit - liabilities, equity, income...
+  deriving (Show, Data, Eq) 
+
 -- | A Ledger has the journal it derives from, and the accounts
 -- derived from that. Accounts are accessible both list-wise and
 -- tree-wise, since each one knows its parent and subs; the first
diff --git a/Hledger/Query.hs b/Hledger/Query.hs
--- a/Hledger/Query.hs
+++ b/Hledger/Query.hs
@@ -225,6 +225,8 @@
     ,"amt"
     ,"code"
     ,"desc"
+    ,"payee"
+    ,"note"
     ,"acct"
     ,"date"
     ,"date2"
@@ -260,6 +262,8 @@
     Right _ -> Left Any -- not:somequeryoption will be ignored
 parseQueryTerm _ (T.stripPrefix "code:" -> Just s) = Left $ Code $ T.unpack s
 parseQueryTerm _ (T.stripPrefix "desc:" -> Just s) = Left $ Desc $ T.unpack s
+parseQueryTerm _ (T.stripPrefix "payee:" -> Just s) = Left $ Tag "payee" $ Just $ T.unpack s
+parseQueryTerm _ (T.stripPrefix "note:" -> Just s) = Left $ Tag "note" $ Just $ T.unpack s
 parseQueryTerm _ (T.stripPrefix "acct:" -> Just s) = Left $ Acct $ T.unpack s
 parseQueryTerm d (T.stripPrefix "date2:" -> Just s) =
         case parsePeriodExpr d s of Left e         -> error' $ "\"date2:"++T.unpack s++"\" gave a "++showDateParseError e
@@ -294,6 +298,8 @@
     "status:!" `gives` (Left $ StatusQ Pending)
     "status:0" `gives` (Left $ StatusQ Unmarked)
     "status:" `gives` (Left $ StatusQ Unmarked)
+    "payee:x" `gives` (Left $ Tag "payee" (Just "x"))
+    "note:x" `gives` (Left $ Tag "note" (Just "x"))
     "real:1" `gives` (Left $ Real True)
     "date:2008" `gives` (Left $ Date $ DateSpan (Just $ parsedate "2008/01/01") (Just $ parsedate "2009/01/01"))
     "date:from 2012/5/17" `gives` (Left $ Date $ DateSpan (Just $ parsedate "2012/05/17") Nothing)
@@ -684,8 +690,10 @@
 -- matchesPosting (Empty True) Posting{pamount=a} = isZeroMixedAmount a
 matchesPosting (Empty _) _ = True
 matchesPosting (Sym r) Posting{pamount=Mixed as} = any (regexMatchesCI $ "^" ++ r ++ "$") $ map (T.unpack . acommodity) as
-matchesPosting (Tag n v) p = not $ null $ matchedTags n v $ postingAllTags p
--- matchesPosting _ _ = False
+matchesPosting (Tag n v) p = case (n, v) of
+  ("payee", Just v) -> maybe False (regexMatchesCI v . T.unpack . transactionPayee) $ ptransaction p
+  ("note", Just v) -> maybe False (regexMatchesCI v . T.unpack . transactionNote) $ ptransaction p
+  (n, v) -> matchesTags n v $ postingAllTags p
 
 tests_matchesPosting = [
    "matchesPosting" ~: do
@@ -737,9 +745,10 @@
 matchesTransaction (Empty _) _ = True
 matchesTransaction (Depth d) t = any (Depth d `matchesPosting`) $ tpostings t
 matchesTransaction q@(Sym _) t = any (q `matchesPosting`) $ tpostings t
-matchesTransaction (Tag n v) t = not $ null $ matchedTags n v $ transactionAllTags t
-
--- matchesTransaction _ _ = False
+matchesTransaction (Tag n v) t = case (n, v) of
+  ("payee", Just v) -> regexMatchesCI v . T.unpack . transactionPayee $ t
+  ("note", Just v) -> regexMatchesCI v . T.unpack . transactionNote $ t
+  (n, v) -> matchesTags n v $ transactionAllTags t
 
 tests_matchesTransaction = [
   "matchesTransaction" ~: do
@@ -749,14 +758,16 @@
    assertBool "" $ (Desc "x x") `matchesTransaction` nulltransaction{tdescription="x x"}
    -- see posting for more tag tests
    assertBool "" $ (Tag "foo" (Just "a")) `matchesTransaction` nulltransaction{ttags=[("foo","bar")]}
+   assertBool "" $ (Tag "payee" (Just "payee")) `matchesTransaction` nulltransaction{tdescription="payee|note"}
+   assertBool "" $ (Tag "note" (Just "note")) `matchesTransaction` nulltransaction{tdescription="payee|note"}
    -- a tag match on a transaction also matches posting tags
    assertBool "" $ (Tag "postingtag" Nothing) `matchesTransaction` nulltransaction{tpostings=[nullposting{ptags=[("postingtag","")]}]}
  ]
 
 -- | Filter a list of tags by matching against their names and
 -- optionally also their values.
-matchedTags :: Regexp -> Maybe Regexp -> [Tag] -> [Tag]
-matchedTags namepat valuepat tags = filter (match namepat valuepat) tags
+matchesTags :: Regexp -> Maybe Regexp -> [Tag] -> Bool
+matchesTags namepat valuepat = not . null . filter (match namepat valuepat)
   where
     match npat Nothing     (n,_) = regexMatchesCI npat (T.unpack n) -- XXX
     match npat (Just vpat) (n,v) = regexMatchesCI npat (T.unpack n) && regexMatchesCI vpat (T.unpack v)
diff --git a/Hledger/Read.hs b/Hledger/Read.hs
--- a/Hledger/Read.hs
+++ b/Hledger/Read.hs
@@ -14,6 +14,7 @@
   PrefixedFilePath,
   defaultJournal,
   defaultJournalPath,
+  readJournalFilesWithOpts,
   readJournalFiles,
   readJournalFile,
   requireJournalFileExists,
@@ -41,18 +42,20 @@
 import Control.Monad.Except
 import Data.List
 import Data.Maybe
+import Data.Ord
 import Data.Text (Text)
 import qualified Data.Text as T
+import Data.Time (Day)
 import Safe
 import System.Directory (doesFileExist, getHomeDirectory)
 import System.Environment (getEnv)
 import System.Exit (exitFailure)
-import System.FilePath ((</>), takeExtension)
-import System.IO (stderr)
+import System.FilePath
+import System.IO
 import Test.HUnit
 import Text.Printf
 
-import Hledger.Data.Dates (getCurrentDay)
+import Hledger.Data.Dates (getCurrentDay, parsedate, showDate)
 import Hledger.Data.Types
 import Hledger.Read.Common
 import qualified Hledger.Read.JournalReader   as JournalReader
@@ -62,7 +65,6 @@
 import qualified Hledger.Read.CsvReader       as CsvReader
 import Hledger.Utils
 import Prelude hiding (getContents, writeFile)
-import Hledger.Utils.UTF8IOCompat (writeFile)
 
 
 journalEnvVar           = "LEDGER_FILE"
@@ -257,6 +259,111 @@
     firstSuccessOrFirstError (e:_) []    = return $ Left e              -- none left, return first error
     path' = fromMaybe "(string)" path
 
+
+--- New versions of readJournal* with easier arguments, and support for --new.
+
+readJournalFilesWithOpts :: InputOpts -> [FilePath] -> IO (Either String Journal)
+readJournalFilesWithOpts iopts =
+  (right mconcat1 . sequence <$>) . mapM (readJournalFileWithOpts iopts)
+  where
+    mconcat1 :: Monoid t => [t] -> t
+    mconcat1 [] = mempty
+    mconcat1 x  = foldr1 mappend x
+
+readJournalFileWithOpts :: InputOpts -> PrefixedFilePath -> IO (Either String Journal)
+readJournalFileWithOpts iopts prefixedfile = do
+  let 
+    (mfmt, f) = splitReaderPrefix prefixedfile
+    iopts' = iopts{mformat_=firstJust [mfmt, mformat_ iopts]}
+  requireJournalFileExists f
+  t <- readFileOrStdinAnyLineEnding f
+  ej <- readJournalWithOpts iopts' (Just f) t
+  case ej of
+    Left e  -> return $ Left e
+    Right j | new_ iopts -> do
+      ds <- previousLatestDates f
+      let (newj, newds) = journalFilterSinceLatestDates ds j
+      when (new_save_ iopts && not (null newds)) $ saveLatestDates newds f
+      return $ Right newj
+    Right j -> return $ Right j
+
+-- A "LatestDates" is zero or more copies of the same date,
+-- representing the latest transaction date read from a file,
+-- and how many transactions there were on that date.
+type LatestDates = [Day]
+
+-- | Get all instances of the latest date in an unsorted list of dates.
+-- Ie, if the latest date appears once, return it in a one-element list,
+-- if it appears three times (anywhere), return three of it.
+latestDates :: [Day] -> LatestDates
+latestDates = headDef [] . take 1 . group . reverse . sort
+
+-- | Remember that these transaction dates were the latest seen when
+-- reading this journal file.
+saveLatestDates :: LatestDates -> FilePath -> IO () 
+saveLatestDates dates f = writeFile (latestDatesFileFor f) $ unlines $ map showDate dates
+
+-- | What were the latest transaction dates seen the last time this 
+-- journal file was read ? If there were multiple transactions on the
+-- latest date, that number of dates is returned, otherwise just one.
+-- Or none if no transactions were read, or if latest dates info is not 
+-- available for this file.
+previousLatestDates :: FilePath -> IO LatestDates
+previousLatestDates f = do
+  let latestfile = latestDatesFileFor f
+  exists <- doesFileExist latestfile
+  if exists
+  then map (parsedate . strip) . lines . strip . T.unpack <$> readFileStrictly latestfile
+  else return []
+
+-- | Where to save latest transaction dates for the given file path.
+-- (.latest.FILE)
+latestDatesFileFor :: FilePath -> FilePath
+latestDatesFileFor f = dir </> ".latest" <.> fname
+  where
+    (dir, fname) = splitFileName f
+
+readFileStrictly :: FilePath -> IO Text
+readFileStrictly f = readFile' f >>= \t -> C.evaluate (T.length t) >> return t
+
+-- | Given zero or more latest dates (all the same, representing the
+-- latest previously seen transaction date, and how many transactions
+-- were seen on that date), remove transactions with earlier dates
+-- from the journal, and the same number of transactions on the
+-- latest date, if any, leaving only transactions that we can assume
+-- are newer. Also returns the new latest dates of the new journal.
+journalFilterSinceLatestDates :: LatestDates -> Journal -> (Journal, LatestDates)
+journalFilterSinceLatestDates [] j       = (j,  latestDates $ map tdate $ jtxns j)
+journalFilterSinceLatestDates ds@(d:_) j = (j', ds')
+  where
+    samedateorlaterts     = filter ((>= d).tdate) $ jtxns j
+    (samedatets, laterts) = span ((== d).tdate) $ sortBy (comparing tdate) samedateorlaterts
+    newsamedatets         = drop (length ds) samedatets
+    j'                    = j{jtxns=newsamedatets++laterts}
+    ds'                   = latestDates $ map tdate $ samedatets++laterts
+
+readJournalWithOpts :: InputOpts -> Maybe FilePath -> Text -> IO (Either String Journal)
+readJournalWithOpts iopts mfile txt =
+  tryReadersWithOpts iopts mfile specifiedorallreaders txt
+  where
+    specifiedorallreaders = maybe stablereaders (:[]) $ findReader (mformat_ iopts) mfile
+    stablereaders = filter (not.rExperimental) readers
+
+tryReadersWithOpts :: InputOpts -> Maybe FilePath -> [Reader] -> Text -> IO (Either String Journal)
+tryReadersWithOpts iopts mpath readers txt = firstSuccessOrFirstError [] readers
+  where
+    firstSuccessOrFirstError :: [String] -> [Reader] -> IO (Either String Journal)
+    firstSuccessOrFirstError [] []        = return $ Left "no readers found"
+    firstSuccessOrFirstError errs (r:rs) = do
+      dbg1IO "trying reader" (rFormat r)
+      result <- (runExceptT . (rParser r) (mrules_file_ iopts) (not $ ignore_assertions_ iopts) path) txt
+      dbg1IO "reader result" $ either id show result
+      case result of Right j -> return $ Right j                        -- success!
+                     Left e  -> firstSuccessOrFirstError (errs++[e]) rs -- keep trying
+    firstSuccessOrFirstError (e:_) []    = return $ Left e              -- none left, return first error
+    path = fromMaybe "(string)" mpath
+
+---
 
 
 -- tests
diff --git a/Hledger/Read/Common.hs b/Hledger/Read/Common.hs
--- a/Hledger/Read/Common.hs
+++ b/Hledger/Read/Common.hs
@@ -13,7 +13,7 @@
 -}
 
 --- * module
-{-# LANGUAGE CPP, RecordWildCards, NamedFieldPuns, NoMonoLocalBinds, ScopedTypeVariables, FlexibleContexts, TupleSections, OverloadedStrings #-}
+{-# LANGUAGE CPP, DeriveDataTypeable, RecordWildCards, NamedFieldPuns, NoMonoLocalBinds, ScopedTypeVariables, FlexibleContexts, TupleSections, OverloadedStrings #-}
 
 module Hledger.Read.Common
 where
@@ -24,6 +24,8 @@
 import Control.Monad.Except (ExceptT(..), runExceptT, throwError) --, catchError)
 import Control.Monad.State.Strict
 import Data.Char (isNumber)
+import Data.Data
+import Data.Default
 import Data.Functor.Identity
 import Data.List.Compat
 import Data.List.NonEmpty (NonEmpty(..))
@@ -42,6 +44,39 @@
 import Hledger.Utils
 
 -- $setup
+
+-- | Various options to use when reading journal files.
+-- Similar to CliOptions.inputflags, simplifies the journal-reading functions.
+data InputOpts = InputOpts {
+     -- files_             :: [FilePath]
+     mformat_           :: Maybe StorageFormat  -- ^ a file/storage format to try, unless overridden
+                                                --   by a filename prefix. Nothing means try all.
+    ,mrules_file_       :: Maybe FilePath       -- ^ a conversion rules file to use (when reading CSV)
+    ,aliases_           :: [String]             -- ^ account name aliases to apply
+    ,anon_              :: Bool                 -- ^ do light anonymisation/obfuscation of the data 
+    ,ignore_assertions_ :: Bool                 -- ^ don't check balance assertions
+    ,new_               :: Bool                 -- ^ read only new transactions since this file was last read
+    ,new_save_          :: Bool                 -- ^ save latest new transactions state for next time
+    ,pivot_             :: String               -- ^ use the given field's value as the account name 
+ } deriving (Show, Data) --, Typeable)
+
+instance Default InputOpts where def = definputopts
+
+definputopts :: InputOpts
+definputopts = InputOpts def def def def def def True def
+
+rawOptsToInputOpts :: RawOpts -> InputOpts
+rawOptsToInputOpts rawopts = InputOpts{
+   -- files_             = map (T.unpack . stripquotes . T.pack) $ listofstringopt "file" rawopts
+   mformat_           = Nothing
+  ,mrules_file_       = maybestringopt "rules-file" rawopts
+  ,aliases_           = map (T.unpack . stripquotes . T.pack) $ listofstringopt "alias" rawopts
+  ,anon_              = boolopt "anon" rawopts
+  ,ignore_assertions_ = boolopt "ignore-assertions" rawopts
+  ,new_               = boolopt "new" rawopts
+  ,new_save_          = True
+  ,pivot_             = stringopt "pivot" rawopts
+  }
 
 --- * parsing utils
 
diff --git a/Hledger/Read/CsvReader.hs b/Hledger/Read/CsvReader.hs
--- a/Hledger/Read/CsvReader.hs
+++ b/Hledger/Read/CsvReader.hs
@@ -1,9 +1,9 @@
-{-# LANGUAGE CPP #-}
 {-|
 
 A reader for CSV data, using an extra rules file to help interpret the data.
 
 -}
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeFamilies #-}
@@ -40,6 +40,7 @@
 import Data.Ord
 import Data.Text (Text)
 import qualified Data.Text as T
+import qualified Data.Text.IO as T
 import Data.Time.Calendar (Day)
 #if MIN_VERSION_time(1,5,0)
 import Data.Time.Format (parseTimeM, defaultTimeLocale)
@@ -50,12 +51,11 @@
 import Safe
 import System.Directory (doesFileExist)
 import System.FilePath
-import System.IO (stderr)
 import Test.HUnit hiding (State)
 import Text.CSV (parseCSV, CSV)
 import Text.Megaparsec.Compat hiding (parse)
 import qualified Text.Parsec as Parsec
-import Text.Printf (hPrintf,printf)
+import Text.Printf (printf)
 
 import Hledger.Data
 import Hledger.Utils.UTF8IOCompat (getContents)
@@ -103,7 +103,7 @@
   rulestext <-
     if rulesfileexists
     then do
-      hPrintf stderr "using conversion rules file %s\n" rulesfile
+      dbg1IO "using conversion rules file" rulesfile
       liftIO $ (readFile' rulesfile >>= expandIncludes (takeDirectory rulesfile))
     else return $ defaultRulesText rulesfile
   rules <- liftIO (runExceptT $ parseAndValidateCsvRules rulesfile rulestext) >>= either throwerr return 
@@ -141,17 +141,27 @@
                    )
                    (initialPos parsecfilename) records
 
-  -- heuristic: if the records appear to have been in reverse date order,
-  -- reverse them all as well as doing a txn date sort,
-  -- so that same-day txns' original order is preserved
-    txns' | length txns > 1 && tdate (head txns) > tdate (last txns) = reverse txns
-          | otherwise = txns
+    -- Ensure transactions are ordered chronologically.
+    -- First, reverse them to get same-date transactions ordered chronologically,
+    -- if the CSV records seem to be most-recent-first, ie if there's an explicit 
+    -- "newest-first" directive, or if there's more than one date and the first date
+    -- is more recent than the last.
+    txns' = 
+      (if newestfirst || mseemsnewestfirst == Just True then reverse else id) txns
+      where
+        newestfirst = dbg3 "newestfirst" $ isJust $ getDirective "newest-first" rules
+        mseemsnewestfirst = dbg3 "mseemsnewestfirst" $  
+          case nub $ map tdate txns of 
+            ds | length ds > 1 -> Just $ head ds > last ds 
+            _                  -> Nothing
+    -- Second, sort by date.
+    txns'' = sortBy (comparing tdate) txns'
 
   when (not rulesfileexists) $ do
-    hPrintf stderr "created default conversion rules file %s, edit this for better results\n" rulesfile
+    dbg1IO "creating conversion rules file" rulesfile
     writeFile rulesfile $ T.unpack rulestext
 
-  return $ Right nulljournal{jtxns=sortBy (comparing tdate) txns'}
+  return $ Right nulljournal{jtxns=txns''}
 
 parseCsv :: FilePath -> String -> IO (Either Parsec.ParseError CSV)
 parseCsv path csvdata =
@@ -210,6 +220,7 @@
   ,"fields date, description, amount"
   ,""
   ,"#skip 1"
+  ,"#newest-first"
   ,""
   ,"#date-format %-d/%-m/%Y"
   ,"#date-format %-m/%-d/%Y"
@@ -232,7 +243,7 @@
 
 RULES: RULE*
 
-RULE: ( FIELD-LIST | FIELD-ASSIGNMENT | CONDITIONAL-BLOCK | SKIP | DATE-FORMAT | COMMENT | BLANK ) NEWLINE
+RULE: ( FIELD-LIST | FIELD-ASSIGNMENT | CONDITIONAL-BLOCK | SKIP | NEWEST-FIRST | DATE-FORMAT | COMMENT | BLANK ) NEWLINE
 
 FIELD-LIST: fields SPACE FIELD-NAME ( SPACE? , SPACE? FIELD-NAME )*
 
@@ -356,22 +367,18 @@
 parseRulesFile f = 
   liftIO (readFile' f >>= expandIncludes (takeDirectory f)) >>= parseAndValidateCsvRules f
 
--- | Look for hledger rules file-style include directives in this text,
--- and interpolate the included files, recursively.
--- Included file paths may be relative to the directory of the
--- provided file path.
+-- | Inline all files referenced by include directives in this hledger CSV rules text, recursively.
+-- Included file paths may be relative to the directory of the provided file path.
 -- This is a cheap hack to avoid rewriting the CSV rules parser.
-expandIncludes :: FilePath -> T.Text -> IO T.Text
-expandIncludes basedir content = do
-  let (ls,rest) = break (T.isPrefixOf "include") $ T.lines content
-  case rest of
-    [] -> return $ T.unlines ls
-    ((T.stripPrefix "include" -> Just f):ls') -> do
-      let f'       = basedir </> dropWhile isSpace (T.unpack f)
-          basedir' = takeDirectory f'
-      included <- readFile' f' >>= expandIncludes basedir'
-      return $ T.unlines [T.unlines ls, included, T.unlines ls']
-    ls' -> return $ T.unlines $ ls ++ ls'   -- should never get here
+expandIncludes dir content = mapM (expandLine dir) (T.lines content) >>= return . T.unlines
+  where
+    expandLine dir line =
+      case line of
+        (T.stripPrefix "include " -> Just f) -> expandIncludes dir' =<< T.readFile f'
+          where
+            f' = dir </> dropWhile isSpace (T.unpack f)
+            dir' = takeDirectory f'
+        _ -> return line 
 
 -- | An error-throwing action that parses this text as CSV conversion rules 
 -- and runs some extra validation checks. The file path is for error messages.
@@ -451,6 +458,7 @@
   -- ,"default-currency"
   -- ,"skip-lines" -- old
   ,"skip"
+  ,"newest-first"
    -- ,"base-account"
    -- ,"base-currency"
   ]
@@ -654,10 +662,10 @@
        ++"change your amount or currency rules, "
        ++"or "++maybe "add a" (const "change your") mskip++" skip rule"
       ]
-    -- Using costOfMixedAmount here to allow complex costs like "10 GBP @@ 15 USD".
-    -- Aim is to have "10 GBP @@ 15 USD" applied to account2, but have "-15USD" applied to account1
-    amount1        = costOfMixedAmount amount
-    amount2        = (-amount)
+    amount1        = amount
+    -- convert balancing amount to cost like hledger print, so eg if 
+    -- amount1 is "10 GBP @@ 15 USD", amount2 will be "-15 USD".
+    amount2        = costOfMixedAmount (-amount)
     s `or` def  = if null s then def else s
     defaccount1 = fromMaybe "unknown" $ mdirective "default-account1"
     defaccount2 = case isNegativeMixedAmount amount2 of
@@ -689,8 +697,8 @@
       tcomment                 = T.pack comment,
       tpreceding_comment_lines = T.pack precomment,
       tpostings                =
-        [posting {paccount=account2, pamount=amount2, ptransaction=Just t}
-        ,posting {paccount=account1, pamount=amount1, ptransaction=Just t, pbalanceassertion=balance}
+        [posting {paccount=account1, pamount=amount1, ptransaction=Just t, pbalanceassertion=balance}
+        ,posting {paccount=account2, pamount=amount2, ptransaction=Just t}
         ]
       }
 
diff --git a/Hledger/Read/JournalReader.hs b/Hledger/Read/JournalReader.hs
--- a/Hledger/Read/JournalReader.hs
+++ b/Hledger/Read/JournalReader.hs
@@ -321,7 +321,7 @@
   old <- rstrip <$> (some $ noneOf ("=" :: [Char]))
   char '='
   many spacenonewline
-  new <- rstrip <$> anyChar `manyTill` eolof  -- don't require a final newline, good for cli options
+  new <- rstrip <$> anyChar `manyTill` eolof  -- eol in journal, eof in command lines, normally
   return $ BasicAlias (T.pack old) (T.pack new)
 
 regexaliasp :: TextParser m AccountAlias
@@ -333,7 +333,7 @@
   many spacenonewline
   char '='
   many spacenonewline
-  repl <- rstrip <$> anyChar `manyTill` eolof
+  repl <- anyChar `manyTill` eolof
   return $ RegexAlias re repl
 
 endaliasesdirectivep :: JournalParser m ()
diff --git a/Hledger/Read/TimedotReader.hs b/Hledger/Read/TimedotReader.hs
--- a/Hledger/Read/TimedotReader.hs
+++ b/Hledger/Read/TimedotReader.hs
@@ -5,7 +5,9 @@
 
 @
 #DATE
-#ACCT DOTS  # Each dot represents 15m, spaces are ignored
+#ACCT  DOTS  # Each dot represents 15m, spaces are ignored
+#ACCT  8    # numbers with or without a following h represent hours
+#ACCT  5m   # numbers followed by m represent minutes
 
 # on 2/1, 1h was spent on FOSS haskell work, 0.25h on research, etc.
 2/1
@@ -126,19 +128,41 @@
   return t
 
 timedotdurationp :: JournalParser m Quantity
-timedotdurationp = try timedotnumberp <|> timedotdotsp
+timedotdurationp = try timedotnumericp <|> timedotdotsp
 
--- | Parse a duration written as a decimal number of hours (optionally followed by the letter h).
+-- | Parse a duration of seconds, minutes, hours, days, weeks, months or years,
+-- written as a decimal number followed by s, m, h, d, w, mo or y, assuming h
+-- if there is no unit. Returns the duration as hours, assuming
+-- 1m = 60s, 1h = 60m, 1d = 24h, 1w = 7d, 1mo = 30d, 1y=365d.
 -- @
+-- 1.5
 -- 1.5h
+-- 90m
 -- @
-timedotnumberp :: JournalParser m Quantity
-timedotnumberp = do
-   (q, _, _, _) <- lift numberp
-   lift (many spacenonewline)
-   optional $ char 'h'
-   lift (many spacenonewline)
-   return q
+timedotnumericp :: JournalParser m Quantity
+timedotnumericp = do
+  (q, _, _, _) <- lift numberp
+  msymbol <- optional $ choice $ map (string . fst) timeUnits
+  lift (many spacenonewline)
+  let q' = 
+        case msymbol of
+          Nothing  -> q
+          Just sym ->
+            case lookup sym timeUnits of
+              Just mult -> q * mult  
+              Nothing   -> q  -- shouldn't happen.. ignore
+  return q'
+
+-- (symbol, equivalent in hours). 
+timeUnits =
+  [("s",2.777777777777778e-4)
+  ,("mo",5040) -- before "m"
+  ,("m",1.6666666666666666e-2)
+  ,("h",1)
+  ,("d",24)
+  ,("w",168)
+  ,("y",61320)
+  ]
 
 -- | Parse a quantity written as a line of dots, each representing 0.25.
 -- @
diff --git a/Hledger/Reports.hs b/Hledger/Reports.hs
--- a/Hledger/Reports.hs
+++ b/Hledger/Reports.hs
@@ -39,5 +39,6 @@
  tests_Hledger_Reports_ReportOptions,
  tests_Hledger_Reports_EntriesReport,
  tests_Hledger_Reports_PostingsReport,
- tests_Hledger_Reports_BalanceReport
+ tests_Hledger_Reports_BalanceReport,
+ tests_Hledger_Reports_MultiBalanceReport
  ]
diff --git a/Hledger/Reports/BalanceReport.hs b/Hledger/Reports/BalanceReport.hs
--- a/Hledger/Reports/BalanceReport.hs
+++ b/Hledger/Reports/BalanceReport.hs
@@ -92,6 +92,7 @@
                          dbg1 "accts" $
                          take 1 $ clipAccountsAndAggregate (queryDepth q) $ flattenAccounts accts
           | flat_ opts = dbg1 "accts" $
+                         maybesortflat $
                          filterzeros $
                          filterempty $
                          drop 1 $ clipAccountsAndAggregate (queryDepth q) $ flattenAccounts accts
@@ -100,6 +101,7 @@
                          drop 1 $ flattenAccounts $
                          markboring $
                          prunezeros $
+                         maybesorttree $
                          clipAccounts (queryDepth q) accts
           where
             balance     = if flat_ opts then aebalance else aibalance
@@ -107,6 +109,12 @@
             filterempty = filter (\a -> anumpostings a > 0 || not (isZeroMixedAmount (balance a)))
             prunezeros  = if empty_ opts then id else fromMaybe nullacct . pruneAccounts (isZeroMixedAmount . balance)
             markboring  = if no_elide_ opts then id else markBoringParentAccounts
+            maybesortflat | sort_amount_ opts = sortBy (maybeflip $ comparing balance)
+                          | otherwise = id
+              where
+                maybeflip = if normalbalance_ opts == Just NormalNegative then id else flip
+            maybesorttree | sort_amount_ opts = sortAccountTreeByAmount (fromMaybe NormalPositive $ normalbalance_ opts)
+                          | otherwise = id
       items = dbg1 "items" $ map (balanceReportItem opts q) accts'
       total | not (flat_ opts) = dbg1 "total" $ sum [amt | (_,_,indent,amt) <- items, indent == 0]
             | otherwise        = dbg1 "total" $
@@ -213,8 +221,10 @@
   ,"balanceReport with no args on sample journal" ~: do
    (defreportopts, samplejournal) `gives`
     ([
-      ("assets","assets",0, mamountp' "$-1.00")
-     ,("assets:bank:saving","bank:saving",1, mamountp' "$1.00")
+      ("assets","assets",0, mamountp' "$0.00")
+     ,("assets:bank","bank",1, mamountp' "$2.00")
+     ,("assets:bank:checking","checking",2, mamountp' "$1.00")
+     ,("assets:bank:saving","saving",2, mamountp' "$1.00")
      ,("assets:cash","cash",1, mamountp' "$-2.00")
      ,("expenses","expenses",0, mamountp' "$2.00")
      ,("expenses:food","food",1, mamountp' "$1.00")
@@ -222,27 +232,22 @@
      ,("income","income",0, mamountp' "$-2.00")
      ,("income:gifts","gifts",1, mamountp' "$-1.00")
      ,("income:salary","salary",1, mamountp' "$-1.00")
-     ,("liabilities:debts","liabilities:debts",0, mamountp' "$1.00")
      ],
      Mixed [usd0])
 
   ,"balanceReport with --depth=N" ~: do
    (defreportopts{depth_=Just 1}, samplejournal) `gives`
     ([
-      ("assets",      "assets",      0, mamountp' "$-1.00")
-     ,("expenses",    "expenses",    0, mamountp'  "$2.00")
+     ("expenses",    "expenses",    0, mamountp'  "$2.00")
      ,("income",      "income",      0, mamountp' "$-2.00")
-     ,("liabilities", "liabilities", 0, mamountp'  "$1.00")
      ],
      Mixed [usd0])
 
   ,"balanceReport with depth:N" ~: do
    (defreportopts{query_="depth:1"}, samplejournal) `gives`
     ([
-      ("assets",      "assets",      0, mamountp' "$-1.00")
-     ,("expenses",    "expenses",    0, mamountp'  "$2.00")
+     ("expenses",    "expenses",    0, mamountp'  "$2.00")
      ,("income",      "income",      0, mamountp' "$-2.00")
-     ,("liabilities", "liabilities", 0, mamountp'  "$1.00")
      ],
      Mixed [usd0])
 
@@ -268,18 +273,29 @@
   ,"balanceReport with not:desc:" ~: do
    (defreportopts{query_="not:desc:income"}, samplejournal) `gives`
     ([
-      ("assets","assets",0, mamountp' "$-2.00")
-     ,("assets:bank","bank",1, Mixed [usd0])
-     ,("assets:bank:checking","checking",2,mamountp' "$-1.00")
-     ,("assets:bank:saving","saving",2, mamountp' "$1.00")
+      ("assets","assets",0, mamountp' "$-1.00")
+     ,("assets:bank:saving","bank:saving",1, mamountp' "$1.00")
      ,("assets:cash","cash",1, mamountp' "$-2.00")
      ,("expenses","expenses",0, mamountp' "$2.00")
      ,("expenses:food","food",1, mamountp' "$1.00")
      ,("expenses:supplies","supplies",1, mamountp' "$1.00")
      ,("income:gifts","income:gifts",0, mamountp' "$-1.00")
-     ,("liabilities:debts","liabilities:debts",0, mamountp' "$1.00")
      ],
      Mixed [usd0])
+
+  ,"balanceReport with period on a populated period" ~: do
+    (defreportopts{period_= PeriodBetween (fromGregorian 2008 1 1) (fromGregorian 2008 1 2)}, samplejournal) `gives`
+     (
+      [
+       ("assets:bank:checking","assets:bank:checking",0, mamountp' "$1.00")
+      ,("income:salary","income:salary",0, mamountp' "$-1.00")
+      ],
+      Mixed [usd0])
+
+   ,"balanceReport with period on an unpopulated period" ~: do
+    (defreportopts{period_= PeriodBetween (fromGregorian 2008 1 2) (fromGregorian 2008 1 3)}, samplejournal) `gives`
+     ([],Mixed [nullamt])
+
 
 
 {-
diff --git a/Hledger/Reports/MultiBalanceReports.hs b/Hledger/Reports/MultiBalanceReports.hs
--- a/Hledger/Reports/MultiBalanceReports.hs
+++ b/Hledger/Reports/MultiBalanceReports.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE FlexibleInstances, ScopedTypeVariables #-}
+{-# LANGUAGE FlexibleInstances, ScopedTypeVariables, OverloadedStrings #-}
 {-|
 
 Multi-column balance reports, used by the balance command.
@@ -10,10 +10,10 @@
   MultiBalanceReportRow,
   multiBalanceReport,
   multiBalanceReportValue,
-  singleBalanceReport
+  singleBalanceReport,
 
   -- -- * Tests
-  -- tests_Hledger_Reports_MultiBalanceReport
+  tests_Hledger_Reports_MultiBalanceReport
 )
 where
 
@@ -22,11 +22,12 @@
 import Data.Ord
 import Data.Time.Calendar
 import Safe
--- import Test.HUnit
+import Test.HUnit
 
 import Hledger.Data
 import Hledger.Query
 import Hledger.Utils
+import Hledger.Read (mamountp')
 import Hledger.Reports.ReportOptions
 import Hledger.Reports.BalanceReport
 
@@ -35,7 +36,7 @@
 --
 -- 1. a list of each column's period (date span)
 --
--- 2. a list of row items, each containing:
+-- 2. a list of rows, each containing:
 --
 --   * the full account name
 --
@@ -43,7 +44,7 @@
 --
 --   * the account's depth
 --
---   * the amounts to show in each column
+--   * a list of amounts, one for each column
 --
 --   * the total of the row's amounts
 --
@@ -53,14 +54,14 @@
 --
 -- The meaning of the amounts depends on the type of multi balance
 -- report, of which there are three: periodic, cumulative and historical
--- (see 'BalanceType' and "Hledger.Cli.Balance").
+-- (see 'BalanceType' and "Hledger.Cli.Commands.Balance").
 newtype MultiBalanceReport =
   MultiBalanceReport ([DateSpan]
                      ,[MultiBalanceReportRow]
                      ,MultiBalanceReportTotals
                      )
 type MultiBalanceReportRow    = (AccountName, AccountName, Int, [MixedAmount], MixedAmount, MixedAmount)
-type MultiBalanceReportTotals = ([MixedAmount], MixedAmount, MixedAmount)
+type MultiBalanceReportTotals = ([MixedAmount], MixedAmount, MixedAmount) -- (Totals list, sum of totals, average of totals)
 
 instance Show MultiBalanceReport where
     -- use ppShow to break long lists onto multiple lines
@@ -90,7 +91,7 @@
 -- showing the change of balance, accumulated balance, or historical balance
 -- in each of the specified periods.
 multiBalanceReport :: ReportOpts -> Query -> Journal -> MultiBalanceReport
-multiBalanceReport opts q j = MultiBalanceReport (displayspans, items, totalsrow)
+multiBalanceReport opts q j = MultiBalanceReport (displayspans, sorteditems, totalsrow)
     where
       symq       = dbg1 "symq"   $ filterQuery queryIsSym $ dbg1 "requested q" q
       depthq     = dbg1 "depthq" $ filterQuery queryIsDepth q
@@ -168,7 +169,7 @@
           [(a, map snd abs) | abs@((a,_):_) <- transpose acctBalChangesPerSpan] -- never null, or used when null...
 
       items :: [MultiBalanceReportRow] =
-          dbg1 "items"
+          dbg1 "items" $
           [(a, accountLeafName a, accountNameLevel a, displayedBals, rowtot, rowavg)
            | (a,changes) <- acctBalChanges
            , let displayedBals = case balancetype_ opts of
@@ -180,11 +181,48 @@
            , empty_ opts || depth == 0 || any (not . isZeroMixedAmount) displayedBals
            ]
 
+      sorteditems :: [MultiBalanceReportRow] =
+        dbg1 "sorteditems" $
+        maybesort items
+        where
+          maybesort
+            | not $ sort_amount_ opts         = id
+            | accountlistmode_ opts == ALTree = sortTreeMultiBalanceReportRowsByAmount
+            | otherwise                       = sortFlatMultiBalanceReportRowsByAmount
+            where
+              -- Sort the report rows, representing a flat account list, by row total. 
+              sortFlatMultiBalanceReportRowsByAmount = sortBy (maybeflip $ comparing fifth6)
+                where
+                  maybeflip = if normalbalance_ opts == Just NormalNegative then id else flip
+
+              -- Sort the report rows, representing a tree of accounts, by row total at each level.
+              -- To do this we recreate an Account tree with the row totals as balances, 
+              -- so we can do a hierarchical sort, flatten again, and then reorder the  
+              -- report rows similarly. Yes this is pretty long winded. 
+              sortTreeMultiBalanceReportRowsByAmount rows = sortedrows
+                where
+                  anamesandrows = [(first6 r, r) | r <- rows]
+                  anames = map fst anamesandrows
+                  atotals = [(a,tot) | (a,_,_,_,tot,_) <- rows]
+                  nametree = treeFromPaths $ map expandAccountName anames
+                  accounttree = nameTreeToAccount "root" nametree
+                  accounttreewithbals = mapAccounts setibalance accounttree
+                    where
+                      -- this error should not happen, but it's ugly TODO 
+                      setibalance a = a{aibalance=fromMaybe (error "sortTreeMultiBalanceReportRowsByAmount 1") $ lookup (aname a) atotals}
+                  sortedaccounttree = sortAccountTreeByAmount (fromMaybe NormalPositive $ normalbalance_ opts) accounttreewithbals
+                  sortedaccounts = drop 1 $ flattenAccounts sortedaccounttree
+                  sortedrows = [ 
+                    -- this error should not happen, but it's ugly TODO 
+                    fromMaybe (error "sortTreeMultiBalanceReportRowsByAmount 2") $ lookup (aname a) anamesandrows
+                    | a <- sortedaccounts 
+                    ]
+
       totals :: [MixedAmount] =
           -- dbg1 "totals" $
           map sum balsbycol
           where
-            balsbycol = transpose [bs | (a,_,_,bs,_,_) <- items, not (tree_ opts) || a `elem` highestlevelaccts]
+            balsbycol = transpose [bs | (a,_,_,bs,_,_) <- sorteditems, not (tree_ opts) || a `elem` highestlevelaccts]
             highestlevelaccts     =
                 dbg1 "highestlevelaccts"
                 [a | a <- displayedAccts, not $ any (`elem` displayedAccts) $ init $ expandAccountName a]
@@ -209,3 +247,53 @@
           (map convert coltotals, convert rowtotaltotal, convert rowavgtotal))
     convert = mixedAmountValue j d
 
+tests_multiBalanceReport =
+  let
+    (opts,journal) `gives` r = do
+      let (eitems, etotal) = r
+          (MultiBalanceReport (_, aitems, atotal)) = multiBalanceReport opts (queryFromOpts nulldate opts) journal
+          showw (acct,acct',indent,lAmt,amt,amt') = (acct, acct', indent, map showMixedAmountDebug lAmt, showMixedAmountDebug amt, showMixedAmountDebug amt')
+      assertEqual "items" (map showw eitems) (map showw aitems)
+      assertEqual "total" (showMixedAmountDebug etotal) ((\(_, b, _) -> showMixedAmountDebug b) atotal) -- we only check the sum of the totals
+    usd0 = usd 0
+    amount0 = Amount {acommodity="$", aquantity=0, aprice=NoPrice, astyle=AmountStyle {ascommodityside = L, ascommodityspaced = False, asprecision = 2, asdecimalpoint = Just '.', asdigitgroups = Nothing}, amultiplier=False}
+  in [
+   "multiBalanceReport with no args on null journal" ~: do
+   (defreportopts, nulljournal) `gives` ([], Mixed [nullamt])
+
+   ,"multiBalanceReport with -H on a populated period" ~: do
+    (defreportopts{period_= PeriodBetween (fromGregorian 2008 1 1) (fromGregorian 2008 1 2), balancetype_=HistoricalBalance}, samplejournal) `gives`
+     (
+      [
+       ("assets:bank:checking","checking",3, [mamountp' "$1.00"], mamountp' "$1.00",Mixed [amount0 {aquantity=1}])
+      ,("income:salary","salary",2, [mamountp' "$-1.00"], mamountp' "$-1.00",Mixed [amount0 {aquantity=(-1)}])
+      ],
+      Mixed [usd0])
+
+   ,"multiBalanceReport tests the ability to have a valid history on an empty period" ~: do
+    (defreportopts{period_= PeriodBetween (fromGregorian 2008 1 2) (fromGregorian 2008 1 3), balancetype_=HistoricalBalance}, samplejournal) `gives`
+     (
+      [
+       ("assets:bank:checking","checking",3, [mamountp' "$1.00"], mamountp' "$1.00",Mixed [amount0 {aquantity=1}])
+      ,("income:salary","salary",2, [mamountp' "$-1.00"], mamountp' "$-1.00",Mixed [amount0 {aquantity=(-1)}])
+      ],
+      Mixed [usd0])
+
+   ,"multiBalanceReport tests the ability to have a valid history on an empty period (More complex)" ~: do
+    (defreportopts{period_= PeriodBetween (fromGregorian 2009 1 1) (fromGregorian 2009 1 2), balancetype_=HistoricalBalance}, samplejournal) `gives`
+     (
+      [
+      ("assets:bank:checking","checking",3, [mamountp' "$1.00"], mamountp' "$1.00",Mixed [amount0 {aquantity=1}])
+      ,("assets:bank:saving","saving",3, [mamountp' "$1.00"], mamountp' "$1.00",Mixed [amount0 {aquantity=1}])
+      ,("assets:cash","cash",2, [mamountp' "$-2.00"], mamountp' "$-2.00",Mixed [amount0 {aquantity=(-2)}])
+      ,("expenses:food","food",2, [mamountp' "$1.00"], mamountp' "$1.00",Mixed [amount0 {aquantity=(1)}])
+      ,("expenses:supplies","supplies",2, [mamountp' "$1.00"], mamountp' "$1.00",Mixed [amount0 {aquantity=(1)}])
+      ,("income:gifts","gifts",2, [mamountp' "$-1.00"], mamountp' "$-1.00",Mixed [amount0 {aquantity=(-1)}])
+      ,("income:salary","salary",2, [mamountp' "$-1.00"], mamountp' "$-1.00",Mixed [amount0 {aquantity=(-1)}])
+      ],
+      Mixed [usd0])
+  ]
+
+tests_Hledger_Reports_MultiBalanceReport :: Test
+tests_Hledger_Reports_MultiBalanceReport = TestList
+  tests_multiBalanceReport
diff --git a/Hledger/Reports/PostingsReport.hs b/Hledger/Reports/PostingsReport.hs
--- a/Hledger/Reports/PostingsReport.hs
+++ b/Hledger/Reports/PostingsReport.hs
@@ -263,17 +263,17 @@
    -- with the query specified explicitly
    let (query, journal) `gives` n = (length $ snd $ postingsReport defreportopts query journal) `is` n
    (Any, nulljournal) `gives` 0
-   (Any, samplejournal) `gives` 11
+   (Any, samplejournal) `gives` 13
    -- register --depth just clips account names
-   (Depth 2, samplejournal) `gives` 11
+   (Depth 2, samplejournal) `gives` 13
    (And [Depth 1, StatusQ Cleared, Acct "expenses"], samplejournal) `gives` 2
    (And [And [Depth 1, StatusQ Cleared], Acct "expenses"], samplejournal) `gives` 2
 
    -- with query and/or command-line options
-   assertEqual "" 11 (length $ snd $ postingsReport defreportopts Any samplejournal)
-   assertEqual ""  9 (length $ snd $ postingsReport defreportopts{interval_=Months 1} Any samplejournal)
-   assertEqual "" 19 (length $ snd $ postingsReport defreportopts{interval_=Months 1, empty_=True} Any samplejournal)
-   assertEqual ""  4 (length $ snd $ postingsReport defreportopts (Acct "assets:bank:checking") samplejournal)
+   assertEqual "" 13 (length $ snd $ postingsReport defreportopts Any samplejournal)
+   assertEqual "" 11 (length $ snd $ postingsReport defreportopts{interval_=Months 1} Any samplejournal)
+   assertEqual "" 20 (length $ snd $ postingsReport defreportopts{interval_=Months 1, empty_=True} Any samplejournal)
+   assertEqual ""  5 (length $ snd $ postingsReport defreportopts (Acct "assets:bank:checking") samplejournal)
 
    -- (defreportopts, And [Acct "a a", Acct "'b"], samplejournal2) `gives` 0
    -- [(Just (parsedate "2008-01-01","income"),assets:bank:checking             $1,$1)
diff --git a/Hledger/Reports/ReportOptions.hs b/Hledger/Reports/ReportOptions.hs
--- a/Hledger/Reports/ReportOptions.hs
+++ b/Hledger/Reports/ReportOptions.hs
@@ -70,9 +70,10 @@
 
 instance Default AccountListMode where def = ALDefault
 
--- | Standard options for customising report filtering and output,
--- corresponding to hledger's command-line options and query language
--- arguments. Used in hledger-lib and above.
+-- | Standard options for customising report filtering and output.
+-- Most of these correspond to standard hledger command-line options
+-- or query arguments, but not all. Some are used only by certain
+-- commands, as noted below. 
 data ReportOpts = ReportOpts {
      period_         :: Period
     ,interval_       :: Interval
@@ -86,10 +87,10 @@
     ,real_           :: Bool
     ,format_         :: Maybe FormatStr
     ,query_          :: String -- all arguments, as a string
-    -- register only
+    -- register command only
     ,average_        :: Bool
     ,related_        :: Bool
-    -- balance only
+    -- balance-type commands only
     ,balancetype_    :: BalanceType
     ,accountlistmode_ :: AccountListMode
     ,drop_           :: Int
@@ -97,11 +98,15 @@
     ,no_total_       :: Bool
     ,value_          :: Bool
     ,pretty_tables_  :: Bool
+    ,sort_amount_    :: Bool
+    ,normalbalance_  :: Maybe NormalBalance
+      -- ^ when running a balance report on accounts of the same normal balance type,
+      -- eg in the income section of an income statement, this helps --sort-amount know
+      -- how to sort negative numbers.
     ,color_          :: Bool
  } deriving (Show, Data, Typeable)
 
 instance Default ReportOpts where def = defreportopts
-instance Default Bool where def = False
 
 defreportopts :: ReportOpts
 defreportopts = ReportOpts
@@ -127,6 +132,8 @@
     def
     def
     def
+    def
+    def
 
 rawOptsToReportOpts :: RawOpts -> IO ReportOpts
 rawOptsToReportOpts rawopts = checkReportOpts <$> do
@@ -154,6 +161,7 @@
     ,row_total_   = boolopt "row-total" rawopts'
     ,no_total_    = boolopt "no-total" rawopts'
     ,value_       = boolopt "value" rawopts'
+    ,sort_amount_ = boolopt "sort-amount" rawopts'
     ,pretty_tables_ = boolopt "pretty-tables" rawopts'
     ,color_       = color
     }
diff --git a/Hledger/Utils.hs b/Hledger/Utils.hs
--- a/Hledger/Utils.hs
+++ b/Hledger/Utils.hs
@@ -35,6 +35,7 @@
 where
 import Control.Monad (liftM)
 -- import Data.Char
+import Data.Default
 import Data.List
 -- import Data.Maybe
 -- import Data.PPrint
@@ -116,6 +117,8 @@
   return $ utcToZonedTime tz t
 
 -- misc
+
+instance Default Bool where def = False
 
 isLeft :: Either a b -> Bool
 isLeft (Left _) = True
diff --git a/Hledger/Utils/Debug.hs b/Hledger/Utils/Debug.hs
--- a/Hledger/Utils/Debug.hs
+++ b/Hledger/Utils/Debug.hs
@@ -10,9 +10,7 @@
 module Hledger.Utils.Debug (
   module Hledger.Utils.Debug
   ,module Debug.Trace
-#if __GLASGOW_HASKELL__ >= 704
   ,ppShow
-#endif
 )
 where
 
@@ -28,14 +26,7 @@
 import           System.IO.Unsafe (unsafePerformIO)
 import           Text.Megaparsec
 import           Text.Printf
-
-#if __GLASGOW_HASKELL__ >= 704
 import           Text.Show.Pretty (ppShow)
-#else
--- the required pretty-show version requires GHC >= 7.4
-ppShow :: Show a => a -> String
-ppShow = show
-#endif
 
 pprint :: Show a => a -> IO ()
 pprint = putStrLn . ppShow
diff --git a/Hledger/Utils/UTF8IOCompat.hs b/Hledger/Utils/UTF8IOCompat.hs
--- a/Hledger/Utils/UTF8IOCompat.hs
+++ b/Hledger/Utils/UTF8IOCompat.hs
@@ -44,10 +44,6 @@
 -- import qualified Data.ByteString.Lazy.UTF8 as U8 (toString, fromString)
 import Prelude hiding (readFile, writeFile, appendFile, getContents, putStr, putStrLn)
 import System.IO -- (Handle)
--- #if __GLASGOW_HASKELL__ < 702
--- import Codec.Binary.UTF8.String as UTF8 (decodeString, encodeString, isUTF8Encoded)
--- import System.Info (os)
--- #endif
 
 -- bom :: B.ByteString
 -- bom = B.pack [0xEF, 0xBB, 0xBF]
@@ -100,24 +96,12 @@
 -- | Convert a system string to an ordinary string, decoding from UTF-8 if
 -- it appears to be UTF8-encoded and GHC version is less than 7.2.
 fromSystemString :: SystemString -> String
--- #if __GLASGOW_HASKELL__ < 702
--- fromSystemString s = if UTF8.isUTF8Encoded s then UTF8.decodeString s else s
--- #else
 fromSystemString = id
--- #endif
 
 -- | Convert a unicode string to a system string, encoding with UTF-8 if
 -- we are on a posix platform with GHC < 7.2.
 toSystemString :: String -> SystemString
--- #if __GLASGOW_HASKELL__ < 702
--- toSystemString = case os of
---                      "unix" -> UTF8.encodeString
---                      "linux" -> UTF8.encodeString
---                      "darwin" -> UTF8.encodeString
---                      _ -> id
--- #else
 toSystemString = id
--- #endif
 
 -- | A SystemString-aware version of error.
 error' :: String -> a
diff --git a/doc/hledger_csv.5 b/doc/hledger_csv.5
--- a/doc/hledger_csv.5
+++ b/doc/hledger_csv.5
@@ -1,5 +1,5 @@
 
-.TH "hledger_csv" "5" "September 2017" "hledger 1.3.2" "hledger User Manuals"
+.TH "hledger_csv" "5" "September 2017" "hledger 1.4" "hledger User Manuals"
 
 
 
@@ -23,7 +23,7 @@
 To learn about \f[I]exporting\f[] CSV, see CSV output.
 .SH CSV RULES
 .PP
-The following six kinds of rule can appear in the rules file, in any
+The following seven kinds of rule can appear in the rules file, in any
 order.
 Blank lines and lines beginning with \f[C]#\f[] or \f[C];\f[] are
 ignored.
@@ -195,32 +195,65 @@
 include\ common.rules
 \f[]
 .fi
+.SS newest\-first
+.PP
+\f[C]newest\-first\f[]
+.PP
+Consider adding this rule if all of the following are true: you might be
+processing just one day of data, your CSV records are in reverse
+chronological order (newest first), and you care about preserving the
+order of same\-day transactions.
+It usually isn\[aq]t needed, because hledger autodetects the CSV order,
+but when all CSV records have the same date it will assume they are
+oldest first.
 .SH CSV TIPS
+.SS CSV ordering
 .PP
-Each generated journal entry will have two postings, to
-\f[C]account1\f[] and \f[C]account2\f[] respectively.
-Currently it\[aq]s not possible to generate entries with more than two
+The generated journal entries will be sorted by date.
+The order of same\-day entries will be preserved (except in the special
+case where you might need \f[C]newest\-first\f[], see above).
+.SS CSV accounts
+.PP
+Each journal entry will have two postings, to \f[C]account1\f[] and
+\f[C]account2\f[] respectively.
+It\[aq]s not yet possible to generate entries with more than two
 postings.
+It\[aq]s conventional and recommended to use \f[C]account1\f[] for the
+account whose CSV we are reading.
+.SS CSV amounts
 .PP
+The \f[C]amount\f[] field sets the amount of the \f[C]account1\f[]
+posting.
+.PP
 If the CSV has debit/credit amounts in separate fields, assign to the
-\f[C]amount\-in\f[] and \f[C]amount\-out\f[] pseudo fields instead of
-\f[C]amount\f[].
+\f[C]amount\-in\f[] and \f[C]amount\-out\f[] pseudo fields instead.
+(Whichever one has a value will be used, with appropriate sign.
+If both contain a value, it may not work so well.)
 .PP
-If the CSV has the currency in a separate field, assign that to the
-\f[C]currency\f[] pseudo field which will be automatically prepended to
-the amount.
-(Or you can do the same thing with a field assignment.)
+If an amount value is parenthesised, it will be de\-parenthesised and
+sign\-flipped.
 .PP
-If the CSV includes a running balance, you can assign that to the
-\f[C]balance\f[] pseudo field to generate a balance assertion on
-\f[C]account1\f[] whenever the balance field is non\-empty.
-(Eg to double\-check your bank\[aq]s balance calculation.)
+If an amount value begins with a double minus sign, those will cancel
+out and be removed.
 .PP
-If an amount value is parenthesised, it will be de\-parenthesised and
-sign\-flipped automatically.
+If the CSV has the currency symbol in a separate field, assign that to
+the \f[C]currency\f[] pseudo field to have it prepended to the amount.
+Or, you can use a field assignment to \f[C]amount\f[] that interpolates
+both CSV fields (giving more control, eg to put the currency symbol on
+the right).
+.SS CSV balance assertions
 .PP
-The generated journal entries will be sorted by date.
-The original order of same\-day entries will be preserved, usually.
+If the CSV includes a running balance, you can assign that to the
+\f[C]balance\f[] pseudo field; whenever the running balance value is
+non\-empty, it will be asserted as the balance after the
+\f[C]account1\f[] posting.
+.SS Reading multiple CSV files
+.PP
+You can read multiple CSV files at once using multiple \f[C]\-f\f[]
+arguments on the command line, and hledger will look for a
+correspondingly\-named rules file for each.
+Note if you use the \f[C]\-\-rules\-file\f[] option, this one rules file
+will be used for all the CSV files being read.
 
 
 .SH "REPORTING BUGS"
diff --git a/doc/hledger_csv.5.info b/doc/hledger_csv.5.info
--- a/doc/hledger_csv.5.info
+++ b/doc/hledger_csv.5.info
@@ -3,8 +3,8 @@
 
 File: hledger_csv.5.info,  Node: Top,  Next: CSV RULES,  Up: (dir)
 
-hledger_csv(5) hledger 1.3.2
-****************************
+hledger_csv(5) hledger 1.4
+**************************
 
 hledger can read CSV files, converting each CSV record into a journal
 entry (transaction), if you provide some conversion hints in a "rules
@@ -27,7 +27,7 @@
 1 CSV RULES
 ***********
 
-The following six kinds of rule can appear in the rules file, in any
+The following seven kinds of rule can appear in the rules file, in any
 order.  Blank lines and lines beginning with '#' or ';' are ignored.
 * Menu:
 
@@ -37,6 +37,7 @@
 * field assignment::
 * conditional block::
 * include::
+* newest-first::
 
 
 File: hledger_csv.5.info,  Node: skip,  Next: date-format,  Up: CSV RULES
@@ -156,7 +157,7 @@
  comment  XXX deductible ? check it
 
 
-File: hledger_csv.5.info,  Node: include,  Prev: conditional block,  Up: CSV RULES
+File: hledger_csv.5.info,  Node: include,  Next: newest-first,  Prev: conditional block,  Up: CSV RULES
 
 1.6 include
 ===========
@@ -171,51 +172,131 @@
 include common.rules
 
 
+File: hledger_csv.5.info,  Node: newest-first,  Prev: include,  Up: CSV RULES
+
+1.7 newest-first
+================
+
+'newest-first'
+
+   Consider adding this rule if all of the following are true: you might
+be processing just one day of data, your CSV records are in reverse
+chronological order (newest first), and you care about preserving the
+order of same-day transactions.  It usually isn't needed, because
+hledger autodetects the CSV order, but when all CSV records have the
+same date it will assume they are oldest first.
+
+
 File: hledger_csv.5.info,  Node: CSV TIPS,  Prev: CSV RULES,  Up: Top
 
 2 CSV TIPS
 **********
 
-Each generated journal entry will have two postings, to 'account1' and
-'account2' respectively.  Currently it's not possible to generate
-entries with more than two postings.
+* Menu:
 
-   If the CSV has debit/credit amounts in separate fields, assign to the
-'amount-in' and 'amount-out' pseudo fields instead of 'amount'.
+* CSV ordering::
+* CSV accounts::
+* CSV amounts::
+* CSV balance assertions::
+* Reading multiple CSV files::
 
-   If the CSV has the currency in a separate field, assign that to the
-'currency' pseudo field which will be automatically prepended to the
-amount.  (Or you can do the same thing with a field assignment.)
+
+File: hledger_csv.5.info,  Node: CSV ordering,  Next: CSV accounts,  Up: CSV TIPS
 
-   If the CSV includes a running balance, you can assign that to the
-'balance' pseudo field to generate a balance assertion on 'account1'
-whenever the balance field is non-empty.  (Eg to double-check your
-bank's balance calculation.)
+2.1 CSV ordering
+================
 
+The generated journal entries will be sorted by date.  The order of
+same-day entries will be preserved (except in the special case where you
+might need 'newest-first', see above).
+
+
+File: hledger_csv.5.info,  Node: CSV accounts,  Next: CSV amounts,  Prev: CSV ordering,  Up: CSV TIPS
+
+2.2 CSV accounts
+================
+
+Each journal entry will have two postings, to 'account1' and 'account2'
+respectively.  It's not yet possible to generate entries with more than
+two postings.  It's conventional and recommended to use 'account1' for
+the account whose CSV we are reading.
+
+
+File: hledger_csv.5.info,  Node: CSV amounts,  Next: CSV balance assertions,  Prev: CSV accounts,  Up: CSV TIPS
+
+2.3 CSV amounts
+===============
+
+The 'amount' field sets the amount of the 'account1' posting.
+
+   If the CSV has debit/credit amounts in separate fields, assign to the
+'amount-in' and 'amount-out' pseudo fields instead.  (Whichever one has
+a value will be used, with appropriate sign.  If both contain a value,
+it may not work so well.)
+
    If an amount value is parenthesised, it will be de-parenthesised and
-sign-flipped automatically.
+sign-flipped.
 
-   The generated journal entries will be sorted by date.  The original
-order of same-day entries will be preserved, usually.
+   If an amount value begins with a double minus sign, those will cancel
+out and be removed.
 
+   If the CSV has the currency symbol in a separate field, assign that
+to the 'currency' pseudo field to have it prepended to the amount.  Or,
+you can use a field assignment to 'amount' that interpolates both CSV
+fields (giving more control, eg to put the currency symbol on the
+right).
+
 
+File: hledger_csv.5.info,  Node: CSV balance assertions,  Next: Reading multiple CSV files,  Prev: CSV amounts,  Up: CSV TIPS
+
+2.4 CSV balance assertions
+==========================
+
+If the CSV includes a running balance, you can assign that to the
+'balance' pseudo field; whenever the running balance value is non-empty,
+it will be asserted as the balance after the 'account1' posting.
+
+
+File: hledger_csv.5.info,  Node: Reading multiple CSV files,  Prev: CSV balance assertions,  Up: CSV TIPS
+
+2.5 Reading multiple CSV files
+==============================
+
+You can read multiple CSV files at once using multiple '-f' arguments on
+the command line, and hledger will look for a correspondingly-named
+rules file for each.  Note if you use the '--rules-file' option, this
+one rules file will be used for all the CSV files being read.
+
+
 Tag Table:
 Node: Top74
-Node: CSV RULES814
-Ref: #csv-rules924
-Node: skip1167
-Ref: #skip1263
-Node: date-format1435
-Ref: #date-format1564
-Node: field list2070
-Ref: #field-list2209
-Node: field assignment2914
-Ref: #field-assignment3071
-Node: conditional block3575
-Ref: #conditional-block3731
-Node: include4627
-Ref: #include4738
-Node: CSV TIPS4969
-Ref: #csv-tips5065
+Node: CSV RULES810
+Ref: #csv-rules920
+Node: skip1182
+Ref: #skip1278
+Node: date-format1450
+Ref: #date-format1579
+Node: field list2085
+Ref: #field-list2224
+Node: field assignment2929
+Ref: #field-assignment3086
+Node: conditional block3590
+Ref: #conditional-block3746
+Node: include4642
+Ref: #include4774
+Node: newest-first5005
+Ref: #newest-first5121
+Node: CSV TIPS5532
+Ref: #csv-tips5628
+Node: CSV ordering5746
+Ref: #csv-ordering5866
+Node: CSV accounts6047
+Ref: #csv-accounts6187
+Node: CSV amounts6441
+Ref: #csv-amounts6589
+Node: CSV balance assertions7364
+Ref: #csv-balance-assertions7548
+Node: Reading multiple CSV files7753
+Ref: #reading-multiple-csv-files7925
 
 End Tag Table
diff --git a/doc/hledger_csv.5.txt b/doc/hledger_csv.5.txt
--- a/doc/hledger_csv.5.txt
+++ b/doc/hledger_csv.5.txt
@@ -19,7 +19,7 @@
        To learn about exporting CSV, see CSV output.
 
 CSV RULES
-       The following six kinds of rule can appear in the rules  file,  in  any
+       The following seven kinds of rule can appear in the rules file, in  any
        order.  Blank lines and lines beginning with # or ; are ignored.
 
    skip
@@ -123,33 +123,62 @@
               # rules reused with several CSV files
               include common.rules
 
+   newest-first
+       newest-first
+
+       Consider adding this rule if all of the following are true:  you  might
+       be  processing  just  one  day of data, your CSV records are in reverse
+       chronological order (newest first), and you care about  preserving  the
+       order  of  same-day  transactions.   It  usually  isn't needed, because
+       hledger autodetects the CSV order, but when all CSV  records  have  the
+       same date it will assume they are oldest first.
+
 CSV TIPS
-       Each generated journal entry will have two postings,  to  account1  and
-       account2 respectively.  Currently it's not possible to generate entries
-       with more than two postings.
+   CSV ordering
+       The  generated  journal  entries  will be sorted by date.  The order of
+       same-day entries will be preserved (except in the  special  case  where
+       you might need newest-first, see above).
 
+   CSV accounts
+       Each  journal  entry  will  have two postings, to account1 and account2
+       respectively.  It's not yet possible to generate entries with more than
+       two  postings.   It's  conventional and recommended to use account1 for
+       the account whose CSV we are reading.
+
+   CSV amounts
+       The amount field sets the amount of the account1 posting.
+
        If the CSV has debit/credit amounts in separate fields, assign  to  the
-       amount-in and amount-out pseudo fields instead of amount.
+       amount-in  and  amount-out pseudo fields instead.  (Whichever one has a
+       value will be used, with appropriate sign.  If both contain a value, it
+       may not work so well.)
 
-       If  the  CSV  has  the currency in a separate field, assign that to the
-       currency pseudo field which will  be  automatically  prepended  to  the
-       amount.  (Or you can do the same thing with a field assignment.)
+       If  an  amount  value is parenthesised, it will be de-parenthesised and
+       sign-flipped.
 
-       If  the CSV includes a running balance, you can assign that to the bal-
-       ance pseudo field to generate a balance assertion on account1  whenever
-       the  balance  field is non-empty.  (Eg to double-check your bank's bal-
-       ance calculation.)
+       If an amount value begins with a double minus sign, those  will  cancel
+       out and be removed.
 
-       If an amount value is parenthesised, it will  be  de-parenthesised  and
-       sign-flipped automatically.
+       If  the CSV has the currency symbol in a separate field, assign that to
+       the currency pseudo field to have it prepended to the amount.  Or,  you
+       can  use a field assignment to amount that interpolates both CSV fields
+       (giving more control, eg to put the currency symbol on the right).
 
-       The  generated  journal  entries  will be sorted by date.  The original
-       order of same-day entries will be preserved, usually.
+   CSV balance assertions
+       If the CSV includes a running balance, you can assign that to the  bal-
+       ance  pseudo field; whenever the running balance value is non-empty, it
+       will be asserted as the balance after the account1 posting.
 
+   Reading multiple CSV files
+       You can read multiple CSV files at once using multiple -f arguments  on
+       the  command  line,  and  hledger will look for a correspondingly-named
+       rules file for each.  Note if you use the --rules-file option, this one
+       rules file will be used for all the CSV files being read.
 
 
+
 REPORTING BUGS
-       Report bugs at http://bugs.hledger.org (or on the #hledger IRC  channel
+       Report  bugs at http://bugs.hledger.org (or on the #hledger IRC channel
        or hledger mail list)
 
 
@@ -163,7 +192,7 @@
 
 
 SEE ALSO
-       hledger(1),      hledger-ui(1),     hledger-web(1),     hledger-api(1),
+       hledger(1),     hledger-ui(1),     hledger-web(1),      hledger-api(1),
        hledger_csv(5), hledger_journal(5), hledger_timeclock(5), hledger_time-
        dot(5), ledger(1)
 
@@ -171,4 +200,4 @@
 
 
 
-hledger 1.3.2                   September 2017                  hledger_csv(5)
+hledger 1.4                     September 2017                  hledger_csv(5)
diff --git a/doc/hledger_journal.5 b/doc/hledger_journal.5
--- a/doc/hledger_journal.5
+++ b/doc/hledger_journal.5
@@ -1,6 +1,6 @@
 .\"t
 
-.TH "hledger_journal" "5" "September 2017" "hledger 1.3.2" "hledger User Manuals"
+.TH "hledger_journal" "5" "September 2017" "hledger 1.4" "hledger User Manuals"
 
 
 
@@ -53,6 +53,10 @@
 \ \ \ \ expenses:supplies\ \ \ \ \ $1\ \ \ \ ;\ <\-\ this\ transaction\ debits\ two\ expense\ accounts
 \ \ \ \ assets:cash\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ;\ <\-\ $\-2\ inferred
 
+2008/10/01\ take\ a\ loan
+\ \ \ \ assets:bank:checking\ \ $1
+\ \ \ \ liabilities:debts\ \ \ \ $\-1
+
 2008/12/31\ *\ pay\ off\ \ \ \ \ \ \ \ \ \ \ \ ;\ <\-\ an\ optional\ *\ or\ !\ after\ the\ date\ means\ "cleared"\ (or\ anything\ you\ want)
 \ \ \ \ liabilities:debts\ \ \ \ \ $1
 \ \ \ \ assets:bank:checking
@@ -73,7 +77,10 @@
 parentheses)
 .IP \[bu] 2
 (optional) a transaction description (any remaining text until end of
-line)
+line or a semicolon)
+.IP \[bu] 2
+(optional) a transaction comment (any remaining text following a
+semicolon until end of line)
 .PP
 Then comes zero or more (but usually at least 2) indented lines
 representing...
@@ -296,6 +303,20 @@
 at your bank, \f[C]\-U\f[] to see things which will probably hit your
 bank soon (like uncashed checks), and no flags to see the most
 up\-to\-date state of your finances.
+.SS Description
+.PP
+A transaction\[aq]s description is the rest of the line following the
+date and status mark (or until a comment begins).
+Sometimes called the "narration" in traditional bookkeeping, it can be
+used for whatever you wish, or left blank.
+Transaction descriptions can be queried, unlike comments.
+.SS Payee and note
+.PP
+You can optionally include a \f[C]|\f[] (pipe) character in a
+description to subdivide it into a payee/payer name on the left and
+additional notes on the right.
+This may be worthwhile if you need to do more precise querying and
+pivoting by payee.
 .SS Account names
 .PP
 Account names typically have several parts separated by a full colon,
@@ -793,24 +814,6 @@
 .PP
 Tags are like Ledger\[aq]s metadata feature, except hledger\[aq]s tag
 values are simple strings.
-.SS Implicit tags
-.PP
-Some predefined "implicit" tags are also provided:
-.IP \[bu] 2
-\f[C]code\f[] \- the transaction\[aq]s code field
-.IP \[bu] 2
-\f[C]description\f[] \- the transaction\[aq]s description
-.IP \[bu] 2
-\f[C]payee\f[] \- the part of description before \f[C]|\f[], or all of
-it
-.IP \[bu] 2
-\f[C]note\f[] \- the part of description after \f[C]|\f[], or all of it
-.PP
-\f[C]payee\f[] and \f[C]note\f[] support descriptions written in a
-special \f[C]PAYEE\ |\ NOTE\f[] format, accessing the parts before and
-after the pipe character respectively.
-For descriptions not containing a pipe character they are the same as
-\f[C]description\f[].
 .SS Directives
 .SS Account aliases
 .PP
@@ -878,9 +881,7 @@
 replaced by REPLACEMENT.
 If REGEX contains parenthesised match groups, these can be referenced by
 the usual numeric backreferences in REPLACEMENT.
-Note, currently regular expression aliases may cause noticeable
-slow\-downs.
-(And if you use Ledger on your hledger file, they will be ignored.) Eg:
+Eg:
 .IP
 .nf
 \f[C]
@@ -888,6 +889,9 @@
 #\ rewrites\ "assets:bank:wells\ fargo:checking"\ to\ \ "assets:wells\ fargo\ checking"
 \f[]
 .fi
+.PP
+Also note that REPLACEMENT continues to the end of line (or on command
+line, to end of option argument), so it can contain trailing whitespace.
 .SS Multiple aliases
 .PP
 You can define as many aliases as you like using directives or
@@ -1119,6 +1123,11 @@
 Text Wrangler \ 
 T}@T{
 https://github.com/ledger/ledger/wiki/Editing\-Ledger\-files\-with\-TextWrangler
+T}
+T{
+Visual Studio Code
+T}@T{
+https://marketplace.visualstudio.com/items?itemName=mark\-hansen.hledger\-vscode
 T}
 .TE
 
diff --git a/doc/hledger_journal.5.info b/doc/hledger_journal.5.info
--- a/doc/hledger_journal.5.info
+++ b/doc/hledger_journal.5.info
@@ -4,8 +4,8 @@
 
 File: hledger_journal.5.info,  Node: Top,  Next: FILE FORMAT,  Up: (dir)
 
-hledger_journal(5) hledger 1.3.2
-********************************
+hledger_journal(5) hledger 1.4
+******************************
 
 hledger's usual data source is a plain text file containing journal
 entries in hledger journal format.  This file represents a standard
@@ -46,6 +46,10 @@
     expenses:supplies     $1    ; <- this transaction debits two expense accounts
     assets:cash                 ; <- $-2 inferred
 
+2008/10/01 take a loan
+    assets:bank:checking  $1
+    liabilities:debts    $-1
+
 2008/12/31 * pay off            ; <- an optional * or ! after the date means "cleared" (or anything you want)
     liabilities:debts     $1
     assets:bank:checking
@@ -67,6 +71,7 @@
 * Postings::
 * Dates::
 * Status::
+* Description::
 * Account names::
 * Amounts::
 * Virtual Postings::
@@ -92,7 +97,9 @@
    * (optional) a transaction code (any short number or text, enclosed
      in parentheses)
    * (optional) a transaction description (any remaining text until end
-     of line)
+     of line or a semicolon)
+   * (optional) a transaction comment (any remaining text following a
+     semicolon until end of line)
 
    Then comes zero or more (but usually at least 2) indented lines
 representing...
@@ -227,7 +234,7 @@
 transaction and DATE2 infers its year from DATE.
 
 
-File: hledger_journal.5.info,  Node: Status,  Next: Account names,  Prev: Dates,  Up: FILE FORMAT
+File: hledger_journal.5.info,  Node: Status,  Next: Description,  Prev: Dates,  Up: FILE FORMAT
 
 1.4 Status
 ==========
@@ -277,9 +284,35 @@
 your finances.
 
 
-File: hledger_journal.5.info,  Node: Account names,  Next: Amounts,  Prev: Status,  Up: FILE FORMAT
+File: hledger_journal.5.info,  Node: Description,  Next: Account names,  Prev: Status,  Up: FILE FORMAT
 
-1.5 Account names
+1.5 Description
+===============
+
+A transaction's description is the rest of the line following the date
+and status mark (or until a comment begins).  Sometimes called the
+"narration" in traditional bookkeeping, it can be used for whatever you
+wish, or left blank.  Transaction descriptions can be queried, unlike
+comments.
+* Menu:
+
+* Payee and note::
+
+
+File: hledger_journal.5.info,  Node: Payee and note,  Up: Description
+
+1.5.1 Payee and note
+--------------------
+
+You can optionally include a '|' (pipe) character in a description to
+subdivide it into a payee/payer name on the left and additional notes on
+the right.  This may be worthwhile if you need to do more precise
+querying and pivoting by payee.
+
+
+File: hledger_journal.5.info,  Node: Account names,  Next: Amounts,  Prev: Description,  Up: FILE FORMAT
+
+1.6 Account names
 =================
 
 Account names typically have several parts separated by a full colon,
@@ -297,7 +330,7 @@
 
 File: hledger_journal.5.info,  Node: Amounts,  Next: Virtual Postings,  Prev: Account names,  Up: FILE FORMAT
 
-1.6 Amounts
+1.7 Amounts
 ===========
 
 After the account name, there is usually an amount.  Important: between
@@ -352,7 +385,7 @@
 
 File: hledger_journal.5.info,  Node: Virtual Postings,  Next: Balance Assertions,  Prev: Amounts,  Up: FILE FORMAT
 
-1.7 Virtual Postings
+1.8 Virtual Postings
 ====================
 
 When you parenthesise the account name in a posting, we call that a
@@ -387,7 +420,7 @@
 
 File: hledger_journal.5.info,  Node: Balance Assertions,  Next: Balance Assignments,  Prev: Virtual Postings,  Up: FILE FORMAT
 
-1.8 Balance Assertions
+1.9 Balance Assertions
 ======================
 
 hledger supports Ledger-style balance assertions in journal files.
@@ -421,7 +454,7 @@
 
 File: hledger_journal.5.info,  Node: Assertions and ordering,  Next: Assertions and included files,  Up: Balance Assertions
 
-1.8.1 Assertions and ordering
+1.9.1 Assertions and ordering
 -----------------------------
 
 hledger sorts an account's postings and assertions first by date and
@@ -440,7 +473,7 @@
 
 File: hledger_journal.5.info,  Node: Assertions and included files,  Next: Assertions and multiple -f options,  Prev: Assertions and ordering,  Up: Balance Assertions
 
-1.8.2 Assertions and included files
+1.9.2 Assertions and included files
 -----------------------------------
 
 With included files, things are a little more complicated.  Including
@@ -452,7 +485,7 @@
 
 File: hledger_journal.5.info,  Node: Assertions and multiple -f options,  Next: Assertions and commodities,  Prev: Assertions and included files,  Up: Balance Assertions
 
-1.8.3 Assertions and multiple -f options
+1.9.3 Assertions and multiple -f options
 ----------------------------------------
 
 Balance assertions don't work well across files specified with multiple
@@ -461,7 +494,7 @@
 
 File: hledger_journal.5.info,  Node: Assertions and commodities,  Next: Assertions and subaccounts,  Prev: Assertions and multiple -f options,  Up: Balance Assertions
 
-1.8.4 Assertions and commodities
+1.9.4 Assertions and commodities
 --------------------------------
 
 The asserted balance must be a simple single-commodity amount, and in
@@ -480,7 +513,7 @@
 
 File: hledger_journal.5.info,  Node: Assertions and subaccounts,  Next: Assertions and virtual postings,  Prev: Assertions and commodities,  Up: Balance Assertions
 
-1.8.5 Assertions and subaccounts
+1.9.5 Assertions and subaccounts
 --------------------------------
 
 Balance assertions do not count the balance from subaccounts; they check
@@ -503,7 +536,7 @@
 
 File: hledger_journal.5.info,  Node: Assertions and virtual postings,  Prev: Assertions and subaccounts,  Up: Balance Assertions
 
-1.8.6 Assertions and virtual postings
+1.9.6 Assertions and virtual postings
 -------------------------------------
 
 Balance assertions are checked against all postings, both real and
@@ -513,8 +546,8 @@
 
 File: hledger_journal.5.info,  Node: Balance Assignments,  Next: Prices,  Prev: Balance Assertions,  Up: FILE FORMAT
 
-1.9 Balance Assignments
-=======================
+1.10 Balance Assignments
+========================
 
 Ledger-style balance assignments are also supported.  These are like
 balance assertions, but with no posting amount on the left side of the
@@ -546,7 +579,7 @@
 
 File: hledger_journal.5.info,  Node: Prices,  Next: Comments,  Prev: Balance Assignments,  Up: FILE FORMAT
 
-1.10 Prices
+1.11 Prices
 ===========
 
 * Menu:
@@ -557,7 +590,7 @@
 
 File: hledger_journal.5.info,  Node: Transaction prices,  Next: Market prices,  Up: Prices
 
-1.10.1 Transaction prices
+1.11.1 Transaction prices
 -------------------------
 
 Within a transaction, you can note an amount's price in another
@@ -618,7 +651,7 @@
 
 File: hledger_journal.5.info,  Node: Market prices,  Prev: Transaction prices,  Up: Prices
 
-1.10.2 Market prices
+1.11.2 Market prices
 --------------------
 
 Market prices are not tied to a particular transaction; they represent
@@ -647,7 +680,7 @@
 
 File: hledger_journal.5.info,  Node: Comments,  Next: Tags,  Prev: Prices,  Up: FILE FORMAT
 
-1.11 Comments
+1.12 Comments
 =============
 
 Lines in the journal beginning with a semicolon (';') or hash ('#') or
@@ -687,7 +720,7 @@
 
 File: hledger_journal.5.info,  Node: Tags,  Next: Directives,  Prev: Comments,  Up: FILE FORMAT
 
-1.12 Tags
+1.13 Tags
 =========
 
 Tags are a way to add extra labels or labelled data to postings and
@@ -726,32 +759,11 @@
 
    Tags are like Ledger's metadata feature, except hledger's tag values
 are simple strings.
-* Menu:
 
-* Implicit tags::
-
 
-File: hledger_journal.5.info,  Node: Implicit tags,  Up: Tags
-
-1.12.1 Implicit tags
---------------------
-
-Some predefined "implicit" tags are also provided:
-
-   * 'code' - the transaction's code field
-   * 'description' - the transaction's description
-   * 'payee' - the part of description before '|', or all of it
-   * 'note' - the part of description after '|', or all of it
-
-   'payee' and 'note' support descriptions written in a special 'PAYEE |
-NOTE' format, accessing the parts before and after the pipe character
-respectively.  For descriptions not containing a pipe character they are
-the same as 'description'.
-
-
 File: hledger_journal.5.info,  Node: Directives,  Prev: Tags,  Up: FILE FORMAT
 
-1.13 Directives
+1.14 Directives
 ===============
 
 * Menu:
@@ -768,7 +780,7 @@
 
 File: hledger_journal.5.info,  Node: Account aliases,  Next: account directive,  Up: Directives
 
-1.13.1 Account aliases
+1.14.1 Account aliases
 ----------------------
 
 You can define aliases which rewrite your account names (after reading
@@ -793,7 +805,7 @@
 
 File: hledger_journal.5.info,  Node: Basic aliases,  Next: Regex aliases,  Up: Account aliases
 
-1.13.1.1 Basic aliases
+1.14.1.1 Basic aliases
 ......................
 
 To set an account alias, use the 'alias' directive in your journal file.
@@ -816,7 +828,7 @@
 
 File: hledger_journal.5.info,  Node: Regex aliases,  Next: Multiple aliases,  Prev: Basic aliases,  Up: Account aliases
 
-1.13.1.2 Regex aliases
+1.14.1.2 Regex aliases
 ......................
 
 There is also a more powerful variant that uses a regular expression,
@@ -829,17 +841,19 @@
    REGEX is a case-insensitive regular expression.  Anywhere it matches
 inside an account name, the matched part will be replaced by
 REPLACEMENT. If REGEX contains parenthesised match groups, these can be
-referenced by the usual numeric backreferences in REPLACEMENT. Note,
-currently regular expression aliases may cause noticeable slow-downs.
-(And if you use Ledger on your hledger file, they will be ignored.)  Eg:
+referenced by the usual numeric backreferences in REPLACEMENT. Eg:
 
 alias /^(.+):bank:([^:]+)(.*)/ = \1:\2 \3
 # rewrites "assets:bank:wells fargo:checking" to  "assets:wells fargo checking"
 
+   Also note that REPLACEMENT continues to the end of line (or on
+command line, to end of option argument), so it can contain trailing
+whitespace.
+
 
 File: hledger_journal.5.info,  Node: Multiple aliases,  Next: end aliases,  Prev: Regex aliases,  Up: Account aliases
 
-1.13.1.3 Multiple aliases
+1.14.1.3 Multiple aliases
 .........................
 
 You can define as many aliases as you like using directives or
@@ -855,7 +869,7 @@
 
 File: hledger_journal.5.info,  Node: end aliases,  Prev: Multiple aliases,  Up: Account aliases
 
-1.13.1.4 end aliases
+1.14.1.4 end aliases
 ....................
 
 You can clear (forget) all currently defined aliases with the 'end
@@ -866,7 +880,7 @@
 
 File: hledger_journal.5.info,  Node: account directive,  Next: apply account directive,  Prev: Account aliases,  Up: Directives
 
-1.13.2 account directive
+1.14.2 account directive
 ------------------------
 
 The 'account' directive predefines account names, as in Ledger and
@@ -887,7 +901,7 @@
 
 File: hledger_journal.5.info,  Node: apply account directive,  Next: Multi-line comments,  Prev: account directive,  Up: Directives
 
-1.13.3 apply account directive
+1.14.3 apply account directive
 ------------------------------
 
 You can specify a parent account which will be prepended to all accounts
@@ -923,7 +937,7 @@
 
 File: hledger_journal.5.info,  Node: Multi-line comments,  Next: commodity directive,  Prev: apply account directive,  Up: Directives
 
-1.13.4 Multi-line comments
+1.14.4 Multi-line comments
 --------------------------
 
 A line containing just 'comment' starts a multi-line comment, and a line
@@ -932,7 +946,7 @@
 
 File: hledger_journal.5.info,  Node: commodity directive,  Next: Default commodity,  Prev: Multi-line comments,  Up: Directives
 
-1.13.5 commodity directive
+1.14.5 commodity directive
 --------------------------
 
 The 'commodity' directive predefines commodities (currently this is just
@@ -964,7 +978,7 @@
 
 File: hledger_journal.5.info,  Node: Default commodity,  Next: Default year,  Prev: commodity directive,  Up: Directives
 
-1.13.6 Default commodity
+1.14.6 Default commodity
 ------------------------
 
 The D directive sets a default commodity (and display format), to be
@@ -984,7 +998,7 @@
 
 File: hledger_journal.5.info,  Node: Default year,  Next: Including other files,  Prev: Default commodity,  Up: Directives
 
-1.13.7 Default year
+1.14.7 Default year
 -------------------
 
 You can set a default year to be used for subsequent dates which don't
@@ -1010,7 +1024,7 @@
 
 File: hledger_journal.5.info,  Node: Including other files,  Prev: Default year,  Up: Directives
 
-1.13.8 Including other files
+1.14.8 Including other files
 ----------------------------
 
 You can pull in the content of additional journal files by writing an
@@ -1043,87 +1057,91 @@
 Sublime Text      https://github.com/ledger/ledger/wiki/Using-Sublime-Text
 Textmate          https://github.com/ledger/ledger/wiki/Using-TextMate-2
 Text Wrangler     https://github.com/ledger/ledger/wiki/Editing-Ledger-files-with-TextWrangler
+Visual Studio     https://marketplace.visualstudio.com/items?itemName=mark-hansen.hledger-vscode
+Code
 
 
 Tag Table:
 Node: Top78
-Node: FILE FORMAT2296
-Ref: #file-format2422
-Node: Transactions2629
-Ref: #transactions2752
-Node: Postings3317
-Ref: #postings3446
-Node: Dates4441
-Ref: #dates4558
-Node: Simple dates4623
-Ref: #simple-dates4751
-Node: Secondary dates5117
-Ref: #secondary-dates5273
-Node: Posting dates6836
-Ref: #posting-dates6967
-Node: Status8341
-Ref: #status8465
-Node: Account names10179
-Ref: #account-names10319
-Node: Amounts10806
-Ref: #amounts10944
-Node: Virtual Postings13045
-Ref: #virtual-postings13206
-Node: Balance Assertions14426
-Ref: #balance-assertions14603
-Node: Assertions and ordering15499
-Ref: #assertions-and-ordering15687
-Node: Assertions and included files16387
-Ref: #assertions-and-included-files16630
-Node: Assertions and multiple -f options16963
-Ref: #assertions-and-multiple--f-options17219
-Node: Assertions and commodities17351
-Ref: #assertions-and-commodities17588
-Node: Assertions and subaccounts18284
-Ref: #assertions-and-subaccounts18518
-Node: Assertions and virtual postings19039
-Ref: #assertions-and-virtual-postings19248
-Node: Balance Assignments19390
-Ref: #balance-assignments19559
-Node: Prices20678
-Ref: #prices20813
-Node: Transaction prices20864
-Ref: #transaction-prices21011
-Node: Market prices23167
-Ref: #market-prices23304
-Node: Comments24264
-Ref: #comments24388
-Node: Tags25501
-Ref: #tags25621
-Node: Implicit tags27050
-Ref: #implicit-tags27158
-Node: Directives27675
-Ref: #directives27790
-Node: Account aliases27983
-Ref: #account-aliases28129
-Node: Basic aliases28733
-Ref: #basic-aliases28878
-Node: Regex aliases29568
-Ref: #regex-aliases29738
-Node: Multiple aliases30453
-Ref: #multiple-aliases30627
-Node: end aliases31125
-Ref: #end-aliases31267
-Node: account directive31368
-Ref: #account-directive31550
-Node: apply account directive31846
-Ref: #apply-account-directive32044
-Node: Multi-line comments32703
-Ref: #multi-line-comments32895
-Node: commodity directive33023
-Ref: #commodity-directive33209
-Node: Default commodity34081
-Ref: #default-commodity34256
-Node: Default year34793
-Ref: #default-year34960
-Node: Including other files35383
-Ref: #including-other-files35542
-Node: EDITOR SUPPORT35939
-Ref: #editor-support36059
+Node: FILE FORMAT2374
+Ref: #file-format2500
+Node: Transactions2723
+Ref: #transactions2846
+Node: Postings3530
+Ref: #postings3659
+Node: Dates4654
+Ref: #dates4771
+Node: Simple dates4836
+Ref: #simple-dates4964
+Node: Secondary dates5330
+Ref: #secondary-dates5486
+Node: Posting dates7049
+Ref: #posting-dates7180
+Node: Status8554
+Ref: #status8676
+Node: Description10390
+Ref: #description10530
+Node: Payee and note10849
+Ref: #payee-and-note10965
+Node: Account names11207
+Ref: #account-names11352
+Node: Amounts11839
+Ref: #amounts11977
+Node: Virtual Postings14078
+Ref: #virtual-postings14239
+Node: Balance Assertions15459
+Ref: #balance-assertions15636
+Node: Assertions and ordering16532
+Ref: #assertions-and-ordering16720
+Node: Assertions and included files17420
+Ref: #assertions-and-included-files17663
+Node: Assertions and multiple -f options17996
+Ref: #assertions-and-multiple--f-options18252
+Node: Assertions and commodities18384
+Ref: #assertions-and-commodities18621
+Node: Assertions and subaccounts19317
+Ref: #assertions-and-subaccounts19551
+Node: Assertions and virtual postings20072
+Ref: #assertions-and-virtual-postings20281
+Node: Balance Assignments20423
+Ref: #balance-assignments20594
+Node: Prices21713
+Ref: #prices21848
+Node: Transaction prices21899
+Ref: #transaction-prices22046
+Node: Market prices24202
+Ref: #market-prices24339
+Node: Comments25299
+Ref: #comments25423
+Node: Tags26536
+Ref: #tags26656
+Node: Directives28058
+Ref: #directives28173
+Node: Account aliases28366
+Ref: #account-aliases28512
+Node: Basic aliases29116
+Ref: #basic-aliases29261
+Node: Regex aliases29951
+Ref: #regex-aliases30121
+Node: Multiple aliases30839
+Ref: #multiple-aliases31013
+Node: end aliases31511
+Ref: #end-aliases31653
+Node: account directive31754
+Ref: #account-directive31936
+Node: apply account directive32232
+Ref: #apply-account-directive32430
+Node: Multi-line comments33089
+Ref: #multi-line-comments33281
+Node: commodity directive33409
+Ref: #commodity-directive33595
+Node: Default commodity34467
+Ref: #default-commodity34642
+Node: Default year35179
+Ref: #default-year35346
+Node: Including other files35769
+Ref: #including-other-files35928
+Node: EDITOR SUPPORT36325
+Ref: #editor-support36445
 
 End Tag Table
diff --git a/doc/hledger_journal.5.txt b/doc/hledger_journal.5.txt
--- a/doc/hledger_journal.5.txt
+++ b/doc/hledger_journal.5.txt
@@ -47,6 +47,10 @@
                   expenses:supplies     $1    ; <- this transaction debits two expense accounts
                   assets:cash                 ; <- $-2 inferred
 
+              2008/10/01 take a loan
+                  assets:bank:checking  $1
+                  liabilities:debts    $-1
+
               2008/12/31 * pay off            ; <- an optional * or ! after the date means "cleared" (or anything you want)
                   liabilities:debts     $1
                   assets:bank:checking
@@ -64,59 +68,62 @@
          parentheses)
 
        o (optional) a transaction description (any remaining text until end of
-         line)
+         line or a semicolon)
 
-       Then comes zero or more (but usually at least 2) indented lines  repre-
+       o (optional) a transaction comment  (any  remaining  text  following  a
+         semicolon until end of line)
+
+       Then  comes zero or more (but usually at least 2) indented lines repre-
        senting...
 
    Postings
-       A  posting  is an addition of some amount to, or removal of some amount
-       from, an account.  Each posting line begins with at least one space  or
+       A posting is an addition of some amount to, or removal of  some  amount
+       from,  an account.  Each posting line begins with at least one space or
        tab (2 or 4 spaces is common), followed by:
 
        o (optional) a status character (empty, !, or *), followed by a space
 
-       o (required)  an  account  name (any text, optionally containing single
+       o (required) an account name (any text,  optionally  containing  single
          spaces, until end of line or a double space)
 
        o (optional) two or more spaces or tabs followed by an amount.
 
-       Positive amounts are being added to the account, negative  amounts  are
+       Positive  amounts  are being added to the account, negative amounts are
        being removed.
 
        The amounts within a transaction must always sum up to zero.  As a con-
-       venience, one amount may be left blank; it will be inferred  so  as  to
+       venience,  one  amount  may be left blank; it will be inferred so as to
        balance the transaction.
 
-       Be  sure  to  note the unusual two-space delimiter between account name
-       and amount.  This makes it easy to write account names containing  spa-
-       ces.   But if you accidentally leave only one space (or tab) before the
+       Be sure to note the unusual two-space delimiter  between  account  name
+       and  amount.  This makes it easy to write account names containing spa-
+       ces.  But if you accidentally leave only one space (or tab) before  the
        amount, the amount will be considered part of the account name.
 
    Dates
    Simple dates
-       Within a journal file, transaction dates use Y/M/D (or Y-M-D or  Y.M.D)
-       Leading  zeros are optional.  The year may be omitted, in which case it
-       will be inferred from  the  context  -  the  current  transaction,  the
-       default  year  set  with  a default year directive, or the current date
-       when the command is run.  Some examples: 2010/01/31, 1/31,  2010-01-31,
+       Within  a journal file, transaction dates use Y/M/D (or Y-M-D or Y.M.D)
+       Leading zeros are optional.  The year may be omitted, in which case  it
+       will  be  inferred  from  the  context  -  the current transaction, the
+       default year set with a default year directive,  or  the  current  date
+       when  the command is run.  Some examples: 2010/01/31, 1/31, 2010-01-31,
        2010.1.31.
 
    Secondary dates
-       Real-life  transactions  sometimes  involve more than one date - eg the
+       Real-life transactions sometimes involve more than one date  -  eg  the
        date you write a cheque, and the date it clears in your bank.  When you
-       want  to  model  this,  eg  for more accurate balances, you can specify
-       individual posting dates, which I recommend.  Or, you can use the  sec-
-       ondary  dates  (aka  auxiliary/effective  dates) feature, supported for
+       want to model this, eg for more  accurate  balances,  you  can  specify
+       individual  posting dates, which I recommend.  Or, you can use the sec-
+       ondary dates (aka auxiliary/effective  dates)  feature,  supported  for
        compatibility with Ledger.
 
        A secondary date can be written after the primary date, separated by an
-       equals  sign.   The  primary date, on the left, is used by default; the
-       secondary date, on the right, is used when the --date2 flag  is  speci-
+       equals sign.  The primary date, on the left, is used  by  default;  the
+       secondary  date,  on the right, is used when the --date2 flag is speci-
        fied (--aux-date or --effective also work).
 
-       The  meaning of secondary dates is up to you, but it's best to follow a
-       consistent rule.  Eg write the bank's clearing  date  as  primary,  and
+       The meaning of secondary dates is up to you, but it's best to follow  a
+       consistent  rule.   Eg  write  the bank's clearing date as primary, and
        when needed, the date the transaction was initiated as secondary.
 
        Here's an example.  Note that a secondary date will use the year of the
@@ -132,18 +139,18 @@
               $ hledger register checking --date2
               2010/02/19 movie ticket         assets:checking                $-10         $-10
 
-       Secondary dates require some effort; you must use them consistently  in
+       Secondary  dates require some effort; you must use them consistently in
        your journal entries and remember whether to use or not use the --date2
        flag for your reports.  They are included in hledger for Ledger compat-
-       ibility,  but  posting  dates  are  a  more powerful and less confusing
+       ibility, but posting dates are  a  more  powerful  and  less  confusing
        alternative.
 
    Posting dates
-       You can give individual postings a different  date  from  their  parent
-       transaction,  by  adding a posting comment containing a tag (see below)
+       You  can  give  individual  postings a different date from their parent
+       transaction, by adding a posting comment containing a tag  (see  below)
        like date:DATE.  This is probably the best way to control posting dates
-       precisely.   Eg  in  this  example  the  expense  should  appear in May
-       reports, and the deduction from checking should be reported on 6/1  for
+       precisely.  Eg in  this  example  the  expense  should  appear  in  May
+       reports,  and the deduction from checking should be reported on 6/1 for
        easy bank reconciliation:
 
               2015/5/30
@@ -156,22 +163,22 @@
               $ hledger -f t.j register checking
               2015/06/01                      assets:checking               $-10          $-10
 
-       DATE  should be a simple date; if the year is not specified it will use
-       the year of the transaction's date.  You can  set  the  secondary  date
-       similarly,  with  date2:DATE2.   The  date:  or date2: tags must have a
-       valid simple date value if they are present, eg a  date:  tag  with  no
+       DATE should be a simple date; if the year is not specified it will  use
+       the  year  of  the  transaction's date.  You can set the secondary date
+       similarly, with date2:DATE2.  The date: or  date2:  tags  must  have  a
+       valid  simple  date  value  if they are present, eg a date: tag with no
        value is not allowed.
 
        Ledger's earlier, more compact bracketed date syntax is also supported:
-       [DATE], [DATE=DATE2] or [=DATE2].  hledger will attempt  to  parse  any
+       [DATE],  [DATE=DATE2]  or  [=DATE2].  hledger will attempt to parse any
        square-bracketed sequence of the 0123456789/-.= characters in this way.
-       With this syntax, DATE infers its year from the transaction  and  DATE2
+       With  this  syntax, DATE infers its year from the transaction and DATE2
        infers its year from DATE.
 
    Status
-       Transactions,  or  individual postings within a transaction, can have a
-       status mark,  which  is  a  single  character  before  the  transaction
-       description  or  posting  account  name,  separated from it by a space,
+       Transactions, or individual postings within a transaction, can  have  a
+       status  mark,  which  is  a  single  character  before  the transaction
+       description or posting account name, separated  from  it  by  a  space,
        indicating one of three statuses:
 
 
@@ -181,39 +188,52 @@
        !        pending
        *        cleared
 
-       When reporting, you  can  filter  by  status  with  the  -U/--unmarked,
-       -P/--pending,  and  -C/--cleared  flags;  or the status:, status:!, and
+       When  reporting,  you  can  filter  by  status  with the -U/--unmarked,
+       -P/--pending, and -C/--cleared flags; or  the  status:,  status:!,  and
        status:* queries; or the U, P, C keys in hledger-ui.
 
-       Note, in Ledger and in older versions of hledger, the "unmarked"  state
-       is  called  "uncleared".   As  of  hledger  1.3  we  have renamed it to
+       Note,  in Ledger and in older versions of hledger, the "unmarked" state
+       is called "uncleared".  As  of  hledger  1.3  we  have  renamed  it  to
        unmarked for clarity.
 
-       To replicate Ledger and old hledger's behaviour of also matching  pend-
+       To  replicate Ledger and old hledger's behaviour of also matching pend-
        ing, combine -U and -P.
 
-       Status  marks  are optional, but can be helpful eg for reconciling with
+       Status marks are optional, but can be helpful eg for  reconciling  with
        real-world accounts.  Some editor modes provide highlighting and short-
-       cuts  for working with status.  Eg in Emacs ledger-mode, you can toggle
+       cuts for working with status.  Eg in Emacs ledger-mode, you can  toggle
        transaction status with C-c C-e, or posting status with C-c C-c.
 
-       What "uncleared", "pending", and "cleared" actually mean is up to  you.
+       What  "uncleared", "pending", and "cleared" actually mean is up to you.
        Here's one suggestion:
 
 
        status       meaning
        --------------------------------------------------------------------------
        uncleared    recorded but not yet reconciled; needs review
-       pending      tentatively  reconciled  (if needed, eg during a big recon-
+       pending      tentatively reconciled (if needed, eg during a  big  recon-
                     ciliation)
-       cleared      complete, reconciled as far  as  possible,  and  considered
+       cleared      complete,  reconciled  as  far  as possible, and considered
                     correct
 
-       With  this scheme, you would use -PC to see the current balance at your
-       bank, -U to see things which will probably hit  your  bank  soon  (like
+       With this scheme, you would use -PC to see the current balance at  your
+       bank,  -U  to  see  things which will probably hit your bank soon (like
        uncashed checks), and no flags to see the most up-to-date state of your
        finances.
 
+   Description
+       A  transaction's description is the rest of the line following the date
+       and status mark (or until a  comment  begins).   Sometimes  called  the
+       "narration" in traditional bookkeeping, it can be used for whatever you
+       wish, or left blank.  Transaction descriptions can be  queried,  unlike
+       comments.
+
+   Payee and note
+       You  can  optionally  include  a | (pipe) character in a description to
+       subdivide it into a payee/payer name on the left and  additional  notes
+       on  the  right.   This may be worthwhile if you need to do more precise
+       querying and pivoting by payee.
+
    Account names
        Account names typically have several parts separated by a  full  colon,
        from  which hledger derives a hierarchical chart of accounts.  They can
@@ -577,25 +597,9 @@
        Tags are like Ledger's metadata feature, except  hledger's  tag  values
        are simple strings.
 
-   Implicit tags
-       Some predefined "implicit" tags are also provided:
-
-       o code - the transaction's code field
-
-       o description - the transaction's description
-
-       o payee - the part of description before |, or all of it
-
-       o note - the part of description after |, or all of it
-
-       payee  and  note support descriptions written in a special PAYEE | NOTE
-       format, accessing the parts before and after the pipe character respec-
-       tively.   For descriptions not containing a pipe character they are the
-       same as description.
-
    Directives
    Account aliases
-       You can define aliases which rewrite your account names (after  reading
+       You  can define aliases which rewrite your account names (after reading
        the journal, before generating reports).  hledger's account aliases can
        be useful for:
 
@@ -612,8 +616,8 @@
        See also Cookbook: rewrite account names.
 
    Basic aliases
-       To set an account alias, use the alias directive in your journal  file.
-       This  affects all subsequent journal entries in the current file or its
+       To  set an account alias, use the alias directive in your journal file.
+       This affects all subsequent journal entries in the current file or  its
        included files.  The spaces around the = are optional:
 
               alias OLD = NEW
@@ -621,31 +625,33 @@
        Or, you can use the --alias 'OLD=NEW' option on the command line.  This
        affects all entries.  It's useful for trying out aliases interactively.
 
-       OLD and NEW are full account names.  hledger will  replace  any  occur-
-       rence  of  the old account name with the new one.  Subaccounts are also
+       OLD  and  NEW  are full account names.  hledger will replace any occur-
+       rence of the old account name with the new one.  Subaccounts  are  also
        affected.  Eg:
 
               alias checking = assets:bank:wells fargo:checking
               # rewrites "checking" to "assets:bank:wells fargo:checking", or "checking:a" to "assets:bank:wells fargo:checking:a"
 
    Regex aliases
-       There is also a more powerful variant that uses a  regular  expression,
+       There  is  also a more powerful variant that uses a regular expression,
        indicated by the forward slashes:
 
               alias /REGEX/ = REPLACEMENT
 
        or --alias '/REGEX/=REPLACEMENT'.
 
-       REGEX  is  a  case-insensitive regular expression.  Anywhere it matches
-       inside an account name, the matched part will be replaced  by  REPLACE-
-       MENT.   If REGEX contains parenthesised match groups, these can be ref-
-       erenced by the usual numeric backreferences in REPLACEMENT.  Note, cur-
-       rently  regular  expression  aliases  may  cause noticeable slow-downs.
-       (And if you use Ledger on your hledger file, they will be ignored.) Eg:
+       REGEX is a case-insensitive regular expression.   Anywhere  it  matches
+       inside  an  account name, the matched part will be replaced by REPLACE-
+       MENT.  If REGEX contains parenthesised match groups, these can be  ref-
+       erenced by the usual numeric backreferences in REPLACEMENT.  Eg:
 
               alias /^(.+):bank:([^:]+)(.*)/ = \1:\2 \3
               # rewrites "assets:bank:wells fargo:checking" to  "assets:wells fargo checking"
 
+       Also  note that REPLACEMENT continues to the end of line (or on command
+       line, to end of option argument), so it  can  contain  trailing  white-
+       space.
+
    Multiple aliases
        You  can  define  as  many aliases as you like using directives or com-
        mand-line options.  Aliases are recursive - each alias sees the  result
@@ -811,7 +817,11 @@
                           ing-Ledger-files-with-TextWrangler
 
 
+       Visual    Studio   https://marketplace.visualstudio.com/items?item-
+       Code               Name=mark-hansen.hledger-vscode
 
+
+
 REPORTING BUGS
        Report  bugs at http://bugs.hledger.org (or on the #hledger IRC channel
        or hledger mail list)
@@ -835,4 +845,4 @@
 
 
 
-hledger 1.3.2                   September 2017              hledger_journal(5)
+hledger 1.4                     September 2017              hledger_journal(5)
diff --git a/doc/hledger_timeclock.5 b/doc/hledger_timeclock.5
--- a/doc/hledger_timeclock.5
+++ b/doc/hledger_timeclock.5
@@ -1,5 +1,5 @@
 
-.TH "hledger_timeclock" "5" "September 2017" "hledger 1.3.2" "hledger User Manuals"
+.TH "hledger_timeclock" "5" "September 2017" "hledger 1.4" "hledger User Manuals"
 
 
 
diff --git a/doc/hledger_timeclock.5.info b/doc/hledger_timeclock.5.info
--- a/doc/hledger_timeclock.5.info
+++ b/doc/hledger_timeclock.5.info
@@ -4,8 +4,8 @@
 
 File: hledger_timeclock.5.info,  Node: Top,  Up: (dir)
 
-hledger_timeclock(5) hledger 1.3.2
-**********************************
+hledger_timeclock(5) hledger 1.4
+********************************
 
 hledger can read timeclock files.  As with Ledger, these are (a subset
 of) timeclock.el's format, containing clock-in and clock-out entries as
diff --git a/doc/hledger_timeclock.5.txt b/doc/hledger_timeclock.5.txt
--- a/doc/hledger_timeclock.5.txt
+++ b/doc/hledger_timeclock.5.txt
@@ -79,4 +79,4 @@
 
 
 
-hledger 1.3.2                   September 2017            hledger_timeclock(5)
+hledger 1.4                     September 2017            hledger_timeclock(5)
diff --git a/doc/hledger_timedot.5 b/doc/hledger_timedot.5
--- a/doc/hledger_timedot.5
+++ b/doc/hledger_timedot.5
@@ -1,5 +1,5 @@
 
-.TH "hledger_timedot" "5" "September 2017" "hledger 1.3.2" "hledger User Manuals"
+.TH "hledger_timedot" "5" "September 2017" "hledger 1.4" "hledger User Manuals"
 
 
 
@@ -9,17 +9,17 @@
 .SH DESCRIPTION
 .PP
 Timedot is a plain text format for logging dated, categorised quantities
-(eg time), supported by hledger.
+(of time, usually), supported by hledger.
 It is convenient for approximate and retroactive time logging, eg when
 the real\-time clock\-in/out required with a timeclock file is too
 precise or too interruptive.
 It can be formatted like a bar chart, making clear at a glance where
 time was spent.
 .PP
-Though called "timedot", the format does not specify the commodity being
-logged, so could represent other dated, quantifiable things.
-Eg you could record a single\-entry journal of financial transactions,
-perhaps slightly more conveniently than with hledger_journal(5) format.
+Though called "timedot", this format is read by hledger as commodityless
+quantities, so it could be used to represent dated quantities other than
+time.
+In the docs below we\[aq]ll assume it\[aq]s time.
 .SH FILE FORMAT
 .PP
 A timedot file contains a series of day entries.
@@ -27,16 +27,26 @@
 pairs, one per line.
 Dates are hledger\-style simple dates (see hledger_journal(5)).
 Categories are hledger\-style account names, optionally indented.
-There must be at least two spaces between the category and the quantity.
-Quantities can be written in two ways:
-.IP "1." 3
-a series of dots (period characters).
-Each dot represents "a quarter" \- eg, a quarter hour.
-Spaces can be used to group dots into hours, for easier counting.
-.IP "2." 3
-a number (integer or decimal), representing "units" \- eg, hours.
-A good alternative when dots are cumbersome.
-(A number also can record negative quantities.)
+As in a hledger journal, there must be at least two spaces between the
+category (account name) and the quantity.
+.PP
+Quantities can be written as:
+.IP \[bu] 2
+a sequence of dots (.) representing quarter hours.
+Spaces may optionally be used for grouping and readability.
+Eg: ....
+\&..
+.IP \[bu] 2
+an integral or decimal number, representing hours.
+Eg: 1.5
+.IP \[bu] 2
+an integral or decimal number immediately followed by a unit symbol
+\f[C]s\f[], \f[C]m\f[], \f[C]h\f[], \f[C]d\f[], \f[C]w\f[], \f[C]mo\f[],
+or \f[C]y\f[], representing seconds, minutes, hours, days weeks, months
+or years respectively.
+Eg: 90m.
+The following equivalencies are assumed, currently: 1m = 60s, 1h = 60m,
+1d = 24h, 1w = 7d, 1mo = 30d, 1y=365d.
 .PP
 Blank lines and lines beginning with #, ; or * are ignored.
 An example:
diff --git a/doc/hledger_timedot.5.info b/doc/hledger_timedot.5.info
--- a/doc/hledger_timedot.5.info
+++ b/doc/hledger_timedot.5.info
@@ -4,20 +4,19 @@
 
 File: hledger_timedot.5.info,  Node: Top,  Next: FILE FORMAT,  Up: (dir)
 
-hledger_timedot(5) hledger 1.3.2
-********************************
+hledger_timedot(5) hledger 1.4
+******************************
 
 Timedot is a plain text format for logging dated, categorised quantities
-(eg time), supported by hledger.  It is convenient for approximate and
-retroactive time logging, eg when the real-time clock-in/out required
-with a timeclock file is too precise or too interruptive.  It can be
-formatted like a bar chart, making clear at a glance where time was
-spent.
+(of time, usually), supported by hledger.  It is convenient for
+approximate and retroactive time logging, eg when the real-time
+clock-in/out required with a timeclock file is too precise or too
+interruptive.  It can be formatted like a bar chart, making clear at a
+glance where time was spent.
 
-   Though called "timedot", the format does not specify the commodity
-being logged, so could represent other dated, quantifiable things.  Eg
-you could record a single-entry journal of financial transactions,
-perhaps slightly more conveniently than with hledger_journal(5) format.
+   Though called "timedot", this format is read by hledger as
+commodityless quantities, so it could be used to represent dated
+quantities other than time.  In the docs below we'll assume it's time.
 * Menu:
 
 * FILE FORMAT::
@@ -31,18 +30,23 @@
 A timedot file contains a series of day entries.  A day entry begins
 with a date, and is followed by category/quantity pairs, one per line.
 Dates are hledger-style simple dates (see hledger_journal(5)).
-Categories are hledger-style account names, optionally indented.  There
-must be at least two spaces between the category and the quantity.
-Quantities can be written in two ways:
+Categories are hledger-style account names, optionally indented.  As in
+a hledger journal, there must be at least two spaces between the
+category (account name) and the quantity.
 
-  1. a series of dots (period characters).  Each dot represents "a
-     quarter" - eg, a quarter hour.  Spaces can be used to group dots
-     into hours, for easier counting.
+   Quantities can be written as:
 
-  2. a number (integer or decimal), representing "units" - eg, hours.  A
-     good alternative when dots are cumbersome.  (A number also can
-     record negative quantities.)
+   * a sequence of dots (.)  representing quarter hours.  Spaces may
+     optionally be used for grouping and readability.  Eg: ....  ..
 
+   * an integral or decimal number, representing hours.  Eg: 1.5
+
+   * an integral or decimal number immediately followed by a unit symbol
+     's', 'm', 'h', 'd', 'w', 'mo', or 'y', representing seconds,
+     minutes, hours, days weeks, months or years respectively.  Eg: 90m.
+     The following equivalencies are assumed, currently: 1m = 60s, 1h =
+     60m, 1d = 24h, 1w = 7d, 1mo = 30d, 1y=365d.
+
    Blank lines and lines beginning with #, ; or * are ignored.  An
 example:
 
@@ -106,7 +110,7 @@
 
 Tag Table:
 Node: Top78
-Node: FILE FORMAT886
-Ref: #file-format989
+Node: FILE FORMAT809
+Ref: #file-format912
 
 End Tag Table
diff --git a/doc/hledger_timedot.5.txt b/doc/hledger_timedot.5.txt
--- a/doc/hledger_timedot.5.txt
+++ b/doc/hledger_timedot.5.txt
@@ -8,33 +8,37 @@
 
 DESCRIPTION
        Timedot  is  a plain text format for logging dated, categorised quanti-
-       ties (eg time), supported by hledger.  It is convenient for approximate
-       and  retroactive  time  logging,  eg  when  the  real-time clock-in/out
-       required with a timeclock file is too precise or too interruptive.   It
-       can  be formatted like a bar chart, making clear at a glance where time
-       was spent.
+       ties (of time, usually), supported by hledger.  It  is  convenient  for
+       approximate  and  retroactive  time  logging,  eg  when  the  real-time
+       clock-in/out required with a timeclock  file  is  too  precise  or  too
+       interruptive.   It can be formatted like a bar chart, making clear at a
+       glance where time was spent.
 
-       Though called "timedot", the format  does  not  specify  the  commodity
-       being  logged, so could represent other dated, quantifiable things.  Eg
-       you could record a single-entry journal of financial transactions, per-
-       haps slightly more conveniently than with hledger_journal(5) format.
+       Though called "timedot", this format is read by hledger  as  commodity-
+       less  quantities,  so  it  could  be used to represent dated quantities
+       other than time.  In the docs below we'll assume it's time.
 
 FILE FORMAT
-       A  timedot  file  contains a series of day entries.  A day entry begins
-       with a date, and is followed by category/quantity pairs, one per  line.
-       Dates  are  hledger-style simple dates (see hledger_journal(5)).  Cate-
-       gories are hledger-style account  names,  optionally  indented.   There
-       must  be  at  least  two  spaces between the category and the quantity.
-       Quantities can be written in two ways:
+       A timedot file contains a series of day entries.  A  day  entry  begins
+       with  a date, and is followed by category/quantity pairs, one per line.
+       Dates are hledger-style simple dates (see  hledger_journal(5)).   Cate-
+       gories  are  hledger-style account names, optionally indented.  As in a
+       hledger journal, there must be at least two spaces between the category
+       (account name) and the quantity.
 
-       1. a series of dots (period characters).  Each dot represents "a  quar-
-          ter"  -  eg,  a quarter hour.  Spaces can be used to group dots into
-          hours, for easier counting.
+       Quantities can be written as:
 
-       2. a number (integer or decimal), representing "units" - eg, hours.   A
-          good  alternative  when  dots  are  cumbersome.   (A number also can
-          record negative quantities.)
+       o a  sequence  of  dots  (.)  representing  quarter  hours.  Spaces may
+         optionally be used for grouping and readability.  Eg: ....  ..
 
+       o an integral or decimal number, representing hours.  Eg: 1.5
+
+       o an integral or decimal number immediately followed by a  unit  symbol
+         s,  m,  h, d, w, mo, or y, representing seconds, minutes, hours, days
+         weeks, months or years respectively.  Eg: 90m.  The following equiva-
+         lencies  are  assumed,  currently: 1m = 60s, 1h = 60m, 1d = 24h, 1w =
+         7d, 1mo = 30d, 1y=365d.
+
        Blank lines and lines beginning with #, ; or * are ignored.   An  exam-
        ple:
 
@@ -120,4 +124,4 @@
 
 
 
-hledger 1.3.2                   September 2017              hledger_timedot(5)
+hledger 1.4                     September 2017              hledger_timedot(5)
diff --git a/hledger-lib.cabal b/hledger-lib.cabal
--- a/hledger-lib.cabal
+++ b/hledger-lib.cabal
@@ -3,7 +3,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           hledger-lib
-version:        1.3.2
+version:        1.4
 synopsis:       Core data types, parsers and functionality for the hledger accounting tools
 description:    This is a reusable library containing hledger's core functionality.
                 .
@@ -21,7 +21,7 @@
 maintainer:     Simon Michael <simon@joyful.com>
 license:        GPL-3
 license-file:   LICENSE
-tested-with:    GHC==7.10.3, GHC==8.0
+tested-with:    GHC==7.10.3, GHC==8.0.2, GHC==8.2.1
 build-type:     Simple
 cabal-version:  >= 1.10
 
@@ -47,11 +47,6 @@
   type: git
   location: https://github.com/simonmichael/hledger
 
-flag oldtime
-  description: If building with time < 1.5, also depend on old-locale. Set automatically by cabal.
-  manual: False
-  default: False
-
 library
   hs-source-dirs:
       ./.
@@ -59,7 +54,7 @@
   build-depends:
       base >=4.8 && <5
     , base-compat >=0.8.1
-    , ansi-terminal >= 0.6.2.3 && < 0.7
+    , ansi-terminal >= 0.6.2.3 && < 0.8
     , array
     , blaze-markup >=0.5.1
     , bytestring
@@ -76,27 +71,18 @@
     , mtl
     , mtl-compat
     , old-time
-    , parsec
+    , parsec >= 3
     , pretty-show >=1.6.4
     , regex-tdfa
     , safe >=0.2
     , semigroups
     , split >=0.1 && <0.3
     , text >=1.2 && <1.3
+    , time >=1.5
     , transformers >=0.2 && <0.6
     , uglymemo
     , utf8-string >=0.3.5 && <1.1
     , HUnit
-  if impl(ghc <7.6)
-    build-depends:
-        ghc-prim
-  if flag(oldtime)
-    build-depends:
-        time <1.5
-      , old-locale
-  else
-    build-depends:
-        time >=1.5
   exposed-modules:
       Hledger
       Hledger.Data
@@ -156,7 +142,7 @@
   build-depends:
       base >=4.8 && <5
     , base-compat >=0.8.1
-    , ansi-terminal >= 0.6.2.3 && < 0.7
+    , ansi-terminal >= 0.6.2.3 && < 0.8
     , array
     , blaze-markup >=0.5.1
     , bytestring
@@ -173,29 +159,20 @@
     , mtl
     , mtl-compat
     , old-time
-    , parsec
+    , parsec >= 3
     , pretty-show >=1.6.4
     , regex-tdfa
     , safe >=0.2
     , semigroups
     , split >=0.1 && <0.3
     , text >=1.2 && <1.3
+    , time >=1.5
     , transformers >=0.2 && <0.6
     , uglymemo
     , utf8-string >=0.3.5 && <1.1
     , HUnit
     , doctest >=0.8
     , Glob >=0.7
-  if impl(ghc <7.6)
-    build-depends:
-        ghc-prim
-  if flag(oldtime)
-    build-depends:
-        time <1.5
-      , old-locale
-  else
-    build-depends:
-        time >=1.5
   other-modules:
       Hledger
       Hledger.Data
@@ -253,7 +230,7 @@
   build-depends:
       base >=4.8 && <5
     , base-compat >=0.8.1
-    , ansi-terminal >= 0.6.2.3 && < 0.7
+    , ansi-terminal >= 0.6.2.3 && < 0.8
     , array
     , blaze-markup >=0.5.1
     , bytestring
@@ -270,13 +247,14 @@
     , mtl
     , mtl-compat
     , old-time
-    , parsec
+    , parsec >= 3
     , pretty-show >=1.6.4
     , regex-tdfa
     , safe >=0.2
     , semigroups
     , split >=0.1 && <0.3
     , text >=1.2 && <1.3
+    , time >=1.5
     , transformers >=0.2 && <0.6
     , uglymemo
     , utf8-string >=0.3.5 && <1.1
@@ -284,16 +262,6 @@
     , hledger-lib
     , test-framework
     , test-framework-hunit
-  if impl(ghc <7.6)
-    build-depends:
-        ghc-prim
-  if flag(oldtime)
-    build-depends:
-        time <1.5
-      , old-locale
-  else
-    build-depends:
-        time >=1.5
   other-modules:
       Hledger
       Hledger.Data
diff --git a/tests/doctests.hs b/tests/doctests.hs
--- a/tests/doctests.hs
+++ b/tests/doctests.hs
@@ -5,6 +5,7 @@
 import Test.DocTest
 
 main = do
-  fs1 <- filter (not . isInfixOf "/.") <$> glob "Hledger/**/*.hs"
-  -- fs2 <- filter (not . isInfixOf "/.") <$> glob "other/ledger-parse/**/*.hs"
-  doctest $ ["Hledger.hs"] ++ fs1 -- ++ fs2
+  fs1 <- glob "Hledger/**/*.hs"
+  fs2 <- glob "Text/**/*.hs"
+  --fs3 <- glob "other/ledger-parse/**/*.hs"
+  doctest $ filter (not . isInfixOf "/.") $ ["Hledger.hs"] ++ fs1 ++ fs2
