qif-1.1.1: A simple QIF file format parser / printer

Safe HaskellNone
LanguageHaskell2010

Data.QIF

Contents

Description

A module for parsing or rendering QIF files.

QIF is a fairly braindead format designed for transfering financial data between applications. If you're writing a new financial application, you might want to find a newer format to share with other applications (if you can find one), and you definitely shouldn't use this as the database. For one thing, this format uses two-digit years, which is just kind of lazy. Also, it enforces absolutely no consistency constraints in terms of cross-references or transaction sums.

To parse a QIF file, I suggest using Data.Attoparsec.Text.Lazy and a lazy Text data structure, as follows:

   do txt <- Text.pack fmap readFile "my.qif"
      case parse parseQIF txt of
        Fail around _ err ->
          fail ("Parse error (" ++ err ++ ") around :" ++
                show (Text.take 10 around))
        Done _ res ->
          somethingInteresting res

To render a QIF file, you can run the builders from Data.Text.LazyBuilder directly, as so:

   Data.Text.Lazy.IO.writeFile "my.qif" (toLazyText (renderQIF myQIF))

Synopsis

Documentation

data QIF Source #

The semantic content of a QIF file. (Explicitly this: very little semantic processing has gone into this data structure, and it could contain semantic errors in the underlying file. Checking for these things is your job.)

Instances

Eq QIF Source # 

Methods

(==) :: QIF -> QIF -> Bool #

(/=) :: QIF -> QIF -> Bool #

Show QIF Source # 

Methods

showsPrec :: Int -> QIF -> ShowS #

show :: QIF -> String #

showList :: [QIF] -> ShowS #

emptyQIF :: QIF Source #

An empty QIF file.

qifAccounts :: Lens' QIF [Account] Source #

The accounts associated with the QIF file. We hope. You might expect that there would be an invariant that qifAccounts would be the same as map fst qifInvestmentTransactions ++ map fst qifNormalTransactions. I would, and it'd be nice if you tried to maintain that in your code. But, unfortuntely, there's nothing in the QIF file format that requires this. So you should probably be careful, and make sure you handle the case in which this item mentions accounts not seen anywhere else, and the case in which qifNormalTransactions and qifInvestmentTransactions suddenly invent new accounts.

qifCategories :: Lens' QIF [Category] Source #

The list of categories saved in this QIF file. Like qifAccounts, there doesn't seem to be anything enforcing consistency in the actual QIF file. So you may find that this list mentions categories not referenced elsewhere -- which is not necessarily too surprising -- but also that there may be transactions that mention new categories unlisted in this field.

qifSecurities :: Lens' QIF [Security] Source #

A cached list of securities. As with the other fields, be warned, as this is not required to be complete, as far as I can tell.

qifInvestmentTransactions :: Lens' QIF [(Account, [InvTransaction])] Source #

A list of investment accounts and the transactions associated with those accounts. Typically each Account should reference an account in qifAccount and include exactly the same date, but there's nothing in the file structure that enforces this invariant.

qifNormalTransactions :: Lens' QIF [(Account, [Transaction])] Source #

A list of non-investment accounts and the transactions associated with them. Again, one might expect that each Account here should reference an account in qifAccount, and contain exactly the same data, but there's nothing in the file structure that enforces this constraint.

parseQIF :: Parser QIF Source #

Parse a QIF file. This function is purely a syntactic parse, and makes no attempt to verify that the data it parses makes sense. So please be a bit paranoid with all the numbers and strings you receive, and perform any validation you need on your own. Also, this function assumes that it is parsing only a QIF file, and that it should run to the end of the input.

renderQIF :: QIF -> Builder Source #

Render out a QIF File. Because it's the order I've seen in my early example QIF files, this renders in the following order: account list, category list, investment accounts and their transactions, non-investment accounts and their transactions, and then security lists.

Various lists in QIF

parseAccountList :: Parser [Account] Source #

Parse the list of accounts associated with this QIF file.

renderAccountList :: [Account] -> Builder Source #

Render the list of accounts associated with this QIF file.

parseCategoryList :: Parser [Category] Source #

Parse the list of categories (and the header for said list).

renderCategoryList :: [Category] -> Builder Source #

Render the header for the list of categories followed by each of the categories.

parseBankEntryList :: Parser [Transaction] Source #

