You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

429 lines
17 KiB

1 year ago
4 years ago
2 years ago
3 years ago
2 years ago
1 year ago
2 years ago
3 years ago
3 years ago
2 years ago
2 years ago
3 years ago
1 year ago
3 years ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
4 years ago
2 years ago
4 years ago
2 years ago
4 years ago
2 years ago
4 years ago
4 years ago
4 years ago
2 years ago
4 years ago
4 years ago
2 years ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
4 years ago
4 years ago
3 years ago
2 years ago
3 years ago
3 years ago
3 years ago
4 years ago
2 years ago
3 years ago
4 years ago
4 years ago
4 years ago
  1. using System;
  2. using System.Globalization;
  3. using System.Text;
  4. using static System.DateTime;
  5. namespace Apewer
  6. {
  7. /// <summary>时钟。</summary>
  8. public static class ClockUtility
  9. {
  10. #region DateTime
  11. /// <summary>尝试转换内容为 DateTime 实例。</summary>
  12. public static Class<DateTime> DateTime(object value)
  13. {
  14. if (value is DateTime dt) return dt;
  15. if (value.IsNull()) return null;
  16. try
  17. {
  18. var text = value.ToString();
  19. var parsed = System.DateTime.TryParse(text, out var result);
  20. return parsed ? new Class<DateTime>(result) : null;
  21. }
  22. catch
  23. {
  24. return null;
  25. }
  26. }
  27. #endregion
  28. #region Fixed
  29. private static DateTime _zero = new DateTime(0L, DateTimeKind.Unspecified);
  30. private static DateTime _origin = NewOrigin(DateTimeKind.Unspecified);
  31. private static DateTime _utc_origin = NewOrigin(DateTimeKind.Utc);
  32. /// <summary>创建新的零值 DateTime 对象。</summary>
  33. public static DateTime Zero { get => _zero; }
  34. /// <summary>获取一个 DateTime 对象,该对象设置为 1970-01-01 00:00:00.000,表示为协调通用时间 (UTC)。</summary>
  35. public static DateTime UtcOrigin { get => _utc_origin; }
  36. /// <summary>获取一个 DateTime 对象,该对象设置为此计算机上的当前日期和时间,表示为本地时间。</summary>
  37. public static DateTime Now { get => System.DateTime.Now; }
  38. /// <summary>获取一个 DateTime 对象,该对象设置为此计算机上的当前日期和时间,表示为协调通用时间 (UTC)。</summary>
  39. public static DateTime UtcNow { get => System.DateTime.UtcNow; }
  40. /// <summary>创建一个 DateTime 对象,该对象设置为 1970-01-01 00:00:00.000。</summary>
  41. public static DateTime NewOrigin(DateTimeKind kind) => new DateTime(1970, 1, 1, 0, 0, 0, 0, kind);
  42. #endregion
  43. #region Clone
  44. /// <summary>克隆 DateTime 对象,并使用新的 Kind。</summary>
  45. /// <param name="dateTime">要克隆的 DateTime 对象。</param>
  46. /// <param name="kind">时间类型。</param>
  47. /// <returns>克隆后带有新 Kind 的 DateTime 对象。</returns>
  48. public static DateTime Clone(this DateTime dateTime, DateTimeKind kind)
  49. {
  50. return new DateTime(dateTime.Ticks, kind);
  51. }
  52. #endregion
  53. #region Round
  54. /// <summary>对齐时间,小于 <see cref="DateTimePart"/> 的部分将被舍弃。</summary>
  55. public static DateTime Round(this DateTime dateTime, DateTimePart datePart)
  56. {
  57. switch (datePart)
  58. {
  59. case DateTimePart.Year: return new DateTime(dateTime.Year, 1, 1, 0, 0, 0, 0, dateTime.Kind);
  60. case DateTimePart.Month: return new DateTime(dateTime.Year, dateTime.Month, 1, 0, 0, 0, 0, dateTime.Kind);
  61. case DateTimePart.Day: return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 0, 0, 0, 0, dateTime.Kind);
  62. case DateTimePart.Hour: return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, 0, 0, 0, dateTime.Kind);
  63. case DateTimePart.Minute: return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, 0, 0, dateTime.Kind);
  64. case DateTimePart.Second: return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, dateTime.Second, 0, dateTime.Kind);
  65. case DateTimePart.Millisecond: return dateTime;
  66. default: throw new ArgumentException($"指定的 {nameof(DateTimePart)} 不受支持。");
  67. }
  68. }
  69. #endregion
  70. #region Common
  71. /// <summary>判断指定年份是闰年。</summary>
  72. public static bool IsLeapYear(this int year)
  73. {
  74. if (year % 400 == 0) return true;
  75. if (year % 100 == 0) return false;
  76. if (year % 4 == 0) return true;
  77. return false;
  78. }
  79. /// <summary>判断指定年份是闰年。</summary>
  80. public static bool IsLeapYear(DateTime dateTime) => IsLeapYear(dateTime.Year);
  81. /// <summary>获取指定年月的天数。</summary>
  82. /// <exception cref="ArgumentOutOfRangeException" />
  83. public static int MonthDays(int year, int month)
  84. {
  85. switch (month)
  86. {
  87. case 1: case 3: case 5: case 7: case 8: case 10: case 12: return 31;
  88. case 4: case 6: case 9: case 11: return 30;
  89. case 2: return IsLeapYear(year) ? 29 : 28;
  90. default: throw new ArgumentOutOfRangeException(nameof(month));
  91. }
  92. }
  93. #endregion
  94. #region Stamp
  95. /// <summary>自定义从 DateTime 转为时间戳的方法。</summary>
  96. public static Func<DateTime, long> CustomToStamp { get; set; }
  97. /// <summary>自定义从时间戳转为 DateTime 的方法。</summary>
  98. public static Func<long, DateTime> CustomFromStamp { get; set; }
  99. /// <summary>获取当前本地时间的毫秒时间戳。</summary>
  100. public static long NowStamp { get => ToStamp(Now); }
  101. /// <summary>获取当前 UTC 的毫秒时间戳。</summary>
  102. public static long UtcStamp { get => ToStamp(UtcNow); }
  103. /// <summary>获取毫秒时间戳。当指定了 <see cref="CustomToStamp"/> 时将优先使用自定义的方法。</summary>
  104. /// <remarks>默认不判断参数的时区,与 <see cref="DateTimeKind.Unspecified"/> 相同。</remarks>
  105. public static long ToStamp(DateTime dateTime)
  106. {
  107. var converter = CustomToStamp;
  108. if (converter != null) return converter.Invoke(dateTime);
  109. var span = dateTime - _origin;
  110. var value = span.TotalMilliseconds;
  111. var stamp = Convert.ToInt64(Math.Floor(value));
  112. return stamp;
  113. }
  114. /// <summary>获取 UTC 毫秒时间戳</summary>
  115. public static long ToUtcStamp(DateTime dateTime)
  116. {
  117. if (dateTime.Kind == DateTimeKind.Local) dateTime = dateTime.ToUniversalTime();
  118. var span = dateTime - _utc_origin;
  119. var value = span.TotalMilliseconds;
  120. var stamp = Convert.ToInt64(Math.Floor(value));
  121. return stamp;
  122. }
  123. /// <summary>解析毫秒时间戳,获取 DateTime 对象。当指定了 <see cref="CustomFromStamp"/> 时将优先使用自定义的方法。</summary>
  124. /// <remarks>默认不判断系统时区,返回的结果是 <see cref="DateTimeKind.Unspecified"/>。</remarks>
  125. /// <exception cref="ArgumentOutOfRangeException"></exception>
  126. public static DateTime FromStamp(long stamp)
  127. {
  128. var converter = CustomFromStamp;
  129. if (converter != null) return converter.Invoke(stamp);
  130. return FromStamp(stamp, DateTimeKind.Unspecified, DateTimeKind.Unspecified);
  131. }
  132. /// <summary>从毫秒时间戳获取 DateTime 对象。</summary>
  133. /// <exception cref="ArgumentOutOfRangeException"></exception>
  134. public static DateTime FromStamp(long stamp, DateTimeKind stampKind, DateTimeKind dateTimeKind)
  135. {
  136. switch (dateTimeKind)
  137. {
  138. case DateTimeKind.Unspecified:
  139. switch (stampKind)
  140. {
  141. case DateTimeKind.Unspecified:
  142. case DateTimeKind.Utc:
  143. case DateTimeKind.Local:
  144. return _origin.AddMilliseconds(stamp);
  145. default:
  146. throw new ArgumentOutOfRangeException(nameof(stampKind));
  147. }
  148. case DateTimeKind.Utc:
  149. switch (stampKind)
  150. {
  151. case DateTimeKind.Unspecified:
  152. case DateTimeKind.Utc:
  153. return _utc_origin.AddMilliseconds(stamp);
  154. case DateTimeKind.Local:
  155. return NewOrigin(DateTimeKind.Local).AddMilliseconds(stamp).ToUniversalTime();
  156. default:
  157. throw new ArgumentOutOfRangeException(nameof(stampKind));
  158. }
  159. case DateTimeKind.Local:
  160. switch (stampKind)
  161. {
  162. case DateTimeKind.Unspecified:
  163. case DateTimeKind.Utc:
  164. return _utc_origin.AddMilliseconds(stamp).ToLocalTime();
  165. case DateTimeKind.Local:
  166. return NewOrigin(DateTimeKind.Local).AddMilliseconds(stamp);
  167. default:
  168. throw new ArgumentOutOfRangeException(nameof(stampKind));
  169. }
  170. default:
  171. throw new ArgumentOutOfRangeException(nameof(dateTimeKind));
  172. }
  173. }
  174. #endregion
  175. #region Text
  176. /// <summary>解析文本,获取 DateTime 对象。</summary>
  177. public static Class<DateTime> FromText(string text)
  178. {
  179. var str = text;
  180. if (string.IsNullOrEmpty(str)) return null;
  181. var utc = false;
  182. var lower = str.ToLower();
  183. if (lower.EndsWith(" utc"))
  184. {
  185. utc = true;
  186. str = str.Substring(0, str.Length - 4);
  187. }
  188. DateTime dt;
  189. if (!TryParse(str, out dt))
  190. {
  191. if (!str.Contains("-") && TryParseExact(str, "yyyy-M-d", null, DateTimeStyles.None, out dt))
  192. {
  193. if (!str.Contains("/") && TryParseExact(str, "yyyy/M/d", null, DateTimeStyles.None, out dt))
  194. {
  195. return null;
  196. }
  197. }
  198. }
  199. if (utc) dt = new DateTime(dt.Ticks, DateTimeKind.Utc);
  200. return new Class<DateTime>(dt);
  201. }
  202. #endregion
  203. #region Lucid & Compact
  204. /// <summary>表示当前本地时间的文本,显示为易于阅读的格式。</summary>
  205. public static string LucidNow { get => Lucid(Now); }
  206. /// <summary>表示当前 UTC 的文本,显示为易于阅读的格式。</summary>
  207. public static string LucidUtc { get => Lucid(UtcNow); }
  208. /// <summary>表示当前本地日期的文本,显示为易于阅读的格式。</summary>
  209. public static string LucidDate { get { return Lucid(Now, true, false, false, false); } }
  210. /// <summary>表示当前本地时间的文本,显示为紧凑的格式。</summary>
  211. public static string CompactNow { get => Compact(Now); }
  212. /// <summary>表示当前 UTC 的文本,显示为紧凑的格式。</summary>
  213. public static string CompactUtc { get => Compact(UtcNow); }
  214. /// <summary>表示当前本地日期的文本,显示为紧凑的格式。</summary>
  215. public static string CompactDate { get { return Compact(Now, true, false, false, false); } }
  216. /// <summary>转换 DateTime 对象到易于阅读的文本。</summary>
  217. public static string Lucid(DateTime dateTime, bool date = true, bool time = true, bool seconds = true, bool milliseconds = true)
  218. {
  219. var sb = new StringBuilder();
  220. if (date) sb.Append(FormatDate(dateTime, true));
  221. if (time)
  222. {
  223. if (date) sb.Append(" ");
  224. sb.Append(FormatTime(dateTime, true, seconds, milliseconds));
  225. }
  226. var lucid = sb.ToString();
  227. return lucid;
  228. }
  229. /// <summary>转换 DateTime 对象到紧凑的文本。</summary>
  230. public static string Compact(DateTime dateTime, bool date = true, bool time = true, bool seconds = true, bool milliseconds = true)
  231. {
  232. var sb = new StringBuilder();
  233. if (date) sb.Append(FormatDate(dateTime, false));
  234. if (time)
  235. {
  236. sb.Append(FormatTime(dateTime, false, seconds, milliseconds));
  237. }
  238. var lucid = sb.ToString();
  239. return lucid;
  240. }
  241. private static string FormatDate(DateTime datetime, bool lucid)
  242. {
  243. var sb = new StringBuilder();
  244. var y = NumberUtility.Restrict(datetime.Year, 0, 9999);
  245. var m = NumberUtility.Restrict(datetime.Month, 1, 12);
  246. var d = NumberUtility.Restrict(datetime.Day, 1, MonthDays(y, m));
  247. if (y < 10) sb.Append("000");
  248. else if (y < 100) sb.Append("00");
  249. else if (y < 1000) sb.Append("0");
  250. sb.Append(y.ToString());
  251. if (lucid) sb.Append("-");
  252. if (m < 10) sb.Append("0");
  253. sb.Append(m.ToString());
  254. if (lucid) sb.Append("-");
  255. if (d < 10) sb.Append("0");
  256. sb.Append(d.ToString());
  257. var date = sb.ToString();
  258. return date;
  259. }
  260. private static string FormatTime(DateTime datetime, bool lucid, bool seconds, bool milliseconds)
  261. {
  262. var sb = new StringBuilder();
  263. var h = NumberUtility.Restrict(datetime.Hour, 0, 23);
  264. var m = NumberUtility.Restrict(datetime.Minute, 0, 59);
  265. var s = NumberUtility.Restrict(datetime.Second, 0, 59);
  266. var ms = NumberUtility.Restrict(datetime.Millisecond, 0, 999);
  267. if (h < 10) sb.Append("0");
  268. sb.Append(h.ToString());
  269. if (lucid) sb.Append(":");
  270. if (m < 10) sb.Append("0");
  271. sb.Append(m.ToString());
  272. if (seconds)
  273. {
  274. if (lucid) sb.Append(":");
  275. if (s < 10) sb.Append("0");
  276. sb.Append(s.ToString());
  277. if (milliseconds)
  278. {
  279. if (lucid) sb.Append(".");
  280. if (ms < 10) sb.Append("00");
  281. else if (ms < 100) sb.Append("0");
  282. sb.Append(ms.ToString());
  283. }
  284. }
  285. var time = sb.ToString();
  286. return time;
  287. }
  288. /// <summary>解析文本。</summary>
  289. /// <param name="text">要被解析的文本。</param>
  290. /// <param name="failed">解析失败时的获取方法。</param>
  291. public static Nullable<DateTime> Parse(string text, Func<DateTime> failed = null)
  292. {
  293. if (string.IsNullOrEmpty(text)) return null;
  294. // 使用默认解析。
  295. {
  296. if (TryParse(text, out DateTime dt)) return dt;
  297. }
  298. // 尝试解析 Lucid 格式。
  299. var byLucid = ParseLucid(text);
  300. if (byLucid != null) return byLucid;
  301. // 尝试 failed 回调。
  302. if (failed != null) return failed.Invoke();
  303. return null;
  304. }
  305. /// <summary>解析文本。</summary>
  306. public static Nullable<DateTime> ParseLucid(string lucid)
  307. {
  308. var failed = null as Nullable<DateTime>;
  309. if (lucid.IsEmpty()) return failed;
  310. int year = 0, month = 0, day = 0;
  311. if (lucid.Length < 10) return failed;
  312. if (lucid[4] != '-' || lucid[7] != '-') return failed;
  313. year = NumberUtility.Int32(lucid.Substring(0, 4));
  314. month = NumberUtility.Int32(lucid.Substring(5, 2));
  315. day = NumberUtility.Int32(lucid.Substring(8, 2));
  316. if (year < 1) return failed;
  317. if (month < 1 || month > 12) return failed;
  318. if (day < 1 || day > DaysInMonth(year, month)) return failed;
  319. int hour = 0, minute = 0, second = 0, milli = 0;
  320. if (lucid.Length >= 16)
  321. {
  322. if (lucid[10] != ' ' || lucid[13] != ':') return failed;
  323. hour = NumberUtility.Int32(lucid.Substring(11, 2));
  324. minute = NumberUtility.Int32(lucid.Substring(14, 2));
  325. if (hour < 0 || hour > 23) return failed;
  326. if (minute < 0 || minute > 59) return failed;
  327. if (lucid.Length >= 19)
  328. {
  329. if (lucid[16] != ':') return failed;
  330. second = NumberUtility.Int32(lucid.Substring(17, 2));
  331. if (second < 0 || second > 59) return failed;
  332. if (lucid.Length >= 23)
  333. {
  334. if (lucid[19] != '.') return failed;
  335. milli = NumberUtility.Int32(lucid.Substring(20, 3));
  336. if (milli < 0 || milli > 999) return failed;
  337. }
  338. }
  339. }
  340. var entity = new DateTime(year, month, day, hour, minute, second, milli);
  341. return new Nullable<DateTime>(entity);
  342. }
  343. #endregion
  344. }
  345. }