五大关系数据库(sqlserver、mysql、oracle、pgsql、sqlite)如何结合c#存取时间

前言:这里讨论的内容即是 《DBUtil》 中的时间的实现

一、在c#中相关的基础时间类型有(简称五大时间类型):

  • 1.1 DateTime
  • 1.2 DateTimeOffset
  • 1.3 DateOnly
  • 1.4 TimeOnly
  • 1.5 TimeSpan

其中,DateTimeDateTimeOffset 可以记录时区信息, 其他的都和时区没关系。

DateTime 记录时区的方式,是通过一个 Kind 字段区分三种情况:UTC时区, 本地时区, 未指定时区

DateTimeOffset 记录了具体偏移量(属性:Offset)

它们的默认json序列化示例如:

json 复制代码
{
  "Id": 1,
  "DateTime": "2025-11-26T20:11:26.1358717+08:00",
  "DateTimeOffset": "2025-11-26T20:11:26.1429332+08:00",
  "DateOnly": "2025-11-26",
  "TimeOnly": "20:11:26.1437892",  
  "TimeSpan": "1.02:03:05.6780000"
}

我们可以指定格式化字符串以达到和json序列化相同的目的(注意: 虽然毫秒部分是7位,但db中一般都支持到6位,超出的自动截断):

csharp 复制代码
var str = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.FFFFFFFzzz");
//"2025-11-29T10:51:10.7098014+08:00"
var str2 = DateTimeOffset.Now.ToString("yyyy-MM-ddTHH:mm:ss.FFFFFFFzzz");
//"2025-11-29T10:51:10.7098967+08:00"
var str3 = DateOnly.FromDateTime(DateTime.Now).ToString("yyyy-MM-dd");
//"2025-11-29"
var str4 = TimeOnly.FromDateTime(DateTime.Now).ToString("HH:mm:ss.FFFFFFF");
//"10:51:10.7099133"
var str5 = (DateTime.Parse("2000-01-01T01:02:03.456789+08:00") - DateTime.Parse("1900-01-01T00:00:00+08:00")).ToString();
//"36524.01:02:03.4567890"

二、明确探讨的存取问题

接下来我们要探讨的是:

  • 写入时,如何将 c# 中的五大时间类型翻译成sql 如:我们有一个类 public class Person { prop DateTime CreateTime } 那么我们如何将它生成sql
  • 读取时,如何从 db驱动中读取数据翻译到 c# 中的五大时间类型 如:我们使用驱动(如:pgsql 的 npgsql, mysql 的MySqlConnector)读取到的数据是什么样的,怎么映射到 c# 中 的五大时间类型(或者不需要映射)

三、统一的时间格式存储之字符串

这是一个通用的存储方法(对 sqlite 来说是主要的存储方法)。

这种方法是明确知道 db 中以字符串存储的时间。

怎么才算明确知道呢?

比如:用户的类是这样定义的

csharp 复制代码
public class Person
{
	[Column(TypeName = "varchar(50)")]
	public DateTime CreateTime { set; get; }
}

或者是当前操作的db为sqlite(sqlite中没有专门的时间类型,只能默认认为存储为字符串)。

3.1 写入时,如何生成sql

考虑到存进去后的时间还可能要参与排序,所以需要一个统一的便于排序的格式,而不能是随便定义的。

比如:

  • 不能是一会存储为 2020-01-02 03:04:05 一会存储为 2020-01-02T03:04:05+08:00
  • 也不能毫秒部分是可省略的格式,如:yyyy-MM-dd HH:mm:ss.FFFzzz 下面两个一会有毫秒一会没有毫秒,不能参与排序
    csharp 复制代码
    var str = DateTime.Parse("2025-02-01 01:02:03+08:00").ToString("yyyy-MM-dd HH:mm:ss.FFFzzz");
    //2025-02-01 01:02:03+08:00
    var str2 = DateTime.Parse("2025-02-01 01:02:03.456+08:00").ToString("yyyy-MM-dd HH:mm:ss.FFFzzz");
    //2025-02-01 01:02:03.456+08:00

先给一个默认的格式,然后再允许用户指定格式应该可以满足要求。

默认格式,可以设定如下:

  • DateTime/DateTimeOffset: yyyy-MM-ddTHH:mm:ss.fffffffzzz
  • DateOnly: yyyy-MM-dd
  • TimeOnly: HH:mm:ss.fffffff
  • TimeSpan: 直接用c#默认的,即什么也不传,输出示例:36524.01:02:03.4567890 注意: TimeSpan 的 C# 默认格式,是没办法进行排序的,除非所有的数据都 超不过一天,所以,TimeSpan 的默认db类型不应该是 string。

如果用户指定了格式,使用即可,用户可以通过如下方式指定:

csharp 复制代码
public class Person
{
    //首先声明 db 中使用字符串存储,然后使用指定的格式覆盖默认的格式
	[TimeFormat("yyyy-MM-dd")]
	[Column(TypeName="varchar(50)")]
    public CreateTime DateTime { get; set; }
}

3.2 读取时,如何从字符串反射生成c#五大基本数据类型

so easy。。。

四、统一的时间存储格式之数字

这是一个通用的存储方法(对 sqlite 来说是主要的存储方法)。

这种方法是明确知道 db 中以数字(整型或浮点型)存储的时间(即:Unix 时间戳, UTC时间相对于epoch(1970-01-01)的秒数)。

怎么才算明确知道呢?

比如:用户的类是这样定义的

csharp 复制代码
public class Person
{
	[Column(TypeName = "decimal(18,7)")]
	[Column(TypeName = "bigint")]
	public DateTime CreateTime { set; get; }
}

注意:

  • 如果声明db中是 int, 那么就到秒数;
  • 如果声明db中是 float,那么整数部分是秒数,小数部分是毫秒数;

4.1 写入时,如何生成sql

下面附上c#五大时间类型转数字的方法

csharp 复制代码
  //c# 转换方法
using DotNetCommon.Extensions;

//DateTime => int/float
var p_datetime_int = DateTime.Now.ToEpochSeconds();
var p_datetime_float = DateTime.Now.ToEpochSecondsV2();

//DateTimeOffset => int/float
var p_datetimeoffset_int = DateTimeOffset.Now.LocalDateTime.ToEpochSeconds();
var p_datetimeoffset_float = DateTimeOffset.Now.LocalDateTime.ToEpochSecondsV2();

//DateOnly => int
//DateOnly 转float没意义
var p_dateonly_int = DateOnly.FromDateTime(DateTime.Now).ToDateTime(TimeOnly.MinValue).ToEpochSeconds();

//TimeOnly => int/float
var timeonlyTimeSpan = TimeOnly.FromDateTime(DateTime.Now).ToTimeSpan();
var p_timeonly_int = (long)timeonlyTimeSpan.TotalSeconds;
var p_timeonly_float = timeonlyTimeSpan.TotalSeconds;

//TimeSpan => int/float
var timespan = (DateTime.Parse("2000-01-01T01:02:03.456789+08:00") - DateTime.Parse("1900-01-01T00:00:00+08:00"));
var p_timespan_int = (long)timespan.TotalSeconds;
var p_timespan_float = timespan.TotalSeconds;

4.2 读取时,如何从字符串反射生成c#五大基本数据类型

so easy。。。

五、各个数据库的存储适配

2.1 mysql

a. 存取为普通字符串 (同 《统一的时间格式存储之字符串》)

b. 存取为 int/float(同 《统一的时间存储格式之数字》)

c. 存取为db时间类型

c.1 将 c# 中的 DateTime or DateTimeOffset 存储到db中时
  • 当已知db中的列类型为: date 时,使用 yyyy-MM-dd 格式
  • 其他,统一使用 yyyy-MM-ddTHH:mm:ss.fffffffzzz

示例如下:

sql 复制代码
drop table if exists test
create table test(
	id int auto_increment primary key,
	p_datetime datetime,
	p_datetime6 datetime(6),
	p_timestamp timestamp,
	p_timestamp6 timestamp(6),
	p_date date -- 以为着放弃了时间部分
)

insert into test(p_datetime,p_datetime6,p_timestamp,p_timestamp6,p_date)
values('2025-11-26T20:11:26.1358717+08:00','2025-11-26T20:11:26.1358717+08:00','2025-11-26T20:11:26.1358717+08:00','2025-11-26T20:11:26.1358717+08:00','2025-11-26')

select * from test

执行结果:

读取时用c#解析,easy。

c.2 将 c# 的 DateOnly 存储到db时

直接使用 yyyy-MM-dd 即可

