I found this document on MS Connect:
http://connect.microsoft.com/SQLServer/feedback/details/259534/patindex-missing-escape-clause
The user asks about ESCAPE clause with PATINDEX, then another user extends the request for CHARINDEX as well.
MS answer: Ticket closed as Won't fix :(
I finished writing my own custom function for LTrim:
CREATE FUNCTION LTrim_Chars (
  @BaseString varchar(2000),
  @TrimChars varchar(100)
)
RETURNS varchar(2000) AS
BEGIN
  DECLARE @TrimCharFound bit
  DECLARE @BaseStringPos int
  DECLARE @TrimCharsPos int
  DECLARE @BaseStringLen int
  DECLARE @TrimCharsLen int
  IF @BaseString IS NULL OR @TrimChars IS NULL
  BEGIN
      RETURN NULL
  END
  SET @BaseStringPos = 1
  SET @BaseStringLen = LEN(@BaseString)
  SET @TrimCharsLen = LEN(@TrimChars)
  WHILE @BaseStringPos <= @BaseStringLen
  BEGIN 
      SET @TrimCharFound = 0
      SET @TrimCharsPos = 1
      WHILE @TrimCharsPos <= @TrimCharsLen
      BEGIN     
          IF SUBSTRING(@BaseString, @BaseStringPos, 1) = SUBSTRING(@TrimChars, @TrimCharsPos, 1)
          BEGIN
              SET @TrimCharFound = 1
              BREAK
          END             
          SET @TrimCharsPos = @TrimCharsPos + 1     
      END
      IF @TrimCharFound = 0
      BEGIN
        RETURN SUBSTRING(@BaseString, @BaseStringPos, @BaseStringLen - @BaseStringPos + 1)
      END       
      SET @BaseStringPos = @BaseStringPos + 1
  END
  RETURN ''
END
And for RTrim:
CREATE FUNCTION RTrim_Chars (
  @BaseString varchar(2000),
  @TrimChars varchar(100)
)
RETURNS varchar(2000) AS
BEGIN
  RETURN REVERSE(LTrim_Chars(REVERSE(@BaseString), @TrimChars))
END
At least, I learnt some MsSql scripting...
EDIT:
I added NULL checks for the two arguments, to reflect Oracle and Postgres' behavior.
Unfortunately, Oracle still behaves slightly differently:
in the case you write LTRIM(string, ''), it returns NULL, since a 0-length string is like NULL in Oracle, so it's actually returning the result of LTRIM(string, NULL), which is NULL indeed.
BTW, this is a really strange case.