DBUtil设计:c#中的DateTime和DateTimeOffset转sql时应该输出时区信息吗?

问题:

csharp 复制代码
db.Insert<PersonEntity>(new Person
	 { 
	 	CreateTime = DateTime.Now, 
	 	CreateTimeOffset = DateTimeOffset.Now  
	}).ToSql();

这个输出应该是: 不带时区(如:"2026-01-01 02:03:04.567") 还是带时区(如: "2026-01-01 02:03:04.567+08:00"

一、观察 mysql 内部行为

mysql 关注的列类型是:timestamp datetime

  • datetime: mysql存数据到datetime时候,将数据先转换成mysql使用的时区(如果指定了时区),取得时候就原封不动取出来(不带时区)。 注意:mysql 8.0.19及以上才允许insert时加时区;

  • timestamp: mysql存数据的时候,将数据先转换成UTC时间(如果指定了时区则换算,如果没指定时区就认为用的是mysql服务器时区),当取出的时候再转换成mysql使用的时区(输出结果中不带时区)。

看测试代码:

sql 复制代码
-- 设置会话级时区为东九区
SET time_zone = '+09:00';
SELECT @@session.time_zone;

drop table if exists test
create table test(
	t_datetime  datetime, 
	t_timestamp  timestamp 
)
insert into test(t_datetime,t_timestamp) values
	('2022-02-11 09:30:00+08:00','2022-02-11 09:30:00+08:00'),
	('2022-02-11 09:30:00','2022-02-11 09:30:00')
select * from test

执行的结果:

二、观察 sqlserver 内部行为

在sqlserver中可以匹配 DateTime 和 DateTimeOffset 的列类型有 datetime datetime2 datetimeoffset 三个。

前两个不存储时区,而且在接受类似 '2026-01-04 02:03:04+09:00' 数据时是直接丢掉的,不存在存储时转到服务器所在时区,或统一转成utc查询的时候再转成服务器所在时区的逻辑。可以认为,这两个列类型不考虑时区、直接丢弃时区(如果用户指定时区的话)。

参考ai的回答,未验证

datetimeoffset 最容易理解了,完整存储时区。

三、观察 pgsql 内部行为

pgsql关注的列是 timestamp 和 timestamptz 他们的存储规则:

  • timestamp: 不带时区, 写入时如果给了带时区的字符串会直接丢弃,读取的时候直接读,和pgsql服务器的时区没有关系。
  • timestamptz: 带时区信息, 将数据先转换成UTC时间(如果指定了时区则换算,如果没指定时区就认为用的是服务器时区),当取出的时候再转换成服务器使用的时区(输出结果中带时区)。

看测试代码:

sql 复制代码
SET TIME ZONE '-09:00'; -- 设置为东9区, pgsql 用的是 POSIX 规则 ,它和 ISO 正好是反的
select now() - (now() AT TIME ZONE 'UTC') AS utc_offset; -- 得到: 09:00:00
-- SET TIME ZONE 'Asia/Shanghai';
-- SHOW TIME ZONE;

drop table test
create table test(t_timestamp timestamp,t_timestamptz timestamptz)
insert into test(t_timestamp,t_timestamptz) values
	('2022-02-11 09:30:00+08:00','2022-02-11 09:30:00+08:00'),
	('2022-02-11 09:30:00','2022-02-11 09:30:00')
select * from test

效果:

四、观察主流 orm 库的处理原则

先说结论:

  • dapper:原生不提供生成sql能力,无对比性;
  • efcore:强制要求数据参数化,无对比性;
  • freesql:
    • mysql下:DateTime转成sql不带时区,直接不支持DateTimeOffset
    • sqlserver下:DateTime转成sql不带时区,DateTimeOffset转成sql带时区
  • sqlsugar:
    • mysql下:DateTime转成sql不带时区,DateTimeOffset转成sql不带时区
    • sqlserver下:DateTime转成sql不带时区,DateTimeOffset转成sql不带时区

测试的代码如下:

xml 复制代码
<Project Sdk="Microsoft.NET.Sdk">
	<PropertyGroup>
		<OutputType>Exe</OutputType>
		<TargetFramework>net8.0</TargetFramework>
		<ImplicitUsings>enable</ImplicitUsings>
		<Nullable>enable</Nullable>
	</PropertyGroup>
	<ItemGroup>
		<PackageReference Include="FreeSql.Provider.MySqlConnector" Version="3.5.305" />
		<PackageReference Include="FreeSql.Provider.SqlServer" Version="3.5.305" />
		<PackageReference Include="SqlSugarCore" Version="5.1.4.211" />
	</ItemGroup>
</Project>
csharp 复制代码
using SqlSugar;

namespace ConsoleApp1
{
    public class Person
    {
        public DateTime CreateTime { get; set; }
        public DateTimeOffset CreateTimeOffset { get; set; }
    }
    internal class Program
    {
        private static DateTime local = DateTime.Now;                      // Kind = Local
        private static DateTime utc = DateTime.UtcNow;                    // Kind = Utc
        private static DateTime unspecified = DateTime.Parse("2026-01-04 02:03:04"); // Unspecified

        private static DateTimeOffset local_offset = DateTimeOffset.Now;
        private static DateTimeOffset utc_offset = DateTimeOffset.UtcNow;
        private static DateTimeOffset other_offset = DateTimeOffset.Parse("2026-01-04 02:03:04+09:00");

        public static void Main()
        {
            FreeSqlMySql();
            FreeSqlSqlServer();

            SqlSugarMySql();
            SqlSugarSqlServer();
        }

        private static void SqlSugarSqlServer()
        {
            var db = new SqlSugarClient(new ConnectionConfig
            {
                ConnectionString = "Data Source=.;Initial Catalog=test;User ID=sa;Password=123456;",
                DbType = DbType.Sqlite,
            });
            var sql = db.Insertable(new[]
            {
                new Person { CreateTime = local, CreateTimeOffset = local_offset  },
                new Person { CreateTime = utc , CreateTimeOffset = utc_offset },
                new Person { CreateTime = unspecified , CreateTimeOffset = other_offset }
            })
            .ToSqlString();
            Console.WriteLine("--------------SqlSugar-SqlSever------------------");
            Console.WriteLine(sql);
            Console.WriteLine("=================================");
            Console.WriteLine();
        }

        private static void SqlSugarMySql()
        {
            var db = new SqlSugarClient(new ConnectionConfig
            {
                ConnectionString = "Server=127.0.0.1;Database=test;Uid=root;Pwd=123456;",
                DbType = DbType.MySql,
            });
            var sql = db.Insertable(new[]
            {
                        new Person { CreateTime = local, CreateTimeOffset = local_offset  },
                        new Person { CreateTime = utc , CreateTimeOffset = utc_offset },
                        new Person { CreateTime = unspecified , CreateTimeOffset = other_offset }
                    })
            .ToSqlString();
            Console.WriteLine("--------------SqlSugar-MySql------------------");
            Console.WriteLine(sql);
            Console.WriteLine("=================================");
            Console.WriteLine();
        }

        private static void FreeSqlSqlServer()
        {
            var orm = new FreeSql.FreeSqlBuilder().UseConnectionString(FreeSql.DataType.SqlServer, "Data Source=.;Initial Catalog=test;User ID=sa;Password=123456;")
                .UseNoneCommandParameter(true)
                .Build();
            var sql = orm.Insert<Person>()
                .AppendData(new Person { CreateTime = local, CreateTimeOffset = local_offset })
                .AppendData(new Person { CreateTime = utc, CreateTimeOffset = utc_offset })
                .AppendData(new Person { CreateTime = unspecified, CreateTimeOffset = other_offset })
                .ToSql();
            Console.WriteLine("--------------freesql-SqlServer------------------");
            Console.WriteLine(sql);
            Console.WriteLine("=================================");
            Console.WriteLine();
        }

        private static void FreeSqlMySql()
        {
            var orm = new FreeSql.FreeSqlBuilder().UseConnectionString(FreeSql.DataType.MySql, "Server=127.0.0.1;Database=test;Uid=root;Pwd=123456;")
                .UseNoneCommandParameter(true)
                .Build();
            var sql = orm.Insert<Person>()
                .InsertColumns(i => new { i.CreateTime })//只能插入 DateTime
                .AppendData(new Person { CreateTime = local, CreateTimeOffset = local_offset })
                .AppendData(new Person { CreateTime = utc, CreateTimeOffset = utc_offset })
                .AppendData(new Person { CreateTime = unspecified, CreateTimeOffset = other_offset })
                .ToSql();
            Console.WriteLine("--------------freesql-MySql------------------");
            Console.WriteLine(sql);
            Console.WriteLine("=================================");
            Console.WriteLine();
        }
    }
}

测试效果:

五、最后总结:

C# 中的 DateTime 转 sql 不应该输出时区信息, 直接舍弃掉即可

C# 中的 DateTimeOffset 转 sql 应该始终输出时区信息

给出整理的表格:

text 复制代码
SessionTimeZone(+09:00)                                                                                                                                                                                                                                                                                           
																																																																													
MySql =========================================================================================================================================================================================================================================================================================================== 
                               |                                         |                                         |                                      |                              |                                     |                                     |                                          | 
mysql datetime                 |      User-provided has timezone         |                                         |     Convert To SessionTimeZone       |         trim timezone        |      Internal Storage Format        |          read direct                |            final return                  | 
(no timezone)                  |      '2022-02-11 09:30:00+08:00'        |                                         |       +01:00 (09:00 - 08:00)         |                              |       '2022-02-11 10:30:00'         |                                     |        '2022-02-11 10:30:00'             | 
                               |                                         |                                         |                                      |                              |                                     |                                     |                                          | 
                               |                                         |                                         |                                      |                              |                                     |                                     |                                          | 
mysql datetime                 |      User-provided no timezone          |                                         |                                      |                              |      Internal Storage Format        |          read direct                |            final return                  | 
(no timezone)                  |       '2022-02-11 09:30:00'             |                                         |                                      |                              |        '2022-02-11 09:30:00'        |                                     |        '2022-02-11 09:30:00'             | 
                               |                                         |                                         |                                      |                              |                                     |                                     |                                          | 
-------------------------------|-----------------------------------------|-----------------------------------------|--------------------------------------|------------------------------|-------------------------------------|-------------------------------------|------------------------------------------| 
                               |                                         |                                         |                                      |                              |                                     |                                     |                                          | 
mysql timestamp                |      User-provided has timezone         |                                         |     Convert To UTC TimeZone zero     |          trim timezone       |      Internal Storage Format        |   Read-Convert To SessionTimeZone   |            final return                  | 
(Internal Storage:utc zero)    |       '2022-02-11 09:30:00+08:00'       |                                         |           -08:00                     |                              |        '2022-02-11 01:30:00Z'       |            +09:00                   |        '2022-02-11 10:30:00'             | 
                               |                                         |                                         |                                      |                              |                                     |                                     |                                          | 
mysql timestamp                |      User-provided no timezone          |         Regard as SessionTimeZone       |      Convert To UTC TimeZone zero    |          trim timezone       |      Internal Storage Format        |   Read-Convert To SessionTimeZone   |            final return                  | 
(Internal Storage:utc zero)    |       '2022-02-11 09:30:00'             |      '2022-02-11 09:30:00+09:00'        |           -09:00                     |                              |        '2022-02-11 00:30:00Z'       |             +09:00                  |        '2022-02-11 09:30:00'             | 
                               |                                         |                                         |                                      |                              |                                     |                                     |                                          | 
SqlServer =====================|=========================================|=========================================|======================================|==============================|=====================================|=====================================|==========================================| 
                               |                                         |                                         |                                      |                              |                                     |                                     |                                          | 
sqlserver datetime/datetime2   |      User-provided has timezone         |                                         |                                      |          trim timezone       |      Internal Storage Format        |          read direct                |            final return                  | 
(no timezone)                  |       '2022-02-11 09:30:00+08:00'       |                                         |                                      |                              |        '2022-02-11 09:30:00'        |                                     |        '2022-02-11 09:30:00'             | 
                               |                                         |                                         |                                      |                              |                                     |                                     |                                          | 
sqlserver datetime/datetime2   |      User-provided no timezone          |                                         |                                      |                              |      Internal Storage Format        |          read direct                |            final return                  | 
(no timezone)                  |       '2022-02-11 09:30:00'             |                                         |                                      |                              |        '2022-02-11 09:30:00'        |                                     |        '2022-02-11 09:30:00'             | 
                               |                                         |                                         |                                      |                              |                                     |                                     |                                          | 
-------------------------------|-----------------------------------------|-----------------------------------------|--------------------------------------|------------------------------|-------------------------------------|-------------------------------------|------------------------------------------| 
                               |                                         |                                         |                                      |                              |                                     |                                     |                                          | 
sqlserver datetimeoffset       |      User-provided has timezone         |                                         |                                      |                              |      Internal Storage Format        |          read direct                |            final return                  | 
(store timezone)               |        '2022-02-11 09:30:00+08:00'      |                                         |                                      |                              |        '2022-02-11 09:30:00+08:00'  |                                     |        '2022-02-11 09:30:00+08:00'       | 
                               |                                         |                                         |                                      |                              |                                     |                                     |                                          | 
sqlserver datetimeoffset       |      User-provided no timezone          |          Regard as ServerTimeZone       |                                      |                              |      Internal Storage Format        |          read direct                |            final return                  | 
(store timezone)               |        '2022-02-11 09:30:00'            |       '2022-02-11 09:30:00+09:00'       |                                      |                              |        '2022-02-11 09:30:00+09:00'  |                                     |        '2022-02-11 09:30:00+09:00'       | 
                               |                                         |                                         |                                      |                              |                                     |                                     |                                          | 
PgSql =========================|=========================================|=========================================|======================================|==============================|=====================================|=====================================|==========================================| 
                               |                                         |                                         |                                      |                              |                                     |                                     |                                          | 
pgsql timestamp                |      User-provided has timezone         |                                         |                                      |          trim timezone       |      Internal Storage Format        |          read direct                |            final return                  | 
(no timezone)                  |        '2022-02-11 09:30:00+08:00'      |                                         |                                      |                              |        '2022-02-11 09:30:00'        |                                     |        '2022-02-11 09:30:00'             | 
                               |                                         |                                         |                                      |                              |                                     |                                     |                                          | 
pgsql timestamp                |      User-provided no timezone          |                                         |                                      |                              |      Internal Storage Format        |          read direct                |            final return                  | 
(no timezone)                  |        '2022-02-11 09:30:00'            |                                         |                                      |                              |        '2022-02-11 09:30:00'        |                                     |        '2022-02-11 09:30:00'             | 
                               |                                         |                                         |                                      |                              |                                     |                                     |                                          | 
-------------------------------|-----------------------------------------|-----------------------------------------|--------------------------------------|------------------------------|-------------------------------------|-------------------------------------|------------------------------------------| 
                               |                                         |                                         |                                      |                              |                                     |                                     |                                          | 
pgsql timestamptz              |        User-provided has timezone       |                                         |      Convert To UTC TimeZone zero    |          trim timezone       |      Internal Storage Format        |    Read-Convert To SessionTimeZone  |            final return                  | 
(Internal Storage:utc zero)    |         '2022-02-11 09:30:00+08:00'     |                                         |          -08:00                      |                              |        '2022-02-11 01:30:00Z'       |           +09:00                    |        '2022-02-11 10:30:00+09:00'       | 
                               |                                         |                                         |                                      |                              |                                     |                                     |                                          | 
pgsql timestamptz              |        User-provided no timezone        |             Regard as SessionTimeZone   |       Convert To UTC TimeZone zero   |          trim timezone       |      Internal Storage Format        |    Read-Convert To SessionTimeZone  |            final return                  | 
(Internal Storage:utc zero)    |         '2022-02-11 09:30:00'           |          '2022-02-11 09:30:00+09:00'    |          -09:00                      |                              |        '2022-02-11 00:30:00Z'       |           +09:00                    |        '2022-02-11 09:30:00+09:00'       | 
                               |                                         |                                         |                                      |                              |                                     |                                     |                                          | 
=========================================================================|======================================================================================================================================================================================================================================= 
相关推荐
拾起零碎2 小时前
U8/不同账套总账期初余额对账
sql
hoiii1872 小时前
使用C#实现文本转语音(TTS)及多音频合并
c#·音视频·语音识别
jiayong232 小时前
Word核心功能完全指南
c#·word·xhtml
摘星编程2 小时前
React Native for OpenHarmony 实战:ToastAndroid 安卓提示详解
android·react native·react.js
brave_zhao2 小时前
关闭 SpringBoot+javaFX混搭程序的最佳实践
spring boot·后端·sql
麦聪聊数据2 小时前
后端不再是瓶颈:如何通过“API 编排协作”重塑数据交付流程?
数据库·sql·mysql
莫叫石榴姐2 小时前
用SQL实现三次指数平滑预测:递归与非递归两种解法详解
大数据·数据库·sql
peachSoda72 小时前
使用HBuilderX 自带hbuilderx-cli 自动化打包uniapp的移动端app(Android,iOS)
android·uni-app·自动化
我的炸串拌饼店2 小时前
C# 邮件发送与附件处理详解
开发语言·网络·c#