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)
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.
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]
Notes:
"EEE MMM dd HH:mm:ss [O][z][VV] uuuu"
has three optional patterns.y
instead of u
but I prefer u
to y
.