using System;
using System.Linq;
using Fort23.UTool;
using UnityEngine;
namespace Fort23.Core
{
public static class TimeHelper
{
public static int refreshHour = 0;
public static readonly long epoch = 0;
private static readonly DateTime ServerStartTime = new DateTime(2025, 4, 1, 0, 0, 0, DateTimeKind.Utc);
public static DateTime dtStart = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1));
public const long OneDay = 86400000;
public const long Hour = 3600000;
public const long Minute = 60000;
private static long _clientFrame;
public static long clientFrame
{
set => _clientFrame = value;
}
// 最近一次校准的网络时间戳毫秒
private static long _startNetworkMs = 0;
// 最近一次校准时的 Unity 时间
private static float _lastSyncUnityTime = 0f;
// 游戏首次启动时间
private static float _gameStartTime;
private const int SyncInterval = 300;
///
/// 是否获取了网络时间
///
public static bool IsNetworkTimeReady { get; private set; } = false;
///
/// 必须在游戏启动时调用(异步)
///
public static async CTask InitNetworkTime()
{
// 启动时的 Unity 时间
_gameStartTime = Time.unscaledTime;
// 首次同步
await SyncWithNetwork();
//每隔五分钟重新同步一次时间
TimerComponent.Instance.AddTimer(SyncInterval * 1000, null, Int32.MaxValue, () => { SyncWithNetwork(); });
}
// 核心:与百度时间同步
private static async CTask SyncWithNetwork()
{
var networkTime = await NetworkTime.GetNetworkTimeAsync();
if (networkTime.HasValue)
{
long networkMs = (long)(networkTime.Value - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc))
.TotalMilliseconds;
if (!IsNetworkTimeReady)
{
// 首次同步
_startNetworkMs = networkMs;
_lastSyncUnityTime = Time.unscaledTime;
IsNetworkTimeReady = true;
LogTool.Log($"首次网络时间同步成功: {networkTime.Value:yyyy-MM-dd HH:mm:ss} UTC");
}
else
{
// 定期校准:计算漂移并修正
float elapsedUnity = Time.unscaledTime - _lastSyncUnityTime;
long expectedMs = _startNetworkMs + (long)(elapsedUnity * 1000);
long driftMs = networkMs - expectedMs;
if (Mathf.Abs((float)driftMs) > 5000) // 漂移 > 5 秒才修正(避免微小误差)
{
_startNetworkMs = networkMs;
_lastSyncUnityTime = Time.unscaledTime;
LogTool.Log($"时间校准: 漂移 {driftMs}ms,已修正");
}
else
{
LogTool.Log($"时间校准: 漂移 {driftMs}ms,正常");
}
}
}
else
{
LogTool.Warning("网络时间校准失败,保持当前时间");
}
}
///
/// 客户端非暂停状态经历的帧数对应的毫秒
///
///
public static long ClientFrame()
{
return _clientFrame;
}
///
/// 获取安全的当前时间戳(毫秒)
///
private static long GetSafeNowMs()
{
if (!IsNetworkTimeReady || _startNetworkMs == 0)
return (long)(DateTime.UtcNow.Ticks / 10000); // 回退
// Unity运行毫秒(完全不受系统时间影响)
float elapsedUnity = Time.unscaledTime - _gameStartTime;
long elapsedMs = (long)(elapsedUnity * 1000);
return _startNetworkMs + elapsedMs;
}
public static long GetServerStartTime()
{
return ((ServerStartTime.Ticks - epoch) / 10000) + (8 * 60 * 60 * 1000);
}
public static long ClientNowMicroseconds()
{
return GetSafeNowMs() * 1000;
}
public static long ClientNow()
{
return GetSafeNowMs() + (8 * 60 * 60 * 1000); // 东八区
}
public static long ClientNowSeconds()
{
return ClientNow() / 1000;
}
public static DateTime DateTimeNow()
{
return DateTimeOffset.FromUnixTimeMilliseconds(ClientNow()).UtcDateTime;
}
public static long ServerNow()
{
return ClientNow();
}
public static long ServerNowSeconds()
{
return ClientNowSeconds();
}
///
/// 客户端时间(年月日)
///
/// 当前时间
///
public static int ClientNowInt(long timeStamp)
{
TimeSpan toNow = new TimeSpan(timeStamp * 10000);
DateTime dateTime = dtStart.Add(toNow);
int year = dateTime.Year;
int month = dateTime.Month;
int day = dateTime.Day;
int date;
string smouth = "";
if (month >= 10)
{
smouth = month.ToString();
}
else
{
smouth = $"0{month}";
}
string sday = "";
if (day >= 10)
{
sday = day.ToString();
}
else
{
sday = $"0{day}";
}
if (int.TryParse($"{year}{smouth}{sday}", out date))
{
return date;
}
return 20201010;
}
///
/// 客户端时间(月日时分秒)
///
/// 当前时间(毫秒)
///
public static string ToString1(long timeStamp)
{
TimeSpan toNow = new TimeSpan(timeStamp * 10000);
DateTime dateTime = dtStart.Add(toNow);
string month = String.Empty;
string day = String.Empty;
string hour = String.Empty;
string minute = String.Empty;
string second = String.Empty;
if (dateTime.Month >= 10)
{
month = dateTime.Month.ToString();
}
else
{
month = $"0{dateTime.Month}";
}
if (dateTime.Day >= 10)
{
day = dateTime.Day.ToString();
}
else
{
day = $"0{dateTime.Day}";
}
if (dateTime.Hour >= 10)
{
hour = dateTime.Hour.ToString();
}
else
{
hour = $"0{dateTime.Hour}";
}
if (dateTime.Minute >= 10)
{
minute = dateTime.Minute.ToString();
}
else
{
minute = $"0{dateTime.Minute}";
}
if (dateTime.Second >= 10)
{
second = dateTime.Second.ToString();
}
else
{
second = $"0{dateTime.Second}";
}
string result = $"{month}月{day}日{hour}:{minute}:{second}";
return result;
}
///
/// 判断法定工作日和节假日
///
/// 时间
/// 0上班,1不需要上班的周末(也包括节假日),2法定节假日
public static int IsHolidayOrWeekend(DateTime dt)
{
//特殊的周末(周末却上班)
string[] specalWeek2022 = { };
string[] specalWeek2023 = { "0128", "0129", "0214", "0429", "0430", "0625", "1007", "1008" };
//法定假日
string[] holiday2022 = { "1231" };
string[] holiday2023 =
{
"0101", "0102", "0121", "0122", "0123", "0124", "0125", "0126", "0127", "0405", "0501", "0502", "0503",
"0504", "0505", "0622", "0623",
"0929", "1002", "1003", "1004", "1005", "1006"
};
bool isHoildayOrWeek = false;
//取年
string weekYear = dt.Year.ToString();
//取月日
string[] weekDate = { dt.ToString("MMdd") };
//判断周末和周五
if ((int)dt.DayOfWeek == 0 || (int)dt.DayOfWeek == 6 || (int)dt.DayOfWeek == 5)
{
//周末是否需要上班
switch (weekYear)
{
case "2022":
isHoildayOrWeek = specalWeek2022.Intersect(weekDate).Count() == 0;
break;
case "2023":
isHoildayOrWeek = specalWeek2023.Intersect(weekDate).Count() == 0;
break;
}
}
if (isHoildayOrWeek)
{
return 1;
}
//判断法定节假日
switch (weekYear)
{
case "2022":
isHoildayOrWeek = holiday2022.Intersect(weekDate).Count() > 0;
break;
case "2023":
isHoildayOrWeek = holiday2023.Intersect(weekDate).Count() > 0;
break;
}
if (isHoildayOrWeek)
{
return 2;
}
return 0;
}
public static long GetBaseRefreshTime(long baseTime, int day = 1)
{
DateTime dateTime = DateTimeOffset.FromUnixTimeMilliseconds(baseTime).DateTime;
int hour = dateTime.Hour;
DateTime refreshDateTime = new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, refreshHour, 0, 0,
DateTimeKind.Utc);
if (hour < refreshHour)
{
day -= 1;
}
refreshDateTime = refreshDateTime.AddDays(day);
long refreshTime = new DateTimeOffset(refreshDateTime).ToUnixTimeMilliseconds();
return refreshTime;
}
}
}