diff --git a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/log/IMessageLogAppender.kt b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/log/IMessageLogAppender.kt index 28a12ace..8d30aa7d 100644 --- a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/log/IMessageLogAppender.kt +++ b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/log/IMessageLogAppender.kt @@ -5,6 +5,6 @@ import kotlin.reflect.KClass interface IMessageLogAppender { - fun logError(loggingClass: KClass<*>, message: String, e: Exception? = null) + fun logError(loggingClass: KClass<*>, message: String, e: Throwable? = null) } \ No newline at end of file diff --git a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/log/MessageLogCollector.kt b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/log/MessageLogCollector.kt index 159afb04..8ed3a84e 100644 --- a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/log/MessageLogCollector.kt +++ b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/log/MessageLogCollector.kt @@ -69,7 +69,7 @@ open class MessageLogCollector( addMessageLogEntry(type, context, messageTrace, prettyPrintMessage, null, parsedSegments) } - open fun logError(loggingClass: KClass<*>, message: String, context: MessageContext, e: Exception? = null) { + open fun logError(loggingClass: KClass<*>, message: String, context: MessageContext, e: Throwable? = null) { val type = MessageLogEntryType.Error val messageTrace = createMessageTraceString(type, context) diff --git a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/mapper/DateFormatter.kt b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/mapper/DateFormatter.kt deleted file mode 100644 index ea44c27c..00000000 --- a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/mapper/DateFormatter.kt +++ /dev/null @@ -1,12 +0,0 @@ -package net.codinux.banking.fints.mapper - -import kotlinx.datetime.LocalDate - -/** - * Be aware that Java DateFormat is not thread safe! - */ -expect class DateFormatter constructor(pattern: String) { - - fun parseDate(dateString: String): LocalDate? - -} \ No newline at end of file diff --git a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/JobContext.kt b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/JobContext.kt index a61ea76b..eb16eadf 100644 --- a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/JobContext.kt +++ b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/JobContext.kt @@ -82,7 +82,7 @@ open class JobContext( messageLogCollector.addMessageLog(type, message, createMessageContext(), parsedSegments) } - override fun logError(loggingClass: KClass<*>, message: String, e: Exception?) { + override fun logError(loggingClass: KClass<*>, message: String, e: Throwable?) { messageLogCollector.logError(loggingClass, message, createMessageContext(), e) } 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 5071f9b4..88b836f9 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 @@ -6,7 +6,6 @@ import net.codinux.log.logger import net.codinux.banking.fints.log.IMessageLogAppender import net.codinux.banking.fints.model.Amount import net.codinux.banking.fints.transactions.mt940.model.* -import net.codinux.banking.fints.mapper.DateFormatter /* @@ -66,8 +65,6 @@ abstract class Mt94xParserBase( const val AmountOfCreditPostingsCode = "90C" - val DateFormatter = DateFormatter("yyMMdd") // TODO: replace with LocalDate.Format { } - val CreditDebitCancellationRegex = Regex("C|D|RC|RD") val AmountRegex = Regex("\\d+,\\d*") @@ -215,7 +212,7 @@ abstract class Mt94xParserBase( val isDebit = fieldValue.startsWith("D") val bookingDateString = fieldValue.substring(1, 7) - val statementDate = parseMt940Date(bookingDateString) + val statementDate = parseDate(bookingDateString) val currency = fieldValue.substring(7, 10) val amountString = fieldValue.substring(10) val amount = parseAmount(amountString) @@ -259,7 +256,7 @@ abstract class Mt94xParserBase( */ protected open fun parseStatementLine(fieldValue: String): StatementLine { val valueDateString = fieldValue.substring(0, 6) - val valueDate = parseMt940Date(valueDateString) + val valueDate = parseDate(valueDateString) val creditMarkMatchResult = CreditDebitCancellationRegex.find(fieldValue) val isDebit = creditMarkMatchResult?.value?.endsWith('D') == true @@ -469,58 +466,51 @@ abstract class Mt94xParserBase( } - open fun parseMt940Date(dateString: String): LocalDate { - // TODO: this should be necessary anymore, isn't it? + open fun parseDate(dateString: String): LocalDate { + try { + var year = dateString.substring(0, 2).toInt() + val month = dateString.substring(2, 4).toInt() + val day = dateString.substring(4, 6).toInt() - // SimpleDateFormat is not thread-safe. Before adding another library i decided to parse - // this really simple date format on my own - if (dateString.length == 6) { - try { - var year = dateString.substring(0, 2).toInt() - val month = dateString.substring(2, 4).toInt() - val day = dateString.substring(4, 6).toInt() - - /** - * Bei 6-stelligen Datumsangaben (d.h. JJMMTT) wird gemäß SWIFT zwischen dem 20. und 21. - * Jahrhundert wie folgt unterschieden: - * - Ist das Jahr (d.h. JJ) größer als 79, bezieht sich das Datum auf das 20. Jahrhundert. Ist - * das Jahr 79 oder kleiner, bezieht sich das Datum auf das 21. Jahrhundert. - * - Ist JJ > 79:JJMMTT = 19JJMMTT - * - sonst: JJMMTT = 20JJMMTT - * - Damit reicht die Spanne des sechsstelligen Datums von 1980 bis 2079. - */ - if (year > 79) { - year += 1900 - } else { - year += 2000 - } - - // ah, here we go, banks (in Germany) calculate with 30 days each month, so yes, it can happen that dates - // like 30th of February or 29th of February in non-leap years occur, see: - // https://de.m.wikipedia.org/wiki/30._Februar#30._Februar_in_der_Zinsberechnung - if (month == 2 && (day > 29 || (day > 28 && year % 4 != 0))) { // fix that for banks each month has 30 days - return LocalDate(year, 3, 1) - } - - return LocalDate(year , month, day) - } catch (e: Exception) { - logError("Could not parse dateString '$dateString'", e) + /** + * Bei 6-stelligen Datumsangaben (d.h. JJMMTT) wird gemäß SWIFT zwischen dem 20. und 21. + * Jahrhundert wie folgt unterschieden: + * - Ist das Jahr (d.h. JJ) größer als 79, bezieht sich das Datum auf das 20. Jahrhundert. Ist + * das Jahr 79 oder kleiner, bezieht sich das Datum auf das 21. Jahrhundert. + * - Ist JJ > 79:JJMMTT = 19JJMMTT + * - sonst: JJMMTT = 20JJMMTT + * - Damit reicht die Spanne des sechsstelligen Datums von 1980 bis 2079. + */ + if (year > 79) { + year += 1900 + } else { + year += 2000 } - } - return DateFormatter.parseDate(dateString)!! // fallback to not thread-safe SimpleDateFormat. Works in most cases but not all + // ah, here we go, banks (in Germany) calculate with 30 days each month, so yes, it can happen that dates + // like 30th of February or 29th of February in non-leap years occur, see: + // https://de.m.wikipedia.org/wiki/30._Februar#30._Februar_in_der_Zinsberechnung + if (month == 2 && (day > 29 || (day > 28 && year % 4 != 0))) { // fix that for banks each month has 30 days + return LocalDate(year, 3, 1) + } + + return LocalDate(year , month, day) + } catch (e: Throwable) { + logError("Could not parse dateString '$dateString'", e) + throw e + } } /** * Booking date string consists only of MMDD -> we need to take the year from value date string. */ protected open fun parseMt940BookingDate(bookingDateString: String, valueDateString: String, valueDate: LocalDate): LocalDate { - val bookingDate = parseMt940Date(valueDateString.substring(0, 2) + bookingDateString) + val bookingDate = parseDate(valueDateString.substring(0, 2) + bookingDateString) // there are rare cases that booking date is e.g. on 31.12.2019 and value date on 01.01.2020 -> booking date would be on 31.12.2020 (and therefore in the future) val bookingDateMonth = bookingDate.month if (bookingDateMonth != valueDate.month && bookingDateMonth == Month.DECEMBER) { - return parseMt940Date("" + (valueDate.year - 1 - 2000) + bookingDateString) + return parseDate("" + (valueDate.year - 1 - 2000) + bookingDateString) } return bookingDate @@ -534,7 +524,7 @@ abstract class Mt94xParserBase( } open fun parseDateTime(dateTimeString: String): Instant { - val date = parseMt940Date(dateTimeString.substring(0, 6)) + val date = parseDate(dateTimeString.substring(0, 6)) val time = parseTime(dateTimeString.substring(6, 10)) @@ -555,10 +545,8 @@ abstract class Mt94xParserBase( } - protected open fun logError(message: String, e: Exception?) { - logAppender?.let { logAppender -> - logAppender.logError(Mt94xParserBase::class, message, e) - } + protected open fun logError(message: String, e: Throwable?) { + logAppender?.logError(Mt94xParserBase::class, message, e) ?: run { log.error(e) { message } } diff --git a/fints4k/src/commonTest/kotlin/net/codinux/banking/fints/transactions/Mt940ParserTest.kt b/fints4k/src/commonTest/kotlin/net/codinux/banking/fints/transactions/Mt940ParserTest.kt index 170ad529..2c041cc9 100644 --- a/fints4k/src/commonTest/kotlin/net/codinux/banking/fints/transactions/Mt940ParserTest.kt +++ b/fints4k/src/commonTest/kotlin/net/codinux/banking/fints/transactions/Mt940ParserTest.kt @@ -15,8 +15,8 @@ import kotlin.test.assertContains class Mt940ParserTest : MtParserTestBase() { private val underTest = object : Mt940Parser() { - public override fun parseMt940Date(dateString: String): LocalDate { - return super.parseMt940Date(dateString) + public override fun parseDate(dateString: String): LocalDate { + return super.parseDate(dateString) } } @@ -229,28 +229,28 @@ class Mt940ParserTest : MtParserTestBase() { @Test fun parseDate() { - val result = underTest.parseMt940Date("240507") + val result = underTest.parseDate("240507") assertEquals(LocalDate(2024, 5, 7), result) } @Test fun parseDateBeforeYear2000() { - val result = underTest.parseMt940Date("990507") + val result = underTest.parseDate("990507") assertEquals(LocalDate(1999, 5, 7), result) } @Test fun parseDate_FixSparkasse29thOFFebruaryInNonLeapYearBug() { - val result = underTest.parseMt940Date("230229") + val result = underTest.parseDate("230229") assertEquals(LocalDate(2023, 3, 1), result) } @Test fun parseDate_FixSparkasse30thOfFebruaryBug() { - val result = underTest.parseMt940Date("230229") + val result = underTest.parseDate("230229") assertEquals(LocalDate(2023, 3, 1), result) } diff --git a/fints4k/src/jsMain/kotlin/net/codinux/banking/fints/mapper/DateFormatter.kt b/fints4k/src/jsMain/kotlin/net/codinux/banking/fints/mapper/DateFormatter.kt deleted file mode 100644 index 51e9b61b..00000000 --- a/fints4k/src/jsMain/kotlin/net/codinux/banking/fints/mapper/DateFormatter.kt +++ /dev/null @@ -1,11 +0,0 @@ -package net.codinux.banking.fints.mapper - -import kotlinx.datetime.LocalDate - -actual class DateFormatter actual constructor(pattern: String) { - - actual fun parseDate(dateString: String): LocalDate? { - return null // is only used in rare cases, don't implement right now - } - -} \ No newline at end of file diff --git a/fints4k/src/jvmMain/kotlin/net/codinux/banking/fints/mapper/DateFormatter.kt b/fints4k/src/jvmMain/kotlin/net/codinux/banking/fints/mapper/DateFormatter.kt deleted file mode 100644 index e1b56e7b..00000000 --- a/fints4k/src/jvmMain/kotlin/net/codinux/banking/fints/mapper/DateFormatter.kt +++ /dev/null @@ -1,16 +0,0 @@ -package net.codinux.banking.fints.mapper - -import kotlinx.datetime.LocalDate -import kotlinx.datetime.toKotlinLocalDate -import java.time.format.DateTimeFormatter - -actual class DateFormatter actual constructor(pattern: String) { - - private val formatter = DateTimeFormatter.ofPattern(pattern) - - - actual fun parseDate(dateString: String): LocalDate? { - return java.time.LocalDate.parse(dateString, formatter)?.toKotlinLocalDate() - } - -} \ No newline at end of file diff --git a/fints4k/src/nativeMain/kotlin/net/codinux/banking/fints/mapper/DateFormatter.kt b/fints4k/src/nativeMain/kotlin/net/codinux/banking/fints/mapper/DateFormatter.kt deleted file mode 100644 index bd2cbc39..00000000 --- a/fints4k/src/nativeMain/kotlin/net/codinux/banking/fints/mapper/DateFormatter.kt +++ /dev/null @@ -1,11 +0,0 @@ -package net.codinux.banking.fints.mapper - -import kotlinx.datetime.LocalDate - -actual class DateFormatter actual constructor(pattern: String) { - - actual fun parseDate(dateString: String): LocalDate? { - return null // is only used in rare cases, don't implement right now - } - -} \ No newline at end of file