cal and date Commands in Linux: Complete Guide to Calendar Display, Date Settings, and Parameters

cal and date command in Linux and it perimeters

Between the two commands in this guide, I use date constantly — in scripts, logs, cron expressions, filenames — and cal occasionally, mostly when I want a quick visual calendar without opening anything else. They seem like small, boring utilities, but date in particular has a genuinely deep feature set once you get past date with no arguments, and it’s worth knowing well because so much shell scripting depends on formatting and calculating dates correctly.

cal: Displaying a Calendar

cal prints a simple text calendar to the terminal. It’s not installed by default on every minimal system or container image — it typically comes from the util-linux package (as cal) or bsdmainutils/ncal package depending on distribution, so if you get a “command not found,” a quick sudo apt install util-linux or sudo apt install ncal (Debian/Ubuntu) usually resolves it.

Basic Usage

cal

This prints the current month, with today’s date typically highlighted, in a layout like:

     July 2026
Su Mo Tu We Th Fr Sa
          1  2  3  4
 5  6  7  8  9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

Common Options

cal 2026

Prints all twelve months of the specified year in a grid layout.

cal 7 2026

Prints a specific month (July) and year.

cal -3

Shows the previous, current, and next month side by side — genuinely useful when you’re planning something that spans a month boundary.

cal -y

Shows the entire current year (equivalent to cal $(date +%Y)).

cal -m monday

On many cal implementations, this changes the calendar to start weeks on Monday instead of Sunday, matching ISO 8601 convention used in most of the world outside the US.

Related: ncal

Some distributions provide ncal, a variant with a different (often more compact, vertical) layout and additional options like -w to show week numbers:

ncal -w

cal is genuinely one of the simplest commands in this whole guide — there’s not much internal complexity to it. It reads the system’s current date (ultimately from the same kernel time source date uses) and formats a static grid. Its real value is purely visual/human convenience, not scripting — for anything script-related involving dates, date is the tool you actually want.

date: The Real Workhorse

date displays or sets the system date and time, and supports an extensive set of format specifiers that make it one of the most frequently used utilities in shell scripting.

Basic Usage

date
Fri Jul 31 01:38:14 UTC 2026

Custom Output Formatting

The real power of date is its +FORMAT syntax, where you supply a format string built from %-prefixed specifiers:

date +"%Y-%m-%d %H:%M:%S %Z"
2026-07-31 01:38:14 UTC
date +"%A, %B %d, %Y"
Friday, July 31, 2026

Common format specifiers:

SpecifierMeaning
%Y4-digit year
%y2-digit year
%mMonth (01–12)
%dDay of month (01–31)
%HHour, 24-hour format
%IHour, 12-hour format
%MMinute
%SSecond
%AFull weekday name
%aAbbreviated weekday name
%BFull month name
%bAbbreviated month name
%jDay of year (001–366)
%ZTimezone abbreviation
%sUnix epoch timestamp (seconds since 1970-01-01 UTC)
%NNanoseconds
%uDay of week (1 = Monday .. 7 = Sunday, ISO 8601)

Checking the current epoch timestamp — extremely common in logging and scripting:

date +"%s"
1785462236

Getting the day-of-year for a specific date, without needing to count manually:

date -d "2026-01-01" +"%j"
001

Calculating Relative Dates with -d / –date

This is where date becomes genuinely powerful for scripting — it understands human-readable relative date expressions:

date -d "next monday"
Mon Aug  3 00:00:00 UTC 2026
date --date="1 week ago"
Fri Jul 24 01:43:56 UTC 2026

Other expressions that work: "tomorrow", "yesterday", "2 days", "3 months", "next year", "last friday", and combinations like "2026-01-01 + 90 days". This is a huge time-saver over manually calculating date math with %s epoch arithmetic, though epoch math is still the right tool when you need exact, unambiguous calculations across timezone/DST boundaries.

Timezone Handling

date -u
Fri Jul 31 01:43:56 UTC 2026

Prints the current time in UTC regardless of the system’s configured local timezone — the -u/--utc flag is essential for consistent logging across distributed systems in different timezones.

TZ="America/New_York" date
Thu Jul 30 21:43:56 EDT 2026

Temporarily overrides the timezone for a single command by setting the TZ environment variable inline, without changing the system’s actual configured timezone.

Setting the System Date and Time

sudo date -s "2026-08-01 09:00:00"

Directly sets the system clock. In practice, on any modern system with NTP (Network Time Protocol) synchronization active, manually setting the date this way is usually temporary — the NTP daemon will correct it back on the next sync. For permanent timezone or sync configuration, the correct tool on systemd-based distributions is timedatectl:

timedatectl status
timedatectl set-timezone America/New_York
timedatectl set-ntp true

Note that timedatectl requires the system to be running under systemd as PID 1 — it won’t function inside minimal containers or non-systemd init systems, where you’d fall back to manually editing /etc/timezone and /etc/localtime (a symlink into /usr/share/zoneinfo/), or using ntpdate/chronyd directly.

ISO 8601 and Standardized Output

date -I
2026-07-31
date --iso-8601=seconds
2026-07-31T01:38:14+00:00
date --rfc-3339=seconds

Produces RFC 3339 formatted output, similar to ISO 8601 but with a space instead of T separating date and time — the format many logging systems and APIs expect.

Practical Sysadmin Use Cases

Timestamping backup filenames:

