python + win10 自动同步系统时间

Python3.8

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

在这里插入图片描述

# win_time_sync.py
# 作用:把 Windows 10 系统时间对齐到「权威网络时间」(NTP),并用 time.is 的 HTTP-Date 做一次“秒级校验”
# 说明:
#   1) 首选 NTP(专业做时间同步的协议),选用 低延迟+健康 状态的时间源;成功后设置系统时间(需要管理员权限)
#   2) 备选:用 w32tm 调用系统服务进行同步
#   3) 验证:用 time.is 的 HTTP 响应头 Date 做一次 sanity check(考虑往返延迟的 1/2)
#
# 用法(管理员 PowerShell):
#   python win_time_sync.py
#
# 提示:
#   - 设置系统时间需要“以管理员身份运行”终端
#   - 若你在公司域内,域策略可能禁止本机自行改时,脚本会自动降级走 w32tm
#   - 不建议强行和某个网站(如 time.is)“像素级”对齐,因为网页时间不是授时协议;咱们用它只做结果检查

import sys
import os
import subprocess
import platform
import socket
import struct
import time
import ctypes
from ctypes import wintypes
from datetime import datetime, timezone, timedelta
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError


"""
todo: 

1. 自动增加 “以管理员身份运行” 
2. 对输出结果进行美化
3. 增加日志记录。即,启动运行的时间,完成的时间。

"""


### 增加计时

start_time = time.time()

# 设置 HTTP 和 HTTPS 代理
os.environ["http_proxy"] = "http://127.0.0.1:7899"
os.environ["https_proxy"] = "http://127.0.0.1:7899"



# 你可以调整的参数
NTP_SERVERS = [
    "time.google.com",
    "time.windows.com",
    "pool.ntp.org",
    "time.cloudflare.com",
    "ntp.ubuntu.com",
]
NTP_TIMEOUT = 2.0           # 单次 NTP 超时(秒)
NTP_RETRIES = 2             # 每台服务器重试次数
OFFSET_APPLY_THRESHOLD = 0.050  # 只有偏差超过 50ms 才尝试设置,以避免抖动
HTTP_VERIFY_HOST = "time.is"    # 用于 HTTP Date 验证的站点
HTTP_TIMEOUT = 3.0

# --- NTP 简易客户端(不依赖第三方库) ---
# 参考 NTP 报文格式(64-bit 时间戳,秒+小数)
# NTP epoch 从 1900-01-01 开始;Unix epoch 从 1970-01-01,需要做偏移
NTP_EPOCH_DELTA = 2208988800  # seconds between 1900-01-01 and 1970-01-01

def ntp_query(host):
    """
    发送最简 NTP 客户端请求,返回 (utc_datetime, round_trip_s, offset_s)
    offset_s 为本地钟相对服务器时间的偏差(本地=服务器+offset)
    """
    port = 123
    addr = (host, port)
    packet = b'\x1b' + 47 * b'\0'  # LI=0, VN=3, Mode=3 (client)

    try:
        with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
            s.settimeout(NTP_TIMEOUT)
            t0 = time.time()
            s.sendto(packet, addr)
            data, _ = s.recvfrom(48)
            t3 = time.time()
    except Exception as e:
        raise e

    if len(data) < 48:
        raise RuntimeError("NTP response too short")

    # unpack transmit timestamp (server send time)
    # bytes 40..47: Transmit Timestamp (64-bit)
    transmit_ts = struct.unpack("!I", data[40:44])[0] + struct.unpack("!I", data[44:48])[0] / 2**32

    # bytes 32..39: Receive Timestamp (server receive time)
    recv_ts = struct.unpack("!I", data[32:36])[0] + struct.unpack("!I", data[36:40])[0] / 2**32

    # convert to unix epoch seconds
    T1 = t0
    T2 = recv_ts - NTP_EPOCH_DELTA
    T3 = transmit_ts - NTP_EPOCH_DELTA
    T4 = t3

    # NTP 经典公式
    delay = (T4 - T1) - (T3 - T2)
    offset = ((T2 - T1) + (T3 - T4)) / 2.0

    utc = datetime.fromtimestamp(T3 + delay/2.0, tz=timezone.utc)  # 近似服务器出站-入站对称,取中点
    return utc, max(0.0, delay), offset

