I'll take your solution so far, and try to give some specific pointers for how to progress with it - but of course, there are many different ways to approach this problem in general, so this is by no means the only approach!
The first critical issue (as you're aware!) is that you're only printing things for the row starting on the 1st of the month, due to this line:
if days[i] == start_month
Sticking with the current overall design, we know we'll need to print something for every line, so clearly a conditional like this isn't going to work. Let's try removing it.
Firstly, it will be more convenient to know which day of the week the month started on as a number, not a string, so we can easily calculate offsets against another day. Let's do that with:
# e.g. for 1st July 2021 this was a Thursday, so we get `4`.
start_of_month_weekday = (Date.today - Date.today.mday + 1).cwday
Next (and this is the crucial step!), we can use the above information to find out "which day of the month is it, on this day of the week?"
Here a first version of that calculation, incorporated into your solution so far:
require 'date'
days = %w[Mon Tue Wed Thu Fri Sat Sun]
puts " #{Date::MONTHNAMES[Date.today.month]} #{Date.today.year}"
i = 0
# e.g. for 1st July 2021 this was a Thursday, so we get `4`.
start_of_month_weekday = (Date.today - Date.today.mday + 1).cwday
while i < days.size
print days[i]
day_of_month = i - start_of_month_weekday + 2 # !!!
while day_of_month <= 31
print " #{day_of_month}"
day_of_month += 7
end
i += 1
puts
end
This outputs:
July 2021
Mon -2 5 12 19 26
Tue -1 6 13 20 27
Wed 0 7 14 21 28
Thu 1 8 15 22 29
Fri 2 9 16 23 30
Sat 3 10 17 24 31
Sun 4 11 18 25
Not bad! Now we're getting somewhere!
I'll leave you to figure out the rest .... But here are some clues, for what I'd tackle next:
- This code,
print " #{day_of_month}"
, needs to print a "blank space" if the day number is less than 1. This could be done with a simple if
statement.
- Similarly, since you want this calendar to line up neatly in a grid, you need this code to always print a something two characters wide.
sprintf
is your friend here! Check out the "Examples of width", about halfway down the page.
- You've hardcoded
31
for the number of days in the month. This should be fixed, of course. (Use the Date
library!)
- It's funny how you used
strftime("%a")
in one place, yet constructed the calendar title awkwardly in the line above! Take a look at the documentation for formatting dates; it's extremely flexible. I think you can use: Date.today.strftime("%B %Y")
.
- If you'd like to add some colour (or background colour?) to the current day of the month, consider doing something like this, or use a library to assist.
- Using
while
loops works OK, but is quite un-rubyish. In 99% of cases, ruby has even better tools for the job; it's a very expressive language - iterators are king! (I'm guessing you first learned another language, before ruby? Seeing while
loops, and/or for
loops, is a dead giveaway that you're more familiar with a different language.) Instead of the outer while loop (while i < days.size
), you could use days.each_with_index
. And instead of the inner while loop (while j < 31
), you could use day_of_month.step(31, 7)
(how cool is that!!).