去年有一个需要同步俩端(客户端、服务端)时间的需求,起因是俩端的系统时间不一致,导致需求实现后出现时差,导致业务出错的问题,故此抽时间整理了一下时间日期相关的知识 ~
兄弟篇
- 进阶之路 - 计算时间差
- 项目实战 - 日期时间工具
If heart is sunny
- 12、24小时制
- 时间更新机制
- 修改系统时间
- Andorid 代码
- cmd 命令
- 修改时区
- 相互转换时区、时间
- 关闭自动同步,实现自定同步
在正式开始前优先要介绍一下时区
,1884年 国际经度会议将地球表面按经线等分为24 区,即以本初子午线为基准, 东西经度各7.5 度的范围作为零时区, 然后每隔15度为一时区,每个时区相差一小时
中国时区横跨东五区、东六区、东七区、东八区、东九区等五个地理时区;自西元1949年起,在中国大陆、台湾、香港、澳门所使用的标准时间皆为东八区(UTC+8)时间,比协调世界时间(UTC)或格林尼治标准时间(GMT)快8小时
由于历史及政治因素,上述各地维持各自的时间标准及时区名称
- 中国大陆的时间标准:北京时间 (北京时区为东八区,要比零时区早8个小时。如果现在零时区的时间是10点的话,那北京时间就是18点)
- 台湾的时间标准称为中原标准时间或国家标准时间:台湾时间或台北时间
- 香港的时间标准:香港时间
- 澳门的时间标准:澳门标准时间
我们平时在程序里面所见到的UTC
时间,就是零时区的时间,它的全称是Coordinated Universal Time ,即世界协调时间。另一个常见的缩写是GMT
,即格林威治标准时间,格林威治位于 零时区,因此,我们平时说的UTC时间和GMT时间在数值上面都是一样的。
UTC(世界标准时间)
协调世界时,又称世界标准时间或世界协调时间,简称UTC(从英文“Coordinated Universal Time”/法文“Temps
Universel Coordonné”而来),是最主要的世界时间标准,其以原子时秒长为基础,在时刻上尽量接近于格林尼治标准时间。
GMT(格林尼治平时)
格林尼治平时(又称格林尼治平均时间或格林尼治标准时间,旧译格林威治标准时间;英语:Greenwich Mean
Time,GMT)是指位于英国伦敦郊区的皇家格林尼治天文台的标准时间,因为本初子午线被定义在通过那里的经线。
理论上来说,格林尼治标准时间的正午是指当太阳横穿格林尼治子午线时(也就是在格林尼治上空最高点时)的时间;
由于地球在它的椭圆轨道里的运动速度不均匀,这个时刻可能与实际的太阳时有误差,最大误差达16分钟
由于地球每天的自转是有些不规则的,而且正在缓慢减速,因此格林尼治时间已经不再被作为标准时间使用。现在的标准时间,是由原子钟报时的协调世界时(UTC)
CST(北京时间)
北京时间,China Standard Time,中国标准时间。在时区划分上,属东八区,比协调世界时早8小时,记为UTC+8
不过这个CST这个缩写比较纠结的是它可以同时代表四个不同的时间:
Central Standard Time (USA) UT-6:00
Central Standard Time (Australia) UT+9:30
China Standard Time UT+8:00
Cuba Standard Time UT-4:00
12、24小时制
格式化时间为12和24小时制
Date date=new Date();
//转换成时间格式12小时制
SimpleDateFormat df_12=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
//转换成时间格式24小时制
SimpleDateFormat df_24=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("12小时制时间:"+df_12.format(date));
System.out.println("24小时制时间:"+df_24.format(date));
输出结果
12小时制时间:2017-12-07 03:55:04
24小时制时间:2017-12-07 15:55:04
时间更新机制
设备上一般都自动更新系统时间,主要有以下俩种更新机制
NITZ
NITZ(Network Identity and Time Zone,网络标识和时区),是一种用于自动配置本地的时间和日期的机制,同时也通过无线网向移动设备提供运营商信息
NITZ是自从PHASE 2+ RELEASE 96 的GSM中的可选功能,经常被用来自动更新移动电话的系统时钟
NITZ需要运营商网络支持(通过CS网络),目前国内电信、移动都支持NITZ方式更新时间日期,而联通目前可能不支持
NTP
NTP:NTP(Network Time Protocol)提供准确时间,首先要有准确的时间来源,这一时间应该是国际标准时间UTC。
NTP获得UTC的时间来源可以是原子钟、天文台、卫星,也可以从Internet上获取。这样就有了准确而可靠的时间源。时间按NTP服务器的等级传播。
NTP与NITZ不同的是,NTP需要从专门的NTP服务器来获取时间,只要手机连接上网络,都可以实现时间的更新。
时区处理
package com.nk.machine.model;
import java.text.DateFormat;
import java.util.Date;
import java.util.TimeZone;
/**
* @author MrLiu
* @date 2020/9/24
* desc
*/
public class TimeZoneTest {
public static void main(String[] args) {
//测试创建TimeZone对象的3种方法
showUsageOfTimeZones();
//测试TimeZone的其它API
testOtherAPIs();
//打印getTimeZone(String id)支持的所有id
//printAllTimeZones() ;
}
/**
* 测试创建TimeZone对象的3种方法
*/
public static void showUsageOfTimeZones() {
TimeZone tz;
// (01) 默认时区
tz = TimeZone.getDefault();
printDateIn(tz);
// (02) 设置时区为"GMT+08:00"
tz = TimeZone.getTimeZone("GMT+08:00");
printDateIn(tz);
// (03) 设置时区为""
tz = TimeZone.getTimeZone("Asia/Chongqing");
printDateIn(tz);
}
/**
* 打印 tz对应的日期/时间
*/
private static void printDateIn(TimeZone tz) {
//date为2013-09-19 14:22:30
Date date = new Date(113, 8, 19, 14, 22, 30);
// 获取默认的DateFormat,用于格式化Date
DateFormat df = DateFormat.getInstance();
// 设置时区为tz
df.setTimeZone(tz);
// 获取格式化后的字符串
String str = df.format(date);
System.out.println(tz.getID() + " :" + str);
}
/**
* 测试TimeZone的其它API
*/
public static void testOtherAPIs() {
// 默认时区
TimeZone tz = TimeZone.getDefault();
// 获取“id”
String id = tz.getID();
// 获取“显示名称”
String name = tz.getDisplayName();
// 获取“时间偏移”。相对于“本初子午线”的偏移,单位是ms。
int offset = tz.getRawOffset();
// 获取“时间偏移” 对应的小时
int gmt = offset / (3600 * 1000);
System.out.printf("id=%s, name=%s, offset=%s(ms), gmt=%s\n",
id, name, offset, gmt);
}
/**
* 打印getTimeZone(String id)支持的所有id
*/
public static void printAllTimeZones() {
String[] ids = TimeZone.getAvailableIDs();
for (String id : ids) {
//int offset = TimeZone.getTimeZone(avaIds[i]).getRawOffset();
//System.out.println(i+" "+avaIds[i]+" "+offset / (3600 * 1000) + "\t");
System.out.printf(id + ", ");
}
System.out.println();
}
}
修改系统时间
Here:如果需要修改系统时间,则首先需要将机器root权限,用到的场景相对较少
Andorid 代码
方式一:getSystemService系统服务
Calendar c = Calendar.getInstance();
// 年 月-1 日 时 分 秒
if (date != null) {
int year = date.getYear() + 1900;
int day = date.getDate();
int month = date.getMonth();
int hour = date.getHours();
int minutes = date.getMinutes();
int seconds = date.getSeconds();
c.set(year, month, day, hour, minutes, seconds);
}
AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
am.setTime(c.getTimeInMillis());
方式二:DataOutputStream日期输入
try {
Process process = Runtime.getRuntime().exec("su");
// 测试的设置的时间【时间格式】
String datetime = "20131023.112800";
// yyyyMMdd.HHmmss
DataOutputStream os = new DataOutputStream(process
.getOutputStream());
os.writeBytes("setprop persist.sys.timezone GMT\n");
os.writeBytes("/system/bin/date -s "
+ datetime + "\n");
os.writeBytes("clock -w\n");
os.writeBytes("exit\n");
os.flush();
Toast.makeText(MainActivity.this, "系统时间同步成功",Toast.LENGTH_SHORT).show();
} catch (IOException e) {
Toast.makeText(MainActivity.this,
"系统时间同步失败,可能没有ROOT权限?", Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
方式三:代码中调用cmd命令
/**
* @param cmd 命令
*/
private static void execSuCmd(String cmd) {
Process process = null;
DataOutputStream os = null;
DataInputStream is = null;
try {
process = Runtime.getRuntime().exec("su");
os = new DataOutputStream(process.getOutputStream());
os.writeBytes(cmd + "\n");
os.writeBytes("exit\n");
os.flush();
int aa = process.waitFor();
is = new DataInputStream(process.getInputStream());
byte[] buffer = new byte[is.available()];
is.read(buffer);
String out = new String(buffer);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (os != null) {
os.close();
}
if (is != null) {
is.close();
}
if (process != null) {
process.destroy();
}
} catch (Exception e) {
}
}
}
调用示例
String curr_time = “052514412019.52”;
execSuCmd("date " + curr_time + "\n busybox hwclock -w\n");
扩展
:Android有专属的 NetworkTimeUpdateServcie系统服务
来负责更新系统时间,该服务在系统启动时在SystemServer.java中被创建
if (!disableNetwork && !disableNetworkTime) {
try {
Slog.i(TAG, "NetworkTimeUpdateService");
networkTimeUpdater = new NetworkTimeUpdateService(context);
} catch (Throwable e) {
reportWtf("starting NetworkTimeUpdate service", e);
}
}
...
...
try {
if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemRunning();
} catch (Throwable e) {
reportWtf("Notifying NetworkTimeService running", e);
}
cmd 命令
shell命令
#格式为date MMddHHmmyyyy.ss set
date 123012002018.59 set
如需使用UTC时间,则需要加上-u选项
如果需要同步更新到硬件RTC中,则还需要使用hwclock
usage: hwclock [-rswtluf]
# 常用
# hwclock -w –从系统时间设置到硬件时钟
# hwclock -s –从硬件时钟设置到系统时间
-f FILE Use specified device file instead of /dev/rtc (--rtc)
-l Hardware clock uses localtime (--localtime)
-r Show hardware clock time (--show)
-s Set system time from hardware clock (--hctosys)
-t Set the system time based on the current timezone (--systz)
-u Hardware clock uses UTC (--utc)
-w Set hardware clock from system time (--systohc)
修改时区
时区设置,new Date()和系统时间相差8个小时
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
public class GetNowDate {
//获取系统当前时间,字符串类型
public static String getStrDate() {
SimpleDateFormat format= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//设置为东八区
format.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
Date newDate = new Date();
String dateStr = sdf.format(newDate);
return dateStr;
}
//获取系统当前时间Date类型,需要将字符串类型转成时间
public static Date getDaDate() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//设置为东八区
sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
Date date = new Date();
String dateStr = sdf.format(date);
//将字符串转成时间
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date newDate = null;
try {
newDate = df.parse(dateStr);
} catch (ParseException e) {
e.printStackTrace();
}
return newDate;
}
}
相互转换时区、时间
SimpleDateFormat:Date、字符串相互转换,格式化时间、解析时间
//字符串转Date
String stringDate = "Thu Oct 16 07:13:48 GMT 2015";
SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM ddHH:mm:ss 'GMT' yyyy",Locale.US);
Date date = sdf .parse(stringDate);
System.out.println(date.toString());
//Date转字符串
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf .format(new Date()));
//格式化时间
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
String time = sdf.format(new Date());
System.out.println(time);
//解析时间 2016-01-05T15:06:58+0800
Date date = sdf.parse(time);
System.out.println(date);
//T代表后面跟着时间,Z代表UTC统一时间
//格式化时间
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
String time = sdf.format(new Date());
System.out.println(time);
//解析时间 2016-01-05T15:09:54Z
Date date = sdf.parse(time);
System.out.println(date);
Date:时区、时间转换
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
public class TestTime {
public static void main(String[] args) {
try {
//获取Date对象,存放的是时间戳
Date date = new Date();
//获取时间戳(毫秒)
long seconds = date.getTime();
System.out.println("当前时间戳: " + seconds);
//当前GMT(格林威治)时间、当前计算机系统所在时区的时间
SimpleDateFormat beijingFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("本地(东八区)时间: " + beijingFormat.format(date) +"; GMT时间: " + date.toGMTString());
//东八区时间转换成东九区(东京)时间,比北京早一个小时
SimpleDateFormat tokyoFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
tokyoFormat.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo"));
System.out.println("东京(东九区)时间: "+tokyoFormat.format(date));
//时间戳转化成Date
SimpleDateFormat timestampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String fotmatString = timestampFormat.format(seconds);
Date parseDate = timestampFormat.parse(fotmatString);
System.out.println("时间戳转化成Date之后的时间: "+parseDate + ";格式化之后的: "+ fotmatString);
} catch (Exception e) {
e.printStackTrace();
}
}
TestLocalTime (Java8工具类)
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
public class TestLocalTime {
public static void main(String[] args) {
//获取当前时区的日期
LocalDate localDate = LocalDate.now();
System.out.println("localDate: " + localDate);
//时间
LocalTime localTime = LocalTime.now();
System.out.println("localTime: " + localTime);
//根据上面两个对象,获取日期时间
LocalDateTime localDateTime = LocalDateTime.of(localDate,localTime);
System.out.println("localDateTime: " + localDateTime);
//使用静态方法生成此对象
LocalDateTime localDateTime2 = LocalDateTime.now();
System.out.println("localDateTime2: " + localDateTime2);
//格式化时间
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss");
System.out.println("格式化之后的时间: " + localDateTime2.format(formatter));
//转化为时间戳(秒)
long epochSecond = localDateTime2.toEpochSecond(ZoneOffset.of("+8"));
//转化为毫秒
long epochMilli = localDateTime2.atZone(ZoneOffset.systemDefault()).toInstant().toEpochMilli();
System.out.println("时间戳为:(秒) " + epochSecond + "; (毫秒): " + epochMilli);
//时间戳(毫秒)转化成LocalDateTime
Instant instant = Instant.ofEpochMilli(epochMilli);
LocalDateTime localDateTime3 = LocalDateTime.ofInstant(instant, ZoneOffset.systemDefault());
System.out.println("时间戳(毫秒)转化成LocalDateTime: " + localDateTime3.format(formatter));
//时间戳(秒)转化成LocalDateTime
Instant instant2 = Instant.ofEpochSecond(epochSecond);
LocalDateTime localDateTime4 = LocalDateTime.ofInstant(instant2, ZoneOffset.systemDefault());
System.out.println("时间戳(秒)转化成LocalDateTime: " + localDateTime4.format(formatter));
}
}
关闭自动同步,实现自定同步
修改系统,时间戳出现8小时差的问题,首先设置的之后采用东八区GMT+8
配置,可通过类型mCalendar.setTimeZone(TimeZone.getTimeZone("GMT+8"));
进行设置,但是因时间戳无法跟随时区进行变化,所以在设置时间时,如果出现问题可以尝试先减去8*60*60*100
的8小时差
root权限
,且在AndroidManifest.xml里面加入以下配置
<uses-permission android:name="android.permission.SET_TIME" />
示例demo
SimpleDateFormat format=new SimpleDateFormat("yyyyMMdd.HHmmss");
//time是从服务器拿到的时间戳
Long t=new Long(time);
t=t-8*60*60*1000;
Date date=new Date(t);
String datetime=format.format(date);
try {
Process process = Runtime.getRuntime().exec("su");
DataOutputStream os = new DataOutputStream(process.getOutputStream());
os.writeBytes("setprop persist.sys.timezone GMT\n");
//把时间设置进去,这个地方一定要是yyyyMMdd.HHmmss的形式
os.writeBytes("/system/bin/date -s "+datetime+"\n");
os.writeBytes("clock -w\n");
os.writeBytes("exit\n");
os.flush();
} catch (IOException e) {
e.printStackTrace();
return false;
}