Parse a list of bank transactions. You should probably call this directly after parseAccountHeader and discovering that it's a Bank account. You should also not trust the results of this, as it does no consistency checking on your behalf.

renderBankEntryList :: [Transaction] -> Builder Source #

Render a list of bank transactions. Please do any consistency checking you want before calling this. You probably also want to have called renderAccountHeader with an appropriate Bank account before calling this one.

parseInvestmentEntries :: Parser [InvTransaction] Source #

Parse a list of investment entries. You probably should've called parseAccountHeader right before this and found an investment account.

renderInvestmentEntries :: [InvTransaction] -> Builder Source #

Render a list of investment transactions. You should probably have just called renderAccountHeader with an investment account.

parseCashEntryList :: Parser [Transaction] Source #

Parse a list of cash transactions. You should probably have just called parseAccountHeader and found a Cash account. You should probably also be a bit paranoid about checking over the date you read, as we perform no semantic checks on your behalf.

renderCashEntryList :: [Transaction] -> Builder Source #

Render a list of cash transactions. You should have just called renderAccountHeader with a Cash account.

parseCreditCardEntryList :: Parser [Transaction] Source #

Parse a list of credit card transactions. You should probably have just called parseAccountHeader and found a Cash account. You should probably also be a bit paranoid about checking over the date you read, as we perform no semantic checks on your behalf.

renderCreditCardEntryList :: [Transaction] -> Builder Source #

Render a list of credit card transactions. You should have just called renderAccountHeader with a CreditCard account.

parseAssetEntryList :: Parser [Transaction] Source #

Parse a list of transactions in an asset account. Again, you probably should have just called parseAccountHeader and found an Asset account, and you should make sure to do any data validation you care about. Because this library just doesn't care.

renderAssetEntryList :: [Transaction] -> Builder Source #

Render a list of transactions on an asset. Did you call renderAccountHeader before this with an asset account? You should have!

parseLiabilityEntryList :: Parser [Transaction] Source #

Last one! Parse a list of transactions about a liability. Probably a loan, which you may or may not regret. You *will* regret it, however, if you didn't call parseAccountHeader first and find a Liability account. You will also regret it if you don't do some input validation on what you get from this function.

renderLiabilityEntryList :: [Transaction] -> Builder Source #

Render a list of transactions about a liability, probably right after you called renderAccountHeader with a liability account.

parseSecurityList :: Parser [Security] Source #

Parse a list of securities out of the QIF file.

renderSecurityList :: [Security] -> Builder Source #

Render a list of securities.

Account Information

data Account Source #

An account in the QIF file. This same structure applies for all the account types.

emptyAccount :: Account Source #

A blank account. Defaults to BankAccount for the type, with the obvious zeros, empty strings, and Nothings elsewhere.

accountName :: Lens' Account Text Source #

The name of the account

accountType :: Lens' Account AccountType Source #

The type of the account

accountDescription :: Lens' Account Text Source #

The description of the account; in my limited experience this can (and most likely will) be empty.

accountCreditLimit :: Lens' Account (Maybe Currency) Source #

For accounts with limits, the credit limit for the account.

accountBalanceDate :: Lens' Account (Maybe Day) Source #

The date at which the balance in the next field was current.

accountBalance :: Lens' Account Currency Source #

The current balance.

parseAccount :: Parser Account Source #

Parse an account.

renderAccount :: Account -> Builder Source #

Render an account.

parseAccountType :: Parser AccountType Source #

Parse a fully-rendered account type (e.g, "!Type:Bank"), used for section headings.

renderAccountType :: AccountType -> Builder Source #

Render a fully-rendered account type (e.g., "!Type:Bank"), used for section headings.

parseShortAccountType :: Parser AccountType Source #

Parse the short version of an account type (e.g., Bank), which is used in a couple different places.

renderShortAccountType :: AccountType -> Builder Source #

Render the short version of an account type (e.g., "Bank").

parseAccountHeader :: Parser Account Source #

Sections full of transactions start with the header demarcating what account the transactions are in regard to. This parses that header, returning the account. Note that if you were expecting to be a somewhat reasonable standard, and just reference a previously-defined account, you're in for a disappointment. This is a completely fresh Account structure, and you'll have to match things up (and merge any differences) yourself.

renderAccountHeader :: Account -> Builder Source #

Render the header that should proceed any list of transactions.

Category Information

data CategoryKind Source #

