一劳永逸,解决.NET发布云服务器的时区问题


国内大多数开发者使用的电脑,都是使用的北京时间,日常开发的过程中其实并没有什么不便;不过,等遇到了阿里云等云服务器,系统默认使用的时间大多为UTC时间,这个时候,时区和时间的问题,就是不容忽视的大问题。

概念

首先明确一点,对于一个时刻,不管你用UTC时间还是UTC+8的时间来表示,本质上是一个时刻,就是一样的。我们处理日期和时间的目标,也是为了保证这个时刻不会因为时区的不同出现对不上的情况。

DateTime与DateTimeOffset

.NET中表示时刻的数据类型有这两个(新出的Date和Time不作讨论),关于这两个数据类型,已经有同学写的很清楚了,阿里云很多服务器使用的时间为UTC时间,这个时候,如果使用DateTime,是很难说清楚时区(Kind只有UTC、Local还有未指定,不支持特定的某个时区),因此我们应当优先使用DateTimeOffset。

TimeZoneInfo

用于跨时区的情况下,时区的信息是很重要的,.NET中使用TimeZoneInfo这个类表示时区的信息。该类提供了一些静态方法,可以用于查找时区和创建时区等等。最早我是倾向于使用这些方法找到东八区的信息的,但是我发现诸如ConvertTimeBySystemTimeZoneIdFindSystemTimeZoneById的方法,都依赖于系统中的定义,不同的系统可能还不一样,自己定义是比较保险的,于是,我使用了CreateCustomTimeZone来新建一个时区。

Unix时间戳是比较于1970年的UTC标准时间,因此在处理的过程中,DateTime的时间表示应当将它转换为UTC时间,以下的代码,是使用TimeZoneInfo实现时间转换的,使用的是DateTime数据类型。如果改用DateTimeOffset,这个类型对转换为Unix时间戳更加友好。

internal static class DateTimeExtension
{
    private static readonly TimeZoneInfo gmt8 = TimeZoneInfo.CreateCustomTimeZone("GMT+8", TimeSpan.FromHours(8), "China Standard Time", "(UTC+8)China Standard Time");
    
    public static long ToUnixTime(this DateTime datetime)
    {
        DateTime dateTimeUtc = datetime;
        if (datetime.Kind != DateTimeKind.Utc)
        {
            dateTimeUtc = datetime.ToUniversalTime();
        }

        if (dateTimeUtc.ToUniversalTime() <= DateTime.UnixEpoch)
        {
            return 0;
        }

        return (long)(dateTimeUtc - DateTime.UnixEpoch).TotalMilliseconds;
    }

    public static DateTime ToDateTime(this long unixTimestamp)
    {
        DateTime time = DateTime.UnixEpoch.AddMilliseconds(unixTimestamp);
        return TimeZoneInfo.ConvertTimeFromUtc(time, gmt8);
    }

    public static DateTime ToDateTime(this long unixTimestamp, int timezone)
    {
        DateTime time = DateTime.UnixEpoch.AddMilliseconds(unixTimestamp);
        return time.AddHours(timezone);
    }
}

其实,只要时区是正确的,那么可以也可以使用网友提供的方法进行转换。

