今天做项目的时候遇到了一个问题,即客户端程序通过一个接口从后台获取到一个服务器时间后,需要在客户端的右下角实时展示服务器时间,并且要提供函数供客户端内的功能随时获取当前的服务器时间。
在这种情况下,每次都调用前后台接口获取服务器时间显然是不显示的,为此我想出了一个方法。
先定义几个概念:
1、历史本地时间(LocalDateTimeHis),即向后台请求服务器时间时获取到的本地时间
2、历史服务器时间(SysDateTimeHis),即向后台请求服务器时间时获取到的服务器时间
3、时间偏移量(Offset),时间偏移量=历史服务器时间-历史本地时间
4、手工校正值(Correct),可根据自身程序运行情况设置,我设置为1秒,以抵消网络延迟导致的时间差
5、当前服务器时间(SysDateTime),DateTime结构类型变量,系统时间=本地当前时间+偏移量+手工校正值
上面五项中,只要知道了(1)和(2),后面的(3)、(4)、(5)都可以计算出来,(1)可以通过DateTime.Now获取,(2)通过调用前后台接口获取。
下面是管理类的C#代码,需要说明:本工具类并未实现客户端获取服务器时间部分的代码,本工具类的功能是在客户端一次性获取服务器时间后,对外提供当前的服务器时间。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace SystemDateTimeTest
{
///
/// 系统时间同步工具类
///
public static class SystemDateTimeHelper
{
///
/// 工具类是否初始化
///
private static bool _isInit = false;
///
/// 工具类是否初始化 - 只有初始化后才能获取系统时间
///
public static bool IsInit
{
get
{
return _isInit;
}
private set
{
_isInit = value;
}
}
///
/// 更新时系统时间
///
private static DateTime _sysDateTimeHis;
///
/// 更新时系统时间
///
public static DateTime SysDateTimeHis
{
get
{
if (!IsInit)
{
throw new Exception("时间同步功能未初始化");
}
return _sysDateTimeHis;
}
private set
{
_sysDateTimeHis = value;
}
}
///
/// 更新时本地时间
///
private static DateTime _localDateTimeHis;
///
/// 更新时本地时间
///
public static DateTime LocalDateTimeHis
{
get
{
if (!IsInit)
{
throw new Exception("时间同步功能未初始化");
}
return _localDateTimeHis;
}
private set
{
_localDateTimeHis = value;
}
}
///
/// 偏移量
///
public static TimeSpan Offset
{
get
{
if (!IsInit)
{
throw new Exception("时间同步功能未初始化");
}
return SysDateTimeHis - LocalDateTimeHis;
}
}
///
/// 手工校正值 - 由程序编写人员根据情况定义,这里采用1s作为偏移量
///
public static TimeSpan Correct
{
get
{
return new TimeSpan(hours: 0, minutes: 0, seconds: 1);
}
}
///
/// 获取系统时间:本地时间+偏移量+校正值
///
///
public static DateTime SysDateTime
{
get
{
if (!IsInit)
{
throw new Exception("时间同步功能未初始化");
}
DateTime dateTimeNow = DateTime.Now;
DateTime dateTimeWithoutMill = new DateTime(
dateTimeNow.Year, dateTimeNow.Month, dateTimeNow.Day,
dateTimeNow.Hour, dateTimeNow.Minute, dateTimeNow.Second);
return dateTimeWithoutMill + Offset + Correct;
}
}
///
/// 更新系统时间 yyyyMMddHHmmss
///
///
public static void RefreshDateTime(string sysDateTime)
{
RefreshDateTime(sysDateTime, DateTime.Now.ToString("yyyyMMddHHmmss"));
}
///
/// 更新系统时间 yyyyMMddHHmmss
///
///
///
public static void RefreshDateTime(string sysDateTime, string localDateTime)
{
//将系统时间与本地时间转换为DateTime
DateTime SysDateTimeHisTmp = ConvertToDateTime(sysDateTime);
DateTime LocalDateTimeHisTmp = ConvertToDateTime(localDateTime);
//最后统一赋值,防止前面的代码发生异常后同步类计算结果错误
SysDateTimeHis = SysDateTimeHisTmp;
LocalDateTimeHis = LocalDateTimeHisTmp;
IsInit = true;
}
///
/// 将 yyyyMMddHHmmss 格式的字符串转换为 DateTime 结构
///
///
///
private static DateTime ConvertToDateTime(string sDateTime)
{
sDateTime = sDateTime.Trim();
if (sDateTime.Length != 14)
{
throw new Exception("输入参数必须是格式为 yyyyMMddHHmmss 的14位字符串");
}
foreach (char ch in sDateTime)
{
if (ch < '0' || ch > '9')
{
throw new Exception("输入参数必须是格式为 yyyyMMddHHmmss 的14位字符串");
}
}
int year = int.Parse(sDateTime.Substring(0, 4));
int month = int.Parse(sDateTime.Substring(4, 2));
int day = int.Parse(sDateTime.Substring(6, 2));
int hour = int.Parse(sDateTime.Substring(8, 2));
int minute = int.Parse(sDateTime.Substring(10, 2));
int second = int.Parse(sDateTime.Substring(12, 2));
DateTime dateTimeResult = new DateTime(year, month, day, hour, minute, second);
return dateTimeResult;
}
}
}
这个类的调用方法为:
1、客户端登录后,调用一次 SystemDateTimeHelper.RefreshDateTime 函数初始化该功能类,传入的时间必须是格式为“yyyyMMddHHmmss”的字符串。
2、功能类初始化后,可随时调用 SystemDateTimeHelper.SysDateTime 获取当前的服务器时间
3、建议每隔30分钟重新同步一次服务器时间,方法为在一个Interval为1秒的计时器中实现以下逻辑:
if (DateTime.Now - SystemDateTimeHelper.LocalDateTimeHis > new TimeSpan(0, 30, 0))
{
//TODO:重新同步
}
END