gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[libeufin] branch master updated (973c88cc -> 46e4838f)


From: gnunet
Subject: [libeufin] branch master updated (973c88cc -> 46e4838f)
Date: Fri, 29 Sep 2023 09:55:11 +0200

This is an automated email from the git hooks/post-receive script.

ms pushed a change to branch master
in repository libeufin.

    from 973c88cc support DESTDIR
     new a6350237 Time types handling.
     new cbdabfad comments
     new 46e4838f Stop using longs to manipulate time.

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../main/kotlin/tech/libeufin/bank/BankMessages.kt |  31 +++---
 .../tech/libeufin/bank/CorebankApiHandlers.kt      |  49 ++++++----
 .../src/main/kotlin/tech/libeufin/bank/Database.kt |  95 ++++++++++++++----
 bank/src/main/kotlin/tech/libeufin/bank/Main.kt    | 107 ++++++++++++++++++---
 .../tech/libeufin/bank/WireGatewayApiHandlers.kt   |  12 +--
 bank/src/main/kotlin/tech/libeufin/bank/helpers.kt |   5 +-
 bank/src/test/kotlin/DatabaseTest.kt               |  20 ++--
 bank/src/test/kotlin/JsonTest.kt                   |  44 ++++-----
 bank/src/test/kotlin/LibeuFinApiTest.kt            |  36 ++++++-
 util/src/main/kotlin/Config.kt                     |   2 +
 util/src/main/kotlin/DB.kt                         |   2 -
 util/src/main/kotlin/Ebics.kt                      |   2 -
 util/src/main/kotlin/time.kt                       |  55 ++++++++++-
 13 files changed, 340 insertions(+), 120 deletions(-)

diff --git a/bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt
index d0807f95..bf400026 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt
@@ -21,10 +21,11 @@ package tech.libeufin.bank
 
 import io.ktor.http.*
 import io.ktor.server.application.*
-import kotlinx.serialization.Contextual
 import kotlinx.serialization.Serializable
+import java.time.Duration
+import java.time.Instant
+import java.time.temporal.ChronoUnit
 import java.util.*
-import kotlin.reflect.jvm.internal.impl.types.AbstractStubType
 
 /**
  * Allowed lengths for fractional digits in amounts.
@@ -38,13 +39,15 @@ enum class FracDigits(howMany: Int) {
 /**
  * Timestamp containing the number of seconds since epoch.
  */
-@Serializable
+@Serializable(with = TalerProtocolTimestampSerializer::class)
 data class TalerProtocolTimestamp(
-    val t_s: Long, // FIXME (?): not supporting "never" at the moment.
+    val t_s: Instant,
 ) {
     companion object {
         fun fromMicroseconds(uSec: Long): TalerProtocolTimestamp {
-            return TalerProtocolTimestamp(uSec / 1000000)
+            return TalerProtocolTimestamp(
+                Instant.EPOCH.plus(uSec, ChronoUnit.MICROS)
+            )
         }
     }
 }
