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 {
api project(":multiplatform-utils")
implementation "org.jetbrains.kotlinx:kotlinx-datetime:0.3.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
implementation "io.ktor:ktor-client-core:$ktorVersion"
@ -92,8 +94,16 @@ 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
*/
// }
//
// jsTest {

View File

@ -2,13 +2,15 @@ package net.dankito.banking.fints
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.datetime.*
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.model.*
import net.dankito.banking.fints.response.BankResponse
import net.dankito.banking.fints.response.client.*
import net.dankito.banking.fints.response.segments.*
import net.dankito.utils.multiplatform.Date
import kotlin.jvm.JvmOverloads
@ -144,7 +146,8 @@ open class FinTsClient @JvmOverloads constructor(
}
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)
}

View File

@ -1,5 +1,6 @@
package net.dankito.banking.fints
import kotlinx.datetime.LocalDate
import net.dankito.banking.fints.messages.MessageBuilder
import net.dankito.banking.fints.messages.MessageBuilderResult
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.util.TanMethodSelector
import net.dankito.utils.multiplatform.log.LoggerFactory
import net.dankito.utils.multiplatform.Date
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
|| (response.successful && (parameter.alsoRetrieveBalance == false || balance != null))
val fromDate = parameter.fromDate
?: parameter.account.countDaysForWhichTransactionsAreKept?.let { Date.today.addDays(it * -1) }
?: bookedTransactions.map { it.valueDate }.sortedBy { it.millisSinceEpoch }.firstOrNull()
val retrievedData = RetrievedAccountData(parameter.account, successful, balance, bookedTransactions, unbookedTransactions, fromDate, parameter.toDate ?: Date.today, response.internalError)
?: parameter.account.countDaysForWhichTransactionsAreKept?.let { LocalDate.todayAtSystemDefaultTimeZone().minusDays(it) }
?: bookedTransactions.minByOrNull { it.valueDate.millisSinceEpochAtEuropeBerlin }?.valueDate
val retrievedData = RetrievedAccountData(parameter.account, successful, balance, bookedTransactions, unbookedTransactions, fromDate, parameter.toDate ?: LocalDate.todayAtEuropeBerlin(), response.internalError)
callback(GetAccountTransactionsResponse(context, response, retrievedData,
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.LoggerFactory
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.toStringWithTwoDigits
import kotlin.reflect.KClass
@ -80,7 +80,7 @@ open class MessageLogCollector {
}
protected open fun twoDigits(number: Int): String {
return number.format("%02d")
return number.toStringWithTwoDigits()
}
protected open fun getMessageTypeString(type: MessageLogEntryType): String {

View File

@ -1,5 +1,6 @@
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.KundensystemID
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.segments.*
import net.dankito.banking.fints.util.FinTsUtils
import net.dankito.utils.multiplatform.Date
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 {
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
import kotlinx.datetime.LocalDate
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.basisformate.NumerischesDatenelement
import net.dankito.utils.multiplatform.Date
import net.dankito.utils.multiplatform.DateFormatter
import net.dankito.utils.multiplatform.extensions.toStringWithMinDigits
import net.dankito.utils.multiplatform.extensions.toStringWithTwoDigits
import net.dankito.utils.multiplatform.log.LoggerFactory
@ -17,17 +18,15 @@ open class Datum(date: Int?, existenzstatus: Existenzstatus) : NumerischesDatene
companion object {
const val HbciDateFormatString = "yyyyMMdd"
val HbciDateFormat = DateFormatter(HbciDateFormatString)
private val log = LoggerFactory.getLogger(Datum::class)
fun format(date: Date): String {
return HbciDateFormat.format(date) // TODO: is this correct?
fun format(date: LocalDate): String { // create HbciDateFormatString
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
if (dateString.length == 8) {
@ -36,7 +35,7 @@ open class Datum(date: Int?, existenzstatus: Existenzstatus) : NumerischesDatene
val month = dateString.substring(4, 6)
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) {
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)
}

View File

@ -1,9 +1,10 @@
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.datenelemente.basisformate.ZiffernDatenelement
import net.dankito.utils.multiplatform.Date
import net.dankito.utils.multiplatform.DateFormatter
import net.dankito.utils.multiplatform.extensions.toStringWithTwoDigits
import net.dankito.utils.multiplatform.log.LoggerFactory
@ -18,17 +19,15 @@ open class Uhrzeit(time: Int?, existenzstatus: Existenzstatus) : ZiffernDatenele
companion object {
const val HbciTimeFormatString = "HHmmss"
val HbciTimeFormat = DateFormatter(HbciTimeFormatString)
private val log = LoggerFactory.getLogger(Uhrzeit::class)
fun format(time: Date): String {
return HbciTimeFormat.format(time) // TODO: is this correct?
fun format(time: LocalDateTime): String { // parse to HbciTimeFormatString
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
if (timeString.length == 6) {
@ -37,18 +36,18 @@ open class Uhrzeit(time: Int?, existenzstatus: Existenzstatus) : ZiffernDatenele
val minute = timeString.substring(2, 4)
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) {
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)
}

View File

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

View File

@ -1,6 +1,7 @@
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
@ -131,8 +132,8 @@ open class SepaMessageCreator : ISepaMessageCreator {
override fun createXmlFile(messageTemplate: PaymentInformationMessages, replacementStrings: Map<String, String>): String {
var xmlFile = messageTemplate.xmlTemplate
val now = Date()
val nowInIsoDate = IsoDateFormat.format(now)
val now = LocalDateTime.nowAtUtc()
val nowInIsoDate = now.toString() // applies formatting to ISO date time string
if (replacementStrings.containsKey(MessageIdKey) == false) {
xmlFile = replacePlaceholderWithValue(xmlFile, MessageIdKey, nowInIsoDate)

View File

@ -1,6 +1,7 @@
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(
@ -8,12 +9,12 @@ open class AccountTransaction(
val amount: Money,
val isReversal: Boolean,
val unparsedReference: String,
val bookingDate: Date,
val bookingDate: LocalDate,
val otherPartyName: String?,
val otherPartyBankCode: String?,
val otherPartyAccountId: String?,
val bookingText: String?,
val valueDate: Date,
val valueDate: LocalDate,
val statementNumber: Int,
val sequenceNumber: Int?,
val openingBalance: Money?,
@ -44,9 +45,9 @@ open class AccountTransaction(
) {
// 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,
0, 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
import net.dankito.utils.multiplatform.Date
import net.dankito.utils.multiplatform.format
import kotlinx.datetime.LocalDate
import net.dankito.utils.multiplatform.extensions.format
open class CreditCardTransaction(
open val amount: Money,
open val transactionDescriptionBase: String?,
open val transactionDescriptionSupplement: String?,
open val bookingDate: Date,
open val valueDate: Date,
open val bookingDate: LocalDate,
open val valueDate: LocalDate,
open val isCleared: Boolean
) {

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,8 @@
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.messages.Separators
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.response.segments.*
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.log.LoggerFactory
@ -674,13 +676,13 @@ open class ResponseParser(
var currency: String? = null
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) {
currency = parseString(dataElements[dateIndex])
date = parseDate(dataElements[++dateIndex])
}
var time: Date? = null
var time: LocalDateTime? = null
if (dataElements.size > dateIndex + 1) {
try {
time = parseTime(dataElements[dateIndex + 1])
@ -969,13 +971,13 @@ open class ResponseParser(
return Amount(adjustedAmountString)
}
protected open fun parseNullableDateTime(dataElementGroup: String): Date? {
protected open fun parseNullableDateTime(dataElementGroup: String): LocalDateTime? {
val dataElements = getDataElements(dataElementGroup)
if (dataElements.size >= 2) {
parseNullableDate(dataElements[0])?.let { date ->
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
}
protected open fun parseDate(dateString: String): Date {
protected open fun parseDate(dateString: String): LocalDate {
return Datum.parse(dateString)
}
protected open fun parseNullableDate(dateString: String): Date? {
protected open fun parseNullableDate(dateString: String): LocalDate? {
try {
return parseDate(dateString)
} catch (ignored: Exception) { }
@ -995,11 +997,11 @@ open class ResponseParser(
return null
}
protected open fun parseTime(timeString: String): Date {
protected open fun parseTime(timeString: String): LocalDateTime {
return Uhrzeit.parse(timeString)
}
protected open fun parseNullableTime(timeString: String): Date? {
protected open fun parseNullableTime(timeString: String): LocalDateTime? {
try {
return parseTime(timeString)
} catch (ignored: Exception) { }

View File

@ -1,14 +1,15 @@
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.utils.multiplatform.Date
open class Balance(
val amount: Amount,
val currency: String?,
val date: Date,
val time: Date?
val date: LocalDate,
val time: LocalDateTime?
) {
override fun toString(): String {

View File

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

View File

@ -1,7 +1,7 @@
package net.dankito.banking.fints.response.segments
import kotlinx.datetime.LocalDateTime
import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanProcess
import net.dankito.utils.multiplatform.Date
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 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
segmentString: String

View File

@ -1,11 +1,12 @@
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.model.Amount
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.Month
import net.dankito.utils.multiplatform.extensions.isUpperCase
import net.dankito.utils.multiplatform.log.LoggerFactory
@ -53,7 +54,7 @@ open class Mt940Parser(
val DateFormatter = DateFormatter("yyMMdd")
val CurrentYearTwoDigit = Date().year() - 2000
val CurrentYearTwoDigit = LocalDate.todayAtEuropeBerlin().year - 2000
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?
// SimpleDateFormat is not thread-safe. Before adding another library i decided to parse
@ -464,25 +465,25 @@ open class Mt940Parser(
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) {
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.
*/
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)
// 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)
val bookingDateMonth = bookingDate.month
if (bookingDateMonth != valueDate.month && bookingDateMonth == Month.DECEMBER) {
return parseMt940Date("" + (valueDate.year - 1 - 2000) + bookingDateString)
}
return bookingDate

View File

@ -1,7 +1,8 @@
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.utils.multiplatform.Date
open class Balance(
@ -23,7 +24,7 @@ open class Balance(
*
* Max length = 6
*/
val bookingDate: Date,
val bookingDate: LocalDate,
/**
* 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 {

View File

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

View File

@ -1,18 +1,21 @@
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.Uhrzeit
import net.dankito.utils.multiplatform.Date
open class FinTsUtils {
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)
}
@ -20,16 +23,16 @@ open class FinTsUtils {
return convertToInt(formatDateToday())
}
open fun formatDateAsInt(date: Date): Int {
open fun formatDateAsInt(date: LocalDate): Int {
return convertToInt(formatDate(date))
}
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)
}
@ -37,7 +40,7 @@ open class FinTsUtils {
return convertToInt(formatTimeNow())
}
open fun formatTimeAsInt(time: Date): Int {
open fun formatTimeAsInt(time: LocalDateTime): Int {
return convertToInt(formatTime(time))
}

View File

@ -1,6 +1,8 @@
package net.dankito.banking.fints
import kotlinx.datetime.LocalDate
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.Laenderkennzeichen
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.ChangeTanMediaParameters
import net.dankito.banking.fints.response.segments.JobParameters
import net.dankito.utils.multiplatform.Date
import kotlin.random.Random
abstract class FinTsTestBase {
@ -70,10 +70,10 @@ abstract class FinTsTestBase {
}
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)
}

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.toBe
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.messages.datenelemente.implementierte.tan.JobTanConfiguration
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.RetrieveAccountTransactionsParameters
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.Ignore
import kotlin.test.Test
@ -22,11 +23,11 @@ import kotlin.test.Test
class MessageBuilderTest : FinTsTestBase() {
private val underTest = object : MessageBuilder(utils = object : FinTsUtils() {
override fun formatDate(date: Date): String {
override fun formatDate(date: LocalDate): String {
return Date.toString()
}
override fun formatTime(time: Date): String {
override fun formatTime(time: LocalDateTime): String {
return Time.toString()
}
}) {
@ -171,8 +172,8 @@ class MessageBuilderTest : FinTsTestBase() {
val context = createContext()
val fromDate = Date(2019, Month.August, 6)
val toDate = Date(2019, Month.October, 21)
val fromDate = LocalDate(2019, Month.AUGUST, 6)
val toDate = LocalDate(2019, Month.OCTOBER, 21)
val maxCountEntries = 99
// when
@ -205,8 +206,8 @@ class MessageBuilderTest : FinTsTestBase() {
val context = createContext()
val fromDate = Date(2019, Month.August, 6)
val toDate = Date(2019, Month.October, 21)
val fromDate = LocalDate(2019, Month.AUGUST, 6)
val toDate = LocalDate(2019, Month.OCTOBER, 21)
val maxCountEntries = 99
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.response.segments.*
import ch.tutteli.atrium.api.verbs.expect
import kotlinx.datetime.LocalDate
import net.dankito.banking.fints.extensions.isFalse
import net.dankito.banking.fints.extensions.isTrue
import net.dankito.banking.fints.model.Amount
import net.dankito.utils.multiplatform.Date
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.fail
@ -1094,7 +1094,7 @@ class ResponseParserTest : FinTsTestBase() {
// given
val balance = "1234,56"
val date = Date(1988, 3, 27)
val date = LocalDate(1988, 3, 27)
val bankCode = "12345678"
val accountId = "0987654321"
val accountProductName = "Sichteinlagen"
@ -1121,7 +1121,7 @@ class ResponseParserTest : FinTsTestBase() {
// given
val balance = Amount.Zero
val date = Date(2020, 6, 11)
val date = LocalDate(2020, 6, 11)
val bankCode = "12345678"
val accountId = "0987654321"
val accountProductName = "Girokonto"
@ -1148,7 +1148,7 @@ class ResponseParserTest : FinTsTestBase() {
// given
val balance = Amount.Zero
val date = Date(2020, 6, 11)
val date = LocalDate(2020, 6, 11)
val bankCode = "12345678"
val accountId = "0987654321"
val accountProductName = "Girokonto"
@ -1231,14 +1231,14 @@ class ResponseParserTest : FinTsTestBase() {
result.getFirstSegmentById<ReceivedCreditCardTransactionsAndBalance>(InstituteSegmentId.CreditCardTransactions)?.let { segment ->
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.transactions.size).toBe(2)
segment.transactions.forEach { transaction ->
expect(transaction.transactionDescriptionBase).toBe(otherPartyName)
expect(transaction.bookingDate).toBe(Date(2020, 8, 19))
expect(transaction.valueDate).toBe(Date(2020, 8, 20))
expect(transaction.bookingDate).toBe(LocalDate(2020, 8, 19))
expect(transaction.valueDate).toBe(LocalDate(2020, 8, 20))
expect(transaction.amount.amount.string).toBe("-" + amount)
expect(transaction.amount.currency.code).toBe("EUR")
expect(transaction.isCleared).isTrue()
@ -1264,7 +1264,7 @@ class ResponseParserTest : FinTsTestBase() {
result.getFirstSegmentById<ReceivedCreditCardTransactionsAndBalance>(InstituteSegmentId.CreditCardTransactions)?.let { segment ->
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.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.StatementLine
import ch.tutteli.atrium.api.verbs.expect
import kotlinx.datetime.LocalDate
import kotlin.test.Test
import net.dankito.banking.fints.extensions.isFalse
import net.dankito.banking.fints.extensions.isTrue
import net.dankito.banking.fints.model.Amount
import net.dankito.utils.multiplatform.Date
import net.dankito.utils.multiplatform.DateFormatter
import net.dankito.utils.multiplatform.extensions.format
class Mt940ParserTest : FinTsTestBase() {
@ -23,8 +24,8 @@ class Mt940ParserTest : FinTsTestBase() {
const val Currency = "EUR"
val AccountStatement1PreviousStatementBookingDate = Date(1988, 2, 26)
val AccountStatement1BookingDate = Date(1988, 2, 27)
val AccountStatement1PreviousStatementBookingDate = LocalDate(1988, 2, 26)
val AccountStatement1BookingDate = LocalDate(1988, 2, 27)
val AccountStatement1OpeningBalanceAmount = Amount("12345,67")
@ -79,7 +80,7 @@ class Mt940ParserTest : FinTsTestBase() {
// given
val amount = Amount("15,00")
val isCredit = false
val bookingDate = Date(2020, 5, 11)
val bookingDate = LocalDate(2020, 5, 11)
// when
val result = underTest.parseMt940String(AccountStatementWithSingleTransaction_SheetNumberOmitted)
@ -160,10 +161,10 @@ class Mt940ParserTest : FinTsTestBase() {
expect(result).hasSize(1)
expect(result.first().transactions).hasSize(2)
expect(result.first().transactions[0].statementLine.bookingDate).toBe(Date(2019, 12, 30))
expect(result.first().transactions[0].statementLine.valueDate).toBe(Date(2020, 1, 1))
expect(result.first().transactions[1].statementLine.bookingDate).toBe(Date(2019, 12, 30))
expect(result.first().transactions[1].statementLine.valueDate).toBe(Date(2020, 1, 1))
expect(result.first().transactions[0].statementLine.bookingDate).toBe(LocalDate(2019, 12, 30))
expect(result.first().transactions[0].statementLine.valueDate).toBe(LocalDate(2020, 1, 1))
expect(result.first().transactions[1].statementLine.bookingDate).toBe(LocalDate(2019, 12, 30))
expect(result.first().transactions[1].statementLine.valueDate).toBe(LocalDate(2020, 1, 1))
}
@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.bookingDate).toBe(bookingDate)
expect(balance.amount).toBe(amount)
expect(balance.currency).toBe(Currency)
}
private fun assertTurnover(statementLine: StatementLine, valueDate: Date, amount: Amount, isCredit: Boolean = true,
bookingDate: Date? = valueDate) {
private fun assertTurnover(statementLine: StatementLine, valueDate: LocalDate, amount: Amount, isCredit: Boolean = true,
bookingDate: LocalDate? = valueDate) {
expect(statementLine.isCredit).toBe(isCredit)
expect(statementLine.isReversal).isFalse()
@ -385,12 +386,12 @@ class Mt940ParserTest : FinTsTestBase() {
""".trimIndent()
private fun convertMt940Date(date: Date): String {
return Mt940DateFormatter.format(date)
private fun convertMt940Date(date: LocalDate): String {
return date.format(Mt940DateFormatter)
}
private fun convertToShortBookingDate(date: Date): String {
return BookingDateFormatter.format(date)
private fun convertToShortBookingDate(date: LocalDate): String {
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.withRepresentation
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.test.Test
@ -60,7 +60,7 @@ class Base64Test {
@ExperimentalWithOptions
fun testRandomStrings() {
val steps = 1000000
val random = Random(Date().millisSinceEpoch)
val random = randomWithSeed()
for (count in 0 until steps) {
// 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.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
class FinTsUtilsTest {
@ -14,7 +16,7 @@ class FinTsUtilsTest {
fun formatDate() {
// given
val date = Date(1988, 3, 27)
val date = LocalDate(1988, 3, 27)
// when
val result = underTest.formatDate(date)
@ -27,7 +29,7 @@ class FinTsUtilsTest {
fun formatDateAsInt() {
// given
val date = Date(1988, 3, 27)
val date = LocalDate(1988, 3, 27)
// when
val result = underTest.formatDateAsInt(date)
@ -41,7 +43,7 @@ class FinTsUtilsTest {
fun formatTime_AM() {
// given
val time = Date(0, 0, 0, 8, 2, 1)
val time = LocalDateTime.of(8, 2, 1)
// when
val result = underTest.formatTime(time)
@ -54,7 +56,7 @@ class FinTsUtilsTest {
fun formatTime_PM() {
// given
val time = Date(0, 0, 0, 18, 22, 51)
val time = LocalDateTime.of(18, 22, 51)
// when
val result = underTest.formatTime(time)
@ -67,7 +69,7 @@ class FinTsUtilsTest {
fun formatTimeAsInt_AM() {
// given
val time = Date(0, 0, 0, 8, 2, 1)
val time = LocalDateTime.of(8, 2, 1)
// when
val result = underTest.formatTimeAsInt(time)
@ -80,7 +82,7 @@ class FinTsUtilsTest {
fun formatTimeAsInt_PM() {
// given
val time = Date(0, 0, 0, 18, 22, 51)
val time = LocalDateTime.of(18, 22, 51)
// when
val result = underTest.formatTimeAsInt(time)

View File

@ -46,7 +46,7 @@ kotlin {
sourceSets {
commonMain {
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 {
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
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
/**
* Be aware that Java DateFormat is not thread safe!
*/
@ -10,8 +13,10 @@ expect class DateFormatter constructor(pattern: String) {
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
fun Int.toStringWithTwoDigits(): String {
return toStringWithMinDigits(2)
}
fun Int.toStringWithMinDigits(countDigits: Int): String {
return format("%0${countDigits}d")
}
fun Int.format(format: String): String {
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
import net.dankito.utils.multiplatform.Date
import kotlinx.datetime.LocalDateTime
import net.dankito.utils.multiplatform.DateFormatter
import net.dankito.utils.multiplatform.Thread
import net.dankito.utils.multiplatform.extensions.nowAtSystemDefaultTimeZone
abstract class LoggerBase(
@ -99,7 +100,7 @@ abstract class LoggerBase(
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
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import platform.Foundation.*
@ -33,13 +35,21 @@ actual class DateFormatter actual constructor(val pattern: String): NSDateFormat
}
actual fun format(date: Date): String {
return stringFromDate(date.date)
actual fun format(date: LocalDateTime): String {
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 ->
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

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
import kotlinx.datetime.*
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.time.format.DateTimeFormatter
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)
: 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() ?: "")
actual fun format(date: Date): String {
return super.format(date)
actual fun format(date: LocalDateTime): String {
return formatter.format(date.toJavaLocalDateTime())
}
actual override fun parse(dateString: String): Date? {
super.parse(dateString)?.let { javaDate ->
return Date(javaDate.time)
}
actual fun parseDate(dateString: String): LocalDate? {
return java.time.LocalDate.parse(dateString, formatter)?.toKotlinLocalDate()
}
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)
}
}