The text you quote already explains why
time is a keyword:
The use of time as a reserved word permits the timing of shell builtins, shell functions, and pipelines. An external time command cannot time these easily.
time was only a builtin, it wouldn't be able to properly measure the time taken by a pipeline, e.g.:
$ time sleep 2 | sleep 4
Here time returned 4 seconds which is the time taken by the whole pipeline. If implemented as a builtin, the shell grammar would only allow it to it return 2 seconds because a command, whether builtin or not, is only seeing its parameters, in that specific case,
Other keywords that cannot be implemented by builtins are the ones used for structured constructions like
for, while, until, case, do, done, select, if, then, else, function. Like
time, they need to be able to process the lines to be interpreted without being restricted to a simple command boundary.
It is for the same reason, i.e. the ability to access to the whole shell input to be parsed and not just a command and its parameters that these keywords are implement as is. For example the
[ command parameters are subject to shell expansion and processing so you cannot reliably use
* in a test and
> would be taken as a redirection with unexpected results.
On the other hand,
[[ is changing the shell behavior so you can use whatever syntax it accepts without being bothered by the shell.
Here are some examples showing the difference in behavior:
$ if [ * = "*" ]; then echo ok; fi
bash: [: too many arguments
$ if [[ * = "*" ]]; then echo ok; fi
$ if [ 1 > 2 ]; then echo unexpected ; else echo expected; fi
$ if [ 1 -gt 2 ]; then echo unexpected ; else echo expected; fi
$ if [[ 1 > 2 ]]; then echo unexpected ; else echo expected; fi
Note that not only does
if [ 1 > 2 ] return an unexpected result but it also creates (or overwrite!) in the current directory a file named