TimeHelper.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. using System;
  2. using System.Linq;
  3. using Fort23.UTool;
  4. using UnityEngine;
  5. namespace Fort23.Core
  6. {
  7. public static class TimeHelper
  8. {
  9. public static int refreshHour = 0;
  10. public static readonly long epoch = 0;
  11. private static readonly DateTime ServerStartTime = new DateTime(2025, 4, 1, 0, 0, 0, DateTimeKind.Utc);
  12. public static DateTime dtStart = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1));
  13. public const long OneDay = 86400000;
  14. public const long Hour = 3600000;
  15. public const long Minute = 60000;
  16. private static long _clientFrame;
  17. public static long clientFrame
  18. {
  19. set => _clientFrame = value;
  20. }
  21. // 最近一次校准的网络时间戳毫秒
  22. private static long _startNetworkMs = 0;
  23. // 最近一次校准时的 Unity 时间
  24. private static float _lastSyncUnityTime = 0f;
  25. // 游戏首次启动时间
  26. private static float _gameStartTime;
  27. private const int SyncInterval = 300;
  28. /// <summary>
  29. /// 是否获取了网络时间
  30. /// </summary>
  31. public static bool IsNetworkTimeReady { get; private set; } = false;
  32. /// <summary>
  33. /// 必须在游戏启动时调用(异步)
  34. /// </summary>
  35. public static async CTask InitNetworkTime()
  36. {
  37. // 启动时的 Unity 时间
  38. _gameStartTime = Time.unscaledTime;
  39. // 首次同步
  40. await SyncWithNetwork();
  41. //每隔五分钟重新同步一次时间
  42. TimerComponent.Instance.AddTimer(SyncInterval * 1000, null, Int32.MaxValue, () => { SyncWithNetwork(); });
  43. }
  44. // 核心:与百度时间同步
  45. private static async CTask SyncWithNetwork()
  46. {
  47. var networkTime = await NetworkTime.GetNetworkTimeAsync();
  48. if (networkTime.HasValue)
  49. {
  50. long networkMs = (long)(networkTime.Value - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc))
  51. .TotalMilliseconds;
  52. if (!IsNetworkTimeReady)
  53. {
  54. // 首次同步
  55. _startNetworkMs = networkMs;
  56. _lastSyncUnityTime = Time.unscaledTime;
  57. IsNetworkTimeReady = true;
  58. LogTool.Log($"首次网络时间同步成功: {networkTime.Value:yyyy-MM-dd HH:mm:ss} UTC");
  59. }
  60. else
  61. {
  62. // 定期校准:计算漂移并修正
  63. float elapsedUnity = Time.unscaledTime - _lastSyncUnityTime;
  64. long expectedMs = _startNetworkMs + (long)(elapsedUnity * 1000);
  65. long driftMs = networkMs - expectedMs;
  66. if (Mathf.Abs((float)driftMs) > 5000) // 漂移 > 5 秒才修正(避免微小误差)
  67. {
  68. _startNetworkMs = networkMs;
  69. _lastSyncUnityTime = Time.unscaledTime;
  70. LogTool.Log($"时间校准: 漂移 {driftMs}ms,已修正");
  71. }
  72. else
  73. {
  74. LogTool.Log($"时间校准: 漂移 {driftMs}ms,正常");
  75. }
  76. }
  77. }
  78. else
  79. {
  80. LogTool.Warning("网络时间校准失败,保持当前时间");
  81. }
  82. }
  83. /// <summary>
  84. /// 客户端非暂停状态经历的帧数对应的毫秒
  85. /// </summary>
  86. /// <returns></returns>
  87. public static long ClientFrame()
  88. {
  89. return _clientFrame;
  90. }
  91. /// <summary>
  92. /// 获取安全的当前时间戳(毫秒)
  93. /// </summary>
  94. private static long GetSafeNowMs()
  95. {
  96. if (!IsNetworkTimeReady || _startNetworkMs == 0)
  97. return (long)(DateTime.UtcNow.Ticks / 10000); // 回退
  98. // Unity运行毫秒(完全不受系统时间影响)
  99. float elapsedUnity = Time.unscaledTime - _gameStartTime;
  100. long elapsedMs = (long)(elapsedUnity * 1000);
  101. return _startNetworkMs + elapsedMs;
  102. }
  103. public static long GetServerStartTime()
  104. {
  105. return ((ServerStartTime.Ticks - epoch) / 10000) + (8 * 60 * 60 * 1000);
  106. }
  107. public static long ClientNowMicroseconds()
  108. {
  109. return GetSafeNowMs() * 1000;
  110. }
  111. public static long ClientNow()
  112. {
  113. return GetSafeNowMs() + (8 * 60 * 60 * 1000); // 东八区
  114. }
  115. public static long ClientNowSeconds()
  116. {
  117. return ClientNow() / 1000;
  118. }
  119. public static DateTime DateTimeNow()
  120. {
  121. return DateTimeOffset.FromUnixTimeMilliseconds(ClientNow()).UtcDateTime;
  122. }
  123. public static long ServerNow()
  124. {
  125. return ClientNow();
  126. }
  127. public static long ServerNowSeconds()
  128. {
  129. return ClientNowSeconds();
  130. }
  131. /// <summary>
  132. /// 客户端时间(年月日)
  133. /// </summary>
  134. /// <param name="timeStamp">当前时间</param>
  135. /// <returns></returns>
  136. public static int ClientNowInt(long timeStamp)
  137. {
  138. TimeSpan toNow = new TimeSpan(timeStamp * 10000);
  139. DateTime dateTime = dtStart.Add(toNow);
  140. int year = dateTime.Year;
  141. int month = dateTime.Month;
  142. int day = dateTime.Day;
  143. int date;
  144. string smouth = "";
  145. if (month >= 10)
  146. {
  147. smouth = month.ToString();
  148. }
  149. else
  150. {
  151. smouth = $"0{month}";
  152. }
  153. string sday = "";
  154. if (day >= 10)
  155. {
  156. sday = day.ToString();
  157. }
  158. else
  159. {
  160. sday = $"0{day}";
  161. }
  162. if (int.TryParse($"{year}{smouth}{sday}", out date))
  163. {
  164. return date;
  165. }
  166. return 20201010;
  167. }
  168. /// <summary>
  169. /// 客户端时间(月日时分秒)
  170. /// </summary>
  171. /// <param name="timeStamp">当前时间(毫秒)</param>
  172. /// <returns></returns>
  173. public static string ToString1(long timeStamp)
  174. {
  175. TimeSpan toNow = new TimeSpan(timeStamp * 10000);
  176. DateTime dateTime = dtStart.Add(toNow);
  177. string month = String.Empty;
  178. string day = String.Empty;
  179. string hour = String.Empty;
  180. string minute = String.Empty;
  181. string second = String.Empty;
  182. if (dateTime.Month >= 10)
  183. {
  184. month = dateTime.Month.ToString();
  185. }
  186. else
  187. {
  188. month = $"0{dateTime.Month}";
  189. }
  190. if (dateTime.Day >= 10)
  191. {
  192. day = dateTime.Day.ToString();
  193. }
  194. else
  195. {
  196. day = $"0{dateTime.Day}";
  197. }
  198. if (dateTime.Hour >= 10)
  199. {
  200. hour = dateTime.Hour.ToString();
  201. }
  202. else
  203. {
  204. hour = $"0{dateTime.Hour}";
  205. }
  206. if (dateTime.Minute >= 10)
  207. {
  208. minute = dateTime.Minute.ToString();
  209. }
  210. else
  211. {
  212. minute = $"0{dateTime.Minute}";
  213. }
  214. if (dateTime.Second >= 10)
  215. {
  216. second = dateTime.Second.ToString();
  217. }
  218. else
  219. {
  220. second = $"0{dateTime.Second}";
  221. }
  222. string result = $"{month}月{day}日{hour}:{minute}:{second}";
  223. return result;
  224. }
  225. /// <summary>
  226. /// 判断法定工作日和节假日
  227. /// </summary>
  228. /// <param name="dt">时间</param>
  229. /// <returns>0上班,1不需要上班的周末(也包括节假日),2法定节假日</returns>
  230. public static int IsHolidayOrWeekend(DateTime dt)
  231. {
  232. //特殊的周末(周末却上班)
  233. string[] specalWeek2022 = { };
  234. string[] specalWeek2023 = { "0128", "0129", "0214", "0429", "0430", "0625", "1007", "1008" };
  235. //法定假日
  236. string[] holiday2022 = { "1231" };
  237. string[] holiday2023 =
  238. {
  239. "0101", "0102", "0121", "0122", "0123", "0124", "0125", "0126", "0127", "0405", "0501", "0502", "0503",
  240. "0504", "0505", "0622", "0623",
  241. "0929", "1002", "1003", "1004", "1005", "1006"
  242. };
  243. bool isHoildayOrWeek = false;
  244. //取年
  245. string weekYear = dt.Year.ToString();
  246. //取月日
  247. string[] weekDate = { dt.ToString("MMdd") };
  248. //判断周末和周五
  249. if ((int)dt.DayOfWeek == 0 || (int)dt.DayOfWeek == 6 || (int)dt.DayOfWeek == 5)
  250. {
  251. //周末是否需要上班
  252. switch (weekYear)
  253. {
  254. case "2022":
  255. isHoildayOrWeek = specalWeek2022.Intersect(weekDate).Count() == 0;
  256. break;
  257. case "2023":
  258. isHoildayOrWeek = specalWeek2023.Intersect(weekDate).Count() == 0;
  259. break;
  260. }
  261. }
  262. if (isHoildayOrWeek)
  263. {
  264. return 1;
  265. }
  266. //判断法定节假日
  267. switch (weekYear)
  268. {
  269. case "2022":
  270. isHoildayOrWeek = holiday2022.Intersect(weekDate).Count() > 0;
  271. break;
  272. case "2023":
  273. isHoildayOrWeek = holiday2023.Intersect(weekDate).Count() > 0;
  274. break;
  275. }
  276. if (isHoildayOrWeek)
  277. {
  278. return 2;
  279. }
  280. return 0;
  281. }
  282. public static long GetBaseRefreshTime(long baseTime, int day = 1)
  283. {
  284. DateTime dateTime = DateTimeOffset.FromUnixTimeMilliseconds(baseTime).DateTime;
  285. int hour = dateTime.Hour;
  286. DateTime refreshDateTime = new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, refreshHour, 0, 0,
  287. DateTimeKind.Utc);
  288. if (hour < refreshHour)
  289. {
  290. day -= 1;
  291. }
  292. refreshDateTime = refreshDateTime.AddDays(day);
  293. long refreshTime = new DateTimeOffset(refreshDateTime).ToUnixTimeMilliseconds();
  294. return refreshTime;
  295. }
  296. }
  297. }