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 🚀
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
Explore-Gitea-Actions:
|
||||
Gitea-Actions:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
|
||||
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
|
||||
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
|
||||
- name: prepare
|
||||
run: |
|
||||
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
|
||||
uses: actions/checkout@v4
|
||||
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
|
||||
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
|
||||
uses: actions/checkout@v5
|
||||
- name: Set up Maven
|
||||
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
|
||||
run: |
|
||||
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