Alternatively, you could switch to ksh or zsh which have had case conversion support for decades (long before bash's ${var^^} added in 4.0), though with a different syntax:
#! /bin/ksh - typeset -u upper="$1" printf '%s\n' "$upper"
(also works with zsh; note that in pdksh/mksh, that only works for ASCII letters).
With zsh, you can also use the U parameter expansion flag or the csh style¹ u modifier:
#! /bin/zsh - printf '%s\n' "${(U)1}" "$1:u"
POSIXLY, you can use:
awk -- 'BEGIN{print toupper(ARGV[1])}' "$1"
There's also:
printf '%s\n' "$1" | tr '[:lower:]' '[:upper:]'
But in a few implementations, including GNU tr, that only works for single-byte characters (so in UTF-8 locales, only on ASCII letters, not accented letters of the Latin alphabet, not ligatures, not letters from other alphabets that also have a concept of case such as the Greek or Cyrillic alphabets).
In practice, none of those work for characters that don't have a single-character uppercase version such as ffi whose uppercase is FFI or the German ß. perl's uc (upper case) would work for those:
$ perl -CSA -le 'print uc for @ARGV' -- groß suffix GROSS SUFFIX
Here with -CLSA to specify that the Stdio streams and Arguments are meant to be encoded in UTF-8 as long as UTF-8 is the charmap used in the Locale.
¹ though that modifier specifically is originally from zsh, already there in 1.0 from 1990, copied by tcsh later in 1992 for 6.01.03.
bash?