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

11 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
11 months ago
3 years ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
4 years ago
4 years ago
2 years ago
4 years ago
4 years ago
2 years ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 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. }