def best_ntp_time(servers):
    """
    多源投票:多次查询,选择 延迟最小 且 响应成功 的一条。
    返回 (utc_datetime, chosen_server, delay_s, offset_s)
    """
    best = None
    for host in servers:
        for _ in range(NTP_RETRIES):
            try:
                utc, rtt, offset = ntp_query(host)
                cand = (rtt, utc, host, offset)
                if (best is None) or (rtt < best[0]):
                    best = cand
            except Exception:
                continue
    if best is None:
        raise RuntimeError("NTP 查询全部失败")
    rtt, utc, host, offset = best
    return utc, host, rtt, offset

# --- Windows 设置系统时间(需要管理员权限) ---
class SYSTEMTIME(ctypes.Structure):
    _fields_ = [
        ("wYear", wintypes.WORD),
        ("wMonth", wintypes.WORD),
        ("wDayOfWeek", wintypes.WORD),
        ("wDay", wintypes.WORD),
        ("wHour", wintypes.WORD),
        ("wMinute", wintypes.WORD),
        ("wSecond", wintypes.WORD),
        ("wMilliseconds", wintypes.WORD),
    ]

def dt_to_systemtime_utc(dt: datetime) -> SYSTEMTIME:
    dt = dt.astimezone(timezone.utc)
    st = SYSTEMTIME()
    st.wYear = dt.year
    st.wMonth = dt.month
    st.wDay = dt.day
    st.wHour = dt.hour
    st.wMinute = dt.minute
    st.wSecond = dt.second
    st.wMilliseconds = int(dt.microsecond / 1000)
    # wDayOfWeek 由系统忽略
    return st

def set_system_time_utc(dt: datetime) -> bool:
    """
    直接调用 Win32 API SetSystemTime 设置 UTC 时间。
    注意:需要管理员权限(SE_SYSTEMTIME_NAME)
    """
    kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
    _SetSystemTime = kernel32.SetSystemTime
    _SetSystemTime.argtypes = [ctypes.POINTER(SYSTEMTIME)]
    _SetSystemTime.restype = wintypes.BOOL

    st = dt_to_systemtime_utc(dt)
    ok = _SetSystemTime(ctypes.byref(st))
    return bool(ok)

def is_admin():
    try:
        import ctypes
        return ctypes.windll.shell32.IsUserAnAdmin() != 0
    except Exception:
        return False

# --- 备选:调用 w32tm 同步(适合域策略/权限受限场景) ---
def w32tm_resync(peer=None, force=True):
    cmds = []
    if peer:
        cmds.append(["w32tm", "/config", f"/manualpeerlist:{peer}", "/syncfromflags:manual", "/update"])
    # /rediscover 可让机器重找时间源(有需要可以加)
    args = ["w32tm", "/resync"]
    if force:
        args.append("/force")
    cmds.append(args)

    for c in cmds:
        try:
            subprocess.check_call(c, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, shell=False)
        except subprocess.CalledProcessError as e:
            return False
    return True

# --- 用 HTTP Date 做“对齐后”的 sanity check(与 time.is 握手计算半程延迟修正) ---
def http_date_verify(host: str):
    """
    返回 (server_utc_dt, rtt_s)
    """
    url = f"https://{host}/"
    req = Request(url, method="HEAD")  # 只拿头部即可
    t0 = time.time()
    try:
        with urlopen(req, timeout=HTTP_TIMEOUT) as resp:
            t1 = time.time()
            date_str = resp.headers.get("Date")  # RFC 9110 / MDN: Date 表示响应产生的 UTC 时间
            if not date_str:
                raise RuntimeError("响应未包含 Date 头")
            # 示例:Tue, 15 Nov 1994 08:12:31 GMT
            server_dt = datetime.strptime(date_str, "%a, %d %b %Y %H:%M:%S GMT").replace(tzinfo=timezone.utc)
            rtt = t1 - t0
            # 用半程延迟修正:以更贴近服务器时间点
            corrected = server_dt + timedelta(seconds=rtt/2.0)
            return corrected, rtt
    except (HTTPError, URLError, socket.timeout) as e:
        raise e

def main():
    if platform.system() != "Windows":
        print("仅支持 Windows。")
        sys.exit(1)

    print("[1/3] 查询 NTP 多源…")
    utc, server, rtt, offset = best_ntp_time(NTP_SERVERS)
    local_utc = datetime.utcnow().replace(tzinfo=timezone.utc)
    diff_before = (utc - local_utc).total_seconds()
    print(f"    选择时间源: {server}  (RTT≈{rtt*1000:.1f} ms)  偏差≈{diff_before*1000:.1f} ms")

    # 若偏差很小,不必改
    if abs(diff_before) < OFFSET_APPLY_THRESHOLD:
        print(f"[跳过设置] 偏差 < {OFFSET_APPLY_THRESHOLD*1000:.0f} ms,无需矫正。")
    else:
        print("[2/3] 应用系统时间…")
        target_dt = utc  # 我们用估计的“中点时间”,已经考虑半程延迟
        applied = False

        if is_admin():
            applied = set_system_time_utc(target_dt)
            print("    直接调用 SetSystemTime:", "成功" if applied else "失败")
        else:
            print("    当前非管理员,尝试使用 w32tm 服务来同步…")
            applied = w32tm_resync(peer=server, force=True)
            print("    w32tm /resync:", "成功" if applied else "失败")

        if not applied:
            print("!! 未能设置系统时间。请以管理员身份运行,或检查域策略/本地策略(允许调整系统时间)。")
            sys.exit(2)

    # 再比较一次本机与 NTP
    local_utc_after = datetime.utcnow().replace(tzinfo=timezone.utc)
    utc2, server2, rtt2, _ = best_ntp_time([server] + NTP_SERVERS)
    diff_after = (utc2 - local_utc_after).total_seconds()
    print(f"[结果] 与 {server2} 偏差≈{diff_after*1000:.1f} ms  (RTT≈{rtt2*1000:.1f} ms)")

    # [3/3] 与 time.is 用 HTTP Date 做 sanity check
    print("[3/3] 用 time.is 响应头 Date 做校验…")
    try:
        http_dt, http_rtt = http_date_verify(HTTP_VERIFY_HOST)
        local_http_diff = (http_dt - datetime.utcnow().replace(tzinfo=timezone.utc)).total_seconds()
        print(f"    time.is 校验:偏差≈{local_http_diff*1000:.1f} ms  (HTTP RTT≈{http_rtt*1000:.1f} ms)")
        print("    说明:HTTP Date 是服务器生成响应的时间点,已用半程延迟近似修正,仅作 sanity check。")
    except Exception as e:
        print(f"    跳过:无法从 {HTTP_VERIFY_HOST} 获取 HTTP Date({e})")

if __name__ == "__main__":
    main()

    end_time = time.time()
    print("总耗时:", end_time - start_time, "秒")



使用 Python 为 Windows 10 同步和校验网络时间

在日常工作和开发中,一个精准的系统时间至关重要,无论是对于日志记录、任务调度,还是分布式系统的数据一致性。虽然 Windows 自带了时间同步服务,但其默认更新频率和精度可能无法满足所有用户的需求。

今天,我们将深入探讨一个名为 win_time_sync.py 的 Python 脚本。它不仅能将你的 Windows 10 系统时间与权威的网络时间源(NTP)进行高精度对齐,还创新性地使用 time.is 网站的 HTTP 响应头进行独立校验,确保时间的准确性。

脚本核心功能一览

这个脚本设计精巧,主要实现了以下几个核心功能:

  1. 多源 NTP 对时,优中选优:脚本会查询包括 time.google.comtime.windows.com 在内的多个权威 NTP 服务器,并智能选择网络延迟最低、最健康的服务器作为时间源,从源头上保证了时间的准确性。
  2. 智能权限判断与时间设置:它能自动检测当前是否拥有管理员权限。
    • 若有,则通过调用 Windows 底层 API SetSystemTime 直接设置系统时间,这种方式最为精准直接。
    • 若没有,它会自动降级,尝试使用 Windows 自带的 w32tm 时间服务命令来请求同步,最大程度地保证了在不同环境下的兼容性。
  3. HTTP-Date 独立校验:在完成 NTP 同步后,脚本会向 time.is 发送一个 HEAD 请求,利用其返回的 Date 响应头进行一次“旁观者清”的 sanity check(健全性检查)。这相当于用一把独立的尺子来复核我们的校准结果。
  4. 纯 Python 实现,零第三方依赖:脚本巧妙地使用 Python 内置的 socketctypesurllib 等标准库,从 NTP 报文的构建到 Win32 API 的调用,都没有引入任何第三方库,做到了“开箱即用”。
代码深度解析

让我们深入代码,看看作者是如何实现这一系列操作的。

第一步:NTP 时间查询 (ntp_query & best_ntp_time)

