Repository: ansible/ansible-navigator (language: python)

Issue:
Version 2.0 Checklist
- [x] FAQ updated with all related issues
- [x] json schema push to repo or ref here
- [x] Upgrade Creator EE to ansible 2.12.4 final


Discussion / hints:

Fix patch:
diff --git a/.config/dictionary.txt b/.config/dictionary.txt
index d0b3c3a6c..86305b79a 100644
--- a/.config/dictionary.txt
+++ b/.config/dictionary.txt
@@ -8,3 +8,4 @@ mqueue              # https://github.com/ansible/ansible-runner/issues/984
 
 
 astimezone
+Kolkata
diff --git a/docs/changelog-fragments.d/1080.feature.md b/docs/changelog-fragments.d/1080.feature.md
new file mode 100644
index 000000000..f926bd0e9
--- /dev/null
+++ b/docs/changelog-fragments.d/1080.feature.md
@@ -0,0 +1,18 @@
+Added the time zone settings entry.
+
+Here are some examples:
+
+```bash
+ansible-navigator --tz local
+ansible-navigator --time-zone Japan
+
+export TZ=America/Los_Angeles
+ansible-navigator
+```
+
+```yaml
+ansible-navigator:
+  time-zone: Asia/Kolkata
+```
+
+-- by {user}`cidrblock`
diff --git a/src/ansible_navigator/configuration_subsystem/navigator_configuration.py b/src/ansible_navigator/configuration_subsystem/navigator_configuration.py
index cbf568518..7586eb0c9 100644
--- a/src/ansible_navigator/configuration_subsystem/navigator_configuration.py
+++ b/src/ansible_navigator/configuration_subsystem/navigator_configuration.py
@@ -520,6 +520,15 @@ class Internals:
             ),
             value=SettingsEntryValue(),
         ),
