diff --git a/hledger-lib/Hledger/Data/Amount.hs b/hledger-lib/Hledger/Data/Amount.hs index fc521ad21..abd545b89 100644 --- a/hledger-lib/Hledger/Data/Amount.hs +++ b/hledger-lib/Hledger/Data/Amount.hs @@ -221,7 +221,9 @@ quoteCommoditySymbolIfNeeded s data AmountDisplayOpts = AmountDisplayOpts { displayPrice :: Bool -- ^ Whether to display the Price of an Amount. , displayZeroCommodity :: Bool -- ^ If the Amount rounds to 0, whether to display its commodity string. - , displayThousandsSep :: Bool -- ^ Whether to display thousands separators. + , displayThousandsSep :: Bool -- ^ Whether to display digit group marks (eg thousands separators) + , displayAddDecimalMark :: Bool -- ^ Whether to add a trailing decimal mark when there are no decimal digits + -- and there are digit group marks, to disambiguate , displayColour :: Bool -- ^ Whether to colourise negative Amounts. , displayOneLine :: Bool -- ^ Whether to display on one line. , displayMinWidth :: Maybe Int -- ^ Minimum width to pad to @@ -240,6 +242,7 @@ noColour = AmountDisplayOpts { displayPrice = True , displayColour = False , displayZeroCommodity = False , displayThousandsSep = True + , displayAddDecimalMark = False , displayOneLine = False , displayMinWidth = Just 0 , displayMaxWidth = Nothing @@ -645,22 +648,25 @@ showAmount = wbUnpack . showAmountB noColour -- showAmountB :: AmountDisplayOpts -> Amount -> WideBuilder showAmountB _ Amount{acommodity="AUTO"} = mempty -showAmountB opts a@Amount{astyle=style} = +showAmountB + AmountDisplayOpts{displayPrice, displayColour, displayZeroCommodity, + displayThousandsSep, displayAddDecimalMark, displayOrder} + a@Amount{astyle=style} = color $ case ascommodityside style of L -> showC (wbFromText comm) space <> quantity' <> price R -> quantity' <> showC space (wbFromText comm) <> price where - color = if displayColour opts && isNegativeAmount a then colorB Dull Red else id - quantity = showamountquantity $ - if displayThousandsSep opts then a else a{astyle=(astyle a){asdigitgroups=Nothing}} + color = if displayColour && isNegativeAmount a then colorB Dull Red else id + quantity = showAmountQuantity displayAddDecimalMark $ + if displayThousandsSep then a else a{astyle=(astyle a){asdigitgroups=Nothing}} (quantity', comm) - | amountLooksZero a && not (displayZeroCommodity opts) = (WideBuilder (TB.singleton '0') 1, "") + | amountLooksZero a && not displayZeroCommodity = (WideBuilder (TB.singleton '0') 1, "") | otherwise = (quantity, quoteCommoditySymbolIfNeeded $ acommodity a) space = if not (T.null comm) && ascommodityspaced style then WideBuilder (TB.singleton ' ') 1 else mempty -- concatenate these texts, -- or return the empty text if there's a commodity display order. XXX why ? - showC l r = if isJust (displayOrder opts) then mempty else l <> r - price = if displayPrice opts then showAmountPrice a else mempty + showC l r = if isJust displayOrder then mempty else l <> r + price = if displayPrice then showAmountPrice a else mempty -- | Colour version. For a negative amount, adds ANSI codes to change the colour, -- currently to hard-coded red. @@ -691,10 +697,10 @@ showAmountDebug Amount{..} = -- | Get a Text Builder for the string representation of the number part of of an amount, -- using the display settings from its commodity. Also returns the width of the number. --- A special case: if it is showing digit group separators but no decimal places, --- show a decimal mark (with nothing after it) to make it easier to parse correctly. -showamountquantity :: Amount -> WideBuilder -showamountquantity amt@Amount{astyle=AmountStyle{asdecimalmark=mdec, asdigitgroups=mgrps}} = +-- With a true first argument, if there are no decimal digits but there are digit group separators, +-- it shows the amount with a trailing decimal mark to help disambiguate it for parsing. +showAmountQuantity :: Bool -> Amount -> WideBuilder +showAmountQuantity disambiguate amt@Amount{astyle=AmountStyle{asdecimalmark=mdec, asdigitgroups=mgrps}} = signB <> intB <> fracB where Decimal decplaces mantissa = amountRoundedQuantity amt @@ -706,13 +712,13 @@ showamountquantity amt@Amount{astyle=AmountStyle{asdecimalmark=mdec, asdigitgrou (intPart, fracPart) = T.splitAt intLen numtxtwithzero intB = applyDigitGroupStyle mgrps intLen $ if decplaces == 0 then numtxt else intPart signB = if mantissa < 0 then WideBuilder (TB.singleton '-') 1 else mempty - fracB = if decplaces > 0 || isshowingdigitgroupseparator + fracB = if decplaces > 0 || (isshowingdigitgroupseparator && disambiguate) then WideBuilder (TB.singleton dec <> TB.fromText fracPart) (1 + fromIntegral decplaces) else mempty - - isshowingdigitgroupseparator = case mgrps of - Just (DigitGroups _ (rightmostgrplen:_)) -> intLen > fromIntegral rightmostgrplen - _ -> False + where + isshowingdigitgroupseparator = case mgrps of + Just (DigitGroups _ (rightmostgrplen:_)) -> intLen > fromIntegral rightmostgrplen + _ -> False -- | Given an integer as text, and its length, apply the given DigitGroupStyle, -- inserting digit group separators between digit groups where appropriate. diff --git a/hledger-lib/Hledger/Data/Posting.hs b/hledger-lib/Hledger/Data/Posting.hs index 7ee8ca0ac..6b5f0513d 100644 --- a/hledger-lib/Hledger/Data/Posting.hs +++ b/hledger-lib/Hledger/Data/Posting.hs @@ -287,7 +287,10 @@ postingAsLines elideamount onelineamounts acctwidth amtwidth p = -- amtwidth at all. shownAmounts | elideamount = [mempty] - | otherwise = showMixedAmountLinesB noColour{displayZeroCommodity=True, displayOneLine=onelineamounts} $ pamount p + | otherwise = showMixedAmountLinesB displayopts $ pamount p + where displayopts = noColour{ + displayZeroCommodity=True, displayAddDecimalMark=True, displayOneLine=onelineamounts + } thisamtwidth = maximumBound 0 $ map wbWidth shownAmounts -- when there is a balance assertion, show it only on the last posting line diff --git a/hledger/hledger.m4.md b/hledger/hledger.m4.md index f1f443568..2fd1bb96a 100644 --- a/hledger/hledger.m4.md +++ b/hledger/hledger.m4.md @@ -4340,6 +4340,32 @@ file. # PART 3: REPORTING CONCEPTS + +# Amount formatting + +When displaying amounts, digit group marks and decimal marks are +handled a little differently depending on the report and output format +and intended consumer. hledger output falls into three rough categories: + +**1. "hledger-readable output" should be readable by hledger and by humans** + - produced by reports that show full journal entries: `print`, `import`, `close`, `rewrite`.. + - shows amounts with their original journal precisions, which may not be consistent + - adds a trailing decimal mark when needed to disambiguate [ambiguous amounts](decimal-marks-digit-group-marks) + (amounts with one digit group mark and no decimal digits) + - can be parsed reliably + +**2. "human-readable output" - usually for humans** + - produced by all other reports + - shows amounts with standard display precisions, which will be consistent within each commodity + - can show ambiguous amounts + - can be parsed reliably in the context of a known report (because of consistent style) + +**3. "machine-readable output" - usually for other software** + - produced by all reports when an output format like `csv`/`tsv`/`json`/`sql` is selected + - shows no digit group marks + - shows a period decimal mark (.) when there are decimal digits + - can be parsed reliably + # Time periods diff --git a/hledger/test/journal/precision.test b/hledger/test/journal/precision.test index d30c3788f..f6bb1375a 100644 --- a/hledger/test/journal/precision.test +++ b/hledger/test/journal/precision.test @@ -147,3 +147,44 @@ $ hledger -f- print --explicit $ hledger -f- reg 2023-01-01 (a) 1.0 A 1.0 A 2023-01-02 (a) 1.2 A 2.2 A + +# ** 10. print-like reports add a trailing decimal mark, when amounts have digit group marks but no decimal digits. +< +commodity 1.000, JPY + +2023-01-01 + (a) 1 JPY + +2023-01-02 + (b) 1,2 JPY + +2023-01-03 + (c) 1.000 JPY + +$ hledger -f - print +2023-01-01 + (a) 1 JPY + +2023-01-02 + (b) 1,2 JPY + +2023-01-03 + (c) 1.000, JPY + +>= + +# ** 11. Non-print-like reports show all amounts with consistent display precision +$ hledger -f - bal + 1 JPY a + 1 JPY b + 1.000 JPY c +-------------------- + 1.002 JPY + +# ** 12. csv, json and other machine-readable formats show all amounts without digit groups and with period decimal marks. +$ hledger -f - bal -O csv +"account","balance" +"a","1 JPY" +"b","1 JPY" +"c","1000 JPY" +"total","1002 JPY"