Whether a category is an income category or an expense category.

Constructors

Income 
Expense 

data Category Source #

Information about a category that one might mark a transaction against.

emptyCategory :: Category Source #

A blank category. We default categories to Expense.

catName :: Lens' Category Text Source #

The name of the category.

catDescription :: Lens' Category Text Source #

A description of the category in question. Often empty.

catIsTaxRelated :: Lens' Category Bool Source #

Whether or not this category might be tax-related.

catBudgetAmount :: Lens' Category (Maybe Currency) Source #

A budget amount, if a budget has been established and published.

catTaxScheduleInfo :: Lens' Category (Maybe Word) Source #

A number describing the tax schedule to look at.

parseCategory :: Parser Category Source #

Parse a category.

renderCategory :: Category -> Builder Source #

Render a category.

Transaction Information

data SplitItem Source #

When a single transaction is split across a couple categories, this is your friend.

emptySplitItem :: SplitItem Source #

An empty SplitItem. No texts, no money. So sad.

entryMemo :: Lens' SplitItem Text Source #

Any memo taken as part of this split.

entryAmount :: Lens' SplitItem Currency Source #

The amount of money in this split.

entryCategory :: Lens' SplitItem Text Source #

The category associated with this split.

parseSplit :: SplitItem -> Parser SplitItem Source #

Parse a split. Note that some banking programs may end up emitting empty splits, and we don't do anything about that. So you might want to check if what you get back is emptyTransaction, or something morally similar.

renderSplit :: SplitItem -> Builder Source #

Render a split. Please be sensible in what you emit; this function won't check your work for you.

Standard Transactions (Bank, Credit Card, etc.)

data Transaction Source #

A normal transaction, that doesn't include an action in the stock market.

emptyTransaction :: Transaction Source #

A transaction with no real data, that happened to occur on January 1st, 2000. Happy new year!

entDate :: Lens' Transaction Day Source #

The date of the transaction.

entParty :: Lens' Transaction Text Source #

The other party to the transaction.

entMemo :: Lens' Transaction Text Source #

Any memos taken about the transaction.

entAmount :: Lens' Transaction Currency Source #

The total amount of the transaction.

entNumber :: Lens' Transaction (Maybe Word) Source #

The check or other number, as appropriate.

entCategory :: Lens' Transaction (Maybe Text) Source #

The category associated with the transaction, if provided.

entCleared :: Lens' Transaction Bool Source #

Whether or not this transaction has cleared.

entReimbursable :: Lens' Transaction Bool Source #

Whether or not this transaction is reimbursable.

entSplits :: Lens' Transaction [SplitItem] Source #

Any splits assocaited with this transaction.

parseTransaction :: Parser Transaction Source #

Parse a transaction. Note that this function only does parsing, not consistency checking. Thus, you may end up with a transaction whose splits do not sum to the total transaction amount, or is missing a category, etc.

renderTransaction :: Transaction -> Builder Source #

Render a transaction. This function assumes that you have performed any consistency checking you're going to do before writing out this transaction. It won't do any for you.

Investment Account Transactions

Trade Information

data TradeInfo Source #

Information about a given trade.

emptyTrade :: Day -> TradeInfo Source #

Build an empty trade made on a given day.

tradeDate :: Lens' TradeInfo Day Source #

The date of the trade.

tradeSecurity :: Lens' TradeInfo Text Source #

The security this trade was about. Note that while we probably should be doing some input validation on this, we're not. So if you're consuming this value, be a bit paranoid.

tradeSharePrice :: Lens' TradeInfo (Maybe Currency) Source #

The share price of the security during the trade, if provided.

tradeQuantity :: Lens' TradeInfo (Maybe ShareQuantity) Source #

The amount of the share traded, if provided.

tradeCommission :: Lens' TradeInfo (Maybe Currency) Source #

The annoying commission taken out of the trade, if provided. Note that QIF does differentiate between Nothing and (Just 0.00), for some reason.

tradeTotalAmount :: Lens' TradeInfo Currency Source #

The total amount of the trade.

Transfer Information

data TransferInfo Source #

Information about a transfer into an investment account. This probably looks like a normal transaction in a non-investment account, and each one probably has a sibling that is exactly that.

emptyTransfer :: Day -> TransferInfo Source #

An empty transfer that occurred on the given day.

transDate :: Lens' TransferInfo Day Source #

