Skip to content

fix: centralize relative time formatting logic#1559

Merged
xionglinlin merged 1 commit into
linuxdeepin:masterfrom
xionglinlin:master
Apr 16, 2026
Merged

fix: centralize relative time formatting logic#1559
xionglinlin merged 1 commit into
linuxdeepin:masterfrom
xionglinlin:master

Conversation

@xionglinlin
Copy link
Copy Markdown
Contributor

@xionglinlin xionglinlin commented Apr 16, 2026

Moved relative time formatting logic from BubbleModel and AppNotifyItem to a common static method in NotifyEntity to eliminate code duplication and fix incorrect time display issues. Previously, BubbleModel used a simple minutes calculation while AppNotifyItem used ICU library for locale-aware formatting, causing inconsistent time display between notification bubble and center. Now both components use the same unified formatting logic.

The changes include:

  1. Added static method NotifyEntity::formatRelativeTime() that handles all relative time formatting using ICU library
  2. Removed QDateTime include from bubblemodel.cpp as it's no longer needed
  3. Simplified updateBubbleTimeTip() in BubbleModel to use the new common method
  4. Simplified updateTime() in AppNotifyItem to use the new common method with fallback to "Just now"
  5. Updated copyright years to 2024-2026 in modified files

Log: Fixed inconsistent time display between notification bubble and center

Influence:

  1. Test notification time display in both bubble and center views
  2. Verify time formatting for different time intervals (just now, minutes, hours, yesterday, days, weeks)
  3. Check locale-specific formatting works correctly
  4. Verify time updates dynamically as notifications age
  5. Test with notifications from different time periods

fix: 集中相对时间格式化逻辑

将相对时间格式化逻辑从 BubbleModel 和 AppNotifyItem 移动到 NotifyEntity 的公共静态方法中,以消除代码重复并修复时间显示错误问题。之前,
BubbleModel 使用简单的分钟计算,而 AppNotifyItem 使用 ICU 库进行本地化感 知的格式化,导致通知气泡和中心之间的时间显示不一致。现在两个组件都使用相
同的统一格式化逻辑。

变更包括:

  1. 添加静态方法 NotifyEntity::formatRelativeTime(),使用 ICU 库处理所有 相对时间格式化
  2. 从 bubblemodel.cpp 中移除不再需要的 QDateTime 包含
  3. 简化 BubbleModel 中的 updateBubbleTimeTip() 以使用新的公共方法
  4. 简化 AppNotifyItem 中的 updateTime() 以使用新的公共方法,并回退到"刚 刚"
  5. 在修改的文件中更新版权年份为 2024-2026

Log: 修复通知气泡和中心之间时间显示不一致的问题

Influence:

  1. 测试通知气泡和中心视图中的时间显示
  2. 验证不同时间间隔(刚刚、分钟、小时、昨天、天数、周数)的时间格式化
  3. 检查本地化特定的格式化是否正确工作
  4. 验证时间随通知老化而动态更新
  5. 测试来自不同时间段的通知

PMS: BUG-355185
Change-Id: I65ddbd0ea5ec943fd7b2c9aa55bc5ed29bd0522b

Summary by Sourcery

Centralize locale-aware relative time formatting for notifications and ensure consistent time display between the notification bubble and center.

Enhancements:

  • Introduce a shared NotifyEntity::formatRelativeTime helper for ICU-based relative time formatting used across notification views.
  • Simplify bubble and center notification time update logic to rely on the common relative time formatter and remove redundant date-time handling.
  • Update SPDX copyright headers in touched notification-related files to cover 2024–2026.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Apr 16, 2026

Reviewer's Guide

Centralizes relative time formatting into a new ICU-based helper on NotifyEntity and updates both the notification center item and bubble model to use it, ensuring consistent, locale-aware time strings while simplifying their code and adjusting includes and copyrights.

Sequence diagram for unified relative time formatting in notifications

