Calculating with the date function

I have been experimenting with calculating the number of days between two dates for a script that I am writing.
This works fine:

keith@E5570:~$ echo $(( ($(date +%s) - $(date -d 5/31/2025 +%s))/86400 ))
8
keith@E5570:~$

where the “%s” means report the number of seconds since the reference date of 1/1/2007 and 86400 is the number seconds in one day.
A minor irritation is that I have to specify the subtractant date in USA format, i.e. mm/dd/yyyy rather than my UK locale format of dd/mm/yyyy.
Curiously my locale is set correctly:

keith@E5570:~$ locale
LANG=en_GB.UTF-8
LANGUAGE=
LC_CTYPE=“en_GB.UTF-8”
LC_NUMERIC=“en_GB.UTF-8”
LC_TIME=“en_GB.UTF-8”
etc etc…

Digging further, we get get detailed info on the en_GB locale at /usr/share/i18n/locales/en_GB where we find under the LC_TIME paragraph:

d_t_fmt “%a %d %b %Y %T %Z” (e.g. Sat 24 Jun 2025 15:02:30 GMT)**
d_fmt “%d//%m//%y” (e.g. 24/06/2025)
t_fmt “%T”
am_pm “am”;“pm”
t_fmt_ampm “%l:%M:%S %P %Z”
date_fmt “%a %e %b %H:%M:%S %Z %Y”
(e.g. simple date command → Sat 24 Jun 15:02:30 GMT 2025 correctly)
week 7;19971130;4
first_weekday 2

Which looks fine except that when I specify a date (e.g. date -d …) it only accepts mm/dd/yyyy format:

keith@E5570:~$ date -d 5/31/2005
Tue 31 May 00:00:00 BST 2005
keith@E5570:~$ date -d 31/5/2005
date: invalid date ‘31/5/2005’
keith@E5570:~$

Even trying to specify the format fails:

keith@E5570:~$ date -d 31/5/2005 +%d/%m/%Y
date: invalid date ‘31/5/2005’
keith@E5570:~$

but

keith@E5570:~$ date -d 5/31/2005 +%d/%m/%Y
31/05/2005
keith@E5570:~$

formats correctly. This doesn’t matter, now I know about it, but it’s irritating.
Any ideas, anyone?

So :slight_smile: this is a “tools for the job” question, I would always start from Python;

from datetime import datetime
date_format = "%d/%m/%Y"
a = datetime.strptime('31/05/2005', date_format)
b = datetime.now()
delta = b - a
print(delta.days) 

The benefits of using this over bash;

  • The code is readable, i.e. you can immediately see what it’s doing
  • Changing the date format is trivial (to include time or US format)
  • Python is somewhat faster than bash (!)

If you really need to use bash for other stuff, then just write a 10-line Python script (as above) then call it from bash, for example;

from sys import argv
from datetime import datetime
if len (argv) < 2:
  print (f'usage: {argv[0]} <date> [<format>]')
  exit
date_format = "%d/%m/%Y" if len(argv) < 3 else argv[2]
a = datetime.strptime(argv[1], date_format)
b = datetime.now()
delta = b - a
print(delta.days)

If you call it “date.py” then do “chmod +x date.py”;

$ ./date.py
usage: ./date.py <date> [<format>]
$ ./date.py 09/03/2025
91
$ ./date.py 09/03/2025 "%m/%d/%Y"
-87
$ a=$(./date.py 09/03/2025 "%m/%d/%Y")
$ echo $a
-87

If you really must do it in bash, then revert to an expert like Gemini … :wink:

#!/bin/bash

# Check if a date is provided
if [ -z "$1" ]; then
  echo "Usage: $0 <DD/MM/YYYY>"
  exit 1
fi

provided_date_str="$1"

# Set locale to en_GB.UTF-8 to ensure date command interprets DD/MM/YYYY correctly
# This is a good practice, though 'date -d' is often flexible.
export LC_ALL="en_GB.UTF-8"

# Validate the provided date format and attempt to convert to YYYY-MM-DD for date -d
# We'll use awk to reformat the date string
reformatted_date=$(echo "$provided_date_str" | awk -F'/' '{print $3"-"$2"-"$1}')

# Check if the reformatted date is valid
if ! date -d "$reformatted_date" &> /dev/null; then
  echo "Error: Invalid date format or date. Please use DD/MM/YYYY, e.g., 25/12/2024."
  exit 1
fi

# Convert provided date to Unix timestamp
provided_date_timestamp=$(date -d "$reformatted_date" +%s)

# Get current date as Unix timestamp
current_date_timestamp=$(date +%s)

# Calculate the difference in seconds
difference_in_seconds=$(( current_date_timestamp - provided_date_timestamp ))

# Convert seconds to days
# Integer division will naturally floor the result, giving you whole days
difference_in_days=$(( difference_in_seconds / (60 * 60 * 24) ))

echo "Number of days from $provided_date_str until the current date: $difference_in_days"

I think the key takeaway here is that if you set the LC_ALL environment variable correctly then bash’s “date” command should work with the date formats you’re expecting :slight_smile:

As always I am grateful for the effort that you have put in to your reply!
And, as is often the case, I struggle to follow what appear to the novice as really complicated solutions.
Python commands (like any language) are easy to follow only if one is familiar with the language. I did make a start on learning Python some years ago it but fell by the wayside as BASH felt more straightforward to me. Horses for courses, I guess.

I can’t find an environment variable LC_ALL that I could alter, and the Locale variables appear to be correct, but just not working.

My application is a simple start-up script to read the date of my last backup and tell me (by espeak) how many days it’s been since then. All I have to do is ensure that the saved date is in USA format and Bob’s-your-uncle. No extra lines of code.
I think I will stick with the easy option, but am grateful for the detailed response.

It’s just this;

$ export LC_ALL="en_GB.UTF-8"
$ date
Sun  8 Jun 19:12:43 BST 2025
$ export LC_ALL="en_US.UTF-8"
$ date
Sun Jun  8 07:13:21 PM BST 2025

So once LC_ALL is set, when you use “date” it will work within that locale.

It’s already outputting that UK format for the plain date command - just as it’s supposed to. My problem is when I specify a date it will only accept USA format. e.g.:

keith@E5570:~$ date -d 5/31/2005
Tue 31 May 00:00:00 BST 2005
keith@E5570:~$ date -d 31/5/2005
date: invalid date ‘31/5/2005’
keith@E5570:~$

But it’s not worth worrying about - I just wondered if I was doing something incorrectly. But it looks like there is a bug in the date command.

Many thanks for your help.

Yeah it’s not a bug, I think it’s what they call a ‘feature’. To avoid any confusion, if you’re going to use “/” for input, it’s always m/d/y. Then for output it will use the locale settings. What you it can do;

$ env LC_ALL="en_GB.UTF-8" date -d "31-May-2005"
Tue 31 May 00:00:00 BST 2005

or as per the script above;

reformatted_date=$(echo "$provided_date_str" | awk -F'/' '{print $3"-"$2"-"$1}')

swaps the d and m

That’s helpful.
I’m away from home at present - with an unreliable laptop. Might be a day or so before I can reply.