Files
xuyueandClaude Opus 5.5 13c3e4c3ec 数据库:参数化的日期字符串比较时也转换格式
- 教材项目 8 用参数传 "2024/1/23  23:59:59"(日期和时间之间两个空格)做 BETWEEN,
  比较时用 tsql_cmp 把形如日期的字符串转成标准格式;日期和时间之间允许多个空格

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 15:13:28 +08:00

106 lines
5.6 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Data.SqlClient;
using System.Globalization;
using System.Text.RegularExpressions;
namespace Sim.Data.Engine;
/// <summary>值在 .NET、SQLite、T-SQL 之间的转换。日期一律以 "yyyy-MM-dd HH:mm:ss" 文本保存,按文本比较即按时间先后。</summary>
internal static class Values
{
private static readonly string[] DateFormats =
{
"yyyy-M-d", "yyyy-M-d H:m", "yyyy-M-d H:m:s", "yyyy-M-d H:m:s.FFFFFFF", "yyyy-M-dTH:m:s", "yyyy-M-dTH:m:s.FFFFFFF",
"yyyy/M/d", "yyyy/M/d H:m", "yyyy/M/d H:m:s", "yyyy/M/d H:m:s.FFFFFFF",
"yyyyMMdd", "yyyyMMdd H:m:s",
"yyyy.M.d", "yyyy.M.d H:m:s",
};
/// <summary>形如日期的字符串字面量(比较时转成标准格式,例如 BETWEEN '2024/1/16 9:05:03' AND ...)</summary>
private static readonly Regex DateLike = new(@"^\d{4}[-/.]\d{1,2}[-/.]\d{1,2}((\s+|T)\d{1,2}:\d{1,2}(:\d{1,2}(\.\d+)?)?)?$");
/// <summary>日期和时间之间多余的空格(教材拼出的 "2024/1/23 23:59:59")</summary>
private static readonly Regex Spaces = new(@"\s+");
public static bool LooksLikeDate(string text) => DateLike.IsMatch(text.Trim());
/// <summary>按 SQL Server 接受的写法解析日期(包括中文 Windows 上 DateTime.ToString() 的 2024/1/16 9:05:03)</summary>
public static bool TryParseDateTime(string text, out DateTime value) =>
DateTime.TryParseExact(Spaces.Replace(text.Trim(), " "), DateFormats, CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces, out value);
public static string FormatDateTime(DateTime value) =>
value.Millisecond == 0 && value.Ticks % TimeSpan.TicksPerMillisecond == 0
? value.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)
: value.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture);
public static string FormatDate(DateTime value) => value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
/// <summary>字符串里的日期改成标准格式;只有日期没有时间的保持只有日期(与 datetime 比较时相当于当天 0 点)</summary>
public static string NormalizeDateLiteral(string text)
{
if (!TryParseDateTime(text, out var value)) return text;
return text.Contains(':') ? FormatDateTime(value) : FormatDate(value);
}
/// <summary>C# 传进来的参数值 → SQLite 能保存的值</summary>
public static object? ToStorage(object? value) => value switch
{
null or DBNull => null,
DateTime d => FormatDateTime(d),
DateTimeOffset d => FormatDateTime(d.LocalDateTime),
TimeSpan t => t.ToString(@"hh\:mm\:ss", CultureInfo.InvariantCulture),
bool b => b ? 1L : 0L,
Guid g => g.ToString().ToUpperInvariant(),
decimal m => (double)m,
Enum e => Convert.ToInt64(e, CultureInfo.InvariantCulture),
_ => value,
};
/// <summary>SQLite 读出的值 → SQL Server 对这个类型返回的 .NET 值</summary>
public static object FromStorage(object value, TSqlType? type)
{
if (value is DBNull) return value;
if (type == null || type.ClrType == typeof(object)) return FromExpression(value);
try
{
return type.ClrType switch
{
var t when t == typeof(int) => Convert.ToInt32(value, CultureInfo.InvariantCulture),
var t when t == typeof(long) => Convert.ToInt64(value, CultureInfo.InvariantCulture),
var t when t == typeof(short) => Convert.ToInt16(value, CultureInfo.InvariantCulture),
var t when t == typeof(byte) => Convert.ToByte(value, CultureInfo.InvariantCulture),
var t when t == typeof(bool) => Convert.ToInt64(value, CultureInfo.InvariantCulture) != 0,
var t when t == typeof(double) => Convert.ToDouble(value, CultureInfo.InvariantCulture),
var t when t == typeof(float) => Convert.ToSingle(value, CultureInfo.InvariantCulture),
var t when t == typeof(decimal) => Convert.ToDecimal(value, CultureInfo.InvariantCulture),
var t when t == typeof(DateTime) => value is string s && TryParseDateTime(s, out var d) ? d : value,
var t when t == typeof(TimeSpan) => value is string s && TimeSpan.TryParse(s, CultureInfo.InvariantCulture, out var ts) ? ts : value,
var t when t == typeof(Guid) => value is string s && Guid.TryParse(s, out var g) ? g : value,
var t when t == typeof(string) => Convert.ToString(value, CultureInfo.InvariantCulture)!,
_ => value,
};
}
catch (Exception ex) when (ex is FormatException or OverflowException or InvalidCastException)
{
return value;
}
}
/// <summary>表达式列(如 count(*)、getdate())没有声明类型:整数按 int 返回,日期样子的文本按 DateTime 返回</summary>
public static object FromExpression(object value) => value switch
{
long l when l is >= int.MinValue and <= int.MaxValue => (int)l,
string s when DateLike.IsMatch(s) && s.Contains(':') && TryParseDateTime(s, out var d) => d,
_ => value,
};
/// <summary>T-SQL 字符串字面量</summary>
public static string Quote(string text) => "'" + text.Replace("'", "''") + "'";
/// <summary>SQLite 标识符</summary>
public static string QuoteName(string name) => "\"" + name.Replace("\"", "\"\"") + "\"";
public static SqlException ConvertError(object? value, string type) =>
SqlException.ConvertFailed(Convert.ToString(value, CultureInfo.InvariantCulture) ?? "", type);
}