2

I have this weird situation where my function using DateTimeFormatter works perfectly for me (i.e. the list of rss feeds is sorted in the order that it was published), but it crashes for some users of my app. At first, I thought maybe there was a locale related problem but it seems as if all rss feeds use the locale.English format, so now I am out of ideas. I also have reports of crashes from users with American English set on their device (I use UK English). Is there something wrong with my code or another possible reason?

I use Rome parser and pubDates are parsed in this way: Sat Aug 12 12:51:34 GMT+01:00 2023

    suspend fun sortDateTimeAndSaveLatestHeadline() {
    val dataStore = AppDataStore(getApplication())
    val listOfStringDates: MutableList<String> = mutableListOf()
    val listOfHeadlines: MutableList<String> = mutableListOf()
    val listOfFeedTitles: MutableList<String> = mutableListOf()

    newsFeed.value?.forEach { feed ->
        listOfStringDates.add(feed.feedItem.pubDate)
        listOfHeadlines.add(feed.feedItem.title)
        listOfFeedTitles.add(feed.feedTitle)
    }

    val zippedLists = listOfStringDates.zip(listOfHeadlines)
        .zip(listOfFeedTitles) { (a, b), c -> Triple(a, b, c) }

    val dateTimeFormatter: DateTimeFormatter =
        DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss O yyyy",  Locale.ENGLISH)

    val result = zippedLists.sortedByDescending {
        LocalDateTime.parse(it.first, dateTimeFormatter)
    }

    if (result.isNotEmpty()) {
        dataStore.saveTileHeadline(result[0].second)
        dataStore.saveFeedTitle(result[0].third)
        println("Headline = ${result[0].second}")
    }
}

