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.

473 lines
19 KiB

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