这是脚本的核心。它并没有依赖 ntplib 等库,而是手动构建了一个最简的 NTP 客户端。

  • NTP 报文:通过 socket 发送一个 UDP 包到 NTP 服务器的 123 端口。包内容 b'\x1b' + 47 * b'\0' 是一个符合规范的最小化 NTP 客户端请求。
  • 时间戳计算:NTP 协议的核心是交换四个时间戳(T1-T4),脚本通过它们计算出两个关键指标:
    1. 网络延迟(Delay): (T4 - T1) - (T3 - T2),即客户端往返总耗时减去服务器处理耗时。
    2. 时钟偏差(Offset): ((T2 - T1) + (T3 - T4)) / 2.0,即本地时钟相对于服务器时钟的偏移量。
  • 优中选优best_ntp_time 函数会遍历预设的服务器列表,对每个服务器进行多次尝试,最终选择那个往返延迟(RTT)最低的成功结果。这确保了我们获取的时间样本受网络抖动的影响最小。
第二步:应用系统时间 (set_system_time_utc & w32tm_resync)

获取到权威时间后,如何设置到系统?脚本提供了两种策略:

  1. 直接 API 调用 (管理员模式)

    • 通过 ctypes 库,脚本可以直接访问 Windows 的核心动态链接库 kernel32.dll
    • 它定义了一个 SYSTEMTIME 结构体,这与 Windows API 的要求完全对应。
    • 最后调用 SetSystemTime 函数,将获取到的 UTC 时间毫秒级地设置到系统中。这是最快、最准的方式。
  2. 调用系统服务 (非管理员/域策略限制)

    • 如果 is_admin() 检测到权限不足,脚本会转而使用 subprocess 模块执行 w32tm /resync /force 命令。
    • w32tm 是 Windows 时间服务的命令行工具,执行它相当于请求系统服务本身去完成一次同步。虽然不如直接 API 调用那么“立竿见影”,但它能很好地兼容域策略等权限受限的环境。
第三步:HTTP-Date 独立校验 (http_date_verify)

这是脚本画龙点睛的一笔。如何确定我们的时间真的准了?

  • 脚本向 time.is 发送一个轻量的 HEAD 请求,只获取响应头。
  • 它解析 HTTP Date 头,该头通常表示服务器生成响应时的 UTC 时间。
  • 精髓在于延迟修正:网络请求需要时间,收到的 Date 其实是“过去”的时间。作者巧妙地将整个 HTTP 请求的往返时间(RTT)除以 2,作为单程延迟的估算值,加回到服务器时间上。corrected = server_dt + timedelta(seconds=rtt/2.0),这样估算出的时间点更接近当前真实时间。
  • 最后,将这个修正后的 time.is 时间与本机同步后的时间进行比较,给出一个最终的校验偏差。
如何使用
  1. 环境准备:确保你的 Windows 10 系统安装了 Python。
  2. 保存脚本:将代码保存为 win_time_sync.py 文件。
  3. 配置代理(可选):如果你的网络环境需要代理,请修改脚本开头的 os.environ 部分,填入你的代理地址。如果不需要,可以注释或删除它们。
  4. 以管理员身份运行:为了达到最佳效果(使用 SetSystemTime),请右键点击 PowerShell 或命令提示符(CMD),选择“以管理员身份运行”。
  5. 执行脚本:在管理员终端中,切换到脚本所在目录,运行命令:
    python win_time_sync.py
    
  6. 解读输出
    • 脚本会依次显示查询NTP、应用时间、进行校验的步骤。
    • 你会看到与首选 NTP 服务器的毫秒级偏差,以及与 time.is 校验后的偏差。
    • 如果初始偏差小于 50ms,脚本会智能地跳过设置,避免不必要的系统调用。
脚本的巧思与总结

这个脚本不仅仅是一个工具,更是一个学习 Python 系统编程的优秀范例。

  • 协议与实践结合:它清晰地展示了 NTP 协议的核心计算原理,并用最基础的 socket 库加以实现。
  • 优雅降级:在权限问题上,它没有粗暴地报错退出,而是提供了一个备用方案,体现了良好的兼容性设计。
  • 交叉验证:NTP 同步 + HTTP 校验的设计思想非常出色。它利用不同协议、不同服务源进行交叉比对,大大增强了结果的可信度。

总而言之,win_time_sync.py 是一个强大、透明且富有教育意义的工具,适合任何对时间精度有较高要求的 Windows 用户。它完美地诠释了如何利用 Python 这一“胶水语言”深入系统底层,解决实际问题。

您可能感兴趣的与本文相关的镜像

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

waterHBO

老哥,支持一下啊

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

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

打赏作者

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

抵扣说明:

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

余额充值