aboutsummaryrefslogtreecommitdiffstats
path: root/src/tasks/mod.rs
diff options
context:
space:
mode:
authorsalmonik <raisersoft@proton.me>2026-07-10 21:04:21 +0200
committersalmonik <raisersoft@proton.me>2026-07-10 21:04:21 +0200
commitcee8d7ff997afbb5b305ad72924b96277c151d8e (patch)
tree41af9ca361b91e5e3ca13159e5c0737e79fabf61 /src/tasks/mod.rs
parentfix cron double fire (diff)
downloadsenator-sperma-cee8d7ff997afbb5b305ad72924b96277c151d8e.tar.gz
senator-sperma-cee8d7ff997afbb5b305ad72924b96277c151d8e.zip
make cron follow local time zone
Signed-off-by: salmonik <raisersoft@proton.me>
Diffstat (limited to '')
-rw-r--r--src/tasks/mod.rs29
1 files changed, 18 insertions, 11 deletions
diff --git a/src/tasks/mod.rs b/src/tasks/mod.rs
index 1519c12..4408857 100644
--- a/src/tasks/mod.rs
+++ b/src/tasks/mod.rs
@@ -1,6 +1,6 @@
1use std::sync::Arc; 1use std::sync::Arc;
2 2
3use chrono::{DateTime, Duration, Timelike, Utc}; 3use chrono::{DateTime, Duration, Local, TimeZone, Timelike, Utc};
4use cron::Schedule; 4use cron::Schedule;
5use poise::serenity_prelude as serenity; 5use poise::serenity_prelude as serenity;
6use std::str::FromStr; 6use std::str::FromStr;
@@ -45,7 +45,7 @@ pub async fn check_and_execute_jobs(ctx: &CronContext) {
45 } 45 }
46 }; 46 };
47 47
48 let now = Utc::now(); 48 let now = Local::now();
49 49
50 for job in jobs { 50 for job in jobs {
51 let schedule = match parse_cron(&job.cron_expression) { 51 let schedule = match parse_cron(&job.cron_expression) {
@@ -63,13 +63,18 @@ pub async fn check_and_execute_jobs(ctx: &CronContext) {
63 // - If last_fire <= now, the job is due; we record last_run = last_fire 63 // - If last_fire <= now, the job is due; we record last_run = last_fire
64 // (not `now`) so a long execution delay can't skip a legitimately 64 // (not `now`) so a long execution delay can't skip a legitimately
65 // pending later fire. 65 // pending later fire.
66 let reference: DateTime<Utc> = job.last_run.unwrap_or_else(|| { 66 // - Evaluate the cron expression in local time so "37 21 * * *" means
67 // Step back a tick so the first scan after startup catches the fire 67 // 21:37 in the server's timezone, not UTC.
68 // scheduled for the minute we wake up on. 68 let reference: DateTime<Local> = job.last_run.map_or_else(
69 now - chrono::Duration::seconds(TICK_SECONDS as i64) 69 || {
70 }); 70 // Step back a tick so the first scan after startup catches the fire
71 71 // scheduled for the minute we wake up on.
72 let mut due_fire: Option<DateTime<Utc>> = None; 72 now - chrono::Duration::seconds(TICK_SECONDS as i64)
73 },
74 |utc_last_run| Local.from_utc_datetime(&utc_last_run.naive_utc()),
75 );
76
77 let mut due_fire: Option<DateTime<Local>> = None;
73 for next_fire in schedule.after(&reference) { 78 for next_fire in schedule.after(&reference) {
74 if next_fire > now { 79 if next_fire > now {
75 break; 80 break;
@@ -92,7 +97,9 @@ pub async fn check_and_execute_jobs(ctx: &CronContext) {
92 // Record last_run so we never re-fire the same schedule instant on the 97 // Record last_run so we never re-fire the same schedule instant on the
93 // next tick. Kept outside the match so a failed/empty send still marks 98 // next tick. Kept outside the match so a failed/empty send still marks
94 // the fire as consumed and doesn't retry on every tick. 99 // the fire as consumed and doesn't retry on every tick.
95 if let Err(e) = database::update_cron_last_run(&ctx.pool, job.id, fire_time).await { 100 // Convert local fire_time to UTC for consistent DB storage.
101 let fire_time_utc = fire_time.with_timezone(&Utc);
102 if let Err(e) = database::update_cron_last_run(&ctx.pool, job.id, fire_time_utc).await {
96 error!("Failed to update last_run for cron job {}: {e}", job.id); 103 error!("Failed to update last_run for cron job {}: {e}", job.id);
97 } 104 }
98 } 105 }
@@ -172,7 +179,7 @@ async fn execute_audio_job(ctx: &CronContext, job: &crate::database::CronJob) {
172 179
173pub async fn cron_scheduler_loop(ctx: CronContext) { 180pub async fn cron_scheduler_loop(ctx: CronContext) {
174 // Align first tick to the start of the next minute, then run every TICK_SECONDS. 181 // Align first tick to the start of the next minute, then run every TICK_SECONDS.
175 let now = Utc::now(); 182 let now = Local::now();
176 let next_minute = (now + Duration::minutes(1)) 183 let next_minute = (now + Duration::minutes(1))
177 .with_second(0) 184 .with_second(0)
178 .and_then(|t| t.with_nanosecond(0)) 185 .and_then(|t| t.with_nanosecond(0))