We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
timesketch_cli_client.commands.search bug in timestamp handling
In [cli_client/python/timesketch_cli_client/commands/search.py#L169](https://github.com/google/timesketch/blob/f861eba06c5c8b248dee4610ac6b401a93714331/cli_client/python/timesketch_cli_client/commands/search.py#L169) the `time_range` and `time` args are assigned to methods of `timesketch_api_client.search.DateRangeChip` instead of being used as method arguments.

Example:
```py
                range_chip.add_start_time = time_range[0]
                range_chip.add_end_time = time_range[1]
```

Should be:
```py
                range_chip.add_start_time(time_range[0])
                range_chip.add_end_time(time_range[1])
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `cli_client/python/timesketch_cli_client/commands/search.py`
Content:
```
1 # Copyright 2021 Google Inc. All rights reserved.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 """Commands for searching Timesketch data."""
15 
16 import json
17 import sys
18 
19 import click
20 from tabulate import tabulate
21 
22 from timesketch_api_client import search
23 
24 
25 def format_output(search_obj, output_format, show_headers):
26     """Format search result output.
27 
28     Args:
29         search_obj: API Search object.
30         output_format: The format to use.
31         show_headers: Boolean indicating if header row should be displayed.
32 
33     Returns:
34         Search results in the requested output format.
35     """
36     dataframe = search_obj.to_pandas()
37 
38     # Label is being set regardless of return_fields. Remove if it is not in
39     # the list of requested fields.
40     if "label" not in search_obj.return_fields:
41         dataframe = dataframe.drop(columns=["label"], errors="ignore")
42 
43     # Remove internal OpenSeearch columns
44     dataframe = dataframe.drop(
45         columns=["__ts_timeline_id", "_id", "_index", "_source", "_type"],
46         errors="ignore",
47     )
48 
49     result = None
50     if output_format == "text":
51         result = dataframe.to_string(index=False, header=show_headers)
52     elif output_format == "csv":
53         result = dataframe.to_csv(index=False, header=show_headers)
54     elif output_format == "tabular":
55         if show_headers:
56             result = tabulate(
57                 dataframe, headers="keys", tablefmt="psql", showindex=False
58             )
59         else:
60             result = tabulate(dataframe, tablefmt="psql", showindex=False)
61 
62     return result
63 
64 
65 def describe_query(search_obj):
66     """Print details of a search query nd filter."""
67     filter_pretty = json.dumps(search_obj.query_filter, indent=2)
68     click.echo(f"Query string: {search_obj.query_string}")
69     click.echo(f"Return fields: {search_obj.return_fields}")
70     click.echo(f"Filter: {filter_pretty}")
71 
72 
73 @click.command("search")
74 @click.option(
75     "--query", "-q", default="*", help="Search query in OpenSearch query string format"
76 )
77 @click.option(
78     "--time", "times", multiple=True, help="Datetime filter (e.g. 2020-01-01T12:00)"
79 )
80 @click.option(
81     "--time-range",
82     "time_ranges",
83     multiple=True,
84     nargs=2,
85     help="Datetime range filter (e.g: 2020-01-01 2020-02-01)",
86 )
87 @click.option("--label", "labels", multiple=True, help="Filter events with label")
88 @click.option(
89     "--header/--no-header",
90     default=True,
91     help="Toggle header information (default is to show)",
92 )
93 @click.option(
94     "--output-format", "output", help="Set output format (overrides global setting)"
95 )
96 @click.option(
97     "--return-fields", "return_fields", default="", help="What event fields to show"
98 )
99 @click.option(
100     "--order", default="asc", help="Order the output (asc/desc) based on the time field"
101 )
102 @click.option(
103     "--limit", type=int, default=40, help="Limit amount of events to show (default: 40)"
104 )
105 @click.option("--saved-search", type=int, help="Query and filter from saved search")
106 @click.option(
107     "--describe",
108     is_flag=True,
109     default=False,
110     help="Show the query and filter then exit",
111 )
112 @click.pass_context
113 # pylint: disable=too-many-arguments
114 def search_group(
115     ctx,
116     query,
117     times,
118     time_ranges,
119     labels,
120     header,
121     output,
122     return_fields,
123     order,
124     limit,
125     saved_search,
126     describe,
127 ):
128     """Search and explore."""
129     sketch = ctx.obj.sketch
130     output_format = ctx.obj.output_format
131     search_obj = search.Search(sketch=sketch)
132 
133     if output:
134         output_format = output
135 
136     new_line = True
137     if output_format == "csv":
138         new_line = False
139 
140     # Construct query from saved search and return early.
141     if saved_search:
142         search_obj.from_saved(saved_search)
143         if describe:
144             describe_query(search_obj)
145             return
146         click.echo(format_output(search_obj, output_format, header), nl=new_line)
147         return
148 
149     # Construct the query from flags.
150     # TODO (berggren): Add support for query DSL.
151     search_obj.query_string = query
152 
153     if return_fields:
154         search_obj.return_fields = return_fields
155 
156     if limit:
157         search_obj.max_entries = limit
158 
159     if order == "asc":
160         search_obj.order_ascending()
161     elif order == "desc":
162         search_obj.order_descending()
163 
164     # TODO: Add term chips.
165     if time_ranges:
166         for time_range in time_ranges:
167             try:
168                 range_chip = search.DateRangeChip()
169                 range_chip.add_start_time = time_range[0]
170                 range_chip.add_end_time = time_range[1]
171                 search_obj.add_chip(range_chip)
172             except ValueError:
173                 click.echo("Error parsing date (make sure it is ISO formatted)")
174                 sys.exit(1)
175 
176     # TODO (berggren): This should support dates like 2021-02-12 and then
177     # convert to ISO format.
178     if times:
179         for time in times:
180             try:
181                 range_chip = search.DateRangeChip()
182                 range_chip.add_start_time = time
183                 range_chip.add_end_time = time
184                 search_obj.add_chip(range_chip)
185             except ValueError:
186                 click.echo("Error parsing date (make sure it is ISO formatted)")
187                 sys.exit(1)
188 
189     if labels:
190         for label in labels:
191             label_chip = search.LabelChip()
192             if label == "star":
193                 label_chip.use_star_label()
194             elif label == "comment":
195                 label_chip.use_comment_label()
196             else:
197                 label_chip.label = label
198             search_obj.add_chip(label_chip)
199 
200     if describe:
201         describe_query(search_obj)
202         return
203 
204     click.echo(format_output(search_obj, output_format, header), nl=new_line)
205 
206 
207 @click.group("saved-searches")
208 def saved_searches_group():
209     """Managed saved searches."""
210 
211 
212 @saved_searches_group.command("list")
213 @click.pass_context
214 def list_saved_searches(ctx):
215     """List saved searches in the sketch."""
216     sketch = ctx.obj.sketch
217     for saved_search in sketch.list_saved_searches():
218         click.echo(f"{saved_search.id} {saved_search.name}")
219 
220 
221 @saved_searches_group.command("describe")
222 @click.argument("search_id", type=int, required=False)
223 @click.pass_context
224 def describe_saved_search(ctx, search_id):
225     """Show details for saved search."""
226     sketch = ctx.obj.sketch
227     # TODO (berggren): Add support for saved search name.
228     saved_search = sketch.get_saved_search(search_id=search_id)
229     if not saved_search:
230         click.echo("No such saved search")
231         return
232     filter_pretty = json.dumps(saved_search.query_filter, indent=2)
233     click.echo(f"query_string: {saved_search.query_string}")
234     click.echo(f"query_filter: {filter_pretty}")
235
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/cli_client/python/timesketch_cli_client/commands/search.py b/cli_client/python/timesketch_cli_client/commands/search.py
--- a/cli_client/python/timesketch_cli_client/commands/search.py
+++ b/cli_client/python/timesketch_cli_client/commands/search.py
@@ -166,8 +166,8 @@
         for time_range in time_ranges:
             try:
                 range_chip = search.DateRangeChip()
-                range_chip.add_start_time = time_range[0]
-                range_chip.add_end_time = time_range[1]
+                range_chip.add_start_time(time_range[0])
+                range_chip.add_end_time(time_range[1])
                 search_obj.add_chip(range_chip)
             except ValueError:
                 click.echo("Error parsing date (make sure it is ISO formatted)")
@@ -179,8 +179,8 @@
         for time in times:
             try:
                 range_chip = search.DateRangeChip()
-                range_chip.add_start_time = time
-                range_chip.add_end_time = time
+                range_chip.add_start_time(time)
+                range_chip.add_end_time(time)
                 search_obj.add_chip(range_chip)
             except ValueError:
                 click.echo("Error parsing date (make sure it is ISO formatted)")

{"golden_diff": "diff --git a/cli_client/python/timesketch_cli_client/commands/search.py b/cli_client/python/timesketch_cli_client/commands/search.py\n--- a/cli_client/python/timesketch_cli_client/commands/search.py\n+++ b/cli_client/python/timesketch_cli_client/commands/search.py\n@@ -166,8 +166,8 @@\n         for time_range in time_ranges:\n             try:\n                 range_chip = search.DateRangeChip()\n-                range_chip.add_start_time = time_range[0]\n-                range_chip.add_end_time = time_range[1]\n+                range_chip.add_start_time(time_range[0])\n+                range_chip.add_end_time(time_range[1])\n                 search_obj.add_chip(range_chip)\n             except ValueError:\n                 click.echo(\"Error parsing date (make sure it is ISO formatted)\")\n@@ -179,8 +179,8 @@\n         for time in times:\n             try:\n                 range_chip = search.DateRangeChip()\n-                range_chip.add_start_time = time\n-                range_chip.add_end_time = time\n+                range_chip.add_start_time(time)\n+                range_chip.add_end_time(time)\n                 search_obj.add_chip(range_chip)\n             except ValueError:\n                 click.echo(\"Error parsing date (make sure it is ISO formatted)\")\n", "issue": "timesketch_cli_client.commands.search bug in timestamp handling\nIn [cli_client/python/timesketch_cli_client/commands/search.py#L169](https://github.com/google/timesketch/blob/f861eba06c5c8b248dee4610ac6b401a93714331/cli_client/python/timesketch_cli_client/commands/search.py#L169) the `time_range` and `time` args are assigned to methods of `timesketch_api_client.search.DateRangeChip` instead of being used as method arguments.\r\n\r\nExample:\r\n```py\r\n                range_chip.add_start_time = time_range[0]\r\n                range_chip.add_end_time = time_range[1]\r\n```\r\n\r\nShould be:\r\n```py\r\n                range_chip.add_start_time(time_range[0])\r\n                range_chip.add_end_time(time_range[1])\r\n```\n", "before_files": [{"content": "# Copyright 2021 Google Inc. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"Commands for searching Timesketch data.\"\"\"\n\nimport json\nimport sys\n\nimport click\nfrom tabulate import tabulate\n\nfrom timesketch_api_client import search\n\n\ndef format_output(search_obj, output_format, show_headers):\n    \"\"\"Format search result output.\n\n    Args:\n        search_obj: API Search object.\n        output_format: The format to use.\n        show_headers: Boolean indicating if header row should be displayed.\n\n    Returns:\n        Search results in the requested output format.\n    \"\"\"\n    dataframe = search_obj.to_pandas()\n\n    # Label is being set regardless of return_fields. Remove if it is not in\n    # the list of requested fields.\n    if \"label\" not in search_obj.return_fields:\n        dataframe = dataframe.drop(columns=[\"label\"], errors=\"ignore\")\n\n    # Remove internal OpenSeearch columns\n    dataframe = dataframe.drop(\n        columns=[\"__ts_timeline_id\", \"_id\", \"_index\", \"_source\", \"_type\"],\n        errors=\"ignore\",\n    )\n\n    result = None\n    if output_format == \"text\":\n        result = dataframe.to_string(index=False, header=show_headers)\n    elif output_format == \"csv\":\n        result = dataframe.to_csv(index=False, header=show_headers)\n    elif output_format == \"tabular\":\n        if show_headers:\n            result = tabulate(\n                dataframe, headers=\"keys\", tablefmt=\"psql\", showindex=False\n            )\n        else:\n            result = tabulate(dataframe, tablefmt=\"psql\", showindex=False)\n\n    return result\n\n\ndef describe_query(search_obj):\n    \"\"\"Print details of a search query nd filter.\"\"\"\n    filter_pretty = json.dumps(search_obj.query_filter, indent=2)\n    click.echo(f\"Query string: {search_obj.query_string}\")\n    click.echo(f\"Return fields: {search_obj.return_fields}\")\n    click.echo(f\"Filter: {filter_pretty}\")\n\n\n@click.command(\"search\")\n@click.option(\n    \"--query\", \"-q\", default=\"*\", help=\"Search query in OpenSearch query string format\"\n)\n@click.option(\n    \"--time\", \"times\", multiple=True, help=\"Datetime filter (e.g. 2020-01-01T12:00)\"\n)\n@click.option(\n    \"--time-range\",\n    \"time_ranges\",\n    multiple=True,\n    nargs=2,\n    help=\"Datetime range filter (e.g: 2020-01-01 2020-02-01)\",\n)\n@click.option(\"--label\", \"labels\", multiple=True, help=\"Filter events with label\")\n@click.option(\n    \"--header/--no-header\",\n    default=True,\n    help=\"Toggle header information (default is to show)\",\n)\n@click.option(\n    \"--output-format\", \"output\", help=\"Set output format (overrides global setting)\"\n)\n@click.option(\n    \"--return-fields\", \"return_fields\", default=\"\", help=\"What event fields to show\"\n)\n@click.option(\n    \"--order\", default=\"asc\", help=\"Order the output (asc/desc) based on the time field\"\n)\n@click.option(\n    \"--limit\", type=int, default=40, help=\"Limit amount of events to show (default: 40)\"\n)\n@click.option(\"--saved-search\", type=int, help=\"Query and filter from saved search\")\n@click.option(\n    \"--describe\",\n    is_flag=True,\n    default=False,\n    help=\"Show the query and filter then exit\",\n)\n@click.pass_context\n# pylint: disable=too-many-arguments\ndef search_group(\n    ctx,\n    query,\n    times,\n    time_ranges,\n    labels,\n    header,\n    output,\n    return_fields,\n    order,\n    limit,\n    saved_search,\n    describe,\n):\n    \"\"\"Search and explore.\"\"\"\n    sketch = ctx.obj.sketch\n    output_format = ctx.obj.output_format\n    search_obj = search.Search(sketch=sketch)\n\n    if output:\n        output_format = output\n\n    new_line = True\n    if output_format == \"csv\":\n        new_line = False\n\n    # Construct query from saved search and return early.\n    if saved_search:\n        search_obj.from_saved(saved_search)\n        if describe:\n            describe_query(search_obj)\n            return\n        click.echo(format_output(search_obj, output_format, header), nl=new_line)\n        return\n\n    # Construct the query from flags.\n    # TODO (berggren): Add support for query DSL.\n    search_obj.query_string = query\n\n    if return_fields:\n        search_obj.return_fields = return_fields\n\n    if limit:\n        search_obj.max_entries = limit\n\n    if order == \"asc\":\n        search_obj.order_ascending()\n    elif order == \"desc\":\n        search_obj.order_descending()\n\n    # TODO: Add term chips.\n    if time_ranges:\n        for time_range in time_ranges:\n            try:\n                range_chip = search.DateRangeChip()\n                range_chip.add_start_time = time_range[0]\n                range_chip.add_end_time = time_range[1]\n                search_obj.add_chip(range_chip)\n            except ValueError:\n                click.echo(\"Error parsing date (make sure it is ISO formatted)\")\n                sys.exit(1)\n\n    # TODO (berggren): This should support dates like 2021-02-12 and then\n    # convert to ISO format.\n    if times:\n        for time in times:\n            try:\n                range_chip = search.DateRangeChip()\n                range_chip.add_start_time = time\n                range_chip.add_end_time = time\n                search_obj.add_chip(range_chip)\n            except ValueError:\n                click.echo(\"Error parsing date (make sure it is ISO formatted)\")\n                sys.exit(1)\n\n    if labels:\n        for label in labels:\n            label_chip = search.LabelChip()\n            if label == \"star\":\n                label_chip.use_star_label()\n            elif label == \"comment\":\n                label_chip.use_comment_label()\n            else:\n                label_chip.label = label\n            search_obj.add_chip(label_chip)\n\n    if describe:\n        describe_query(search_obj)\n        return\n\n    click.echo(format_output(search_obj, output_format, header), nl=new_line)\n\n\n@click.group(\"saved-searches\")\ndef saved_searches_group():\n    \"\"\"Managed saved searches.\"\"\"\n\n\n@saved_searches_group.command(\"list\")\n@click.pass_context\ndef list_saved_searches(ctx):\n    \"\"\"List saved searches in the sketch.\"\"\"\n    sketch = ctx.obj.sketch\n    for saved_search in sketch.list_saved_searches():\n        click.echo(f\"{saved_search.id} {saved_search.name}\")\n\n\n@saved_searches_group.command(\"describe\")\n@click.argument(\"search_id\", type=int, required=False)\n@click.pass_context\ndef describe_saved_search(ctx, search_id):\n    \"\"\"Show details for saved search.\"\"\"\n    sketch = ctx.obj.sketch\n    # TODO (berggren): Add support for saved search name.\n    saved_search = sketch.get_saved_search(search_id=search_id)\n    if not saved_search:\n        click.echo(\"No such saved search\")\n        return\n    filter_pretty = json.dumps(saved_search.query_filter, indent=2)\n    click.echo(f\"query_string: {saved_search.query_string}\")\n    click.echo(f\"query_filter: {filter_pretty}\")\n", "path": "cli_client/python/timesketch_cli_client/commands/search.py"}], "after_files": [{"content": "# Copyright 2021 Google Inc. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"Commands for searching Timesketch data.\"\"\"\n\nimport json\nimport sys\n\nimport click\nfrom tabulate import tabulate\n\nfrom timesketch_api_client import search\n\n\ndef format_output(search_obj, output_format, show_headers):\n    \"\"\"Format search result output.\n\n    Args:\n        search_obj: API Search object.\n        output_format: The format to use.\n        show_headers: Boolean indicating if header row should be displayed.\n\n    Returns:\n        Search results in the requested output format.\n    \"\"\"\n    dataframe = search_obj.to_pandas()\n\n    # Label is being set regardless of return_fields. Remove if it is not in\n    # the list of requested fields.\n    if \"label\" not in search_obj.return_fields:\n        dataframe = dataframe.drop(columns=[\"label\"], errors=\"ignore\")\n\n    # Remove internal OpenSeearch columns\n    dataframe = dataframe.drop(\n        columns=[\"__ts_timeline_id\", \"_id\", \"_index\", \"_source\", \"_type\"],\n        errors=\"ignore\",\n    )\n\n    result = None\n    if output_format == \"text\":\n        result = dataframe.to_string(index=False, header=show_headers)\n    elif output_format == \"csv\":\n        result = dataframe.to_csv(index=False, header=show_headers)\n    elif output_format == \"tabular\":\n        if show_headers:\n            result = tabulate(\n                dataframe, headers=\"keys\", tablefmt=\"psql\", showindex=False\n            )\n        else:\n            result = tabulate(dataframe, tablefmt=\"psql\", showindex=False)\n\n    return result\n\n\ndef describe_query(search_obj):\n    \"\"\"Print details of a search query nd filter.\"\"\"\n    filter_pretty = json.dumps(search_obj.query_filter, indent=2)\n    click.echo(f\"Query string: {search_obj.query_string}\")\n    click.echo(f\"Return fields: {search_obj.return_fields}\")\n    click.echo(f\"Filter: {filter_pretty}\")\n\n\n@click.command(\"search\")\n@click.option(\n    \"--query\", \"-q\", default=\"*\", help=\"Search query in OpenSearch query string format\"\n)\n@click.option(\n    \"--time\", \"times\", multiple=True, help=\"Datetime filter (e.g. 2020-01-01T12:00)\"\n)\n@click.option(\n    \"--time-range\",\n    \"time_ranges\",\n    multiple=True,\n    nargs=2,\n    help=\"Datetime range filter (e.g: 2020-01-01 2020-02-01)\",\n)\n@click.option(\"--label\", \"labels\", multiple=True, help=\"Filter events with label\")\n@click.option(\n    \"--header/--no-header\",\n    default=True,\n    help=\"Toggle header information (default is to show)\",\n)\n@click.option(\n    \"--output-format\", \"output\", help=\"Set output format (overrides global setting)\"\n)\n@click.option(\n    \"--return-fields\", \"return_fields\", default=\"\", help=\"What event fields to show\"\n)\n@click.option(\n    \"--order\", default=\"asc\", help=\"Order the output (asc/desc) based on the time field\"\n)\n@click.option(\n    \"--limit\", type=int, default=40, help=\"Limit amount of events to show (default: 40)\"\n)\n@click.option(\"--saved-search\", type=int, help=\"Query and filter from saved search\")\n@click.option(\n    \"--describe\",\n    is_flag=True,\n    default=False,\n    help=\"Show the query and filter then exit\",\n)\n@click.pass_context\n# pylint: disable=too-many-arguments\ndef search_group(\n    ctx,\n    query,\n    times,\n    time_ranges,\n    labels,\n    header,\n    output,\n    return_fields,\n    order,\n    limit,\n    saved_search,\n    describe,\n):\n    \"\"\"Search and explore.\"\"\"\n    sketch = ctx.obj.sketch\n    output_format = ctx.obj.output_format\n    search_obj = search.Search(sketch=sketch)\n\n    if output:\n        output_format = output\n\n    new_line = True\n    if output_format == \"csv\":\n        new_line = False\n\n    # Construct query from saved search and return early.\n    if saved_search:\n        search_obj.from_saved(saved_search)\n        if describe:\n            describe_query(search_obj)\n            return\n        click.echo(format_output(search_obj, output_format, header), nl=new_line)\n        return\n\n    # Construct the query from flags.\n    # TODO (berggren): Add support for query DSL.\n    search_obj.query_string = query\n\n    if return_fields:\n        search_obj.return_fields = return_fields\n\n    if limit:\n        search_obj.max_entries = limit\n\n    if order == \"asc\":\n        search_obj.order_ascending()\n    elif order == \"desc\":\n        search_obj.order_descending()\n\n    # TODO: Add term chips.\n    if time_ranges:\n        for time_range in time_ranges:\n            try:\n                range_chip = search.DateRangeChip()\n                range_chip.add_start_time(time_range[0])\n                range_chip.add_end_time(time_range[1])\n                search_obj.add_chip(range_chip)\n            except ValueError:\n                click.echo(\"Error parsing date (make sure it is ISO formatted)\")\n                sys.exit(1)\n\n    # TODO (berggren): This should support dates like 2021-02-12 and then\n    # convert to ISO format.\n    if times:\n        for time in times:\n            try:\n                range_chip = search.DateRangeChip()\n                range_chip.add_start_time(time)\n                range_chip.add_end_time(time)\n                search_obj.add_chip(range_chip)\n            except ValueError:\n                click.echo(\"Error parsing date (make sure it is ISO formatted)\")\n                sys.exit(1)\n\n    if labels:\n        for label in labels:\n            label_chip = search.LabelChip()\n            if label == \"star\":\n                label_chip.use_star_label()\n            elif label == \"comment\":\n                label_chip.use_comment_label()\n            else:\n                label_chip.label = label\n            search_obj.add_chip(label_chip)\n\n    if describe:\n        describe_query(search_obj)\n        return\n\n    click.echo(format_output(search_obj, output_format, header), nl=new_line)\n\n\n@click.group(\"saved-searches\")\ndef saved_searches_group():\n    \"\"\"Managed saved searches.\"\"\"\n\n\n@saved_searches_group.command(\"list\")\n@click.pass_context\ndef list_saved_searches(ctx):\n    \"\"\"List saved searches in the sketch.\"\"\"\n    sketch = ctx.obj.sketch\n    for saved_search in sketch.list_saved_searches():\n        click.echo(f\"{saved_search.id} {saved_search.name}\")\n\n\n@saved_searches_group.command(\"describe\")\n@click.argument(\"search_id\", type=int, required=False)\n@click.pass_context\ndef describe_saved_search(ctx, search_id):\n    \"\"\"Show details for saved search.\"\"\"\n    sketch = ctx.obj.sketch\n    # TODO (berggren): Add support for saved search name.\n    saved_search = sketch.get_saved_search(search_id=search_id)\n    if not saved_search:\n        click.echo(\"No such saved search\")\n        return\n    filter_pretty = json.dumps(saved_search.query_filter, indent=2)\n    click.echo(f\"query_string: {saved_search.query_string}\")\n    click.echo(f\"query_filter: {filter_pretty}\")\n", "path": "cli_client/python/timesketch_cli_client/commands/search.py"}]}