diff --git a/src/BootstrapBlazor/Components/DateTimePicker/DateTimePicker.razor.cs b/src/BootstrapBlazor/Components/DateTimePicker/DateTimePicker.razor.cs
index e0500380ce6..1f7a774fd46 100644
--- a/src/BootstrapBlazor/Components/DateTimePicker/DateTimePicker.razor.cs
+++ b/src/BootstrapBlazor/Components/DateTimePicker/DateTimePicker.razor.cs
@@ -4,7 +4,6 @@
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
using Microsoft.Extensions.Localization;
-using System.Globalization;
namespace BootstrapBlazor.Components;
@@ -569,10 +568,9 @@ private async Task OnClear()
///
protected override bool TryParseValueFromString(string value, [MaybeNullWhen(false)] out TValue result, out string? validationErrorMessage)
{
- var format = ViewMode == DatePickerViewMode.DateTime ? DateTimeFormat : DateFormat;
result = default;
validationErrorMessage = null;
- var ret = DateTime.TryParseExact(value, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out var val);
+ var ret = DateTimeHelper.TryToDateTime(value, out var val);
if (ret)
{
result = (TValue)(object)val;
diff --git a/src/BootstrapBlazor/Utils/DateTimeHelper.cs b/src/BootstrapBlazor/Utils/DateTimeHelper.cs
new file mode 100644
index 00000000000..1c5bd7e6976
--- /dev/null
+++ b/src/BootstrapBlazor/Utils/DateTimeHelper.cs
@@ -0,0 +1,87 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the Apache 2.0 License
+// See the LICENSE file in the project root for more information.
+// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
+
+using System.Globalization;
+
+namespace BootstrapBlazor.Components;
+
+static class DateTimeHelper
+{
+ ///
+ /// 无分隔符等标准解析无法识别的紧凑格式,需要显式列出
+ ///
+ private static readonly string[] CompactFormats =
+ [
+ "yyyyMMdd",
+ "yyyyMMddHHmm",
+ "yyyyMMddHHmmss",
+ "yyyyMMddHHmmssfff",
+ "yyyyMMdd HHmmss",
+ "yyyyMMdd HH:mm:ss",
+ "yyyyMMdd HH:mm",
+ "yyyy-M-d",
+ "yyyy/M/d"
+ ];
+
+ ///
+ /// 将字符串解析为 ,无法解析时返回
+ ///
+ public static DateTime? ToDateTime(string value)
+ => TryToDateTime(value, out var result) ? result : null;
+
+ ///
+ /// 将字符串解析为 ,无法解析时返回
+ ///
+ /// 要解析的字符串
+ /// 解析失败时返回的默认值
+ public static DateTime ToDateTime(string value, DateTime defaultValue)
+ => TryToDateTime(value, out var result) ? result : defaultValue;
+
+ ///
+ /// 尝试将字符串解析为
+ ///
+ /// 要解析的字符串
+ /// 解析成功时的结果,失败时为
+ public static bool TryToDateTime(string value, out DateTime result)
+ {
+ result = DateTime.MinValue;
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ return false;
+ }
+
+ var text = value.Trim();
+
+ // 先尝试无分隔符等紧凑格式(如 20260501),这类格式标准解析无法识别
+ if (DateTime.TryParseExact(text, CompactFormats, CultureInfo.InvariantCulture, DateTimeStyles.None, out result))
+ {
+ return true;
+ }
+
+ // 再回退到通用解析,覆盖 2026-5-1、2026/5/1、2026-05-01 13:04:05 等带分隔符写法
+ return DateTime.TryParse(text, CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces, out result);
+ }
+
+ ///
+ /// 将字符串解析为 ,无法解析时返回
+ ///
+ ///
+ /// 适用于 DateTimePicker 绑定 的场景;
+ /// 不含时区信息时按本地时区处理。
+ ///
+ /// 要解析的字符串
+ /// 解析成功返回对应的 ,否则返回
+ public static DateTimeOffset? ToDateTimeOffset(string value)
+ {
+ if (TryToDateTime(value, out var dateTime))
+ {
+ // 紧凑/无时区字符串解析出的 Kind 为 Unspecified,按本地时区补齐偏移
+ return dateTime.Kind == DateTimeKind.Unspecified
+ ? new DateTimeOffset(DateTime.SpecifyKind(dateTime, DateTimeKind.Local))
+ : new DateTimeOffset(dateTime);
+ }
+ return null;
+ }
+}
diff --git a/test/UnitTest/Utils/DateTimeHelperTest.cs b/test/UnitTest/Utils/DateTimeHelperTest.cs
new file mode 100644
index 00000000000..a1db225c7c4
--- /dev/null
+++ b/test/UnitTest/Utils/DateTimeHelperTest.cs
@@ -0,0 +1,148 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the Apache 2.0 License
+// See the LICENSE file in the project root for more information.
+// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
+
+using System.Runtime.CompilerServices;
+
+namespace UnitTest.Utils;
+
+///
+/// DateTimeHelper 为内部类,通过 配合
+/// 直接访问其静态方法进行测试
+///
+public class DateTimeHelperTest
+{
+ ///
+ /// 内部类型的程序集限定名
+ ///
+ private const string HelperTypeName = "BootstrapBlazor.Components.DateTimeHelper, BootstrapBlazor";
+
+ [Fact]
+ public void ToDateTime_Ok()
+ {
+ // 紧凑格式解析成功
+ Assert.Equal(new DateTime(2026, 5, 1), ToDateTime(null, "20260501"));
+
+ // 带分隔符格式解析成功
+ Assert.Equal(new DateTime(2026, 5, 1, 13, 4, 5), ToDateTime(null, "2026-05-01 13:04:05"));
+ }
+
+ [Fact]
+ public void ToDateTime_Null()
+ {
+ // 解析失败返回 null
+ Assert.Null(ToDateTime(null, "test"));
+ Assert.Null(ToDateTime(null, null!));
+ }
+
+ [Fact]
+ public void ToDateTime_DefaultValue_Ok()
+ {
+ var defaultValue = new DateTime(2020, 1, 1);
+
+ // 解析成功返回解析值
+ Assert.Equal(new DateTime(2026, 5, 1), ToDateTimeWithDefaultValue(null, "20260501", defaultValue));
+
+ // 解析失败返回默认值
+ Assert.Equal(defaultValue, ToDateTimeWithDefaultValue(null, "test", defaultValue));
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ [InlineData("\t")]
+ public void TryToDateTime_NullOrWhiteSpace(string? value)
+ {
+ // 空字符串直接返回 false 且结果为 DateTime.MinValue
+ Assert.False(TryToDateTime(null, value!, out var result));
+ Assert.Equal(DateTime.MinValue, result);
+ }
+
+ [Fact]
+ public void TryToDateTime_Invalid()
+ {
+ // 两种解析方式均失败
+ Assert.False(TryToDateTime(null, "test", out var result));
+ Assert.Equal(DateTime.MinValue, result);
+
+ Assert.False(TryToDateTime(null, "20261301", out _));
+ }
+
+ [Theory]
+ [InlineData("20260501", 2026, 5, 1, 0, 0, 0, 0)]
+ [InlineData("202605011304", 2026, 5, 1, 13, 4, 0, 0)]
+ [InlineData("20260501130405", 2026, 5, 1, 13, 4, 5, 0)]
+ [InlineData("20260501130405123", 2026, 5, 1, 13, 4, 5, 123)]
+ [InlineData("20260501 130405", 2026, 5, 1, 13, 4, 5, 0)]
+ [InlineData("20260501 13:04:05", 2026, 5, 1, 13, 4, 5, 0)]
+ [InlineData("20260501 13:04", 2026, 5, 1, 13, 4, 0, 0)]
+ [InlineData("2026-5-1", 2026, 5, 1, 0, 0, 0, 0)]
+ [InlineData("2026/5/1", 2026, 5, 1, 0, 0, 0, 0)]
+ public void TryToDateTime_CompactFormats(string value, int year, int month, int day, int hour, int minute, int second, int millisecond)
+ {
+ // 紧凑格式由 TryParseExact 分支解析
+ Assert.True(TryToDateTime(null, value, out var result));
+ Assert.Equal(new DateTime(year, month, day, hour, minute, second, millisecond), result);
+ }
+
+ [Theory]
+ [InlineData("2026-05-01")]
+ [InlineData("2026/05/01")]
+ public void TryToDateTime_Fallback(string value)
+ {
+ // 标准格式由 TryParse 回退分支解析
+ Assert.True(TryToDateTime(null, value, out var result));
+ Assert.Equal(new DateTime(2026, 5, 1), result);
+ }
+
+ [Fact]
+ public void TryToDateTime_Trim()
+ {
+ // 前后空白被裁剪后可正常解析
+ Assert.True(TryToDateTime(null, " 20260501 ", out var result));
+ Assert.Equal(new DateTime(2026, 5, 1), result);
+ }
+
+ [Fact]
+ public void ToDateTimeOffset_Unspecified()
+ {
+ // 无时区信息按本地时区补齐偏移
+ var value = new DateTime(2026, 5, 1, 13, 4, 5);
+ var expected = TimeZoneInfo.Local.GetUtcOffset(value);
+
+ var actual = ToDateTimeOffset(null, "20260501 130405");
+ Assert.NotNull(actual);
+ Assert.Equal(expected, actual.Value.Offset);
+ Assert.Equal(value, actual.Value.DateTime);
+ }
+
+ [Fact]
+ public void ToDateTimeOffset_Local()
+ {
+ // 带时区信息的字符串解析出的 Kind 不是 Unspecified,直接构造
+ var actual = ToDateTimeOffset(null, "2026-05-01T13:04:05Z");
+ Assert.NotNull(actual);
+ Assert.Equal(new DateTime(2026, 5, 1, 13, 4, 5, DateTimeKind.Utc), actual.Value.UtcDateTime);
+ }
+
+ [Fact]
+ public void ToDateTimeOffset_Null()
+ {
+ // 解析失败返回 null
+ Assert.Null(ToDateTimeOffset(null, "test"));
+ }
+
+ [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "ToDateTime")]
+ static extern DateTime? ToDateTime([UnsafeAccessorType(HelperTypeName)] object? @this, string value);
+
+ [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "ToDateTime")]
+ static extern DateTime ToDateTimeWithDefaultValue([UnsafeAccessorType(HelperTypeName)] object? @this, string value, DateTime defaultValue);
+
+ [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "TryToDateTime")]
+ static extern bool TryToDateTime([UnsafeAccessorType(HelperTypeName)] object? @this, string value, out DateTime result);
+
+ [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "ToDateTimeOffset")]
+ static extern DateTimeOffset? ToDateTimeOffset([UnsafeAccessorType(HelperTypeName)] object? @this, string value);
+}