public static function isUrn($value): bool
    {
        if (!static::isStringCastable($value)) {
            return false;
        }

        // http://tools.ietf.org/html/rfc2141#section-2.1
        // To avoid confusion with the "urn:" identifier, the NID "urn" is
        // reserved and MUST NOT be used
        if (substr((string) $value, 0, 8) === "urn:urn:") {
            return false;
        }

        // http://tools.ietf.org/html/rfc2141
        //
        // <URN> ::= "urn:" <NID> ":" <NSS>
        //
        // <NID> ::= <let-num> [ 1,31<let-num-hyp> ]
        // <let-num-hyp> ::= <upper> | <lower> | <number> | "-"
        // <let-num>     ::= <upper> | <lower> | <number>
        // <upper>       ::= "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" |
        //                   "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" |
        //                   "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" |
        //                   "Y" | "Z"
        // <lower>       ::= "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" |
        //                   "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" |
        //                   "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" |
        //                   "y" | "z"
        // <number>      ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" |
        //                   "8" | "9"
        //
        // <NSS>         ::= 1*<URN chars>
        // <URN chars>   ::= <trans> | "%" <hex> <hex>
        // <trans>       ::= <upper> | <lower> | <number> | <other> | <reserved>
        // <hex>         ::= <number> | "A" | "B" | "C" | "D" | "E" | "F" |
        //                   "a" | "b" | "c" | "d" | "e" | "f"
        // <other>       ::= "(" | ")" | "+" | "," | "-" | "." |
        //                   ":" | "=" | "@" | ";" | "$" |
        //                   "_" | "!" | "*" | "'"
        //
        // Therefore, these characters are RESERVED for future developments.
        // Namespace developers SHOULD NOT use these characters in unencoded
        // form, but rather use the appropriate %-encoding for each character.
        //
        // In addition, octet 0 (0 hex) should NEVER be used, in either
        // unencoded or %-encoded form.
        $pattern = sprintf(
            '/\Aurn:%s:%s\z/i',
            '[a-z0-9](?:[a-z0-9\-]{1,31})?',
            '(?:[a-z0-9()+,\-.:=@;$_!*\']|%(?:0[a-f1-9]|[a-f1-9][a-f0-9]))+'
        );

        return !!preg_match($pattern, (string) $value);
    }