Android 时区使用和总结

2023-11-07

最近负责的车机项目是海外项目,涉及到全球多个地区。应用开发人员在使用时区时遇到一些问题,故本人做了一点学习;本文基于android9;
可以使用这些网址查询城市的时间时区等信息:
https://time.bmcx.com/Chatham_Islands__localtime/
http://www.timeofdate.com/city

目录

  • 模块架构
  • 基本方法使用
  • 主要方法代码流程

一、模块架构

主要分为三个部分,API层AlarmManager,framework层AlarmManagerService,以及Native层com_android_server_AlarmManagerService;

在这里插入图片描述
AlarmManager:封装在framework.jar中,提供给APP调用的接口,会用到一些工具类zoneInfo、TzData等;
AlarmManagerService:service层,时区、时间等相关的逻辑处理,也会用到工具类zoneInfo、TimeZone等;
com_android_server_AlarmManagerService:Native层,通过JNI被服务层调用;

二、时区基本方法使用

使用比较简单,通过AlarmManager完成对alarm服务的一系列操作。基本的操作主要包括设置时区、监听时区变化、主动查询时区,基本满足应用开发的使用。主要demo代码如下:

2.1 设置系统默认时区

在系统层编译时,在mk文件中设置系统属性如下,调试时也可以根据查询这个属性确定当前得时区是哪里;

 PRODUCT_DEFAULT_PROPERTY_OVERRIDES += persist.sys.timezone=Asia/Shanghai   //设置系统默认时区为上海时区

2.2 查询系统支持的时区

不是所有得城市都支持直接的城市时区设置。当前系统大体支持两种方式设置时区,一种是通过设置城市得到相应得时区,另一种是设置GMT时区获得到对应时区;如下方法可以获得当前支持设置得时区参数;

	String[] availableIDs = TimeZone.getAvailableIDs();
	Log.i("luyao", "onClick: bt_setzone可用zoneId总数 ="+availableIDs.length);
	for (String zoneId : availableIDs) {
		Log.i("luyao", "onClick: bt_setzone= "+zoneId);
	}

对应的打印如下

2020-08-15 00:37:26.957 12555-12555/com.example.mtktest I/luyao: onClick: bt_setzone可用zoneId总数 =591
2020-08-15 00:37:26.957 12555-12555/com.example.mtktest I/luyao: onClick: bt_setzone= Africa/Abidjan
2020-08-15 00:37:26.957 12555-12555/com.example.mtktest I/luyao: onClick: bt_setzone= Africa/Accra
2020-08-15 00:37:26.957 12555-12555/com.example.mtktest I/luyao: onClick: bt_setzone= Africa/Addis_Ababa
2020-08-15 00:37:26.957 12555-12555/com.example.mtktest I/luyao: onClick: bt_setzone= Africa/Algiers
2020-08-15 00:37:26.957 12555-12555/com.example.mtktest I/luyao: onClick: bt_setzone= Africa/Asmara
2020-08-15 00:37:26.957 12555-12555/com.example.mtktest I/luyao: onClick: bt_setzone= Africa/Asmera
...
2020-08-15 00:37:26.987 12555-12555/com.example.mtktest I/luyao: onClick: bt_setzone= Etc/GMT+0
2020-08-15 00:37:26.988 12555-12555/com.example.mtktest I/luyao: onClick: bt_setzone= Etc/GMT+1
2020-08-15 00:37:26.988 12555-12555/com.example.mtktest I/luyao: onClick: bt_setzone= Etc/GMT+10
2020-08-15 00:37:26.988 12555-12555/com.example.mtktest I/luyao: onClick: bt_setzone= Etc/GMT+11
...

2.3 设置时区

主要可以通过设置城市或者设置GMT的方式设置时区

AlarmManager alarm;
...
alarm= (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);//获得AlarmManager服务对象

alarm.setTimeZone("Asia/Shanghai");//设置上海时区
alarm.setTimeZone("GMT"); //0时区时间,可以去除夏令时
alarm.setTimeZone("Etc/GMT-8"); //设置东八区的时区

