时间校准(全网最全最准确方案)完美无解

10 篇文章 0 订阅
本文介绍了如何通过NTP网络协议与开机时间相结合,确保在用户更改手机时间或日期时获取准确的时间戳。通过NTPClient类获取NTP时间并校准本地时间,TimeUtils工具类提供校准后的实时时间获取,适用于用户行为统计分析的SDK。
摘要由CSDN通过智能技术生成

背景:前段时间要做一个用户行为统计分析的SDK,其中要做到一个功能是,比如用户更改了手机的时间以及年份日期等,那么此时获取的System.currentTimeMillis()就不准确,本人也是通过研究全球各大第三方顶尖统计开源项目+结合优化得出以下方案,无论用户如何更改手机本地的时间或者日期都可以获取当前用户产生的精确时间(已验证)
使用此功能需要了解以下api:

//什么是开机时间?返回自启动以来的毫秒数,包括睡眠时间
long elapsedRealtime = SystemClock.elapsedRealtime();
//什么是ntp网络时间? 是网络时间协议(Network Time Protocol),它是用来同步网络中各个计算机的时间的协议。直接copy复制以下NTPClient代码即可。

解决方案:NTP网络协议时间+开机时间(返回自启动以来的毫秒数,包括睡眠时间)

步骤一:新建一个类可以命名为NTPClient.java或其他自定义名称(已验证,可直接copy使用)

public class NTPClient {

    private static final int ORIGINATE_TIME_OFFSET = 24;
    private static final int RECEIVE_TIME_OFFSET = 32;
    private static final int TRANSMIT_TIME_OFFSET = 40;
    private static final int NTP_PACKET_SIZE = 48;

    private static final int NTP_PORT = 123;
    private static final int NTP_MODE_CLIENT = 3;
    private static final int NTP_VERSION = 3;

    // Number of seconds between Jan 1, 1900 and Jan 1, 1970
    // 70 years plus 17 leap days
    private static final long OFFSET_1900_TO_1970 = ((365L * 70L) + 17L) * 24L * 60L * 60L;

    private long mOffSet;

    public long getOffset() {
        return mOffSet;
    }

    public boolean requestTime(String host, int timeout) {
        DatagramSocket socket = null;
        try {
            socket = new DatagramSocket();
            socket.setSoTimeout(timeout);
            InetAddress address = InetAddress.getByName(host);
            byte[] buffer = new byte[NTP_PACKET_SIZE];
            DatagramPacket request = new DatagramPacket(buffer, buffer.length, address, NTP_PORT);

            // set mode = 3 (client) and version = 3
            // mode is in low 3 bits of first byte
            // version is in bits 3-5 of first byte
            buffer[0] = NTP_MODE_CLIENT | (NTP_VERSION << 3);

            // get current time and write it to the request packet
            long requestTime = System.currentTimeMillis();
            long requestTicks = SystemClock.elapsedRealtime();
            writeTimeStamp(buffer, TRANSMIT_TIME_OFFSET, requestTime);

            socket.send(request);

            // read the response
            DatagramPacket response = new DatagramPacket(buffer, buffer.length);
            socket.receive(response);
            long responseTicks = SystemClock.elapsedRealtime();
            long responseTime = requestTime + (responseTicks - requestTicks);

            // extract the results
            long originateTime = readTimeStamp(buffer, ORIGINATE_TIME_OFFSET);
            long receiveTime = readTimeStamp(buffer, RECEIVE_TIME_OFFSET);
            long transmitTime = readTimeStamp(buffer, TRANSMIT_TIME_OFFSET);
            long roundTripTime = responseTicks - requestTicks - (transmitTime - receiveTime);
            // receiveTime = originateTime + transit + skew
            // responseTime = transmitTime + transit - skew
            // clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2
            // = ((originateTime + transit + skew - originateTime) +
            // (transmitTime - (transmitTime + transit - skew)))/2
            // = ((transit + skew) + (transmitTime - transmitTime - transit + skew))/2
            // = (transit + skew - transit + skew)/2
            // = (2 * skew)/2 = skew
            long clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime)) / 2;
            // if (false) Log.d(TAG, "round trip: " + roundTripTime + " ms");
            // if (false) Log.d(TAG, "clock offset: " + clockOffset + " ms");
            mOffSet = clockOffset;
        } catch (Exception e) {
            return false;
        } finally {
            if (socket != null) {
                socket.close();
            }
        }

        return true;
    }

    /**
     * Reads an unsigned 32 bit big endian number from the given offset in the buffer.
     */
    private long read32(byte[] buffer, int offset) {
        byte b0 = buffer[offset];
        byte b1 = buffer[offset + 1];
        byte b2 = buffer[offset + 2];
        byte b3 = buffer[offset + 3];

        // convert signed bytes to unsigned values
        int i0 = ((b0 & 0x80) == 0x80 ? (b0 & 0x7F) + 0x80 : b0);
        int i1 = ((b1 & 0x80) == 0x80 ? (b1 & 0x7F) + 0x80 : b1);
        int i2 = ((b2 & 0x80) == 0x80 ? (b2 & 0x7F) + 0x80 : b2);
        int i3 = ((b3 & 0x80) == 0x80 ? (b3 & 0x7F) + 0x80 : b3);

        return ((long) i0 << 24) + ((long) i1 << 16) + ((long) i2 << 8) + (long) i3;
    }

    /**
     * Reads the NTP time stamp at the given offset in the buffer and returns * it as a system time (milliseconds since January 1, 1970).
     */
    private long readTimeStamp(byte[] buffer, int offset) {
        long seconds = read32(buffer, offset);
        long fraction = read32(buffer, offset + 4);
        return ((seconds - OFFSET_1900_TO_1970) * 1000) + ((fraction * 1000L) / 0x100000000L);
    }

    /**
     * Writes system time (milliseconds since January 1, 1970) as an NTP time stamp * at the given offset in the buffer.
     */
    private void writeTimeStamp(byte[] buffer, int offset, long time) {
        long seconds = time / 1000L;
        long milliseconds = time - seconds * 1000L;
        seconds += OFFSET_1900_TO_1970;

        // write seconds in big endian format
        buffer[offset++] = (byte) (seconds >> 24);
        buffer[offset++] = (byte) (seconds >> 16);
        buffer[offset++] = (byte) (seconds >> 8);
        buffer[offset++] = (byte) (seconds >> 0);

        long fraction = milliseconds * 0x100000000L / 1000L;
        // write fraction in big endian format
        buffer[offset++] = (byte) (fraction >> 24);
        buffer[offset++] = (byte) (fraction >> 16);
        buffer[offset++] = (byte) (fraction >> 8);
        // low order bits should be random data
        buffer[offset++] = (byte) (Math.random() * 255.0);
    }
}

