-2

I need to output to the console the calendar of the current month in Ruby. The result should be similar to ncal on UNIX-like systems. I found a solution for C ++ but can't adapt for Ruby. So far, I only realized that I need to use nested loops to output the height and width. Tell me in which direction to move?

require 'date'
days = %w[Mun Tue Wed Thu Fri Sat Sun]
puts "    #{Date::MONTHNAMES[Date.today.month]} #{Date.today.year}"
i = 0
start_month = (Date.today - Date.today.mday + 1).strftime("%a")

while i < days.size
  print days[i]
  j = 1

  while j <= 31
    if days[i] == start_month 
      print " #{j}"
    end
    j += 7
  end

  i += 1
  puts
end
CekyndA
  • 9
  • 1
  • reference: https://github.com/ruby/ruby/blob/master/sample/cal.rb, a simple calendar written by ruby core team. – Lam Phan Jul 24 '21 at 12:59
  • 1
    *"I only realized that I need to use nested loops to output the height and width"* -- That's quite a vague starting point! Do you have some actual code to share? Other than literally writing the whole from scratch, or pasting a link to a calendar someone else has already written (like the commenter above!), it's not clear how I can help you. And neither of those two options appeals to me. – Tom Lord Jul 24 '21 at 13:53
  • Have you figured out how to display the current month and year? Have you tried checking which day of the week the month starts on? Is there anything more specific that's stumping you about your implementation so far? – Tom Lord Jul 24 '21 at 13:55
  • @TomLord Yes you are right. I'm sorry. I managed to display only numbers from the beginning of the month to the console. The code that I have at the moment I added to the question. I will be grateful if you can tell me what to think about or what to read – CekyndA Jul 24 '21 at 22:26

4 Answers4

1

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:

  1. 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.
  2. 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.
  3. You've hardcoded 31 for the number of days in the month. This should be fixed, of course. (Use the Date library!)
  4. 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").
  5. 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.
  6. 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!!).
Tom Lord
  • 27,404
  • 4
  • 50
  • 77
  • Also a very minor point: I fixed your spelling. **Mon**day is spelt with M**o**n, not M**u**n. English is weird. – Tom Lord Jul 25 '21 at 12:31
  • Thank you so much for help. I finally did it with hints help. And one more time sorry about uncorrectable question. – CekyndA Jul 25 '21 at 19:01
0

This is one way:

  • Construct a one-dimensional array, beginning with the daynames (Mon Tue ...).
  • Figure out a way to determine with how many "blanks" the month starts (these are days from the previous month. wday might help). Attach that amount of empty strings to the array.
  • Determine how many days the month has (hint Date.new(2021,7,-1), and attach all these daynumbers to the array.
  • Attach empty strings to the array until the size of the array is divisible by 7 (or better, calculate). Skip this if you're skipping the last bullet.
  • Convert all elements of this array to right-adjusted strings of size 3 or some-such.
  • Use each_slice(7) to slice the array into weeks.
  • If desired, transpose this array of week-slices to mimic the ncal output.
steenslag
  • 79,051
  • 16
  • 138
  • 171
0

Thank you for your help, literally 10 hours and I figured it out thanks to you. I apologize once again for the initially incorrectly posed question.

With the help of hints, I assembled such a solution.

require 'date'
days = %w[Mon Tue Wed Thu Fri Sat Sun]
p days
blanks = Date.new(2021,7,1).wday - 1

blanks.times do
  days.push(' ')
end

days_in_month = Date.new(2021, 7, -1).day
days_in_month
day = 1

while day <= days_in_month
  days.push(day)
  day += 1
end 

unless (days.size % 7) == 0
 days.push(' ')
end 

days.join(', ')
new_arr = days.each_slice(7).to_a
 puts"Массив дней: #{new_arr}"

for i in 0...7
 
 for j in 0...new_arr.size
  print "  #{new_arr[j][i]}"
 end
puts
end
CekyndA
  • 9
  • 1
0
require 'date'

# init
DAYS_ORDER = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
today = Date.today
month = today.month
year = today.year
first_day = Date.new(year, month, 1)
last_day = Date.new(year, month, -1)
hash_days = {}

# get all current months days and add to hash_days
first_day.upto(last_day) { |day| hash_days[day.day] = day.strftime('%a') }

# group by wday
grouped_hash = hash_days.group_by { |day| day.pop }.transform_values { |days| days.flatten }

# sort by wday from DAYS_ORDER
sorted_arr = grouped_hash.sort_by { |k, v| DAYS_ORDER.index(k) }

#  rendering current month's calendar with mark current day
## title
print "\x1b[4m#{today.strftime("%B %Y")}\x1b[0m\n"

## calendar
indent = true

sorted_arr.each do |wday, days|
  print wday

  if days[0] != 1 && indent == true
    print "   "
  else
    indent = false
  end

  days.each do |value|
    spaces = " " * (value > 9 ? 1 : 2)
    str_day = spaces + value.to_s
    current_day = "\x1b[1;31m#{str_day}\x1b[0m"

    print value == today.day ? current_day : str_day
  end

  puts
end

view

iL53n
  • 13
  • 3