Repository: Delgan/loguru

Issue:
The log file is abnormal when the log is rotating
Hi, thank you so much for your project, it's really great. However, some problems have been found in the process of using it recently. Can you help to have a look?

I created a new file `logger.py`
```python
from loguru import logger

logger.remove()
logger.add(
    'logs/log.{time:YYYY-MM-DD}.log',
    level='INFO',
    rotation="00:00",
    retention='1 months',
    enqueue=True
)

logger.info('hello world')
logger.info('hello world')
```
I execute it on a time period of zero, for example at 00:10. Got two log files, but the other one is not what I expected. When I set the rotation parameter to 01:00, the normal result of executing the code will only generate the file `log.log`
`python logger.py`
<img width="378" alt="image" src="https://github.com/Delgan/loguru/assets/23091750/c4cd15b9-d9f6-4a87-8727-d94df736b8a6">

This should be a bug that must appear, and it will appear when it is executed at the same hour of the time set by the rotation parameter value.
@Delgan 


Discussion:

Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 47ca76e..8286a31 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -2,10 +2,11 @@
 =============
 
 - Add a new ``context`` optional argument to ``logger.add()`` specifying ``multiprocessing`` context (like ``"spawn"`` or ``"fork"``) to be used internally instead of the default one (`#851 <https://github.com/Delgan/loguru/issues/851>`_).
-- Raise ``ValueError`` if an attempt to use nanosecond precision for time formatting is detected (`#855 <https://github.com/Delgan/loguru/issues/855>`_).
 - Add support for true colors on Windows using ANSI/VT console when available (`#934 <https://github.com/Delgan/loguru/issues/934>`_, thanks `@tunaflsh <https://github.com/tunaflsh>`_).
-- Fix inverted ``"<hide>"`` and ``"<strike>"`` color tags (`#943 <https://github.com/Delgan/loguru/issues/943>`_, thanks `@tunaflsh <https://github.com/tunaflsh>`_).
+- Fix file possibly rotating too early or too late when re-starting an application around midnight (`#894 <https://github.com/Delgan/loguru/issues/894>`_).
+- Fix inverted ``"<hide>"`` and ``"<strike>"`` color tags (`#943 <https://github.com/Delgan/loguru/pull/943>`_, thanks `@tunaflsh <https://github.com/tunaflsh>`_).
 - Fix possible errors raised by logging non-picklable ``Exception`` instances while using ``enqueue=True`` (`#342 <https://github.com/Delgan/loguru/issues/342>`_, thanks `@ncoudene <https://github.com/ncoudene>`_).
+- Raise ``ValueError`` if an attempt to use nanosecond precision for time formatting is detected (`#855 <https://github.com/Delgan/loguru/issues/855>`_).
 
 
 `0.7.0`_ (2023-04-10)
diff --git a/loguru/_file_sink.py b/loguru/_file_sink.py
index 35b252f..564e93f 100644
--- a/loguru/_file_sink.py
+++ b/loguru/_file_sink.py
@@ -128,15 +128,12 @@ class Rotation:
                     limit = start_time.astimezone(record_time.tzinfo).replace(tzinfo=None)
                     limit = self._step_forward(limit)
                 else:
