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.

464 lines
18 KiB

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