1

Currently, I went on tinkering with the features of one of my pet projects. I decided to include the feature to block a user from doing certain action/s and display a banner to show the cooldown period.

Eg.

Given: User is blocked for doing certain action/s from November 19, 2019 2:00:00 AM (1574128800) up to November 21, 2019 6:32:00 PM (1574361120).

Output: 2 days 16 hours 32 minutes

This is the method I have right now. Unfortunately, it only works on Android API >= 26.

/**
 * If using for Android, `Duration` requires API > 26
 * @param start Epoch time in milliseconds
 * @param end Epoch time in milliseconds
 */
fun getReadableBetween(start: Long, end: Long): String {
    val startDate = Date(start)
    val endDate = Date(end)
    var duration = Duration.between(startDate.toInstant(), endDate.toInstant())

    val days = duration.toDays()
    duration = duration.minusDays(days)
    val hours = duration.toHours()
    duration = duration.minusHours(hours)
    val minutes = duration.toMinutes()

    val stringBuilder = StringBuilder()

    if (days != 0L) {
        stringBuilder.append(days)
        if (days == 1L) {
            stringBuilder.append(" day ")
        } else {
            stringBuilder.append(" days ")
        }
    }

    if (hours != 0L) {
        stringBuilder.append(hours)
        if (hours == 1L) {
            stringBuilder.append(" hour ")
        } else {
            stringBuilder.append(" hours ")
        }
    }


    if (minutes != 0L) {
        stringBuilder.append(minutes)
        if (minutes == 1L) {
            stringBuilder.append(" minute.")
        } else {
            stringBuilder.append(" minutes.")
        }
    }

    println("Breakdown:")
    println("Days: $days")
    println("Hours: $hours")
    println("Minutes: $minutes")

    return stringBuilder.toString()
}

As much as I would like to use 3rd party libraries, I'd like to get my hands dirty using the fundamentals of date and time APIs available out of the box.

PS. Before linking this to other SO posts, please consider the scenario.

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
Joshua de Guzman
  • 2,063
  • 9
  • 24
  • Does this answer your question? [Call requires API level 26 (current min is 23): java.time.Instant#now](https://stackoverflow.com/questions/55550936/call-requires-api-level-26-current-min-is-23-java-time-instantnow) – Ole V.V. Nov 26 '19 at 10:47
  • The answer is that the best alternative to `java.time.Duration.bewteen` is `org.threeten.bp.Duration.between`. java.time has been backported and the backport adapted to Android. Add ThreeTenABP to your project, change the impots to use `org.threeten.bp` and keep using the code you already have. – Ole V.V. Nov 26 '19 at 10:49
  • Yes, I'm actually aware that the library exists, but I'm currently leaning towards doing it what's available from the out of the box SDK without using a backport or another lib. – Joshua de Guzman Nov 26 '19 at 10:50
  • 1
    Thanks for reporting back. One should always think twice before accepting a third party dependency. This is a backport, so if you trust it on API level 26+, I should think that you have good reasons to trust it on lower levels too. And it will simplify things later when you move up to 26+. You should do what you find best for your situation, of course. – Ole V.V. Nov 26 '19 at 10:58
  • I'll try to have a look on this a little bit more. I appreciate your time dropping by here! – Joshua de Guzman Nov 26 '19 at 11:10

2 Answers2

3

Decided to create a legacy function for Android API < 26.

/**
 * If using for Android & API < 26
 * @param start Epoch time in milliseconds
 * @param end Epoch time in milliseconds
 */
fun getReadableBetweenLegacy(start: Long, end: Long): String {
    val startDate = Date(start)
    val endDate = Date(end)
    val calendar = Calendar.getInstance()
    val diff = endDate.time - startDate.time
    calendar.timeInMillis = diff

    val daySeconds = 1000 * 60 * 60 * 24
    val hourSeconds = 1000 * 60 * 60
    val days = diff / daySeconds
    val hours = ((diff - (daySeconds * days)) / (hourSeconds));
    val minutes = (diff - (daySeconds * days) - (hourSeconds * hours)) / (1000 * 60)

    val stringBuilder = StringBuilder()
    if (days != 0L) {
        stringBuilder.append(days)
        if (days == 1L) {
            stringBuilder.append(" day ")
        } else {
            stringBuilder.append(" days ")
        }
    }

    if (hours != 0L) {
        stringBuilder.append(hours)
        if (hours == 1L) {
            stringBuilder.append(" hour ")
        } else {
            stringBuilder.append(" hours ")
        }
    }


    if (minutes != 0L) {
        stringBuilder.append(minutes)
        if (minutes == 1L) {
            stringBuilder.append(" minute.")
        } else {
            stringBuilder.append(" minutes.")
        }
    }

    println("Breakdown:")
    println("Days: $days")
    println("Hours: $hours")
    println("Minutes: $minutes")

    return stringBuilder.toString()
}

The solution is pretty straightforward with little to no margin of error at all.

Joshua de Guzman
  • 2,063
  • 9
  • 24
3

If you're using Gradle plugin 4.0 or later (with Android Studio 4.0 or later), you can take advantage of D8 Core Library Desugaring. This includes a subset of the functionality found in java.time and will allow you to use java.time.Duration in your project; even if you need to support versions older than API 26.

In your module's build.gradle file:

android {

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
        coreLibraryDesugaringEnabled true
    }

    // If using Kotlin
    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_1_8
    }
}
dependencies {
    …
    coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.5'
}

You should now be able to use this class error free.

Some sources:

HBG
  • 1,731
  • 2
  • 23
  • 35
  • Not working on AS 4.2 Beta 6. Build fails with following Gradle 6.4.1 / AGP 4.0.2 exception: ```> No signature of method: build_dvug2jv3jezfh5oc48zh5hqe.android() is applicable for argument types: (build_dvug2jv3jezfh5oc48zh5hqe$_run_closure1) values: [build_dvug2jv3jezfh5oc48zh5hqe$_run_closure1@752d785a]``` – HX_unbanned Mar 15 '21 at 14:56
  • For follow-up, issue submitted: https://github.com/StylingAndroid/Time/issues/1 – HX_unbanned Mar 15 '21 at 15:42