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

using System;
using System.Globalization;
using System.Text;
using static System.DateTime;
namespace Apewer
{
/// <summary>时钟。</summary>
public static class ClockUtility
{
#region To DateTime
/// <summary>尝试转换内容为 DateTime 实例。</summary>
public static Class<DateTime> DateTime(object value)
{
if (value is DateTime dt) return new Class<DateTime>(dt);
if (value.IsNull()) return null;
try
{
var text = value.ToString();
return Parse(text);
}
catch
{
return null;
}
}
/// <summary>尝试转换内容为 DateTime 实例。</summary>
/// <exception cref="ArgumentNullException" />
public static DateTime DateTime(object value, Func<DateTime> failed)
{
if (failed == null) throw new ArgumentNullException(nameof(failed));
var parsed = DateTime(value);
if (parsed != null) return parsed.Value;
return failed.Invoke();
}
/// <summary>尝试转换内容为 DateTime 实例。</summary>
public static DateTime DateTime(object value, DateTime failed)
{
var parsed = DateTime(value);
if (parsed != null) return parsed.Value;
return failed;
}
#endregion
#region Fixed
private static DateTime _zero = new DateTime(0L, DateTimeKind.Unspecified);
private static DateTime _origin = NewOrigin(DateTimeKind.Unspecified);
private static DateTime _utc_origin = NewOrigin(DateTimeKind.Utc);
/// <summary>创建新的零值 DateTime 对象。</summary>
public static DateTime Zero { get => _zero; }
/// <summary>获取一个 DateTime 对象,该对象设置为 1970-01-01 00:00:00.000,表示为协调通用时间 (UTC)。</summary>
public static DateTime UtcOrigin { get => _utc_origin; }
/// <summary>获取一个 DateTime 对象,该对象设置为此计算机上的当前日期和时间,表示为本地时间。</summary>
public static DateTime Now { get => System.DateTime.Now; }
/// <summary>获取一个 DateTime 对象,该对象设置为此计算机上的当前日期和时间,表示为协调通用时间 (UTC)。</summary>
public static DateTime UtcNow { get => System.DateTime.UtcNow; }
/// <summary>创建一个 DateTime 对象,该对象设置为 1970-01-01 00:00:00.000。</summary>
public static DateTime NewOrigin(DateTimeKind kind) => new DateTime(1970, 1, 1, 0, 0, 0, 0, kind);
#endregion
#region Clone
/// <summary>克隆 DateTime 对象,并使用新的 Kind。</summary>
/// <param name="dateTime">要克隆的 DateTime 对象。</param>
/// <param name="kind">时间类型。</param>
/// <returns>克隆后带有新 Kind 的 DateTime 对象。</returns>
public static DateTime Clone(this DateTime dateTime, DateTimeKind kind)
{
return new DateTime(dateTime.Ticks, kind);
}
#endregion
#region Round
/// <summary>对齐时间,小于 <see cref="DateTimePart"/> 的部分将被舍弃。</summary>
public static DateTime Round(this DateTime dateTime, DateTimePart datePart)
{
switch (datePart)
{
case DateTimePart.Year: return new DateTime(dateTime.Year, 1, 1, 0, 0, 0, 0, dateTime.Kind);
case DateTimePart.Month: return new DateTime(dateTime.Year, dateTime.Month, 1, 0, 0, 0, 0, dateTime.Kind);
case DateTimePart.Day: return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 0, 0, 0, 0, dateTime.Kind);
case DateTimePart.Hour: return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, 0, 0, 0, dateTime.Kind);
case DateTimePart.Minute: return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, 0, 0, dateTime.Kind);
case DateTimePart.Second: return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, dateTime.Second, 0, dateTime.Kind);
case DateTimePart.Millisecond: return dateTime;
default: throw new ArgumentException($"指定的 {nameof(DateTimePart)} 不受支持。");
}
}
#endregion
#region Common
/// <summary>判断指定年份是闰年。</summary>
public static bool IsLeapYear(this int year)
{
if (year % 400 == 0) return true;
if (year % 100 == 0) return false;
if (year % 4 == 0) return true;
return false;
}
/// <summary>判断指定年份是闰年。</summary>
public static bool IsLeapYear(DateTime dateTime) => IsLeapYear(dateTime.Year);
/// <summary>获取指定年月的天数。</summary>
/// <exception cref="ArgumentOutOfRangeException" />
public static int MonthDays(int year, int month)
{
switch (month)
{
case 1: case 3: case 5: case 7: case 8: case 10: case 12: return 31;
case 4: case 6: case 9: case 11: return 30;
case 2: return IsLeapYear(year) ? 29 : 28;
default: throw new ArgumentOutOfRangeException(nameof(month));
}
}
#endregion
#region Stamp
/// <summary>自定义从 DateTime 转为时间戳的方法。</summary>
public static Func<DateTime, long> CustomToStamp { get; set; }
/// <summary>自定义从时间戳转为 DateTime 的方法。</summary>
public static Func<long, DateTime> CustomFromStamp { get; set; }
/// <summary>获取当前本地时间的毫秒时间戳。</summary>
public static long NowStamp { get => ToStamp(Now); }
/// <summary>获取当前 UTC 的毫秒时间戳。</summary>
public static long UtcStamp { get => ToStamp(UtcNow); }
/// <summary>获取毫秒时间戳。当指定了 <see cref="CustomToStamp"/> 时将优先使用自定义的方法。</summary>
/// <remarks>默认不判断参数的时区,与 <see cref="DateTimeKind.Unspecified"/> 相同。</remarks>
public static long ToStamp(DateTime dateTime)
{
var converter = CustomToStamp;
if (converter != null) return converter.Invoke(dateTime);
var span = dateTime - _origin;
var value = span.TotalMilliseconds;
var stamp = Convert.ToInt64(Math.Floor(value));
return stamp;
}
/// <summary>获取 UTC 毫秒时间戳</summary>
public static long ToUtcStamp(DateTime dateTime)
{
if (dateTime.Kind == DateTimeKind.Local) dateTime = dateTime.ToUniversalTime();
var span = dateTime - _utc_origin;
var value = span.TotalMilliseconds;
var stamp = Convert.ToInt64(Math.Floor(value));
return stamp;
}
/// <summary>解析毫秒时间戳,获取 DateTime 对象。当指定了 <see cref="CustomFromStamp"/> 时将优先使用自定义的方法。</summary>
/// <remarks>默认不判断系统时区,返回的结果是 <see cref="DateTimeKind.Unspecified"/>。</remarks>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static DateTime FromStamp(long stamp)
{
var converter = CustomFromStamp;
if (converter != null) return converter.Invoke(stamp);
return FromStamp(stamp, DateTimeKind.Unspecified, DateTimeKind.Unspecified);
}
/// <summary>从毫秒时间戳获取 DateTime 对象。</summary>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static DateTime FromStamp(long stamp, DateTimeKind stampKind, DateTimeKind dateTimeKind)
{
switch (dateTimeKind)
{
case DateTimeKind.Unspecified:
switch (stampKind)
{
case DateTimeKind.Unspecified:
case DateTimeKind.Utc:
case DateTimeKind.Local:
return _origin.AddMilliseconds(stamp);
default:
throw new ArgumentOutOfRangeException(nameof(stampKind));
}
case DateTimeKind.Utc:
switch (stampKind)
{
case DateTimeKind.Unspecified:
case DateTimeKind.Utc:
return _utc_origin.AddMilliseconds(stamp);
case DateTimeKind.Local:
return NewOrigin(DateTimeKind.Local).AddMilliseconds(stamp).ToUniversalTime();
default:
throw new ArgumentOutOfRangeException(nameof(stampKind));
}
case DateTimeKind.Local:
switch (stampKind)
{
case DateTimeKind.Unspecified:
case DateTimeKind.Utc:
return _utc_origin.AddMilliseconds(stamp).ToLocalTime();
case DateTimeKind.Local:
return NewOrigin(DateTimeKind.Local).AddMilliseconds(stamp);
default:
throw new ArgumentOutOfRangeException(nameof(stampKind));
}
default:
throw new ArgumentOutOfRangeException(nameof(dateTimeKind));
}
}
#endregion
#region Lucid & Compact
/// <summary>表示当前本地时间的文本,显示为易于阅读的格式。</summary>
public static string LucidNow { get => Lucid(Now); }
/// <summary>表示当前 UTC 的文本,显示为易于阅读的格式。</summary>
public static string LucidUtc { get => Lucid(UtcNow); }
/// <summary>表示当前本地日期的文本,显示为易于阅读的格式。</summary>
public static string LucidDate { get { return Lucid(Now, true, false, false, false); } }
/// <summary>表示当前本地时间的文本,显示为紧凑的格式。</summary>
public static string CompactNow { get => Compact(Now); }
/// <summary>表示当前 UTC 的文本,显示为紧凑的格式。</summary>
public static string CompactUtc { get => Compact(UtcNow); }
/// <summary>表示当前本地日期的文本,显示为紧凑的格式。</summary>
public static string CompactDate { get { return Compact(Now, true, false, false, false); } }
/// <summary>转换 DateTime 对象到易于阅读的文本。</summary>
public static string Lucid(DateTime dateTime, bool date = true, bool time = true, bool seconds = true, bool milliseconds = true)
{
var sb = new StringBuilder();
if (date) sb.Append(FormatDate(dateTime, true));
if (time)
{
if (date) sb.Append(" ");
sb.Append(FormatTime(dateTime, true, seconds, milliseconds));
}
var lucid = sb.ToString();
return lucid;
}
/// <summary>转换 DateTime 对象到紧凑的文本。</summary>
public static string Compact(DateTime dateTime, bool date = true, bool time = true, bool seconds = true, bool milliseconds = true)
{
var sb = new StringBuilder();
if (date) sb.Append(FormatDate(dateTime, false));
if (time)
{
sb.Append(FormatTime(dateTime, false, seconds, milliseconds));
}
var lucid = sb.ToString();
return lucid;
}
private static string FormatDate(DateTime datetime, bool lucid)
{
var sb = new StringBuilder();
var y = NumberUtility.Restrict(datetime.Year, 0, 9999);
var m = NumberUtility.Restrict(datetime.Month, 1, 12);
var d = NumberUtility.Restrict(datetime.Day, 1, MonthDays(y, m));
if (y < 10) sb.Append("000");
else if (y < 100) sb.Append("00");
else if (y < 1000) sb.Append("0");
sb.Append(y.ToString());
if (lucid) sb.Append("-");
if (m < 10) sb.Append("0");
sb.Append(m.ToString());
if (lucid) sb.Append("-");
if (d < 10) sb.Append("0");
sb.Append(d.ToString());
var date = sb.ToString();
return date;
}
private static string FormatTime(DateTime datetime, bool lucid, bool seconds, bool milliseconds)
{
var sb = new StringBuilder();
var h = NumberUtility.Restrict(datetime.Hour, 0, 23);
var m = NumberUtility.Restrict(datetime.Minute, 0, 59);
var s = NumberUtility.Restrict(datetime.Second, 0, 59);
var ms = NumberUtility.Restrict(datetime.Millisecond, 0, 999);
if (h < 10) sb.Append("0");
sb.Append(h.ToString());
if (lucid) sb.Append(":");
if (m < 10) sb.Append("0");
sb.Append(m.ToString());
if (seconds)
{
if (lucid) sb.Append(":");
if (s < 10) sb.Append("0");
sb.Append(s.ToString());
if (milliseconds)
{
if (lucid) sb.Append(".");
if (ms < 10) sb.Append("00");
else if (ms < 100) sb.Append("0");
sb.Append(ms.ToString());
}
}
var time = sb.ToString();
return time;
}
/// <summary>解析文本。</summary>
/// <param name="text">要被解析的文本。</param>
public static Class<DateTime> Parse(string text)
{
if (string.IsNullOrEmpty(text)) return null;
// 尝试解析 Lucid 格式。
var lucid = ParseLucid(text);
if (lucid != null) return lucid;
// 使用默认解析。
if (TryParse(text, out DateTime system)) return new Class<DateTime>(system);
return null;
}
/// <summary>解析文本,获取 DateTime 对象。</summary>
static Class<DateTime> ParseExact(string text)
{
var str = text;
if (string.IsNullOrEmpty(str)) return null;
var utc = false;
var lower = str.ToLower();
if (lower.EndsWith(" utc"))
{
utc = true;
str = str.Substring(0, str.Length - 4);
}
DateTime dt;
if (!TryParse(str, out dt))
{
if (!str.Contains("-") && TryParseExact(str, "yyyy-M-d", null, DateTimeStyles.None, out dt))
{
if (!str.Contains("/") && TryParseExact(str, "yyyy/M/d", null, DateTimeStyles.None, out dt))
{
return null;
}
}
}
if (utc) dt = new DateTime(dt.Ticks, DateTimeKind.Utc);
return new Class<DateTime>(dt);
}
/// <summary>解析文本。</summary>
/// <param name="text">要被解析的文本。</param>
/// <param name="failed">解析失败时的获取方法。</param>
/// <exception cref="ArgumentNullException" />
public static DateTime Parse(string text, Func<DateTime> failed)
{
if (failed == null) throw new ArgumentNullException(nameof(failed));
var parsed = Parse(text);
if (parsed != null) return parsed.Value;
return failed.Invoke();
}
/// <summary>解析文本。</summary>
/// <param name="text">要被解析的文本。</param>
/// <param name="failed">解析失败时的默认值。</param>
/// <exception cref="ArgumentNullException" />
public static DateTime Parse(string text, DateTime failed)
{
var parsed = Parse(text);
if (parsed != null) return parsed.Value;
return failed;
}
/// <summary>解析文本。</summary>
static Class<DateTime> ParseLucid(string lucid)
{
if (lucid.IsEmpty()) return null;
int year = 0, month = 0, day = 0;
if (lucid.Length < 10) return null;
if (lucid[4] != '-' || lucid[7] != '-') return null;
year = NumberUtility.Int32(lucid.Substring(0, 4));
month = NumberUtility.Int32(lucid.Substring(5, 2));
day = NumberUtility.Int32(lucid.Substring(8, 2));
if (year < 1) return null;
if (month < 1 || month > 12) return null;
if (day < 1 || day > DaysInMonth(year, month)) return null;
int hour = 0, minute = 0, second = 0, milli = 0;
if (lucid.Length >= 16)
{
if (lucid[10] != ' ' || lucid[13] != ':') return null;
hour = NumberUtility.Int32(lucid.Substring(11, 2));
minute = NumberUtility.Int32(lucid.Substring(14, 2));
if (hour < 0 || hour > 23) return null;
if (minute < 0 || minute > 59) return null;
if (lucid.Length >= 19)
{
if (lucid[16] != ':') return null;
second = NumberUtility.Int32(lucid.Substring(17, 2));
if (second < 0 || second > 59) return null;
if (lucid.Length >= 23)
{
if (lucid[19] != '.') return null;
milli = NumberUtility.Int32(lucid.Substring(20, 3));
if (milli < 0 || milli > 999) return null;
}
}
}
var entity = new DateTime(year, month, day, hour, minute, second, milli);
return new Class<DateTime>(entity);
}
#endregion
}
}