-                    limit = datetime.datetime(
-                        start_time.year,
-                        start_time.month,
-                        start_time.day,
-                        time_init.hour,
-                        time_init.minute,
-                        time_init.second,
-                        time_init.microsecond,
-                        record_time.tzinfo if time_init.tzinfo is None else time_init.tzinfo,
+                    tzinfo = record_time.tzinfo if time_init.tzinfo is None else time_init.tzinfo
+                    limit = start_time.astimezone(tzinfo).replace(
+                        hour=time_init.hour,
+                        minute=time_init.minute,
+                        second=time_init.second,
+                        microsecond=time_init.microsecond,
                     )
 
                     if limit <= start_time:


Test patch:
diff --git a/tests/test_filesink_rotation.py b/tests/test_filesink_rotation.py
index ef211ed..1bd09cf 100644
--- a/tests/test_filesink_rotation.py
+++ b/tests/test_filesink_rotation.py
@@ -605,6 +605,98 @@ def test_time_rotation_when_timezone_changes_backward_rename_file(freeze_time, t
     )
 
 
+@pytest.mark.parametrize(
+    "rotation",
+    [
+        "00:15",
+        datetime.time(0, 15, 0),
+        datetime.time(23, 15, 0, tzinfo=datetime.timezone.utc),
+        datetime.time(0, 15, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=+3600))),
+        datetime.time(22, 15, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=-3600))),
+    ],
+)
+def test_dont_rotate_earlier_when_utc_is_one_day_before(freeze_time, tmp_path, rotation):
+    with freeze_time("2018-10-24 00:30:00", ("CET", +3600)) as frozen:
+        logger.add(tmp_path / "test.log", format="{message}", rotation=rotation)
+        logger.info("First")
+        logger.remove()
+
+        frozen.tick(delta=datetime.timedelta(hours=1))
+        logger.add(tmp_path / "test.log", format="{message}", rotation=rotation)
+        logger.info("Second")
+        logger.remove()
+
+        frozen.tick(delta=datetime.timedelta(hours=23))
+        logger.add(tmp_path / "test.log", format="{message}", rotation=rotation)
+        logger.info("Third")
+        logger.remove()
+
+    check_dir(
+        tmp_path,
+        files=[
+            ("test.2018-10-24_00-30-00_000000.log", "First\nSecond\n"),
+            ("test.log", "Third\n"),
+        ],
+    )
+
+
+@pytest.mark.parametrize(
+    "rotation",
+    [
+        "23:45",
+        datetime.time(23, 45, 0),
+        datetime.time(0, 45, 0, tzinfo=datetime.timezone.utc),
+        datetime.time(1, 45, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=+3600))),
+        datetime.time(23, 45, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=-3600))),
+    ],
+)
+def test_dont_rotate_later_when_utc_is_one_day_after(freeze_time, tmp_path, rotation):
+    with freeze_time("2018-10-23 23:30:00", ("CET", -3600)) as frozen:
+        logger.add(tmp_path / "test.log", format="{message}", rotation=rotation)
+        logger.info("First")
+        logger.remove()
+
+        frozen.tick(delta=datetime.timedelta(hours=1))
+        logger.add(tmp_path / "test.log", format="{message}", rotation=rotation)
+        logger.info("Second")
+        logger.remove()
+
+        frozen.tick(delta=datetime.timedelta(hours=23))
+        logger.add(tmp_path / "test.log", format="{message}", rotation=rotation)
+        logger.info("Third")
+        logger.remove()
+
+    check_dir(
+        tmp_path,
+        files=[
+            ("test.2018-10-23_23-30-00_000000.log", "First\n"),
+            ("test.log", "Second\nThird\n"),
+        ],
+    )
+
+
+@pytest.mark.parametrize("timezone", [("CET", +3600), ("CET", -3600), ("UTC", 0)])
+def test_rotation_at_midnight_with_date_in_filename(freeze_time, tmp_path, timezone):
+    with freeze_time("2018-10-23 23:55:00", timezone) as frozen:
+        logger.add(tmp_path / "test.{time:YYYY-MM-DD}.log", format="{message}", rotation="00:00")
+        logger.info("First")
+        logger.remove()
+
+        frozen.tick(delta=datetime.timedelta(minutes=10))
+
+        logger.add(tmp_path / "test.{time:YYYY-MM-DD}.log", format="{message}", rotation="00:00")
+        logger.info("Second")
+        logger.remove()
+
+    check_dir(
+        tmp_path,
+        files=[
+            ("test.2018-10-23.log", "First\n"),
+            ("test.2018-10-24.log", "Second\n"),
+        ],
+    )
+
+
 @pytest.mark.parametrize("delay", [False, True])
 def test_time_rotation_reopening_native(tmp_path_local, delay):
     with tempfile.TemporaryDirectory(dir=str(tmp_path_local)) as test_dir: