Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Code Reviews

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

Post History

83%
+8 −0
Code Reviews Counting Sundays without Python datetime module

First of all, I've added a print in your code to show the dates: if days % 7 == 0 and current_day == 1: print(f'{current_year}-{current_month:>02}-{current_day:>02}') sundays += 1 ...

posted 3y ago by hkotsubo‭

Answer
#1: Initial revision by user avatar hkotsubo‭ · 2021-06-15T19:33:11Z (over 3 years ago)
First of all, I've added a `print` in your code to show the dates:

    if days % 7 == 0 and current_day == 1:
        print(f'{current_year}-{current_month:>02}-{current_day:>02}')
        sundays += 1

And in the first lines I've noticed that you're actually counting the **Tuesdays**:

```none
1901-01-01
1901-10-01
1902-04-01
1902-07-01
1903-09-01
1903-12-01
... # all the dates are Tuesdays
```

It was a coincidence that the result was "correct" (`171`), but in programming we shouldn't rely on coincidences.

---
# Calculating the day of month

I didn't use the given information (I didn't care about Jan 1st 1900 being a Monday). Instead, I <strike>cheated</strike> ported/adapted [this Java code](https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/time/LocalDate.java) to Python, in order to get the day of week, given a day, month and year:

```python
def isLeapYear(year): # no need to break in 2 functions
    return (year % 4 == 0) and (year % 100 != 0 or year % 400 == 0)

# number of days in a 400 year cycle
DAYS_PER_CYCLE = 146097

# The number of days from year zero to year 1970.
# There are five 400 year cycles from year zero to 2000.
# There are 7 leap years from 1970 to 2000.
DAYS_0000_TO_1970 = (DAYS_PER_CYCLE * 5) - (30 * 365 + 7)

def toEpochDay(day, month, year):
    total = 365 * year;
    if year >= 0:
        total += (year + 3) // 4 - (year + 99) // 100 + (year + 399) // 400
    else:
        total -= year // -4 - year // -100 + year // -400
    total += (367 * month - 362) // 12
    total += day - 1;
    if month > 2:
        total -= 1
        if not isLeapYear(year):
            total -= 1
    return total - DAYS_0000_TO_1970;

# it returns values from 0 (Monday) to 6 (Sunday)
def getDayOfWeek(day, month, year):
    return (toEpochDay(day, month, year) + 3) % 7
```

And, considering that the problem only wants to know about the first day of each month, there's no need to advance one day at a time. I can advance the whole month instead:

```python
# considering that day of month is always 1
def count_sundays(start_month, start_year, end_month, end_year):
    current_month, current_year = start_month, start_year
    sundays = 0

    while current_month != end_month or current_year != end_year:
        if getDayOfWeek(1, current_month, current_year) == 6:
            sundays += 1

        # go to next month (considering that day is always 1)
        if current_month == 12:
            current_month = 1
            current_year += 1
        else:
            current_month += 1
    return sundays

print(count_sundays(1, 1901, 12, 2000)) # 171
```

---
If you want a more generic solution that accepts any day of month, you can still advance one month at a time. But if the day is, let's say, 31, we can skip months that don't have 31 days. Like this:

```python
days_in_month = [None, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]

def count_dow(start_day, start_month, start_year, end_day, end_month, end_year, dow):
    current_day, current_month, current_year = start_day, start_month, start_year
    count = 0

    while current_day != end_day or current_month != end_month or current_year != end_year:
        if getDayOfWeek(current_day, current_month, current_year) == dow:
            #print(f'{current_year}-{current_month:>02}-{current_day:>02}')
            count += 1

        # go to next month (skipping months that don't have current_day)
        while True:
            if current_month == 12:
                current_month = 1
                current_year += 1
            else:
                current_month += 1
            last_day = days_in_month[current_month]
            if current_month == 2 and isLeapYear(current_year):
                last_day = 29
            # this month has the current_day
            if current_day <= last_day:
                break

    return count

# last parameter is from 0 (Monday) to 6 (Sunday)
print(count_dow(1, 1, 1901, 1, 12, 2000, 6)) # 171
```

---

### Testing

I've made a quick test, using the [`timeit` module](https://docs.python.org/3/library/timeit.html):

```python
from timeit import timeit

# run 100 times each test
params = { 'number' : 100, 'globals': globals() }

# print the execution time in seconds

# considering day is always 1
print(timeit('count_sundays(1, 1901, 12, 2000)', **params))

# generic version (but still skipping one month at a time)
print(timeit('count_dow(1, 1, 1901, 1, 12, 2000, 6)', **params))

# your solution (without printing the dates, of course)
print(timeit('solution(1, 1, 1901, 31, 12, 2000)', **params))
```

Of course the execution time will vary according to many conditions (such as hardware, etc), in my machine I've got:

```none
0.09944487499888055
0.09361006100152736
1.0343273129983572
```

Therefore, the solutions above were about 10 times faster.


---
# Using the given information

Considering that 1900 isn't a leap year and Jan 1st 1900 is a Monday: 1900 has 365 days, and `365 % 7` is 1 (52 weeks plus one day, hence, the last day of 1900 is also a Monday, so Jan 1st 1901 is a Tuesday).

Would I be cheating if, knowing that the start date is a Tuesday, I'd start my count on the next Sunday (Jan 6th)? With this, I could just add 7 days, making the proper adjustments, and increment the count if the result is 1:

```python
def count():
    current_day, current_month, current_year = 6, 1, 1901 # "cheating"
    end_day, end_month, end_year = 1, 12, 2000 # I'm lazy and not using parameters
    c = 0
    while True:
        last_day = days_in_month[current_month] # it's the same list as in the previous examples
        if current_month == 2 and isLeapYear(current_year): # isLeapYear is the same function defined above
            last_day = 29
        # add 7 days
        current_day += 7
        if current_day > last_day: # month changed
            current_day -= last_day
            if current_month == 12:
                current_month = 1
                current_year += 1
            else:
                current_month += 1
        if current_year > end_year:
            break
        if current_day == 1:
            c += 1
    return c
```

I didn't defined parameters in the function, but that can be easily adapted. It was just to show the algorithm, given that we know the first Sunday in the interval (assuming you needed to use all the given information).

Testing this with `timeit`, it was a little bit faster than the previous solutions.

If you didn't like the "cheat", you could start at Jan 1st and make a small loop, adding one day at a time, until it finds a Sunday (perhaps using the `getDayOfWeek` function). Then, I can start the algorithm above, iterating each 7 days.