You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在T-SQL中实现波斯(Shamsi)日期与公历的转换?求高效SQL方案

嘿,我来分享两个经过验证的纯T-SQL解决方案,正好对应你的两个需求——不管是波斯转公历,还是批量把公历转成波斯日期,都能高效搞定,完全在数据库层面处理,比外部转换快得多。

1. 波斯(Shamsi)日期转公历日期

如果你手里的波斯日期是yyyy/mm/dd这样的字符串,或者已经拆分成年、月、日的数值,可以用下面这个标量函数来转换。它基于标准的波斯历与公历转换算法,准确性可靠,而且纯SQL实现,不需要任何外部依赖:

CREATE FUNCTION dbo.ShamsiToGregorian
(
    @ShamsiYear INT,
    @ShamsiMonth INT,
    @ShamsiDay INT
)
RETURNS DATE
AS
BEGIN
    DECLARE @gy INT, @gm INT, @gd INT;
    DECLARE @jy INT = @ShamsiYear, @jm INT = @ShamsiMonth, @jd INT = @ShamsiDay;

    -- 定义波斯历各月份的天数(考虑闰年)
    DECLARE @daysInMonthJY TABLE (Month INT, Days INT);
    INSERT INTO @daysInMonthJY VALUES
        (1, 31), (2, 31), (3, 31), (4, 31), (5, 31), (6, 31),
        (7, 30), (8, 30), (9, 30), (10, 30), (11, 30), (12, 29 + CASE WHEN (@jy % 33) IN (1, 5, 9, 13, 17, 22, 26, 30) THEN 1 ELSE 0 END);

    -- 计算该日期在波斯历中的累计天数
    DECLARE @totalDays INT = @jd;
    SELECT @totalDays = @totalDays + Days FROM @daysInMonthJY WHERE Month < @jm;

    -- 转换为公历年份基准
    DECLARE @jy1 INT = @jy - 474;
    DECLARE @cycle INT = @jy1 / 2820;
    DECLARE @remaining INT = @jy1 % 2820;
    DECLARE @gy1 INT;

    IF @remaining <= 68
        SET @gy1 = 2820 * @cycle + @remaining + 474;
    ELSE
        SET @gy1 = 2820 * @cycle + @remaining + 473;

    -- 计算对应公历日期
    DECLARE @daysSinceMarch21 INT = @totalDays - 1;
    DECLARE @march21Gregorian DATE = DATEFROMPARTS(@gy1, 3, 21);
    DECLARE @gregorianDate DATE = DATEADD(DAY, @daysSinceMarch21, @march21Gregorian);

    SET @gy = YEAR(@gregorianDate);
    SET @gm = MONTH(@gregorianDate);
    SET @gd = DAY(@gregorianDate);

    RETURN DATEFROMPARTS(@gy, @gm, @gd);
END
GO

使用示例:

如果你有一个波斯日期字符串1402/05/15,可以拆分后调用函数:

SELECT dbo.ShamsiToGregorian(1402, 5, 15) AS GregorianDate;

或者如果你的字段是字符串类型,可以先拆分后转换:

SELECT 
    dbo.ShamsiToGregorian(
        CAST(SUBSTRING(ShamsiDateColumn, 1, 4) AS INT),
        CAST(SUBSTRING(ShamsiDateColumn, 6, 2) AS INT),
        CAST(SUBSTRING(ShamsiDateColumn, 9, 2) AS INT)
    ) AS GregorianDate
FROM YourTable;

2. 批量将公历日期转换为波斯日期的高效方案

对于大量数据的报表场景,我更推荐用内联表值函数(ITVF),它的性能比标量函数好很多,因为SQL Server优化器会把它和主查询合并执行,避免逐行调用的开销:

CREATE FUNCTION dbo.GregorianToShamsi
(
    @GregorianDate DATE
)
RETURNS TABLE
AS
RETURN
(
    WITH GregorianInfo AS (
        SELECT
            YEAR(@GregorianDate) AS gy,
            MONTH(@GregorianDate) AS gm,
            DAY(@GregorianDate) AS gd,
            DATEDIFF(DAY, '0001-01-01', @GregorianDate) + 1 AS julianDay
    ),
    JulianConversion AS (
        SELECT
            gy, gm, gd, julianDay,
            julianDay - DATEDIFF(DAY, '0001-03-21', DATEFROMPARTS(gy, 3, 21)) + 1 AS dayOfYear,
            CASE WHEN gm > 2 THEN gy ELSE gy - 1 END AS gyForMarch
        FROM GregorianInfo
    ),
    ShamsiCalculation AS (
        SELECT
            gy, gm, gd, julianDay, dayOfYear,
            474 + ((gyForMarch - 474) * 365 + (gyForMarch - 474) / 4 - (gyForMarch - 474) / 100 + (gyForMarch - 474) / 400 - DATEDIFF(DAY, '0001-03-21', '0001-01-01')) / 365.2425 AS jyFloat
        FROM JulianConversion
    ),
    ShamsiYear AS (
        SELECT
            gy, gm, gd, julianDay, dayOfYear,
            FLOOR(jyFloat) AS jy,
            jyFloat - FLOOR(jyFloat) AS jyFraction
        FROM ShamsiCalculation
    ),
    ShamsiStart AS (
        SELECT
            gy, gm, gd, julianDay, dayOfYear, jy,
            DATEADD(DAY, FLOOR(jyFraction * 365.2425), DATEFROMPARTS(474, 3, 21)) AS shamsiYearStart
        FROM ShamsiYear
    ),
    DayOfShamsiYear AS (
        SELECT
            gy, gm, gd, julianDay, jy,
            DATEDIFF(DAY, shamsiYearStart, @GregorianDate) + 1 AS doy
        FROM ShamsiStart
    ),
    ShamsiMonth AS (
        SELECT
            jy, doy,
            CASE
                WHEN doy <= 186 THEN CEILING(doy / 31.0)
                ELSE CEILING((doy - 186) / 30.0) + 6
            END AS jm,
            CASE
                WHEN doy <= 186 THEN doy - (CEILING(doy / 31.0) - 1) * 31
                ELSE doy - 186 - (CEILING((doy - 186) / 30.0) - 1) * 30
            END AS jd
        FROM DayOfShamsiYear
    )
    SELECT
        jy AS ShamsiYear,
        jm AS ShamsiMonth,
        jd AS ShamsiDay,
        CONCAT(jy, '/', FORMAT(jm, '00'), '/', FORMAT(jd, '00')) AS ShamsiDateString
    FROM ShamsiMonth
)
GO

使用示例(批量处理):

直接在查询中调用这个表值函数,处理整个表的日期字段,性能非常适合大数据量:

SELECT
    t.YourId,
    t.GregorianDateColumn,
    s.ShamsiYear,
    s.ShamsiMonth,
    s.ShamsiDay,
    s.ShamsiDateString
FROM YourTable t
CROSS APPLY dbo.GregorianToShamsi(t.GregorianDateColumn) s;

性能小贴士:

  • 内联表值函数会被SQL Server优化器展开,和查询本身一起执行,避免了标量函数的逐行调用开销,处理百万级数据时优势明显。
  • 如果你的日期字段经常需要转换,可以考虑添加持久化计算列,把波斯日期预计算并存储,报表查询时直接读取即可:
    ALTER TABLE YourTable
    ADD ShamsiDate AS (SELECT ShamsiDateString FROM dbo.GregorianToShamsi(GregorianDateColumn)) PERSISTED;
    
    上面的函数是确定性的,完全符合持久化计算列的要求。

内容的提问来源于stack exchange,提问作者Difender Amini

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:38:58