From 0f14ceb108e8d6ed13af4504489eeec7cb249ccb Mon Sep 17 00:00:00 2001 From: leoca Date: Fri, 11 Sep 2026 10:23:03 +0200 Subject: [PATCH] Parse scaffolded date and time default literals with the invariant culture - SqliteDatabaseModelFactory and SqlServerDatabaseModelFactory parsed DateTime, DateOnly, TimeOnly and DateTimeOffset default literals with the current culture. Under a culture whose calendar is not Gregorian, '1968-10-23' is read in that calendar: fa-IR scaffolds HasDefaultValue(2590-01-12), ar-SA silently drops the default. - Pass CultureInfo.InvariantCulture, as #35675 already does for decimal and the other numeric literals. - Add fa-IR tests next to the existing da-DK decimal ones. --- .../Internal/SqlServerDatabaseModelFactory.cs | 8 ++-- .../Internal/SqliteDatabaseModelFactory.cs | 8 ++-- .../SqlServerDatabaseModelFactoryTest.cs | 37 +++++++++++++++ .../SqliteDatabaseModelFactoryTest.cs | 46 +++++++++++++++++++ 4 files changed, 91 insertions(+), 8 deletions(-) diff --git a/src/EFCore.SqlServer/Scaffolding/Internal/SqlServerDatabaseModelFactory.cs b/src/EFCore.SqlServer/Scaffolding/Internal/SqlServerDatabaseModelFactory.cs index 6ec23327ce8..9ffcaddbae1 100644 --- a/src/EFCore.SqlServer/Scaffolding/Internal/SqlServerDatabaseModelFactory.cs +++ b/src/EFCore.SqlServer/Scaffolding/Internal/SqlServerDatabaseModelFactory.cs @@ -933,25 +933,25 @@ FROM [sys].[views] v } if (type == typeof(DateTime) - && DateTime.TryParse(defaultValueSql, out var dateTime)) + && DateTime.TryParse(defaultValueSql, CultureInfo.InvariantCulture, out var dateTime)) { return dateTime; } if (type == typeof(DateOnly) - && DateOnly.TryParse(defaultValueSql, out var dateOnly)) + && DateOnly.TryParse(defaultValueSql, CultureInfo.InvariantCulture, out var dateOnly)) { return dateOnly; } if (type == typeof(TimeOnly) - && TimeOnly.TryParse(defaultValueSql, out var timeOnly)) + && TimeOnly.TryParse(defaultValueSql, CultureInfo.InvariantCulture, out var timeOnly)) { return timeOnly; } if (type == typeof(DateTimeOffset) - && DateTimeOffset.TryParse(defaultValueSql, out var dateTimeOffset)) + && DateTimeOffset.TryParse(defaultValueSql, CultureInfo.InvariantCulture, out var dateTimeOffset)) { return dateTimeOffset; } diff --git a/src/EFCore.Sqlite.Core/Scaffolding/Internal/SqliteDatabaseModelFactory.cs b/src/EFCore.Sqlite.Core/Scaffolding/Internal/SqliteDatabaseModelFactory.cs index 076baa4191f..31ad9e831a2 100644 --- a/src/EFCore.Sqlite.Core/Scaffolding/Internal/SqliteDatabaseModelFactory.cs +++ b/src/EFCore.Sqlite.Core/Scaffolding/Internal/SqliteDatabaseModelFactory.cs @@ -469,22 +469,22 @@ private void ParseClrDefaults(DatabaseTable table) column.DefaultValue = guid; } else if (type == typeof(DateTime) - && DateTime.TryParse(defaultValueSql, out var dateTime)) + && DateTime.TryParse(defaultValueSql, CultureInfo.InvariantCulture, out var dateTime)) { column.DefaultValue = dateTime; } else if (type == typeof(DateOnly) - && DateOnly.TryParse(defaultValueSql, out var dateOnly)) + && DateOnly.TryParse(defaultValueSql, CultureInfo.InvariantCulture, out var dateOnly)) { column.DefaultValue = dateOnly; } else if (type == typeof(TimeOnly) - && TimeOnly.TryParse(defaultValueSql, out var timeOnly)) + && TimeOnly.TryParse(defaultValueSql, CultureInfo.InvariantCulture, out var timeOnly)) { column.DefaultValue = timeOnly; } else if (type == typeof(DateTimeOffset) - && DateTimeOffset.TryParse(defaultValueSql, out var dateTimeOffset)) + && DateTimeOffset.TryParse(defaultValueSql, CultureInfo.InvariantCulture, out var dateTimeOffset)) { column.DefaultValue = dateTimeOffset; } diff --git a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/SqlServerDatabaseModelFactoryTest.cs b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/SqlServerDatabaseModelFactoryTest.cs index 488e7cad853..e27a956f145 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/SqlServerDatabaseModelFactoryTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/SqlServerDatabaseModelFactoryTest.cs @@ -4482,6 +4482,43 @@ B datetimeoffset DEFAULT (CONVERT([datetimeoffset],('1973-09-03T01:02:03'))), }, "DROP TABLE MyTable;"); + [Fact] + public void Simple_date_literals_are_parsed_for_HasDefaultValue_with_Persian_locale() + { + var currentCulture = CultureInfo.CurrentCulture; + + try + { + CultureInfo.CurrentCulture = new CultureInfo("fa-IR"); + Test( + @" +CREATE TABLE MyTable ( + Id int, + A datetime2 DEFAULT (1968-10-23), + B date DEFAULT (1968-10-23), +);", + [], + [], + (dbModel, scaffoldingFactory) => + { + var columns = dbModel.Tables.Single().Columns; + + var column = columns.Single(c => c.Name == "A"); + Assert.Equal("(1968-10-23)", column.DefaultValueSql); + Assert.Equal(new DateTime(1968, 10, 23, 0, 0, 0, 0, DateTimeKind.Unspecified), column.DefaultValue); + + column = columns.Single(c => c.Name == "B"); + Assert.Equal("(1968-10-23)", column.DefaultValueSql); + Assert.Equal(new DateOnly(1968, 10, 23), column.DefaultValue); + }, + "DROP TABLE MyTable;"); + } + finally + { + CultureInfo.CurrentCulture = currentCulture; + } + } + [Fact] public void Simple_Guid_literals_are_parsed_for_HasDefaultValue() => Test( diff --git a/test/EFCore.Sqlite.FunctionalTests/Scaffolding/SqliteDatabaseModelFactoryTest.cs b/test/EFCore.Sqlite.FunctionalTests/Scaffolding/SqliteDatabaseModelFactoryTest.cs index 2d4fe1a5519..2fd892186ea 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Scaffolding/SqliteDatabaseModelFactoryTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Scaffolding/SqliteDatabaseModelFactoryTest.cs @@ -990,6 +990,52 @@ CREATE TABLE MyTable ( }, "DROP TABLE MyTable;"); + [Fact] + public void Simple_date_and_time_literals_are_parsed_for_HasDefaultValue_with_Persian_locale() + { + var culture = CultureInfo.CurrentCulture; + + try + { + CultureInfo.CurrentCulture = new CultureInfo("fa-IR"); + + Test( + @" +CREATE TABLE MyTable ( + Id int, + A datetime DEFAULT (1968-10-23 12:00:01), + B date DEFAULT (1968-10-23), + C datetimeoffset DEFAULT (1973-09-03 12:00:01+10:00)); + +INSERT INTO MyTable VALUES (1, 2023-01-20 13:37:00, 2023-01-20, 1973-09-03 12:00:01+10:00);", + [], + [], + dbModel => + { + var columns = dbModel.Tables.Single().Columns; + + var column = columns.Single(c => c.Name == "A"); + Assert.Equal("1968-10-23 12:00:01", column.DefaultValueSql); + Assert.Equal(new DateTime(1968, 10, 23, 12, 0, 1, 0, DateTimeKind.Unspecified), column.DefaultValue); + + column = columns.Single(c => c.Name == "B"); + Assert.Equal("1968-10-23", column.DefaultValueSql); + Assert.Equal(new DateOnly(1968, 10, 23), column.DefaultValue); + + column = columns.Single(c => c.Name == "C"); + Assert.Equal("1973-09-03 12:00:01+10:00", column.DefaultValueSql); + Assert.Equal( + new DateTimeOffset(new DateTime(1973, 9, 3, 12, 0, 1, 0, DateTimeKind.Unspecified), new TimeSpan(0, 10, 0, 0, 0)), + column.DefaultValue); + }, + "DROP TABLE MyTable;"); + } + finally + { + CultureInfo.CurrentCulture = culture; + } + } + [Fact] public void Simple_Guid_literals_are_parsed_for_HasDefaultValue() => Test(