Execute Jobs Every 30 Minutes Weekdays 9-5 | CronBase

cron expression Quartz
$ 0 0/30 9-17 ? * MON-FRI

Every 30 minutes on weekdays between 9 AM and 5 PM

0
Second
0/30
Minute
9-17
Hour
?
Day of Month
*
Month
MON-FRI
Day of Week

* In a Nutshell

The cron expression 0 0/30 9-17 ? * MON-FRI runs Every 30 minutes on weekdays between 9 AM and 5 PM. This cadence is ideal for operational tasks requiring frequent updates during standard business hours, such as real-time system monitoring or frequent data synchronization. It avoids running during off-hours, conserving resources while ensuring timely operational visibility and data freshness when it matters most.

* When to use this

Use 0 0/30 9-17 ? * MON-FRI when a recurring task needs to run Every 30 minutes on weekdays between 9 AM and 5 PM. This schedule is commonly associated with api polling and business hours schedules and weekday schedules workloads. It uses Quartz Scheduler (6–7 Fields) syntax, supported by Unix cron daemons, cloud schedulers such as AWS EventBridge, and container orchestration platforms such as Kubernetes CronJob.

CronBase parses 0 0/30 9-17 ? * MON-FRI using a dialect-aware rules engine that identifies the Quartz Scheduler (6–7 Fields) format, validates field structure against the Quartz Scheduler (6–7 Fields) specification, and produces the translation above. Next run times are calculated by forward-scanning from the current UTC clock. Learn how CronBase works.

Platform Implementations

Bash

Prerequisites

Unix/Linux host with a cron daemon running (vixie-cron, cronie, or systemd-cron). The script at /usr/local/bin/run-task.sh must exist and be executable (chmod +x).

Configuration

Run crontab -e to open the crontab editor. Cron reads the server's local timezone; prefix with CRON_TZ=UTC before the expression to pin to UTC. Always redirect output with >> /var/log/cron-tasks.log 2>&1 to capture both stdout and stderr.

Gotchas

Cron jobs inherit a minimal PATH — use full binary paths or set PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin at the top of the crontab. For sub-hourly schedules, add flock -n /tmp/run-task.lock before the command to skip overlapping runs if the previous execution is still running.

bash
# Add to crontab with: crontab -e
0/30 9-17 * * MON-FRI /usr/local/bin/run-task.sh >> /var/log/cron-tasks.log 2>&1

Last verified:

Nodejs

Prerequisites

Node.js 18+ with node-cron installed (npm install node-cron). Add "type": "module" to package.json to use the import syntax shown above.

Configuration

Pass { timezone: 'UTC' } as the third argument to cron.schedule(). Without this option the schedule uses the Node.js process timezone, which shifts during DST transitions when servers are not pinned to UTC.

Gotchas

node-cron uses standard 5-field cron syntax — not Quartz 6-field. If your job runs longer than the schedule interval the next trigger fires while the previous is still executing. Use a boolean guard or a queue to skip concurrent runs rather than relying on the scheduler to enforce it.

nodejs
import cron from 'node-cron';

cron.schedule('0/30 9-17 * * MON-FRI', async () => {
  console.log('[cron] running at', new Date().toISOString());
  // your task logic here
}, { timezone: 'UTC' });

Last verified:

Python

Prerequisites

Python 3.8+ with apscheduler installed (pip install apscheduler). For Python 3.12+ use apscheduler>=3.10.

Configuration

CronTrigger.from_crontab() accepts a standard 5-field cron string. Always pass timezone='UTC' to both the BlockingScheduler constructor and the trigger to ensure consistent scheduling regardless of the server's locale.

Gotchas

BlockingScheduler.start() blocks the calling thread indefinitely. For a web application, use BackgroundScheduler instead and call scheduler.start() at application startup. APScheduler logs missed fires if the process is stopped and restarted — configure misfire_grace_time (in seconds) to control how late a missed job is allowed to run.

python
from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.triggers.cron import CronTrigger

scheduler = BlockingScheduler(timezone='UTC')

@scheduler.scheduled_job(
    CronTrigger.from_crontab('0/30 9-17 * * MON-FRI', timezone='UTC')
)
def run_task() -> None:
    print('task running')
    # your task logic here

scheduler.start()

Last verified:

Golang

Prerequisites

Go 1.18+ and github.com/robfig/cron/v3 (go get github.com/robfig/cron/v3).

Configuration

Always create the scheduler with cron.New(cron.WithLocation(time.UTC)). Without WithLocation, the library defaults to the server's local timezone. Call c.Start() to begin the scheduler, then block with select {} to keep the process alive.

Gotchas

robfig/cron v3 uses standard 5-field cron expressions by default. To add second-level precision (6 fields), pass cron.WithSeconds() to cron.New() — but this changes field positions, so never mix syntaxes in the same scheduler. Always check the error returned by c.AddFunc() — a malformed expression is silently ignored without it.

golang
package main

import (
	"fmt"
	"time"

	"github.com/robfig/cron/v3"
)

func main() {
	c := cron.New(cron.WithLocation(time.UTC))
	_, err := c.AddFunc("0/30 9-17 * * MON-FRI", func() {
		fmt.Println("task running at", time.Now().UTC())
		// your task logic here
	})
	if err != nil {
		panic(err)
	}
	c.Start()
	defer c.Stop()
	select {}
}

Last verified:

Java

Prerequisites

Spring Boot 2.7+ (or Spring Framework 5.3+) with spring-context on the classpath. Annotate your @SpringBootApplication class with @EnableScheduling — without it, @Scheduled methods are silently ignored.

Configuration

Spring @Scheduled uses a 6-field Quartz-style expression: [sec] [min] [hr] [dom] [mon] [dow]. The expression 0 0/30 9-17 ? * MON-FRI is derived from 0 0/30 9-17 ? * MON-FRI — a leading 0 is prepended for the seconds field, and ? replaces the unconstrained day field.

Gotchas

Standard Unix cron has 5 fields; Spring requires 6. Pasting a 5-field expression directly into @Scheduled shifts every field by one and the job misfires silently. Use ? for either dom or dow — Quartz does not allow both to be *.

java
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class ScheduledTask {

    @Scheduled(cron = "0 0/30 9-17 ? * MON-FRI")
    public void runTask() {
        System.out.println("task running at: " + java.time.Instant.now());
        // your task logic here
    }
}

Last verified:

Kubernetes

Prerequisites

Kubernetes 1.21+ (CronJob API is GA). kubectl configured with cluster access. Container image must be pullable from within the cluster.

Configuration

Apply with kubectl apply -f cronjob.yaml. Check status with kubectl get cronjobs and inspect run history with kubectl get jobs. concurrencyPolicy: Allow is set because this schedule fires infrequently — parallel runs are acceptable.

Gotchas

Without startingDeadlineSeconds, a missed job (e.g., due to cluster downtime) triggers as soon as the controller recovers. Kubernetes 1.25+ supports timeZone: UTC in the spec to avoid timezone ambiguity. Keep successfulJobsHistoryLimit and failedJobsHistoryLimit low to avoid accumulating stale Job objects in the cluster.

kubernetes
apiVersion: batch/v1
kind: CronJob
metadata:
  name: scheduled-task
spec:
  schedule: "0/30 9-17 * * MON-FRI"
  concurrencyPolicy: Allow
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
          - name: task
            image: alpine:3.19
            command: ["/bin/sh", "-c", "echo 'task running'"]

Last verified:

Frequently Asked Questions

What does the '0 0/30 9-17 ? * MON-FRI' cron expression do?

This expression is set to run jobs at minutes 0 and 30 past the hour, every hour from 9 AM to 5 PM, Monday through Friday. It's designed for operations that need to happen regularly but only during specific business working hours.

How does this schedule handle timezones and Daylight Saving Time changes?

Cron schedules typically operate based on the server's configured timezone. Adjustments for Daylight Saving Time are usually handled automatically by the operating system or the scheduling platform, ensuring your jobs run at the intended local time.

How can I verify that my job is running according to this schedule?

You can verify job execution by checking your system's logs for entries related to the job's start and end times, or by looking at the job's history within your scheduling tool. Ensure the logs show runs at the correct 30-minute intervals during the specified weekdays and hours.

What potential issues should I be aware of with this schedule?

A primary concern is ensuring jobs complete within their 30-minute window. If a job takes longer than this, subsequent runs might overlap. It's crucial to monitor job duration or implement concurrency controls to prevent overlapping executions.

What is a common variant or alternative to this schedule?

A common variant might be to run every hour on the hour during business days. Alternatively, you could widen the interval slightly, for instance, running every 15 minutes on the hour and half-hour, to increase check frequency.

More schedules like this

Explore API Polling →

* Try any expression

Standard, Quartz, AWS EventBridge, Jenkins, named schedules (@daily, @hourly…)

* Keep Exploring

Related expressions you might need