Understanding the Holiday Calendar
Xi-Batch keeps a calendar of holidays that a repeating job can be told to avoid, in the same way it avoids days of the week.
- One file, one instance
- The calendar is a single file called holfile in the Xi-Batch spool directory - /var/spool/xi/batch/holfile on a default installation. Every job on that scheduler shares it. A second Xi-Batch environment on the same machine has its own spool directory and therefore its own calendar.
- One calendar per host, not per network
- A job's run times are calculated by the scheduler that owns the job, reading that machine's own holfile. Networked hosts do not share a calendar and nothing synchronises them.
- A bitmap, not a list
- The file holds one bit per day of the year, 46 bytes per year, with 1990 as the first year. It is not text and cannot be read or edited with an editor, and there is nothing in it to say what a holiday is called.
- Created automatically
- If the scheduler finds no holfile at start-up it creates an empty one, mode 0644, owned by the batch user. So the file always exists on a running system, and an empty file simply means no holidays are set.
- Read by everyone, changed by administrators
- The file is world-readable, so any user can view the calendar. Changing it needs the Write administration file privilege.
- Years covered
- Days before 1990 are never holidays. The btq calendar screen moves between 1990 and 2099. The bthols command accepts 1990 to 2037 and refuses anything outside that with Invalid year.
Why Use the Holiday Calendar
Business day scheduling:
# Daily at 08:00, skipping weekends and holidays
btr -T "08:00" -r Days:1 -A "Sat,Sun,Hday" daily-report.sh
Month-end processing. Combined with a Monthse repeat this gives "last working day of the month", which is the one pattern the calendar makes possible and nothing else does:
# Last working day of every month at 18:00
btr -T "2026/3/31,18:00" -r Monthse:1:31 -A "Sat,Sun,Hday" month-end.sh
Pick a start date that is itself a working day. Avoidance is applied to calculated times and never to the date given with -T, so a first run set on a holiday will happen on that holiday.
Payroll and similar fixed-weekday runs. Note the direction before relying on this one: for every repeat unit except Monthse, an avoided day moves the run forward. A fortnightly Friday job that lands on a holiday runs on the following working day, not on the Thursday before.
# Every second Friday at 15:00, moving forward off holidays and weekends
btr -T "2026/2/6,15:00" -r Weeks:2 -A "Sat,Sun,Hday" payroll.sh
To run before a holiday rather than after it, the repeat has to be Monthse - that is the only unit whose search goes backward.
How Avoid Days Work with Holidays
Holidays are one entry in the days-to-avoid list, alongside the seven weekday names, and are named Hday. They are independent of the weekend: naming Hday does not imply Sat and Sun, and the shipped default list - Sat and Sun - does not include holidays.
# Replaces the default list: avoids holidays only, and will run on a Saturday
btr -T "09:00" -r Days:1 -A "Hday" job.sh
# Adds to the default list: avoids Sat, Sun and holidays
btr -T "09:00" -r Days:1 -A ",Hday" job.sh
Forward search, for every unit except Monthse:
Schedule: daily 09:00, avoiding Sat, Sun and Hday
Thursday (ordinary day): 09:00 - runs
Friday (holiday): avoided
Saturday: avoided
Sunday: avoided
Monday: 09:00 - runs
Backward search, for Monthse:
Schedule: last day of month, avoiding Sat, Sun and Hday
Month ends on a Friday that is a holiday: Thursday is used
Month ends on a Saturday: Friday is used
Month ends on a Sunday: Friday is used
The calendar is consulted when a run time is calculated, not when the job falls due. A run time is calculated at the end of each run, and when a past time given to btr is advanced into the future. So marking a day as a holiday today does not move a job whose next run time is already set to that day. To apply the change to a job that is already scheduled, advance it by hand:
# Recalculate this job's next run time, applying the current calendar
btjadv <job_number>
A time you set explicitly with the btr -T option is used exactly as given, so the first run of a job can fall on a holiday even though every later one will not.
One safety rule is worth knowing: if a job is somehow set to avoid all seven days of the week, the whole avoid list is ignored, including the holiday entry. The btr command refuses to set that, but the btq time screen can produce it.
Viewing the Holiday Calendar
In btq:
- Press H from the job list or the variable list
- The whole of the current year is displayed, three months to a row
- Holidays are shown in reverse video; Saturdays and Sundays are dimmed and the other days bold, on terminals that support it
- Press P for the previous year, N for the next
- Press Q to leave
Viewing needs no special privilege.
From the command line. There is no command that prints the calendar on its own. What there is, is a job listing escape: %Y prints the holidays falling in the next twelve months, for any job whose avoid list includes Hday.
# List the upcoming holidays as this scheduler sees them
btjlist -N -F "%Y" <job_number_of_a_job_avoiding_Hday>
The dates are printed as dd/mm, or mm/dd in time zones four or more hours behind UTC, comma separated. If the job does not avoid holidays, or has no time set, the escape prints nothing.
Editing the Holiday Calendar
Requires: Write administration file privilege. Without it the calendar screen opens read-only and every attempt to change a day reports cannot update holidays file.
In btq - the route available on every installation:
- Press H from the job list or the variable list
- Move to the date: h and l, or the left and right arrow keys, move a day; j and k move a week; TAB moves to the next month and Shift-TAB to the previous; B goes to 1 January and e to 1 December
- Press t, s or y to mark the day as a holiday; f, u or n to unmark it; ! or ~ to toggle it
- Press P or N to move to another year, or Q to leave
Changes are written to the file when you leave the year - by pressing P, N or Q. There is no separate save step and no confirmation prompt, and there is no undo.
The bthols Command, and Why You Probably Do Not Have It
The reference manual documents a command called bthols that prints a year's holidays as text and reads them back. It is built with the product, and no installation route installs it - it is absent from the RPM, from the Debian package, and from the list the tarball installer copies into the user path. A packaged installation has no command-line holiday editor at all.
On an RPM or Debian installation the btq calendar screen is the only way to change holidays. Plan for that: entering a year of holidays is a keyboard exercise, and it has to be repeated on every host in a network.
On a tarball installation the binary is present in the unpacked distribution even though the installer does not deploy it, as BINARY/bthols under the directory the tarball extracts to. It is delivered without the execute bit and without the set-user-id bit the installed commands carry, so it has to be made executable and run as the batch user or as root:
cd <unpacked distribution>/BINARY
chmod +x bthols
# Print the holidays already set for 2026
./bthols 2026
# Replace 2026's holidays with the contents of a file
./bthols -s -C 2026 holidays-2026.txt
The text format is one line per month: the month name, a colon, and the days separated by spaces. Month names may be given abbreviated or in full and are matched without regard to case; they are always printed in full.
January: 1
April: 3 6
May: 4 25
August: 31
December: 25 28
The options are:
- -d
- Display the year's holidays. This is the default.
- -s
- Set holidays, reading from the named file or from standard input.
- -C
- With -s, clear the year first. Without it, the days read in are added to whatever is already set for that year.
- -r
- Cancel a -C set earlier, for instance one coming from the BTHOLS environment variable.
The year is a required argument and an optional file name follows it. Setting needs the Write administration file privilege. Each input line is checked, and a bad month name or a day number outside the month is reported with the offending line and nothing is written.
There is no import or export tool beyond this. The conversion tools that carry jobs, variables, command interpreters and users between systems have no holiday counterpart, and the xb-backup driver, where it is installed, checks four files and never this one.
Setting Up a Year of Holidays
Whichever route is available, the work is the same: decide the dates, then enter them. The five lines above are a UK working year for 2026 - New Year's Day, Good Friday and Easter Monday, the two May bank holidays, the late-summer bank holiday, Christmas Day and the substitute day for Boxing Day.
Holidays that move - the spring and late-summer bank holidays, Easter, the substitute days when a fixed holiday falls at a weekend - have to be recalculated each year. Keep the list somewhere outside Xi-Batch as well: the calendar file itself records only which days are marked, with no names and no reasons.
Keeping the Calendar Through Backups, Migrations and Removals
A backup must copy the file itself. There is no export command on a packaged installation, and xb-backup covers four files - the saved jobs, the saved variables, the command interpreter table and the user table - with the calendar outside its scope. Copy /var/spool/xi/batch/holfile as part of the system backup.
A migration to another machine has no conversion step. The file is a bitmap indexed by year and day of the year, with no machine-dependent fields, so copying it to the new machine's spool directory is the practical route; restore it with the scheduler stopped, and check the result on the calendar screen afterwards. Where the two machines differ in ways that make a raw copy unattractive, the alternative is to print the calendar, re-enter it through btq, and compare.
Removing the product destroys it. Removing the RPM or the Debian package deletes the whole Xi-Batch spool directory, holfile with it. The tarball de-installer removes only the files it installed, so it leaves the calendar in place. Either way, the next installation's scheduler creates an empty holfile at start-up without saying anything, and the first symptom is jobs running on days they used to skip.
The same applies to a scheduler reinitialisation that clears the spool directory. Take a copy of holfile before any of these operations.
Regional Holiday Calendars
Separate hosts already have separate calendars. Each scheduler reads its own holfile and calculates the times of the jobs it owns, so a network with a host in each region needs nothing extra - set each host's calendar to its own region's holidays and queue each region's jobs on its own host.
Within one scheduler there is only one calendar. If jobs for several regions have to live on one host, the calendar can only be the union of the regions' holidays, and every job then skips every region's days. Where that is unacceptable, the alternative is a second Xi-Batch environment on the same machine, which has its own spool directory and therefore its own calendar.
Verifying the Configuration
Check the calendar itself:
# From btq: press H, check the year, check the marked days
# From the command line, using any job that avoids holidays:
btjlist -N -F "%Y" <job_number>
Check that a job is really using it:
btjlist -H -F "%N %H %T %r %a" <job_number>
The Avoiding column must contain Hday. A job created without an explicit avoid list carries the shipped default of Sat and Sun alone, which leaves holidays unavoided.
Check the effect:
# A daily job that avoids weekends and holidays
btr -T "09:00" \
-r Days:1 \
-A "Sat,Sun,Hday" \
-h "Holiday test job" \
/bin/true
# Read the next run time back in full
btjlist -N -F "%T" <job_number>
Then advance it repeatedly with btjadv and read %T again after each step; the sequence of dates it produces is exactly what the scheduler will use.
Best Practices
Load next year's holidays in December:
A repeating job whose next run time is calculated in December against an empty next year lands on a day that has not been marked yet, and the change made afterwards will not move it.
Keep the source list outside Xi-Batch:
The calendar records marked days and nothing else. Keep a dated text list of the holidays with their names, so the next person can tell an intentional holiday from a mistake.
Back up holfile explicitly:
No conversion tool covers it and the packaged removals delete it. It belongs on the same list as the master configuration file and the hosts file.
Apply the same calendar to every host in a network:
Unless the hosts really are in different regions, a calendar set on one machine and not the others produces jobs that skip a holiday on one host and run on another, with nothing to indicate why.
Check the direction before relying on avoidance:
Everything except Monthse moves forward off an avoided day. If the requirement is "the last working day before", the repeat has to be Monthse.
Re-advance jobs after a late change:
Adding a holiday does not move a job whose next run time is already set. Use btjadv on the jobs concerned, and confirm with %T.