1

I'm getting from server an UTC date for instance

"endValidityDate": "2021-11-18T22:59:59Z"

I'd like to know what is the optimal way to calculate remaining days from now.

Here's what I got now :

I'm creating a date for 2 days from now as :

DateTime.now().plusSeconds(172800)

I'm parsing it to a DateTime joda I can use other if you say so.

When doing the diff of days I'm doing this way

val diff = endValidityDate.toDate().time - Date().time
val daysRemaining = TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS)
return if (daysRemaining > 1) "$daysRemaining days}"
       else TimeUnit.DAYS.convert(diff, TimeUnit.SECONDS).toString()

The scenario that I'm trying to achieve is :

If the days remaining is more than one (24h) then print "2 days remaining" for instance, and instead of showing "1 day remaining" then just add a timer as :

"0h 43m 3s".

To do the timer I'm just subtracting the time remaining with now

val expireDate = LocalDateTime.now()
                   .plusSeconds(uiState.endValidityDate.timeLeft.toLong())
                   .toEpochSecond(ZoneOffset.UTC)
val currentTime = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC)

And then on every second it happen I print it like this :

val duration = Duration.ofSeconds(it)
binding.myTextView.text = String.format(
    "%02dh: %02dm: %02ds",
    duration.seconds / 3600,
    (duration.seconds % 3600) / 60,
    duration.seconds % 60,
)

But I'm not getting the 2 days, I'm just getting as an output :

00h: 33m: 50s

So, I'm having some problems here for instance :

Is this an optimal solution? If not could you describe a better one where I can achieve my goal? Why my timer is showing with 00h: 13m: 813s? Am I doing the regex incorrectly or it's because of epochSeconds?

To achieve

Given an UTC date from server when trying to print it to the device then it should follow this rules.

1.- If days remaining is > than 1 day then print "N days remaining"

2.- If days remaining is <= 1 then print a timer (it's already done, the thing is how to print it correctly).

  • Minimum 1 digit (0h 2m 1s)
  • Maximum 2 digits (1h 23m 3s)

Note :

I'm using Java 8 I can change also the way I'm doing the countdown to use millis instead of epochSeconds if that's the problem.

StuartDTO
  • 783
  • 7
  • 26
  • 72
  • There's `Duration.toMinutesPart()` since Java 9, if you are using a Java version less than 9, you have to calculate the parts yourself, like `duration.toMinutes() % 60` or similar. – deHaar Nov 21 '21 at 19:11
  • You should use `toMinutesPart()` and `toSecondsPart()`. – MC Emperor Nov 21 '21 at 19:13
  • @deHaar sorry I totally forgot about mentioning which Java I am using, I'm using Java 8. – StuartDTO Nov 21 '21 at 19:13
  • @deHaar I've changed it using the mathematic thing, but still not showing as expected, could be because I'm using epoch seconds? – StuartDTO Nov 21 '21 at 19:43

1 Answers1

5

You could do that using a ZonedDateTime for now and the future datetime, and then calculate a Duration.between instead of calculating remaining seconds first and then use a Duration.ofSeconds().

Here`s a Kotlin example:

fun main() {
    val utc = ZoneId.of("UTC")
    val now = ZonedDateTime.now(utc)
    val twoDaysFromNow = now.plusDays(2)
    
    val remaining = Duration.between(now, twoDaysFromNow)
    
    println(
        String.format("%02dh: %02dm: %02ds",
                        remaining.seconds / 3600,
                        (remaining.seconds % 3600) / 60,
                        remaining.seconds % 60
                     )
    )
}

Output: 48h: 00m: 00s


If you are interested in remaining full days only, then consider using ChronoUnit.DAYS.between, maybe like this:

fun main() {
    val utc = ZoneId.of("UTC")
    val now = ZonedDateTime.now(utc)
    val twoDaysFromNow = now.plusDays(2)
    
    val remainingDays = ChronoUnit.DAYS.between(now, twoDaysFromNow)
    
    println(
        String.format("%d days", remainingDays)
    )
}

Output: 2 days


Additional:

Since it is unclear to me what data type you are trying to use for the calculation of the time left until end of validity, you will have to choose between providing more detailed information in your question or using one of the following funs:

Pass a ZonedDateTime

private fun getRemainingTime(endValidityDate: ZonedDateTime): String {
    // get the current moment in time as a ZonedDateTime in UTC
    val now = ZonedDateTime.now(ZoneId.of("UTC"))
    // calculate the difference directly
    val timeLeft = Duration.between(now, endValidityDate)
    // return the messages depending on hours left
    return if (timeLeft.toHours() >= 24) "${timeLeft.toDays()} days"
    else String.format("%02dh: %02dm: %02ds",
                        timeLeft.toHours(),
                        timeLeft.toMinutes() % 60,
                        timeLeft.toSeconds() % 60)
}

Pass an Instant

private fun getRemainingTime(endValidityDate: Instant): String {
    // get the current moment in time, this time as an Instant directly
    val now = Instant.now()
    // calculate the difference
    val timeLeft = Duration.between(now, endValidityDate)
    // return the messages depending on hours left
    return if (timeLeft.toHours() >= 24) "${timeLeft.toDays()} days"
    else String.format("%02dh: %02dm: %02ds",
                        timeLeft.toHours(),
                        timeLeft.toMinutes() % 60,
                        timeLeft.toSeconds() % 60)
}

Pass the String directly

private fun getRemainingTime(endValidityDate: String): String {
    // get the current moment in time as a ZonedDateTime in UTC
    val now = ZonedDateTime.now(ZoneId.of("UTC"))
    // parse the endValidtyDate String
    val then = ZonedDateTime.parse(endValidityDate)
    // calculate the difference
    val timeLeft = Duration.between(now, then)
    // return the messages depending on hours left
    return if (timeLeft.toHours() >= 24) "${timeLeft.toDays()} days"
    else String.format("%02dh: %02dm: %02ds",
                        timeLeft.toHours(),
                        timeLeft.toMinutes() % 60,
                        timeLeft.toSeconds() % 60)
}
deHaar
  • 17,687
  • 10
  • 38
  • 51
  • Thanks for answering... I'm getting endVailidityTime as DateTime how can I convert it to Temporal to be able to use the Duration.between? – StuartDTO Nov 21 '21 at 20:18
  • As I put on my question I got this from backend "2021-11-18T22:59:59Z" it's UTC and then I convert with my parser (Gson) to a DateTime, so I need to know how do I pass this expiryDateTime as a parameter of Duration.between – StuartDTO Nov 21 '21 at 20:24
  • @StuartDTO Don't you get it as `String`? I mean like in your example `"2021-11-18T22:59:59Z"`... You could make it a `Temporal` by `ZonedDateTime.parse("2021-11-18T22:59:59Z")`. – deHaar Nov 21 '21 at 20:24
  • For GSON, find out if you can use a `ZonedDateTime`, like [here](https://stackoverflow.com/a/46726037/1712135), for example. – deHaar Nov 21 '21 at 20:26
  • I mean I can use Temporal api, but is it possible to use another one? Or that's because it's simpler doing like this? Is it possible to do it with JodaTime for instance? Or Instant? – StuartDTO Nov 21 '21 at 20:41
  • I wouldn't use JodaTime and stick to `java.time` instead. An `Instant` can be used in `Duration.between` as well. – deHaar Nov 21 '21 at 20:47
  • And regarding if it's more than 1 day (24h) print 2 days remaining and otherwise print 24:00:00 you recommend me to use ChronoUnit.Days.between? I mean I have remainingDays > 1 then print remainingDays + days, but in the else clause I have to return the seconds remaining so I can set up the timer – StuartDTO Nov 21 '21 at 21:13
  • I think I don't fully understand your requirements, which makes me recommend you to implement some test cases with each possibility and decide what to choose yourself based on the results and maybe more criteria, like readability or reusability. – deHaar Nov 21 '21 at 21:16
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/239439/discussion-between-stuartdto-and-dehaar). – StuartDTO Nov 21 '21 at 21:20