You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

仅部分用户出现DateTimeFormatter解析崩溃问题求助

RSS订阅日期解析崩溃问题排查

本地运行时,使用DateTimeFormatter的排序功能正常,但部分用户反馈应用崩溃。所有RSS的pubDate均为英文格式(示例: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}")
    }
}

崩溃堆栈信息

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.compose.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)

问题原因及修复方案

1. 时区格式匹配错误

原代码中使用的O格式符,匹配的是不带冒号的时区偏移(如GMT+1),但pubDate是GMT+01:00这种带冒号的格式,应该使用ZZZZ或XXX格式符:

  • ZZZZ:匹配GMT+01:00这种完整格式
  • XXX:匹配+01:00这种偏移格式

2. 错误使用LocalDateTime解析带时区的日期

LocalDateTime不包含时区信息,解析带时区的字符串时,在部分系统环境下会出现解析失败。应该使用ZonedDateTime来处理带时区的日期,确保跨环境的兼容性。

3. 缺少异常防护

单个无效的日期字符串会导致整个排序流程崩溃,需要添加异常捕获来过滤无效条目。

修改后的代码

suspend fun sortDateTimeAndSaveLatestHeadline() {
    val dataStore = AppDataStore(getApplication())
    val newsFeed = newsFeed.value ?: return

    // 简化列表生成逻辑,直接映射为Triple
    val zippedLists = newsFeed.map { feed ->
        Triple(feed.feedItem.pubDate, feed.feedItem.title, feed.feedTitle)
    }

    // 使用ZZZZ匹配带冒号的GMT时区格式
    val dateTimeFormatter: DateTimeFormatter =
        DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss ZZZZ yyyy", Locale.ENGLISH)

    // 过滤无效日期并排序
    val result = zippedLists.filter { (dateStr) ->
        try {
            ZonedDateTime.parse(dateStr, dateTimeFormatter)
            true
        } catch (e: DateTimeParseException) {
            // 可在此处记录日志,追踪无效日期的来源
            false
        }
    }.sortedByDescending { (dateStr) ->
        // 转成Instant保证跨时区的正确排序
        ZonedDateTime.parse(dateStr, dateTimeFormatter).toInstant()
    }

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

关键修改说明

  • 简化了列表生成逻辑,避免创建多个可变列表,提升代码可读性
  • 替换时区格式符为ZZZZ,匹配pubDate的GMT+01:00格式
  • 使用ZonedDateTime解析带时区的日期,转成Instant进行排序,确保跨时区排序的正确性
  • 添加filter和异常捕获,过滤无效日期条目,避免单个错误导致整个流程崩溃

内容的提问来源于stack exchange,提问作者Code Poet

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 00:24:52