@@ -101,8 +104,9 @@ data class RegisterAccountRequest(
  * Internal representation of relative times.  The
  * "forever" case is represented with Long.MAX_VALUE.
  */
+@Serializable(with = RelativeTimeSerializer::class)
 data class RelativeTime(
-    val d_us: Long
+    val d_us: Duration
 )
 
 /**
@@ -112,7 +116,6 @@ data class RelativeTime(
 @Serializable
 data class TokenRequest(
     val scope: TokenScope,
-    @Contextual
     val duration: RelativeTime? = null,
     val refreshable: Boolean = false
 )
@@ -169,6 +172,7 @@ data class Customer(
  * maybeCurrency is typically null when the TalerAmount object gets
  * defined by the Database class.
  */
+@Serializable(with = TalerAmountSerializer::class)
 class TalerAmount(
     val value: Long,
     val frac: Int,
@@ -241,8 +245,8 @@ data class BearerToken(
     val content: ByteArray,
     val scope: TokenScope,
     val isRefreshable: Boolean = false,
-    val creationTime: Long,
-    val expirationTime: Long,
+    val creationTime: Instant,
+    val expirationTime: Instant,
     /**
      * Serial ID of the database row that hosts the bank customer
      * that is associated with this token.  NOTE: if the token is
@@ -266,7 +270,7 @@ data class BankInternalTransaction(
     val debtorAccountId: Long,
     val subject: String,
     val amount: TalerAmount,
-    val transactionDate: Long,
+    val transactionDate: Instant,
     val accountServicerReference: String = "not used", // ISO20022
     val endToEndId: String = "not used", // ISO20022
     val paymentInformationId: String = "not used" // ISO20022
@@ -284,7 +288,7 @@ data class BankAccountTransaction(
     val debtorName: String,
     val subject: String,
     val amount: TalerAmount,
-    val transactionDate: Long, // microseconds
+    val transactionDate: Instant,
     /**
      * Is the transaction debit, or credit for the
      * bank account pointed by this object?
@@ -334,8 +338,8 @@ data class Cashout(
     val sellAtRatio: Int,
     val sellOutFee: TalerAmount,
     val subject: String,
-    val creationTime: Long,
-    val tanConfirmationTime: Long? = null,
+    val creationTime: Instant,
+    val tanConfirmationTime: Instant? = null,
     val tanChannel: TanChannel,
     val tanCode: String,
     val bankAccount: Long,
@@ -592,7 +596,6 @@ data class IncomingReserveTransaction(
 @Serializable
 data class TransferRequest(
     val request_uid: String,
-    @Contextual
     val amount: TalerAmount,
     val exchange_base_url: String,
     val wtid: String,
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
index a523b291..f4286e58 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
@@ -10,6 +10,9 @@ import net.taler.wallet.crypto.Base32Crockford
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import tech.libeufin.util.*
+import java.time.Duration
+import java.time.Instant
+import java.time.temporal.ChronoUnit
 import java.util.*
 
 private val logger: Logger = 
LoggerFactory.getLogger("tech.libeufin.bank.accountsMgmtHandlers")
@@ -50,29 +53,36 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: 
BankApplicationContext) {
         val tokenBytes = ByteArray(32).apply {
             Random().nextBytes(this)
         }
-        val tokenDurationUs = req.duration?.d_us ?: TOKEN_DEFAULT_DURATION_US
+        val tokenDuration: Duration = req.duration?.d_us ?: 
TOKEN_DEFAULT_DURATION
+
+        val creationTime = Instant.now()
+        val expirationTimestamp = if (tokenDuration == 
ChronoUnit.FOREVER.duration) {
+            Instant.MAX
+        } else {
+            try {
+                creationTime.plus(tokenDuration)
+            } catch (e: Exception) {
+                logger.error("Could not add token duration to current time: 
${e.message}")
+                throw badRequest("Bad token duration: ${e.message}")
+            }
+        }
         val customerDbRow = customer.dbRowId ?: throw internalServerError(
             "Could not get customer '${customer.login}' database row ID"
         )
-        val creationTime = getNowUs()
-        val expirationTimestampUs: Long = creationTime + tokenDurationUs
-        if (expirationTimestampUs < tokenDurationUs) throw badRequest(
-            "Token duration caused arithmetic overflow", // FIXME: need 
dedicate EC (?)
-            talerErrorCode = TalerErrorCode.TALER_EC_END
-        )
         val token = BearerToken(
             bankCustomer = customerDbRow,
             content = tokenBytes,
             creationTime = creationTime,
-            expirationTime = expirationTimestampUs,
+            expirationTime = expirationTimestamp,
             scope = req.scope,
             isRefreshable = req.refreshable
         )
-        if (!db.bearerTokenCreate(token)) throw internalServerError("Failed at 
inserting new token in the database")
+        if (!db.bearerTokenCreate(token))
+            throw internalServerError("Failed at inserting new token in the 
database")
         call.respond(
             TokenSuccessResponse(
                 access_token = Base32Crockford.encode(tokenBytes), expiration 
= TalerProtocolTimestamp(
-                    t_s = expirationTimestampUs / 1000000L
+                    t_s = expirationTimestamp
                 )
             )
         )
@@ -167,7 +177,7 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: 
BankApplicationContext) {
                 debtorAccountId = adminBankAccount.expectRowId(),
                 amount = bonusAmount,
                 subject = "Registration bonus.",
-                transactionDate = getNowUs()
+                transactionDate = Instant.now()
             )
             when (db.bankTransactionCreate(adminPaysBonus)) {
                 Database.BankTransactionResult.NO_CREDITOR -> throw 
internalServerError("Bonus impossible: creditor not found, despite its recent 
creation.")
@@ -294,11 +304,12 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: 
BankApplicationContext) {
         // to the selected state _and_ wire the funds to the exchange.
         // Note: 'when' helps not to omit more result codes, should more
         // be added.
-        when (db.talerWithdrawalConfirm(op.withdrawalUuid, getNowUs())) {
-            WithdrawalConfirmationResult.BALANCE_INSUFFICIENT -> throw 
conflict(
-                "Insufficient funds", TalerErrorCode.TALER_EC_END // FIXME: 
define EC for this.
-            )
-
+        when (db.talerWithdrawalConfirm(op.withdrawalUuid, Instant.now())) {
+            WithdrawalConfirmationResult.BALANCE_INSUFFICIENT ->
+                throw conflict(
+                "Insufficient funds",
+                    TalerErrorCode.TALER_EC_END // FIXME: define EC for this.
+                )
             WithdrawalConfirmationResult.OP_NOT_FOUND ->
                 /**
                  * Despite previous checks, the database _still_ did not
@@ -345,7 +356,7 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: 
BankApplicationContext) {
                     subject = it.subject,
                     amount = it.amount.toString(),
                     direction = it.direction,
-                    date = 
TalerProtocolTimestamp.fromMicroseconds(it.transactionDate),
+                    date = TalerProtocolTimestamp(it.transactionDate),
                     row_id = it.dbRowId ?: throw internalServerError(
                         "Transaction timestamped with '${it.transactionDate}' 
did not have row ID"
                     )
@@ -380,7 +391,7 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: 
BankApplicationContext) {
             creditorAccountId = creditorCustomerData.owningCustomerId,
             subject = subject,
             amount = amount,
-            transactionDate = getNowUs()
+            transactionDate = Instant.now()
         )
         val res = db.bankTransactionCreate(dbInstructions)
         when (res) {
@@ -420,7 +431,7 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: 
BankApplicationContext) {
                 amount = 
"${tx.amount.currency}:${tx.amount.value}.${tx.amount.frac}",
                 creditor_payto_uri = tx.creditorPaytoUri,
                 debtor_payto_uri = tx.debtorPaytoUri,
-                date = 
TalerProtocolTimestamp.fromMicroseconds(tx.transactionDate),
+                date = TalerProtocolTimestamp(tx.transactionDate),
                 direction = tx.direction,
                 subject = tx.subject,
                 row_id = txRowId
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
index 110f3a1e..c5f06190 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
@@ -24,11 +24,14 @@ import org.postgresql.jdbc.PgConnection
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import tech.libeufin.util.getJdbcConnectionFromPg
+import tech.libeufin.util.microsToJavaInstant
+import tech.libeufin.util.toDbMicros
 import java.io.File
 import java.sql.DriverManager
 import java.sql.PreparedStatement
 import java.sql.ResultSet
 import java.sql.SQLException
+import java.time.Instant
 import java.util.*
 import kotlin.math.abs
 
@@ -41,6 +44,25 @@ fun BankAccountTransaction.expectRowId(): Long = 
this.dbRowId ?: throw internalS
 
 private val logger: Logger = 
LoggerFactory.getLogger("tech.libeufin.bank.Database")
 
+/**
+ * This error occurs in case the timestamp took by the bank for some
+ * event could not be converted in microseconds.  Note: timestamp are
+ * taken via the Instant.now(), then converted to nanos, and then divided
+ * by 1000 to obtain the micros.
+ *
+ * It could be that a legitimate timestamp overflows in the process of
+ * being converted to micros - as described above.  In the case of a timestamp,
+ * the fault lies to the bank, because legitimate timestamps must (at the
+ * time of writing!) go through the conversion to micros.
+ *
+ * On the other hand (and for the sake of completeness), in the case of a
+ * timestamp that was calculated after a client-submitted duration, the 
overflow
+ * lies to the client, because they must have specified a gigantic amount of 
time
+ * that overflew the conversion to micros and should simply have specified 
"forever".
+ */
+private fun faultyTimestampByBank() = internalServerError("Bank took 
overflowing timestamp")
+private fun faultyDurationByClient() = badRequest("Overflowing duration, 
please specify 'forever' instead.")
+
 fun initializeDatabaseTables(dbConfig: String, sqlDir: String) {
     logger.info("doing DB initialization, sqldir $sqlDir, dbConfig $dbConfig")
     val jdbcConnStr = getJdbcConnectionFromPg(dbConfig)
@@ -299,8 +321,8 @@ class Database(private val dbConfig: String, private val 
bankCurrency: String) {
                (?, ?, ?, ?::token_scope_enum, ?, ?)
         """)
         stmt.setBytes(1, token.content)
-        stmt.setLong(2, token.creationTime)
-        stmt.setLong(3, token.expirationTime)
+        stmt.setLong(2, token.creationTime.toDbMicros() ?: throw 
faultyTimestampByBank())
+        stmt.setLong(3, token.expirationTime.toDbMicros() ?: throw 
faultyDurationByClient())
         stmt.setString(4, token.scope.name)
         stmt.setLong(5, token.bankCustomer)
         stmt.setBoolean(6, token.isRefreshable)
@@ -318,14 +340,13 @@ class Database(private val dbConfig: String, private val 
bankCurrency: String) {
             FROM bearer_tokens
             WHERE content=?;            
         """)
-
         stmt.setBytes(1, token)
         stmt.executeQuery().use {
             if (!it.next()) return null
             return BearerToken(
                 content = token,
-                creationTime = it.getLong("creation_time"),
-                expirationTime = it.getLong("expiration_time"),
+                creationTime = 
it.getLong("creation_time").microsToJavaInstant() ?: throw 
faultyTimestampByBank(),
+                expirationTime = 
it.getLong("expiration_time").microsToJavaInstant() ?: throw 
faultyDurationByClient(),
                 bankCustomer = it.getLong("bank_customer"),
                 scope = it.getString("scope").run {
                     if (this == TokenScope.readwrite.name) return@run 
TokenScope.readwrite
@@ -512,7 +533,7 @@ class Database(private val dbConfig: String, private val 
bankCurrency: String) {
         stmt.setString(3, tx.subject)
         stmt.setLong(4, tx.amount.value)
         stmt.setInt(5, tx.amount.frac)
-        stmt.setLong(6, tx.transactionDate)
+        stmt.setLong(6, tx.transactionDate.toDbMicros() ?: throw 
faultyTimestampByBank())
         stmt.setString(7, tx.accountServicerReference)
         stmt.setString(8, tx.paymentInformationId)
         stmt.setString(9, tx.endToEndId)
@@ -604,7 +625,7 @@ class Database(private val dbConfig: String, private val 
bankCurrency: String) {
                 bankAccountId = it.getLong("bank_account_id"),
                 paymentInformationId = it.getString("payment_information_id"),
                 subject = it.getString("subject"),
-                transactionDate = it.getLong("transaction_date")
+                transactionDate = 
it.getLong("transaction_date").microsToJavaInstant() ?: throw 
faultyTimestampByBank()
             )
         }
     }
@@ -700,7 +721,7 @@ class Database(private val dbConfig: String, private val 
bankCurrency: String) {
                         bankAccountId = it.getLong("bank_account_id"),
                         paymentInformationId = 
it.getString("payment_information_id"),
                         subject = it.getString("subject"),
-                        transactionDate = it.getLong("transaction_date"),
+                        transactionDate = 
it.getLong("transaction_date").microsToJavaInstant() ?: throw 
faultyTimestampByBank(),
                         dbRowId = it.getLong("bank_transaction_id")
                 ))
             } while (it.next())
@@ -811,11 +832,12 @@ class Database(private val dbConfig: String, private val 
bankCurrency: String) {
     }
 
     /**
-     *
+     * Confirms a Taler withdrawal: flags the operation as
+     * confirmed and performs the related wire transfer.
      */
     fun talerWithdrawalConfirm(
         opUuid: UUID,
-        timestamp: Long,
+        timestamp: Instant,
         accountServicerReference: String = "NOT-USED",
         endToEndId: String = "NOT-USED",
         paymentInfId: String = "NOT-USED"
@@ -831,7 +853,7 @@ class Database(private val dbConfig: String, private val 
bankCurrency: String) {
         """
         )
         stmt.setObject(1, opUuid)
-        stmt.setLong(2, timestamp)
+        stmt.setLong(2, timestamp.toDbMicros() ?: throw 
faultyTimestampByBank())
         stmt.setString(3, accountServicerReference)
         stmt.setString(4, endToEndId)
         stmt.setString(5, paymentInfId)
@@ -847,6 +869,9 @@ class Database(private val dbConfig: String, private val 
bankCurrency: String) {
         return WithdrawalConfirmationResult.SUCCESS
     }
 
+    /**
+     * Creates a cashout operation in the database.
+     */
     fun cashoutCreate(op: Cashout): Boolean {
         reconnect()
         val stmt = prepare("""
@@ -895,7 +920,7 @@ class Database(private val dbConfig: String, private val 
bankCurrency: String) {
         stmt.setLong(10, op.sellOutFee.value)
         stmt.setInt(11, op.sellOutFee.frac)
         stmt.setString(12, op.subject)
-        stmt.setLong(13, op.creationTime)
+        stmt.setLong(13, op.creationTime.toDbMicros() ?: throw 
faultyTimestampByBank())
         stmt.setString(14, op.tanChannel.name)
         stmt.setString(15, op.tanCode)
         stmt.setLong(16, op.bankAccount)
@@ -904,6 +929,11 @@ class Database(private val dbConfig: String, private val 
bankCurrency: String) {
         return myExecute(stmt)
     }
 
+    /**
+     * Flags one cashout operation as confirmed.  The backing
+     * payment should already have taken place, before calling
+     * this function.
+     */
     fun cashoutConfirm(
         opUuid: UUID,
         tanConfirmationTimestamp: Long,
@@ -920,11 +950,18 @@ class Database(private val dbConfig: String, private val 
bankCurrency: String) {
         stmt.setObject(3, opUuid)
         return myExecute(stmt)
     }
-    // used by /abort
+
+    /**
+     * This type is used by the cashout /abort handler.
+     */
     enum class CashoutDeleteResult {
         SUCCESS,
         CONFLICT_ALREADY_CONFIRMED
     }
+
+    /**
+     * Deletes a cashout operation from the database.
+     */
     fun cashoutDelete(opUuid: UUID): CashoutDeleteResult {
         val stmt = prepare("""
            SELECT out_already_confirmed
@@ -939,6 +976,11 @@ class Database(private val dbConfig: String, private val 
bankCurrency: String) {
             return CashoutDeleteResult.SUCCESS
         }
     }
+
+    /**
+     * Gets a cashout operation from the database, according
+     * to its uuid.
+     */
     fun cashoutGetFromUuid(opUuid: UUID): Cashout? {
         val stmt = prepare("""
            SELECT
@@ -988,7 +1030,7 @@ class Database(private val dbConfig: String, private val 
bankCurrency: String) {
                 credit_payto_uri = it.getString("credit_payto_uri"),
                 cashoutCurrency = it.getString("cashout_currency"),
                 cashoutUuid = opUuid,
-                creationTime = it.getLong("creation_time"),
+                creationTime = 
it.getLong("creation_time").microsToJavaInstant() ?: throw 
faultyTimestampByBank(),
                 sellAtRatio = it.getInt("sell_at_ratio"),
                 sellOutFee = TalerAmount(
                     value = it.getLong("sell_out_fee_val"),
@@ -1008,13 +1050,17 @@ class Database(private val dbConfig: String, private 
val bankCurrency: String) {
                 localTransaction = it.getLong("local_transaction"),
                 tanConfirmationTime = it.getLong("tan_confirmation_time").run {
                     if (this == 0L) return@run null
-                    return@run this
+                    return@run this.microsToJavaInstant() ?: throw 
faultyTimestampByBank()
                 }
             )
         }
     }
+
+    /**
+     * Represents the database row related to one payment
+     * that was requested by the Taler exchange.
+     */
     data class TalerTransferFromDb(
-        // Only used when this type if defined from a DB record
         val timestamp: Long,
         val debitTxRowId: Long,
         val requestUid: String,
@@ -1023,7 +1069,9 @@ class Database(private val dbConfig: String, private val 
bankCurrency: String) {
         val wtid: String,
         val creditAccount: String
     )
-    // Gets a Taler transfer request, given its UID.
+    /**
+     * Gets a Taler transfer request, given its UID.
+     */
     fun talerTransferGetFromUid(requestUid: String): TalerTransferFromDb? {
         reconnect()
         val stmt = prepare("""
@@ -1060,10 +1108,15 @@ class Database(private val dbConfig: String, private 
val bankCurrency: String) {
         }
     }
 
+    /**
+     * Holds the result of inserting a Taler transfer request
+     * into the database.
+     */
     data class TalerTransferCreationResult(
         val txResult: BankTransactionResult,
-        // Row ID of the debit bank transaction
-        // of a successful case.  Null upon errors
+        /**
+         * bank transaction that backs this Taler transfer request.
+         */
         val txRowId: Long? = null
     )
     /**
@@ -1080,7 +1133,7 @@ class Database(private val dbConfig: String, private val 
bankCurrency: String) {
     fun talerTransferCreate(
         req: TransferRequest,
         exchangeBankAccountId: Long,
-        timestamp: Long,
+        timestamp: Instant,
         acctSvcrRef: String = "not used",
         pmtInfId: String = "not used",
         endToEndId: String = "not used",
@@ -1113,7 +1166,7 @@ class Database(private val dbConfig: String, private val 
bankCurrency: String) {
         stmt.setString(5, req.exchange_base_url)
         stmt.setString(6, req.credit_account)
         stmt.setLong(7, exchangeBankAccountId)
-        stmt.setLong(8, timestamp)
+        stmt.setLong(8, timestamp.toDbMicros() ?: throw 
faultyTimestampByBank())
         stmt.setString(9, acctSvcrRef)
         stmt.setString(10, pmtInfId)
         stmt.setString(11, endToEndId)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
index c556b0f2..a8ef3e81 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
@@ -51,21 +51,23 @@ import kotlinx.coroutines.withContext
 import kotlinx.serialization.descriptors.*
 import kotlinx.serialization.encoding.Decoder
 import kotlinx.serialization.encoding.Encoder
+import kotlinx.serialization.encoding.encodeStructure
 import kotlinx.serialization.json.*
-import kotlinx.serialization.modules.SerializersModule
 import net.taler.common.errorcodes.TalerErrorCode
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import org.slf4j.event.Level
 import tech.libeufin.util.*
 import java.time.Duration
+import java.time.Instant
+import java.time.temporal.ChronoUnit
 import java.util.zip.InflaterInputStream
 import kotlin.system.exitProcess
 
 // GLOBALS
 private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.bank.Main")
 const val GENERIC_UNDEFINED = -1 // Filler for ECs that don't exist yet.
-val TOKEN_DEFAULT_DURATION_US = Duration.ofDays(1L).seconds * 1000000
+val TOKEN_DEFAULT_DURATION: java.time.Duration = Duration.ofDays(1L)
 
 
 /**
@@ -115,6 +117,58 @@ data class BankApplicationContext(
     val spaCaptchaURL: String?,
 )
 
+
+/**
+ * This custom (de)serializer interprets the Timestamp JSON
+ * type of the Taler common API.  In particular, it is responsible
+ * for _serializing_ timestamps, as this datatype is so far
+ * only used to respond to clients.
+ */
+object TalerProtocolTimestampSerializer : KSerializer<TalerProtocolTimestamp> {
+    override fun serialize(encoder: Encoder, value: TalerProtocolTimestamp) {
+        // Thanks: 
https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#hand-written-composite-serializer
+        encoder.encodeStructure(descriptor) {
+            if (value.t_s == Instant.MAX) {
+                encodeStringElement(descriptor, 0, "never")
+                return@encodeStructure
+            }
+            encodeLongElement(descriptor, 0, value.t_s.epochSecond)
+        }
+    }
+
+    override fun deserialize(decoder: Decoder): TalerProtocolTimestamp {
+        val jsonInput = decoder as? JsonDecoder ?: throw 
internalServerError("TalerProtocolTimestamp had no JsonDecoder")
+        val json = try {
+            jsonInput.decodeJsonElement().jsonObject
+        } catch (e: Exception) {
+            throw badRequest(
+                "Did not find a JSON object for TalerProtocolTimestamp: 
${e.message}",
+                TalerErrorCode.TALER_EC_GENERIC_JSON_INVALID
+            )
+        }
+        val maybeTs = json["t_s"]?.jsonPrimitive ?: throw badRequest("Taler 
timestamp invalid: t_s field not found")
+        if (maybeTs.isString) {
+            if (maybeTs.content != "never") throw badRequest("Only 'never' 
allowed for t_s as string, but '${maybeTs.content}' was found")
+            return TalerProtocolTimestamp(t_s = Instant.MAX)
+        }
+        val ts: Long = maybeTs.longOrNull
+            ?: throw badRequest("Could not convert t_s '${maybeTs.content}' to 
a number")
+        val instant = try {
+            Instant.ofEpochSecond(ts)
+        } catch (e: Exception) {
+            logger.error("Could not get Instant from t_s: $ts: ${e.message}")
+            // Bank's fault.  API doesn't allow clients to pass this datatype.
+            throw internalServerError("Could not serialize this t_s: ${ts}")
+        }
+        return TalerProtocolTimestamp(instant)
+    }
+
+    override val descriptor: SerialDescriptor =
+        buildClassSerialDescriptor("TalerProtocolTimestamp") {
+            element<JsonElement>("t_s")
+        }
+}
+
 /**
  * This custom (de)serializer interprets the RelativeTime JSON
  * type.  In particular, it is responsible for converting the
@@ -122,10 +176,30 @@ data class BankApplicationContext(
  * is passed as is.
  */
 object RelativeTimeSerializer : KSerializer<RelativeTime> {
+    /**
+     * Internal representation to JSON.
+     */
     override fun serialize(encoder: Encoder, value: RelativeTime) {
-        throw internalServerError("Encoding of RelativeTime not implemented.") 
// API doesn't require this.
+        // Thanks: 
https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#hand-written-composite-serializer
+        encoder.encodeStructure(descriptor) {
+            if (value.d_us == ChronoUnit.FOREVER.duration) {
+                encodeStringElement(descriptor, 0, "forever")
+                return@encodeStructure
+            }
+            val dUs = try {
+                value.d_us.toNanos()
+            } catch (e: Exception) {
+                logger.error(e.message)
+                // Bank's fault, as each numeric value should be checked 
before entering the system.
+                throw internalServerError("Could not convert 
java.time.Duration to JSON")
+            }
+            encodeLongElement(descriptor, 0, dUs / 1000L)
+        }
     }
 
+    /**
+     * JSON to internal representation.
+     */
     override fun deserialize(decoder: Decoder): RelativeTime {
         val jsonInput = decoder as? JsonDecoder ?: throw 
internalServerError("RelativeTime had no JsonDecoder")
         val json = try {
@@ -139,15 +213,22 @@ object RelativeTimeSerializer : KSerializer<RelativeTime> 
{
         val maybeDUs = json["d_us"]?.jsonPrimitive ?: throw 
badRequest("Relative time invalid: d_us field not found")
         if (maybeDUs.isString) {
             if (maybeDUs.content != "forever") throw badRequest("Only 
'forever' allowed for d_us as string, but '${maybeDUs.content}' was found")
-            return RelativeTime(d_us = Long.MAX_VALUE)
+            return RelativeTime(d_us = ChronoUnit.FOREVER.duration)
+        }
+        val dUs: Long = maybeDUs.longOrNull
+            ?: throw badRequest("Could not convert d_us: '${maybeDUs.content}' 
to a number")
+        val duration = try {
+            Duration.ofNanos(dUs * 1000L)
+        } catch (e: Exception) {
+            logger.error("Could not get Duration out of d_us content: ${dUs}. 
${e.message}")
+            throw badRequest("Could not get Duration out of d_us content: 
${dUs}")
         }
-        val dUs: Long =
-            maybeDUs.longOrNull ?: throw badRequest("Could not convert d_us: 
'${maybeDUs.content}' to a number")
-        return RelativeTime(d_us = dUs)
+        return RelativeTime(d_us = duration)
     }
 
     override val descriptor: SerialDescriptor =
         buildClassSerialDescriptor("RelativeTime") {
+            // JsonElement helps to obtain "union" type Long|String
             element<JsonElement>("d_us")
         }
 }
@@ -232,15 +313,6 @@ fun Application.corebankWebApp(db: Database, ctx: 
BankApplicationContext) {
             encodeDefaults = true
             prettyPrint = true
             ignoreUnknownKeys = true
-            // Registering custom parser for RelativeTime
-            serializersModule = SerializersModule {
-                contextual(RelativeTime::class) {
-                    RelativeTimeSerializer
-                }
-                contextual(TalerAmount::class) {
-                    TalerAmountSerializer
-                }
-            }
         })
     }
     install(RequestValidation)
@@ -295,6 +367,9 @@ fun Application.corebankWebApp(db: Database, ctx: 
BankApplicationContext) {
          */
         exception<LibeufinBankException> { call, cause ->
             logger.error(cause.talerError.hint)
+            // Stacktrace if bank's fault
+            if (cause.httpStatus.toString().startsWith('5'))
+                cause.printStackTrace()
             call.respond(
                 status = cause.httpStatus,
                 message = cause.talerError
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApiHandlers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApiHandlers.kt
index 9c5cd9af..b040883d 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApiHandlers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApiHandlers.kt
@@ -27,7 +27,7 @@ import io.ktor.server.request.*
 import io.ktor.server.response.*
 import io.ktor.server.routing.*
 import net.taler.common.errorcodes.TalerErrorCode
-import tech.libeufin.util.getNowUs
+import java.time.Instant
 
 fun Routing.talerWireGatewayHandlers(db: Database, ctx: 
BankApplicationContext) {
     get("/taler-wire-gateway/config") {
@@ -60,7 +60,7 @@ fun Routing.talerWireGatewayHandlers(db: Database, ctx: 
BankApplicationContext)
                 IncomingReserveTransaction(
                     row_id = it.expectRowId(),
                     amount = it.amount.toString(),
-                    date = 
TalerProtocolTimestamp.fromMicroseconds(it.transactionDate),
+                    date = TalerProtocolTimestamp(it.transactionDate),
                     debit_account = it.debtorPaytoUri,
                     reserve_pub = it.subject
                 )
@@ -102,7 +102,7 @@ fun Routing.talerWireGatewayHandlers(db: Database, ctx: 
BankApplicationContext)
             throw badRequest("Currency mismatch: $internalCurrency vs 
${req.amount.currency}")
         val exchangeBankAccount = db.bankAccountGetFromOwnerId(c.expectRowId())
             ?: throw internalServerError("Exchange does not have a bank 
account")
-        val transferTimestamp = getNowUs()
+        val transferTimestamp = Instant.now()
         val dbRes = db.talerTransferCreate(
             req = req,
             exchangeBankAccountId = exchangeBankAccount.expectRowId(),
@@ -122,7 +122,7 @@ fun Routing.talerWireGatewayHandlers(db: Database, ctx: 
BankApplicationContext)
             ?: throw internalServerError("Database did not return the debit tx 
row ID")
         call.respond(
             TransferResponse(
-                timestamp = 
TalerProtocolTimestamp.fromMicroseconds(transferTimestamp),
+                timestamp = TalerProtocolTimestamp(transferTimestamp),
                 row_id = debitRowId
             )
         )
@@ -152,7 +152,7 @@ fun Routing.talerWireGatewayHandlers(db: Database, ctx: 
BankApplicationContext)
             )
         val exchangeAccount = db.bankAccountGetFromOwnerId(c.expectRowId())
             ?: throw internalServerError("exchange bank account not found, 
despite it's a customer")
-        val txTimestamp = getNowUs()
+        val txTimestamp = Instant.now()
         val op = BankInternalTransaction(
             debtorAccountId = walletAccount.expectRowId(),
             amount = amount,
@@ -175,7 +175,7 @@ fun Routing.talerWireGatewayHandlers(db: Database, ctx: 
BankApplicationContext)
         call.respond(
             AddIncomingResponse(
                 row_id = rowId,
-                timestamp = 
TalerProtocolTimestamp.fromMicroseconds(txTimestamp)
+                timestamp = TalerProtocolTimestamp(txTimestamp)
             )
         )
         return@post
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
index 34a966d8..3007021b 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
@@ -30,6 +30,7 @@ import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import tech.libeufin.util.*
 import java.net.URL
+import java.time.Instant
 import java.util.*
 
 const val FRACTION_BASE = 100000000
@@ -117,7 +118,7 @@ fun doTokenAuth(
         logger.error("Auth token not found")
         return null
     }
-    if (maybeToken.expirationTime - getNowUs() < 0) {
+    if (maybeToken.expirationTime.isBefore(Instant.now())) {
         logger.error("Auth token is expired")
         return null
     }
@@ -437,4 +438,4 @@ fun maybeCreateAdminAccount(db: Database, ctx: 
BankApplicationContext): Boolean
         }
     }
     return true
-}
+}
\ No newline at end of file
diff --git a/bank/src/test/kotlin/DatabaseTest.kt 
b/bank/src/test/kotlin/DatabaseTest.kt
index 45466947..890ec41a 100644
--- a/bank/src/test/kotlin/DatabaseTest.kt
+++ b/bank/src/test/kotlin/DatabaseTest.kt
@@ -20,7 +20,7 @@
 import org.junit.Test
 import tech.libeufin.bank.*
 import tech.libeufin.util.CryptoUtil
-import tech.libeufin.util.getNowUs
+import java.time.Instant
 import java.util.Random
 import java.util.UUID
 
@@ -38,7 +38,7 @@ fun genTx(
         accountServicerReference = "acct-svcr-ref",
         endToEndId = "end-to-end-id",
         paymentInformationId = "pmtinfid",
-        transactionDate = 100000L
+        transactionDate = Instant.now()
     )
 
 class DatabaseTest {
@@ -123,7 +123,7 @@ class DatabaseTest {
         val res = db.talerTransferCreate(
             req = exchangeReq,
             exchangeBankAccountId = 1L,
-            timestamp = getNowUs()
+            timestamp = Instant.now()
         )
         assert(res.txResult == Database.BankTransactionResult.SUCCESS)
     }
@@ -136,8 +136,8 @@ class DatabaseTest {
         val token = BearerToken(
             bankCustomer = 1L,
             content = tokenBytes,
-            creationTime = getNowUs(), // make .toMicro()? implicit?
-            expirationTime = getNowUs(),
+            creationTime = Instant.now(),
+            expirationTime = Instant.now().plusSeconds(10),
             scope = TokenScope.readonly
         )
         assert(db.bearerTokenGet(token.content) == null)
@@ -197,7 +197,7 @@ class DatabaseTest {
             accountServicerReference = "acct-svcr-ref",
             endToEndId = "end-to-end-id",
             paymentInformationId = "pmtinfid",
-            transactionDate = 100000L
+            transactionDate = Instant.now()
         )
         val barPays = db.bankTransactionCreate(barPaysFoo)
         assert(barPays == Database.BankTransactionResult.SUCCESS)
@@ -273,7 +273,7 @@ class DatabaseTest {
         ))
         val opSelected = db.talerWithdrawalGet(uuid)
         assert(opSelected?.selectionDone == true && 
!opSelected.confirmationDone)
-        assert(db.talerWithdrawalConfirm(uuid, 1L) == 
WithdrawalConfirmationResult.SUCCESS)
+        assert(db.talerWithdrawalConfirm(uuid, Instant.now()) == 
WithdrawalConfirmationResult.SUCCESS)
         // Finally confirming the operation (means customer wired funds to the 
exchange.)
         assert(db.talerWithdrawalGet(uuid)?.confirmationDone == true)
     }
@@ -316,10 +316,10 @@ class DatabaseTest {
             sellOutFee = TalerAmount(0, 44, currency),
             credit_payto_uri = "IBAN",
             cashoutCurrency = "KUDOS",
-            creationTime = 3L,
+            creationTime = Instant.now(),
             subject = "31st",
             tanChannel = TanChannel.sms,
-            tanCode = "secret",
+            tanCode = "secret"
         )
         val fooId = db.customerCreate(customerFoo)
         assert(fooId != null)
@@ -344,7 +344,7 @@ class DatabaseTest {
             accountServicerReference = "acct-svcr-ref",
             endToEndId = "end-to-end-id",
             paymentInformationId = "pmtinfid",
-            transactionDate = 100000L
+            transactionDate = Instant.now()
         )
         ) == Database.BankTransactionResult.SUCCESS)
         // Confirming the cash-out
diff --git a/bank/src/test/kotlin/JsonTest.kt b/bank/src/test/kotlin/JsonTest.kt
index 63eff6f9..263ed613 100644
--- a/bank/src/test/kotlin/JsonTest.kt
+++ b/bank/src/test/kotlin/JsonTest.kt
@@ -1,12 +1,11 @@
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.encodeToString
 import kotlinx.serialization.json.Json
-import kotlinx.serialization.modules.SerializersModule
 import org.junit.Test
-import tech.libeufin.bank.RelativeTime
-import tech.libeufin.bank.RelativeTimeSerializer
-import tech.libeufin.bank.TokenRequest
-import tech.libeufin.bank.TokenScope
+import tech.libeufin.bank.*
+import java.time.Duration
+import java.time.Instant
+import java.time.temporal.ChronoUnit
 
 @Serializable
 data class MyJsonType(
@@ -27,23 +26,24 @@ class JsonTest {
         """.trimIndent()
         Json.decodeFromString<MyJsonType>(serialized)
     }
+
+    /**
+     * Testing the custom absolute and relative time serializers.
+     */
     @Test
-    fun unionTypeTest() {
-        val jsonCfg = Json {
-            serializersModule = SerializersModule {
-                contextual(RelativeTime::class) {
-                    RelativeTimeSerializer
-                }
-            }
-        }
-        assert(jsonCfg.decodeFromString<RelativeTime>("{\"d_us\": 3}").d_us == 
3L)
-        assert(jsonCfg.decodeFromString<RelativeTime>("{\"d_us\": 
\"forever\"}").d_us == Long.MAX_VALUE)
-        val tokenReq = jsonCfg.decodeFromString<TokenRequest>("""
-            {
-              "scope": "readonly",
-              "duration": {"d_us": 30}
-            }
-        """.trimIndent())
-        assert(tokenReq.scope == TokenScope.readonly && 
tokenReq.duration?.d_us == 30L)
+    fun timeSerializers() {
+        // from JSON to time types
+        assert(Json.decodeFromString<RelativeTime>("{\"d_us\": 
3}").d_us.toNanos() == 3000L)
+        assert(Json.decodeFromString<RelativeTime>("{\"d_us\": 
\"forever\"}").d_us == ChronoUnit.FOREVER.duration)
+        assert(Json.decodeFromString<TalerProtocolTimestamp>("{\"t_s\": 
3}").t_s == Instant.ofEpochSecond(3))
+        assert(Json.decodeFromString<TalerProtocolTimestamp>("{\"t_s\": 
\"never\"}").t_s == Instant.MAX)
+
+        // from time types to JSON
+        val oneDay = RelativeTime(d_us = Duration.of(1, ChronoUnit.DAYS))
+        val oneDaySerial = Json.encodeToString(oneDay)
+        assert(Json.decodeFromString<RelativeTime>(oneDaySerial).d_us == 
oneDay.d_us)
+        val forever = RelativeTime(d_us = ChronoUnit.FOREVER.duration)
+        val foreverSerial = Json.encodeToString(forever)
+        assert(Json.decodeFromString<RelativeTime>(foreverSerial).d_us == 
forever.d_us)
     }
 }
\ No newline at end of file
diff --git a/bank/src/test/kotlin/LibeuFinApiTest.kt 
b/bank/src/test/kotlin/LibeuFinApiTest.kt
index 4cd2323f..8683fef8 100644
--- a/bank/src/test/kotlin/LibeuFinApiTest.kt
+++ b/bank/src/test/kotlin/LibeuFinApiTest.kt
@@ -8,8 +8,9 @@ import net.taler.wallet.crypto.Base32Crockford
 import org.junit.Test
 import tech.libeufin.bank.*
 import tech.libeufin.util.CryptoUtil
-import tech.libeufin.util.getNowUs
 import java.time.Duration
+import java.time.Instant
+import java.time.temporal.ChronoUnit
 import kotlin.random.Random
 
 class LibeuFinApiTest {
@@ -131,6 +132,31 @@ class LibeuFinApiTest {
         }
     }
 
+    // Creating token with "forever" duration.
+    @Test
+    fun tokenForeverTest() {
+        val db = initDb()
+        val ctx = getTestContext()
+        assert(db.customerCreate(customerFoo) != null)
+        testApplication {
+            application {
+                corebankWebApp(db, ctx)
+            }
+            val newTok = client.post("/accounts/foo/token") {
+                expectSuccess = true
+                contentType(ContentType.Application.Json)
+                basicAuth("foo", "pw")
+                setBody(
+                    """
+                    {"duration": {"d_us": "forever"}, "scope": "readonly"}
+                """.trimIndent()
+                )
+            }
+            val newTokObj = 
Json.decodeFromString<TokenSuccessResponse>(newTok.bodyAsText())
+            assert(newTokObj.expiration.t_s == Instant.MAX)
+        }
+    }
+
     // Checking the POST /token handling.
     @Test
     fun tokenTest() {
@@ -154,8 +180,8 @@ class LibeuFinApiTest {
             // Checking that the token lifetime defaulted to 24 hours.
             val newTokObj = 
Json.decodeFromString<TokenSuccessResponse>(newTok.bodyAsText())
             val newTokDb = 
db.bearerTokenGet(Base32Crockford.decode(newTokObj.access_token))
-            val lifeTime = newTokDb!!.expirationTime - newTokDb.creationTime
-            assert(Duration.ofHours(24).seconds * 1000000 == lifeTime)
+            val lifeTime = Duration.between(newTokDb!!.creationTime, 
newTokDb.expirationTime)
+            assert(lifeTime == Duration.ofDays(1))
             // foo tries on bar endpoint
             val r = client.post("/accounts/bar/token") {
                 expectSuccess = false
@@ -170,9 +196,9 @@ class LibeuFinApiTest {
                         content = fooTok,
                         bankCustomer = 1L, // only foo exists.
                         scope = TokenScope.readonly,
-                        creationTime = getNowUs(),
+                        creationTime = Instant.now(),
                         isRefreshable = true,
-                        expirationTime = getNowUs() + 
(Duration.ofHours(1).toMillis() * 1000)
+                        expirationTime = Instant.now().plus(1, ChronoUnit.DAYS)
                     )
                 )
             )
diff --git a/util/src/main/kotlin/Config.kt b/util/src/main/kotlin/Config.kt
index 62a302a5..c54dc4ed 100644
--- a/util/src/main/kotlin/Config.kt
+++ b/util/src/main/kotlin/Config.kt
@@ -4,8 +4,10 @@ import ch.qos.logback.classic.Level
 import ch.qos.logback.classic.LoggerContext
 import ch.qos.logback.core.util.Loader
 import io.ktor.util.*
+import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 
+val logger: Logger = LoggerFactory.getLogger("tech.libeufin.util")
 /**
  * Putting those values into the 'attributes' container because they
  * are needed by the util routines that do NOT have Sandbox and Nexus
diff --git a/util/src/main/kotlin/DB.kt b/util/src/main/kotlin/DB.kt
index 7f518ebd..af26d3d3 100644
--- a/util/src/main/kotlin/DB.kt
+++ b/util/src/main/kotlin/DB.kt
@@ -33,8 +33,6 @@ import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import java.net.URI
 
-private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.util.DB")
-
 fun getCurrentUser(): String = System.getProperty("user.name")
 
 fun isPostgres(): Boolean {
diff --git a/util/src/main/kotlin/Ebics.kt b/util/src/main/kotlin/Ebics.kt
index 8b78932d..837f49ba 100644
--- a/util/src/main/kotlin/Ebics.kt
+++ b/util/src/main/kotlin/Ebics.kt
@@ -45,8 +45,6 @@ import javax.xml.bind.JAXBElement
 import javax.xml.datatype.DatatypeFactory
 import javax.xml.datatype.XMLGregorianCalendar
 
-private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.util")
-
 data class EbicsProtocolError(
     val httpStatusCode: HttpStatusCode,
     val reason: String,
diff --git a/util/src/main/kotlin/time.kt b/util/src/main/kotlin/time.kt
index 687a97f7..6c1b9464 100644
--- a/util/src/main/kotlin/time.kt
+++ b/util/src/main/kotlin/time.kt
@@ -21,6 +21,59 @@ package tech.libeufin.util
 
 import java.time.*
 import java.time.temporal.ChronoUnit
+import java.util.concurrent.TimeUnit
 
+/**
+ * Converts the 'this' Instant to the number of nanoseconds
+ * since the Epoch.  It returns the result as Long, or null
+ * if one arithmetic overflow occurred.
+ */
+private fun Instant.toNanos(): Long? {
+    val oneSecNanos = ChronoUnit.SECONDS.duration.toNanos()
+    val nanoBase: Long = this.epochSecond * oneSecNanos
+    if (nanoBase != 0L && nanoBase / this.epochSecond != oneSecNanos) {
+        logger.error("Multiplication overflow: could not convert Instant to 
nanos.")
+        return null
+    }
+    val res = nanoBase + this.nano
+    if (res < nanoBase) {
+        logger.error("Addition overflow: could not convert Instant to nanos.")
+        return null
+    }
+    return res
+}
+
+/**
+ * This function converts an Instant input to the
+ * number of microseconds since the Epoch, except that
+ * it yields Long.MAX if the Input is Instant.MAX.
+ *
+ * Takes the name after the way timestamps are designed
+ * in the database: micros since Epoch, or Long.MAX for
+ * "never".
+ *
+ * Returns the Long representation of 'this' or null
+ * if that would overflow.
+ */
+fun Instant.toDbMicros(): Long? {
+    if (this == Instant.MAX)
+        return Long.MAX_VALUE
+    val nanos = this.toNanos() ?: return null
+    return nanos / 1000L
+}
 
-fun getNowUs(): Long = ChronoUnit.MICROS.between(Instant.EPOCH, Instant.now())
\ No newline at end of file
+/**
+ * This helper is typically used to convert a timestamp expressed
+ * in microseconds from the DB back to the Web application.  In case
+ * of _any_ error, it logs it and returns null.
+ */
+fun Long.microsToJavaInstant(): Instant? {
+    if (this == Long.MAX_VALUE)
+        return Instant.MAX
+    return try {
+        Instant.EPOCH.plus(this, ChronoUnit.MICROS)
+    } catch (e: Exception) {
+        logger.error(e.message)
+        return null
+    }
+}
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]