sequenceDiagram
    actor User
    participant UI as NotificationUI
    participant Center as AppNotifyItem
    participant Bubble as BubbleModel
    participant Entity as NotifyEntity

    User->>UI: Open notification center / see bubble
    UI->>Center: Request display data
    Center->>Entity: formatRelativeTime(m_entity.cTime())
    Entity-->>Center: relative time string or empty
    alt center got empty string
        Center->>Center: validate QDateTime
        Center->>Center: fallback to localized Just now
    end
    Center-->>UI: Formatted time for center item

    UI->>Bubble: Request bubble updates
    loop each bubble item
        Bubble->>Entity: formatRelativeTime(item.ctime())
        alt non empty result
            Entity-->>Bubble: relative time string
            Bubble->>Bubble: item.setTimeTip(timeTip)
        else empty result
            Entity-->>Bubble: empty (no update)
        end
    end
    Bubble-->>UI: Updated bubble time tips
Loading

Class diagram for centralized relative time formatting logic

classDiagram
    class NotifyEntity {
        +qint64 cTime()
        +QString bodyIcon()
        +static QString formatRelativeTime(qint64 ctimeMs)
    }

    class AppNotifyItem {
        -NotifyEntity m_entity
        -QString m_time
        +QString time()
        +void updateTime()
    }

    class BubbleModel {
        -QList~NotifyEntity*~ m_bubbles
        +void updateBubbleTimeTip()
    }

    AppNotifyItem --> NotifyEntity : uses m_entity
    BubbleModel --> NotifyEntity : calls formatRelativeTime
    AppNotifyItem ..> NotifyEntity : calls formatRelativeTime
Loading

File-Level Changes

Change Details Files
Introduce a shared, ICU-based relative time formatter on NotifyEntity and its supporting helpers
  • Add NotifyEntity::formatRelativeTime(qint64) that validates the timestamp, computes elapsed time, and returns locale-aware relative strings for minutes, hours, yesterday-with-time, recent days, and older dates
  • Use a static icu::RelativeDateTimeFormatter with appropriate locale, style, and capitalization, plus SimpleDateFormat for time-of-day in the "yesterday" case
  • Add a small ICU-to-Qt conversion helper toQString in an anonymous namespace and include ICU, QLocale, and headers
  • Document behavior that invalid or <1 minute timestamps yield an empty string
panels/notification/common/notifyentity.cpp
panels/notification/common/notifyentity.h
Refactor AppNotifyItem to delegate time formatting to NotifyEntity::formatRelativeTime with a fallback
  • Remove the local ICU-based relative time logic, helper functions, and related ICU/QLocale includes from notifyitem.cpp
  • In AppNotifyItem::updateTime(), call NotifyEntity::formatRelativeTime(m_entity.cTime()) and, if it returns empty but the timestamp is valid, fall back to the translated "Just now" string
  • Keep m_time updated solely via the unified formatter result
panels/notification/center/notifyitem.cpp
Unify bubble time tooltips to use the shared relative time formatter
  • Replace manual minute-based diff calculation in BubbleModel::updateBubbleTimeTip() with a call to NotifyEntity::formatRelativeTime(item->ctime())
  • Only update an item's timeTip when the formatter returns a non-empty string, avoiding tooltips for very recent items
  • Drop now-unused QDateTime include from bubblemodel.cpp
panels/notification/bubble/bubblemodel.cpp
Update SPDX copyright ranges to reflect 2024-2026
  • Adjust SPDX-FileCopyrightText years from "2024" to "2024 - 2026" in modified notification source files
panels/notification/center/notifyitem.cpp
panels/notification/common/notifyentity.cpp
panels/notification/common/notifyentity.h

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • In NotifyEntity::formatRelativeTime, the ICU UErrorCode values (cachedStatus, status, timeStatus) are never checked; consider handling U_FAILURE to avoid using an invalid formatter and provide a deterministic fallback string when ICU operations fail.
  • formatRelativeTime returns an empty string for timestamps less than one minute old, which is treated as "Just now" only by AppNotifyItem::updateTime but not by BubbleModel::updateBubbleTimeTip; please double-check whether this intentional divergence is desired or whether the "Just now" fallback should be handled consistently at the caller or callee level.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `NotifyEntity::formatRelativeTime`, the ICU `UErrorCode` values (`cachedStatus`, `status`, `timeStatus`) are never checked; consider handling `U_FAILURE` to avoid using an invalid formatter and provide a deterministic fallback string when ICU operations fail.
- `formatRelativeTime` returns an empty string for timestamps less than one minute old, which is treated as "Just now" only by `AppNotifyItem::updateTime` but not by `BubbleModel::updateBubbleTimeTip`; please double-check whether this intentional divergence is desired or whether the "Just now" fallback should be handled consistently at the caller or callee level.

## Individual Comments

### Comment 1
<location path="panels/notification/common/notifyentity.cpp" line_range="407-416" />
<code_context>
-            formatter->format(hour, UDAT_DIRECTION_LAST, UDAT_RELATIVE_HOURS, result, status);
-            ret = toQString(result);
-        }
-    } else if (elapsedDay >= 1 && elapsedDay < 2) {
-        formatter->format(1, UDAT_DIRECTION_LAST, UDAT_RELATIVE_DAYS, result, status);
-        UnicodeString combinedString;
</code_context>
<issue_to_address>
**suggestion:** Combining relative day text with a fixed "HH:mm" pattern ignores user’s time-format preferences.

In the 1‑day range you combine ICU’s relative day text with `SimpleDateFormat("HH:mm", ...)`, which forces a 24‑hour clock and ignores system/user time-format settings. Other branches already use `QLocale` to respect local conventions. Please derive the time pattern from `QLocale` (or a user preference) before passing it to `SimpleDateFormat`, or format the time via `QLocale` and use ICU only for the relative day text so the time format stays consistent with user expectations.

Suggested implementation:

```cpp
    } else if (elapsedDay >= 1 && elapsedDay < 2) {
        formatter->format(1, UDAT_DIRECTION_LAST, UDAT_RELATIVE_DAYS, result, status);

        // Format the time according to the user's locale preferences
        const auto locale = QLocale::system();
        const QString localTimeString = locale.toString(time, QLocale::ShortFormat);

        // Convert the localized time string to an ICU UnicodeString
        UnicodeString icuTimeString = icu::UnicodeString::fromUTF8(localTimeString.toUtf8().constData());

        UnicodeString combinedString;
        formatter->combineDateAndTime(result, icuTimeString, combinedString, status);
        return toQString(combinedString);
    } else if (elapsedDay >= 2 && elapsedDay < 7) {

```

1. If not already available in this translation unit, ensure the necessary ICU headers are included for `icu::UnicodeString::fromUTF8` (typically `#include <unicode/unistr.h>` and `#include <unicode/stringpiece.h>`).
2. If the project already has a helper to convert `QString` to `UnicodeString` (for example a `toUnicodeString(const QString &)`), replace the direct `fromUTF8` call with that helper to stay consistent with existing conventions.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread panels/notification/common/notifyentity.cpp
@deepin-ci-robot
Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: 18202781743, xionglinlin

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

Moved relative time formatting logic from BubbleModel and AppNotifyItem
to a common static method in NotifyEntity to eliminate code duplication
and fix incorrect time display issues. Previously, BubbleModel used
a simple minutes calculation while AppNotifyItem used ICU library for
locale-aware formatting, causing inconsistent time display between
notification bubble and center. Now both components use the same unified
formatting logic.

The changes include:
1. Added static method NotifyEntity::formatRelativeTime() that handles
all relative time formatting using ICU library
2. Removed QDateTime include from bubblemodel.cpp as it's no longer
needed
3. Simplified updateBubbleTimeTip() in BubbleModel to use the new common
method
4. Simplified updateTime() in AppNotifyItem to use the new common method
with fallback to "Just now"
5. Updated copyright years to 2024-2026 in modified files

Log: Fixed inconsistent time display between notification bubble and
center

Influence:
1. Test notification time display in both bubble and center views
2. Verify time formatting for different time intervals (just now,
minutes, hours, yesterday, days, weeks)
3. Check locale-specific formatting works correctly
4. Verify time updates dynamically as notifications age
5. Test with notifications from different time periods

fix: 集中相对时间格式化逻辑

将相对时间格式化逻辑从 BubbleModel 和 AppNotifyItem 移动到 NotifyEntity
的公共静态方法中,以消除代码重复并修复时间显示错误问题。之前,
BubbleModel 使用简单的分钟计算,而 AppNotifyItem 使用 ICU 库进行本地化感
知的格式化,导致通知气泡和中心之间的时间显示不一致。现在两个组件都使用相
同的统一格式化逻辑。

变更包括:
1. 添加静态方法 NotifyEntity::formatRelativeTime(),使用 ICU 库处理所有
相对时间格式化
2. 从 bubblemodel.cpp 中移除不再需要的 QDateTime 包含
3. 简化 BubbleModel 中的 updateBubbleTimeTip() 以使用新的公共方法
4. 简化 AppNotifyItem 中的 updateTime() 以使用新的公共方法,并回退到"刚
刚"
5. 在修改的文件中更新版权年份为 2024-2026

Log: 修复通知气泡和中心之间时间显示不一致的问题

Influence:
1. 测试通知气泡和中心视图中的时间显示
2. 验证不同时间间隔(刚刚、分钟、小时、昨天、天数、周数)的时间格式化
3. 检查本地化特定的格式化是否正确工作
4. 验证时间随通知老化而动态更新
5. 测试来自不同时间段的通知

PMS: BUG-355185
Change-Id: I65ddbd0ea5ec943fd7b2c9aa55bc5ed29bd0522b
@deepin-ci-robot
Copy link
Copy Markdown

deepin pr auto review

这段,这个 diff 主要进行了代码重构,将时间格式化的逻辑从 notifyitem.cppbubblemodel.cpp 提取到了 NotifyEntity 类中,作为一个静态工具函数 formatRelativeTime。这种修改符合 DRY(Don't Repeat Yourself)原则。

以下是对代码的详细审查意见:

1. 语法逻辑

  • 正确性:代码逻辑基本正确。将原本分散的 ICU 初始化和格式化逻辑移入 NotifyEntity::formatRelativeTime 中,并在调用处(notifyitem.cppbubblemodel.cpp)进行复用。
  • 版权年份更新SPDX-FileCopyrightText2024 更新为 2024 - 2026,这通常是预期的,表示维护周期。
  • 头文件包含
    • bubblemodel.cpp 移除了 #include <QDateTime>。由于 bubblemodel.cpp 不再直接调用 QDateTime::currentMSecsSinceEpoch,这是正确的。
    • notifyitem.cpp 移除了 #include <QLocale> 和 ICU 相关头文件。由于逻辑被移走,这也是正确的。
    • notifyentity.cpp 新增了 #include <QLocale> 和 ICU 头文件,这是必要的。
  • 逻辑一致性
    • bubblemodel.cpp 中原来的逻辑是 "如果超过 60 秒,显示 'X minutes ago'"。
    • 新的 formatRelativeTime 逻辑是 "如果小于等于 0 分钟,返回空字符串;如果大于 0 且小于 60 分钟,显示 ICU 格式化的分钟"。
    • 潜在问题:在 bubblemodel.cpp 中,原逻辑 if (diff >= 60) 意味着 60 秒到 119 秒之间显示 "1 minutes ago"。新逻辑中 minute > 0 && minute < 60 涵盖了 1 到 59 分钟。逻辑上存在细微差别:原代码在 60 秒整时(1分钟)更新,新代码在 61 秒(>1分钟)时更新。不过考虑到 updateBubbleTimeTip 是定时器调用的,这个差异影响极小,且新逻辑更符合人类直觉(刚过1分钟才显示)。
    • 空字符串处理formatRelativeTime 对于 minute <= 0 的情况返回空字符串 {}。在 notifyitem.cpp 中,针对空字符串有回退逻辑:if (ret.isEmpty()) { ... ret = tr("Just now"); }。这是很好的设计,保留了"刚刚"的显示。但在 bubblemodel.cpp 中,if (!timeTip.isEmpty()) { item->setTimeTip(timeTip); },如果返回空,气泡的时间提示将不会被更新(保持旧值或为空)。建议:确认 bubblemodel.cpp 中对于小于 1 分钟的通知,是否需要显示 "刚刚" 或者保持不显示。如果需要保持行为一致,bubblemodel.cpp 也应添加类似的回退逻辑。

2. 代码质量

  • 可读性与可维护性:显著提升。将复杂的 ICU 相对时间格式化逻辑集中在一个地方,修改时间显示格式只需改动一处。
  • 静态局部变量:在 formatRelativeTime 中使用了 static std::unique_ptr<RelativeDateTimeFormatter> formatter。这是一个很好的性能优化,避免了每次调用都重新创建 ICU formatter 对象(ICU 对象初始化开销较大)。
  • 匿名命名空间:在 notifyentity.cpp 中使用匿名命名空间包裹 toQString 辅助函数,限制了其作用域,避免了污染全局命名空间,符合 C++ 最佳实践。
  • 注释notifyentity.h 中新增的函数注释 // Formats a creation time... 清晰明了,值得肯定。

3. 代码性能

  • ICU 对象复用:如上所述,static std::unique_ptr 的使用极大地提升了性能,减少了内存分配和初始化开销。
  • QDateTime 计算QDateTime::currentDateTime()QDateTime::currentMSecsSinceEpoch() 在函数中被调用。由于这是一个静态工具函数,且可能在短时间内被多次调用(例如遍历通知列表),这是合理的。
  • 字符串转换toQString 函数直接操作 ICU 内部 buffer 并转换为 QString,避免了不必要的中间拷贝,效率较高。

4. 代码安全

  • ICU 错误处理:代码中使用了 UErrorCode status = U_ZERO_ERROR;,但在调用 formatter->formattimeFormatter.format 后,并未检查 status。虽然 ICU 库在大多数情况下能优雅处理错误,但在生产环境中,忽略 ICU 错误状态可能导致格式化失败但程序静默继续执行,或者返回空字符串/乱码。
    • 建议:在关键 ICU 操作后检查 U_FAILURE(status),如果失败,记录日志或回退到简单的 Qt 格式化方式。
  • 空指针检查formatter 是静态智能指针,初始化后不会为空。但在多线程环境下,第一次初始化时的竞态条件需要考虑。不过,由于 static 局部变量的初始化在 C++11 后是线程安全的,所以这里没有问题。
  • 类型转换reinterpret_cast<const QChar *> 用于将 UChar* 转换为 QChar*。在大多数平台(两者都是 16 位)这是安全的,但如果 ICU 配置为使用 8 位(UChar8),则会有问题。不过标准的 ICU 构建通常使用 16 位 UChar,且 Qt 也是 UTF-16,所以风险较低。

改进建议

  1. 统一行为
    bubblemodel.cpp 中,当 formatRelativeTime 返回空字符串时(即小于 1 分钟),目前逻辑是不更新时间提示。建议修改为与 notifyitem.cpp 一致,显示 "刚刚"。

    // bubblemodel.cpp
    QString timeTip = NotifyEntity::formatRelativeTime(item->ctime());
    if (timeTip.isEmpty()) {
        timeTip = tr("Just now");
    }
    item->setTimeTip(timeTip);
  2. ICU 错误检查
    notifyentity.cppformatRelativeTime 中增加错误检查,增强健壮性。

    // 示例:在 format 调用后
    formatter->format(minute, UDAT_DIRECTION_LAST, UDAT_RELATIVE_MINUTES, result, status);
    if (U_FAILURE(status)) {
        qCWarning(notifyLog) << "ICU format failed:" << u_errorName(status);
        return {}; // 或者回退到 Qt 的简单格式化
    }
  3. 时间格式化的一致性
    原代码在 notifyitem.cpp 中对于 elapsedDay >= 2 && elapsedDay < 7 使用了 QLocale::system().toString(time, "ddd hh:mm")。注意这里使用的是 12 小时制的 hh。而 else 分支使用的是 QLocale::system().dateFormat(...),这通常是系统设置的短日期格式。新代码完全保留了这一逻辑。请确认这是否符合需求(例如某些用户可能偏好 24 小时制 HH:mm)。如果需要统一,可以考虑使用 QLocale::system().toString(time, QLocale::ShortFormat) 或其他统一格式。

总结

这次重构是一个正向的改动,显著提高了代码的模块化程度和可维护性,并通过复用 ICU formatter 对象优化了性能。主要的风险点在于 ICU 错误处理的缺失以及 bubblemodel.cpp 中对空返回值的处理可能与 notifyitem.cpp 不一致。建议采纳上述改进建议以增强代码的健壮性和一致性。

@xionglinlin xionglinlin merged commit afb9331 into linuxdeepin:master Apr 16, 2026
11 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants