How-to GuidePublished 10 min read

Running GitHub Actions cron on Japan Time: UTC Conversion Table and Fixing Day Shifts and Delays

You wrote “0 9 * * 1-5” in your GitHub Actions schedule, but the workflow ran at 6 p.m. Japan time. That isn’t a typo: GitHub Actions always reads cron expressions in UTC (Coordinated Universal Time). This article covers how to convert a Japan Standard Time (JST) schedule into a UTC cron expression, with a ready-made table; why times between midnight and 8:59 a.m. shift the weekday and date back by one; how to split a schedule into two lines when one won’t do; and what to do when a schedule runs late or not at all.

Why is it 9 hours off? GitHub Actions cron is always UTC

In GitHub Actions, adding on.schedule with a cron expression to a workflow’s YAML lets you run jobs at set times. That cron expression is read as UTC, no matter which country the repository owner lives in or where the runner is. It is never adjusted to Japan time based on your computer’s clock or any repository setting.

Japan Standard Time (JST) is 9 hours ahead of UTC. So 0:00 UTC is 9:00 in Japan, and 9:00 UTC is 18:00 in Japan. If you write “0 9 * * *” thinking in Japan time, it runs at 9:00 UTC, which is 18:00 in Japan. The “it ran at 6 p.m.” story above is exactly this.

  • Japan time → UTC: subtract 9 from the hour (9:00 → 0:00, 18:30 → 9:30)
  • If the result is negative (Japan time 0:00–8:59), add 24 and treat it as the previous day (8:00 → 23:00 the day before)
  • UTC → Japan time: add 9 to the hour. If it reaches 24 or more, subtract 24 and treat it as the next day
  • Japan has no daylight saving time, so the offset never changes with the season (always 9 hours)

Japan time → UTC cron expression table

Here are common times, with the cron expression you’d write in Japan time next to the UTC expression you actually need in GitHub Actions. Paste the right-hand column straight into - cron: and the job runs at the Japan time on the left (plus the delays explained later).

Desired time (Japan time)Cron in Japan timeWrite this in GitHub Actions (UTC)
Every day 9:000 9 * * *0 0 * * *
Every day 8:3030 8 * * *30 23 * * * (23:30 the day before)
Every day 18:000 18 * * *0 9 * * *
Every day 0:00 (as the date changes)0 0 * * *0 15 * * * (15:00 the day before)
Weekdays (Mon–Fri) 9:000 9 * * 1-50 0 * * 1-5
Weekdays (Mon–Fri) 8:000 8 * * 1-50 23 * * 0-4 (23:00 Sun–Thu)
Weekdays (Mon–Fri) 6:000 6 * * 1-50 21 * * 0-4 (21:00 Sun–Thu)
Weekdays, on the hour from 9:00 to 18:000 9-18 * * 1-50 0-9 * * 1-5
Every Monday 0:000 0 * * 10 15 * * 0 (Sunday 15:00)
Every 2 hours (0, 2, 4…)0 */2 * * *0 1-23/2 * * *
15th of every month 9:000 9 15 * *0 0 15 * *
1st of every month 3:000 3 1 * *Can’t be written exactly in one line (see below)

The rows to watch are the ones that target 0:00–8:59 Japan time. Moving the time back 9 hours lands on the previous day, so the weekday or date has to move back by one as well, not just the hour. If you only fix the hour and turn “weekdays 8:00” into “0 23 * * 1-5”, it runs at 23:00 UTC Monday to Friday, which is 8:00 Tuesday to Saturday in Japan. The job then skips Monday morning and runs on Saturday morning instead.

“Every 2 hours” is another easy one to miss. Japan’s 0, 2, 4…22 o’clock become 15, 17, 19, 21, 23, 1, 3…13 in UTC, all odd hours, so leaving “*/2” as is puts every run an hour off. You need to rewrite it as “1-23/2”, which means the odd hours in UTC.

0:00–8:59 becomes “the previous day”: how to work out the shift

When converting Japan time to UTC, only the hour decides whether the date changes. From 9:00 onward it stays the same day; from 0:00 to 8:59 it becomes the previous day. Here is how to fix the weekday and day-of-month fields.

  1. 1

    Subtract 9 from the hour

    For 8:00, 8 − 9 = −1. If it goes negative, add 24 to get 23 and remember that it shifts to the previous day. If the hour is 9 or more, use the result as is; the date doesn’t move.

  2. 2

    Move the weekday field back by one

    If it shifts to the previous day, subtract 1 from the weekday numbers. Monday–Friday (1-5) becomes Sunday–Thursday (0-4), Monday (1) becomes Sunday (0), and Sunday (0) becomes Saturday (6). If the weekday is *, nothing needs changing.

  3. 3

    Move the day-of-month field back by one

    If you schedule by date, subtract 1 in the same way: the 15th becomes the 14th. But moving the 1st back gives “the last day of the previous month”, which is the 28th to 31st depending on the month, so it can’t be written exactly in a single cron line.

  4. 4

    Check against the next run times

    List about 10 upcoming runs of the converted expression in Japan time and check they match what you meant (runs at 8:00 on Monday mornings, and so on). Mistakes in mental arithmetic show up here.

With a single time you can do this in your head, but once several times, weekdays and dates are combined it quickly gets error-prone. The “Convert Japan time and UTC” tab of the cron expression checker takes a Japan-time expression, outputs the UTC version with weekday and date shifts included, and automatically confirms that the next 40 run times match before and after conversion.

When one line won’t do, split - cron: into two lines

If times before and after 9:00 are mixed, as in “weekdays at 7:00 and 12:00” Japan time, only the 7:00 run moves to the previous day when converted to UTC. Times that shift and times that don’t need different weekday fields, so they can’t be combined into one cron expression.

Japan timeUTC timeWeekday fieldWrite in GitHub Actions
Weekdays 12:003:00 (same day)Keep 1-50 3 * * 1-5
Weekdays 7:0022:00 (previous day)Move back to 0-40 22 * * 0-4

GitHub Actions lets you list several cron entries under on.schedule, so in a case like this write two lines: “- cron: '0 3 * * 1-5'” and “- cron: '0 22 * * 0-4'”. The workflow runs whenever either one matches. If the job needs to know which one triggered it, github.event.schedule contains the cron string that fired, so you can branch on it with an if condition.

Why schedules run late or don’t run, and what to do

Even with the time correctly converted to UTC, GitHub Actions schedules don’t always fire exactly on time. GitHub’s own documentation says runs can be delayed during periods of high load, and that queued jobs may be dropped when it’s busy. Here are the common causes.

SymptomCauseFix
Always 5–30 minutes lateSchedules from all over the world pile up at minute 0 of every hourMove the minute away from 0 (e.g. 17 or 43)
Occasionally a whole run is missingJobs can be dropped from the queue when it’s busyMake the job safe to miss, or consider another mechanism for critical work
“*/1” or “*/3” still only runs every 5 minutesThe shortest schedule interval is 5 minutesUse a different scheduler if you need under 5 minutes
Doesn’t run from a branchSchedules only use the file in the latest commit on the default branchMerge into main (the default branch)
Stopped after being left aloneIn public repositories, schedules are disabled after 60 days without activityRe-enable the workflow from the Actions tab
Doesn’t run in a forkActions are often disabled in forked repositoriesEnable Actions in the fork

Shifting the minute makes the biggest difference. If the job doesn’t need to run at exactly 9:00, writing “17 0 * * *” (9:17 Japan time) avoids the minute-0 rush and visibly shortens the delay. Conversely, for work that must finish before business hours, schedule it 30 minutes to an hour early to allow for delays.

Check after setting it: manual runs and upcoming run times

With a schedule, you don’t know whether it works until the next time comes around. For daily or weekly workflows, a mistake can go unnoticed for days. Checking these three things before and after you push saves rework.

  • Add workflow_dispatch: under on: as well, and run it manually from the Actions tab to confirm the job itself succeeds
  • Paste the expression into the cron expression checker with GitHub Actions selected, list the upcoming runs in Japan time, and check the weekdays and times are what you intended
  • After the first run, add 9 hours to the time in the run history (UTC) and compare it with the Japan time you expected

