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; } } }