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;
}
}