+        SettingsEntry(
+            name="time_zone",
+            cli_parameters=CliParameters(short="--tz"),
+            environment_variable_override="TZ",
+            short_description=(
+                "Specify the IANA time zone to use or 'local' to use the system time zone."
+            ),
+            value=SettingsEntryValue(default="UTC"),
+        ),
         SettingsEntry(
             name="workdir",
             cli_parameters=CliParameters(short="--bwd"),
diff --git a/src/ansible_navigator/configuration_subsystem/navigator_post_processor.py b/src/ansible_navigator/configuration_subsystem/navigator_post_processor.py
index 6f692d339..194cadf0a 100644
--- a/src/ansible_navigator/configuration_subsystem/navigator_post_processor.py
+++ b/src/ansible_navigator/configuration_subsystem/navigator_post_processor.py
@@ -13,6 +13,7 @@
 from typing import List
 from typing import Tuple
 
+from ..utils.compatibility import zoneinfo
 from ..utils.functions import ExitMessage
 from ..utils.functions import ExitPrefix
 from ..utils.functions import LogMessage
@@ -771,3 +772,37 @@ def set_environment_variable(
         if entry.value.source is not C.NOT_SET:
             entry.value.current = {k: str(v) for k, v in entry.value.current.items()}
         return messages, exit_messages
+
+    @staticmethod
+    @_post_processor
+    def time_zone(
+        entry: SettingsEntry,
+        config: ApplicationConfiguration,
+    ) -> PostProcessorReturn:
+        # pylint: disable=unused-argument
+        """Post process ``time_zone``
+
+        :param entry: The current settings entry
+        :param config: The full application configuration
+        :returns: An instance of the standard post process return object
+        """
+        messages: List[LogMessage] = []
+        exit_messages: List[ExitMessage] = []
+
+        exit_msg = (
+            f"The specified time zone '{entry.value.current}', set by"
+            f" {entry.value.source.value.lower()}, "
+        )
+        if isinstance(entry.value.current, str):
+            available_timezones = sorted(zoneinfo.available_timezones())
+            if entry.value.current in available_timezones or entry.value.current == "local":
+                return messages, exit_messages
+            exit_msg += "could not be found."
+        else:
+            exit_msg += (
+                f"must be a string but was found to be a '{type(entry.value.current).__name__}'."
+            )
+        exit_messages.append(ExitMessage(message=exit_msg))
+        exit_msg = "Please try again with a valid IANA time zone."
+        exit_messages.append(ExitMessage(message=exit_msg, prefix=ExitPrefix.HINT))
+        return messages, exit_messages


Test patch:
diff --git a/tests/fixtures/unit/configuration_subsystem/ansible-navigator.yml b/tests/fixtures/unit/configuration_subsystem/ansible-navigator.yml
index dc93d9aed..174c556dc 100644
--- a/tests/fixtures/unit/configuration_subsystem/ansible-navigator.yml
+++ b/tests/fixtures/unit/configuration_subsystem/ansible-navigator.yml
@@ -69,3 +69,4 @@ ansible-navigator:
     enable: True
     replay: /tmp/test_artifact.json
     save-as: /tmp/test_artifact.json
+  time-zone: Japan
diff --git a/tests/unit/configuration_subsystem/data.py b/tests/unit/configuration_subsystem/data.py
index 0958d124d..19d970b7c 100644
--- a/tests/unit/configuration_subsystem/data.py
+++ b/tests/unit/configuration_subsystem/data.py
@@ -250,6 +250,7 @@ def cli_data():
     ("pull_arguments", "--tls-verify=false", ["--tls-verify=false"]),
     ("pull_policy", "never", "never"),
     ("set_environment_variable", "T1=A,T2=B,T3=C", {"T1": "A", "T2": "B", "T3": "C"}),
+    ("time_zone", "Japan", "Japan"),
     ("workdir", "/tmp/", "/tmp/"),
 ]
 
diff --git a/tests/unit/configuration_subsystem/post_processors/test_time_zone.py b/tests/unit/configuration_subsystem/post_processors/test_time_zone.py
new file mode 100644
index 000000000..6dab5103e
--- /dev/null
+++ b/tests/unit/configuration_subsystem/post_processors/test_time_zone.py
@@ -0,0 +1,149 @@
+"""Tests for the time zone post processor."""
+from dataclasses import dataclass
+from typing import Dict
+from typing import Optional
+from typing import Union
+
+import pytest
+
+from ansible_navigator.configuration_subsystem import Constants as C
+from ansible_navigator.configuration_subsystem import NavigatorConfiguration
+from ansible_navigator.configuration_subsystem.configurator import Configurator
+from ansible_navigator.configuration_subsystem.navigator_post_processor import (
+    NavigatorPostProcessor,
+)
+
+
+@dataclass
+class Scenario:
+    """Data structure for the time zone post processor tests."""
+
+    current: Union[bool, str, Dict]
+    source: C
+    exit_message_substr: str = ""
+    expected: Optional[str] = None
+    index: int = 0
+
+    def __post_init__(self):
+        """Set the expected if errors are expected."""
+        if self.expected is None:
+            object.__setattr__(self, "expected", self.current)
+
+    def __str__(self):
+        """Provide a test id.
+
+        :returns: The test id
+        """
+        return f"{self.source}-{self.current}"
+
+
+test_data = (
+    Scenario(
+        current="foo",
+        exit_message_substr=(
+            "The specified time zone 'foo', set by environment variable, could not be found."
+        ),
+        source=C.ENVIRONMENT_VARIABLE,
+    ),
+    Scenario(
+        current="Japan",
+        source=C.ENVIRONMENT_VARIABLE,
+    ),
+    Scenario(
+        current="local",
+        source=C.ENVIRONMENT_VARIABLE,
+    ),
+    Scenario(
+        current={},
+        exit_message_substr=(
+            "The specified time zone '{}', set by settings file,"
+            " must be a string but was found to be a 'dict'."
+        ),
+        source=C.USER_CFG,
+    ),
+    Scenario(
+        current=True,
+        exit_message_substr=(
+            "The specified time zone 'True', set by settings file,"
+            " must be a string but was found to be a 'bool'."
+        ),
+        source=C.USER_CFG,
+    ),
+    Scenario(
+        current="foo",
+        source=C.USER_CFG,
+        exit_message_substr=(
+            "The specified time zone 'foo', set by settings file, could not be found."
+        ),
+    ),
+    Scenario(
+        current="Japan",
+        source=C.USER_CFG,
+    ),
+    Scenario(
+        current="local",
+        source=C.USER_CFG,
+    ),
+    Scenario(
+        current="foo",
+        exit_message_substr=(
+            "The specified time zone 'foo', set by command line, could not be found."
+        ),
+        source=C.USER_CLI,
+    ),
+    Scenario(
+        current="Japan",
+        source=C.USER_CLI,
+    ),
+    Scenario(
+        current="local",
+        source=C.USER_CLI,
+    ),
+)
+
+
+@pytest.mark.parametrize(argnames="data", argvalues=test_data, ids=str)
+def test_pp_direct(data: Scenario):
+    """Test the time zone post processor.
+
+    :param data: The test data
+    """
+    settings = NavigatorConfiguration
+    entry = settings.entry("time_zone")
+
+    entry.value.current = data.current
+    entry.value.source = data.source
+
+    _messages, exit_messages = NavigatorPostProcessor().time_zone(
+        entry=entry,
+        config=settings,
+    )
+
+    if data.exit_message_substr:
+        assert data.exit_message_substr in exit_messages[0].message
+    else:
+        assert entry.value.current == data.expected
+
+
+env_var_test_data = [s for s in test_data if s.source is C.ENVIRONMENT_VARIABLE]
+
+
+@pytest.mark.parametrize(argnames="data", argvalues=env_var_test_data, ids=str)
+def test_env_var(monkeypatch: pytest.MonkeyPatch, data: Scenario):
+    """Test the time zone post processor using the environment variable.
+
+    :param monkeypatch: The monkey patch fixture
+    :param data: The test data
+    """
+    application_configuration = NavigatorConfiguration
+    application_configuration.internals.initializing = True
+    configurator = Configurator(application_configuration=application_configuration, params=[])
+
+    monkeypatch.setenv("TZ", str(data.current))
+
+    _messages, exit_messages = configurator.configure()
+
+    if data.exit_message_substr:
+        assert data.exit_message_substr in exit_messages[1].message
+    else:
+        assert application_configuration.entry("time_zone").value.current == data.expected