Repository: deepset-ai/haystack
Pull request: feat: time extension for ChatPromptBuilder
### Related Issues

- fixes [#8985
](https://github.com/deepset-ai/haystack/issues/8985)
### Proposed Changes:

 <!--- In case of a bug: Describe what caused the issue and how you solved it -->
 <!--- In case of a feature: Describe what did you add and how it works -->

### How did you test it?

<!-- unit tests, integration tests, manual verification, instructions for manual tests -->

### Notes for the reviewer

<!-- E.g. point out section where the reviewer  -->

### Checklist

- I have read the [contributors guidelines](https://github.com/deepset-ai/haystack/blob/main/CONTRIBUTING.md) and the [code of conduct](https://github.com/deepset-ai/haystack/blob/main/code_of_conduct.txt)
- I have updated the related issue with new insights and changes
- I added unit tests and updated the docstrings
- I've used one of the [conventional commit types](https://www.conventionalcommits.org/en/v1.0.0/) for my PR title: `fix:`, `feat:`, `build:`, `chore:`, `ci:`, `docs:`, `style:`, `refactor:`, `perf:`, `test:` and added `!` in case the PR includes breaking changes.
- I documented my code
- I ran [pre-commit hooks](https://github.com/deepset-ai/haystack/blob/main/CONTRIBUTING.md#installation) and fixed any issue


Problem statement:
Add Jinja2TimeExtension to ChatPromptBuilder
**Is your feature request related to a problem? Please describe.**
In the PromptBuilder, we use the Jinja2TimeExtension if it is available to allow users to inject the current date into the prompt. The same should be possible for the ChatPromptBuilder.

**Describe the solution you'd like**
- Add extension to ChatPromptBuilder
- documentation

Hints from issue discussion:

Commit message: chore: release notes

Patch to review:
diff --git a/haystack/components/builders/chat_prompt_builder.py b/haystack/components/builders/chat_prompt_builder.py
index 33e2feda2d..b8d4acdfac 100644
--- a/haystack/components/builders/chat_prompt_builder.py
+++ b/haystack/components/builders/chat_prompt_builder.py
@@ -10,6 +10,7 @@
 
 from haystack import component, default_from_dict, default_to_dict, logging
 from haystack.dataclasses.chat_message import ChatMessage, ChatRole, TextContent
+from haystack.utils import Jinja2TimeExtension
 
 logger = logging.getLogger(__name__)
 
@@ -124,7 +125,13 @@ def __init__(
         self.required_variables = required_variables or []
         self.template = template
         variables = variables or []
-        self._env = SandboxedEnvironment()
+        try:
+            # The Jinja2TimeExtension needs an optional dependency to be installed.
+            # If it's not available we can do without it and use the PromptBuilder as is.
+            self._env = SandboxedEnvironment(extensions=[Jinja2TimeExtension])
+        except ImportError:
+            self._env = SandboxedEnvironment()
+
         if template and not variables:
             for message in template:
                 if message.is_from(ChatRole.USER) or message.is_from(ChatRole.SYSTEM):
diff --git a/releasenotes/notes/feat-time-extension-chatprompt-builder-fb159207088943d8.yaml b/releasenotes/notes/feat-time-extension-chatprompt-builder-fb159207088943d8.yaml
new file mode 100644
index 0000000000..f1c707c0dc
--- /dev/null
+++ b/releasenotes/notes/feat-time-extension-chatprompt-builder-fb159207088943d8.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    Users can now work with date and time in the ChatPromptBuilder.
+    In the same way as the PromptBuilder, the ChatPromptBuilder now supports arrow to work with datetime.
diff --git a/test/components/builders/test_chat_prompt_builder.py b/test/components/builders/test_chat_prompt_builder.py
index 1c39922df4..9fe5991fd2 100644
--- a/test/components/builders/test_chat_prompt_builder.py
+++ b/test/components/builders/test_chat_prompt_builder.py
@@ -1,5 +1,7 @@
 from typing import Any, Dict, List, Optional
 from jinja2 import TemplateSyntaxError
+
+import arrow
 import pytest
 
 from haystack.components.builders.chat_prompt_builder import ChatPromptBuilder
@@ -335,6 +337,87 @@ def test_example_in_pipeline_simple(self):
         }
         assert result == expected_dynamic
 
+    def test_with_custom_dateformat(self) -> None:
+        template = [ChatMessage.from_user("Formatted date: {% now 'UTC', '%Y-%m-%d' %}")]
+        builder = ChatPromptBuilder(template=template)
+
+        result = builder.run()["prompt"]
+
+        now_formatted = f"Formatted date: {arrow.now('UTC').strftime('%Y-%m-%d')}"
+        assert len(result) == 1
+        assert result[0].role == "user"
+        assert result[0].text == now_formatted
+
+    def test_with_different_timezone(self) -> None:
+        template = [ChatMessage.from_user("Current time in New York is: {% now 'America/New_York' %}")]
+        builder = ChatPromptBuilder(template=template)
+
+        result = builder.run()["prompt"]
+
+        now_ny = f"Current time in New York is: {arrow.now('America/New_York').strftime('%Y-%m-%d %H:%M:%S')}"
+        assert len(result) == 1
+        assert result[0].role == "user"
+        assert result[0].text == now_ny
+
+    def test_date_with_addition_offset(self) -> None:
+        template = [ChatMessage.from_user("Time after 2 hours is: {% now 'UTC' + 'hours=2' %}")]
+        builder = ChatPromptBuilder(template=template)
+
+        result = builder.run()["prompt"]
+
+        now_plus_2 = f"Time after 2 hours is: {(arrow.now('UTC').shift(hours=+2)).strftime('%Y-%m-%d %H:%M:%S')}"
+        assert len(result) == 1
+        assert result[0].role == "user"
+        assert result[0].text == now_plus_2
+
+    def test_date_with_subtraction_offset(self) -> None:
+        template = [ChatMessage.from_user("Time after 12 days is: {% now 'UTC' - 'days=12' %}")]
+        builder = ChatPromptBuilder(template=template)
+
+        result = builder.run()["prompt"]
+
+        now_minus_12 = f"Time after 12 days is: {(arrow.now('UTC').shift(days=-12)).strftime('%Y-%m-%d %H:%M:%S')}"
+        assert len(result) == 1
+        assert result[0].role == "user"
+        assert result[0].text == now_minus_12
+
+    def test_invalid_timezone(self) -> None:
+        template = [ChatMessage.from_user("Current time is: {% now 'Invalid/Timezone' %}")]
+        builder = ChatPromptBuilder(template=template)
+
+        # Expect ValueError for invalid timezone
+        with pytest.raises(ValueError, match="Invalid timezone"):
+            builder.run()
+
+    def test_invalid_offset(self) -> None:
+        template = [ChatMessage.from_user("Time after invalid offset is: {% now 'UTC' + 'invalid_offset' %}")]
+        builder = ChatPromptBuilder(template=template)
+
+        # Expect ValueError for invalid offset
+        with pytest.raises(ValueError, match="Invalid offset or operator"):
+            builder.run()
+
+    def test_multiple_messages_with_date_template(self) -> None:
+        template = [
+            ChatMessage.from_user("Current date is: {% now 'UTC' %}"),
+            ChatMessage.from_assistant("Thank you for providing the date"),
+            ChatMessage.from_user("Yesterday was: {% now 'UTC' - 'days=1' %}"),
+        ]
+        builder = ChatPromptBuilder(template=template)
+
+        result = builder.run()["prompt"]
+
+        now = f"Current date is: {arrow.now('UTC').strftime('%Y-%m-%d %H:%M:%S')}"
+        yesterday = f"Yesterday was: {(arrow.now('UTC').shift(days=-1)).strftime('%Y-%m-%d %H:%M:%S')}"
+
+        assert len(result) == 3
+        assert result[0].role == "user"
+        assert result[0].text == now
+        assert result[1].role == "assistant"
+        assert result[1].text == "Thank you for providing the date"
+        assert result[2].role == "user"
+        assert result[2].text == yesterday
+
 
 class TestChatPromptBuilderDynamic:
     def test_multiple_templated_chat_messages(self):


Code review comments:
diff_hunk: @@ -124,7 +125,13 @@ def __init__(
         self.required_variables = required_variables or []
         self.template = template
         variables = variables or []
-        self._env = SandboxedEnvironment()
+        try:
+            # The Jinja2TimeExtension needs an optional dependency to be installed.
+            # If it's not available we can do without it and use the PromptBuilder as is.
path: haystack/components/builders/chat_prompt_builder.py
text: @user1:
Update doctoring to ChatPromptBuilder