c.3 将 c# 的 TimeOnly or TimeSpan 存储到db时

只能认为db时 time 类型,示例如下:

sql 复制代码
drop table if exists test
create table test(
	id int auto_increment primary key,
	p_timeonly time,
	p_timeonly6 time(6),
	p_timespan time,
	p_timespan6 time(6)
)

insert into test(p_timeonly,p_timeonly6,p_timespan,p_timespan6)
values('48:01:02.123456','48:01:02.123456','48:01:02.123456','48:01:02.123456')

执行结果:

注意:

  • 从c#生成sql时:
    TimeOnly 生成sql使用 HH:mm:ss.fffffff, TimeSpan则使用如下:

    csharp 复制代码
    var timeSpan = new TimeSpan(2, 0, 1, 2, 123, 456);
    var arr = timeSpan.TotalSeconds.ToString().Split('.');
    var precision = arr.Length > 1 ? '.' + arr.Last() : "";
    var str = $"{timeSpan.Days * 24 + timeSpan.Hours}:{timeSpan.Minutes.ToString().PadLeft(2, '0')}:{timeSpan.Seconds.ToString().PadLeft(2, '0')}{precision}";
    //48:01:02.123456
  • 从 db 读取解析时, 驱动读取后为 TimeSpan,如果c#中是 TimeOnly 的话,直接将 TimeSpan 的天数给舍弃转成 TimeOnly 即可。

2.2 sqlite

因为 sqlite 本身没有专门的时间列类型,所以基本上只有两种方案。

a. 存取为普通字符串 (同 《统一的时间格式存储之字符串》)

b. 存取为 int/float(同 《统一的时间存储格式之数字》)

2.3 pgsql

a. 存取为普通字符串 (同 《统一的时间格式存储之字符串》)

b. 存取为 int/float(同 《统一的时间存储格式之数字》)

c. 存取为db时间类型

c.1 将 c# 中的 DateTime or DateTimeOffset 存储到db中时
  • 当已知db中的列类型为: date 时,使用 yyyy-MM-dd 格式
  • 其他,统一使用 yyyy-MM-ddTHH:mm:ss.fffffffzzz

示例如下:

sql 复制代码
drop table if exists test
create table test(
	id serial primary key,
	p_timestamp timestamp,
	p_timestamp6 timestamp(6),
	p_date date -- 认为着放弃了时间部分
)

insert into test(p_timestamp,p_timestamp6,p_date)
values('2025-11-26T20:11:26.1358717+08:00','2025-11-26T20:11:26.1358717+08:00','2025-11-26')

select * from test

执行结果:

温馨提示:如果你查询的结果时间的小数部分只有三位(总之不够6位)那么可能时客户端的问题,在 DBEaver中如下配置:

在c#中使用 Npgsql 驱动时是没这个问题的。

c.2 将 c# 的 DateOnly 存储到db时

直接使用 yyyy-MM-dd 即可

c.3 将 c# 的 TimeOnly 存储到db时

只能认为db时 time 类型,示例如下:

sql 复制代码
drop table if exists test
create table test(
	id serial primary key,
	p_timeonly time,
	p_timeonly6 time(6),
	p_timespan time,
	p_timespan6 time(6)
)

insert into test(p_timeonly,p_timeonly6,p_timespan,p_timespan6)
values('01:01:02.123456','01:01:02.123456','12:01:02.123456','12:01:02.123456')

select * from test

执行结果:

温馨提示:如果发现输出的时间没有小数部分或精度不够参考上面 "c.1 将 c# 中的 DateTime or DateTimeOffset 存储到db中时" 中的提示。

c.4 将 c# 的 TimeSpan 存储到db时

此时只能认为 db 中的类型是 interval 类型,如下所示:

sql 复制代码
drop table if exists test
create table test(
	id serial primary key,
	p_timespan INTERVAL DAY TO second,
	p_timespan6 INTERVAL DAY TO second(6)
)
insert into test(p_timespan,p_timespan6)
values('2.04:05:06','2.04:05:06.1234567')

select * from test

执行结果:

c.5 存储 pgsql 中的时间范围

此处只讨论读写,下面先附上 chatgpt 的一个解释:

使用示例:

sql 复制代码
drop table if exists test
create table test(
   	id serial primary key,
   	ts  tsrange,
    tsz tstzrange,
    dr  daterange
)

