In a standard setup, datetime related indexes are extremely broad. As you likely know, an index maps an indexed value to the set of objects indexed under this value. For the above indexes, those sets typically have only a single event. As a consequence, the typical range queries for those indexes are effectively huge "or" queries where each term requires at least one ZODB load. A ZODB load may need access to the filesystem (when the object is not cached) and this can take several ms.
There are specialized indexes to index datetimes ("DateTimeIndex", "DateRangeIndex") to optimize. They have a configuration parameter "resolution" which allows you to specify the datetime resolution. A more coarse resolution makes the index less broad and thereby more efficient (the "or" become smaller). Often, you do not need a nanosecond resolution but a day resolution is sufficient.
I suggest, you check what index type is used for your created index. If necessary, you change it to a datetime index. Then you set its resolution to the value you need and retry your benchmark.
If this still does not satisfy your speed requirements, note that ZCatalog is build on top of the ZODB: a very simple and general object database. Unlike other database systems, it has (almost) no knowledge about the objects it manages. The advantage: it can store (almost) any object without configuration; the disadvantage: it must use a general and expensive storage format (Python pickles). A further consequence: it cannot support filtering (querying) at the database level: this must be done at application level. Thus, if query speed is necessary in large installations, you must integrate with a specialized search engine (such as e.g. Sol/r, elasticsearch, ...).
On a cold Plone catalog needs to load many objects from the database.
On a cold Plone the catalog query plan is empty and the first query is not optimized.
How long does it take for the second and the followoing queries?
To help with (1) - if the database connection is "slow" (read: not as fast as local storage) - a local storage persistent database cache can help to bridge restarts after updates etc.