电话时区检测

对于搭载 Android 11 或更低版本的设备,AOSP 中的自动时区检测依赖于来自电话子系统的信号。由于依赖于电话子系统,因此 Android 11 或更低版本上的自动时区检测功能仅限用于电话设备。

电话时区检测功能可用时,会使用移动设备国家/地区代码 (MCC)网络身份和时区 (NITZ) 信号工作。

例如,位于法国的设备只能根据附近的手机基站报告的 MCC 来识别时区。之所以能这么做,是因为法国已知使用一个时区。

如果某个国家/地区使用多个时区,则仅靠 MCC 不足以识别时区。对于这类国家/地区,设备还会使用 NITZ 信号来识别正确的时区。这种方法适合世界上的许多地方,但它要求 NITZ 信号可用且正确,因此依赖于运营商。

电话时区检测是一种被动的检测器。它会一直运行,因此即使 time_zone_detector 来源目前不是电话,系统通常也会提供电话建议。

电话时区检测的限制

即使 NITZ 信号可用且正确,电话时区检测也不一定适合所有国家/地区。这是因为 NITZ 仅包含偏移值和夏令时的信息,而依靠这些信息有时无法唯一地标识时区。

世界上有许多地方都可能会出现此类时区问题。例如,美国的科罗拉多州丹佛和亚利桑那州菲尼克斯的时区在冬季无法使用 NITZ 信号进行区分,但在其他季节可以。具有类似重叠时区的任何位置都可能会遇到这种问题。

下表根据季节对丹佛和菲尼克斯的设备行为进行了细分,提供了一个示例:

位置和季节 来自 MCC 或 NITZ 的信息 检测到的时区和行为
科罗拉多州丹佛
冬季
时间:2021 年 1 月 1 日 12:00:00
国家/地区:美国
偏移值:UTC-7,没有夏令时
两个时区 ID 匹配:
  • 美国/丹佛
  • 美国/菲尼克斯

设备正确地设置为美国/丹佛。
亚利桑那州菲尼克斯
冬季
时间:2021 年 1 月 1 日 12:00:00
国家/地区:美国
偏移值:UTC-7,没有夏令时
两个时区 ID 匹配:
  • 美国/丹佛
  • 美国/菲尼克斯

设备错误地设置为美国/丹佛。
科罗拉多州丹佛
夏季
时间:2021 年 7 月 1 日 12:00:00
国家/地区:美国
偏移值:UTC-6,有夏令时
一个时区 ID 匹配:
  • 美国/丹佛

设备正确地设置为美国/丹佛。
亚利桑那州菲尼克斯
夏季
时间:2021 年 7 月 1 日 12:00:00
国家/地区:美国
偏移值:UTC-7,没有夏令时
一个时区 ID 匹配:
  • 美国/菲尼克斯

设备正确地设置为美国/菲尼克斯。

上面的示例表明,在冬天,位于丹佛或亚利桑纳的 Android 设备必须从两个匹配的时区 ID 中选择一个,相应 ID 对于某些设备可能是不正确的,但这些设备仍然会显示明显正确的当地时间。即使时区 ID 不正确,设备时钟、日历和其他应用也会显示预期的当地时间,因为两个时区 ID 在冬季计算的是同一当地时间。

不过,当春天丹佛实行夏令时而菲尼克斯不实行时,某些设备如果设置为对用户的位置而言错误的时区 ID,可能会暂时显示错误的当地时间。设备收到新的 NITZ 信号(具体而言,包含“UTC-7,没有夏令时”偏移值信息的信号)后,会立即纠正此错误,但这可能需要一些时间,且依赖于运营商。

因此,从冬季到春季,日历或会存储或保留时区 ID 的其他应用可能会显示和使用错误的当地时间,直到相关应用更新时区 ID。

调试和测试

以下部分介绍了用于调试和测试电话时区检测功能的 shell 命令。

测试环境设置

测试人员通常使用包含测试或模拟电话手机的测试环境来检查电话时区检测行为。测试手机可用于模拟具有不同 MCC 的网络,以及将 NITZ 信号发送到设备,然后监控其影响。

为了让设备能够检测时区,NITZ 信号信息必须正确无误,与 MCC 一致,且符合设备的 IANA TZDB 副本(时区规则)。与 MCC 不一致的 NITZ 信号会导致电话来源变得不确定。

例如,如果测试手机使用的 MCC 对应美国,则 NITZ 信号必须包含关于美国某地的正确的“世界时间”、偏移值和夏令时信息。

与 com.android.phone 服务交互

如需验证设备是否收到了正确的电话时区建议,请使用以下命令:

adb shell dumpsys activity service \
    com.android.phone/com.android.phone.TelephonyDebugService

这会转储电话信息,这些信息也可在 Android bug 报告中找到。在安装了多张 SIM 卡的设备上,提供了各 SIM 卡无线装置的信息。

“时区日志”显示电话流程已发送到 time_zone_detector 的建议,以及发送建议的原因。

TimeServiceHelperImpl:
          SystemClock.elapsedRealtime()=11864061
          System.currentTimeMillis()=1620652067178
          Time Logs:
...

Time zone Logs:
    18602 / 2021-05-10T09:50:21.718Z - Suggesting time zone update:
    TelephonyTimeZoneSuggestion{mSlotIndex=0, mZoneId='null', mMatchType=0, mQuality=0,
    mDebugInfo=[getTimeZoneSuggestion: nitzSignal=TimestampedValue{mReferenceTimeMillis=14098,
    mValue=NitzData{mOriginalString=21/05/10,09:50:18+04,01, mZoneOffset=3600000,
    mDstOffset=3600000, mCurrentTimeMillis=1620640218000, mEmulatorHostTimeZone=null}},
    countryIsoCode=null, Detection
    reason=handleNitzReceived(TimestampedValue{mReferenceTimeMillis=14098,
    mValue=NitzData{mOriginalString=21/05/10,09:50:18+04,01, mZoneOffset=3600000,
    mDstOffset=3600000, mCurrentTimeMillis=1620640218000, mEmulatorHostTimeZone=null}})]}
    18831 / 2021-05-10T09:50:21.948Z - Suggesting time zone update:
    TelephonyTimeZoneSuggestion{mSlotIndex=0, mZoneId='Europe/London', mMatchType=3, mQuality=1,
    mDebugInfo=[findTimeZoneFromCountryAndNitz: countryIsoCode=gb,
    nitzSignal=TimestampedValue{mReferenceTimeMillis=14098,
    mValue=NitzData{mOriginalString=21/05/10,09:50:18+04,01, mZoneOffset=3600000,
    mDstOffset=3600000, mCurrentTimeMillis=1620640218000, mEmulatorHostTimeZone=null}},
    findTimeZoneFromCountryAndNitz: lookupResult=OffsetResult{mTimeZone(ID)=Europe/London,
    mIsOnlyMatch=true}, Detection reason=handleCountryDetected("gb")]}