Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

odd daylight saving behaviour #598

Closed
bellt opened this issue Sep 17, 2023 · 2 comments
Closed

odd daylight saving behaviour #598

bellt opened this issue Sep 17, 2023 · 2 comments

Comments

@bellt
Copy link

bellt commented Sep 17, 2023

I have a job I want to schedule every day at a particular time UTC.

import schedule
from datetime import datetime


def job():
    return True


schedule.every().day.at("00:00", "Etc/UTC").do(job)

print(
    f"Current local time: {datetime.now()}\n"
    f"Next run time: {schedule.jobs[0].next_run}\n"
    f"Idle time: {round(schedule.idle_seconds() / 3600)} hours"
)

Running at 10:59 local time (which is Pacific/Auckland - currently UTC+12) I get the following output:

Current local time: 2023-09-18 10:59:23.170523
Next run time: 2023-09-19 12:00:00
Idle time: 25 hours

This shows the bug highlighted in issue #579 with it skipping the closest 00:00UTC.

If I change the schedule to
schedule.every().monday.at("00:00", "Etc/UTC").do(job)

Then I get the following output

Current local time: 2023-09-18 11:01:08.323132
Next run time: 2023-09-18 13:00:00
Idle time: 2 hours

As you can see, it is now scheduling the job for 13:00 local time. However it should be 12:00 local time because we are currently UTC+12. This behaviour started yesterday (Sunday). Prior to that it would schedule the job at the correct time. So it looks like somewhere daylight saving time is being incorrectly applied a week early (my local timezone shifts to daylight saving [UTC+13] next Sunday). But this is only happening with schedule.every().weekday and not schedule.every().day

SijmenHuizenga added a commit to SijmenHuizenga/schedule that referenced this issue Oct 1, 2023
SijmenHuizenga added a commit to SijmenHuizenga/schedule that referenced this issue Oct 1, 2023
@SijmenHuizenga
Copy link
Collaborator

Resolved with #583 and released in 1.2.1. I am also adding a test to cover your specific case in #602. Cheers!

@bellt
Copy link
Author

bellt commented Nov 10, 2023

Hi There,
From my testing, it looks like 1.2.1 has fixed this problem for the example given. However, if I go the other way and fastforward to one week before daylight saving time ends next year, I encounter the problem again. Here is some code which demonstrates this:

import schedule
import datetime
import time
import os

# POSIX TZ string format
TZ_AUCKLAND = "NZST-12NZDT-13,M10.1.0/02:00:00,M3.3.0/03:00:00"

def job():
    return True

class mock_datetime:
    """
    Monkey-patch datetime for predictable results
    """

    def __init__(self, year, month, day, hour, minute, second=0, zone=None):
        self.year = year
        self.month = month
        self.day = day
        self.hour = hour
        self.minute = minute
        self.second = second
        self.zone = zone
        self.original_datetime = None
        self.original_zone = None

    def __enter__(self):
        class MockDate(datetime.datetime):
            @classmethod
            def today(cls):
                return cls(self.year, self.month, self.day)

            @classmethod
            def now(cls):
                return cls(
                    self.year,
                    self.month,
                    self.day,
                    self.hour,
                    self.minute,
                    self.second,
                )

        self.original_datetime = datetime.datetime
        datetime.datetime = MockDate

        self.original_zone = os.environ.get("TZ")
        if self.zone:
            os.environ["TZ"] = self.zone
            time.tzset()

        return MockDate(
            self.year, self.month, self.day, self.hour, self.minute, self.second
        )

    def __exit__(self, *args, **kwargs):
        datetime.datetime = self.original_datetime
        if self.original_zone:
            os.environ["TZ"] = self.original_zone
            time.tzset()

with mock_datetime(2024, 4, 1, 10, 00, 0, TZ_AUCKLAND):
    # We've set the date to 10:00 on Monday 1 April NZDT (which is 21:00 on Sunday 31 March UTC)
    # This is the week before daylight saving ends

    # Testing correct application of daylight saving
    schedule.clear()
    next = schedule.every().sunday.at("23:00", "UTC").do(job).next_run
    print(
    f"Current local time: {datetime.datetime.now()}\n"
    f"Next run time: {schedule.jobs[0].next_run} (expected: 2024-04-01 12:00:00)\n"
    f"Idle time: {round(schedule.idle_seconds() / 3600)} hours (expected: 2 hours)\n"
    )

with mock_datetime(2024, 4, 8, 10, 00, 0, TZ_AUCKLAND):
    # We've set the date to 10:00 on Monday 8 April NZST (which is 22:00 on Sunday 7 April UTC)
    # This is the week after daylight saving ends

    # Testing correct application of daylight saving (or lack of, at this date)
    schedule.clear()
    next = schedule.every().sunday.at("23:00", "UTC").do(job).next_run
    print(
    f"Current local time: {datetime.datetime.now()}\n"
    f"Next run time: {schedule.jobs[0].next_run} (expected: 2023-04-08 11:00:00)\n"
    f"Idle time: {round(schedule.idle_seconds() / 3600)} hours (expected: 1 hours)"
    )

The output from this is:

Current local time: 2024-04-01 10:00:00
Next run time: 2024-04-01 11:00:00 (expected: 2024-04-01 12:00:00)
Idle time: 1 hours (expected: 2 hours)

Current local time: 2024-04-08 10:00:00
Next run time: 2024-04-08 11:00:00 (expected: 2023-04-08 11:00:00)
Idle time: 1 hours (expected: 1 hours)

Which as you can see doesn't have the expected next run time or idle time for the first example, which is one week before daylight saving ends.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants