Brief
Since I don't know which programming language you are using, I'll present you with regex in the PCRE flavour. This allows such things as negative lookbehinds, which, you will see in the username validation.
Also, you shouldn't restrict the password length to 16.
Code
Username
See this code in use here
^(?![_ -])(?:(?![_ -]{2})[\w -]){5,16}(?<![_ -])$
Password
See this code in use here
^(?:(?=.*?\p{N})(?=.*?[\p{S}\p{P} ])(?=.*?\p{Lu})(?=.*?\p{Ll}))[^\p{C}]{8,16}$
Results
Input - Username
** VALID **
usern
username-is-writ
username is here
username_is_here
** INVALID **
user
username-is-written-here
-username
_username
username
username-
username_
username
username--here
username here
username__here
username- here
username _here
Note: The 8th invalid input above has a trailing space
Output - Username
usern
username-is-writ
username is here
username_is_here
Input - Password
********** VALID **********
Password1!
TestPass###231
My Pass#123~12`1
!#$Afs1!@(*''
VDFt35q#@$@
éA1!@#!@#!
********* INVALID *********
PASSWORD1!
password1!
Password1
Password
Passw1!
ThisIsMySuperLongPassword1!
Ae! 1
Note: The last invalid example above uses tabs (an invalid character)
Output - Password
Password1!
TestPass###231
My Pass#123~12`1
!#$Afs1!@(*''
VDFt35q#@$@
éA1!@#!@#!
Explanation
Username
^
Assert position at start of line.
(?![_ -]
Negative lookahead to ensure that the username does not begin with the characters in the set _ -
(?:(?![_ -]{2})[\w -]){5,16}
- Match between 5 and 16 characters where the following format is met.
(?![_ -]{2})
Negative lookahead to ensure that there are no two characters in the set _ -
that immediately follow each other.
[\w -]
Match any word character (a-zA-Z0-9_
) or space or -
(?<![_ -]
Negative lookbehind to ensure that the username does not end with the characters in the set _ -
$
Assert position at end of line.
Password
^
Assert position at start of line
(?:(?=.*?\p{N})(?=.*?[\p{S}\p{P} ])(?=.*?\p{Lu})(?=.*?\p{Ll}))
Grouping of positive lookaheads for easier visibility, can remove non-capture group if prefered. Each positive lookahead that follows uses .*?
. This will match any character any number of times, but as few as possible.
(?=.*?\p{N})
Positive lookahead ensuring that at least one numeric character is present.
(?=.*?[\p{S}\p{P} ]
Positive lookahead ensuring that at least one symbol, punctuation, or space is used.
(?=.*?\p{Lu})
Positive lookahead ensuring that at least one uppercase letter is used.
(?=.*?\p{Ll})
Positive lookahead ensuring that at least one lowercase letter is used.
[^\p{C}]{8,16}
Match any non-control character between 8 and 16 times.
$
Assert position at end of line.