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

带天数倒计时的订阅系统实现及C#日期与天数互转问询

嘿,这个订阅系统的需求很典型,我帮你拆成两部分来解决——自动每日扣减账户额度,还有C#里日期和天数的转换,都是实操性很强的点:

一、实现每日自动扣减账户天数并输出归零日期

1. 核心数据结构与逻辑设计

首先,你的用户表至少需要这两个核心字段(选一种存储方式即可):

  • 方案A:存剩余天数:*RemainingDays*(int类型) + *SubscriptionStartDate*(DateTime类型,可选,用于追溯)
  • 方案B:存到期日期:*ExpiryDate*(DateTime类型)——更推荐这个方案,因为不需要每日批量更新,逻辑更高效

如果用方案A,每日任务的核心逻辑是:遍历所有RemainingDays > 0的用户,给RemainingDays -= 1;当扣减后RemainingDays == 0,记录当前日期作为归零日期(输出格式DD-MM-YYYY)。

如果用方案B,根本不需要每日扣减——直接通过ExpiryDate判断用户是否过期,剩余天数可以实时计算,归零日期就是ExpiryDate本身,这种方式能避免批量更新的性能开销,尤其用户量较大时更友好。

2. 每日任务的实现方式

在C#生态里,有几种常用的定时任务触发方式:

(1)ASP.NET Core 后台服务+Quartz.NET(Web项目首选)

适合Web应用,用Quartz.NET做灵活调度,比如每天凌晨1点执行扣减:

// 先安装NuGet包:Quartz
public class DailySubscriptionDeductionJob : IJob
{
    private readonly AppDbContext _dbContext;

    public DailySubscriptionDeductionJob(AppDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public async Task Execute(IJobExecutionContext context)
    {
        // 防重复执行:先检查今日是否已运行过任务
        var lastRunRecord = await _dbContext.SystemConfigs
            .FirstOrDefaultAsync(c => c.ConfigKey == "LastDeductionDate");
        
        if (lastRunRecord != null && lastRunRecord.ConfigValueDate?.Date == DateTime.Now.Date)
        {
            Console.WriteLine("今日已完成扣减任务,无需重复执行");
            return;
        }

        // 执行扣减逻辑(方案A)
        var eligibleUsers = await _dbContext.Users
            .Where(u => u.RemainingDays > 0)
            .ToListAsync();

        foreach (var user in eligibleUsers)
        {
            user.RemainingDays -= 1;
            if (user.RemainingDays == 0)
            {
                user.ExpiryDate = DateTime.Now.Date;
                // 输出归零日期
                Console.WriteLine($"用户ID {user.Id} 订阅额度已归零,日期:{user.ExpiryDate:dd-MM-yyyy}");
            }
        }

        // 更新任务执行记录
        if (lastRunRecord == null)
        {
            _dbContext.SystemConfigs.Add(new SystemConfig 
            { 
                ConfigKey = "LastDeductionDate", 
                ConfigValueDate = DateTime.Now.Date 
            });
        }
        else
        {
            lastRunRecord.ConfigValueDate = DateTime.Now.Date;
        }

        await _dbContext.SaveChangesAsync();
    }
}

// 在Program.cs中配置Quartz
builder.Services.AddQuartz(q =>
{
    var jobKey = new JobKey("DailySubscriptionDeduction");
    q.AddJob<DailySubscriptionDeductionJob>(opts => opts.WithIdentity(jobKey));

    // 每天凌晨1点执行,Cron表达式:秒 分 时 日 月 周 年
    q.AddTrigger(opts => opts
        .ForJob(jobKey)
        .WithIdentity("DailyDeductionTrigger")
        .WithCronSchedule("0 0 1 * * ?"));
});

builder.Services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);

(2)Windows服务(桌面/服务器端独立应用)

如果是独立的Windows应用,用System.Timers.Timer或者Quartz.NET来实现定时触发,逻辑和上面的Job一致,只是部署方式不同。

(3)数据库定时任务(轻量场景)

比如SQL Server的Agent作业,直接写存储过程完成扣减,不需要应用层介入:

CREATE PROCEDURE DeductSubscriptionDays
AS
BEGIN
    SET NOCOUNT ON;

    -- 检查今日是否已执行
    DECLARE @LastRunDate DATE = (SELECT ConfigValue FROM SystemConfigs WHERE ConfigKey = 'LastDeductionDate');
    IF @LastRunDate = CAST(GETDATE() AS DATE)
        RETURN;

    -- 扣减剩余天数并记录归零日期
    UPDATE Users
    SET RemainingDays = RemainingDays - 1,
        ExpiryDate = CASE WHEN RemainingDays - 1 = 0 THEN CAST(GETDATE() AS DATE) ELSE ExpiryDate END
    WHERE RemainingDays > 0;

    -- 更新执行记录
    IF EXISTS(SELECT 1 FROM SystemConfigs WHERE ConfigKey = 'LastDeductionDate')
        UPDATE SystemConfigs SET ConfigValue = CAST(GETDATE() AS DATE) WHERE ConfigKey = 'LastDeductionDate';
    ELSE
        INSERT INTO SystemConfigs(ConfigKey, ConfigValue) VALUES('LastDeductionDate', CAST(GETDATE() AS DATE));
END
二、C#中日期转天数的方法

你已经会天数转日期,反过来的话,核心是计算两个日期的间隔天数,或者将日期转换为相对于某个基准日的天数差:

1. 计算两个日期的间隔天数

比如从订阅开始日期到当前日期的天数:

DateTime startDate = new DateTime(2024, 1, 15);
DateTime today = DateTime.Now.Date; // 用.Date忽略时间部分,避免计算误差
int daysPassed = (today - startDate).Days;

2. 将日期转换为基准日的天数差(比如Unix纪元)

如果需要把日期转成一个整数天数(比如从1970-01-01开始计数):

DateTime unixEpoch = new DateTime(1970, 1, 1);
DateTime targetDate = new DateTime(2024, 5, 20);
int daysSinceEpoch = (targetDate - unixEpoch).Days;

3. 从到期日期反推剩余天数(方案B专用)

如果用户表存的是ExpiryDate,实时计算剩余天数:

DateTime expiryDate = user.ExpiryDate;
int remainingDays = Math.Max(0, (expiryDate - DateTime.Now.Date).Days);
// 用Math.Max确保不会出现负数天数

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 07:33:49