using System.Globalization; namespace FoodLabeling.Application.Helpers; /// /// Reports Print Log 日期时间展示:统一为美式短日期 MM/dd/yy,含时间时精确到分钟。 /// public static class ReportsDateTimeDisplayHelper { /// Print Log 日期部分格式(MM/DD/YY) public const string PrintLogDateFormat = "MM/dd/yy"; /// Print Log 日期+时间格式(精确到分钟) public const string PrintLogDateTimeFormat = "MM/dd/yy HH:mm"; private static readonly string[] ExpiryParseFormats = { PrintLogDateTimeFormat, PrintLogDateFormat, "MM/dd/yyyy HH:mm", "MM/dd/yyyy hh:mm tt", "MM/dd/yyyy h:mm tt", "MM/dd/yyyy", "M/d/yyyy HH:mm", "M/d/yyyy hh:mm tt", "M/d/yyyy h:mm tt", "M/d/yyyy", "yyyy-MM-dd HH:mm", "yyyy-MM-dd H:mm", "yyyy-MM-dd", "yyyy/MM/dd HH:mm", "yyyy/MM/dd", "dd/MM/yyyy HH:mm", "dd/MM/yyyy", "d/M/yyyy HH:mm", "d/M/yyyy", "MM/dd HH:mm", "MM/dd", "HH:mm", "H:mm", "hh:mm tt", "h:mm tt", }; /// 打印时间展示(Printed at 列) public static string FormatPrintedAt(DateTime printedAt) { if (printedAt == DateTime.MinValue) { return "无"; } return printedAt.ToString(PrintLogDateTimeFormat, CultureInfo.InvariantCulture); } /// 保质期展示(Expiration 列):解析后统一为 public static string FormatExpiryDisplay(string? raw, DateTime? referenceTime = null) { if (string.IsNullOrWhiteSpace(raw) || string.Equals(raw.Trim(), "无", StringComparison.Ordinal)) { return "无"; } var s = raw.Trim(); if (TryParseToDateTime(s, referenceTime, out var dt)) { return FormatExpiryDateTime(dt); } return s; } /// 将已解析的到期时刻格式化为 Print Log 展示文案。 public static string FormatExpiryDateTime(DateTime dt) { if (dt == DateTime.MinValue) { return "无"; } return dt.ToString(PrintLogDateTimeFormat, CultureInfo.InvariantCulture); } /// 尝试将字面量解析为 (支持仅日期、仅时间、缺少年份等)。 public static bool TryParseToDateTime(string s, DateTime? referenceTime, out DateTime dt) { dt = default; var reference = referenceTime ?? DateTime.Now; if (DateTime.TryParse( s, CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeLocal, out dt)) { return true; } if (DateTime.TryParse( s, CultureInfo.GetCultureInfo("en-US"), DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeLocal, out dt)) { return true; } foreach (var fmt in ExpiryParseFormats) { if (DateTime.TryParseExact( s, fmt, CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeLocal, out dt)) { return NormalizePartialDateTime(dt, fmt, reference); } if (DateTime.TryParseExact( s, fmt, CultureInfo.GetCultureInfo("en-US"), DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeLocal, out dt)) { return NormalizePartialDateTime(dt, fmt, reference); } } if (TryParseTimeOnly(s, reference, out dt)) { return true; } if (TryParseMonthDayOnly(s, reference, out dt)) { return true; } return false; } private static bool NormalizePartialDateTime(DateTime dt, string format, DateTime reference) { if (format is "MM/dd" or "MM/dd HH:mm") { var year = reference.Year; var candidate = new DateTime(year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second); if (candidate.Date < reference.Date) { candidate = candidate.AddYears(1); } dt = candidate; return true; } if (format is "HH:mm" or "H:mm" or "hh:mm tt" or "h:mm tt") { dt = new DateTime(reference.Year, reference.Month, reference.Day, dt.Hour, dt.Minute, dt.Second); return true; } return true; } private static bool TryParseTimeOnly(string s, DateTime reference, out DateTime dt) { dt = default; foreach (var fmt in new[] { "HH:mm", "H:mm", "hh:mm tt", "h:mm tt" }) { if (DateTime.TryParseExact( s, fmt, CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeLocal, out var parsed)) { dt = new DateTime(reference.Year, reference.Month, reference.Day, parsed.Hour, parsed.Minute, 0); return true; } if (DateTime.TryParseExact( s, fmt, CultureInfo.GetCultureInfo("en-US"), DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeLocal, out parsed)) { dt = new DateTime(reference.Year, reference.Month, reference.Day, parsed.Hour, parsed.Minute, 0); return true; } } return false; } private static bool TryParseMonthDayOnly(string s, DateTime reference, out DateTime dt) { dt = default; foreach (var fmt in new[] { "MM/dd", "M/d" }) { if (DateTime.TryParseExact( s, fmt, CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeLocal, out var parsed)) { var year = reference.Year; var candidate = new DateTime(year, parsed.Month, parsed.Day, 0, 0, 0); if (candidate.Date < reference.Date) { candidate = candidate.AddYears(1); } dt = candidate; return true; } } return false; } }