And this is the stack trace:

  Exception java.time.format.DateTimeParseException:
  at java.time.format.DateTimeFormatter.parseResolved0 (DateTimeFormatter.java:1949)
  at java.time.format.DateTimeFormatter.parse (DateTimeFormatter.java:1851)
  at java.time.LocalDateTime.parse (LocalDateTime.java:486)
  at java.util.TimSort.countRunAndMakeAscending (TimSort.java:355)
  at java.util.TimSort.sort (TimSort.java:220)
  at java.util.Arrays.sort (Arrays.java:1424)
  at kotlin.collections.CollectionsKt___CollectionsKt.sortedWith (CollectionsKt___Collections.kt)
  at com.strangerweather.news.presentation.screens.screens.FeedItemsScreenKt$FeedItemsScreen$7$1.invoke (FeedItemsScreen.kt)
  at androidx.wear.compose.foundation.lazy.ScalingLazyColumnKt$ScalingLazyColumn$1$1$2$1.invoke (ScalingLazyColumn.kt)
  at androidx.compose.foundation.lazy.layout.LazyLayoutIntervalContent.<init> (LazyLayoutIntervalContent.java)
  at androidx.compose.foundation.lazy.LazyListIntervalContent.<init> (LazyListIntervalContent.java)
  at androidx.compose.runtime.snapshots.Snapshot$Companion.observe (Snapshot.java)
  at androidx.compose.runtime.DerivedSnapshotState.currentRecord (DerivedSnapshotState.java)
  at androidx.compose.runtime.snapshots.SnapshotKt.getLock (Snapshot.kt)
  at androidx.compose.runtime.DerivedSnapshotState$ResultRecord.readableHash (DerivedSnapshotState.java)
  at androidx.compose.runtime.snapshots.SnapshotKt.getLock (Snapshot.kt)
  at androidx.compose.runtime.DerivedSnapshotState$ResultRecord.isValid (DerivedSnapshotState.java)
  at androidx.compose.runtime.DerivedSnapshotState.currentRecord (DerivedSnapshotState.java)
  at androidx.compose.runtime.DerivedSnapshotState.getCurrentRecord (DerivedSnapshotState.java)
  at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.recordInvalidation (SnapshotStateObserver.java)
  at androidx.compose.runtime.snapshots.SnapshotStateObserver.drainChanges (SnapshotStateObserver.java)
  at androidx.compose.runtime.snapshots.SnapshotStateObserver.access$drainChanges (SnapshotStateObserver.java)
  at androidx.compose.runtime.snapshots.SnapshotKt.advanceGlobalSnapshot (Snapshot.kt)
  at androidx.compose.runtime.snapshots.SnapshotKt.advanceGlobalSnapshot (Snapshot.kt)
  at androidx.compose.runtime.snapshots.SnapshotKt.access$advanceGlobalSnapshot (Snapshot.kt)
  at androidx.compose.runtime.snapshots.GlobalSnapshot.notifyObjectsInitialized$runtime_release (GlobalSnapshot.java)
  at androidx.compose.runtime.DerivedSnapshotState.currentRecord (DerivedSnapshotState.java)
  at androidx.compose.runtime.snapshots.Snapshot$Companion.getCurrent (Snapshot.java)
  at androidx.compose.runtime.DerivedSnapshotState.getValue (DerivedSnapshotState.java)
  at androidx.wear.compose.foundation.lazy.ScalingLazyListState.getLayoutInfo (ScalingLazyListState.java)
  at androidx.wear.compose.foundation.lazy.ScalingLazyListState.scrollToItem$compose_foundation_release (ScalingLazyListState.java)
  at androidx.wear.compose.foundation.lazy.ScalingLazyListState.scrollToInitialItem$compose_foundation_release (ScalingLazyListState.java)
  at androidx.wear.compose.foundation.lazy.ScalingLazyColumnKt$ScalingLazyColumn$1$1$3$1.invokeSuspend (ScalingLazyColumn.kt)
  at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (BaseContinuationImpl.java)
  at kotlinx.coroutines.DispatchedTask.run (DispatchedTask.java)
  at androidx.compose.ui.platform.AndroidUiDispatcher.nextTask (AndroidUiDispatcher.java)
  at androidx.compose.ui.platform.AndroidUiDispatcher.performTrampolineDispatch (AndroidUiDispatcher.java)
  at androidx.compose.ui.platform.AndroidUiDispatcher.access$performTrampolineDispatch (AndroidUiDispatcher.java)
  at androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.run (AndroidUiDispatcher.java)
  at android.os.Handler.handleCallback (Handler.java:938)
  at android.os.Handler.dispatchMessage (Handler.java:99)
  at android.os.Looper.loop (Looper.java:246)
  at android.app.ActivityThread.main (ActivityThread.java:7690)
  at java.lang.reflect.Method.invoke
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:593)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:995)
Code Poet
  • 6,222
  • 2
  • 29
  • 50
  • 1
    Is there a way you can show us the problematic date strings? – Nick Sherrill Aug 15 '23 at 21:01
  • This is an example of the date strings that need to be ordered : "Sat Aug 12 12:51:34 GMT+01:00 2023". Is this what you are asking for? – Code Poet Aug 15 '23 at 21:10
  • 2
    That example does not cause the exception described in your problem. Do you have some way of capturing examples of strings that cause the provided exception? Something like replacing the sorting anonymous function with `runCatching { LocalDate.parse(it.first, dateTimeFormatter) }.getOrElse { e-> throw IllegalArgumentException("Invalid date str: ${it.first}", e) }`. This will help you figure out what kind of strings are breaking your parser and plan how to get around it, since you should be able to see the problematic date string in the error message. – Nick Sherrill Aug 18 '23 at 14:40
  • Thanks, sounds like a good way of finding out what is going on. I am now wondering also whether I should be using ```ZonedDateTime``` parser rather than ```LocalDateTime```. – Code Poet Aug 18 '23 at 17:29
  • 2
    while your end result should be a `ZonedDateTime`, that isn't really where the issue stems from in this context. At least to me, it looks like `DateTimeFormatter`'s part of the job is failing, and the translation from the user time zone to your system's time zone is the last step to complete *after* this parsing issue is done. I was able to parse the provided example as a LocalDate just fine. – Nick Sherrill Aug 18 '23 at 18:42
  • So I decided to change the time zone of my emulator and I am getting this: ```Text 'Fri Aug 18 12:34:50 EDT 2023' could not be parsed```. Any ideas? – Code Poet Aug 18 '23 at 18:48
  • ```java.lang.IllegalArgumentException: Invalid date str: Fri Aug 18 12:34:50 EDT 2023``` – Code Poet Aug 18 '23 at 21:11
  • 1
    Ideally you would not be parsing such formatted strings. Those should be used only for presentation to the user. For data exchange use only standard ISO 8601 formats. – Basil Bourque Aug 19 '23 at 15:24
  • 1
    Do not parse into `LocalDateTime`. Your string contains a UTC offset or time zone abbreviation, and you want to pick that up. Also when EDT is ambiguous as time zone abbreviation. Parse into a `ZonedDateTime` – Ole V.V. Aug 26 '23 at 08:22

1 Answers1

2

Update:

Thanks to Ole V.V. for this update.

DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss zzz yyyy", Locale.ROOT) parses both of the example strings in the original answer just fine.

Online Demo

Original answer:

From the description you have provided in the code and in the comment, it looks like your application is getting timezone information in different forms e.g. GMT+01:00, EDT etc. Therefore, you should use multiple optional patterns for time zones.

Demo:

public class Main {
    public static void main(String[] args) {
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss [O][z] uuuu", Locale.ENGLISH);

        // Test
        Stream.of(
                "Sat Aug 12 12:51:34 GMT+01:00 2023",
                "Fri Aug 18 12:34:50 EDT 2023"
        ).forEach(s -> System.out.println(ZonedDateTime.parse(s, dtf)));
    }
}

Output:

2023-08-12T12:51:34+01:00
2023-08-18T12:34:50-04:00[America/New_York]

Online Demo

Notes:

  1. I have used only two optional patterns for the demo. You can extend it further as per your requirement e.g. "EEE MMM dd HH:mm:ss [O][z][VV] uuuu" has three optional patterns.
  2. Here, you can use y instead of u but I prefer u to y.
Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110