From 35daa092de9467cf26f28e5e16e2b95e596d1ce6 Mon Sep 17 00:00:00 2001
From: Robert Goldmann <deadlocker@gmx.de>
Date: Mon, 13 Feb 2023 23:08:11 +0100
Subject: [PATCH] #731 - amount parser should respect grouping separators

---
 .../TransactionImportService.java             |  2 +-
 .../transactions/csvimport/AmountParser.java  | 28 ++++--
 .../csvimport/AmountParserTest.java           | 98 ++++++++++++++-----
 3 files changed, 95 insertions(+), 33 deletions(-)

diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionImportService.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionImportService.java
index eaf3ecde7..cc7ddc647 100644
--- a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionImportService.java
+++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionImportService.java
@@ -41,7 +41,7 @@ public class TransactionImportService
 		final String description = csvRow.getColumns().get(csvColumnSettings.columnDescription() - 1);
 
 		final String amount = csvRow.getColumns().get(csvColumnSettings.columnAmount() - 1);
-		final Optional<Integer> parsedAmountOptional = AmountParser.parse(amount);
+		final Optional<Integer> parsedAmountOptional = AmountParser.parse(amount, '.', ',');
 		if(parsedAmountOptional.isEmpty())
 		{
 			throw new CsvTransactionParseException(Localization.getString("transactions.import.error.parse.amount", index + 1));
diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/csvimport/AmountParser.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/csvimport/AmountParser.java
index 2d87372d5..4b3ef7fdb 100644
--- a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/csvimport/AmountParser.java
+++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/csvimport/AmountParser.java
@@ -1,19 +1,22 @@
 package de.deadlocker8.budgetmaster.transactions.csvimport;
 
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
 import java.text.MessageFormat;
+import java.text.ParseException;
 import java.util.Optional;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 public class AmountParser
 {
-	private static final Pattern PATTERN_AMOUNT = Pattern.compile("^\\s*([-+]?)\\s*(\\d+(,\\d+)?(\\.\\d+)?)");
+	private static final Pattern PATTERN_AMOUNT = Pattern.compile("^\\s*([-+]?)\\s*(\\d+(.*\\d+)?)");
 
 	private AmountParser()
 	{
 	}
 
-	public static Optional<Integer> parse(String amountString)
+	public static Optional<Integer> parse(String amountString, char decimalSeparator, char groupingSeparator)
 	{
 		if(amountString == null)
 		{
@@ -24,17 +27,28 @@ public class AmountParser
 		boolean matchFound = matcher.find();
 		if(matchFound)
 		{
-			final String sign = matcher.group(1);
-			String amount = matcher.group(2);
-			amount = amount.replace(',', '.');
+			String sign = matcher.group(1);
+			if(sign.equals("+"))
+			{
+				sign = "";
+			}
+
+			final String amount = matcher.group(2);
+
+			final DecimalFormat decimalFormat = new DecimalFormat();
+			final DecimalFormatSymbols symbols = new DecimalFormatSymbols();
+			symbols.setDecimalSeparator(decimalSeparator);
+			symbols.setGroupingSeparator(groupingSeparator);
+			decimalFormat.setNegativePrefix("-");
+			decimalFormat.setDecimalFormatSymbols(symbols);
 
 			final String parseableString = MessageFormat.format("{0}{1}", sign, amount);
 			try
 			{
-				final double parseDouble = Double.parseDouble(parseableString);
+				final double parseDouble = decimalFormat.parse(parseableString).doubleValue();
 				return Optional.of((int) (parseDouble * 100));
 			}
-			catch(NumberFormatException e)
+			catch(ParseException e)
 			{
 				return Optional.empty();
 			}
diff --git a/BudgetMasterServer/src/test/java/de/deadlocker8/budgetmaster/unit/transaction/csvimport/AmountParserTest.java b/BudgetMasterServer/src/test/java/de/deadlocker8/budgetmaster/unit/transaction/csvimport/AmountParserTest.java
index 5fdf610d8..ed074cb52 100644
--- a/BudgetMasterServer/src/test/java/de/deadlocker8/budgetmaster/unit/transaction/csvimport/AmountParserTest.java
+++ b/BudgetMasterServer/src/test/java/de/deadlocker8/budgetmaster/unit/transaction/csvimport/AmountParserTest.java
@@ -13,7 +13,7 @@ class AmountParserTest
 	@Test
 	void test_dot_positive_noCurrency()
 	{
-		assertThat(AmountParser.parse("12.03"))
+		assertThat(AmountParser.parse("12.03", '.', ','))
 				.isPresent()
 				.get().isEqualTo(1203);
 	}
@@ -21,7 +21,7 @@ class AmountParserTest
 	@Test
 	void test_dot_negative_noCurrency()
 	{
-		assertThat(AmountParser.parse("-18.41"))
+		assertThat(AmountParser.parse("-18.41", '.', ','))
 				.isPresent()
 				.get().isEqualTo(-1841);
 	}
@@ -29,7 +29,7 @@ class AmountParserTest
 	@Test
 	void test_dot_negativeWithSpace_noCurrency()
 	{
-		assertThat(AmountParser.parse("- 200.30"))
+		assertThat(AmountParser.parse("- 200.30", '.', ','))
 				.isPresent()
 				.get().isEqualTo(-20030);
 	}
@@ -37,7 +37,7 @@ class AmountParserTest
 	@Test
 	void test_dot_positive_currency()
 	{
-		assertThat(AmountParser.parse("12.03 €"))
+		assertThat(AmountParser.parse("12.03 €", '.', ','))
 				.isPresent()
 				.get().isEqualTo(1203);
 	}
@@ -45,7 +45,7 @@ class AmountParserTest
 	@Test
 	void test_dot_negative_currency()
 	{
-		assertThat(AmountParser.parse("-18.41€"))
+		assertThat(AmountParser.parse("-18.41€", '.', ','))
 				.isPresent()
 				.get().isEqualTo(-1841);
 	}
@@ -53,7 +53,7 @@ class AmountParserTest
 	@Test
 	void test_dot_positiveWithSign_noCurrency()
 	{
-		assertThat(AmountParser.parse("+12.03"))
+		assertThat(AmountParser.parse("+12.03", '.', ','))
 				.isPresent()
 				.get().isEqualTo(1203);
 	}
@@ -61,7 +61,7 @@ class AmountParserTest
 	@Test
 	void test_dot_positiveWithSignWithSpace_noCurrency()
 	{
-		assertThat(AmountParser.parse("+ 12.03"))
+		assertThat(AmountParser.parse("+ 12.03", '.', ','))
 				.isPresent()
 				.get().isEqualTo(1203);
 	}
@@ -69,7 +69,7 @@ class AmountParserTest
 	@Test
 	void test_comma_positive_noCurrency()
 	{
-		assertThat(AmountParser.parse("12,03"))
+		assertThat(AmountParser.parse("12,03", ',', '.'))
 				.isPresent()
 				.get().isEqualTo(1203);
 	}
@@ -77,7 +77,7 @@ class AmountParserTest
 	@Test
 	void test_comma_negative_noCurrency()
 	{
-		assertThat(AmountParser.parse("-18,41"))
+		assertThat(AmountParser.parse("-18,41", ',', '.'))
 				.isPresent()
 				.get().isEqualTo(-1841);
 	}
@@ -85,7 +85,7 @@ class AmountParserTest
 	@Test
 	void test_comma_negativeWithSpace_noCurrency()
 	{
-		assertThat(AmountParser.parse("- 200,30"))
+		assertThat(AmountParser.parse("- 200,30", ',', '.'))
 				.isPresent()
 				.get().isEqualTo(-20030);
 	}
@@ -93,7 +93,7 @@ class AmountParserTest
 	@Test
 	void test_comma_positive_currency()
 	{
-		assertThat(AmountParser.parse("12,03 €"))
+		assertThat(AmountParser.parse("12,03 €", ',', '.'))
 				.isPresent()
 				.get().isEqualTo(1203);
 	}
@@ -101,7 +101,7 @@ class AmountParserTest
 	@Test
 	void test_comma_negative_currency()
 	{
-		assertThat(AmountParser.parse("-18,41€"))
+		assertThat(AmountParser.parse("-18,41€", ',', '.'))
 				.isPresent()
 				.get().isEqualTo(-1841);
 	}
@@ -109,7 +109,7 @@ class AmountParserTest
 	@Test
 	void test_comma_positiveWithSign_noCurrency()
 	{
-		assertThat(AmountParser.parse("+12,03"))
+		assertThat(AmountParser.parse("+12,03", ',', '.'))
 				.isPresent()
 				.get().isEqualTo(1203);
 	}
@@ -117,7 +117,7 @@ class AmountParserTest
 	@Test
 	void test_comma_positiveWithSignWithSpace_noCurrency()
 	{
-		assertThat(AmountParser.parse("+12,03"))
+		assertThat(AmountParser.parse("+12,03", ',', '.'))
 				.isPresent()
 				.get().isEqualTo(1203);
 	}
@@ -125,35 +125,35 @@ class AmountParserTest
 	@Test
 	void test_invalid_null()
 	{
-		assertThat(AmountParser.parse(null))
+		assertThat(AmountParser.parse(null, '.', ','))
 				.isEmpty();
 	}
 
 	@Test
 	void test_invalid_empty()
 	{
-		assertThat(AmountParser.parse(""))
+		assertThat(AmountParser.parse("", '.', ','))
 				.isEmpty();
 	}
 
 	@Test
 	void test_invalid_empty2()
 	{
-		assertThat(AmountParser.parse("    "))
+		assertThat(AmountParser.parse("    ", '.', ','))
 				.isEmpty();
 	}
 
 	@Test
 	void test_invalid()
 	{
-		assertThat(AmountParser.parse("abc.42€"))
+		assertThat(AmountParser.parse("abc.42€", '.', ','))
 				.isEmpty();
 	}
 
 	@Test
 	void test_integer_positive_noCurrency()
 	{
-		assertThat(AmountParser.parse("12"))
+		assertThat(AmountParser.parse("12", '.', ','))
 				.isPresent()
 				.get().isEqualTo(1200);
 	}
@@ -161,7 +161,7 @@ class AmountParserTest
 	@Test
 	void test_integer_negative_noCurrency()
 	{
-		assertThat(AmountParser.parse("-18"))
+		assertThat(AmountParser.parse("-18", '.', ','))
 				.isPresent()
 				.get().isEqualTo(-1800);
 	}
@@ -169,7 +169,7 @@ class AmountParserTest
 	@Test
 	void test_integer_negativeWithSpace_noCurrency()
 	{
-		assertThat(AmountParser.parse("- 200"))
+		assertThat(AmountParser.parse("- 200", '.', ','))
 				.isPresent()
 				.get().isEqualTo(-20000);
 	}
@@ -177,7 +177,7 @@ class AmountParserTest
 	@Test
 	void test_integer_positive_currency()
 	{
-		assertThat(AmountParser.parse("12 €"))
+		assertThat(AmountParser.parse("12 €", '.', ','))
 				.isPresent()
 				.get().isEqualTo(1200);
 	}
@@ -185,7 +185,7 @@ class AmountParserTest
 	@Test
 	void test_integer_negative_currency()
 	{
-		assertThat(AmountParser.parse("-18€"))
+		assertThat(AmountParser.parse("-18€", '.', ','))
 				.isPresent()
 				.get().isEqualTo(-1800);
 	}
@@ -193,7 +193,7 @@ class AmountParserTest
 	@Test
 	void test_integer_positiveWithSign_noCurrency()
 	{
-		assertThat(AmountParser.parse("+12"))
+		assertThat(AmountParser.parse("+12", '.', ','))
 				.isPresent()
 				.get().isEqualTo(1200);
 	}
@@ -201,8 +201,56 @@ class AmountParserTest
 	@Test
 	void test_integer_positiveWithSignWithSpace_noCurrency()
 	{
-		assertThat(AmountParser.parse("+ 12"))
+		assertThat(AmountParser.parse("+ 12", '.', ','))
 				.isPresent()
 				.get().isEqualTo(1200);
 	}
+
+	@Test
+	void test_thousandsDelimiter_integer()
+	{
+		assertThat(AmountParser.parse("1,234", '.', ','))
+				.isPresent()
+				.get().isEqualTo(123400);
+	}
+
+	@Test
+	void test_thousandsDelimiter_dot()
+	{
+		assertThat(AmountParser.parse("1,234.03", '.', ','))
+				.isPresent()
+				.get().isEqualTo(123403);
+	}
+
+	@Test
+	void test_thousandsDelimiter_withCurrency()
+	{
+		assertThat(AmountParser.parse("1,234.03 €", '.', ','))
+				.isPresent()
+				.get().isEqualTo(123403);
+	}
+
+	@Test
+	void test_thousandsDelimiter_negative()
+	{
+		assertThat(AmountParser.parse("-1,234.03 €", '.', ','))
+				.isPresent()
+				.get().isEqualTo(-123403);
+	}
+
+	@Test
+	void test_thousandsDelimiter_specialDelimiter()
+	{
+		assertThat(AmountParser.parse("-1.234,03 €", ',', '.'))
+				.isPresent()
+				.get().isEqualTo(-123403);
+	}
+
+	@Test
+	void test_thousandsDelimiter_bigNumber()
+	{
+		assertThat(AmountParser.parse("-1.234.567,03 €", ',', '.'))
+				.isPresent()
+				.get().isEqualTo(-123456703);
+	}
 }
-- 
GitLab