Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1e17c93c6a | |||
| a19da9a161 |
@@ -1,18 +1,30 @@
|
|||||||
name: Gitea Actions Demo
|
name: Gitea Actions
|
||||||
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
|
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
|
||||||
on: [push]
|
on: [push]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
Explore-Gitea-Actions:
|
Gitea-Actions:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
|
- name: prepare
|
||||||
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
|
run: |
|
||||||
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
|
echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
|
||||||
|
echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
|
||||||
|
echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
|
||||||
- name: Check out repository code
|
- name: Check out repository code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
|
- name: Set up Maven
|
||||||
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
|
uses: stCarolas/setup-maven@v5
|
||||||
|
with:
|
||||||
|
maven-version: '3.9.9'
|
||||||
|
- name: Set up JDK 17
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: '17'
|
||||||
|
distribution: 'temurin'
|
||||||
|
cache: maven
|
||||||
|
- name: Build with Maven
|
||||||
|
run: mvn -B package
|
||||||
- name: List files in the repository
|
- name: List files in the repository
|
||||||
run: |
|
run: |
|
||||||
ls ${{ gitea.workspace }}
|
ls ${{ gitea.workspace }}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package dev.darktale
|
||||||
|
|
||||||
|
import dev.darktale.model.Rate
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
|
class AmountByRateCalculator {
|
||||||
|
private val leapYearDuration = BigDecimal.valueOf(366L)
|
||||||
|
private val nonLeapYearDuration = BigDecimal.valueOf(365L)
|
||||||
|
fun calculate(amount: BigDecimal, rate: Rate, dateFrom: LocalDate, dateTo: LocalDate): BigDecimal {
|
||||||
|
var result = amount
|
||||||
|
var curRate = rate.rateOnDate(dateFrom)
|
||||||
|
while (curRate != null && curRate.since < dateTo) {
|
||||||
|
val curDateFrom = max(curRate.since, dateFrom)
|
||||||
|
val curDateTo = min(dateTo, curRate.till)
|
||||||
|
result *= (BigDecimal.ONE + getRateForPeriod(curRate.rate!!, curDateFrom, curDateTo))
|
||||||
|
curRate = rate.rateOnDate(curDateTo.plusDays(1))?.let {
|
||||||
|
if (it === curRate) null else it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getRateForPeriod(rate: BigDecimal, dateFrom: LocalDate, dateTo: LocalDate): BigDecimal {
|
||||||
|
return if (dateFrom.year == dateTo.year) {
|
||||||
|
rate.setScale(20) *
|
||||||
|
(dateTo.dayOfYear - dateFrom.dayOfYear + 1).toBigDecimal() /
|
||||||
|
if (dateFrom.isLeapYear) leapYearDuration else nonLeapYearDuration
|
||||||
|
} else getRateForPeriod(rate, dateFrom, LocalDate.of(dateFrom.year, 12, 31)) +
|
||||||
|
getRateForPeriod(rate, LocalDate.of(dateTo.year, 1, 1), dateTo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun max(localDate1: LocalDate, localDate2: LocalDate?) =
|
||||||
|
if (localDate2 == null || localDate1 >= localDate2) localDate1 else localDate2
|
||||||
|
|
||||||
|
private fun min(localDate1: LocalDate, localDate2: LocalDate?) =
|
||||||
|
if (localDate2 == null || localDate1 <= localDate2) localDate1 else localDate2
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package dev.darktale.model
|
||||||
|
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
sealed class Rate {
|
||||||
|
open val rate: BigDecimal? = null
|
||||||
|
abstract val since: LocalDate
|
||||||
|
open val till: LocalDate? = null
|
||||||
|
abstract fun rateOnDate(date: LocalDate): Rate?
|
||||||
|
|
||||||
|
override fun toString() = "Rate($rate since $since ${till?.let { "till $it" }}"
|
||||||
|
}
|
||||||
|
|
||||||
|
class SimpleRate(
|
||||||
|
override val rate: BigDecimal,
|
||||||
|
override val since: LocalDate,
|
||||||
|
override val till: LocalDate? = null,
|
||||||
|
) : Rate() {
|
||||||
|
|
||||||
|
constructor(rate: Double, since: LocalDate, till: LocalDate?) :
|
||||||
|
this(BigDecimal.valueOf(rate), since, till)
|
||||||
|
|
||||||
|
override fun rateOnDate(date: LocalDate) =
|
||||||
|
if (date >= since && (till == null || date <= till)) this else null
|
||||||
|
}
|
||||||
|
|
||||||
|
class PeriodicRate(
|
||||||
|
private val rates: TreeMap<LocalDate, Rate>
|
||||||
|
) : Rate() {
|
||||||
|
|
||||||
|
constructor(vararg rates: Rate) : this(TreeMap(rates.associateBy { it.since }))
|
||||||
|
|
||||||
|
override val since: LocalDate
|
||||||
|
get() = rates.firstKey()
|
||||||
|
|
||||||
|
override fun rateOnDate(date: LocalDate) =
|
||||||
|
rates.floorEntry(date)?.value?.let {
|
||||||
|
if (it.till == null || it.till!! >= date) it else null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString() = rates.values.toString()
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package dev.darktale
|
||||||
|
|
||||||
|
import dev.darktale.model.PeriodicRate
|
||||||
|
import dev.darktale.model.Rate
|
||||||
|
import dev.darktale.model.SimpleRate
|
||||||
|
import org.junit.jupiter.api.extension.ExtensionContext
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest
|
||||||
|
import org.junit.jupiter.params.provider.Arguments
|
||||||
|
import org.junit.jupiter.params.provider.ArgumentsProvider
|
||||||
|
import org.junit.jupiter.params.provider.ArgumentsSource
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.util.stream.Stream
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.pow
|
||||||
|
import kotlin.test.DefaultAsserter.assertTrue
|
||||||
|
|
||||||
|
class AmountByRateCalculatorTest {
|
||||||
|
private val amountByRateCalculator = AmountByRateCalculator()
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ArgumentsSource(RateProvider::class)
|
||||||
|
fun testRateCalculator(
|
||||||
|
rate: Rate,
|
||||||
|
sourceAmount: BigDecimal,
|
||||||
|
dateFrom: LocalDate,
|
||||||
|
dateTo: LocalDate,
|
||||||
|
expectedAmount: BigDecimal
|
||||||
|
) {
|
||||||
|
val result = amountByRateCalculator.calculate(sourceAmount, rate, dateFrom, dateTo)
|
||||||
|
val maxDelta = 10.0.pow(-expectedAmount.scale().toDouble())
|
||||||
|
assertTrue(
|
||||||
|
"expected amount was $expectedAmount ± ${
|
||||||
|
BigDecimal.valueOf(maxDelta).toPlainString()
|
||||||
|
}, but actual is $result",
|
||||||
|
abs((expectedAmount - result).toDouble()) <= maxDelta,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RateProvider : ArgumentsProvider {
|
||||||
|
override fun provideArguments(context: ExtensionContext): Stream<out Arguments> {
|
||||||
|
val baseAmount = BigDecimal.valueOf(1000L)
|
||||||
|
val startOfYear = LocalDate.of(2025, 1, 1)
|
||||||
|
val endOfMonth = LocalDate.of(2025, 1, 31)
|
||||||
|
val endOfMonth2 = LocalDate.of(2025, 2, 28)
|
||||||
|
val endOfYear = LocalDate.of(2025, 12, 31)
|
||||||
|
val middleOfYear = startOfYear.plusDays((endOfYear.dayOfYear / 2).toLong())
|
||||||
|
val simpleRate = SimpleRate(0.1, startOfYear, endOfYear)
|
||||||
|
val periodicRate = PeriodicRate(
|
||||||
|
SimpleRate(0.1, startOfYear, endOfMonth),
|
||||||
|
SimpleRate(0.1, endOfMonth.plusDays(1), endOfMonth2)
|
||||||
|
)
|
||||||
|
val decemberOfNonLeapYear = LocalDate.of(2023, 12, 1)
|
||||||
|
val januaryOfLeapYear = LocalDate.of(2024, 1, 31)
|
||||||
|
val nonLeapAndLeapYearRate = SimpleRate(
|
||||||
|
0.1,
|
||||||
|
decemberOfNonLeapYear,
|
||||||
|
januaryOfLeapYear
|
||||||
|
)
|
||||||
|
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(
|
||||||
|
simpleRate, baseAmount, startOfYear, endOfYear, BigDecimal.valueOf(1100L)
|
||||||
|
),
|
||||||
|
Arguments.of(
|
||||||
|
simpleRate,
|
||||||
|
baseAmount,
|
||||||
|
startOfYear,
|
||||||
|
middleOfYear,
|
||||||
|
BigDecimal.valueOf(1050.137)
|
||||||
|
),
|
||||||
|
Arguments.of(
|
||||||
|
periodicRate,
|
||||||
|
baseAmount,
|
||||||
|
startOfYear,
|
||||||
|
endOfMonth2,
|
||||||
|
BigDecimal.valueOf(1016.2295)
|
||||||
|
),
|
||||||
|
Arguments.of(
|
||||||
|
periodicRate,
|
||||||
|
baseAmount,
|
||||||
|
startOfYear,
|
||||||
|
middleOfYear,
|
||||||
|
BigDecimal.valueOf(1016.2295)
|
||||||
|
),
|
||||||
|
Arguments.of(
|
||||||
|
nonLeapAndLeapYearRate,
|
||||||
|
baseAmount,
|
||||||
|
nonLeapAndLeapYearRate.since,
|
||||||
|
nonLeapAndLeapYearRate.till,
|
||||||
|
BigDecimal.valueOf(1016.963096)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user