步骤二:新建一个工具类TimeUtils ,获取当前校准过后的时间(已验证,可直接copy使用)

public class TimeUtils {
    private String[] ntpServerPool = {"ntp1.aliyun.com", "ntp.ntsc.ac.cn", "time.google.com", "time.windows.com", "time.nist.gov", "time.apple.com",
            "time1.cloud.tencent.com", "time2.apple.com", "time2.google.com", "3.asia.pool.ntp.org"};
    //校准时间起始开机时间
    private long startRealTime;
    private static TimeUtils instance;
    //校准后 开始时间
    private long startTime;
    //是否校准
    public boolean isCalibrated = false;

    public static TimeUtils getInstance() {
        if (null == instance) {
            instance = new TimeUtils();
        }
        return instance;
    }

    /**
     * 返回自启动以来的毫秒数,包括睡眠时间
     *
     * @return
     */
    public long getElapsedRealtime() {
        return SystemClock.elapsedRealtime();
    }

    /**
     * 获取时间
     *
     * @return
     */
    public Date getDate() {
        long elapsedRealtime = SystemClock.elapsedRealtime();
        return startRealTime == 0 ? new Date(System.currentTimeMillis() - SystemClock.elapsedRealtime() + elapsedRealtime)
                : new Date(elapsedRealtime - this.startRealTime + startTime);
    }

    /**
     * 使用ntp网络时间与本地时间进行校准
     */
    public void checkTime() {
    //异步调用获取ntp协议网络时间
    new Thread(new Runnable() {
            @Override
            public void run() {
                NTPClient ntpclient = new NTPClient();
                for (String ntpHost : ntpServerPool) {
                    if (ntpclient.requestTime(ntpHost, 30000)) {
                        long currTime = ntpclient.getNtpTime() + SystemClock.elapsedRealtime() - ntpclient.getNtpTimeReference();
                        long nowTime = currTime / 1000; //当前获取到的准确ntp时间戳(秒单位)
                        startRealTime = SystemClock.elapsedRealtime();
                        startTime = System.currentTimeMillis() + ntpclient.getTimeOffset();
                        isCalibrated = true; //为true代表当前已经拉到网络ntp
                        break;
                    }
                }
            }
        }).start();
    }
}

步骤三:接口调用实现(已验证,可直接copy使用)

 //1、在你初始化或者在你需要初始化获取ntp网络时间的地方调用一次即可,以下举例子:
 public void init(Context context){
 TimeUtils.getInstance().checkTime();
}
//2、在你想要获取准确时间处调用以下代码,获取当前已校准的用户时间(实时)
long cunrrent_time = TimeUtils.getInstance().getDate().getTime();//单位为毫秒,如需到秒需除以1000

完结:总之,经过自动化以及各种环境测试,无论用户怎么更改本地时间或者日期更改多往前往后此方案都可以解决。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LiuHai2014csd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值