From 52d7db579cda8adc3045aec9c2a99a5cf3b22f66 Mon Sep 17 00:00:00 2001 From: Sad Ellie Date: Fri, 3 Feb 2023 14:10:27 +0400 Subject: [PATCH] Less sugar --- app/build.gradle.kts | 2 + .../sadellie/unitto/ConfigureKotlinAndroid.kt | 10 +++ data/epoch/build.gradle.kts | 4 ++ .../unitto/data/epoch/EpochDateConverter.kt | 69 ++++++------------ .../unitto/data/epoch/DateToEpochTest.kt | 60 ++++++++++++++++ .../unitto/data/epoch/EpochToDateTest.kt | 72 +++++++++++++++++++ .../unitto/feature/epoch/EpochViewModel.kt | 8 ++- gradle/libs.versions.toml | 2 + 8 files changed, 178 insertions(+), 49 deletions(-) create mode 100644 data/epoch/src/test/java/com/sadellie/unitto/data/epoch/DateToEpochTest.kt create mode 100644 data/epoch/src/test/java/com/sadellie/unitto/data/epoch/EpochToDateTest.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2ab8d095..d7115c4a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -79,6 +79,7 @@ android { compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 + isCoreLibraryDesugaringEnabled = true } packagingOptions { @@ -101,6 +102,7 @@ android { dependencies { implementation(libs.androidx.core) + coreLibraryDesugaring(libs.android.desugarJdkLibs) implementation(libs.androidx.compose.material3) implementation(libs.androidx.lifecycle.runtime.compose) diff --git a/build-logic/convention/src/main/java/com/sadellie/unitto/ConfigureKotlinAndroid.kt b/build-logic/convention/src/main/java/com/sadellie/unitto/ConfigureKotlinAndroid.kt index 60b05472..7220d6bd 100644 --- a/build-logic/convention/src/main/java/com/sadellie/unitto/ConfigureKotlinAndroid.kt +++ b/build-logic/convention/src/main/java/com/sadellie/unitto/ConfigureKotlinAndroid.kt @@ -21,7 +21,10 @@ package com.sadellie.unitto import com.android.build.api.dsl.CommonExtension import org.gradle.api.JavaVersion import org.gradle.api.Project +import org.gradle.api.artifacts.VersionCatalogsExtension import org.gradle.api.plugins.ExtensionAware +import org.gradle.kotlin.dsl.dependencies +import org.gradle.kotlin.dsl.getByType import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions internal fun Project.configureKotlinAndroid( @@ -47,6 +50,7 @@ internal fun Project.configureKotlinAndroid( compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 + isCoreLibraryDesugaringEnabled = true } buildFeatures { @@ -75,6 +79,12 @@ internal fun Project.configureKotlinAndroid( jvmTarget = JavaVersion.VERSION_1_8.toString() } } + + val libs = extensions.getByType().named("libs") + + dependencies { + add("coreLibraryDesugaring", libs.findLibrary("android.desugarJdkLibs").get()) + } } fun CommonExtension<*, *, *, *>.kotlinOptions(block: KotlinJvmOptions.() -> Unit) { diff --git a/data/epoch/build.gradle.kts b/data/epoch/build.gradle.kts index e90d319c..387eed23 100644 --- a/data/epoch/build.gradle.kts +++ b/data/epoch/build.gradle.kts @@ -24,3 +24,7 @@ plugins { android { namespace = "com.sadellie.unitto.data.epoch" } + +dependencies { + testImplementation(libs.junit) +} diff --git a/data/epoch/src/main/java/com/sadellie/unitto/data/epoch/EpochDateConverter.kt b/data/epoch/src/main/java/com/sadellie/unitto/data/epoch/EpochDateConverter.kt index e949ea3c..d4957eb2 100644 --- a/data/epoch/src/main/java/com/sadellie/unitto/data/epoch/EpochDateConverter.kt +++ b/data/epoch/src/main/java/com/sadellie/unitto/data/epoch/EpochDateConverter.kt @@ -18,60 +18,33 @@ package com.sadellie.unitto.data.epoch -import java.math.BigDecimal -import java.util.* +import java.time.LocalDateTime +import java.time.ZoneOffset +import java.time.format.DateTimeFormatter +import java.time.format.DateTimeParseException object EpochDateConverter { + private val pattern by lazy { + DateTimeFormatter.ofPattern("HHmmssddMMyyyy") + } + fun convertDateToUnix(date: String): String { - // Here we add some zeros, so that input is 14 symbols long - val inputWithPadding = date.padEnd(14, '0') - - // Now we break input that is 14 symbols into pieces - val hour = inputWithPadding.substring(0, 2) - val minute = inputWithPadding.substring(2, 4) - val second = inputWithPadding.substring(4, 6) - val day = inputWithPadding.substring(6, 8) - val month = inputWithPadding.substring(8, 10) - val year = inputWithPadding.substring(10, 14) - - val cal = Calendar.getInstance() - cal.set( - year.toIntOrNull() ?: 1970, - (month.toIntOrNull() ?: 1) - 1, - day.toIntOrNull() ?: 0, - hour.toIntOrNull() ?: 0, - minute.toIntOrNull() ?: 0, - second.toIntOrNull() ?: 0, - ) - return (cal.timeInMillis / 1000).toString() + return try { + // Here we add some zeros, so that input is 14 symbols long + LocalDateTime + .parse(date.padEnd(14, '0'), pattern) + .toEpochSecond(ZoneOffset.UTC) + } catch (e: DateTimeParseException) { + 0 + }.toString() } fun convertUnixToDate(unix: String): String { - var date = "" - val cal2 = Calendar.getInstance() - cal2.clear() - cal2.isLenient = true + val unixLong = unix.toLong().takeIf { it <= 253402300559L } + ?: throw IllegalArgumentException("Max unix is 253402300559") - // This lets us bypass calendars limits (it uses Int, we want BigDecimal) - try { - val unixBg = BigDecimal(unix.ifEmpty { "0" }) - val division = unixBg.divideAndRemainder(BigDecimal(Int.MAX_VALUE)) - val intTimes = division.component1() - val rem = division.component2() - repeat(intTimes.intValueExact()) { - cal2.add(Calendar.SECOND, Int.MAX_VALUE) - } - cal2.add(Calendar.SECOND, rem.intValueExact()) - } catch (e: NumberFormatException) { - return "" - } - date += cal2.get(Calendar.HOUR_OF_DAY).toString().padStart(2, '0') - date += cal2.get(Calendar.MINUTE).toString().padStart(2, '0') - date += cal2.get(Calendar.SECOND).toString().padStart(2, '0') - date += cal2.get(Calendar.DAY_OF_MONTH).toString().padStart(2, '0') - date += (cal2.get(Calendar.MONTH) + 1).toString().padStart(2, '0') - // Year is 4 symbols long - date += cal2.get(Calendar.YEAR).toString().padStart(4, '0') - return date + return LocalDateTime + .ofEpochSecond(unixLong, 0, ZoneOffset.UTC) + .format(pattern) } } \ No newline at end of file diff --git a/data/epoch/src/test/java/com/sadellie/unitto/data/epoch/DateToEpochTest.kt b/data/epoch/src/test/java/com/sadellie/unitto/data/epoch/DateToEpochTest.kt new file mode 100644 index 00000000..5a4ee4f7 --- /dev/null +++ b/data/epoch/src/test/java/com/sadellie/unitto/data/epoch/DateToEpochTest.kt @@ -0,0 +1,60 @@ +/* + * 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 . + */ + +package com.sadellie.unitto.data.epoch + +import org.junit.Assert.assertEquals +import org.junit.Test + +class DateToEpochTest { + + @Test + fun `00h0m00s 00 00 0000`() { + convertDateToUnixTest("00:00:00 00.00.0000", "0") + } + + @Test + fun `00h00m00s 01 01 0001`() { + convertDateToUnixTest("00:00:00 01.01.0001", "-62135596800") + } + + @Test + fun `00h00m00s 01 01 1970`() { + convertDateToUnixTest("00:00:00 01.01.1970", "0") + } + + @Test + fun `23h55m59s 31 12 9999`() { + convertDateToUnixTest("23:55:59 31.12.9999", "253402300559") + } + + @Test + fun `99h99m99s 99 99 9999`() { + convertDateToUnixTest("99:99:99 99.99.9999", "0") + } + + private fun convertDateToUnixTest(inputDate: String, expectedUnix: String) { + // Date input comes "fancy" + val cleanInputDate = inputDate.filter{ it.isDigit() } + assertEquals( + "Couldn't convert $inputDate ($cleanInputDate) into unix", + expectedUnix, + EpochDateConverter.convertDateToUnix(cleanInputDate) + ) + } +} diff --git a/data/epoch/src/test/java/com/sadellie/unitto/data/epoch/EpochToDateTest.kt b/data/epoch/src/test/java/com/sadellie/unitto/data/epoch/EpochToDateTest.kt new file mode 100644 index 00000000..12e16a68 --- /dev/null +++ b/data/epoch/src/test/java/com/sadellie/unitto/data/epoch/EpochToDateTest.kt @@ -0,0 +1,72 @@ +/* + * 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 . + */ + +package com.sadellie.unitto.data.epoch + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertThrows +import org.junit.Test + +class EpochToDateTest { + + @Test + fun `0`() { + convertUnixToDate("0", "00:00:00 01.01.1970") + } + + @Test + fun `00000000000000 max input length`() { + convertUnixToDate("00000000000000", "00:00:00 01.01.1970") + } + + @Test + fun `253402300559 max possible date`() { + convertUnixToDate("253402300559", "23:55:59 31.12.9999") + } + + @Test + fun `2534023005599999 input longe than allowed`() { + convertUnixToDate("2534023005599999", "23:55:59 31.12.9999", IllegalArgumentException()) + } + + @Test + fun `99999999999999 max possible unix with max length`() { + convertUnixToDate("99999999999999", "23:55:59 31.12.9999", IllegalArgumentException()) + } + + private fun convertUnixToDate(inputUnix: String, expectedDate: String, throwable: Throwable? = null) { + // Date input comes "fancy" + val cleanExpectedDate = expectedDate.filter{ it.isDigit() } + + // Will throw + if (throwable != null) { + assertThrows( + "Failed to throw ${throwable.javaClass} when converting $inputUnix into $expectedDate ($cleanExpectedDate)", + throwable.javaClass + ) { EpochDateConverter.convertUnixToDate(inputUnix) } + return + } + + // Should actually convert + assertEquals( + "Couldn't convert $inputUnix into $expectedDate ($cleanExpectedDate)", + cleanExpectedDate, + EpochDateConverter.convertUnixToDate(inputUnix) + ) + } +} diff --git a/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/EpochViewModel.kt b/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/EpochViewModel.kt index 48a15248..cb671d87 100644 --- a/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/EpochViewModel.kt +++ b/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/EpochViewModel.kt @@ -52,9 +52,15 @@ class EpochViewModel @Inject constructor() : ViewModel() { dateToUnix = fromDateToUnix ) } else { + val date = try { + EpochDateConverter.convertUnixToDate(input) + } catch (e: IllegalArgumentException) { + "" + } + EpochUIState( unixField = input, - dateField = EpochDateConverter.convertUnixToDate(input), + dateField = date, dateToUnix = fromDateToUnix ) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7a952db5..547e1a37 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,6 +4,7 @@ appName = "Glaucous" androidxCore = "1.9.0" androidxTest = "1.5.0" androidxTestExt = "1.1.4" +androidDesugarJdkLibs = "2.0.0" junit = "4.13.2" androidxTestRunner = "1.5.1" androidxTestRules = "1.5.0" @@ -61,6 +62,7 @@ com-squareup-retrofit2 = { group = "com.squareup.retrofit2", name = "converter-m com-github-sadellie-themmo = { group = "com.github.sadellie", name = "themmo", version.ref = "comGithubSadellieThemmo" } org-burnoutcrew-composereorderable = { group = "org.burnoutcrew.composereorderable", name = "reorderable", version.ref = "orgBurnoutcrewComposereorderable" } com-github-sadellie-exprk = { group = "com.github.sadellie", name = "ExprK", version.ref = "comGithubSadellieExprk" } +android-desugarJdkLibs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "androidDesugarJdkLibs" } # classpath android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }