Decided against parsing Mt942 creationTime and smallest amount (should rarely be used and may only cause parsing errors)

This commit is contained in:
dankito 2024-09-11 00:19:45 +02:00
parent 90a7543641
commit 7ddeb88475
5 changed files with 113 additions and 32 deletions

View File

@ -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<Transaction>,
fieldsByCode: List<Pair<String, String>>
): 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

View File

@ -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<T: AccountStatementCommon>(
}
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<T: AccountStatementCommon>(
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)
}

View File

@ -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<Transaction>,
@ -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()}"
}
}

View File

@ -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)

View File

@ -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<AccountStatement>() {
override fun createAccountStatement(orderReferenceNumber: String, referenceNumber: String?, bankCodeBicOrIban: String, accountIdentifier: String?, statementNumber: Int, sheetNumber: Int?, transactions: List<Transaction>, fieldsByCode: List<Pair<String, String>>): 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)
// }
}