Removed fallback of using platform specific DateFormatter
This commit is contained in:
parent
7ddeb88475
commit
fd9eadf45e
|
@ -5,6 +5,6 @@ import kotlin.reflect.KClass
|
||||||
|
|
||||||
interface IMessageLogAppender {
|
interface IMessageLogAppender {
|
||||||
|
|
||||||
fun logError(loggingClass: KClass<*>, message: String, e: Exception? = null)
|
fun logError(loggingClass: KClass<*>, message: String, e: Throwable? = null)
|
||||||
|
|
||||||
}
|
}
|
|
@ -69,7 +69,7 @@ open class MessageLogCollector(
|
||||||
addMessageLogEntry(type, context, messageTrace, prettyPrintMessage, null, parsedSegments)
|
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 type = MessageLogEntryType.Error
|
||||||
val messageTrace = createMessageTraceString(type, context)
|
val messageTrace = createMessageTraceString(type, context)
|
||||||
|
|
||||||
|
|
|
@ -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?
|
|
||||||
|
|
||||||
}
|
|
|
@ -82,7 +82,7 @@ open class JobContext(
|
||||||
messageLogCollector.addMessageLog(type, message, createMessageContext(), parsedSegments)
|
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)
|
messageLogCollector.logError(loggingClass, message, createMessageContext(), e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import net.codinux.log.logger
|
||||||
import net.codinux.banking.fints.log.IMessageLogAppender
|
import net.codinux.banking.fints.log.IMessageLogAppender
|
||||||
import net.codinux.banking.fints.model.Amount
|
import net.codinux.banking.fints.model.Amount
|
||||||
import net.codinux.banking.fints.transactions.mt940.model.*
|
import net.codinux.banking.fints.transactions.mt940.model.*
|
||||||
import net.codinux.banking.fints.mapper.DateFormatter
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -66,8 +65,6 @@ abstract class Mt94xParserBase<T: AccountStatementCommon>(
|
||||||
const val AmountOfCreditPostingsCode = "90C"
|
const val AmountOfCreditPostingsCode = "90C"
|
||||||
|
|
||||||
|
|
||||||
val DateFormatter = DateFormatter("yyMMdd") // TODO: replace with LocalDate.Format { }
|
|
||||||
|
|
||||||
val CreditDebitCancellationRegex = Regex("C|D|RC|RD")
|
val CreditDebitCancellationRegex = Regex("C|D|RC|RD")
|
||||||
|
|
||||||
val AmountRegex = Regex("\\d+,\\d*")
|
val AmountRegex = Regex("\\d+,\\d*")
|
||||||
|
@ -215,7 +212,7 @@ abstract class Mt94xParserBase<T: AccountStatementCommon>(
|
||||||
|
|
||||||
val isDebit = fieldValue.startsWith("D")
|
val isDebit = fieldValue.startsWith("D")
|
||||||
val bookingDateString = fieldValue.substring(1, 7)
|
val bookingDateString = fieldValue.substring(1, 7)
|
||||||
val statementDate = parseMt940Date(bookingDateString)
|
val statementDate = parseDate(bookingDateString)
|
||||||
val currency = fieldValue.substring(7, 10)
|
val currency = fieldValue.substring(7, 10)
|
||||||
val amountString = fieldValue.substring(10)
|
val amountString = fieldValue.substring(10)
|
||||||
val amount = parseAmount(amountString)
|
val amount = parseAmount(amountString)
|
||||||
|
@ -259,7 +256,7 @@ abstract class Mt94xParserBase<T: AccountStatementCommon>(
|
||||||
*/
|
*/
|
||||||
protected open fun parseStatementLine(fieldValue: String): StatementLine {
|
protected open fun parseStatementLine(fieldValue: String): StatementLine {
|
||||||
val valueDateString = fieldValue.substring(0, 6)
|
val valueDateString = fieldValue.substring(0, 6)
|
||||||
val valueDate = parseMt940Date(valueDateString)
|
val valueDate = parseDate(valueDateString)
|
||||||
|
|
||||||
val creditMarkMatchResult = CreditDebitCancellationRegex.find(fieldValue)
|
val creditMarkMatchResult = CreditDebitCancellationRegex.find(fieldValue)
|
||||||
val isDebit = creditMarkMatchResult?.value?.endsWith('D') == true
|
val isDebit = creditMarkMatchResult?.value?.endsWith('D') == true
|
||||||
|
@ -469,58 +466,51 @@ abstract class Mt94xParserBase<T: AccountStatementCommon>(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
open fun parseMt940Date(dateString: String): LocalDate {
|
open fun parseDate(dateString: String): LocalDate {
|
||||||
// TODO: this should be necessary anymore, isn't it?
|
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
|
* Bei 6-stelligen Datumsangaben (d.h. JJMMTT) wird gemäß SWIFT zwischen dem 20. und 21.
|
||||||
if (dateString.length == 6) {
|
* Jahrhundert wie folgt unterschieden:
|
||||||
try {
|
* - Ist das Jahr (d.h. JJ) größer als 79, bezieht sich das Datum auf das 20. Jahrhundert. Ist
|
||||||
var year = dateString.substring(0, 2).toInt()
|
* das Jahr 79 oder kleiner, bezieht sich das Datum auf das 21. Jahrhundert.
|
||||||
val month = dateString.substring(2, 4).toInt()
|
* - Ist JJ > 79:JJMMTT = 19JJMMTT
|
||||||
val day = dateString.substring(4, 6).toInt()
|
* - sonst: JJMMTT = 20JJMMTT
|
||||||
|
* - Damit reicht die Spanne des sechsstelligen Datums von 1980 bis 2079.
|
||||||
/**
|
*/
|
||||||
* Bei 6-stelligen Datumsangaben (d.h. JJMMTT) wird gemäß SWIFT zwischen dem 20. und 21.
|
if (year > 79) {
|
||||||
* Jahrhundert wie folgt unterschieden:
|
year += 1900
|
||||||
* - Ist das Jahr (d.h. JJ) größer als 79, bezieht sich das Datum auf das 20. Jahrhundert. Ist
|
} else {
|
||||||
* das Jahr 79 oder kleiner, bezieht sich das Datum auf das 21. Jahrhundert.
|
year += 2000
|
||||||
* - 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)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
* 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 {
|
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)
|
// 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
|
val bookingDateMonth = bookingDate.month
|
||||||
if (bookingDateMonth != valueDate.month && bookingDateMonth == Month.DECEMBER) {
|
if (bookingDateMonth != valueDate.month && bookingDateMonth == Month.DECEMBER) {
|
||||||
return parseMt940Date("" + (valueDate.year - 1 - 2000) + bookingDateString)
|
return parseDate("" + (valueDate.year - 1 - 2000) + bookingDateString)
|
||||||
}
|
}
|
||||||
|
|
||||||
return bookingDate
|
return bookingDate
|
||||||
|
@ -534,7 +524,7 @@ abstract class Mt94xParserBase<T: AccountStatementCommon>(
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun parseDateTime(dateTimeString: String): Instant {
|
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))
|
val time = parseTime(dateTimeString.substring(6, 10))
|
||||||
|
|
||||||
|
@ -555,10 +545,8 @@ abstract class Mt94xParserBase<T: AccountStatementCommon>(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected open fun logError(message: String, e: Exception?) {
|
protected open fun logError(message: String, e: Throwable?) {
|
||||||
logAppender?.let { logAppender ->
|
logAppender?.logError(Mt94xParserBase::class, message, e)
|
||||||
logAppender.logError(Mt94xParserBase::class, message, e)
|
|
||||||
}
|
|
||||||
?: run {
|
?: run {
|
||||||
log.error(e) { message }
|
log.error(e) { message }
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,8 @@ import kotlin.test.assertContains
|
||||||
class Mt940ParserTest : MtParserTestBase() {
|
class Mt940ParserTest : MtParserTestBase() {
|
||||||
|
|
||||||
private val underTest = object : Mt940Parser() {
|
private val underTest = object : Mt940Parser() {
|
||||||
public override fun parseMt940Date(dateString: String): LocalDate {
|
public override fun parseDate(dateString: String): LocalDate {
|
||||||
return super.parseMt940Date(dateString)
|
return super.parseDate(dateString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,28 +229,28 @@ class Mt940ParserTest : MtParserTestBase() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun parseDate() {
|
fun parseDate() {
|
||||||
val result = underTest.parseMt940Date("240507")
|
val result = underTest.parseDate("240507")
|
||||||
|
|
||||||
assertEquals(LocalDate(2024, 5, 7), result)
|
assertEquals(LocalDate(2024, 5, 7), result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun parseDateBeforeYear2000() {
|
fun parseDateBeforeYear2000() {
|
||||||
val result = underTest.parseMt940Date("990507")
|
val result = underTest.parseDate("990507")
|
||||||
|
|
||||||
assertEquals(LocalDate(1999, 5, 7), result)
|
assertEquals(LocalDate(1999, 5, 7), result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun parseDate_FixSparkasse29thOFFebruaryInNonLeapYearBug() {
|
fun parseDate_FixSparkasse29thOFFebruaryInNonLeapYearBug() {
|
||||||
val result = underTest.parseMt940Date("230229")
|
val result = underTest.parseDate("230229")
|
||||||
|
|
||||||
assertEquals(LocalDate(2023, 3, 1), result)
|
assertEquals(LocalDate(2023, 3, 1), result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun parseDate_FixSparkasse30thOfFebruaryBug() {
|
fun parseDate_FixSparkasse30thOfFebruaryBug() {
|
||||||
val result = underTest.parseMt940Date("230229")
|
val result = underTest.parseDate("230229")
|
||||||
|
|
||||||
assertEquals(LocalDate(2023, 3, 1), result)
|
assertEquals(LocalDate(2023, 3, 1), result)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue