3

I'm seeing the following error when trying to parse a date string, can anyone point me in the right direction to parse this date string?

"2019-01-22T12:43:01Z"

Error:

java.text.ParseException: Unparseable date: "2019-01-22T12:43:01Z"

Code:

package ch02.ex1_1_HelloWorld

import java.lang.Exception
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.Date
import java.util.concurrent.TimeUnit

const val SERVER_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss:SS'Z'" 
val sdf = SimpleDateFormat(SERVER_TIME_FORMAT, Locale.US)

fun main(args: Array<String>) {
    timeSince("2019-01-22T12:43:01Z")
}

fun timeSince(dateStr: String) {
    var diff : Long = 0
    try {
        diff = sdf.parse(dateStr).time - Date().time      
    } catch (e: Exception) {
        print(e)
    }
    "${TimeUnit.HOURS.convert(diff, TimeUnit.MILLISECONDS)} h ago"
} 
xingbin
  • 27,410
  • 9
  • 53
  • 103
patrick-fitzgerald
  • 2,561
  • 3
  • 35
  • 49
  • I recommend you don’t use `SimpleDateFormat` and `Date`. Those classes are poorly designed and long outdated, the former in particular notoriously troublesome. Instead use `Instant` from [java.time, the modern Java date and time API](https://docs.oracle.com/javase/tutorial/datetime/). – Ole V.V. Feb 01 '19 at 09:00
  • Possible duplicate of [ISO 8601 String to Date/Time object in Android](https://stackoverflow.com/questions/3941357/iso-8601-string-to-date-time-object-in-android) – Ole V.V. Feb 01 '19 at 09:01
  • This question may also have a duplicate of [parsing date/time to localtimezone](https://stackoverflow.com/questions/51206465/parsing-date-time-to-localtimezone) in it. – Ole V.V. Feb 01 '19 at 09:42

3 Answers3

5

Since your input does not contain milli seconds, you can remove the :SS in the pattern:

const val SERVER_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'" 

And I would suggest to use java.time package.

xingbin
  • 27,410
  • 9
  • 53
  • 103
5

java.time

import java.time.Instant;
import java.time.temporal.ChronoUnit;

fun timeSince(dateStr: String): String {
    val diffHours = ChronoUnit.HOURS.between(Instant.parse(dateStr), Instant.now())
    return "%d h ago".format(diffHours)
}

Let’s try it out:

fun main() {
    println(timeSince("2019-01-22T12:43:01Z"))
}

This just printed:

236 h ago

I am using java.time, the modern Java date and time API. Compared to the old classes Date and not least SimpleDateFormat I find it so much nicer to work with. The method above will throw a DateTimeParseException if the string passed to it is not in ISO 8601 format. For most purposes this is probably better than returning 0 h ago. The method is tolerant to presence and absence of (up to 9) decimals on the seconds, so accepts for example 2019-01-22T12:43:01.654321Z.

Don’t we need a formatter? No. I am taking advantage of the fact that your string is in ISO 8601 format, the format that the modern date and time classes parse (and also print) as their default.

I wish I could use ChronoUnit and Instant

Edit:

I wish I could use ChronoUnit & Instant, but it requires a min V26 of Android. My current min is 23.

java.time works nicely on older Android devices too. It just requires at least Java 6.

  • In Java 8 and later and on newer Android devices (from API level 26) the modern API comes built-in.
  • In Java 6 and 7 get the ThreeTen Backport, the backport of the modern classes (ThreeTen for JSR 310; see the links at the bottom).
  • On (older) Android use the Android edition of ThreeTen Backport. It’s called ThreeTenABP. And make sure you import the date and time classes from org.threeten.bp with subpackages.

What went wrong in your code?

I see two bugs in the code in your question:

  1. As 竹杖芒鞋轻胜马 already pointed out in this answer, your format expects 4 parts between T and Z, separated by colons, but your string has only three parts there. With SimpleDateFormat using two SS for fraction of second is also wrong since uppercase S is for milliseconds, so only three SSS would make sense (by contrast, with the modern DateTimeFormatter S is for fraction of second, so any number (up to 9 SSSSSSSSS) makes sense).
  2. You are treating the Z in the string as a literal. It’s a UTC offset of zero and needs to be parsed as such, or you will get an incorrect time (on the vast majority of JVMs).

Links

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • I wish I could use ChronoUnit & Instant, but it requires a min V26 of Android. My current min is 23. – patrick-fitzgerald Feb 01 '19 at 23:50
  • 1
    You hadn’t told us this was for Android, though your SO user name of course gave a hint. Using `ChronoUnit` and `Instant` on API level 23 just requires using the backport. Your wish has come true. See my edit. – Ole V.V. Feb 02 '19 at 08:43
  • Didn't work, until single quotes were added around Z - **'Z'** – J A S K I E R Apr 15 '21 at 08:39
  • As in `2019-01-22T12:43:01'Z'`?? @JASKIER That’s very weird. It doesn’t work *with* those for me. – Ole V.V. Apr 15 '21 at 08:46
  • @JASKIER It works on https://pl.kotl.in/SyG51ab4N (just now it printed `19532 h ago`). – Ole V.V. Apr 15 '21 at 09:02
0

My original value from server is:

2021-04-08T11:27:40.278Z

You can simply, make this shorter. With kotlin, 2021:

// Read the value until the minutes only
val pattern = "yyyy-MM-dd'T'HH:mm"
val serverDateFormat = SimpleDateFormat(pattern, Locale.getDefault())
// Format the date output, as you wish to see, after we read the Date() value
val userPattern = "dd MMMM, HH:mm"
val userDateFormat = SimpleDateFormat(userPattern, Locale.getDefault())

val defaultDate = serverDateFormat.parse(INPUT_DATE_STRING)
if ( defaultDate != null ) {
   val userDate = userDateFormat.format(defaultDate)
   textView.text = userDate
}

Result:

08 April, 11:27

J A S K I E R
  • 1,976
  • 3
  • 24
  • 42
  • This will give you a `Date` with the wrong value. The `Z` is an offset of 0 from UTC and needs to be parsed as such, or the device default time zone will be used instead leading to a wrong result. – Ole V.V. Apr 13 '21 at 18:54
  • @OleV.V. My server is located in the same country. We read data from the server as I see, therefore you will see the same time for each device. I think this is enough for this kind of question. However, none of the previous answers helped me in some cases. – J A S K I E R Apr 14 '21 at 22:46