去年有一个需要同步俩端(客户端、服务端)时间的需求,起因是俩端的系统时间不一致,导致需求实现后出现时差,导致业务出错的问题,故此抽时间整理了一下时间日期相关的知识 ~

兄弟篇

  • 进阶之路 - 计算时间差
  • 项目实战 - 日期时间工具


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;
    }