Dynamics 365日期和时间字段

2870 字
14 分钟
Dynamics 365日期和时间字段

概述#

由于 Dataverse 统一以 UTC 时区存储日期时间数据,而 (1)界面显示、(2)Client API 与 (3)Web API 又根据字段配置和用户时区产生不同的行为,这类字段常常成为以下问题的重灾区:

  • 不同用户看到的日期不一致
  • 表单显示正确,但数据库或接口返回 “少了/多了8小时”
  • Client API / Web API 返回的时间与预期不符

本文通过字段配置说明 + 实际实验 + 数据库存储 / API 返回对比的方式,一次性理清这些容易混淆的点

基本概念#

DataverseUTC 时区存储所有日期和时间值,当应用显示值或处理用户输入时,会根据字段的“格式”和“时区调整”选项,结合当前用户的时区设置进行动态转换

基本概念1:仅日期或日期和时间#

在实体新建日期和时间字段时,可以选择以下以下格式

格式描述
仅日期存储时间为 00:00<00>(UTC),界面仅显示日期
日期和时间同时存储并显示日期与时间

选择日期和时间字段的 “格式”
选择日期和时间字段的 “格式”

基本概念2:时区调整#

在“高级选项”中,可以设置“时区调整”,需要注意的是,“时区调整”的选项和“格式”(仅日期或日期和时间)联动,联动逻辑如下

高级选项中的时区调整
高级选项中的时区调整

格式等于“仅日期”#

如果格式选择了“仅日期”,那么“时区调整”将有如下3个选项