tar -czf backup_$(date +%Y%m%d_%H%M%S).tar.gz /etc

Produces something like backup_20260731_014300.tar.gz — sortable by filename, unambiguous, no spaces.

Logging with consistent, greppable timestamps in a script:

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
log "Starting deployment"

Calculating a certificate or log rotation cutoff date:

cutoff=$(date -d "30 days ago" +%Y-%m-%d)
find /var/log/myapp -name "*.log" -newermt "$cutoff" -delete

Measuring script execution duration using epoch math:

start=$(date +%s)
# ... work happens here ...
end=$(date +%s)
echo "Elapsed: $((end - start)) seconds"

Cross-timezone coordination for scheduled maintenance:

TZ="Asia/Tokyo" date -d "2026-08-15 09:00:00 UTC"

Troubleshooting

Security Implications

Accurate system time matters more for security than people usually assume: TLS certificate validation depends on correct system time (a clock too far in the past or future can cause valid certificates to be rejected, or expired ones to be silently accepted), and many authentication protocols (like Kerberos, and TOTP-based two-factor authentication) have strict time-synchronization tolerances — a drifted clock can cause legitimate logins to fail or, in some misconfigured cases, weaken security assumptions. Keeping NTP sync active and monitored is a genuinely important, if unglamorous, security hygiene item.

date/cal vs Related Tools

ToolPurpose
dateDisplay/format/calculate dates and times; set system clock
calHuman-readable visual calendar display only
timedatectlManage system time, timezone, and NTP sync (systemd systems)
hwclockRead/set the hardware (BIOS/RTC) clock directly, independent of the OS
chronyc / ntpqInspect and manage NTP synchronization state

Compatibility Across Distributions

date is provided by GNU coreutils on virtually every Linux distribution (Debian, Ubuntu, RHEL, Fedora, Arch, openSUSE), so its extensive -d/+FORMAT feature set is consistent across all of them. cal availability varies more — it’s standard on most desktop-oriented installs but sometimes absent from minimal server or container images, requiring a manual install. Be aware that macOS and BSD systems ship a fundamentally different (non-GNU) date implementation with different relative-date syntax (-v flags instead of -d strings) — scripts intended to be portable across Linux and macOS need to account for this difference explicitly, often by detecting the OS and branching, or using a portable epoch-based approach instead.

Shell Scripting Patterns Worth Knowing

A few patterns come up often enough in real automation work that they’re worth having memorized rather than looked up each time.

Generating a range of dates for a report:

for i in {0..6}; do
    date -d "$i days ago" +%Y-%m-%d
done

This produces the last seven calendar days in order, which is a common building block for generating date-partitioned report filenames or querying a week’s worth of logs one day at a time.

Comparing two dates numerically:

d1=$(date -d "2026-01-01" +%s)
d2=$(date -d "2026-06-15" +%s)
diff_days=$(( (d2 - d1) / 86400 ))
echo "Difference: $diff_days days"

Converting both dates to epoch seconds first sidesteps all the messiness of manually accounting for month lengths, leap years, and daylight saving transitions — the kernel’s date arithmetic already handles all of that correctly, so leaning on %s for comparisons is far more reliable than trying to do calendar math by hand in a script.

Validating that a string is actually a well-formed date before using it:

if date -d "$user_input" >/dev/null 2>&1; then
    echo "Valid date"
else
    echo "Invalid date string"
fi

This is a genuinely useful guard in any script that accepts a date as a parameter from a user or config file, since date -d will happily fail loudly (and predictably, via a non-zero exit code) on garbage input rather than silently producing something wrong.

A Note on Locale Behavior

Both cal and date are locale-aware, meaning weekday names, month names, and the first day of the week can change depending on the system’s configured locale (LANG/LC_TIME environment variables). A server configured with LC_TIME=de_DE.UTF-8, for instance, will print German month and day names by default. This is worth knowing if a script’s output looks unexpectedly “wrong” on one server but not another — it’s very often a locale difference, not a bug. You can force a specific, predictable locale for a single command without changing the system default:

LC_TIME=C date +"%A, %B %d, %Y"

Setting LC_TIME=C (the “POSIX”/default locale) guarantees consistent, English, unambiguous output — a good habit for scripts and logs that might run on differently-configured systems, where locale-dependent formatting could otherwise silently break downstream parsing.

Handling Leap Years and Edge Cases

Date arithmetic edge cases are exactly where manual calculation goes wrong and date -d proves its worth. Leap years, month-end rollovers, and daylight saving transitions are all handled correctly by GNU date without any special-casing on your part:

date -d "2028-02-29 + 1 year" +%Y-%m-%d
2029-03-01

Notice that adding exactly one year to a leap-day date correctly rolls forward to March 1st in a non-leap year, rather than producing an invalid February 29th — this is the kind of edge case that’s easy to get subtly wrong in a hand-rolled date calculation, and exactly why offloading date math to date (or a proper date-handling library in a scripting language, for more complex applications) is worth doing rather than reimplementing calendar logic yourself.

Summary

cal is a small convenience for visually checking a calendar in the terminal, while date is genuinely one of the most-used tools in day-to-day Linux scripting — from timestamped filenames and log lines to relative date math and timezone-aware calculations. Learning its +FORMAT specifiers and -d relative date syntax pays off constantly, since nearly every non-trivial shell script eventually needs to know or calculate “what time is it, in what format, relative to what.”

References

Exit mobile version