insert into test(ts,tsz,dr) values(
    '[2025-01-01T10:00:00.1234567+08:00,2025-01-01T12:00:00.1234567+08:00)',
    '[2025-01-01T10:00:00.1234567+08:00,2025-01-01T12:00:00.1234567+08:00)',
    '[2025-01-01,2025-01-02]'
)

select * from test

执行结果:

那么如何在c#中接收呢?直接使用 npgsql 中定义的数据类型即可,如:

csharp 复制代码
public class Person
{
    public NpgsqlTypes.NpgsqlRange<DateTime> Ts { get; set; }
    public NpgsqlTypes.NpgsqlRange<DateTime> Tsz { get; set; }
    public NpgsqlTypes.NpgsqlRange<DateTime> Dr { get; set; }
}

npgsql 驱动读取 reader 的时候,就已经是 NpgsqlRange<> 类型了,只需要生成sql的时候转换成对应的字符串就行了,还有的问题是?当使用 BullkCopy 时 给 npgsql 的 datatable 的 数据应该是什么?字符串还是 NpgsqlRange<>

2.4 sqlsever

a. 存取为普通字符串 (同 《统一的时间格式存储之字符串》)

b. 存取为 int/float(同 《统一的时间存储格式之数字》)

c. 存取为db时间类型

c.1 将 c# 中的 DateTime 存储到db中时
  • 当已知db中的列类型为: date 时,使用 yyyy-MM-dd 格式
  • 其他,统一使用 yyyy-MM-dd HH:mm:ss.fff
    示例如下:
sql 复制代码
-- drop table  test
create table test(
	id int identity(1,1) primary key,
	p_datetime datetime,
	p_smalldatetime smalldatetime,
	p_datetime2 datetime2,
	p_datetime2_7 datetime2(7),
	p_datetimeoffset datetimeoffset,
	p_datetimeoffset7 datetimeoffset(7),
	p_date date -- 认为着放弃了时间部分
)

insert into test(
  p_datetime,
  p_smalldatetime,
  p_datetime2,p_datetime2_7,
  p_datetimeoffset,p_datetimeoffset7,
  p_date
)
values(
  '2025-11-26 20:11:26.123', -- 带时区 或者 多一位小数就会报转换错误 Conversion failed when converting date and/or time from character string.
  '2025-11-26 20:11:26.123', -- 带时区 或者 多一位小数就会报转换错误
  '2025-11-26T20:11:26.1234567+08:00','2025-11-26T20:11:26.1234567+08:00',
  '2025-11-26T20:11:26.1234567+08:00','2025-11-26T20:11:26.1234567+08:00',
  '2025-11-26'
)

select * from test

执行结果:

因为对于 db 中的 datetimesmalldatetime 极易发生截断异常,所以只能将 DateTime 格式化为: yyyy-MM-dd HH:mm:ss.fff

c.2 将 c# 的 DateTimeOffset 存储到db时

这个就不像存 DateTime 那么小心了, 可以直接格式化为: yyyy-MM-ddTHH:mm:ss.fffffffzzz

如下:

sql 复制代码
-- drop table  test
create table test(
	id int identity(1,1) primary key,
	p_datetime2 datetime2,
	p_datetime2_7 datetime2(7),
	p_datetimeoffset datetimeoffset,
	p_datetimeoffset7 datetimeoffset(7)
)

insert into test(
  p_datetime2,p_datetime2_7,
  p_datetimeoffset,p_datetimeoffset7
)
values(
  '2025-11-26T20:11:26.1234567+08:00','2025-11-26T20:11:26.1234567+08:00',
  '2025-11-26T20:11:26.1234567+08:00','2025-11-26T20:11:26.1234567+08:00'
)

select * from test

执行结果:

c.3 将 c# 的 DateOnly 存储到db时

直接使用 yyyy-MM-dd 即可

c.4 将 c# 的 TimeOnly 存储到db时

只能认为db时 time 类型,示例如下:

sql 复制代码
-- drop table  test
create table test(
	id int identity(1,1) primary key,
	p_timeonly time,
	p_timeonly6 time(6)
)

insert into test(p_timeonly,p_timeonly6)
values('12:01:02.123456','12:01:02.123456')

select * from test

执行结果:

c.5 将 c# 的 TimeSpan 存储到db时

因为 sqlserver 中并没有专门的 时间段 类型,所以只能存储为 int or float