选项时区调整描述
1用户当地时间默认值,根据用户时区进行转换
2时区无关不进行时区转换
3仅日期不存储时间部分(始终为 00:00<00>

格式等于“日期和时间”#

如果格式选择了“日期和时间”,那么“时区调整”将有如下2个选项

选项时区调整描述
1用户当地时间默认值,根据用户时区进行转换
2时区无关不进行时区转换
Tip

字段创建完成后,“格式” 和 “时区调整” 在一定条件下仍可修改(详见下文)

基本概念3:已创建字段“格式”和“时区调整”还能修改吗?#

经实际验证,结论如下:

“格式” & “时区调整” “格式”是否可以修改? “时区调整”是否可以修改?
仅日期 & 用户当地时间
仅日期 & 时区无关
仅日期 & 仅日期
日期和时间 & 用户当地时间
日期和时间 & 时区无关

基本概念4:“时区调整”该如何选择?#

用户当地时间#

这是默认选项。字段值会根据每个用户个人设置中的时区进行动态转换。例如,一个销售订单的“创建时间”在伦敦的用户看来是 2024-01-01 12:00:00,而在纽约的用户看来则会显示为 2024-01-01 07:00:00(如果该时间点对应的是同一UTC时刻)。这个选项适用于绝大多数需要记录事件发生时刻,并期望在不同地区用户视角下呈现为当地时间场景,例如会议时间、任务截止日期、操作日志等

时区无关#

选择此选项后,字段值将不进行任何时区转换。无论查看者身处哪个时区,他们在界面上看到的日期和时间值都是完全相同的。数据库存储的值是什么,用户看到的就是什么。这适用于那些本身就不包含时区信息,或不应受时区影响的场景。例如,酒店的入住登记时间(通常以酒店当地时间为准)、健身房的打卡记录(以打卡地当地时间为准)、或者一个全球统一发布的公告的“发布日期和时间”

仅日期#

此选项仅在字段“格式”为“仅日期”时可用。选择后,字段将仅存储日期部分,时间部分固定为 00:00<00>(UTC),并且不进行任何时区转换。所有用户,无论身处何地,都将看到完全相同的日期。适用于生日、结婚纪念日、节日等只关心日期,不关心具体时间和时区的场景

实例#

接下来,我将在 Invoice 实体新建如下5个“日期和时间”字段,然后对它们进行赋值,接着到数据库上看存储情况

Tip
  • 目前我所在的时区是(GMT+08:00) Beijing, Chongqing, Hong Kong, Urumqi
  • 用户 Test06 使用的的时区是 (GMT+04:00) Baku

接下来是具体的“实验”步骤

Step 1. 新建日期和时间字段#

字段清单如下

#名称字段名称格式时区调整
1D - User Localgdh_d_userlocal仅日期用户当地时间
2D - TZ independentgdh_d_tz_independent仅日期时区无关
3D - Dgdh_d_d仅日期仅日期
4DT - User Localgdh_dt_userlocal日期和时间用户当地时间
5DT - TZ independentgdh_dt_tz_independent日期和时间时区无关

Step 2. 在表单中填写日期和时间字段#

(GMT+08:00) Beijing, Chongqing, Hong Kong, Urumqi 时区下,对字段进行赋值

对日期和时间字段进行赋值
对日期和时间字段进行赋值

#名称字段名称填写内容
1D - User Localgdh_d_userlocal2025-03-10
2D - TZ independentgdh_d_tz_independent2025-03-12
3D - Dgdh_d_d2025-03-14
4DT - User Localgdh_dt_userlocal2025-03-10 14:30
5DT - TZ independentgdh_dt_tz_independent2025-03-24 09:30

Step 3. 数据库查看数据#

SELECT gdh_d_userlocal AS 'D - User Local',
gdh_d_tz_independent AS 'D - TZ independent',
gdh_d_d AS 'D - D',
gdh_dt_userlocal AS 'DT - User Local',
gdh_dt_tz_independent AS 'DT - TZ independent'
FROM gdh_invoice
WHERE gdh_no = 'SAS-00000001';

在数据库查看数据
在数据库查看数据

#名称字段名称填写内容数据库存储的值
1D - User Localgdh_d_userlocal2025-03-102025-03-09 16:00:00.000
2D - TZ independentgdh_d_tz_independent2025-03-122025-03-12 00:00:00.000
3D - Dgdh_d_d2025-03-142025-03-14 00:00:00.000
4DT - User Localgdh_dt_userlocal2025-03-10 14:302025-03-10 06:30:00.000
5DT - TZ independentgdh_dt_tz_independent2025-03-24 09:302025-03-24 09:30:00.000

问题:为什么D-User Local在数据库中存储的是2025-03-09 16:00<00>.000#

前面说过,我是在(GMT+08:00) Beijing, Chongqing, Hong Kong, Urumqi时区下填写 2025-03-10,Dataverse 是以UTC时区存储所有日期时间数据,且 “仅日期 + 用户当地时间” 格式的字段会先将用户填写的本地日期转换为 UTC 时间,再存储到数据库

为了转换为 UTC 时间存储,计算逻辑:
= 2025-03-10 00:00:00 (GMT+08:00) - 8 小时
= 2025-03-09 16:00:00 (UTC)

Step 4. 使用 Test06 查看数据#

Tip

用户 Test06 使用的的时区是 (GMT+04:00) Baku

使用 Test06 查看数据
使用 Test06 查看数据

#名称字段名称数据库存储的值看到的值
1D - User Localgdh_d_userlocal2025-03-09 16:00:00.0002025-03-09
2D - TZ independentgdh_d_tz_independent2025-03-12 00:00:00.0002025-03-12
3D - Dgdh_d_d2025-03-14 00:00:00.0002025-03-14
4DT - User Localgdh_dt_userlocal2025-03-10 06:30:00.0002025-03-10 10:30
5DT - TZ independentgdh_dt_tz_independent2025-03-24 09:30:00.0002025-03-24 09:30

问题:为什么 Test06 在表单上看到的 DT - User Local 是 2025-03-10 10:30<00>.000#

Test06(GMT+04<00> 巴库时区)看到 DT - User Local2025-03-10 10:30,本质是 Dataverse 从 UTC 存储值反向转换为用户本地时区时间的规则导致,Test06 所在时区是 GMT+04<00>(Baku,比 UTC 快 4 小时),系统会将 UTC 存储值转换为其本地时间:

Test06 看到的时间 = UTC 存储值 + 4 小时
= 2025-03-10 06:30:00 + 4 小时
= 2025-03-10 10:30:00

问题:为什么 D - User Local2025-03-09#

D - User Local 是“仅日期+用户当地时间”,数据库存储的 UTC2025-03-09 16:00,转换为巴库时间:

2025-03-09 16:00(UTC) + 4 小时 = 2025-03-09 20:00(巴库时间)

由于是“仅日期”格式,表单只显示日期部分 2025-03-09

Step 5. Client API 获取日期时间字段值的结果解析#

(1) “用户当地时间”字段(D/DT - User Local)的取值逻辑#

js获取gdh_d_userlocal和gdh_dt_userlocal
js获取gdh_d_userlocal和gdh_dt_userlocal

Xrm.Page.getAttribute("gdh_d_userlocal").getValue();
// Mon Mar 10 2025 00:00:00 GMT+0800 (中国标准时间)
Xrm.Page.getAttribute("gdh_d_userlocal").getValue().toUTCString();
// 'Sun, 09 Mar 2025 16:00:00 GMT'
Xrm.Page.getAttribute("gdh_dt_userlocal").getValue();
// Mon Mar 10 2025 14:30:00 GMT+0800 (中国标准时间)
Xrm.Page.getAttribute("gdh_dt_userlocal").getValue().toUTCString();
// 'Mon, 10 Mar 2025 06:30:00 GMT'
  • Client API 中,getAttribute("字段名").getValue() 返回的是 JavaScript Date 对象,返回的是
  • 对于“用户当地时间”类型的字段,这个 Date 对象的底层存储是 UTC 时间(和数据库一致)
  • 调用 toUTCString() 会直接输出该 Date 对象的 UTC 字符串,因此和数据库值完全匹配

(2) “时区无关”字段(D/DT - TZ independent)的取值逻辑#

Xrm.Page.getAttribute("gdh_d_tz_independent").getValue();
// Wed Mar 12 2025 00:00:00 GMT+0800 (中国标准时间)
Xrm.Page.getAttribute("gdh_d_tz_independent").getValue().toString();
// 'Wed Mar 12 2025 00:00:00 GMT+0800 (中国标准时间)'
Xrm.Page.getAttribute("gdh_dt_tz_independent").getValue();
// Mon Mar 24 2025 09:30:00 GMT+0800 (中国标准时间)
Xrm.Page.getAttribute("gdh_dt_tz_independent").getValue().toString();
// 'Mon Mar 24 2025 09:30:00 GMT+0800 (中国标准时间)'

我所在浏览器的时区是 GMT+08,因此 toString() 输出的是 GMT+0800 对应的时间(和数据库存储的数值完全一致,只是时区标识变成了浏览器时区)

Step 6. Web API 获取日期时间字段的解析#

var req = new XMLHttpRequest();
var apiVersion = "v9.1";
var entityName = "gdh_invoices";
var recordId = "98F83878-1E35-EF11-8409-0017FA0671FA";
var selectFields = [
"gdh_d_d",
"gdh_d_tz_independent",
"gdh_d_userlocal",
"gdh_dt_tz_independent",
"gdh_dt_userlocal",
].join(",");
var baseUrl = Xrm.Page.context.getClientUrl();
var apiPath = `/api/data/${apiVersion}/${entityName}(${recordId})`;
var queryParams = `?$select=${selectFields}`;
var fullUrl = baseUrl + apiPath + queryParams;
req.open("GET", fullUrl, true);
req.setRequestHeader("OData-MaxVersion", "4.0");
req.setRequestHeader("OData-Version", "4.0");
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
req.setRequestHeader("Prefer", 'odata.include-annotations="*"');
req.onreadystatechange = function () {
if (this.readyState === 4) {
req.onreadystatechange = null;
if (this.status === 200) {
var result = JSON.parse(this.response);
var gdh_d_d = result["gdh_d_d"];
var gdh_d_tz_independent = result["gdh_d_tz_independent"];
var gdh_d_userlocal = result["gdh_d_userlocal"];
var gdh_dt_tz_independent = result["gdh_dt_tz_independent"];
var gdh_dt_userlocal = result["gdh_dt_userlocal"];
} else {
Xrm.Utility.alertDialog(this.statusText);
}
}
};
req.send();
{
"@odata.context": "https://sample.com/api/data/v9.1/$metadata#gdh_invoices(gdh_d_d,gdh_d_tz_independent,gdh_d_userlocal,gdh_dt_tz_independent,gdh_dt_userlocal)/$entity",
"@odata.etag": "W/\"9300793\"",
"gdh_invoiceid": "98f83878-1e35-ef11-8409-0017fa0671fa",
"gdh_dt_userlocal@OData.Community.Display.V1.FormattedValue": "2025/03/10 14:30",
"gdh_dt_userlocal": "2025-03-10T06:30:00Z", // [!code highlight]
"gdh_d_d@OData.Community.Display.V1.FormattedValue": "2025/03/14",
"gdh_d_d": "2025-03-14", // [!code highlight]
"gdh_d_userlocal@OData.Community.Display.V1.FormattedValue": "2025/03/10",
"gdh_d_userlocal": "2025-03-09T16:00:00Z", // [!code highlight]
"gdh_d_tz_independent@OData.Community.Display.V1.FormattedValue": "2025/03/12",
"gdh_d_tz_independent": "2025-03-12T00:00:00Z", // [!code highlight]
"gdh_dt_tz_independent@OData.Community.Display.V1.FormattedValue": "2025/03/24 09:30",
"gdh_dt_tz_independent": "2025-03-24T09:30:00Z" // [!code highlight]
}

(补充说明)ISO 8601 中 T/Z 的含义#

字符定义Web API中的实际作用
T分隔日期和时间固定格式分隔符,Web API 所有带时间的日期字段返回值都会包含 T
(如 2025-03-10T06:30<00z>
Z代表 UTC 时区(Zulu 时间)明确标识该时间是 UTC 时间,无偏移量;
Web API中所有用户当地时间/时区无关的时间字段都会带Z

(补充说明)Web API 与 Client API 的取值对比#

特性Web APIClient API
返回格式ISO 8601 字符串(带 T/Z)JavaScript Date 对象
时区处理仅返回 UTC 原始值(无转换)用户当地时间返回 UTC Date 对象,
时区无关返回浏览器时区 Date 对象
仅日期字段D-D 无 T/Z,其他带 T/Z均返回 Date 对象(含时间)
适用场景跨系统集成(如对接外部服务)表单内前端逻辑处理

补充说明#

避坑提醒:仅日期字段的查询注意事项#

“仅限日期” 类型的日期和时间字段,不允许使用下面的查询运算符,当这些运算符用于查询时,会引发无效运算符异常错误

  • X 分钟以前
  • X 小时以前
  • 过去 X 小时
  • 接下来 X 小时

例如:对 “D-D” 列进行筛选,筛选条件如下:

对“D-D”列设置筛选条件
对“D-D”列设置筛选条件

执行后会报错:2147779605The operator is not valid or it is not supported.

出现异常:2147779605The operator is not valid or it is not supported.
出现异常:2147779605The operator is not valid or it is not supported.

该错误码对应 Dataverse 官方文档中的 DMQE_INVALID_OPERATOR 错误,描述为:

The specified operator is not valid for the attribute type, or the operator is not supported for the query type.(指定的运算符对该属性类型无效,或该运算符不支持此查询类型。)

如何设置用户的时区?#

个人设置#

右上角的设置按钮 —> 个性化设置 —> General Tab —> 选择时区

后台设置#

后台设置的具体操作步骤,请参考我的另一篇文章:

XrmToolBox设置用户默认时区
介绍如何在XrmToolBox中设置用户的默认时区,通过使用User Settings Utility轻松设置用户时区
2025-08-28Dynamics 365#XrmToolBox

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

打赏
Dynamics 365日期和时间字段
https://mgrowup.com/posts/dynamics365/date-time-fields/
作者
Donghai
发布于
2025-03-07
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
Donghai
Hello, I'm Donghai.
公告
欢迎来到我的博客!这是一则示例公告。
分类
标签
最新动态
站点统计
文章
62
分类
9
标签
37
总字数
76,949
运行时长
0
最后活动
0 天前
站点信息
构建平台
Vercel
博客版本
Firefly v6.16.5
文章许可
CC BY-NC-SA 4.0