0

I don't understand why the code below is returning wrong values.

Ideally, it should return the next Tuesday 4th from today. Instead, it returns today's date, '2021-08-17':

import datetime

def tue_the_fourth():
    this_d = datetime.date.today()
    if this_d.weekday() ==1 and this_d.day ==4:
        return this_d.strftime("%Y-%m-%d")
    else:
        while (this_d.weekday() is not 1) and (this_d.day is not 4):
            this_d=this_d + datetime.timedelta(days=1)
            print(this_d)
    return this_d.strftime("%Y-%m-%d")
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Kalphie
  • 3
  • 1
  • 4
    So, what do you get instead? An exception, a wrong value (share that value!), your computer starts singing Opera badly? Please share those details, they could help us help you! – Martijn Pieters Aug 17 '21 at 17:58
  • Be aware that using `X is not 1` is a bad practice. It only works for a small range of integers (-5 to 255 IIRC) thanks to an optimization done by CPython. It will not work in general. – Brian61354270 Aug 17 '21 at 17:59
  • 6
    Style note: `is not 1` and `is not 4` is not the correct way to test for integer values, use `!=`. That this works at all is a coincidence of how the main Python implementation caches small integer objects. Python 3.10 and up will warn you about those lines. – Martijn Pieters Aug 17 '21 at 17:59
  • @MartijnPieters I would say it's more than a mere style issue, it's just wrong, although as you state, on CPython it will work by accident. – juanpa.arrivillaga Aug 17 '21 at 18:00
  • Besides that, it seems you really want to use OR and not AND. – trincot Aug 17 '21 at 18:01
  • @juanpa.arrivillaga: I prefixed it with 'style note' to avoid anyone thinking that that was the core of the problem. – Martijn Pieters Aug 17 '21 at 18:22

3 Answers3

2

I'll ignore the incorrect use of is not for now, lets focus on the core issue: your while test is almost always false. Sure, it is false for your target condition:

>>> (1 is not 1) and (4 is not 4)
False

Think about it:

  • 1 is not 1 is False, because 1 is equal to 1, so it's the opposite of your test.
  • 4 is not 4 is False, because 4 is equal to 4, so it's also the opposite of your test.

But, if you changed one of those values in the test you would still get False:

  • 1 is not 1 is False,
  • 17 is not 4 is True,

but your while loop only runs if both tests are true, because you used and. Since today is a Tuesday, but the date is the 17th of the month, the while loop will not run. On other days, it'll run until the first tuesday after the current date, or when it reaches the 4th of a month, whatever comes first.

You want to run the loop is one or the other is true, instead, so you want to use or here, but see below for a better option.

Note that the correct test for integer inequality, is !=, so you wanted to use:

while this_d.weekday() != 1 or this_d.day != 4:

but it might be easier to say:

while not (this_d.weekday() == 1 and this_d.day == 4):

You probably started with this_d.weekday() == 1 and this_d.day == 4 and tried to invert that test, but you can't just add not to those tests and have it pass. You fell in to the classic boolean inversion trap and want to learn about De Morgan's laws:

  • not (A or B) = (not A) and (not B)
  • not (A and B) = (not A) or (not B)

The inverse of A and B is (not A) or (not B). But, sometimes it is just clearer to keep the not (A and B) test as is.

Further notes:

  • You don't need the first if test. The loop will not run when the condition is met too, so the whole if branch is redundant.
  • You can use this_d += datetime.timedelta(days=1) instead of this_d = this_d + datetime.timedelta(days=1). Look up augmented assignment, it is easier and shorter here.
  • I'd return the date object. That would make your function re-usable in more situations. Sometimes you don't need to format a date object immediately!

So, I'd write it as:

def tue_the_fourth():
    this_d = datetime.date.today()
    while not (this_d.weekday() == 1 and this_d.day == 4):
        this_d += datetime.timedelta(days=1)
    return this_d

and only format the date when you have to convert it to a string, as late as possible.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
2

Two issues:

  • Don't compare primitive values with is or is not, but with == or !=.
  • The while condition (which is wrong) is not the negation of the if condition (which is correct)

I would also change the algorithm so that it doesn't step with one day at a time, since it is clear when the 4th isn't a Tuesday, none of the next days of the same month will be a match. You might as well add a month to the date and only then try again.

Also, you could make a generic function that finds a certain day of the month that is a certain weekday.

Here is an implementation of that idea:

import datetime

def next_month(dt, day, month_count=1):
    try:
        return datetime.datetime(dt.year + int(dt.month + month_count > 12),
                                 (dt.month + month_count - 1) % 12 + 1, day)
    except ValueError:  # invalid day in this month (like 30 February)
        return next_month(dt, day, month_count+1)

def find_next(day, weekday):
    this_d = datetime.date.today()
    this_d = next_month(this_d, day, int(this_d.day > day))
    while this_d.weekday() != weekday:
        this_d = next_month(this_d, day)
    return this_d.strftime("%Y-%m-%d")

# find next 4th on a Tuesday
print(find_next(4, 1))  # 2022-01-04 (when run on 2021-08-17)
trincot
  • 317,000
  • 35
  • 244
  • 286
1

If I understand your goal correctly, here's what I would write:

def tue_the_fourth():
    d = datetime.date.today()
    while (d.weekday() != 1) or (d.day != 4):
        d = d + datetime.timedelta(days=1)
    print(d)
    return d.strftime("%Y-%m-%d")

What's changed and why?

  1. using is not for the logical != is bad practice and inaccurate.

  2. I changed the while to do a logical or on the conditions instead of and, basically this is how to get the required answer. Using answer will stop whenever we get a day that is either a Tuesday or a 4th, not both.

  3. Overall cleanliness. No need for the if - else statement, if today is a Tuesday the 4th, then the while doesn't run a single time and today is returned.

aydee45
  • 516
  • 3
  • 14