2.4 接收时区变化

通过广播的方式,系统层通过广播发送,应用层接收:

private TimeZoneChangeReceiver timeZoneChangeReceiver;
...
timeZoneChangeReceiver = new TimeZoneChangeReceiver();
IntentFilter filter = new IntentFilter(ACTION_TIMEZONE_CHANGED);
registerReceiver(timeZoneChangeReceiver, filter);//注册广播接收器
...
//创建广播接收器
    public class TimeZoneChangeReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals(ACTION_TIMEZONE_CHANGED)) {
                // 处理时区变化的逻辑
                String newTimeZone = intent.getStringExtra("time-zone");
                Log.d("luyao", "时区变化:" + newTimeZone);
            }
        }
    }

2.5 查询当前时区

TimeZone timeZone2 = TimeZone.getDefault();//获得当前时区对象
String timeZoneName = timeZone2.getID();//时区名字
String timeZoneutc = timeZone2.getDisplayName();//时区对应的称呼,比如格林尼治时区,中国标准时区

TimeZone timeZone = TimeZone.getTimeZone("Asia/Shanghai");//获取上海时区对象
boolean usesDaylightTime = timeZone.useDaylightTime();//查询当前的城市时区是否使用夏令时

三、主要方法代码流程

setTimeZone

设置时区方法,接口时序图如下;
源码路径:
frameworks/base/core/java/android/app/AlarmManager.java
frameworks/base/services/core/java/com/android/server/AlarmManagerService.java
frameworks/base/services/core/jni/com_android_server_AlarmManagerService.cpp
libcore/luni/src/main/java/libcore/util/ZoneInfo.java
libcore/luni/src/main/java/libcore/util/ZoneInfoDB.java
libcore/ojluni/src/main/java/java/time/ZoneId.java
在这里插入图片描述

