NTP库详解
NTPClient
NTPClient
库为 Arduino 提供了连接 NTP 服务器并获取准确时间的功能。
项目地址:https://github.com/arduino-libraries/NTPClient
UTC时间戳计算工具
https://www.epochconverter.com/
核心函数
构造函数
NTPClient(UDP& udp)
- 功能:创建一个
NTPClient
对象,使用默认的 NTP 服务器pool.ntp.org
,无时间偏移,更新间隔为 60 秒。 - 示例:
#include <WiFiUdp.h>
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP);
NTPClient(UDP& udp, long timeOffset)
- 功能:创建一个
NTPClient
对象,指定时间偏移量(以秒为单位)。 - 示例:
#include <WiFiUdp.h>
WiFiUDP ntpUDP;
long offset = 3600; // 偏移1小时
//long offset = 8 * 3600; // 北京时间,东八区,偏移8小时
NTPClient timeClient(ntpUDP, offset);
NTPClient(UDP& udp, const char* poolServerName)
- 功能:创建一个
NTPClient
对象,指定 NTP 服务器名称。 - 示例:
#include <WiFiUdp.h>
WiFiUDP ntpUDP;
const char* serverName = "europe.pool.ntp.org";
NTPClient timeClient(ntpUDP, serverName);
NTPClient(UDP& udp, IPAddress poolServerIP)
- 功能:创建一个
NTPClient
对象,指定 NTP 服务器的 IP 地址。 - 示例:
#include <WiFiUdp.h>
WiFiUDP ntpUDP;
IPAddress serverIP(192, 168, 1, 100);
NTPClient timeClient(ntpUDP, serverIP);
NTPClient(UDP& udp, const char* poolServerName, long timeOffset)
- 功能:创建一个
NTPClient
对象,指定 NTP 服务器名称和时间偏移量。 - 示例:
#include <WiFiUdp.h>
WiFiUDP ntpUDP;
const char* serverName = "europe.pool.ntp.org";
long offset = 3600;
NTPClient timeClient(ntpUDP, serverName, offset);
NTPClient(UDP& udp, IPAddress poolServerIP, long timeOffset)
- 功能:创建一个
NTPClient
对象,指定 NTP 服务器的 IP 地址和时间偏移量。 - 示例:
#include <WiFiUdp.h>
WiFiUDP ntpUDP;
IPAddress serverIP(192, 168, 1, 100);
long offset = 3600;
NTPClient timeClient(ntpUDP, serverIP, offset);
NTPClient(UDP& udp, const char* poolServerName, long timeOffset, unsigned long updateInterval)
- 功能:创建一个
NTPClient
对象,指定 NTP 服务器名称、时间偏移量和更新间隔(以毫秒为单位)。 - 示例:
#include <WiFiUdp.h>
WiFiUDP ntpUDP;
const char* serverName = "europe.pool.ntp.org";
long offset = 3600;
unsigned long interval = 120000; // 2分钟更新一次
NTPClient timeClient(ntpUDP, serverName, offset, interval);
NTPClient(UDP& udp, IPAddress poolServerIP, long timeOffset, unsigned long updateInterval)
- 功能:创建一个
NTPClient
对象,指定 NTP 服务器的 IP 地址、时间偏移量和更新间隔。 - 示例:
#include <WiFiUdp.h>
WiFiUDP ntpUDP;
IPAddress serverIP(192, 168, 1, 100);
long offset = 3600;
unsigned long interval = 120000;
NTPClient timeClient(ntpUDP, serverIP, offset, interval);
成员函数
void begin()
- 功能:启动底层 UDP 客户端,使用默认的本地端口
NTP_DEFAULT_LOCAL_PORT
(1337)。 - 示例:
timeClient.begin();
void begin(unsigned int port)
- 功能:启动底层 UDP 客户端,使用指定的本地端口。
- 示例:
unsigned int port = 1234;
timeClient.begin(port);
bool update()
- 功能:根据更新间隔检查是否需要从 NTP 服务器更新时间。如果需要更新,则进行更新并返回
true
;否则返回false
。 - 示例:
if (timeClient.update()) {Serial.println("Time updated successfully");
} else {Serial.println("Time not updated");
}
bool forceUpdate()
- 功能:强制从 NTP 服务器更新时间。更新成功返回
true
,失败返回false
。 - 示例:
if (timeClient.forceUpdate()) {Serial.println("Forced time update successful");
} else {Serial.println("Forced time update failed");
}
bool isTimeSet() const
- 功能:检查
NTPClient
是否成功接收 NTP 数据包并设置了时间。如果时间已设置,返回true
;否则返回false
。 - 示例:
if (timeClient.isTimeSet()) {Serial.println("Time is set");
} else {Serial.println("Time is not set");
}
int getDay() const
- 功能:获取当前是星期几(0 表示星期日)。
- 示例:
int day = timeClient.getDay();
Serial.print("Day of the week: ");
Serial.println(day);
int getHours() const
- 功能:获取当前小时数(0 - 23)。
- 示例:
int hours = timeClient.getHours();
Serial.print("Hours: ");
Serial.println(hours);
int getMinutes() const
- 功能:获取当前分钟数(0 - 59)。
- 示例:
int minutes = timeClient.getMinutes();
Serial.print("Minutes: ");
Serial.println(minutes);
int getSeconds() const
- 功能:获取当前秒数(0 - 59)。
- 示例:
int seconds = timeClient.getSeconds();
Serial.print("Seconds: ");
Serial.println(seconds);
String getFormattedTime() const
- 功能:返回格式化的时间字符串,格式为
hh:mm:ss
。有设置偏移的话,这个是偏移后的时间。 - 示例:
String formattedTime = timeClient.getFormattedTime();
Serial.println(formattedTime);
unsigned long getEpochTime() const
- 功能:返回自 1970 年 1 月 1 日 00:00:00 UTC 以来的秒数(Unix 时间戳)。如果设置了时间偏移量,该偏移量将被添加到时间戳中。
时间戳(timestamp)是一个绝对的时间表示,通常定义为从1970 年 1 月 1 日 00:00:00
UTC开始经过的秒数。时间戳与时区无关,无论你在哪个时区,同一时刻的时间戳都是相同的。北京时间的时间戳与 UTC 时间戳在同一时刻应该是完全相同的,不需要加上时区偏移。
注意:NTP库函数定义:getEpochTime()返回值 = UTC纪元时间戳 + 时间偏移量
- 示例:
unsigned long epochTime = timeClient.getEpochTime();
Serial.print("Epoch time: ");
Serial.println(epochTime);
UTC纪元时间戳
1.在线工具
https://www.epochconverter.com/
2.python计算
from datetime import datetime, timedelta, timezonedef beijing_time_to_utc_timestamp(date_str, time_str="00:00:00"):"""将北京时间(格式:YYYY.M.D)转换为UTC纪元时间戳参数:- date_str: 日期字符串,例如 "2025.3.5"- time_str: 时间字符串,例如 "12:30:45"(默认00:00:00)返回:- utc_timestamp: UTC时间戳(秒级)- utc_time_str: UTC时间字符串(格式:YYYY-MM-DD HH:MM:SS)"""try:# 解析日期和时间year, month, day = map(int, date_str.split('.'))hour, minute, second = map(int, time_str.split(':'))# 创建北京时间的时区对象(UTC+8)beijing_tz = timezone(timedelta(hours=8))# 创建带时区的北京时间对象beijing_time = datetime(year, month, day, hour, minute, second, tzinfo=beijing_tz)# 转换为UTC时间utc_time = beijing_time.astimezone(timezone.utc)# 计算UTC时间戳utc_timestamp = int(utc_time.timestamp())# 格式化为字符串utc_time_str = utc_time.strftime("%Y-%m-%d %H:%M:%S")return utc_timestamp, utc_time_strexcept ValueError as ve:print(f"输入格式错误: {ve}")return None, Noneexcept Exception as e:print(f"发生未知错误: {e}")return None, None# 示例使用
if __name__ == "__main__":# 用户输入案例input_date = input("STEP 1/2:请输入日期(格式:年.月.日,例如:2025.3.5):")input_time = input("STEP 2/2:请输入时间(格式:时:分:秒,例如:12:30:45,默认为00:00:00):")# 处理默认时间if not input_time.strip():input_time = "00:00:00"# 计算UTC时间戳和UTC时间timestamp, utc_str = beijing_time_to_utc_timestamp(input_date, input_time)# 打印结果if timestamp is not None:print(f"输入北京时间:{input_date} {input_time}")print(f"对应的UTC时间:{utc_str}")print(f"UTC纪元时间戳(注意:NTP库getEpochTime() = UTC纪元时间戳 + 时间偏移量):{timestamp}")
void end()
- 功能:停止底层 UDP 客户端。
- 示例:
timeClient.end();
void setTimeOffset(int timeOffset)
- 功能:设置时间偏移量(以秒为单位),可动态更改时区。
- 示例:
int offset = 7200; // 偏移2小时
timeClient.setTimeOffset(offset);
void setUpdateInterval(unsigned long updateInterval)
- 功能:设置时间更新间隔(以毫秒为单位)。
- 示例:
unsigned long interval = 180000; // 3分钟更新一次
timeClient.setUpdateInterval(interval);
void setPoolServerName(const char* poolServerName)
- 功能:设置 NTP 服务器名称。
- 示例:
const char* serverName = "asia.pool.ntp.org";
timeClient.setPoolServerName(serverName);
void setRandomPort(unsigned int minValue, unsigned int maxValue)
- 功能:设置一个随机的本地端口,端口范围在
minValue
到maxValue
之间。 - 示例:
unsigned int minPort = 49152;
unsigned int maxPort = 65535;
timeClient.setRandomPort(minPort, maxPort);
示例
格式化时间1
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <NTPClient.h>
#include <WiFiUdp.h>// WiFi网络信息
const char *ssid = "<SSID>";
const char *password = "<PASSWORD>";// NTP服务器地址
const char* ntpServerName = "ntp.aliyun.com";
// 北京时间所在时区为UTC+8
const long gmtOffset_sec = 8 * 3600;
// 设置NTP更新最小时间间隔,参数单位为毫秒
const int daylightOffset_sec = 60000;// 创建WiFiUDP对象
WiFiUDP ntpUDP;
// 创建NTPClient对象
NTPClient timeClient(ntpUDP, ntpServerName, gmtOffset_sec, daylightOffset_sec);void setup() {Serial.begin(115200);// 连接 WiFiWiFi.begin(ssid, password);while (WiFi.status() != WL_CONNECTED) {delay(1000);Serial.println("Connecting to WiFi...");}Serial.println("Connected to WiFi");// 初始化 NTP 客户端timeClient.begin();
}void loop() {// 更新 NTP 时间timeClient.update();// 获取格式化后的时间字符串,HH:MM:SSString formattedTime = timeClient.getFormattedTime();// 打印原始格式化时间Serial.print("Formatted Time: ");Serial.println(formattedTime);// 提取小时、分钟和秒int hour, minute, second; // 从格式化时间字符串中提取小时hour = formattedTime.substring(0, 2).toInt(); // 从格式化时间字符串中提取分钟minute = formattedTime.substring(3, 5).toInt(); // 从格式化时间字符串中提取秒second = formattedTime.substring(6, 8).toInt();// 打印提取的时间位Serial.print("Hour: ");Serial.print(hour);Serial.print(", Minute: ");Serial.print(minute);Serial.print(", Second: ");Serial.println(second);// timeClient.getEpochTime()返回自 1970-01-01 00:00:00 UTC 以来的秒数(即Unix时间戳)unsigned long epochTime = timeClient.getEpochTime(); Serial.print("Epoch Time: ");Serial.println(epochTime);delay(1000);
}
格式化时间2
#include <NTPClient.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>// WiFi网络信息
const char *ssid = "<SSID>";
const char *password = "<PASSWORD>";// 创建WiFi UDP客户端
WiFiUDP ntpUDP;// 创建NTPClient对象,使用默认的NTP服务器,东八区(偏移28800秒),更新间隔为60秒
NTPClient timeClient(ntpUDP, "pool.ntp.org", 28800, 60000);// 定义中文星期数组
const char* weekDays[] = {"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};// 补零函数
String zeroPad(int num) {return (num < 10) ? "0" + String(num) : String(num);
}void setup() {// 初始化串口通信Serial.begin(115200);// 连接WiFiWiFi.begin(ssid, password);while (WiFi.status() != WL_CONNECTED) {delay(500);Serial.print(".");}Serial.println("");Serial.println("WiFi connected");// 启动NTPClienttimeClient.begin();
}void loop() {// 更新时间timeClient.update();// 获取星期、小时、分钟和秒int day = timeClient.getDay();int hours = timeClient.getHours();int minutes = timeClient.getMinutes();int seconds = timeClient.getSeconds();// 格式化输出String formattedTime = weekDays[day] + String(" ") + zeroPad(hours) + ":" + zeroPad(minutes) + ":" + zeroPad(seconds);Serial.println(formattedTime);delay(1000); // 每秒更新一次
}
距离目标时间还有多久
#include <NTPClient.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>// WiFi网络信息
const char *ssid = "<SSID>";
const char *password = "<PASSWORD>";// 创建WiFi UDP客户端
WiFiUDP ntpUDP;// 创建NTPClient对象,使用默认的NTP服务器,东八区(偏移28800秒),更新间隔为60秒
NTPClient timeClient(ntpUDP, "pool.ntp.org", 28800, 60000);// 2025年6月14日星期六凌晨12点00分 GMT+08:00
// 用https://www.epochconverter.com/在线工具计算时间戳
// 工具计算出的时间戳为UTC纪元时间戳,getEpochTime() = UTC纪元时间戳 + 时间偏移量
// 所以计算剩余时间,需要加上时区偏移
const unsigned long targetTime = 1749830400;void setup() {// 初始化串口通信Serial.begin(115200);// 连接WiFiWiFi.begin(ssid, password);while (WiFi.status() != WL_CONNECTED) {delay(500);Serial.print(".");}Serial.println("");Serial.println("WiFi connected");// 启动NTPClienttimeClient.begin();}void loop() {// 更新时间timeClient.update();Serial.println(timeClient.getFormattedTime());Serial.println(timeClient.getEpochTime());// 获取当前时间戳unsigned long currentTime = timeClient.getEpochTime();// 计算剩余时间,计算时间差需要加上时区偏移long remainingTime = targetTime - currentTime + 8 * 3600;if (remainingTime > 0) {// 显示剩余时间Serial.print("Remaining time: ");unsigned long days = remainingTime / (24 * 3600);String daysStr = days < 10 ? "0" + String(days) : String(days);Serial.print(daysStr);Serial.print("d:");remainingTime %= (24 * 3600);unsigned long hours = remainingTime / 3600;String hoursStr = hours < 10 ? "0" + String(hours) : String(hours);Serial.print(hoursStr);Serial.print("h:");remainingTime %= 3600;unsigned long minutes = remainingTime / 60;String minuteStr = minutes < 10 ? "0" + String(minutes) : String(minutes);Serial.print(minuteStr);Serial.print("m:");unsigned long seconds = remainingTime % 60;String secondStr = seconds < 10 ? "0" + String(seconds) : String(seconds);Serial.print(secondStr);Serial.println("s");} else {Serial.println("Countdown finished!");}delay(1000); // 每秒更新一次
}