2.5 oracle

a. 存取为普通字符串 (同 《统一的时间格式存储之字符串》)

b. 存取为 int/float(同 《统一的时间存储格式之数字》)

c. 存取为db时间类型

先附上chatgpt关于oracle时间列类型的介绍

c.1 将 c# 的 DateTime or DateTimeOffset 存储到db时

使用格式: yyyy-MM-dd HH:mm:ss.fffffff zzz 且指定 timestamp 前缀,如下:

sql 复制代码
drop table test
CREATE TABLE test (
    p_date DATE,
    p_timestamp timestamp,
    p_timestamp7 timestamp(7),
    p_timestampts TIMESTAMP WITH TIME ZONE,
    p_timestampts_7 TIMESTAMP(7) WITH TIME ZONE,
    p_timestampts_local TIMESTAMP WITH LOCAL TIME ZONE,
    p_timestampts_local_7 TIMESTAMP(7) WITH LOCAL TIME ZONE
);

insert into test(
    p_date,
    p_timestamp,
    p_timestamp7,
    p_timestampts,
    p_timestampts_7,
    p_timestampts_local,
    p_timestampts_local_7
) values
(
    TIMESTAMP '2025-11-26 20:11:26.1234567 +08:00',-- 可以 且自动截断
    TIMESTAMP '2025-11-26 20:11:26.1234567 +08:00',
    TIMESTAMP '2025-11-26 20:11:26.1234567 +08:00',
    TIMESTAMP '2025-11-26 20:11:26.1234567 +08:00',
    TIMESTAMP '2025-11-26 20:11:26.1234567 +08:00',
    TIMESTAMP '2025-11-26 20:11:26.1234567 +08:00',
    TIMESTAMP '2025-11-26 20:11:26.1234567 +08:00'
)

select * from test

执行结果:

c.2 将 c# 中的 DateOnly 存储到db时

直接使用 yyyy-MM-dd 即可

c.3 将 c# 中的 TimeOnly 存储到db时

因为 oracle 中没有专门的 time 类型,所以存储时,取一个固定的日期,如:'1970-01-01', 示例如下:

sql 复制代码
drop table test
CREATE TABLE test (
    p_time DATE,
    p_time2 TIMESTAMP(7)
);

insert into test(p_time,p_time2) values(
    TIMESTAMP '1970-01-01 20:11:26.1234567', -- 会自行截断
    TIMESTAMP '1970-01-01 20:11:26.1234567'
)

select * from test

执行结果:

c.4 将 c# 的 TimeSpan 存储到db时

认为db中的存储类型是: INTERVAL DAYTO SECOND,示例如下:

sql 复制代码
drop table test
CREATE TABLE test (
    duration INTERVAL DAY(3) TO SECOND(6)
);

INSERT INTO test(duration) VALUES(INTERVAL '5 12:30:15.123456' DAY TO SECOND);
INSERT INTO test(duration) VALUES(INTERVAL '-5 12:30:15.123456' DAY TO SECOND);
-- INSERT INTO test(duration) VALUES(INTERVAL '5.12:30:15.123456' DAY TO SECOND);-- 会报错: ORA-01867: the interval is invalid

select * from test

执行结果:

注意:oracle天数后面用的是空格,而c#默认用的是 .

相关推荐
不想画图1 小时前
数据库概念和编译安装mysql流程
linux·数据库·mysql
一点事1 小时前
oracle:创建表空间、用户和授权
数据库·oracle
w***95491 小时前
运维实战---多种方式在Linux中部署并初始化MySQL
linux·运维·mysql
q***73551 小时前
三分钟内快速完成MySQL到达梦数据库的迁移
数据库·mysql
卿雪1 小时前
MySQL【基础】篇:什么是MySQL、主键和外键、三大范式、DDL、DML、DDL、DCL...
java·服务器·开发语言·数据库·后端·mysql·golang
6***S2221 小时前
SQL Server查看数据库中每张表的数据量和总数据量
数据库·sql·oracle
s***41132 小时前
MySQL——表操作及查询
android·mysql·adb
可爱又迷人的反派角色“yang”2 小时前
Mysql数据库(一)
运维·服务器·前端·网络·数据库·mysql·nginx
垦***耪2 小时前
SSA-KELM多输入多输出回归预测:基于SSA算法优化KELM的Matlab代码实现,适用于...
mysql