首先暴露给到应用使用的接口setTimeZone(String timeZone) ,参数timeZone的格式为:大洲/城市名,例如Asia/Shanghai;
源码路径:
frameworks/base/core/java/android/app/AlarmManager.java

    public void setTimeZone(String timeZone) {
        if (TextUtils.isEmpty(timeZone)) {
            return;
        }

        // Reject this timezone if it isn't an Olson zone we recognize.
        if (mTargetSdkVersion >= Build.VERSION_CODES.M) {
            boolean hasTimeZone = false;
            try {
                hasTimeZone = ZoneInfoDB.getInstance().hasTimeZone(timeZone);//判断设置的城市是否在时区数据库中,是否支持设置
            } catch (IOException ignored) {
            }
            if (!hasTimeZone) {
                throw new IllegalArgumentException("Timezone: " + timeZone + " is not an Olson ID");
            }
        }
        try {
            mService.setTimeZone(timeZone);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }

关键方法,判断是否是支持的城市:ZoneInfoDB.getInstance().hasTimeZone(timeZone),如果是支持的城市继续调用AlarmManagerService的setTimeZone方法;
ZoneInfoDB源码路径:
libcore/luni/src/main/java/libcore/util/ZoneInfo.java

   public boolean hasTimeZone(String id) throws IOException {
      checkNotClosed();
      return cache.get(id) != null;//返回是否对应城市是否存在TimeZone对象
    }

AlarmManagerService源码路径:
frameworks/base/services/core/java/com/android/server/AlarmManagerService.java

    @Override
    public void setTimeZone(String tz) {
				...
                setTimeZoneImpl(tz);
 				...
    }
	...
   
   void setTimeZoneImpl(String tz) {
		...
        TimeZone zone = TimeZone.getTimeZone(tz);//获得对应timezone对象
        // Prevent reentrant calls from stepping on each other when writing
        // the time zone property
        boolean timeZoneWasChanged = false;
        synchronized (this) {
            String current = SystemProperties.get(TIMEZONE_PROPERTY);
            if (current == null || !current.equals(zone.getID())) {
                //if (localLOGV) {
                    Slog.i(TAG, "timezone changed: " + current + ", new=" + zone.getID());
                //}
                timeZoneWasChanged = true;
                SystemProperties.set(TIMEZONE_PROPERTY, zone.getID());
            }
            // Update the kernel timezone information
            // Kernel tracks time offsets as 'minutes west of GMT'
            int gmtOffset = zone.getOffset(System.currentTimeMillis());//查询当前时间和对应时区的时间的偏差值,是个时间戳,单位是毫秒 1000*60*60*24

            setKernelTimezone(mNativeData, -(gmtOffset / 60000));//把需要修改的时间设置给到内核
        }

        TimeZone.setDefault(null);

        if (timeZoneWasChanged) {//时区如果变化,广播出去
            Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
            intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
                    | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
                    | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
            intent.putExtra("time-zone", zone.getID());
            getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
        }
    }

上面方法实现中主要调用了两个方法,TimeZone.getOffset(System.currentTimeMillis())和setKernelTimezone(mNativeData, -(gmtOffset / 60000)); 其中getoffset用于查询当前时间和对应时区的时间的偏差值,而setKernelTimezone是将需要修改的时间设置给到系统内核,完成系统时间的修改;
Timezone源码路径:
libcore/ojluni/src/main/java/java/util/TimeZone.java

   public int getOffset(long date) {
        if (inDaylightTime(new Date(date))) {//判断是否是夏令时,如果是加上夏令时的时间
            return getRawOffset() + getDSTSavings();
        }
        return getRawOffset();
    }

inDaylightTime、getDSTSavings方法源码目录:
libcore/luni/src/main/java/libcore/util/ZoneInfo.java

    @Override public boolean inDaylightTime(Date time) {
        long when = time.getTime();
        int offsetIndex = findOffsetIndexForTimeInMilliseconds(when);//查找此时间在数组中的index
        if (offsetIndex == -1) { //未查到,说明无夏令时
            // Assume that all times before our first transition are non-daylight.
            // Transition data tends to start with a transition to daylight, so just
            // copying the first transition would assume the opposite.
            // http://code.google.com/p/android/issues/detail?id=14395
            return false;
        }
        return mIsDsts[offsetIndex] == 1;//此数据库保存是否支持夏令时
    }
public int getDSTSavings() {
        if (useDaylightTime()) {
            return 3600000;//一个小时
        }
        return 0;
    }

四、夏令时DST

夏令时又称夏季时间(没有冬令时概念)。它是为节约能源而人为规定地方时间的制度(鼓励人们早睡早起,不要浪费电,夏天日照时间长尽量多用自然资源),全球约40%的国家在夏季使用夏令时,其他国家则全年只使用标准时间。正在使用夏令时的代表国家:美国、欧盟、俄罗斯等等。
夏令时设置的原理:
通过改变时区和时间偏移来实现的。当夏令时开始时,系统会自动将时间偏移增加一小时;当夏令时结束时,系统会自动将时间偏移减少一小时。这样,系统的时钟就会自动调整为夏令时或标准时间。每年的夏令时时间段还不一样(一般在3月的第2个周日开始),比如美国2020年夏令时时间是:2020年3月8日 - 2020年11月1日。具体做法是:在3.8号这天将时钟往前拨拨1个小时,11.1号这天还原回来。
夏令时相关的方法使用如下所示:


alarm.setTimeZone("Australia/Sydney");//设置悉尼时区

TimeZone timeZone2 = TimeZone.getDefault();
String timeZoneName = timeZone2.getID();

Date date = new Date();
Log.d("luyao2", "当前data:" +date.toString());
boolean isdayligt = timeZone2.inDaylightTime(date);//查询当前时间,悉尼是否在使用夏令时
boolean usesDaylightTime = timeZone.useDaylightTime();//查询当前的城市时区是否支持使用夏令时
timeZone2.getDSTSavings();//夏令时的时间偏差是多少
timeZone2.getRawOffset();//UTC的时间偏差是多少
Log.d("luyao2", "当前时区:" + timeZoneName+  "  timeZone2.getDisplayName() "+timeZone2.getDisplayName()+ "  isdayligt="+isdayligt +"  useDaylightTime():" + timeZone2.useDaylightTime()+" getDSTSavings()"+timeZone2.getDSTSavings()+ " "+timeZone2.getRawOffset());
上面的打印日志如下:
2020-08-15 00:42:47.125 13157-13157/com.example.mtktest D/luyao: 时区变化:Australia/Sydney
2020-08-15 00:42:48.274 13157-13157/com.example.mtktest D/luyao2: 当前data:Sat Aug 15 02:42:48 GMT+10:00 2020
2020-08-15 00:42:48.275 13157-13157/com.example.mtktest D/luyao2: 当前时区:Australia/Sydney  timeZone2.getDisplayName() Australian Eastern Standard Time  isdayligt=false  useDaylightTime():true getDSTSavings()3600000 36000000

Android系统默认支持夏令时策略,所以在做国际多国项目时需要对夏令时做充足的了解。在应用使用setTimeZone(“Asia/Shanghai”);的方式设置时区时,会默认存在夏令时。而使用setTimeZone(“Etc/GMT-8”)的方式设置时区时不会有夏令时的策略存在。夏令时的策略是由android集成的一个数据库tzdata里定义。若发生某城市夏令时不准确时,可以考虑是否时这个数据库太老导致,可以尝试更新这个库。

五、常见问题

城市不支持设置时区
UE团队出的设计图中,需要在设置里提供很多城市的时区设置。但是当前系统的tzdata数据库中仅支持部分城市时区设置,比如需要设置北京时区,但是无法通过setTimeZone(“Asia/Beijing”)这种方式设置,因为数据库中没有这个城市;
部分夏令时不准确
由于数据库太老,可以尝试更新tzdata数据库。在不方便更新数据库时,可以考虑对这个城市的夏令时时间做二次加工。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android 时区使用和总结 的相关文章

随机推荐

  • Android面试常用面试题

    1 Android手机横竖屏切换的问题及其解决方案 默认情况下 横竖屏切换的时候 系统会销毁当前的Activity 然后新建一个Activity 显然 太浪费资源了 http www cnblogs com zhangkai281 arch
  • 机器学习(machine learning)之AdaBoost算法

    转载自 http blog csdn net haidao2009 article details 7514787 菜鸟最近开始学习machine learning 发现adaboost 挺有趣 就把自己的一些思考写下来 主要参考了http
  • TCP吞吐量的理论计算公式

    源 本篇文章本来是收录AIMD拥塞控制吞吐量的计算公式 Valve游戏公司开源GameNetworkingSockets 1 既支持可靠的数据传输 也支持不可靠的数据传输 数据的传输速率 是直接计算出来的 const int64 k nMi
  • HDLBits刷题_Verilog Language_Module pos

    学习内容 This problem is similar to the previous one module You are given a module named mod a that has 2 outputs and 4 inpu
  • 2019年TI电赛总结

    本人某不知名大学菜鸡一个 近几年比赛分析 首先 TI公司承办了未来好几年的电子设计竞赛 TI杯近几年出题还是很有技术的 省赛好多题目可以说是为国赛做准备的 所以想要参加国赛取得好成绩的同学 就要提前参加省赛或者做一做省赛的相关题目 之前不是
  • 使用.debug_info调试信息查看结构体、位域变量内存分配

    上一篇使用readelf查看了 debug info调试信息 现在我们对它进行分析 首先将调试信息保存到文档中 readelf wi test gt out txt 结构体分析 首先 我在结构体里定义了一个char型和一个double型 编
  • 容器的docker-compose怎样写agent.jar配置 -javaagent和其他环境配置怎么写

    在 Docker Compose 文件中配置 Java Agent 以及其他环境配置时 您需要将这些配置添加到 environment 字段中 以下是一个示例 演示了如何在 Docker Compose 文件中配置 Java Agent 和
  • 盘点各种边缘检测算子

    Sobel算子 原理 Sobel算子是一种基于梯度算子的边缘检测算子 它使用两个3x3的卷积核 分别对图像在水平和垂直方向进行卷积操作 然后将两个方向的梯度响应合并得到边缘强度 特点 Sobel算子简单易实现 计算效率高 对噪声有一定的抑制
  • MES系统的优势和上了MES系统后的成果

    1 辅助排产 2 状态监控 3 进度监控 4 效率统计 5 品质管理 在五大功能的保驾护航下 树字工厂机床管家云具有六大优势 1 花费低 2 上线快 3 使用易 4 功能全 完全具备MES的核心功能 智能辅助排产 进度监控 设备状态监控 生
  • MS5607使用中的问题,温度低于20℃,数值不对

    MS5607使用中的问题 温度低于20 马后炮 因为是网上找的代码 懒得看手册 导致后面低于20摄氏度还会有问题 只好认真的看了手册 发现我的代码是ms5611的 照着手册重新改写后就木有问题了 不能省的地方就别省了 公司使用MS5607设
  • Unity Animator入门:使用Animator和trigger参数做简单的UI动画

    Unity中使用Animator和trigger参数做简单的UI动画 概述 原理 Toast组件构成 添加Animator组件 创建default状态动画 录制hide和show动画 设置loopTime 调整各状态的关系 编写脚本 概述
  • Vcs+Verdi 联调

    lmg vcs lmdown y 等待两分钟 再次输入指令 lmg vcs 激活license 目录 主要参考示例 问题1 需要修改默认的shell 问题2 v 快速在flist添加路径失败 问题0 bash alias verdi 未找到
  • 一张图弄明白开源协议-GPL、BSD、MIT、Mozilla、Apache和LGPL 之间的区别

    导读 在开源软件中经常看到各种协议说明 GPL BSD MIT Mozilla Apache和LGPL 这些协议之间的有什么区别 如何选择合适的开源协议 请看下文 特作记录一篇 以供后续查看 参考 阮一峰的网络日志
  • 图灵完备机

    1936年提出的一种抽象计算模型 它可以模拟任何可计算函数 如果一个计算模型或编程语言具有与图灵机等效的计算能力 那么它就被称为图灵完备的 图灵完备性是计算机科学中的一个重要概念 因为它代表了一种最小化的计算能力标准 一个图灵完备的系统能够
  • 互关,互关,互赞,有关必回,有赞必会,有评论必回

    互关 互关 互赞 有关必回 有赞必会 有评论必回
  • 按位与、按位或、按位异或等等(&

    位运算概述 从现代计算机中所有的数据二进制的形式存储在设备中 即0 1两种状态 计算机对二进制数据进行的运算 都是叫位运算 即将符号位共同参与运算的运算 口说无凭 举一个简单的例子来看下CPU是如何进行计算的 比如这行代码 int a 35
  • 找工作知识储备(3)---从头说12种排序算法:原理、图解、动画视频演示、代码以及笔试面试题目中的应用

    作者 寒小阳 时间 2013年9月 出处 http blog csdn net han xiaoyang article details 12163251 声明 版权所有 转载请注明出处 谢谢 0 前言 从这一部分开始直接切入我们计算机互联
  • 【Go】sqlite3包配置和使用

    系统 Win10 IDE VsCode 1 初始化mod 在项目中先初始化mod 已经初始化过则忽略 打开终端 快捷键Ctrl Shift 执行go mod init xxx xxx为文件夹名 2 获取sqlite3 在golang官网的p
  • Idea maven项目不能新建package和class的解决

    如图 新建的maven项目不能新建package 这是因为java是普通的文件夹 要设置为 原文地址
  • Android 时区使用和总结

    最近负责的车机项目是海外项目 涉及到全球多个地区 应用开发人员在使用时区时遇到一些问题 故本人做了一点学习 本文基于android9 可以使用这些网址查询城市的时间时区等信息 https time bmcx com Chatham Isla