If you’re unsure about cron syntax itself (the five fields and symbols like */5), the basics are in how to write crontab, with a cheat sheet. For schedules that are hard to express in GitHub Actions cron alone, such as “second Monday of the month” or “end of the month”, cron’s day-of-month/weekday OR rule and how to write nth weekdays and month-end shows how to check the date in the command. To do the same on AWS, see how to write EventBridge cron expressions.

FAQ

How do I run a GitHub Actions cron at 9:00 Japan time?
GitHub Actions reads cron in UTC, so specify 0:00 UTC, which is 9 hours earlier than Japan time. Every day is “0 0 * * *” and weekdays only is “0 0 * * 1-5”. From 9:00 Japan time onward, neither the date nor the weekday shifts, so subtracting 9 from the hour is all you need.
Can I set the GitHub Actions schedule time zone to Japan time?
The cron expression is read as UTC, so the standard approach is to convert the Japan time to UTC. If you only want dates and times inside the job to be Japan time, set TZ: Asia/Tokyo as an environment variable on the step or job. That affects output such as the date command, not the schedule time.
What happens to the weekday when 8:00 a.m. Japan time is converted to UTC?
8:00 Japan time is 23:00 UTC on the previous day, so the weekday moves back one too. Weekdays (Mon–Fri) at 8:00 become 23:00 Sunday–Thursday in UTC, written “0 23 * * 0-4”. If you only fix the hour and write “0 23 * * 1-5”, it runs on Tuesday to Saturday mornings in Japan.
Why doesn’t my GitHub Actions schedule run on time?
Workflows from all over the world pile up at minute 0 of each hour, so runs can be delayed by minutes or tens of minutes, or skipped entirely when it’s busy. Moving the minute away from 0 (to 17, for example) reduces delays. Also check that the workflow is on the default branch, and that in a public repository the schedule hasn’t been disabled after 60 days without activity.
Can GitHub Actions cron run every minute?
No. The shortest interval for a schedule is 5 minutes. Writing “* * * * *” or “*/1 * * * *” still won’t run more often than every 5 minutes. For minute-by-minute monitoring, use a different mechanism, such as an external monitoring service or cron on an always-on server.

Tools in this article

Related articles

How-to Guide10 min read

How to Write crontab, with a Cheat Sheet: The Five Fields, Symbols, Common Examples and Commands

Recurring jobs like “back up every day at 3:00” or “run a monitoring script every 5 minutes” take a single line in crontab on Linux. But the five fields (minute, hour, day of month, month, day of week) and the symbols * , - / are easy to misread at first, and accidents are common: a job that runs 60 times an hour, or one meant to run every 7 minutes that ends up with a 4-minute gap. This article walks through the basics of crontab syntax and what each symbol means, a cheat sheet you can copy from, how to use crontab -e, -l and -r, and what to watch out for when writing the command part, mostly through examples.

Read article →
How It Works10 min read

cron’s Day-of-Month and Day-of-Week Are ORed: Writing Second Mondays, Month-End, Last Fridays and Every Other Week Correctly

You wrote “0 9 1-7 * 1” for “9:00 on the first Monday of every month”, and it ran every Monday plus every day from the 1st to the 7th. This very common cron mistake comes from a rule unique to cron: if you specify both the day of month and the day of week, the job runs on days that match either one. This article explains how the OR works and its exception, how to make crontab handle nth weekdays, month-end, last Fridays and every other week correctly, and how to use L, # and W in AWS EventBridge and Spring, with real run dates as examples.

Read article →
How-to Guide10 min read

How to Write EventBridge cron Expressions: What ? Means, Weekday Numbers, Japan Time and Converting from crontab

EventBridge cron expressions, used on AWS to run Lambda functions or ECS tasks on a schedule, look like Linux crontab but differ in the number of fields, the use of ?, the weekday numbers and the time zone. Write “0 9 * * 1-5” the crontab way and you get an error; copy the weekday numbers across and everything shifts by a day; and times are read in UTC, so you’re 9 hours off from Japan time. This article covers the six fields of an EventBridge cron expression and the ? rule, the weekday numbers, how to use L, W and #, how rate expressions differ, how Japan time works in rules versus Scheduler, and a conversion table for crontab, Kubernetes, Spring and systemd.

Read article →