Repository: ibis-project/ibis

Issue:
bug(timestamps): `ibis.range` doesn't accept `ibis.timestamp` objects (ibis expressions)
### What happened?

```
In [1]: from ibis.interactive import *

In [2]: expr = ibis.range(ibis.now() - ibis.interval(days=1), ibis.now(), ibis.interval(seconds=1))
```

Fails with 

```
TypeError: Unable to normalize <class 'ibis.expr.types.temporal.TimestampScalar'> to timestamp
```

Looks like we expect timestamp-like inputs to `range` to be Python literals, but we should allow arbitrary timestamp-typed expressions.

### What version of ibis are you using?

`main`

### What backend(s) are you using, if any?

DuckDB

### Relevant log output

_No response_

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct

Discussion:
cpcloud: Ok, looks like I, uh, didn't add very many API level tests the first time around. I'll add some that aren't just doctests 😬 

Patch:
diff --git a/ibis/expr/api.py b/ibis/expr/api.py
index 6ec3e8e36..f8aca6d68 100644
--- a/ibis/expr/api.py
+++ b/ibis/expr/api.py
@@ -2237,7 +2237,9 @@ def _timestamp_range(
     step: datetime.timedelta | ir.IntervalValue,
 ) -> ir.ArrayValue:
     return ops.TimestampRange(
-        start=normalize_datetime(start), stop=normalize_datetime(stop), step=step
+        start=normalize_datetime(start) if isinstance(start, str) else start,
+        stop=normalize_datetime(stop) if isinstance(stop, str) else stop,
+        step=step,
     ).to_expr()
 
 


Test patch:
diff --git a/ibis/tests/expr/test_timestamp.py b/ibis/tests/expr/test_timestamp.py
index 45b8972d5..f24bd9171 100644
--- a/ibis/tests/expr/test_timestamp.py
+++ b/ibis/tests/expr/test_timestamp.py
@@ -1,6 +1,6 @@
 from __future__ import annotations
 
-from datetime import datetime
+from datetime import datetime, timedelta
 
 import numpy as np
 import pandas as pd
@@ -184,3 +184,39 @@ def test_timestamp_field_access_on_time_failure(
 def test_integer_timestamp_fails(value):
     with pytest.raises(TypeError, match=r"Use ibis\.literal\(\.\.\.\)\.to_timestamp"):
         ibis.timestamp(value)
+
+
+@pytest.mark.parametrize(
+    "start",
+    [
+        "2002-01-01 00:00:00",
+        datetime(2002, 1, 1, 0, 0, 0),
+        ibis.timestamp("2002-01-01 00:00:00"),
+        ibis.timestamp(datetime(2002, 1, 1, 0, 0, 0)),
+        ibis.table({"start": "timestamp"}).start,
+    ],
+)
+@pytest.mark.parametrize(
+    "stop",
+    [
+        "2002-01-02 00:00:00",
+        datetime(2002, 1, 2, 0, 0, 0),
+        ibis.timestamp("2002-01-02 00:00:00"),
+        ibis.timestamp(datetime(2002, 1, 2, 0, 0, 0)),
+        ibis.table({"stop": "timestamp"}).stop,
+    ],
+)
+@pytest.mark.parametrize("step", [ibis.interval(seconds=1), timedelta(seconds=1)])
+def test_timestamp_range_with_str_inputs(start, stop, step):
+    expr = ibis.range(start, stop, step)
+
+    op = expr.op()
+
+    assert op.start.dtype.is_timestamp()
+    assert op.stop.dtype.is_timestamp()
+    assert op.step.dtype.is_interval()
+
+    dtype = expr.type()
+
+    assert dtype.is_array()
+    assert dtype.value_type.is_timestamp()