diff --git a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/transactions/mt940/Mt942Parser.kt b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/transactions/mt940/Mt942Parser.kt index 1575b468..cc75437c 100644 --- a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/transactions/mt940/Mt942Parser.kt +++ b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/transactions/mt940/Mt942Parser.kt @@ -1,6 +1,5 @@ package net.codinux.banking.fints.transactions.mt940 -import kotlinx.datetime.Instant import net.codinux.banking.fints.log.IMessageLogAppender import net.codinux.banking.fints.transactions.mt940.model.AmountAndCurrency import net.codinux.banking.fints.transactions.mt940.model.InterimAccountStatement @@ -42,9 +41,12 @@ open class Mt942Parser( transactions: List, fieldsByCode: List> ): InterimAccountStatement { - val smallestAmounts = fieldsByCode.filter { it.first.startsWith(SmallestAmountCode) } // should we parse it? i see no use in it - .mapIndexed { index, field -> parseAmountAndCurrency(field.second, index == 0) } - val creationTime = fieldsByCode.first { it.first == CreationTimeCode || it.first.startsWith(CreationTimeStartCode) } // should we parse it? i see no use in it + // also decided against parsing smallest amounts, i don't think they ever going to be used +// val smallestAmounts = fieldsByCode.filter { it.first.startsWith(SmallestAmountCode) } // should we parse it? i see no use in it +// .mapIndexed { index, field -> parseAmountAndCurrency(field.second, index == 0) } + + // decided against parsing creation time as there are so many non specification confirm time formats that parsing is likely to fail 'cause of this unused value +// val creationTime = parseDateTime(fieldsByCode.first { it.first == CreationTimeCode || it.first.startsWith(CreationTimeStartCode) }.second) val numberAndTotalOfDebitPostings = fieldsByCode.firstOrNull { it.first.equals(AmountOfDebitPostingsCode) } ?.let { parseNumberAndTotalOfPostings(it.second) } @@ -55,9 +57,6 @@ open class Mt942Parser( orderReferenceNumber, referenceNumber, bankCodeBicOrIban, accountIdentifier, statementNumber, sheetNumber, - smallestAmounts.first(), - if (smallestAmounts.size > 1) smallestAmounts[1] else null, - Instant.DISTANT_PAST, transactions, numberAndTotalOfDebitPostings, numberAndTotalOfCreditPostings diff --git a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/transactions/mt940/Mt94xParserBase.kt b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/transactions/mt940/Mt94xParserBase.kt index ea3dffd4..5071f9b4 100644 --- a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/transactions/mt940/Mt94xParserBase.kt +++ b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/transactions/mt940/Mt94xParserBase.kt @@ -1,7 +1,7 @@ package net.codinux.banking.fints.transactions.mt940 -import kotlinx.datetime.LocalDate -import kotlinx.datetime.Month +import kotlinx.datetime.* +import net.codinux.banking.fints.extensions.EuropeBerlin import net.codinux.log.logger import net.codinux.banking.fints.log.IMessageLogAppender import net.codinux.banking.fints.model.Amount @@ -469,7 +469,7 @@ abstract class Mt94xParserBase( } - protected open fun parseMt940Date(dateString: String): LocalDate { + open fun parseMt940Date(dateString: String): LocalDate { // TODO: this should be necessary anymore, isn't it? // SimpleDateFormat is not thread-safe. Before adding another library i decided to parse @@ -526,6 +526,30 @@ abstract class Mt94xParserBase( return bookingDate } + open fun parseTime(timeString: String): LocalTime { + val hour = timeString.substring(0, 2).toInt() + val minute = timeString.substring(2, 4).toInt() + + return LocalTime(hour, minute) + } + + open fun parseDateTime(dateTimeString: String): Instant { + val date = parseMt940Date(dateTimeString.substring(0, 6)) + + val time = parseTime(dateTimeString.substring(6, 10)) + + val dateTime = LocalDateTime(date, time) + + return if (dateTimeString.length == 15) { // actually mandatory, but by far not always stated: the time zone + val plus = dateTimeString[10] == '+' + val timeDifference = parseTime(dateTimeString.substring(11)) + + dateTime.toInstant(UtcOffset(if (plus) timeDifference.hour else timeDifference.hour * -1, timeDifference.minute)) + } else { // we then assume the server states the DateTime in FinTS's default time zone, Europe/Berlin + dateTime.toInstant(TimeZone.EuropeBerlin) + } + } + protected open fun parseAmount(amountString: String): Amount { return Amount(amountString) } diff --git a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/transactions/mt940/model/InterimAccountStatement.kt b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/transactions/mt940/model/InterimAccountStatement.kt index 9854af65..a1509b9f 100644 --- a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/transactions/mt940/model/InterimAccountStatement.kt +++ b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/transactions/mt940/model/InterimAccountStatement.kt @@ -1,7 +1,5 @@ package net.codinux.banking.fints.transactions.mt940.model -import kotlinx.datetime.Instant - open class InterimAccountStatement( orderReferenceNumber: String, referenceNumber: String?, @@ -12,11 +10,12 @@ open class InterimAccountStatement( statementNumber: Int, sheetNumber: Int?, - val smallestAmountOfReportedTransactions: AmountAndCurrency, - - val smallestAmountOfReportedCreditTransactions: AmountAndCurrency? = null, - - val creationTime: Instant, + // decided against parsing them, see Mt942Parser +// val smallestAmountOfReportedTransactions: AmountAndCurrency, +// +// val smallestAmountOfReportedCreditTransactions: AmountAndCurrency? = null, +// +// val creationTime: Instant, transactions: List, @@ -27,11 +26,11 @@ open class InterimAccountStatement( ) : AccountStatementCommon(orderReferenceNumber, referenceNumber, bankCodeBicOrIban, accountIdentifier, statementNumber, sheetNumber, transactions) { // for object deserializers - private constructor() : this("", "", "", null, 0, null, AmountAndCurrency(), null, Instant.DISTANT_PAST, listOf()) + private constructor() : this("", "", "", null, 0, null, listOf()) override fun toString(): String { - return "$smallestAmountOfReportedTransactions ${super.toString()}" + return "${amountAndTotalOfDebitPostings?.amount} ${super.toString()}" } } \ No newline at end of file diff --git a/fints4k/src/commonTest/kotlin/net/codinux/banking/fints/transactions/Mt942ParserTest.kt b/fints4k/src/commonTest/kotlin/net/codinux/banking/fints/transactions/Mt942ParserTest.kt index 492913a5..e6b65302 100644 --- a/fints4k/src/commonTest/kotlin/net/codinux/banking/fints/transactions/Mt942ParserTest.kt +++ b/fints4k/src/commonTest/kotlin/net/codinux/banking/fints/transactions/Mt942ParserTest.kt @@ -103,13 +103,13 @@ class Mt942ParserTest { assertEquals(5, statement.statementNumber) assertEquals(1, statement.sheetNumber) - assertEquals("20,50", statement.smallestAmountOfReportedTransactions.amount) - assertEquals("EUR", statement.smallestAmountOfReportedTransactions.currency) - assertEquals(false, statement.smallestAmountOfReportedTransactions.isCredit) - - assertEquals("155,34", statement.smallestAmountOfReportedCreditTransactions?.amount) - assertEquals("EUR", statement.smallestAmountOfReportedCreditTransactions?.currency) - assertEquals(true, statement.smallestAmountOfReportedCreditTransactions?.isCredit) +// assertEquals("20,50", statement.smallestAmountOfReportedTransactions.amount) +// assertEquals("EUR", statement.smallestAmountOfReportedTransactions.currency) +// assertEquals(false, statement.smallestAmountOfReportedTransactions.isCredit) +// +// assertEquals("155,34", statement.smallestAmountOfReportedCreditTransactions?.amount) +// assertEquals("EUR", statement.smallestAmountOfReportedCreditTransactions?.currency) +// assertEquals(true, statement.smallestAmountOfReportedCreditTransactions?.isCredit) assertEquals(1, statement.amountAndTotalOfDebitPostings?.numberOfPostings) assertEquals("20,50", statement.amountAndTotalOfDebitPostings?.amount) @@ -167,10 +167,10 @@ class Mt942ParserTest { assertEquals(0, statement.statementNumber) assertEquals(1, statement.sheetNumber) - assertEquals("60,77", statement.smallestAmountOfReportedTransactions.amount) - assertEquals("EUR", statement.smallestAmountOfReportedTransactions.currency) - assertEquals(false, statement.smallestAmountOfReportedTransactions.isCredit) - assertNull(statement.smallestAmountOfReportedCreditTransactions) +// assertEquals("60,77", statement.smallestAmountOfReportedTransactions.amount) +// assertEquals("EUR", statement.smallestAmountOfReportedTransactions.currency) +// assertEquals(false, statement.smallestAmountOfReportedTransactions.isCredit) +// assertNull(statement.smallestAmountOfReportedCreditTransactions) assertEquals(1, statement.amountAndTotalOfDebitPostings?.numberOfPostings) assertEquals("60,77", statement.amountAndTotalOfDebitPostings?.amount) @@ -199,8 +199,8 @@ class Mt942ParserTest { assertEquals(0, statement.statementNumber) assertEquals(1, statement.sheetNumber) - assertEquals("0,", statement.smallestAmountOfReportedTransactions.amount) - assertEquals("EUR", statement.smallestAmountOfReportedTransactions.currency) +// assertEquals("0,", statement.smallestAmountOfReportedTransactions.amount) +// assertEquals("EUR", statement.smallestAmountOfReportedTransactions.currency) assertSize(0, statement.transactions) diff --git a/fints4k/src/commonTest/kotlin/net/codinux/banking/fints/transactions/Mt94xParserBaseTest.kt b/fints4k/src/commonTest/kotlin/net/codinux/banking/fints/transactions/Mt94xParserBaseTest.kt new file mode 100644 index 00000000..04703919 --- /dev/null +++ b/fints4k/src/commonTest/kotlin/net/codinux/banking/fints/transactions/Mt94xParserBaseTest.kt @@ -0,0 +1,59 @@ +package net.codinux.banking.fints.transactions + +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime +import net.codinux.banking.fints.extensions.EuropeBerlin +import net.codinux.banking.fints.test.assertEquals +import net.codinux.banking.fints.transactions.mt940.Mt94xParserBase +import net.codinux.banking.fints.transactions.mt940.model.AccountStatement +import net.codinux.banking.fints.transactions.mt940.model.Transaction +import kotlin.test.Test + +class Mt94xParserBaseTest { + + private val underTest = object : Mt94xParserBase() { + override fun createAccountStatement(orderReferenceNumber: String, referenceNumber: String?, bankCodeBicOrIban: String, accountIdentifier: String?, statementNumber: Int, sheetNumber: Int?, transactions: List, fieldsByCode: List>): AccountStatement { + throw IllegalStateException("We are testing base functionality, not parsing (Interim)AccountStatements") + } + } + + + @Test + fun parseDateTimeWithTimeZoneUtc() { + val result = underTest.parseDateTime("1311130945+0000") + + val resultAtEuropeBerlin = result.toLocalDateTime(TimeZone.EuropeBerlin) + + assertEquals(LocalDateTime(2013, 11, 13, 10, 45), resultAtEuropeBerlin) + } + + @Test + fun parseDateTimeWithTimeZoneEuropeBerlin() { + val result = underTest.parseDateTime("2408210742+0200") + + val resultAtEuropeBerlin = result.toLocalDateTime(TimeZone.EuropeBerlin) + + assertEquals(LocalDateTime(2024, 8, 21, 7, 42), resultAtEuropeBerlin) + } + + @Test + fun parseDateTimeWithoutTimeZone() { // actually the time zone is mandatory, but by far not all banks add it + val result = underTest.parseDateTime("2408232156") + + val resultAtEuropeBerlin = result.toLocalDateTime(TimeZone.EuropeBerlin) + + assertEquals(LocalDateTime(2024, 8, 23, 21, 56), resultAtEuropeBerlin) + } + +// @Test +// fun parseDateTimeStartingWithCharacter() { +// // really don't know where's the 'C' at the start is coming from, but this is an example from DFÜ-Abkommen PDF, p. 674 +// val result = underTest.parseDateTime("C1311130945+0000") +// +// val resultAtEuropeBerlin = result.toLocalDateTime(TimeZone.EuropeBerlin) +// +// assertEquals(LocalDateTime(2024, 8, 23, 21, 56), resultAtEuropeBerlin) +// } + +} \ No newline at end of file