The date of the transfer.

transSummary :: Lens' TransferInfo Text Source #

A summary of the transfer. Sometimes the other party in the transfer, or just a short name, and sometimes blank.

transMemo :: Lens' TransferInfo Text Source #

A memo or note about the transaction. Often blank, in our limited experience.

transAmount :: Lens' TransferInfo Currency Source #

The amount of the transfer.

transCleared :: Lens' TransferInfo Bool Source #

Whether or not the transfer has cleared.

transAccount :: Lens' TransferInfo Text Source #

The account with which this transaction took place ... usually. Sometimes this is empty. Make of that as you will.

transSplits :: Lens' TransferInfo [SplitItem] Source #

Any splits associated with the transaction.

Actual Investment Actions

data InvTransaction Source #

An action in an investment account. These are the ones I've seen in QIF files shown to me. If you run into other ones, please file a bug or submit a patch.

invEntDate :: Lens' InvTransaction Day Source #

The date of an investment account action, regardless of what kind of transaction it was.

parseInvTransaction :: Parser InvTransaction Source #

Parse an investment transaction. Like it's sister function, parseTransaction, this function doesn't do any semantic validation. So it's possible that the date in the transaction doesn't make any sense. So ... that's on you.

renderInvTransaction :: InvTransaction -> Builder Source #

Render an investment transaction. As you might expect, this doesn't check your work. So be careful.

Security Types

parseSecurityType :: Parser SecurityType Source #

Parse a security type.

renderSecurityType :: SecurityType -> Builder Source #

Render a security type.

data Security Source #

The information QIF keeps about a security.

emptySecurity :: Security Source #

An empty security, forlorn and alone, with no name, no ticker, and no goals. Definitely a stock, though.

secName :: Lens' Security Text Source #

The name of the security.

secTicker :: Lens' Security Text Source #

The ticker symbol for the security. If I was a better person this would do some validation on the input.

secType :: Lens' Security SecurityType Source #

The type of security.

secGoal :: Lens' Security (Maybe Text) Source #

The goal for the security. I think this is for things like "Buying a house" or "Saving for college", but I've never actually seen this used in the wild.

parseSecurity :: Parser Security Source #

Parse a security. Performs no validation that the name makes sense, the ticker makes sense, or that the two go together. Good luck with that.

renderSecurity :: Security -> Builder Source #

Render a security. You should probably make sure that your data structure makes sense before you write it, but that's your thing. This function won't judget you.

Fixed-width quantities

type Currency = Fixed E2 Source #

A fixed width implementation of currency, based on the U.S. dollar. Future versions of this library that wish to support other currencies may wish to change this, or to abstract the rest of the library over a currency type.

parseCurrency :: Parser Currency Source #

Parse a currency. This is slightly differentiated from parseQuantity in that it will happily ignore a dollar sign placed in the correct location. Note that this will support negative amounts written as either "-$500" or as "$-500".

renderCurrency :: Bool -> Currency -> Builder Source #

Render a currency. The boolean state whether or not to include a dollar sign. When dollar signs are included, negatives are written as "-$500" rather than "$-500".

type ShareQuantity = Fixed E4 Source #

A fixed-width implementation of quantities for shares. So far, I have seen sites report share quantities to up to four decimal points, henced the value.

parseShareQuantity :: Parser ShareQuantity Source #

Parse a share quantity. Currently an alias for parseQuantity.

renderShareQuantity :: ShareQuantity -> Builder Source #

Render a share quantity. Currently an alias for renderQuantity.

parseQuantity :: HasResolution a => Parser (Fixed a) Source #

Parse a fixed-width number. Should parse negative values, as well. This does support QIF's annoying "5." notation, as well.

renderQuantity :: HasResolution a => Fixed a -> Builder Source #

Render a quantity. As opposed to the parser, this output function will always represent numbers to their full precision.

Old-school dates

parseDate :: Parser Day Source #

Parse a date, using old-school, incredibly unwise, "mmddyy" formats. To simplify my life, this assumes that all dates start in 2000, rather than in 1970 or some other date. Thus, if you have data going back before 2000, you'll need to post-process this to the appropriate date, by subtracting 100 appropriately. Hopefully by 2100 noone will be using QIF anymore, and this won't matter.

renderDate :: Day -> Builder Source #

Render the date in QIF's silly format.