docker-volume-backup
docker-volume-backup copied to clipboard
Question about scheduling the first Saturday of January using Cron and date command
What are you trying to do?
I am trying to schedule a Cron job that runs specifically on the first Saturday of January at midnight. I noticed that the Cron expression 0 0 1 ? 1 SAT#1 * can achieve this, but according to the configuration reference it has not allowed special characters.
Likewise, I want to know if a similar approach using standard cron syntax and the date command is supported.
What is your current configuration? Here is the Cron expression I am considering:
# run every Saturday in January at midnight, check if it's the first Saturday
BACKUP_CRON_EXPRESSION="0 0 * 1 6 [ "$(date +\%d)" -le 7 ]"
Log output
time=2025-04-05T17:35:13.058+02:00 level=ERROR msg="Fatal error running command: unexpected command substitution at 1:38" error="main.(*command).runInForeground: error scheduling: main.(*command).schedule: error sourcing configuration: main.sourceConfiguration: error loading config files: main.loadConfigsFromEnvFiles: error reading config file /etc/dockervolumebackup/conf.d/04yearly.conf: main.source: error expanding env: unexpected command substitution at 1:38"
Additional context I would like to confirm if this approach is valid in your Cron implementation. If it is not supported, could you recommend an alternative method to schedule a job for the first Saturday of January? Any guidance or examples would be greatly appreciated.
As a workaround, I will be using the following Cron expression:
sudo vi /etc/cron.d/backup_nextcloud_cron
0 0 * 1 6 root [ "$(date +\%d)" -le 7 ] && podman exec podman_backup_nextcloud /bin/sh -c 'set -a; source /etc/dockervolumebackup/conf.d/04yearly.conf; set +a && backup'
I am not sure how I would model this tbh. The package used is https://github.com/robfig/cron and the documentation is copied verbatim from there. Maybe the repo's issues have some helpful information that helps your uese case?
@m90 Thanks for the info.
It seems to be that robfig/cron is abandoned. This fork clarkmcc/cron supports it (I think). The same issue is connected to 434 and 319.
Wouldn't this fork only support L when your use case would need #?
Wouldn't this fork only support
Lwhen your use case would need#?
True, but in my use case I do not really care about first (SAT#1) or last one (L). 😄
I think I'd be fine to switch to the fork sometime soon to enable this, but I have to admit I don't think it'd warrant cutting a new release. Are you able to work off a self-built image in the meantime?
So I just tried swapping the dependency and unfortunately I found out that the clarkmcc fork is based off the v1 version of package cron, when the latest release of the upstream package is already at v3. I would think this happened inadvertently because of the strange ways in which Go modules handle major versions, but unfortunately it also means that swapping out the package isn't really an option as the code in this image relies on features provided by that v3.
I don't know how much work it would be to create yet another fork of package cron and backport the changes that allow providing Ls to the v3 branch, but until then, this change is unfortunately blocked.
A workaround that has come in handy in life for me for situations when Cron doesn't cut it is to install a daily cron that runs a more sophisticated date checking program. That code does the complex checking of the current DateTime. If it's "time to run", then I execute the command I want to run.
In your crontab you would have something like:
0 0 * * * /usr/local/bin/first_sat && docker-backup-command
For example, here is a python function that checks the last working day of each month. You can write something similar for your first Saturday of the year case.
import datetime
def is_last_working_day(date=None):
"""
Check if the given date (or today) is the last working day of the month.
Working days are Monday through Friday (weekdays 0-4).
"""
if date is None:
date = datetime.date.today()
# Get the last day of the current month
if date.month == 12:
next_month = date.replace(year=date.year + 1, month=1, day=1)
else:
next_month = date.replace(month=date.month + 1, day=1)
last_day_of_month = next_month - datetime.timedelta(days=1)
# Find the last working day by going backwards from the last day
current_day = last_day_of_month
while current_day.weekday() > 4: # 0-4 are Mon-Fri, 5-6 are Sat-Sun
current_day -= datetime.timedelta(days=1)
return date == current_day
def main() -> bool:
"""
Main function to check if today is the last working day and run tasks.
"""
today = datetime.date.today()
if is_last_working_day(today):
print(f"Today ({today}) is the last working day of the month!")
return True
else:
print(f"Today ({today}) is not the last working day of the month, skipping...")
return False
if __name__ == "__main__":
return main()