Repository: blakeblackshear/frigate

Issue:
Event time filter
Hello,

I would like to be able to have the date picker also have a time option. For example, I can just view events that are overnight as there are so many during the day. 



Discussion:
I concur.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

I second that. Much less events are during nights/early morning as well as most criminal activities happens around the same time frame. It will be much easier to track that.
This has been implemented for 0.13 👍 
> This has been implemented for 0.13 👍

Firstly, thanks for adding this option!  

Can I just add feedback that the time selector isn't super functional in 0.13. It doesn't let you select say 10pm - 6am . You need to do two different selections as you can only do after midnight or before midnight. 
@sinamics is that a bug or expected for the current implementation?

(Haven't tested myself)
This is the selector:

![2023-09-13_8-21-43](https://github.com/blakeblackshear/frigate/assets/8526509/5c6eef29-5a13-4b7f-b676-dc454672038a)

When you select the initial start time, it only lets you select an item greater than that time. So you cannot select 2200 to 0600. Only 2200 to 2300 then you would need a second selection of 0000 to 0600
It is just a time selector for specific date range (on the screenshot above). It is not the same what was requested. 
E.g. I need to finds events which happened from 1am till 7am over the last 4 weeks (I keep detections for 90 days).
> This is the selector:
> 
> ![2023-09-13_8-21-43](https://user-images.githubusercontent.com/8526509/267481083-5c6eef29-5a13-4b7f-b676-dc454672038a.png)
> 
> When you select the initial start time, it only lets you select an item greater than that time. So you cannot select 2200 to 0600. Only 2200 to 2300 then you would need a second selection of 0000 to 0600

that's not what I am seeing, it worked just fine to choose overnight

![Screen Shot 2023-09-12 at 17 20 58 PM](https://github.com/blakeblackshear/frigate/assets/14866235/0dbda79f-fa99-4bee-9c33-de0e9e0b88ec)

> > This is the selector:
> > ![2023-09-13_8-21-43](https://user-images.githubusercontent.com/8526509/267481083-5c6eef29-5a13-4b7f-b676-dc454672038a.png)
> > When you select the initial start time, it only lets you select an item greater than that time. So you cannot select 2200 to 0600. Only 2200 to 2300 then you would need a second selection of 0000 to 0600
> 
> that's not what I am seeing, it worked just fine to choose overnight
> 
> ![Screen Shot 2023-09-12 at 17 20 58 PM](https://user-images.githubusercontent.com/14866235/267489497-0dbda79f-fa99-4bee-9c33-de0e9e0b88ec.png)

Yep, it works for one night, overnight. Not show all the events for 10pm - 6am for more than one day...

_Don't get me wrong though, it is better than what we had previously_
I don't think it will be too much to adjust it to behave that way, I do agree that that is likely how more people will want to use it.
@NickM-27, this behavior is by design, not a bug. I may have misunderstood the original requirements. 
However, modifying it to support time-from to time-to selection for each day should be doable.

Resolving patch:
diff --git a/frigate/http.py b/frigate/http.py
--- a/frigate/http.py
+++ b/frigate/http.py
@@ -56,6 +56,8 @@
 
 logger = logging.getLogger(__name__)
 
+DEFAULT_TIME_RANGE = "00:00,24:00"
+
 bp = Blueprint("frigate", __name__)
 
 
@@ -769,6 +771,7 @@ def events():
     limit = request.args.get("limit", 100)
     after = request.args.get("after", type=float)
     before = request.args.get("before", type=float)
+    time_range = request.args.get("time_range", DEFAULT_TIME_RANGE)
     has_clip = request.args.get("has_clip", type=int)
     has_snapshot = request.args.get("has_snapshot", type=int)
     in_progress = request.args.get("in_progress", type=int)
@@ -851,6 +854,36 @@ def events():
     if before:
         clauses.append((Event.start_time < before))
 
+    if time_range != DEFAULT_TIME_RANGE:
+        # get timezone arg to ensure browser times are used
+        tz_name = request.args.get("timezone", default="utc", type=str)
+        hour_modifier, minute_modifier = get_tz_modifiers(tz_name)
+
+        times = time_range.split(",")
+        time_after = times[0]
+        time_before = times[1]
+
+        start_hour_fun = fn.strftime(
+            "%H:%M",
+            fn.datetime(Event.start_time, "unixepoch", hour_modifier, minute_modifier),
+        )
+
+        # cases where user wants events overnight, ex: from 20:00 to 06:00
+        # should use or operator
+        if time_after > time_before:
+            clauses.append(
+                (
+                    reduce(
+                        operator.or_,
+                        [(start_hour_fun > time_after), (start_hour_fun < time_before)],
+                    )
+                )
+            )
+        # all other cases should be and operator
+        else:
+            clauses.append((start_hour_fun > time_after))
+            clauses.append((start_hour_fun < time_before))
+
     if has_clip is not None:
         clauses.append((Event.has_clip == has_clip))
 


Test patch:
diff --git a/frigate/test/test_http.py b/frigate/test/test_http.py
--- a/frigate/test/test_http.py
+++ b/frigate/test/test_http.py
@@ -236,6 +236,44 @@ def test_event_retention(self):
             assert event["id"] == id
             assert event["retain_indefinitely"] is False
 
+    def test_event_time_filtering(self):
+        app = create_app(
+            FrigateConfig(**self.minimal_config),
+            self.db,
+            None,
+            None,
+            None,
+            None,
+            None,
+            PlusApi(),
+        )
+        morning_id = "123456.random"
+        evening_id = "654321.random"
+        morning = 1656590400  # 06/30/2022 6 am (GMT)
+        evening = 1656633600  # 06/30/2022 6 pm (GMT)
+
+        with app.test_client() as client:
+            _insert_mock_event(morning_id, morning)
+            _insert_mock_event(evening_id, evening)
+            # both events come back
+            events = client.get("/events").json
+            assert events
+            assert len(events) == 2
+            # morning event is excluded
+            events = client.get(
+                "/events",
+                query_string={"time_range": "07:00,24:00"},
+            ).json
+            assert events
+            # assert len(events) == 1
+            # evening event is excluded
+            events = client.get(
+                "/events",
+                query_string={"time_range": "00:00,18:00"},
+            ).json
+            assert events
+            assert len(events) == 1
+
     def test_set_delete_sub_label(self):
         app = create_app(
             FrigateConfig(**self.minimal_config),
@@ -351,14 +389,17 @@ def test_stats(self, mock_stats):
             assert stats == self.test_stats
 
 
-def _insert_mock_event(id: str) -> Event:
+def _insert_mock_event(
+    id: str,
+    start_time: datetime.datetime = datetime.datetime.now().timestamp(),
+) -> Event:
     """Inserts a basic event model with a given id."""
     return Event.insert(
         id=id,
         label="Mock",
         camera="front_door",
-        start_time=datetime.datetime.now().timestamp(),
-        end_time=datetime.datetime.now().timestamp() + 20,
+        start_time=start_time,
+        end_time=start_time + 20,
         top_score=100,
         false_positive=False,
         zones=list(),