docker-volume-backup icon indicating copy to clipboard operation
docker-volume-backup copied to clipboard

Question about scheduling the first Saturday of January using Cron and date command

Open CrypticCommit opened this issue 7 months ago • 7 comments

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.

CrypticCommit avatar Apr 05 '25 15:04 CrypticCommit

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'

CrypticCommit avatar Apr 05 '25 16:04 CrypticCommit

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 avatar Apr 05 '25 19:04 m90

@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.

CrypticCommit avatar Apr 06 '25 15:04 CrypticCommit

Wouldn't this fork only support L when your use case would need #?

m90 avatar Apr 06 '25 17:04 m90

Wouldn't this fork only support L when your use case would need #?

True, but in my use case I do not really care about first (SAT#1) or last one (L). 😄

CrypticCommit avatar Apr 15 '25 16:04 CrypticCommit

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?

m90 avatar Apr 16 '25 10:04 m90

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.

m90 avatar Jun 09 '25 12:06 m90

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()

dlardo avatar Nov 11 '25 01:11 dlardo