SQL Server中RFC2822格式带时区日期的规范转换方法
RFC2822格式日期在SQL Server中的可靠转换方案
你目前用的临时截取方法存在两个无法规避的硬伤,这也是转换效果不稳定的根本原因:
- 固定截取20个字符的长度适配性极差:RFC2822格式里的日期是1-2位数字,遇到个位数日期(比如
2 Mar 2021)时,固定长度截取会直接截断时间部分的内容,导致转换报错 - 完全忽略了末尾的时区偏移值:不同数据源发来的日期自带时区标记,如果不对时间做偏移换算,最终转出的时间根本不是统一基准的数值,和实际时间存在固定的小时差
实现逻辑说明
RFC2822格式的日期结构是完全固定的:
- 开头固定是3位英文星期缩写 + 逗号 + 空格,这部分的总长度永远是5个字符,且不参与时间计算,可以直接跳过
- 后续内容按空格分隔依次是:日期(1-2位数字)、3位月份缩写、4位年份、
hh:mi:ss格式的时间、5位时区偏移(比如+0000、-0530)
我们只要跳过开头的星期部分,把剩余的带时区内容解析成SQL Server支持的带时区时间类型,再统一换算到目标基准时区,最后格式化输出即可,完全不需要靠固定长度猜位置。
具体实现代码
推荐方案(SQL Server 2016及以上版本)
这个版本开始SQL Server原生支持DATETIMEOFFSET类型的字符串解析,不需要手动拆分字段,稳定性最高:
-- 测试RFC2822原日期串 DECLARE @WeirdDate VARCHAR(50) = 'Thu, 18 Mar 2021 10:37:31 +0800'; -- 跳过固定长度的星期前缀(3位星期+逗号+空格,共5个字符) DECLARE @DateCore VARCHAR(50) = SUBSTRING(@WeirdDate, 6, LEN(@WeirdDate)); -- 1. 原生解析为带时区的时间类型 -- 2. 用SWITCHOFFSET换算到目标基准时区(示例为UTC时区,改成'+08:00'就是东八区时间) -- 3. 用样式120直接输出要求的YYYY-MM-DD hh:mi:ss格式 SELECT CONVERT(VARCHAR(19), SWITCHOFFSET(CAST(@DateCore AS DATETIMEOFFSET), '+00:00'), 120) AS StandardDateTime
- 这里跳过固定长度前缀,比靠查找逗号定位的方式更稳定,不会被后续内容里的特殊符号干扰
SWITCHOFFSET会自动识别原字符串里的时区偏移值,不管数据源是哪个时区,都会自动做时间差换算,不存在时区错位的问题
低版本兼容方案(SQL Server 2014及以下)
低版本没有原生的RFC2822字符串解析能力,就按空格拆分各个字段,手动组装转换:
DECLARE @WeirdDate VARCHAR(50) = 'Thu, 30 Jul 2015 20:00:00 +0530'; -- 第一步:跳过星期前缀 DECLARE @CleanPart VARCHAR(50) = LTRIM(SUBSTRING(@WeirdDate, CHARINDEX(',', @WeirdDate) + 1, LEN(@WeirdDate))); -- 第二步:按空格拆分各段内容 DECLARE @DayStr VARCHAR(2), @MonthStr VARCHAR(3), @YearStr VARCHAR(4), @TimeStr VARCHAR(8), @TZStr VARCHAR(5); SET @DayStr = PARSENAME(REPLACE(@CleanPart, ' ', '.'), 5); SET @MonthStr = PARSENAME(REPLACE(@CleanPart, ' ', '.'), 4); SET @YearStr = PARSENAME(REPLACE(@CleanPart, ' ', '.'), 3); SET @TimeStr = PARSENAME(REPLACE(@CleanPart, ' ', '.'), 2); SET @TZStr = PARSENAME(REPLACE(@CleanPart, ' ', '.'), 1); -- 第三步:将3位月份缩写转为对应的数字 DECLARE @MonthNum INT = CASE @MonthStr WHEN 'Jan' THEN 1 WHEN 'Feb' THEN 2 WHEN 'Mar' THEN 3 WHEN 'Apr' THEN 4 WHEN 'May' THEN 5 WHEN 'Jun' THEN 6 WHEN 'Jul' THEN 7 WHEN 'Aug' THEN 8 WHEN 'Sep' THEN 9 WHEN 'Oct' THEN 10 WHEN 'Nov' THEN 11 WHEN 'Dec' THEN 12 END; -- 第四步:拆分解时区偏移值 DECLARE @TzSign INT = CASE LEFT(@TZStr, 1) WHEN '+' THEN -1 ELSE 1 END; DECLARE @TzHour INT = CAST(SUBSTRING(@TZStr, 2, 2) AS INT); DECLARE @TzMin INT = CAST(SUBSTRING(@TZStr, 4, 2) AS INT); -- 第五步:组装本地时间,叠加时区偏移得到基准时间,输出目标格式 DECLARE @LocalTime DATETIME = DATETIMEFROMPARTS( CAST(@YearStr AS INT), @MonthNum, CAST(@DayStr AS INT), CAST(SUBSTRING(@TimeStr, 1,2) AS INT), CAST(SUBSTRING(@TimeStr,4,2) AS INT), CAST(SUBSTRING(@TimeStr,7,2) AS INT), 0 ); SELECT CONVERT(VARCHAR(19), DATEADD(MINUTE, @TzSign * (@TzHour * 60 + @TzMin), @LocalTime), 120) AS StandardDateTime
注意事项
- 不需要额外校验开头的星期标识是否正确:RFC2822格式约定星期和后续日期是对应的,解析过程我们完全不使用星期的值,直接跳过即可
- 时区换算逻辑不要搞反:原串里的时区偏移为
+xxxx代表该本地时间比UTC快对应的时长,要得到UTC时间就需要从本地时间里减去这个时长,代码里的符号逻辑已经做了适配 - 不要再用固定长度截取20个字符的写法,这种写法遇到个位数日期时一定会截断内容,转换稳定性极差
内容的提问来源于stack exchange,提问作者Romy Bos
相关产品推荐
相关产品推荐

