Replaced own Date implementation with kotlinx-datetime

This commit is contained in:
dankito 2022-02-12 16:31:46 +01:00
parent fa378f6249
commit da9d5c018b
43 changed files with 393 additions and 432 deletions

View File

@ -45,6 +45,8 @@ kotlin {
dependencies { dependencies {
api project(":multiplatform-utils") api project(":multiplatform-utils")
implementation "org.jetbrains.kotlinx:kotlinx-datetime:0.3.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
implementation "io.ktor:ktor-client-core:$ktorVersion" implementation "io.ktor:ktor-client-core:$ktorVersion"
@ -92,8 +94,16 @@ kotlin {
// jsMain { // jsMain {
// dependencies { // dependencies {
// implementation npm("@js-joda/timezone", "2.3.0")
// implementation "io.ktor:ktor-client-js:$ktorVersion" // implementation "io.ktor:ktor-client-js:$ktorVersion"
// } // }
/* Plus:
@JsModule("@js-joda/timezone")
@JsNonModule
external object JsJodaTimeZoneModule
private val jsJodaTz = JsJodaTimeZoneModule
*/
// } // }
// //
// jsTest { // jsTest {

View File

@ -2,13 +2,15 @@ package net.dankito.banking.fints
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.datetime.*
import net.dankito.banking.fints.callback.FinTsClientCallback import net.dankito.banking.fints.callback.FinTsClientCallback
import net.dankito.utils.multiplatform.extensions.minusDays
import net.dankito.utils.multiplatform.extensions.todayAtEuropeBerlin
import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.* import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.*
import net.dankito.banking.fints.model.* import net.dankito.banking.fints.model.*
import net.dankito.banking.fints.response.BankResponse import net.dankito.banking.fints.response.BankResponse
import net.dankito.banking.fints.response.client.* import net.dankito.banking.fints.response.client.*
import net.dankito.banking.fints.response.segments.* import net.dankito.banking.fints.response.segments.*
import net.dankito.utils.multiplatform.Date
import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmOverloads
@ -144,7 +146,8 @@ open class FinTsClient @JvmOverloads constructor(
} }
protected open fun createGetAccountTransactionsOfLast90DaysParameter(bank: BankData, account: AccountData): GetAccountTransactionsParameter { protected open fun createGetAccountTransactionsOfLast90DaysParameter(bank: BankData, account: AccountData): GetAccountTransactionsParameter {
val ninetyDaysAgo = Date.today.addDays(-90) // Europe/Berlin: we're communicating with German bank servers, so we have to use their time zone
val ninetyDaysAgo = LocalDate.todayAtEuropeBerlin().minusDays(90)
return GetAccountTransactionsParameter(bank, account, account.supportsRetrievingBalance, ninetyDaysAgo, abortIfTanIsRequired = true) return GetAccountTransactionsParameter(bank, account, account.supportsRetrievingBalance, ninetyDaysAgo, abortIfTanIsRequired = true)
} }

View File

@ -1,5 +1,6 @@
package net.dankito.banking.fints package net.dankito.banking.fints
import kotlinx.datetime.LocalDate
import net.dankito.banking.fints.messages.MessageBuilder import net.dankito.banking.fints.messages.MessageBuilder
import net.dankito.banking.fints.messages.MessageBuilderResult import net.dankito.banking.fints.messages.MessageBuilderResult
import net.dankito.banking.fints.messages.datenelemente.implementierte.signatur.VersionDesSicherheitsverfahrens import net.dankito.banking.fints.messages.datenelemente.implementierte.signatur.VersionDesSicherheitsverfahrens
@ -16,8 +17,11 @@ import net.dankito.banking.fints.tan.FlickerCodeDecoder
import net.dankito.banking.fints.tan.TanImageDecoder import net.dankito.banking.fints.tan.TanImageDecoder
import net.dankito.banking.fints.util.TanMethodSelector import net.dankito.banking.fints.util.TanMethodSelector
import net.dankito.utils.multiplatform.log.LoggerFactory import net.dankito.utils.multiplatform.log.LoggerFactory
import net.dankito.utils.multiplatform.Date
import net.dankito.utils.multiplatform.ObjectReference import net.dankito.utils.multiplatform.ObjectReference
import net.dankito.utils.multiplatform.extensions.millisSinceEpochAtEuropeBerlin
import net.dankito.utils.multiplatform.extensions.minusDays
import net.dankito.utils.multiplatform.extensions.todayAtEuropeBerlin
import net.dankito.utils.multiplatform.extensions.todayAtSystemDefaultTimeZone
/** /**
@ -236,9 +240,9 @@ open class FinTsJobExecutor(
val successful = response.tanRequiredButWeWereToldToAbortIfSo val successful = response.tanRequiredButWeWereToldToAbortIfSo
|| (response.successful && (parameter.alsoRetrieveBalance == false || balance != null)) || (response.successful && (parameter.alsoRetrieveBalance == false || balance != null))
val fromDate = parameter.fromDate val fromDate = parameter.fromDate
?: parameter.account.countDaysForWhichTransactionsAreKept?.let { Date.today.addDays(it * -1) } ?: parameter.account.countDaysForWhichTransactionsAreKept?.let { LocalDate.todayAtSystemDefaultTimeZone().minusDays(it) }
?: bookedTransactions.map { it.valueDate }.sortedBy { it.millisSinceEpoch }.firstOrNull() ?: bookedTransactions.minByOrNull { it.valueDate.millisSinceEpochAtEuropeBerlin }?.valueDate
val retrievedData = RetrievedAccountData(parameter.account, successful, balance, bookedTransactions, unbookedTransactions, fromDate, parameter.toDate ?: Date.today, response.internalError) val retrievedData = RetrievedAccountData(parameter.account, successful, balance, bookedTransactions, unbookedTransactions, fromDate, parameter.toDate ?: LocalDate.todayAtEuropeBerlin(), response.internalError)
callback(GetAccountTransactionsResponse(context, response, retrievedData, callback(GetAccountTransactionsResponse(context, response, retrievedData,
if (parameter.maxCountEntries != null) parameter.isSettingMaxCountEntriesAllowedByBank else null)) if (parameter.maxCountEntries != null) parameter.isSettingMaxCountEntriesAllowedByBank else null))

View File

@ -6,8 +6,8 @@ import net.dankito.banking.fints.model.MessageLogEntryType
import net.dankito.utils.multiplatform.log.Logger import net.dankito.utils.multiplatform.log.Logger
import net.dankito.utils.multiplatform.log.LoggerFactory import net.dankito.utils.multiplatform.log.LoggerFactory
import net.dankito.utils.multiplatform.StackTraceHelper import net.dankito.utils.multiplatform.StackTraceHelper
import net.dankito.utils.multiplatform.extensions.format
import net.dankito.utils.multiplatform.extensions.getInnerException import net.dankito.utils.multiplatform.extensions.getInnerException
import net.dankito.utils.multiplatform.extensions.toStringWithTwoDigits
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -80,7 +80,7 @@ open class MessageLogCollector {
} }
protected open fun twoDigits(number: Int): String { protected open fun twoDigits(number: Int): String {
return number.format("%02d") return number.toStringWithTwoDigits()
} }
protected open fun getMessageTypeString(type: MessageLogEntryType): String { protected open fun getMessageTypeString(type: MessageLogEntryType): String {

View File

@ -1,5 +1,6 @@
package net.dankito.banking.fints.messages package net.dankito.banking.fints.messages
import net.dankito.utils.multiplatform.extensions.randomWithSeed
import net.dankito.banking.fints.messages.datenelemente.implementierte.Aufsetzpunkt import net.dankito.banking.fints.messages.datenelemente.implementierte.Aufsetzpunkt
import net.dankito.banking.fints.messages.datenelemente.implementierte.KundensystemID import net.dankito.banking.fints.messages.datenelemente.implementierte.KundensystemID
import net.dankito.banking.fints.messages.datenelemente.implementierte.Synchronisierungsmodus import net.dankito.banking.fints.messages.datenelemente.implementierte.Synchronisierungsmodus
@ -22,9 +23,7 @@ import net.dankito.banking.fints.model.*
import net.dankito.banking.fints.response.InstituteSegmentId import net.dankito.banking.fints.response.InstituteSegmentId
import net.dankito.banking.fints.response.segments.* import net.dankito.banking.fints.response.segments.*
import net.dankito.banking.fints.util.FinTsUtils import net.dankito.banking.fints.util.FinTsUtils
import net.dankito.utils.multiplatform.Date
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.random.Random
/** /**
@ -453,7 +452,7 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
} }
protected open fun createControlReference(): String { protected open fun createControlReference(): String {
return Random(Date().millisSinceEpoch).nextInt().absoluteValue.toString() return randomWithSeed().nextInt().absoluteValue.toString()
} }

View File

@ -1,9 +1,10 @@
package net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate package net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate
import kotlinx.datetime.LocalDate
import net.dankito.banking.fints.messages.Existenzstatus import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.basisformate.NumerischesDatenelement import net.dankito.banking.fints.messages.datenelemente.basisformate.NumerischesDatenelement
import net.dankito.utils.multiplatform.Date import net.dankito.utils.multiplatform.extensions.toStringWithMinDigits
import net.dankito.utils.multiplatform.DateFormatter import net.dankito.utils.multiplatform.extensions.toStringWithTwoDigits
import net.dankito.utils.multiplatform.log.LoggerFactory import net.dankito.utils.multiplatform.log.LoggerFactory
@ -17,17 +18,15 @@ open class Datum(date: Int?, existenzstatus: Existenzstatus) : NumerischesDatene
companion object { companion object {
const val HbciDateFormatString = "yyyyMMdd" const val HbciDateFormatString = "yyyyMMdd"
val HbciDateFormat = DateFormatter(HbciDateFormatString)
private val log = LoggerFactory.getLogger(Datum::class) private val log = LoggerFactory.getLogger(Datum::class)
fun format(date: Date): String { fun format(date: LocalDate): String { // create HbciDateFormatString
return HbciDateFormat.format(date) // TODO: is this correct? return date.year.toStringWithMinDigits(4) + date.monthNumber.toStringWithTwoDigits() + date.dayOfMonth.toStringWithTwoDigits() // TODO: is this correct?
} }
fun parse(dateString: String): Date { fun parse(dateString: String): LocalDate {
// do not use DateFormatter as Java DateFormat is not thread safe, resulting in a lot of curious errors in parallel execution // do not use DateFormatter as Java DateFormat is not thread safe, resulting in a lot of curious errors in parallel execution
if (dateString.length == 8) { if (dateString.length == 8) {
@ -36,7 +35,7 @@ open class Datum(date: Int?, existenzstatus: Existenzstatus) : NumerischesDatene
val month = dateString.substring(4, 6) val month = dateString.substring(4, 6)
val day = dateString.substring(6, 8) val day = dateString.substring(6, 8)
return Date(year.toInt(), month.toInt(), day.toInt()) return LocalDate(year.toInt(), month.toInt(), day.toInt())
} catch (e: Exception) { } catch (e: Exception) {
log.error(e) { "Could not parse date string '$dateString' to HBCI date" } log.error(e) { "Could not parse date string '$dateString' to HBCI date" }
} }
@ -47,7 +46,7 @@ open class Datum(date: Int?, existenzstatus: Existenzstatus) : NumerischesDatene
} }
constructor(date: Date?, existenzstatus: Existenzstatus) constructor(date: LocalDate?, existenzstatus: Existenzstatus)
: this(date?.let { format(it).toInt() }, existenzstatus) : this(date?.let { format(it).toInt() }, existenzstatus)
} }

View File

@ -1,9 +1,10 @@
package net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate package net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate
import kotlinx.datetime.LocalDateTime
import net.dankito.utils.multiplatform.extensions.of
import net.dankito.banking.fints.messages.Existenzstatus import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.basisformate.ZiffernDatenelement import net.dankito.banking.fints.messages.datenelemente.basisformate.ZiffernDatenelement
import net.dankito.utils.multiplatform.Date import net.dankito.utils.multiplatform.extensions.toStringWithTwoDigits
import net.dankito.utils.multiplatform.DateFormatter
import net.dankito.utils.multiplatform.log.LoggerFactory import net.dankito.utils.multiplatform.log.LoggerFactory
@ -18,17 +19,15 @@ open class Uhrzeit(time: Int?, existenzstatus: Existenzstatus) : ZiffernDatenele
companion object { companion object {
const val HbciTimeFormatString = "HHmmss" const val HbciTimeFormatString = "HHmmss"
val HbciTimeFormat = DateFormatter(HbciTimeFormatString)
private val log = LoggerFactory.getLogger(Uhrzeit::class) private val log = LoggerFactory.getLogger(Uhrzeit::class)
fun format(time: Date): String { fun format(time: LocalDateTime): String { // parse to HbciTimeFormatString
return HbciTimeFormat.format(time) // TODO: is this correct? return time.hour.toStringWithTwoDigits() + time.minute.toStringWithTwoDigits() + time.second.toStringWithTwoDigits() // TODO: is this correct?
} }
fun parse(timeString: String): Date { fun parse(timeString: String): LocalDateTime {
// do not use DateFormatter as Java DateFormat is not thread safe, resulting in a lot of curious errors in parallel execution // do not use DateFormatter as Java DateFormat is not thread safe, resulting in a lot of curious errors in parallel execution
if (timeString.length == 6) { if (timeString.length == 6) {
@ -37,18 +36,18 @@ open class Uhrzeit(time: Int?, existenzstatus: Existenzstatus) : ZiffernDatenele
val minute = timeString.substring(2, 4) val minute = timeString.substring(2, 4)
val second = timeString.substring(4, 6) val second = timeString.substring(4, 6)
return Date(0, 0, 0, hour.toInt(), minute.toInt(), second.toInt()) return LocalDateTime.of(hour.toInt(), minute.toInt(), second.toInt())
} catch (e: Exception) { } catch (e: Exception) {
log.error(e) { "Could not parse time string '$timeString' to HBCI time" } log.error(e) { "Could not parse time string '$timeString' to HBCI time" }
} }
} }
throw IllegalArgumentException("Cannot parse '$timeString' to HBCI Time. Only times in format '${Uhrzeit.HbciTimeFormatString}' are allowed in HBCI / FinTS.") throw IllegalArgumentException("Cannot parse '$timeString' to HBCI Time. Only times in format '${HbciTimeFormatString}' are allowed in HBCI / FinTS.")
} }
} }
constructor(time: Date?, existenzstatus: Existenzstatus) constructor(time: LocalDateTime?, existenzstatus: Existenzstatus)
: this(time?.let { format(time).toInt() }, existenzstatus) : this(time?.let { format(time).toInt() }, existenzstatus)
} }

View File

@ -1,6 +1,6 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte.tan package net.dankito.banking.fints.messages.datenelemente.implementierte.tan
import net.dankito.utils.multiplatform.Date import kotlinx.datetime.LocalDate
open class TanGeneratorTanMedium( open class TanGeneratorTanMedium(
@ -9,8 +9,8 @@ open class TanGeneratorTanMedium(
val cardNumber: String, val cardNumber: String,
val cardSequenceNumber: String?, val cardSequenceNumber: String?,
val cardType: Int?, val cardType: Int?,
val validFrom: Date?, val validFrom: LocalDate?,
val validTo: Date?, val validTo: LocalDate?,
mediumName: String? mediumName: String?
) : TanMedium(mediumClass, status, mediumName) { ) : TanMedium(mediumClass, status, mediumName) {

View File

@ -1,6 +1,7 @@
package net.dankito.banking.fints.messages.segmente.implementierte.sepa package net.dankito.banking.fints.messages.segmente.implementierte.sepa
import net.dankito.utils.multiplatform.Date import kotlinx.datetime.LocalDateTime
import net.dankito.utils.multiplatform.extensions.nowAtUtc
import net.dankito.utils.multiplatform.DateFormatter import net.dankito.utils.multiplatform.DateFormatter
@ -131,8 +132,8 @@ open class SepaMessageCreator : ISepaMessageCreator {
override fun createXmlFile(messageTemplate: PaymentInformationMessages, replacementStrings: Map<String, String>): String { override fun createXmlFile(messageTemplate: PaymentInformationMessages, replacementStrings: Map<String, String>): String {
var xmlFile = messageTemplate.xmlTemplate var xmlFile = messageTemplate.xmlTemplate
val now = Date() val now = LocalDateTime.nowAtUtc()
val nowInIsoDate = IsoDateFormat.format(now) val nowInIsoDate = now.toString() // applies formatting to ISO date time string
if (replacementStrings.containsKey(MessageIdKey) == false) { if (replacementStrings.containsKey(MessageIdKey) == false) {
xmlFile = replacePlaceholderWithValue(xmlFile, MessageIdKey, nowInIsoDate) xmlFile = replacePlaceholderWithValue(xmlFile, MessageIdKey, nowInIsoDate)

View File

@ -1,6 +1,7 @@
package net.dankito.banking.fints.model package net.dankito.banking.fints.model
import net.dankito.utils.multiplatform.Date import kotlinx.datetime.LocalDate
import net.dankito.utils.multiplatform.extensions.atUnixEpochStart
open class AccountTransaction( open class AccountTransaction(
@ -8,12 +9,12 @@ open class AccountTransaction(
val amount: Money, val amount: Money,
val isReversal: Boolean, val isReversal: Boolean,
val unparsedReference: String, val unparsedReference: String,
val bookingDate: Date, val bookingDate: LocalDate,
val otherPartyName: String?, val otherPartyName: String?,
val otherPartyBankCode: String?, val otherPartyBankCode: String?,
val otherPartyAccountId: String?, val otherPartyAccountId: String?,
val bookingText: String?, val bookingText: String?,
val valueDate: Date, val valueDate: LocalDate,
val statementNumber: Int, val statementNumber: Int,
val sequenceNumber: Int?, val sequenceNumber: Int?,
val openingBalance: Money?, val openingBalance: Money?,
@ -44,9 +45,9 @@ open class AccountTransaction(
) { ) {
// for object deserializers // for object deserializers
internal constructor() : this(AccountData(), Money(Amount.Zero, ""), "", Date(0), null, null, null, null, Date(0)) internal constructor() : this(AccountData(), Money(Amount.Zero, ""), "", LocalDate.atUnixEpochStart, null, null, null, null, LocalDate.atUnixEpochStart)
constructor(account: AccountData, amount: Money, unparsedReference: String, bookingDate: Date, otherPartyName: String?, otherPartyBankCode: String?, otherPartyAccountId: String?, bookingText: String?, valueDate: Date) constructor(account: AccountData, amount: Money, unparsedReference: String, bookingDate: LocalDate, otherPartyName: String?, otherPartyBankCode: String?, otherPartyAccountId: String?, bookingText: String?, valueDate: LocalDate)
: this(account, amount, false, unparsedReference, bookingDate, otherPartyName, otherPartyBankCode, otherPartyAccountId, bookingText, valueDate, : this(account, amount, false, unparsedReference, bookingDate, otherPartyName, otherPartyBankCode, otherPartyAccountId, bookingText, valueDate,
0, null, null, null, 0, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,

View File

@ -1,15 +1,15 @@
package net.dankito.banking.fints.model package net.dankito.banking.fints.model
import net.dankito.utils.multiplatform.Date import kotlinx.datetime.LocalDate
import net.dankito.utils.multiplatform.format import net.dankito.utils.multiplatform.extensions.format
open class CreditCardTransaction( open class CreditCardTransaction(
open val amount: Money, open val amount: Money,
open val transactionDescriptionBase: String?, open val transactionDescriptionBase: String?,
open val transactionDescriptionSupplement: String?, open val transactionDescriptionSupplement: String?,
open val bookingDate: Date, open val bookingDate: LocalDate,
open val valueDate: Date, open val valueDate: LocalDate,
open val isCleared: Boolean open val isCleared: Boolean
) { ) {

View File

@ -1,6 +1,6 @@
package net.dankito.banking.fints.model package net.dankito.banking.fints.model
import net.dankito.utils.multiplatform.Date import kotlinx.datetime.LocalDate
import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmOverloads
@ -8,8 +8,8 @@ open class GetAccountTransactionsParameter @JvmOverloads constructor(
bank: BankData, bank: BankData,
account: AccountData, account: AccountData,
alsoRetrieveBalance: Boolean = true, alsoRetrieveBalance: Boolean = true,
fromDate: Date? = null, fromDate: LocalDate? = null,
toDate: Date? = null, toDate: LocalDate? = null,
/** /**
* Be aware this is by far not supported by all banks. * Be aware this is by far not supported by all banks.

View File

@ -1,14 +1,14 @@
package net.dankito.banking.fints.model package net.dankito.banking.fints.model
import net.dankito.utils.multiplatform.Date import kotlinx.datetime.LocalDate
import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmOverloads
open class GetTransactionsParameter @JvmOverloads constructor( open class GetTransactionsParameter @JvmOverloads constructor(
open val bank: BankData, open val bank: BankData,
open val alsoRetrieveBalance: Boolean = true, open val alsoRetrieveBalance: Boolean = true,
open val fromDate: Date? = null, open val fromDate: LocalDate? = null,
open val toDate: Date? = null, open val toDate: LocalDate? = null,
/** /**
* Be aware this is by far not supported by all banks. * Be aware this is by far not supported by all banks.

View File

@ -1,14 +1,15 @@
package net.dankito.banking.fints.model package net.dankito.banking.fints.model
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import net.dankito.banking.fints.log.MessageContext import net.dankito.banking.fints.log.MessageContext
import net.dankito.utils.multiplatform.Date
open class MessageLogEntry( open class MessageLogEntry(
open val type: MessageLogEntryType, open val type: MessageLogEntryType,
open val message: String, open val message: String,
open val context: MessageContext, open val context: MessageContext,
open val time: Date = Date() open val time: Instant = Clock.System.now()
) { ) {
override fun toString(): String { override fun toString(): String {

View File

@ -1,6 +1,6 @@
package net.dankito.banking.fints.model package net.dankito.banking.fints.model
import net.dankito.utils.multiplatform.Date import kotlinx.datetime.LocalDate
open class RetrievedAccountData( open class RetrievedAccountData(
@ -9,8 +9,8 @@ open class RetrievedAccountData(
open val balance: Money?, open val balance: Money?,
open var bookedTransactions: Collection<AccountTransaction>, open var bookedTransactions: Collection<AccountTransaction>,
open var unbookedTransactions: Collection<Any>, open var unbookedTransactions: Collection<Any>,
open val retrievedTransactionsFrom: Date?, open val retrievedTransactionsFrom: LocalDate?,
open val retrievedTransactionsTo: Date?, open val retrievedTransactionsTo: LocalDate?,
open val errorMessage: String? = null open val errorMessage: String? = null
) { ) {

View File

@ -1,5 +1,8 @@
package net.dankito.banking.fints.response package net.dankito.banking.fints.response
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.atTime
import net.dankito.banking.fints.log.IMessageLogAppender import net.dankito.banking.fints.log.IMessageLogAppender
import net.dankito.banking.fints.messages.Separators import net.dankito.banking.fints.messages.Separators
import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Datum import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Datum
@ -20,7 +23,6 @@ import net.dankito.banking.fints.model.CreditCardTransaction
import net.dankito.banking.fints.model.Money import net.dankito.banking.fints.model.Money
import net.dankito.banking.fints.response.segments.* import net.dankito.banking.fints.response.segments.*
import net.dankito.banking.fints.util.MessageUtils import net.dankito.banking.fints.util.MessageUtils
import net.dankito.utils.multiplatform.Date
import net.dankito.utils.multiplatform.extensions.getAllExceptionMessagesJoined import net.dankito.utils.multiplatform.extensions.getAllExceptionMessagesJoined
import net.dankito.utils.multiplatform.log.LoggerFactory import net.dankito.utils.multiplatform.log.LoggerFactory
@ -674,13 +676,13 @@ open class ResponseParser(
var currency: String? = null var currency: String? = null
var dateIndex = 2 var dateIndex = 2
var date: Date? = parseNullableDate(dataElements[dateIndex]) // in older versions dateElements[2] was the currency var date: LocalDate? = parseNullableDate(dataElements[dateIndex]) // in older versions dateElements[2] was the currency
if (date == null) { if (date == null) {
currency = parseString(dataElements[dateIndex]) currency = parseString(dataElements[dateIndex])
date = parseDate(dataElements[++dateIndex]) date = parseDate(dataElements[++dateIndex])
} }
var time: Date? = null var time: LocalDateTime? = null
if (dataElements.size > dateIndex + 1) { if (dataElements.size > dateIndex + 1) {
try { try {
time = parseTime(dataElements[dateIndex + 1]) time = parseTime(dataElements[dateIndex + 1])
@ -969,13 +971,13 @@ open class ResponseParser(
return Amount(adjustedAmountString) return Amount(adjustedAmountString)
} }
protected open fun parseNullableDateTime(dataElementGroup: String): Date? { protected open fun parseNullableDateTime(dataElementGroup: String): LocalDateTime? {
val dataElements = getDataElements(dataElementGroup) val dataElements = getDataElements(dataElementGroup)
if (dataElements.size >= 2) { if (dataElements.size >= 2) {
parseNullableDate(dataElements[0])?.let { date -> parseNullableDate(dataElements[0])?.let { date ->
parseNullableTime(dataElements[1])?.let { time -> parseNullableTime(dataElements[1])?.let { time ->
return Date(date.millisSinceEpoch + time.millisSinceEpoch) // TODO: is this correct? return date.atTime(time.hour, time.minute, time.second) // TODO: is this correct?
} }
} }
} }
@ -983,11 +985,11 @@ open class ResponseParser(
return null return null
} }
protected open fun parseDate(dateString: String): Date { protected open fun parseDate(dateString: String): LocalDate {
return Datum.parse(dateString) return Datum.parse(dateString)
} }
protected open fun parseNullableDate(dateString: String): Date? { protected open fun parseNullableDate(dateString: String): LocalDate? {
try { try {
return parseDate(dateString) return parseDate(dateString)
} catch (ignored: Exception) { } } catch (ignored: Exception) { }
@ -995,11 +997,11 @@ open class ResponseParser(
return null return null
} }
protected open fun parseTime(timeString: String): Date { protected open fun parseTime(timeString: String): LocalDateTime {
return Uhrzeit.parse(timeString) return Uhrzeit.parse(timeString)
} }
protected open fun parseNullableTime(timeString: String): Date? { protected open fun parseNullableTime(timeString: String): LocalDateTime? {
try { try {
return parseTime(timeString) return parseTime(timeString)
} catch (ignored: Exception) { } } catch (ignored: Exception) { }

View File

@ -1,14 +1,15 @@
package net.dankito.banking.fints.response.segments package net.dankito.banking.fints.response.segments
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import net.dankito.banking.fints.model.Amount import net.dankito.banking.fints.model.Amount
import net.dankito.utils.multiplatform.Date
open class Balance( open class Balance(
val amount: Amount, val amount: Amount,
val currency: String?, val currency: String?,
val date: Date, val date: LocalDate,
val time: Date? val time: LocalDateTime?
) { ) {
override fun toString(): String { override fun toString(): String {

View File

@ -1,13 +1,13 @@
package net.dankito.banking.fints.response.segments package net.dankito.banking.fints.response.segments
import kotlinx.datetime.LocalDate
import net.dankito.banking.fints.model.Amount import net.dankito.banking.fints.model.Amount
import net.dankito.utils.multiplatform.Date
open class BalanceSegment( open class BalanceSegment(
val balance: Amount, val balance: Amount,
val currency: String, val currency: String,
val date: Date, val date: LocalDate,
val accountProductName: String, val accountProductName: String,
val balanceOfPreBookedTransactions: Amount?, val balanceOfPreBookedTransactions: Amount?,
segmentString: String segmentString: String

View File

@ -1,7 +1,7 @@
package net.dankito.banking.fints.response.segments package net.dankito.banking.fints.response.segments
import kotlinx.datetime.LocalDateTime
import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanProcess import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanProcess
import net.dankito.utils.multiplatform.Date
open class TanResponse( open class TanResponse(
@ -31,7 +31,7 @@ open class TanResponse(
val challenge: String?, // M: bei TAN-Prozess=1, 3, 4. O: bei TAN-Prozess=2 val challenge: String?, // M: bei TAN-Prozess=1, 3, 4. O: bei TAN-Prozess=2
val challengeHHD_UC: String?, val challengeHHD_UC: String?,
val validityDateTimeForChallenge: Date?, val validityDateTimeForChallenge: LocalDateTime?,
val tanMediaIdentifier: String? = null, // M: bei TAN-Prozess=1, 3, 4 und „Anzahl unterstützter aktiver TAN-Medien“ nicht vorhanden. O: sonst val tanMediaIdentifier: String? = null, // M: bei TAN-Prozess=1, 3, 4 und „Anzahl unterstützter aktiver TAN-Medien“ nicht vorhanden. O: sonst
segmentString: String segmentString: String

View File

@ -1,11 +1,12 @@
package net.dankito.banking.fints.transactions.mt940 package net.dankito.banking.fints.transactions.mt940
import kotlinx.datetime.LocalDate
import kotlinx.datetime.Month
import net.dankito.utils.multiplatform.extensions.todayAtEuropeBerlin
import net.dankito.banking.fints.log.IMessageLogAppender import net.dankito.banking.fints.log.IMessageLogAppender
import net.dankito.banking.fints.model.Amount import net.dankito.banking.fints.model.Amount
import net.dankito.banking.fints.transactions.mt940.model.* import net.dankito.banking.fints.transactions.mt940.model.*
import net.dankito.utils.multiplatform.Date
import net.dankito.utils.multiplatform.DateFormatter import net.dankito.utils.multiplatform.DateFormatter
import net.dankito.utils.multiplatform.Month
import net.dankito.utils.multiplatform.extensions.isUpperCase import net.dankito.utils.multiplatform.extensions.isUpperCase
import net.dankito.utils.multiplatform.log.LoggerFactory import net.dankito.utils.multiplatform.log.LoggerFactory
@ -53,7 +54,7 @@ open class Mt940Parser(
val DateFormatter = DateFormatter("yyMMdd") val DateFormatter = DateFormatter("yyMMdd")
val CurrentYearTwoDigit = Date().year() - 2000 val CurrentYearTwoDigit = LocalDate.todayAtEuropeBerlin().year - 2000
val CreditDebitCancellationRegex = Regex("C|D|RC|RD") val CreditDebitCancellationRegex = Regex("C|D|RC|RD")
@ -449,7 +450,7 @@ open class Mt940Parser(
} }
protected open fun parseMt940Date(dateString: String): Date { protected open fun parseMt940Date(dateString: String): LocalDate {
// TODO: this should be necessary anymore, isn't it? // TODO: this should be necessary anymore, isn't it?
// SimpleDateFormat is not thread-safe. Before adding another library i decided to parse // SimpleDateFormat is not thread-safe. Before adding another library i decided to parse
@ -464,25 +465,25 @@ open class Mt940Parser(
year -= 100 year -= 100
} }
return Date(year + 2000, month, day) // java.util.Date years start at 1900 at month at 0 not at 1 return LocalDate(year + 2000, month, day) // java.util.Date years start at 1900 at month at 0 not at 1
} catch (e: Exception) { } catch (e: Exception) {
logError("Could not parse dateString '$dateString'", e) logError("Could not parse dateString '$dateString'", e)
} }
} }
return DateFormatter.parse(dateString)!! // fallback to not thread-safe SimpleDateFormat. Works in most cases but not all return DateFormatter.parseDate(dateString)!! // fallback to not thread-safe SimpleDateFormat. Works in most cases but not all
} }
/** /**
* 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: Date): Date { protected open fun parseMt940BookingDate(bookingDateString: String, valueDateString: String, valueDate: LocalDate): LocalDate {
val bookingDate = parseMt940Date(valueDateString.substring(0, 2) + bookingDateString) val bookingDate = parseMt940Date(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 parseMt940Date("" + (valueDate.year - 1 - 2000) + bookingDateString)
} }
return bookingDate return bookingDate

View File

@ -1,7 +1,8 @@
package net.dankito.banking.fints.transactions.mt940.model package net.dankito.banking.fints.transactions.mt940.model
import kotlinx.datetime.LocalDate
import net.dankito.utils.multiplatform.extensions.atUnixEpochStart
import net.dankito.banking.fints.model.Amount import net.dankito.banking.fints.model.Amount
import net.dankito.utils.multiplatform.Date
open class Balance( open class Balance(
@ -23,7 +24,7 @@ open class Balance(
* *
* Max length = 6 * Max length = 6
*/ */
val bookingDate: Date, val bookingDate: LocalDate,
/** /**
* Währungsschlüssel gem. ISO 4217 * Währungsschlüssel gem. ISO 4217
@ -41,7 +42,7 @@ open class Balance(
) { ) {
internal constructor() : this(false, false, Date(0), "", Amount.Zero) // for object deserializers internal constructor() : this(false, false, LocalDate.atUnixEpochStart, "", Amount.Zero) // for object deserializers
override fun toString(): String { override fun toString(): String {

View File

@ -1,7 +1,7 @@
package net.dankito.banking.fints.transactions.mt940.model package net.dankito.banking.fints.transactions.mt940.model
import kotlinx.datetime.LocalDate
import net.dankito.banking.fints.model.Amount import net.dankito.banking.fints.model.Amount
import net.dankito.utils.multiplatform.Date
open class StatementLine( open class StatementLine(
@ -25,14 +25,14 @@ open class StatementLine(
* *
* Length = 6 * Length = 6
*/ */
val valueDate: Date, val valueDate: LocalDate,
/** /**
* MMTT * MMTT
* *
* Length = 4 * Length = 4
*/ */
val bookingDate: Date?, val bookingDate: LocalDate?,
/** /**
* dritte Stelle der Währungsbezeichnung, falls sie zur Unterscheidung notwendig ist * dritte Stelle der Währungsbezeichnung, falls sie zur Unterscheidung notwendig ist

View File

@ -1,18 +1,21 @@
package net.dankito.banking.fints.util package net.dankito.banking.fints.util
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import net.dankito.utils.multiplatform.extensions.nowAtEuropeBerlin
import net.dankito.utils.multiplatform.extensions.todayAtEuropeBerlin
import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Datum import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Datum
import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Uhrzeit import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Uhrzeit
import net.dankito.utils.multiplatform.Date
open class FinTsUtils { open class FinTsUtils {
open fun formatDateToday(): String { open fun formatDateToday(): String {
return formatDate(Date()) return formatDate(LocalDate.todayAtEuropeBerlin())
} }
open fun formatDate(date: Date): String { open fun formatDate(date: LocalDate): String {
return Datum.format(date) return Datum.format(date)
} }
@ -20,16 +23,16 @@ open class FinTsUtils {
return convertToInt(formatDateToday()) return convertToInt(formatDateToday())
} }
open fun formatDateAsInt(date: Date): Int { open fun formatDateAsInt(date: LocalDate): Int {
return convertToInt(formatDate(date)) return convertToInt(formatDate(date))
} }
open fun formatTimeNow(): String { open fun formatTimeNow(): String {
return formatTime(Date()) return formatTime(LocalDateTime.nowAtEuropeBerlin())
} }
open fun formatTime(time: Date): String { open fun formatTime(time: LocalDateTime): String {
return Uhrzeit.format(time) return Uhrzeit.format(time)
} }
@ -37,7 +40,7 @@ open class FinTsUtils {
return convertToInt(formatTimeNow()) return convertToInt(formatTimeNow())
} }
open fun formatTimeAsInt(time: Date): Int { open fun formatTimeAsInt(time: LocalDateTime): Int {
return convertToInt(formatTime(time)) return convertToInt(formatTime(time))
} }

View File

@ -1,6 +1,8 @@
package net.dankito.banking.fints package net.dankito.banking.fints
import kotlinx.datetime.LocalDate
import net.dankito.banking.fints.callback.SimpleFinTsClientCallback import net.dankito.banking.fints.callback.SimpleFinTsClientCallback
import net.dankito.utils.multiplatform.extensions.randomWithSeed
import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Datum import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Datum
import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Laenderkennzeichen import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Laenderkennzeichen
import net.dankito.banking.fints.messages.datenelemente.implementierte.Dialogsprache import net.dankito.banking.fints.messages.datenelemente.implementierte.Dialogsprache
@ -9,8 +11,6 @@ import net.dankito.banking.fints.model.*
import net.dankito.banking.fints.response.segments.AccountType import net.dankito.banking.fints.response.segments.AccountType
import net.dankito.banking.fints.response.segments.ChangeTanMediaParameters import net.dankito.banking.fints.response.segments.ChangeTanMediaParameters
import net.dankito.banking.fints.response.segments.JobParameters import net.dankito.banking.fints.response.segments.JobParameters
import net.dankito.utils.multiplatform.Date
import kotlin.random.Random
abstract class FinTsTestBase { abstract class FinTsTestBase {
@ -70,10 +70,10 @@ abstract class FinTsTestBase {
} }
protected open fun createDialogId(): String { protected open fun createDialogId(): String {
return Random(net.dankito.utils.multiplatform.Date.nanoSecondsSinceEpoch).nextInt(1000000, 9999999).toString() return randomWithSeed().nextInt(1000000, 9999999).toString()
} }
protected open fun convertDate(date: Date): String { protected open fun convertDate(date: LocalDate): String {
return Datum.format(date) return Datum.format(date)
} }

View File

@ -3,6 +3,9 @@ package net.dankito.banking.fints.messages
import ch.tutteli.atrium.api.fluent.en_GB.notToBeNull import ch.tutteli.atrium.api.fluent.en_GB.notToBeNull
import ch.tutteli.atrium.api.fluent.en_GB.toBe import ch.tutteli.atrium.api.fluent.en_GB.toBe
import ch.tutteli.atrium.api.verbs.expect import ch.tutteli.atrium.api.verbs.expect
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.Month
import net.dankito.banking.fints.FinTsTestBase import net.dankito.banking.fints.FinTsTestBase
import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.JobTanConfiguration import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.JobTanConfiguration
import net.dankito.banking.fints.messages.segmente.id.CustomerSegmentId import net.dankito.banking.fints.messages.segmente.id.CustomerSegmentId
@ -12,8 +15,6 @@ import net.dankito.banking.fints.response.segments.JobParameters
import net.dankito.banking.fints.response.segments.PinInfo import net.dankito.banking.fints.response.segments.PinInfo
import net.dankito.banking.fints.response.segments.RetrieveAccountTransactionsParameters import net.dankito.banking.fints.response.segments.RetrieveAccountTransactionsParameters
import net.dankito.banking.fints.util.FinTsUtils import net.dankito.banking.fints.util.FinTsUtils
import net.dankito.utils.multiplatform.Date
import net.dankito.utils.multiplatform.Month
import kotlin.test.AfterTest import kotlin.test.AfterTest
import kotlin.test.Ignore import kotlin.test.Ignore
import kotlin.test.Test import kotlin.test.Test
@ -22,11 +23,11 @@ import kotlin.test.Test
class MessageBuilderTest : FinTsTestBase() { class MessageBuilderTest : FinTsTestBase() {
private val underTest = object : MessageBuilder(utils = object : FinTsUtils() { private val underTest = object : MessageBuilder(utils = object : FinTsUtils() {
override fun formatDate(date: Date): String { override fun formatDate(date: LocalDate): String {
return Date.toString() return Date.toString()
} }
override fun formatTime(time: Date): String { override fun formatTime(time: LocalDateTime): String {
return Time.toString() return Time.toString()
} }
}) { }) {
@ -171,8 +172,8 @@ class MessageBuilderTest : FinTsTestBase() {
val context = createContext() val context = createContext()
val fromDate = Date(2019, Month.August, 6) val fromDate = LocalDate(2019, Month.AUGUST, 6)
val toDate = Date(2019, Month.October, 21) val toDate = LocalDate(2019, Month.OCTOBER, 21)
val maxCountEntries = 99 val maxCountEntries = 99
// when // when
@ -205,8 +206,8 @@ class MessageBuilderTest : FinTsTestBase() {
val context = createContext() val context = createContext()
val fromDate = Date(2019, Month.August, 6) val fromDate = LocalDate(2019, Month.AUGUST, 6)
val toDate = Date(2019, Month.October, 21) val toDate = LocalDate(2019, Month.OCTOBER, 21)
val maxCountEntries = 99 val maxCountEntries = 99
val continuationId = "9345-10-26-11.52.15.693455" val continuationId = "9345-10-26-11.52.15.693455"

View File

@ -13,10 +13,10 @@ import net.dankito.banking.fints.messages.segmente.id.ISegmentId
import net.dankito.banking.fints.messages.segmente.id.MessageSegmentId import net.dankito.banking.fints.messages.segmente.id.MessageSegmentId
import net.dankito.banking.fints.response.segments.* import net.dankito.banking.fints.response.segments.*
import ch.tutteli.atrium.api.verbs.expect import ch.tutteli.atrium.api.verbs.expect
import kotlinx.datetime.LocalDate
import net.dankito.banking.fints.extensions.isFalse import net.dankito.banking.fints.extensions.isFalse
import net.dankito.banking.fints.extensions.isTrue import net.dankito.banking.fints.extensions.isTrue
import net.dankito.banking.fints.model.Amount import net.dankito.banking.fints.model.Amount
import net.dankito.utils.multiplatform.Date
import kotlin.test.Ignore import kotlin.test.Ignore
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.fail import kotlin.test.fail
@ -1094,7 +1094,7 @@ class ResponseParserTest : FinTsTestBase() {
// given // given
val balance = "1234,56" val balance = "1234,56"
val date = Date(1988, 3, 27) val date = LocalDate(1988, 3, 27)
val bankCode = "12345678" val bankCode = "12345678"
val accountId = "0987654321" val accountId = "0987654321"
val accountProductName = "Sichteinlagen" val accountProductName = "Sichteinlagen"
@ -1121,7 +1121,7 @@ class ResponseParserTest : FinTsTestBase() {
// given // given
val balance = Amount.Zero val balance = Amount.Zero
val date = Date(2020, 6, 11) val date = LocalDate(2020, 6, 11)
val bankCode = "12345678" val bankCode = "12345678"
val accountId = "0987654321" val accountId = "0987654321"
val accountProductName = "Girokonto" val accountProductName = "Girokonto"
@ -1148,7 +1148,7 @@ class ResponseParserTest : FinTsTestBase() {
// given // given
val balance = Amount.Zero val balance = Amount.Zero
val date = Date(2020, 6, 11) val date = LocalDate(2020, 6, 11)
val bankCode = "12345678" val bankCode = "12345678"
val accountId = "0987654321" val accountId = "0987654321"
val accountProductName = "Girokonto" val accountProductName = "Girokonto"
@ -1231,14 +1231,14 @@ class ResponseParserTest : FinTsTestBase() {
result.getFirstSegmentById<ReceivedCreditCardTransactionsAndBalance>(InstituteSegmentId.CreditCardTransactions)?.let { segment -> result.getFirstSegmentById<ReceivedCreditCardTransactionsAndBalance>(InstituteSegmentId.CreditCardTransactions)?.let { segment ->
expect(segment.balance.amount.string).toBe(balance) expect(segment.balance.amount.string).toBe(balance)
expect(segment.balance.date).toBe(Date(2020, 9, 23)) expect(segment.balance.date).toBe(LocalDate(2020, 9, 23))
expect(segment.balance.time).notToBeNull() expect(segment.balance.time).notToBeNull()
expect(segment.transactions.size).toBe(2) expect(segment.transactions.size).toBe(2)
segment.transactions.forEach { transaction -> segment.transactions.forEach { transaction ->
expect(transaction.transactionDescriptionBase).toBe(otherPartyName) expect(transaction.transactionDescriptionBase).toBe(otherPartyName)
expect(transaction.bookingDate).toBe(Date(2020, 8, 19)) expect(transaction.bookingDate).toBe(LocalDate(2020, 8, 19))
expect(transaction.valueDate).toBe(Date(2020, 8, 20)) expect(transaction.valueDate).toBe(LocalDate(2020, 8, 20))
expect(transaction.amount.amount.string).toBe("-" + amount) expect(transaction.amount.amount.string).toBe("-" + amount)
expect(transaction.amount.currency.code).toBe("EUR") expect(transaction.amount.currency.code).toBe("EUR")
expect(transaction.isCleared).isTrue() expect(transaction.isCleared).isTrue()
@ -1264,7 +1264,7 @@ class ResponseParserTest : FinTsTestBase() {
result.getFirstSegmentById<ReceivedCreditCardTransactionsAndBalance>(InstituteSegmentId.CreditCardTransactions)?.let { segment -> result.getFirstSegmentById<ReceivedCreditCardTransactionsAndBalance>(InstituteSegmentId.CreditCardTransactions)?.let { segment ->
expect(segment.balance.amount.string).toBe(balance) expect(segment.balance.amount.string).toBe(balance)
expect(segment.balance.date).toBe(Date(2020, 9, 23)) expect(segment.balance.date).toBe(LocalDate(2020, 9, 23))
expect(segment.balance.time).notToBeNull() expect(segment.balance.time).notToBeNull()
expect(segment.transactions).isEmpty() expect(segment.transactions).isEmpty()

View File

@ -9,12 +9,13 @@ import net.dankito.banking.fints.transactions.mt940.model.Balance
import net.dankito.banking.fints.transactions.mt940.model.InformationToAccountOwner import net.dankito.banking.fints.transactions.mt940.model.InformationToAccountOwner
import net.dankito.banking.fints.transactions.mt940.model.StatementLine import net.dankito.banking.fints.transactions.mt940.model.StatementLine
import ch.tutteli.atrium.api.verbs.expect import ch.tutteli.atrium.api.verbs.expect
import kotlinx.datetime.LocalDate
import kotlin.test.Test import kotlin.test.Test
import net.dankito.banking.fints.extensions.isFalse import net.dankito.banking.fints.extensions.isFalse
import net.dankito.banking.fints.extensions.isTrue import net.dankito.banking.fints.extensions.isTrue
import net.dankito.banking.fints.model.Amount import net.dankito.banking.fints.model.Amount
import net.dankito.utils.multiplatform.Date
import net.dankito.utils.multiplatform.DateFormatter import net.dankito.utils.multiplatform.DateFormatter
import net.dankito.utils.multiplatform.extensions.format
class Mt940ParserTest : FinTsTestBase() { class Mt940ParserTest : FinTsTestBase() {
@ -23,8 +24,8 @@ class Mt940ParserTest : FinTsTestBase() {
const val Currency = "EUR" const val Currency = "EUR"
val AccountStatement1PreviousStatementBookingDate = Date(1988, 2, 26) val AccountStatement1PreviousStatementBookingDate = LocalDate(1988, 2, 26)
val AccountStatement1BookingDate = Date(1988, 2, 27) val AccountStatement1BookingDate = LocalDate(1988, 2, 27)
val AccountStatement1OpeningBalanceAmount = Amount("12345,67") val AccountStatement1OpeningBalanceAmount = Amount("12345,67")
@ -79,7 +80,7 @@ class Mt940ParserTest : FinTsTestBase() {
// given // given
val amount = Amount("15,00") val amount = Amount("15,00")
val isCredit = false val isCredit = false
val bookingDate = Date(2020, 5, 11) val bookingDate = LocalDate(2020, 5, 11)
// when // when
val result = underTest.parseMt940String(AccountStatementWithSingleTransaction_SheetNumberOmitted) val result = underTest.parseMt940String(AccountStatementWithSingleTransaction_SheetNumberOmitted)
@ -160,10 +161,10 @@ class Mt940ParserTest : FinTsTestBase() {
expect(result).hasSize(1) expect(result).hasSize(1)
expect(result.first().transactions).hasSize(2) expect(result.first().transactions).hasSize(2)
expect(result.first().transactions[0].statementLine.bookingDate).toBe(Date(2019, 12, 30)) expect(result.first().transactions[0].statementLine.bookingDate).toBe(LocalDate(2019, 12, 30))
expect(result.first().transactions[0].statementLine.valueDate).toBe(Date(2020, 1, 1)) expect(result.first().transactions[0].statementLine.valueDate).toBe(LocalDate(2020, 1, 1))
expect(result.first().transactions[1].statementLine.bookingDate).toBe(Date(2019, 12, 30)) expect(result.first().transactions[1].statementLine.bookingDate).toBe(LocalDate(2019, 12, 30))
expect(result.first().transactions[1].statementLine.valueDate).toBe(Date(2020, 1, 1)) expect(result.first().transactions[1].statementLine.valueDate).toBe(LocalDate(2020, 1, 1))
} }
@Test @Test
@ -313,15 +314,15 @@ class Mt940ParserTest : FinTsTestBase() {
} }
private fun assertBalance(balance: Balance, isCredit: Boolean, bookingDate: Date, amount: Amount) { private fun assertBalance(balance: Balance, isCredit: Boolean, bookingDate: LocalDate, amount: Amount) {
expect(balance.isCredit).toBe(isCredit) expect(balance.isCredit).toBe(isCredit)
expect(balance.bookingDate).toBe(bookingDate) expect(balance.bookingDate).toBe(bookingDate)
expect(balance.amount).toBe(amount) expect(balance.amount).toBe(amount)
expect(balance.currency).toBe(Currency) expect(balance.currency).toBe(Currency)
} }
private fun assertTurnover(statementLine: StatementLine, valueDate: Date, amount: Amount, isCredit: Boolean = true, private fun assertTurnover(statementLine: StatementLine, valueDate: LocalDate, amount: Amount, isCredit: Boolean = true,
bookingDate: Date? = valueDate) { bookingDate: LocalDate? = valueDate) {
expect(statementLine.isCredit).toBe(isCredit) expect(statementLine.isCredit).toBe(isCredit)
expect(statementLine.isReversal).isFalse() expect(statementLine.isReversal).isFalse()
@ -385,12 +386,12 @@ class Mt940ParserTest : FinTsTestBase() {
""".trimIndent() """.trimIndent()
private fun convertMt940Date(date: Date): String { private fun convertMt940Date(date: LocalDate): String {
return Mt940DateFormatter.format(date) return date.format(Mt940DateFormatter)
} }
private fun convertToShortBookingDate(date: Date): String { private fun convertToShortBookingDate(date: LocalDate): String {
return BookingDateFormatter.format(date) return date.format(BookingDateFormatter)
} }
} }

View File

@ -4,7 +4,7 @@ import ch.tutteli.atrium.api.fluent.en_GB.ExperimentalWithOptions
import ch.tutteli.atrium.api.fluent.en_GB.toBe import ch.tutteli.atrium.api.fluent.en_GB.toBe
import ch.tutteli.atrium.api.fluent.en_GB.withRepresentation import ch.tutteli.atrium.api.fluent.en_GB.withRepresentation
import ch.tutteli.atrium.api.verbs.expect import ch.tutteli.atrium.api.verbs.expect
import net.dankito.utils.multiplatform.Date import net.dankito.utils.multiplatform.extensions.randomWithSeed
import kotlin.random.Random import kotlin.random.Random
import kotlin.test.Test import kotlin.test.Test
@ -60,7 +60,7 @@ class Base64Test {
@ExperimentalWithOptions @ExperimentalWithOptions
fun testRandomStrings() { fun testRandomStrings() {
val steps = 1000000 val steps = 1000000
val random = Random(Date().millisSinceEpoch) val random = randomWithSeed()
for (count in 0 until steps) { for (count in 0 until steps) {
// given // given

View File

@ -2,7 +2,9 @@ package net.dankito.banking.fints.util
import ch.tutteli.atrium.api.fluent.en_GB.toBe import ch.tutteli.atrium.api.fluent.en_GB.toBe
import ch.tutteli.atrium.api.verbs.expect import ch.tutteli.atrium.api.verbs.expect
import net.dankito.utils.multiplatform.Date import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import net.dankito.utils.multiplatform.extensions.of
import kotlin.test.Test import kotlin.test.Test
class FinTsUtilsTest { class FinTsUtilsTest {
@ -14,7 +16,7 @@ class FinTsUtilsTest {
fun formatDate() { fun formatDate() {
// given // given
val date = Date(1988, 3, 27) val date = LocalDate(1988, 3, 27)
// when // when
val result = underTest.formatDate(date) val result = underTest.formatDate(date)
@ -27,7 +29,7 @@ class FinTsUtilsTest {
fun formatDateAsInt() { fun formatDateAsInt() {
// given // given
val date = Date(1988, 3, 27) val date = LocalDate(1988, 3, 27)
// when // when
val result = underTest.formatDateAsInt(date) val result = underTest.formatDateAsInt(date)
@ -41,7 +43,7 @@ class FinTsUtilsTest {
fun formatTime_AM() { fun formatTime_AM() {
// given // given
val time = Date(0, 0, 0, 8, 2, 1) val time = LocalDateTime.of(8, 2, 1)
// when // when
val result = underTest.formatTime(time) val result = underTest.formatTime(time)
@ -54,7 +56,7 @@ class FinTsUtilsTest {
fun formatTime_PM() { fun formatTime_PM() {
// given // given
val time = Date(0, 0, 0, 18, 22, 51) val time = LocalDateTime.of(18, 22, 51)
// when // when
val result = underTest.formatTime(time) val result = underTest.formatTime(time)
@ -67,7 +69,7 @@ class FinTsUtilsTest {
fun formatTimeAsInt_AM() { fun formatTimeAsInt_AM() {
// given // given
val time = Date(0, 0, 0, 8, 2, 1) val time = LocalDateTime.of(8, 2, 1)
// when // when
val result = underTest.formatTimeAsInt(time) val result = underTest.formatTimeAsInt(time)
@ -80,7 +82,7 @@ class FinTsUtilsTest {
fun formatTimeAsInt_PM() { fun formatTimeAsInt_PM() {
// given // given
val time = Date(0, 0, 0, 18, 22, 51) val time = LocalDateTime.of(18, 22, 51)
// when // when
val result = underTest.formatTimeAsInt(time) val result = underTest.formatTimeAsInt(time)

View File

@ -46,7 +46,7 @@ kotlin {
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-datetime:0.3.2"
} }
} }
@ -87,6 +87,21 @@ kotlin {
} }
// jsMain {
// dependencies {
// implementation npm("@js-joda/timezone", "2.3.0")
// implementation "io.ktor:ktor-client-js:$ktorVersion"
// }
// }
/* Plus:
@JsModule("@js-joda/timezone")
@JsNonModule
external object JsJodaTimeZoneModule
private val jsJodaTz = JsJodaTimeZoneModule
*/
iosMain { iosMain {
dependencies { dependencies {

View File

@ -1,56 +0,0 @@
package net.dankito.utils.multiplatform
fun Date.isBefore(other: Date): Boolean {
return compareTo(other) < 0
}
fun Date.isBeforeOrEquals(other: Date): Boolean {
return compareTo(other) <= 0
}
fun Date.format(format: DateFormatter): String {
return format.format(this)
}
fun Date.format(pattern: String): String {
return this.format(DateFormatter(pattern))
}
expect class Date(millisSinceEpoch: Long) {
companion object {
val today: Date
val nanoSecondsSinceEpoch: Long
}
constructor()
constructor(year: Int, month: Int, day: Int, hour: Int = 0, minute: Int = 0, second: Int = 0)
constructor(year: Int, month: Month, day: Int, hour: Int = 0, minute: Int = 0, second: Int = 0)
val millisSinceEpoch: Long
fun year(): Int
fun month(): Month
fun monthInt(): Int
fun day(): Int
fun addDays(days: Int): Date
fun compareTo(other: Date): Int
}

View File

@ -1,5 +1,8 @@
package net.dankito.utils.multiplatform package net.dankito.utils.multiplatform
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
/** /**
* Be aware that Java DateFormat is not thread safe! * Be aware that Java DateFormat is not thread safe!
*/ */
@ -10,8 +13,10 @@ expect class DateFormatter constructor(pattern: String) {
constructor(dateStyle: DateFormatStyle, timeStyle: DateFormatStyle) constructor(dateStyle: DateFormatStyle, timeStyle: DateFormatStyle)
fun format(date: Date): String fun format(date: LocalDateTime): String
fun parse(dateString: String): Date? fun parseDate(dateString: String): LocalDate?
fun parse(dateString: String): LocalDateTime?
} }

View File

@ -0,0 +1,77 @@
package net.dankito.utils.multiplatform.extensions
import kotlinx.datetime.*
import net.dankito.utils.multiplatform.DateFormatter
val LocalDate.Companion.atUnixEpochStart: LocalDate
get() = fromEpochMillisecondsAtUtc(0)
fun LocalDate.Companion.fromEpochMillisecondsAtUtc(epochMilliseconds: Long): LocalDate {
return fromEpochMilliseconds(epochMilliseconds, TimeZone.UTC)
}
fun LocalDate.Companion.fromEpochMillisecondsAtSystemDefaultTimeZone(epochMilliseconds: Long): LocalDate {
return fromEpochMilliseconds(epochMilliseconds, TimeZone.currentSystemDefault())
}
fun LocalDate.Companion.fromEpochMillisecondsAtEuropeBerlin(epochMilliseconds: Long): LocalDate {
return fromEpochMilliseconds(epochMilliseconds, TimeZone.europeBerlin)
}
fun LocalDate.Companion.fromEpochMilliseconds(epochMilliseconds: Long, timeZone: TimeZone): LocalDate {
return Instant.fromEpochMilliseconds(epochMilliseconds).toLocalDateTime(timeZone).date
}
fun LocalDate.Companion.todayAtUtc(): LocalDate {
return nowAt(TimeZone.UTC)
}
fun LocalDate.Companion.todayAtSystemDefaultTimeZone(): LocalDate {
return nowAt(TimeZone.currentSystemDefault())
}
fun LocalDate.Companion.todayAtEuropeBerlin(): LocalDate {
return nowAt(TimeZone.europeBerlin)
}
fun LocalDate.Companion.nowAt(timeZone: String): LocalDate {
return nowAt(TimeZone.of(timeZone))
}
fun LocalDate.Companion.nowAt(timeZone: TimeZone): LocalDate {
return Clock.System.todayAt(timeZone)
}
val LocalDate.millisSinceEpochAtUtc: Long
get() = this.toEpochMillisecondsAt(TimeZone.UTC)
val LocalDate.millisSinceEpochAtSystemDefaultTimeZone: Long
get() = this.toEpochMillisecondsAt(TimeZone.currentSystemDefault())
val LocalDate.millisSinceEpochAtEuropeBerlin: Long
get() = this.toEpochMillisecondsAt(TimeZone.europeBerlin)
fun LocalDate.toEpochMillisecondsAt(timeZone: TimeZone): Long {
return this.atTime(0, 0).toInstant(timeZone).toEpochMilliseconds()
}
fun LocalDate.addDays(days: Int): LocalDate {
return this.plus(days, DateTimeUnit.DAY)
}
fun LocalDate.minusDays(days: Int): LocalDate {
return this.minus(days, DateTimeUnit.DAY)
}
fun LocalDate.format(formatter: DateFormatter): String {
return this.atTime(0, 0).format(formatter)
}
fun LocalDate.format(pattern: String): String {
return this.format(DateFormatter(pattern))
}

View File

@ -0,0 +1,69 @@
package net.dankito.utils.multiplatform.extensions
import kotlinx.datetime.*
import net.dankito.utils.multiplatform.DateFormatter
fun LocalDateTime.Companion.of(hour: Int, minute: Int, second: Int = 0, nanosecond: Int = 0): LocalDateTime {
return LocalDateTime(0, 1, 1, hour, minute, second, nanosecond) // minimum values for month and day are 1
}
fun LocalDateTime.Companion.fromEpochMillisecondsAtUtc(epochMilliseconds: Long): LocalDateTime {
return fromEpochMilliseconds(epochMilliseconds, TimeZone.UTC)
}
fun LocalDateTime.Companion.fromEpochMillisecondsAtSystemDefaultTimeZone(epochMilliseconds: Long): LocalDateTime {
return fromEpochMilliseconds(epochMilliseconds, TimeZone.currentSystemDefault())
}
fun LocalDateTime.Companion.fromEpochMillisecondsAtEuropeBerlin(epochMilliseconds: Long): LocalDateTime {
return fromEpochMilliseconds(epochMilliseconds, TimeZone.europeBerlin)
}
fun LocalDateTime.Companion.fromEpochMilliseconds(epochMilliseconds: Long, timeZone: TimeZone): LocalDateTime {
return Instant.fromEpochMilliseconds(epochMilliseconds).toLocalDateTime(timeZone)
}
fun LocalDateTime.Companion.nowAtUtc(): LocalDateTime {
return nowAt(TimeZone.UTC)
}
fun LocalDateTime.Companion.nowAtSystemDefaultTimeZone(): LocalDateTime {
return nowAt(TimeZone.currentSystemDefault())
}
fun LocalDateTime.Companion.nowAtEuropeBerlin(): LocalDateTime {
return nowAt(TimeZone.europeBerlin)
}
fun LocalDateTime.Companion.nowAt(timeZone: String): LocalDateTime {
return nowAt(TimeZone.of(timeZone))
}
fun LocalDateTime.Companion.nowAt(timeZone: TimeZone): LocalDateTime {
return Clock.System.now().toLocalDateTime(timeZone)
}
val LocalDateTime.millisSinceEpochAtUtc: Long
get() = this.toEpochMillisecondsAt(TimeZone.UTC)
val LocalDateTime.millisSinceEpochAtSystemDefaultTimeZone: Long
get() = this.toEpochMillisecondsAt(TimeZone.currentSystemDefault())
val LocalDateTime.millisSinceEpochAtEuropeBerlin: Long
get() = this.toEpochMillisecondsAt(TimeZone.europeBerlin)
fun LocalDateTime.toEpochMillisecondsAt(timeZone: TimeZone): Long {
return this.toInstant(timeZone).toEpochMilliseconds()
}
fun LocalDateTime.format(formatter: DateFormatter): String {
return formatter.format(this)
}
fun LocalDateTime.format(pattern: String): String {
return this.format(DateFormatter(pattern))
}

View File

@ -3,6 +3,14 @@ package net.dankito.utils.multiplatform.extensions
import net.dankito.utils.multiplatform.StringHelper import net.dankito.utils.multiplatform.StringHelper
fun Int.toStringWithTwoDigits(): String {
return toStringWithMinDigits(2)
}
fun Int.toStringWithMinDigits(countDigits: Int): String {
return format("%0${countDigits}d")
}
fun Int.format(format: String): String { fun Int.format(format: String): String {
return StringHelper.format(format, this) return StringHelper.format(format, this)
} }

View File

@ -0,0 +1,11 @@
package net.dankito.utils.multiplatform.extensions
import kotlinx.datetime.Clock
import kotlin.random.Random
fun randomWithSeed(): Random = Random(randomSeed())
fun randomSeed(): Long {
return Clock.System.now().nanosecondsOfSecond.toLong() + Clock.System.now().toEpochMilliseconds()
}

View File

@ -0,0 +1,7 @@
package net.dankito.utils.multiplatform.extensions
import kotlinx.datetime.TimeZone
val TimeZone.Companion.europeBerlin: TimeZone
get() = TimeZone.of("Europe/Berlin")

View File

@ -1,8 +1,9 @@
package net.dankito.utils.multiplatform.log package net.dankito.utils.multiplatform.log
import net.dankito.utils.multiplatform.Date import kotlinx.datetime.LocalDateTime
import net.dankito.utils.multiplatform.DateFormatter import net.dankito.utils.multiplatform.DateFormatter
import net.dankito.utils.multiplatform.Thread import net.dankito.utils.multiplatform.Thread
import net.dankito.utils.multiplatform.extensions.nowAtSystemDefaultTimeZone
abstract class LoggerBase( abstract class LoggerBase(
@ -99,7 +100,7 @@ abstract class LoggerBase(
protected open fun createLogOutput(level: LogLevel, message: String): String { protected open fun createLogOutput(level: LogLevel, message: String): String {
return "${DateFormatter.format(Date())} [$level] ${Thread.current.threadName} $name - $message" return "${DateFormatter.format(LocalDateTime.nowAtSystemDefaultTimeZone())} [$level] ${Thread.current.threadName} $name - $message"
} }
} }

View File

@ -1,120 +0,0 @@
package net.dankito.utils.multiplatform
import platform.CoreFoundation.*
import platform.Foundation.*
fun NSTimeInterval.toMillis(): Long {
return this.toLong() * 1000
}
fun NSDate?.toDate(): Date? {
return this?.toDate()
}
fun NSDate.toDate(): Date {
return Date(this)
}
actual class Date(val date: NSDate) { // cannot subclass NSDate as it's a class cluster
actual companion object {
val DiffBetweenEpochTimeAndReferenceDate = (NSDate.timeIntervalSinceReferenceDate - NSTimeIntervalSince1970).toMillis()
fun from(year: Int, month: Int, day: Int, hour: Int = 0, minute: Int = 0, second: Int = 0): NSDate {
val dateComponents = NSDateComponents()
dateComponents.year = year.toLong()
dateComponents.month = month.toLong()
dateComponents.day = day.toLong()
dateComponents.hour = hour.toLong()
dateComponents.minute = minute.toLong()
dateComponents.second = second.toLong()
val calendar = NSCalendar.currentCalendar
val todayInUtc = calendar.dateFromComponents(dateComponents) !!
return calendar.dateByAddingUnit(NSCalendarUnitSecond, NSTimeZone.defaultTimeZone.secondsFromGMT, todayInUtc, 0)!!
}
actual val today: Date
get() {
val now = Date()
return Date(from(now.year(), now.monthInt(), now.day()))
}
actual val nanoSecondsSinceEpoch: Long
get() = CFAbsoluteTimeGetCurrent().toLong() * 1000
}
actual constructor(millisSinceEpoch: Long) : this(NSDate(timeIntervalSinceReferenceDate = ((millisSinceEpoch / 1000).toDouble() - NSTimeIntervalSince1970)))
actual constructor() : this(NSDate())
actual constructor(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int) : this(from(year, month, day, hour, minute, second))
actual constructor(year: Int, month: Month, day: Int, hour: Int, minute: Int, second: Int) : this(year, month.month, day, hour, minute, second)
actual val millisSinceEpoch: Long
get() = date.timeIntervalSince1970.toMillis()
actual fun year(): Int {
val components = NSCalendar.currentCalendar.components(NSCalendarUnitYear, date)
return components.year.toInt()
}
actual fun month(): Month {
return Month.fromInt(monthInt())
}
actual fun monthInt(): Int {
val components = NSCalendar.currentCalendar.components(NSCalendarUnitMonth, date)
return components.month.toInt()
}
actual fun day(): Int {
val components = NSCalendar.currentCalendar.components(NSCalendarUnitDay, date)
return components.day.toInt()
}
actual fun addDays(days: Int): Date {
val calendar = NSCalendar.currentCalendar
return calendar.dateByAddingUnit(NSCalendarUnitDay, days, this.date, 0)!!
}
actual fun compareTo(other: Date): Int {
return date.compare(other.date).toCompareToResult()
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Date) return false
if (date != other.date) return false
return true
}
override fun hashCode(): Int {
return date.hashCode()
}
override fun toString(): String {
return date.description ?: "Date(date=$date)"
}
}

View File

@ -1,5 +1,7 @@
package net.dankito.utils.multiplatform package net.dankito.utils.multiplatform
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import platform.Foundation.* import platform.Foundation.*
@ -33,13 +35,21 @@ actual class DateFormatter actual constructor(val pattern: String): NSDateFormat
} }
actual fun format(date: Date): String { actual fun format(date: LocalDateTime): String {
return stringFromDate(date.date) return stringFromDate(date) // TODO: convert to NSDate when back on Mac
} }
actual fun parse(dateString: String): Date? { actual fun parseDate(dateString: String): LocalDate? {
super.dateFromString(dateString)?.let { nsDate -> super.dateFromString(dateString)?.let { nsDate ->
return Date(nsDate) return LocalDate(nsDate) // TODO: convert to NSDate when back on Mac
}
return null
}
actual fun parse(dateString: String): LocalDateTime? {
super.dateFromString(dateString)?.let { nsDate ->
return LocalDateTime(nsDate) // TODO: convert to NSDate when back on Mac
} }
return null return null

View File

@ -1,79 +0,0 @@
package net.dankito.utils.multiplatform
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import net.dankito.utils.multiplatform.serialization.DateDeserializer
import java.text.DateFormat
import java.util.*
@JsonDeserialize(using = DateDeserializer::class)
actual class Date actual constructor(millisSinceEpoch: Long) : java.util.Date(millisSinceEpoch) {
actual companion object {
actual val today: Date
get() {
val today = Calendar.getInstance()
today.set(Calendar.HOUR_OF_DAY, 0)
today.set(Calendar.MINUTE, 0)
today.set(Calendar.SECOND, 0)
today.set(Calendar.MILLISECOND, 0)
return Date(today.time.time)
}
actual val nanoSecondsSinceEpoch: Long
get() = System.nanoTime()
}
actual constructor() : this(System.currentTimeMillis())
actual constructor(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int) : this(java.util.Date(year - 1900, month - 1, day, hour, minute, second).time)
actual constructor(year: Int, month: Month, day: Int, hour: Int, minute: Int, second: Int) : this(year, month.month, day, hour, minute, second)
actual val millisSinceEpoch: Long
get() = time
actual fun year(): Int {
return super.getYear() + 1900
}
actual fun month(): Month {
return Month.fromInt(monthInt())
}
actual fun monthInt(): Int {
return super.getMonth() + 1
}
actual fun day(): Int {
return super.getDate()
}
actual fun addDays(days: Int): Date {
val calendar = Calendar.getInstance()
calendar.time = this
calendar.add(Calendar.DATE, days)
return Date(calendar.time.time)
}
private fun formatDate(dateFormatStyle: Int): Int {
val dateStringString = DateFormat.getDateInstance(dateFormatStyle).format(this)
return dateStringString.toInt()
}
actual fun compareTo(other: Date): Int {
return super.compareTo(other)
}
}

View File

@ -1,7 +1,9 @@
package net.dankito.utils.multiplatform package net.dankito.utils.multiplatform
import kotlinx.datetime.*
import java.text.DateFormat import java.text.DateFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.time.format.DateTimeFormatter
fun DateFormatStyle.convert(): Int { fun DateFormatStyle.convert(): Int {
@ -14,7 +16,10 @@ fun DateFormatStyle.convert(): Int {
} }
actual class DateFormatter actual constructor(val pattern: String): SimpleDateFormat(pattern) { actual class DateFormatter actual constructor(pattern: String) {
private val formatter = DateTimeFormatter.ofPattern(pattern)
actual constructor(dateStyle: DateFormatStyle) actual constructor(dateStyle: DateFormatStyle)
: this((DateFormat.getDateInstance(dateStyle.convert()) as? SimpleDateFormat)?.toPattern() ?: "") : this((DateFormat.getDateInstance(dateStyle.convert()) as? SimpleDateFormat)?.toPattern() ?: "")
@ -23,16 +28,16 @@ actual class DateFormatter actual constructor(val pattern: String): SimpleDateFo
: this((DateFormat.getDateTimeInstance(dateStyle.convert(), timeStyle.convert()) as? SimpleDateFormat)?.toPattern() ?: "") : this((DateFormat.getDateTimeInstance(dateStyle.convert(), timeStyle.convert()) as? SimpleDateFormat)?.toPattern() ?: "")
actual fun format(date: Date): String { actual fun format(date: LocalDateTime): String {
return super.format(date) return formatter.format(date.toJavaLocalDateTime())
} }
actual override fun parse(dateString: String): Date? { actual fun parseDate(dateString: String): LocalDate? {
super.parse(dateString)?.let { javaDate -> return java.time.LocalDate.parse(dateString, formatter)?.toKotlinLocalDate()
return Date(javaDate.time)
} }
return null actual fun parse(dateString: String): LocalDateTime? {
return java.time.LocalDateTime.parse(dateString, formatter)?.toKotlinLocalDateTime()
} }
} }

View File

@ -1,21 +0,0 @@
package net.dankito.utils.multiplatform.serialization
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import net.dankito.utils.multiplatform.Date
open class DateDeserializer : StdDeserializer<Date>(Date::class.java) {
override fun deserialize(parser: JsonParser, context: DeserializationContext): Date? {
val millisSinceEpoch = parser.readValueAs(Long::class.java)
if (millisSinceEpoch == null) {
return null
}
return Date(millisSinceEpoch)
}
}