// Code from https://stackoverflow.com/questions/5615538/parse-a-date-string-into-a-certain-timezone-supporting-daylight-saving-time
public DateTimeOffset ParseDateExactForTimeZone(string dateTime, TimeZoneInfo timezone)
{
    var parsedDateLocal = DateTimeOffset.ParseExact(dateTime, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
    var tzOffset = timezone.GetUtcOffset(parsedDateLocal.DateTime);
    var parsedDateTimeZone = new DateTimeOffset(parsedDateLocal.DateTime, tzOffset);
    return parsedDateTimeZone;
}

实践指南

处理日期与时间的过程中,如果加入了TimeZoneInfo的情况下会使得程序变得非常麻烦,特别是各种TimeZone的Id和名称,不同系统也不统一的情况下,容易出现各种各样的问题。我想的就是避免用它,说说我的处理原则吧。

  1. 日期时间不使用DateTime类,全部使用DateTimeOffset类型
  2. 系统的内部处理,全部使用UTC标准时间进行数据表示
  3. 对于字符串的转换为DataTimeOffset的情况,显式指定时区的小时偏移量
  4. 直接使用时间的加减,避免使用时区的信息转换导致的代码复杂度增加
  5. 【可选】如果不用考虑2038年的情况下,可以考虑Unix时间戳简化时间表示

直接贴上我现在使用的代码段,思路就是在强制给字符串表示的时间,加上UTC标准时区信息,然后再修正时差。

public static class DateTimeExtension
{
    public static long? ParseUnixTimeMillisecondsWithTimeZone(string datetimeString, string format = "yyyyMMddHHmmss", int timezoneOffset = 8)
    {
    	//注意这里非常关键的参数DateTimeStyles.AssumeUniversal,就是设定数据都是UTC的,不管是不是,都强行指定为UTC,然后再按照时区的信息调整为正确的时间。
    	//给定的数据是东八区时间,但是加上这个参数,实际上的时间就会提前了8个小时,因此需要在后面的数据中直接减去8个小时,如果是其他地区的时间,那么也是一样操作。
        if (!DateTimeOffset.TryParseExact(datetimeString, format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out DateTimeOffset time)) return null;
        DateTimeOffset dateTimeUtcOffset = time.AddHours(-timezoneOffset);
        return dateTimeUtcOffset.ToUnixTimeMilliseconds();
    }

    public static DateTimeOffset ToDateTime(this long unixTimestamp) => DateTimeOffset.FromUnixTimeMilliseconds(unixTimestamp);
}

对于ASP.NET CORE,JSON.NET会自动处理符合ISO8601规范的日期格式,只要指定数据类型为DateTimeOffset,就能够准确转换了。

参考

引用来源:https://www.cnblogs.com/podolski/archive/2022/01/27/15850584.html

版权声明:本文为YES开发框架网发布内容,转载请附上原文出处连接
管理员
上一篇:迁移 dotnet 6 提示必须将目标平台设置为 Windows 平台
下一篇:C# 读取txt文件生成Word文档
评论列表

发表评论

评论内容
昵称:
验证码:
验证码
关联文章

一劳永逸解决.NET发布服务器时区问题
解决VUE发布后因为浏览器缓存造成页面没更新问题
阿里服务器使用xlight搭建ftp服务器
阿里搭建FTP服务器访问报错 200,227错误
html发布服务器后部分jpg报错:net::ERR_BLOCKED_BY_CLIENT
XLSX SheetJS解决日期43秒误差导致少一天问题处理
VS ClickOnce发布时候,版本号有更新,但是就是提示“已发布版本在服务器上已存在,是否覆盖它?”
服务器安全:限制外网访问解决方案
兼容不同时区时间戳方案
从一次解决Nancy参数绑定“bug”开始发布自己第一个nuget包(上篇)
Visual Studio(VS)发布自动使用Net Reactor给生成DLL加壳
消息发送时问题
Python使用UUID模块服务器上获取MAC地址,重启后就不一样了
腾讯服务器OpenVPN Service搭建
Docker 部署后文章发布时间变成 UTC 排查与修复
发布后,登录界面不见了elementUI节点
模仿写了一个摸鱼应用解决原作者问题
服务器ntlmssp攻击防御措施,windows server大量审核失败问题
解决WebClient或HttpWebRequest首次连接缓慢问题
解决 Navicat 隔一段时间就需要重新连接数据库问题

热门标签
.NET Core .NET Reactor ag-grid AI发布 api安全 ASP.NET Core C#DLL加密 C#播放声音 C#代码混淆 C#代码加密 ChromeDriver Codex DateTime DBeaver devexpress devTool DLL混淆 edge.js EF EFCore Electron element-ui el-form el-table excel FastReport FileStream FolderBrowerDialog FolderSelectDialog form提交 git gridcontrol gridview input javascript json字符串 JS转换对象JSON jwt JWT授权 linq log Math MCP mitmproxy MVC MySQL Navicat netstat nginx node_modules NSwag Nuget Nuget镜像 number PowerShell pyinstaller python pythoncom python爬虫 python抓包 pywin32 redis Requests-html RestSharp Selenium sql SQL Server Swagger to-cms Visual Studio VSCode vue VueRouter vue路由 VUE页面通讯 Webpack Windows Windows服务 winform wmi xlrd yaml YESCMS YESWEB开发框架 白象 表单提交 播放声音 打开URL 代码混淆 弹窗提醒 端口占用 对象转换 分布式 公共字典 机器码 进程排查 静态资源 开发指南 路由参数 密钥 配置教程 配置文件 权限 人工智能 任务 任务调度 日期间隔 日志 日志记录 省市区 授权验证 数据库 四舍五入 文案 文件读取 文件夹选择 文件目录选择 问题排查 行政区域数据 页面通讯 中间件 CSharp 事务锁 工单系统 并发控制 重复提交 CMS Markdig Markdown markdown-it marked 技术选型 VS Code 开发工具 源代码管理 版本控制 Docker PostgreSQL 时区 部署排查 CMS架构 EF Core 主题系统 二次开发 插件系统 容器 运维命令 镜像清理 Linux NAS 远程挂载 飞牛 fnOS S/4HANA SAP GUI SAP HANA SAP R/3 SAP入门 SAP版本 ERP SAP SAP MM 库存管理 物料管理 采购管理 入门教程 SAP S/4HANA SPRO 企业结构 采购组织 MM01 物料主数据 物料类型 BP分组 业务伙伴 供应商主数据 ME41 RFQ 库存物料 采购流程 ME51 消耗性物料 科目分配 采购申请 AC03 ML81N 外部服务 服务主数据 Business Partner SAP培训 ME51N MM模块 Lean Services MM-SRV 外部服务采购 PIR 供应来源 采购主数据 采购信息记录 ME31K 框架协议 计划协议 采购合同 ME01 供应来源确定 货源清单 MEQ1 供应源确定 配额安排 配额评分 MD04 MD21 MRP 计划文件 需求计划 批量程序 MD01N MD02 MRP Live MD05 MM 物料计划 优化采购 供应源 采购订单 ME2A 供应商确认 采购监控 Flexible Workflow 凭证释放 采购审批 释放策略 实地盘点 物料凭证 货物移动 MIGO 收货 移动类型 已撤回 供应商退货 货物发出 STO 库存转储 转移过账 生产订单 预留 GR/IR MIRO 供应商发票 物流发票校验 OMR2 税码 FI PP SD 实操教程 MRBR OMR6 发票差异 交货成本 后续借记 MI01 实物盘点 盘点差异 公司代码 工厂 组织结构 OMS2 主数据定制 自动科目确定 BP角色 CVI 伙伴确定 编号范围 凭证类型 字段选择 FBN1 OMBT OMC2 会计凭证 OMJJ BOM 委外加工 项目类别L MRKO 供应商寄售 特殊库存K MRKON PIPE Pipeline 特殊库存P ERS MRIS 发票计划 周期性结算 里程碑付款 变更追踪 版本管理 采购凭证 SFTP WebDAV 网盘 飞牛fnOS AMPL HERS MPN 中文教程 库存确定 可用性检查 缺件检查 Output Management 消息确定 输出确定 分割评估 库存计价 评估类别 评估类型 PB00 RM0000 条件技术 采购定价 MM-FI集成 OBYC 库存估价 文本类型 文本采用 EFB EVO MSV SU3 用户参数 发票校验 合同参照 履约保留款 特别总账 预付款 Fiori Launchpad SAP Fiori 应用导航 用户体验 LSMW LTMC Migration Cockpit 数据迁移 BRFplus OPD Output Control My Inbox 审批流程 灵活工作流 SAP PP 外部加工 SAP QM 检验批 质量信息记录 采购收货 SAP PM 维护BOM 维护订单 SAP SD SAP Service 端到端流程 MM模块培训 FI-MM集成 供应商管理 审批配置 FICO入门 SAP FICO 财务配置 供应商税务 预扣税 House Bank 银行对账 客户清账 应收账款 FI控制 验证与替代 印度 GST 税务配置 F110 FBZP EWM入门 SAP EWM 仓库管理 OX14 成本核算 物料评估 后勤配置 物料组 价值更新 数量更新 PP-PI 流程制造 生产计划 容差配置 SAP事务码 SAP基础 TCODE Basis 事务代码 MMNR 编号区间 采购实操 组织架构 OMSF SAP实操 FI配置 端口修改 密码设置 数据库配置 远程访问 ABAP基础 SAP ABAP 内表 变量定义 常量 数据类型 ABAP SAP开发 变量 基础语法 系统变量 结构体 字符串处理 循环语句 控制语句 DDIC SE11 数据字典 透明表 ABAP开发 ALE EDI IDoc 增强技术 ABAP内表 HASHED TABLE SORTED TABLE STANDARD TABLE 基本概念 性能优化 PARAMETERS SELECT-OPTIONS SELECTION-SCREEN 报表程序 选择屏幕 F4帮助 Report事件 输入校验 ABAP SQL ABAP语法 Open SQL SELECT 数据库访问 IKuai IP修改 PVE 网络配置 虚拟化
联系我们
联系电话:15090125178(微信同号)
电子邮箱:garson_zhang@163.com
站长微信二维码
微信二维码