Added more date difference modes

closes: #129
This commit is contained in:
Sad Ellie 2023-11-25 19:12:19 +03:00
parent c843732e0e
commit 93404c1fac
11 changed files with 433 additions and 157 deletions

View File

@ -31,6 +31,7 @@ android {
dependencies {
testImplementation(libs.junit.junit)
implementation(project(":data:common"))
implementation(project(":data:model"))
implementation(project(":data:userprefs"))
}

View File

@ -0,0 +1,32 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.datecalculator
import java.time.ZonedDateTime
import java.time.temporal.ChronoUnit
internal object ZonedDateTimeUtils {
/**
* Get current [ZonedDateTime] with seconds and nanoseconds set to zero. Used in date calculator
* module.
*
* @return [ZonedDateTime] with units less than minutes zeroed out.
*/
fun nowWithMinutes(): ZonedDateTime = ZonedDateTime.now().truncatedTo(ChronoUnit.MINUTES)
}

View File

@ -80,6 +80,7 @@ import com.sadellie.unitto.core.ui.common.textfield.addTokens
import com.sadellie.unitto.core.ui.common.textfield.deleteTokens
import com.sadellie.unitto.core.ui.isPortrait
import com.sadellie.unitto.core.ui.showToast
import com.sadellie.unitto.feature.datecalculator.ZonedDateTimeUtils
import com.sadellie.unitto.feature.datecalculator.components.AddSubtractKeyboard
import com.sadellie.unitto.feature.datecalculator.components.DateTimeDialogs
import com.sadellie.unitto.feature.datecalculator.components.DateTimeSelectorBlock
@ -193,7 +194,7 @@ private fun AddSubtractView(
containerColor = MaterialTheme.colorScheme.secondaryContainer,
title = stringResource(R.string.date_calculator_start),
dateTime = uiState.start,
onLongClick = { updateStart(ZonedDateTime.now()) },
onLongClick = { updateStart(ZonedDateTimeUtils.nowWithMinutes()) },
onClick = { dialogState = DialogState.FROM },
onTimeClick = { dialogState = DialogState.FROM_TIME },
onDateClick = { dialogState = DialogState.FROM_DATE },
@ -402,8 +403,8 @@ fun AddSubtractViewPreview() {
AddSubtractView(
uiState = AddSubtractState(
years = TextFieldValue("12"),
start = ZonedDateTime.now(),
result = ZonedDateTime.now().plusSeconds(1)
start = ZonedDateTimeUtils.nowWithMinutes(),
result = ZonedDateTimeUtils.nowWithMinutes().plusSeconds(1)
),
toggleTopBar = {},
showKeyboard = false,

View File

@ -20,11 +20,12 @@ package com.sadellie.unitto.feature.datecalculator.addsubtract
import androidx.compose.ui.text.input.TextFieldValue
import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols
import com.sadellie.unitto.feature.datecalculator.ZonedDateTimeUtils
import java.time.ZonedDateTime
internal data class AddSubtractState(
val start: ZonedDateTime = ZonedDateTime.now(),
val result: ZonedDateTime = ZonedDateTime.now(),
val start: ZonedDateTime = ZonedDateTimeUtils.nowWithMinutes(),
val result: ZonedDateTime = ZonedDateTimeUtils.nowWithMinutes(),
val years: TextFieldValue = TextFieldValue(),
val months: TextFieldValue = TextFieldValue(),
val days: TextFieldValue = TextFieldValue(),

View File

@ -19,112 +19,155 @@
package com.sadellie.unitto.feature.datecalculator.components
import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ContentCopy
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.core.ui.common.squashable
import com.sadellie.unitto.core.ui.common.PagedIsland
import com.sadellie.unitto.feature.datecalculator.difference.ZonedDateTimeDifference
import java.math.BigDecimal
@Composable
internal fun DateTimeResultBlock(
modifier: Modifier = Modifier,
zonedDateTimeDifference: ZonedDateTimeDifference
diff: ZonedDateTimeDifference.Default,
format: (BigDecimal) -> String
) {
val clipboardManager = LocalClipboardManager.current
val years = zonedDateTimeDifference.years.formatDateTimeValue(R.string.date_calculator_years)
val months = zonedDateTimeDifference.months.formatDateTimeValue(R.string.date_calculator_months)
val days = zonedDateTimeDifference.days.formatDateTimeValue(R.string.date_calculator_days)
val hours = zonedDateTimeDifference.hours.formatDateTimeValue(R.string.date_calculator_hours)
val minutes = zonedDateTimeDifference.minutes.formatDateTimeValue(R.string.date_calculator_minutes)
val texts = listOf(years, months, days, hours, minutes)
Column(
modifier = modifier
.squashable(
onClick = {},
interactionSource = remember { MutableInteractionSource() },
cornerRadiusRange = 8.dp..32.dp,
)
.background(MaterialTheme.colorScheme.tertiaryContainer)
.padding(16.dp),
horizontalAlignment = Alignment.Start
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Bottom
) {
PagedIsland(
modifier = modifier,
pagesCount = 6,
backgroundColor = MaterialTheme.colorScheme.tertiaryContainer
) { currentPage ->
when(currentPage) {
0 -> {
Text(
stringResource(R.string.date_calculator_difference),
text = stringResource(R.string.date_calculator_difference),
style = MaterialTheme.typography.labelMedium
)
IconButton(
onClick = {
clipboardManager.setText(
AnnotatedString(texts.filter { it.isNotEmpty() }.joinToString(" "))
)
SelectionContainer {
Column {
// Years
if (diff.years > 0) {
DateText(R.string.date_calculator_years, diff.years.toBigDecimal(), format)
}
// Months
if (diff.months > 0) {
DateText(R.string.date_calculator_months, diff.months.toBigDecimal(), format)
}
// Days
if (diff.days > 0) {
DateText(R.string.date_calculator_days, diff.days.toBigDecimal(), format)
}
// Hours
if (diff.hours > 0) {
DateText(R.string.date_calculator_hours, diff.hours.toBigDecimal(), format)
}
// Minutes
if (diff.minutes > 0) {
DateText(R.string.date_calculator_minutes, diff.minutes.toBigDecimal(), format)
}
}
) {
Icon(Icons.Default.ContentCopy, null)
}
}
texts.forEach {
AnimatedVisibility(
visible = it.isNotEmpty(),
enter = expandVertically(),
exit = shrinkVertically()
) {
Text(it, style = MaterialTheme.typography.displaySmall)
1 -> {
Text(
text = stringResource(R.string.date_calculator_years),
style = MaterialTheme.typography.labelMedium
)
SelectionContainer {
DateText(diff.sumYears, format)
}
}
2 -> {
Text(
text = stringResource(R.string.date_calculator_months),
style = MaterialTheme.typography.labelMedium
)
SelectionContainer {
DateText(diff.sumMonths, format)
}
}
3 -> {
Text(
text = stringResource(R.string.date_calculator_days),
style = MaterialTheme.typography.labelMedium
)
SelectionContainer {
DateText(diff.sumDays, format)
}
}
4 -> {
Text(
text = stringResource(R.string.date_calculator_hours),
style = MaterialTheme.typography.labelMedium
)
SelectionContainer {
DateText(diff.sumHours, format)
}
}
5 -> {
Text(
text = stringResource(R.string.date_calculator_minutes),
style = MaterialTheme.typography.labelMedium
)
SelectionContainer {
DateText(diff.sumMinutes, format)
}
}
}
}
}
@Composable
@ReadOnlyComposable
private fun Long.formatDateTimeValue(@StringRes id: Int): String {
if (this <= 0) return ""
private fun DateText(
@StringRes id: Int,
value: BigDecimal,
format: (BigDecimal) -> String,
) = Text(
text = "${stringResource(id)}: ${format(value)}",
style = MaterialTheme.typography.displaySmall
)
return "${stringResource(id)}: $this"
}
@Composable
private fun DateText(
value: BigDecimal,
format: (BigDecimal) -> String,
) = Text(
text = format(value),
style = MaterialTheme.typography.displaySmall
)
@Preview
@Composable
private fun DateTimeResultBlockPreview() {
DateTimeResultBlock(
modifier = Modifier,
zonedDateTimeDifference = ZonedDateTimeDifference.Default(
diff = ZonedDateTimeDifference.Default(
years = 0,
months = 1,
days = 2,
hours = 3,
minutes = 4
)
days = 1,
hours = 0,
minutes = 0,
sumYears = BigDecimal.ZERO,
sumMonths = BigDecimal.ZERO,
sumDays = BigDecimal.ZERO,
sumHours = BigDecimal.ZERO,
sumMinutes = BigDecimal("46080"),
),
format = { it.toPlainString() }
)
}

View File

@ -50,6 +50,7 @@ import com.sadellie.unitto.core.ui.common.squashable
import com.sadellie.unitto.core.ui.datetime.formatDateWeekDayMonthYear
import com.sadellie.unitto.core.ui.datetime.formatTimeAmPm
import com.sadellie.unitto.core.ui.datetime.formatTimeShort
import com.sadellie.unitto.feature.datecalculator.ZonedDateTimeUtils
import java.time.ZonedDateTime
@Composable
@ -162,6 +163,6 @@ fun DateTimeSelectorBlockPreview() {
.width(224.dp),
containerColor = MaterialTheme.colorScheme.secondaryContainer,
title = "End",
dateTime = ZonedDateTime.now(),
dateTime = ZonedDateTimeUtils.nowWithMinutes(),
)
}

View File

@ -18,9 +18,7 @@
package com.sadellie.unitto.feature.datecalculator.difference
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.FlowRow
@ -39,29 +37,36 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.sadellie.unitto.core.base.OutputFormat
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols
import com.sadellie.unitto.core.ui.common.textfield.formatExpression
import com.sadellie.unitto.data.common.format
import com.sadellie.unitto.feature.datecalculator.ZonedDateTimeUtils
import com.sadellie.unitto.feature.datecalculator.components.DateTimeDialogs
import com.sadellie.unitto.feature.datecalculator.components.DateTimeResultBlock
import com.sadellie.unitto.feature.datecalculator.components.DateTimeSelectorBlock
import com.sadellie.unitto.feature.datecalculator.components.DialogState
import java.time.ZonedDateTime
import java.time.temporal.ChronoUnit
@Composable
internal fun DateDifferencePage(
viewModel: DateDifferenceViewModel = hiltViewModel(),
) {
val uiState = viewModel.uiState.collectAsStateWithLifecycle()
DateDifferenceView(
uiState = uiState.value,
when (val uiState = viewModel.uiState.collectAsStateWithLifecycle().value) {
DifferenceUIState.Loading -> Unit
is DifferenceUIState.Ready -> DateDifferenceView(
uiState = uiState,
setStartDate = viewModel::setStartDate,
setEndDate = viewModel::setEndDate
)
}
}
@Composable
private fun DateDifferenceView(
uiState: DifferenceUIState,
uiState: DifferenceUIState.Ready,
setStartDate: (ZonedDateTime) -> Unit,
setEndDate: (ZonedDateTime) -> Unit,
) {
@ -83,7 +88,7 @@ private fun DateDifferenceView(
title = stringResource(R.string.date_calculator_start),
dateTime = uiState.start,
onClick = { dialogState = DialogState.FROM },
onLongClick = { setStartDate(ZonedDateTime.now()) },
onLongClick = { setStartDate(ZonedDateTimeUtils.nowWithMinutes()) },
onTimeClick = { dialogState = DialogState.FROM_TIME },
onDateClick = { dialogState = DialogState.FROM_DATE },
containerColor = MaterialTheme.colorScheme.secondaryContainer
@ -97,24 +102,30 @@ private fun DateDifferenceView(
title = stringResource(R.string.date_calculator_end),
dateTime = uiState.end,
onClick = { dialogState = DialogState.TO },
onLongClick = { setStartDate(ZonedDateTime.now()) },
onLongClick = { setEndDate(ZonedDateTimeUtils.nowWithMinutes()) },
onTimeClick = { dialogState = DialogState.TO_TIME },
onDateClick = { dialogState = DialogState.TO_DATE },
containerColor = MaterialTheme.colorScheme.secondaryContainer
)
AnimatedVisibility(
visible = uiState.result is ZonedDateTimeDifference.Default,
enter = expandVertically(),
exit = shrinkVertically()
) {
AnimatedContent(
targetState = uiState.result,
modifier = Modifier.weight(2f)
) { result ->
when (result) {
is ZonedDateTimeDifference.Default -> {
DateTimeResultBlock(
modifier = Modifier
.weight(2f)
.fillMaxWidth(),
zonedDateTimeDifference = uiState.result,
modifier = Modifier.fillMaxWidth(),
diff = result,
format = {
it.format(uiState.precision, uiState.outputFormat)
.formatExpression(uiState.formatterSymbols)
}
)
}
ZonedDateTimeDifference.Zero -> Unit
}
}
}
DateTimeDialogs(
@ -143,7 +154,14 @@ private fun DateDifferenceView(
@Composable
fun DateDifferenceViewPreview() {
DateDifferenceView(
uiState = DifferenceUIState(),
uiState = DifferenceUIState.Ready(
start = ZonedDateTimeUtils.nowWithMinutes(),
end = ZonedDateTimeUtils.nowWithMinutes().truncatedTo(ChronoUnit.MINUTES),
result = ZonedDateTimeDifference.Zero,
precision = 3,
outputFormat = OutputFormat.PLAIN,
formatterSymbols = FormatterSymbols.Spaces
),
setStartDate = {},
setEndDate = {},
)

View File

@ -20,32 +20,70 @@ package com.sadellie.unitto.feature.datecalculator.difference
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.sadellie.unitto.core.ui.common.textfield.AllFormatterSymbols
import com.sadellie.unitto.data.common.stateIn
import com.sadellie.unitto.data.model.repository.UserPreferencesRepository
import com.sadellie.unitto.feature.datecalculator.ZonedDateTimeUtils
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import java.time.ZonedDateTime
import javax.inject.Inject
@HiltViewModel
internal class DateDifferenceViewModel @Inject constructor() : ViewModel() {
private val _uiState = MutableStateFlow(DifferenceUIState())
internal class DateDifferenceViewModel @Inject constructor(
userPrefsRepository: UserPreferencesRepository,
) : ViewModel() {
private val _start = MutableStateFlow(ZonedDateTimeUtils.nowWithMinutes())
private val _end = MutableStateFlow(ZonedDateTimeUtils.nowWithMinutes())
private val _result = MutableStateFlow<ZonedDateTimeDifference>(ZonedDateTimeDifference.Zero)
val uiState = _uiState
.onEach { updateResult() }
.stateIn(
viewModelScope, SharingStarted.WhileSubscribed(5000L), DifferenceUIState()
val uiState: StateFlow<DifferenceUIState> = combine(
userPrefsRepository.formattingPrefs,
_start,
_end,
_result
) { prefs, start, end, result ->
return@combine DifferenceUIState.Ready(
start = start,
end = end,
result = result,
precision = prefs.digitsPrecision,
outputFormat = prefs.outputFormat,
formatterSymbols = AllFormatterSymbols.getById(prefs.separator)
)
}
.mapLatest { ui ->
updateResult(
start = ui.start,
end = ui.end
)
fun setStartDate(newValue: ZonedDateTime) = _uiState.update { it.copy(start = newValue) }
ui
}
.stateIn(
viewModelScope, DifferenceUIState.Loading
)
fun setEndDate(newValue: ZonedDateTime) = _uiState.update { it.copy(end = newValue) }
fun setStartDate(newValue: ZonedDateTime) = _start.update { newValue }
private fun updateResult() = viewModelScope.launch(Dispatchers.Default) {
_uiState.update { ui -> ui.copy(result = ui.start - ui.end) }
fun setEndDate(newValue: ZonedDateTime) = _end.update { newValue }
private fun updateResult(
start: ZonedDateTime,
end: ZonedDateTime
) = viewModelScope.launch(Dispatchers.Default) {
_result.update {
try {
start - end
} catch (e: Exception) {
ZonedDateTimeDifference.Zero
}
}
}
}

View File

@ -18,10 +18,18 @@
package com.sadellie.unitto.feature.datecalculator.difference
import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols
import java.time.ZonedDateTime
internal data class DifferenceUIState(
val start: ZonedDateTime = ZonedDateTime.now(),
val end: ZonedDateTime = ZonedDateTime.now(),
val result: ZonedDateTimeDifference = ZonedDateTimeDifference.Zero
)
internal sealed class DifferenceUIState {
data object Loading : DifferenceUIState()
data class Ready(
val start: ZonedDateTime,
val end: ZonedDateTime,
val result: ZonedDateTimeDifference,
val precision: Int,
val outputFormat: Int,
val formatterSymbols: FormatterSymbols,
) : DifferenceUIState()
}

View File

@ -18,43 +18,65 @@
package com.sadellie.unitto.feature.datecalculator.difference
import com.sadellie.unitto.core.base.MAX_PRECISION
import java.math.BigDecimal
import java.math.RoundingMode
import java.time.ZonedDateTime
import java.time.temporal.ChronoUnit
internal sealed class ZonedDateTimeDifference(
open val years: Long = 0,
open val months: Long = 0,
open val days: Long = 0,
open val hours: Long = 0,
open val minutes: Long = 0,
) {
internal sealed class ZonedDateTimeDifference {
data class Default(
override val years: Long = 0,
override val months: Long = 0,
override val days: Long = 0,
override val hours: Long = 0,
override val minutes: Long = 0,
) : ZonedDateTimeDifference(
years = years,
months = months,
days = days,
hours = hours,
minutes = minutes,
)
val years: Long,
val months: Long,
val days: Long,
val hours: Long,
val minutes: Long,
val sumYears: BigDecimal,
val sumMonths: BigDecimal,
val sumDays: BigDecimal,
val sumHours: BigDecimal,
val sumMinutes: BigDecimal,
) : ZonedDateTimeDifference()
data object Zero : ZonedDateTimeDifference()
}
// https://stackoverflow.com/a/25760725
internal infix operator fun ZonedDateTime.minus(localDateTime: ZonedDateTime): ZonedDateTimeDifference {
if (this == localDateTime) return ZonedDateTimeDifference.Zero
/**
* Same as other [ZonedDateTime.minus] but `MAX_PRECISION` passed as scale.
*
* @receiver First [ZonedDateTime].
* @param zonedDateTime Second [ZonedDateTime].
* @return [ZonedDateTimeDifference.Default] (_always positive_) or [ZonedDateTimeDifference.Zero]
*/
internal infix operator fun ZonedDateTime.minus(
zonedDateTime: ZonedDateTime
): ZonedDateTimeDifference = this.minus(zonedDateTime = zonedDateTime, scale = MAX_PRECISION)
/**
* Calculate difference between [this] and [zonedDateTime]. Return absolute value, order of operands
* doesn't matter.
*
* @receiver First [ZonedDateTime].
* @param zonedDateTime Second [ZonedDateTime].
* @param scale Scale that will be used to calculate [ZonedDateTimeDifference.Default.sumDays] and
* others summed values.
* @return [ZonedDateTimeDifference.Default] (_always positive_) or [ZonedDateTimeDifference.Zero]
*/
internal fun ZonedDateTime.minus(
zonedDateTime: ZonedDateTime,
scale: Int
): ZonedDateTimeDifference {
// https://stackoverflow.com/a/25760725
if (this == zonedDateTime) return ZonedDateTimeDifference.Zero
var fromDateTime: ZonedDateTime = this
var toDateTime: ZonedDateTime = localDateTime
var toDateTime: ZonedDateTime = zonedDateTime
val epSeconds: BigDecimal = (this.toEpochSecond() - zonedDateTime.toEpochSecond()).toBigDecimal().abs()
// Swap to avoid negative
if (this > localDateTime) {
fromDateTime = localDateTime
if (this > zonedDateTime) {
fromDateTime = zonedDateTime
toDateTime = this
}
@ -77,6 +99,21 @@ internal infix operator fun ZonedDateTime.minus(localDateTime: ZonedDateTime): Z
if (listOf(years, months, days, hours, minutes).sum() == 0L) return ZonedDateTimeDifference.Zero
return ZonedDateTimeDifference.Default(
years = years, months = months, days = days, hours = hours, minutes = minutes
years = years,
months = months,
days = days,
hours = hours,
minutes = minutes,
sumYears = epSeconds.divide(yearInSeconds, scale, RoundingMode.HALF_EVEN),
sumMonths = epSeconds.divide(monthsInSeconds, scale, RoundingMode.HALF_EVEN),
sumDays = epSeconds.divide(dayInSeconds, scale, RoundingMode.HALF_EVEN),
sumHours = epSeconds.divide(hourInSeconds, scale, RoundingMode.HALF_EVEN),
sumMinutes = epSeconds.divide(minuteInSeconds, scale, RoundingMode.HALF_EVEN),
)
}
private val yearInSeconds by lazy { BigDecimal("31104000") }
private val monthsInSeconds by lazy { BigDecimal("2592000") }
private val dayInSeconds by lazy { BigDecimal("86400") }
private val hourInSeconds by lazy { BigDecimal("3600") }
private val minuteInSeconds by lazy { BigDecimal("60") }

View File

@ -20,32 +20,128 @@ package com.sadellie.unitto.feature.datecalculator.difference
import org.junit.Assert.assertEquals
import org.junit.Test
import java.math.BigDecimal
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
class ZonedDateTimeDifferenceKtTest {
private val formatter: DateTimeFormatter = DateTimeFormatter.ISO_ZONED_DATE_TIME
private val `may 1 2023`: ZonedDateTime = ZonedDateTime.parse("2023-05-01T12:00+01:00[Europe/Paris]", formatter)
private val `may 2 2023`: ZonedDateTime = ZonedDateTime.parse("2023-05-02T12:00+01:00[Europe/Paris]", formatter)
private val `june 1 2023`: ZonedDateTime = ZonedDateTime.parse("2023-06-01T12:00+01:00[Europe/Paris]", formatter)
@Test
fun `same dates`() {
assertEquals(ZonedDateTimeDifference.Zero, `may 1 2023` - `may 1 2023`)
val date1: ZonedDateTime = ZonedDateTime.parse("2023-05-01T12:00+01:00[Europe/Paris]", formatter)
assertEquals(ZonedDateTimeDifference.Zero, date1 - date1)
}
@Test
fun `positive difference dates one day`() {
assertEquals(ZonedDateTimeDifference.Default(days = 1), `may 1 2023` - `may 2 2023`)
fun `positive difference one day`() {
val date1: ZonedDateTime = ZonedDateTime.parse("2023-05-01T12:00+01:00[Europe/Paris]", formatter)
val date2: ZonedDateTime = ZonedDateTime.parse("2023-05-02T12:00+01:00[Europe/Paris]", formatter)
assertEquals(
ZonedDateTimeDifference.
Default(
years = 0,
months = 0,
days = 1,
hours = 0,
minutes = 0,
sumYears = BigDecimal("0.003"),
sumMonths = BigDecimal("0.033"),
sumDays = BigDecimal("1.000"),
sumHours = BigDecimal("24.000"),
sumMinutes = BigDecimal("1440.000"),
),
date1.minus(date2, 3)
)
}
@Test
fun `positive difference dates one minth`() {
assertEquals(ZonedDateTimeDifference.Default(months = 1), `may 1 2023` - `june 1 2023`)
fun `positive difference one month`() {
val date1: ZonedDateTime = ZonedDateTime.parse("2023-05-01T12:00+01:00[Europe/Paris]", formatter)
val date2: ZonedDateTime = ZonedDateTime.parse("2023-06-01T12:00+01:00[Europe/Paris]", formatter)
assertEquals(
ZonedDateTimeDifference.Default(
years = 0,
months = 1,
days = 0,
hours = 0,
minutes = 0,
sumYears = BigDecimal("0.086"),
sumMonths = BigDecimal("1.033"),
sumDays = BigDecimal("31.000"),
sumHours = BigDecimal("744.000"),
sumMinutes = BigDecimal("44640.000"),
),
date1.minus(date2, 3)
)
}
@Test
fun `negative difference dates one day`() {
assertEquals(ZonedDateTimeDifference.Default(days = 1), `may 2 2023` - `may 1 2023`)
fun `negative difference one day`() {
val date1: ZonedDateTime = ZonedDateTime.parse("2023-05-02T12:00+01:00[Europe/Paris]", formatter)
val date2: ZonedDateTime = ZonedDateTime.parse("2023-05-01T12:00+01:00[Europe/Paris]", formatter)
assertEquals(
ZonedDateTimeDifference.Default(
years = 0,
months = 0,
days = 1,
hours = 0,
minutes = 0,
sumYears = BigDecimal("0.003"),
sumMonths = BigDecimal("0.033"),
sumDays = BigDecimal("1.000"),
sumHours = BigDecimal("24.000"),
sumMinutes = BigDecimal("1440.000"),
),
date1.minus(date2, 3)
)
}
@Test
fun `positive big difference`() {
val date1: ZonedDateTime = ZonedDateTime.parse("2023-10-25T12:00+01:00[Europe/Paris]", formatter)
val date2: ZonedDateTime = ZonedDateTime.parse("2023-11-25T12:00+01:00[Europe/Paris]", formatter)
assertEquals(
ZonedDateTimeDifference.Default(
years = 0,
months = 0,
days = 30,
hours = 23,
minutes = 0,
sumYears = BigDecimal("0.086"),
sumMonths = BigDecimal("1.033"),
sumDays = BigDecimal("31.000"),
sumHours = BigDecimal("744.000"),
sumMinutes = BigDecimal("44640.000"),
),
date1.minus(date2, 3)
)
}
@Test
fun `positive big difference 2`() {
val date1: ZonedDateTime = ZonedDateTime.parse("2023-11-25T12:00+01:00[Europe/Paris]", formatter)
val date2: ZonedDateTime = ZonedDateTime.parse("2023-12-25T12:00+01:00[Europe/Paris]", formatter)
assertEquals(
ZonedDateTimeDifference.Default(
years = 0,
months = 1,
days = 0,
hours = 0,
minutes = 0,
sumYears = BigDecimal("0.083"),
sumMonths = BigDecimal("1.000"),
sumDays = BigDecimal("30.000"),
sumHours = BigDecimal("720.000"),
sumMinutes = BigDecimal("43200.000"),
),
date1.minus(date2, 3)
)
}
}