Making a monthly calendar

Hey

I trying to do a calendar based app…

So i have a monthly view with 5 weeks of 7 days = 35 boxes…

Just like google calendar i need to show the selected month, and fill in
the blanks with the previous and next months days…

so february is ending today on a friday. So in the last 2 boxes of the
5. week i need to show march 1 and 2 and the same in the front…

I have this Month class. Which contains all the dates for a given month,
as well as references to the next and previous months…

there must be some kind of pattern im not getting bevause i keep
slamming my head against the wall on this…

anyone who have a quuck solution???

thanks!!

mikkel

Something like this?

daniel@daniel-desktop:~$ cat /tmp/dats.rb
require ‘date’

start_date = Date.civil(2007, 1, 27) # Must be a Sunday.
0.upto(4 * 7 - 1) do |offset| # 4 weeks
curr_date = start_date + offset
print curr_date.mday.to_s.rjust(3)
if offset % 7 == 6
puts
end
end
puts
daniel@daniel-desktop:~$ ruby /tmp/dats.rb
27 28 29 30 31 1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23

daniel@daniel-desktop:~$

The formatting is better if you run it at the command line.

Dan

On Fri, Feb 29, 2008 at 4:30 PM, Mikkel B. [email protected]
wrote:

  1. week i need to show march 1 and 2 and the same in the front…

mikkel

Here’s one for fun. I’m pretty sure you don’t want to use this (for
all I know, you might be laughed out of the room :), but it
demonstrates some methods…

require ‘enumerator’
o = (t = Date.today).wday
c = (-1…1).inject([]) {|a, i| a << (t.month + i)}.map {|i|
1…Date.new( t.year, i, -1 ).day}.map {|i| i.to_a}
o.times {c[1].unshift c[0].pop}
c[1…2].flatten.slice(0…35).compact.each_slice(7) {|i| p i}

You’d have to tidy up the output of course (using #join or whatever).
Also, as you can see, 5 weeks doesn’t necessarily cover a month. In
March’s calendar for 2008, there are 6 days out of February.

Todd

On Sat, Mar 1, 2008 at 1:51 AM, Todd B. [email protected] wrote:

On Fri, Feb 29, 2008 at 4:30 PM, Mikkel B. [email protected] wrote:

anyone who have a quuck solution???

thanks!!

mikkel

Here’s one for fun. I’m pretty sure you don’t want to use this (for
all I know, you might be laughed out of the room :), but it
demonstrates some methods…

[snip beginning code]

c = (-1…1).inject([]) {|a, i| a << (t.month + i)}.map {|i|
1…Date.new( t.year, i, -1 ).day}.map {|i| i.to_a}

That is supposed to be one line, just so you know, if your reader
didn’t pick it up right.

Todd

Todd B. wrote:

On Fri, Feb 29, 2008 at 4:30 PM, Mikkel B. [email protected]
wrote:

  1. week i need to show march 1 and 2 and the same in the front…

mikkel

Here’s one for fun. I’m pretty sure you don’t want to use this (for
all I know, you might be laughed out of the room :), but it
demonstrates some methods…

woot, it actually works…

ive been doing ruby since 2000 but i cant undestand what the h*ll going
on there…

can you give me a walkthrough or a more humane rewrite :wink:

On Sat, Mar 1, 2008 at 3:03 AM, Todd B. [email protected] wrote:

just running through -1 to 1 and adding to the current month

day_sets[1…2].flatten.slice(0…35).compact.each_slice(7) {|i| p i}

Keep in mind scope. The “i” variable is local to each block; all by
himself. It doesn’t have to be “i”, also.

My apologies, this was aimed at newbies, not at you.

Todd

On Sat, Mar 1, 2008 at 2:34 AM, Mikkel B. [email protected] wrote:

all I know, you might be laughed out of the room :), but it
demonstrates some methods…

woot, it actually works…

ive been doing ruby since 2000 but i cant undestand what the h*ll going
on there…

can you give me a walkthrough or a more humane rewrite :wink:

Okay, changing the code…

require ‘enumerator’

I need that for the each_slice method later

today = Date.today
offset = today.wday

gives me the day of the week

months_of_concern = (-1…1).inject([]) {|arr, i| arr << (today.month +
i)}

just running through -1 to 1 and adding to the current month

it gives you last month, current month, and next month numbers

which will be handed to months_of_concern after the block finishes

month_ranges = months_of_concern.map {|month| 1…Date.new( today.year,
month, -1 ).day}

building ranges to later turn into arrays

you get previous, current, and future month ranges

the month days

-1 is used, as Morton so excellently pointed out

as the last number

this is common in Ruby

day_sets = month_ranges.map {|i| i.to_a}

turning the Range objects (3 of them) into Array objects

I should have used {|i| i.map} here because it

would have been cuter, which is mostly disapproved

of in production code :slight_smile:

offset.times { day_sets[1].unshift day_sets[0].pop }

Here, we’re just moving the last numbers of the last

month to the first part of this month one by one

day_sets[1…2].flatten.slice(0…35).compact.each_slice(7) {|i| p i}

This one is actually easy to understand.

Grab what we now have as the current month,

tap on the ending month,

flatten it (turn it into one big array),

slice off the numbers that are in slots 0 to 35

compact (there might be nils)

each_slice simply builds arrays by

grabbing things 7 at a time.

Keep in mind scope. The “i” variable is local to each block; all by
himself. It doesn’t have to be “i”, also.

Todd

On Sat, Mar 1, 2008 at 5:45 AM, Mikkel B. [email protected] wrote:

Keep in mind scope. The “i” variable is local to each block; all by
himself. It doesn’t have to be “i”, also.

My apologies, this was aimed at newbies, not at you.

Todd

no problem…

thanks for your reply…ill see wha i can make of it…

Thanks for the exercise. I’ve been meaning to make use of a graphical
calendar for a while, and now you’ve given me an excuse to finish it!

Todd

Todd B. wrote:

On Sat, Mar 1, 2008 at 3:03 AM, Todd B. [email protected] wrote:

just running through -1 to 1 and adding to the current month

day_sets[1…2].flatten.slice(0…35).compact.each_slice(7) {|i| p i}

Keep in mind scope. The “i” variable is local to each block; all by
himself. It doesn’t have to be “i”, also.

My apologies, this was aimed at newbies, not at you.

Todd

no problem…

thanks for your reply…ill see wha i can make of it…

Oh my goodness! A thousand apologies. I gave you a rotating month.
See below.

On Sat, Mar 1, 2008 at 3:03 AM, Todd B. [email protected] wrote:

Okay, changing the code…

require ‘enumerator’

I need that for the each_slice method later

today = Date.today

offset = Date.new(today.year, today.month, 1).wday

-1 is used, as Morton so excellently pointed out

Here, we’re just moving the last numbers of the last

grabbing things 7 at a time.

Keep in mind scope. The “i” variable is local to each block; all by
himself. It doesn’t have to be “i”, also.

My original code only worked when run on the first of the month.
Pretty funny, actually. It’s one of those things that a unit test
wouldn’t catch unless the test was written correctly.

Todd

So I ended up with:

def calendar_view
firstday = days[0] #all days in month
offset = firstday.wday-1
boxes =Array.new(5*7) # the viewport
i=0
#this month from wday of firstday
offset.upto(days.length+offset){boxes[offset+i]=days[i];i=i+1}
i=0
#pad with previous
1.upto(offset){boxes[i]=previous.days[-offset+i];i=i+1}
i=0
#pad with next…
(offset+days.length).upto(boxes.length){boxes[days.length+i+offset]=next_month.days[i];i=i+1}
return boxes
end

I show this in a rails app using array.groups_of(7) which chops the
boxes array into parts of 7

which works ok…untill i hit june 08…where the first week starts
with jun 2nd…

How are your algorithms working??

On Mon, Mar 3, 2008 at 2:05 AM, Mikkel B. [email protected] wrote:

#pad with previous

which works ok…untill i hit june 08…where the first week starts
with jun 2nd…

How are your algorithms working??

You can use my code (with the correction in my last post), and omit
the each_slice part (you do want one big array right?).

To explain what I was doing in a different way, I build three arrays
with the correct number of days in each, centered around the current
month (that’s why I add -1, 0, and 1 to the current month number). I
use (didn’t with the first code, sorry) the first day of the current
month as the offset. I build ranges after I get the month numbers,
then build arrays off those ranges. So I have a single array of three
arrays. When I #pop off the first array and #unshift onto the
second array, it moves the last day number of the previous month array
onto the first day of the current month array; sort of what you are
doing by hand. I do this offset number of times using the #times
method. Then I flatten (the weird current month array rolled into the
next month array – the use of the 1…2 range). I used each_slice for
display, but, obviously, you won’t have to do that, because you have
#groups_of. Just flatten and slice.

In any case, google calendar uses a 7 x 7 grid. I would recommend at
least a 7 x 6 grid for reasons I pointed out earlier. When you
display March, for example, you will only go up to 29 with 7 x 5.

Here’s the script code without the previous comments, and
surreptitiously using a 7 x 6 grid (I still would code this
differently by separating out into methods, and also probably a Month
and/or Calendar class)…

today = Date.today #use whatever date you want
offset = Date.new(today.year, today.month, 1).wday

^^^^^^ that was the line I screwed up at first

months_of_concern = (-1…1).inject([]) {|a, i| a << (today.month + i)}
month_ranges = months_of_concern.map {|month| 1…Date.new( today.year,
month, -1 ).day}
day_sets = month_ranges.map {|i| i.map}
offset.times {day_sets[1].unshift day_sets[0].pop}
p day_sets[1…2].flatten.slice(0…42)

Todd

Todd B. wrote:

On Mon, Mar 3, 2008 at 1:02 PM, Todd B. [email protected] wrote:

offset.upto(days.length+offset){boxes[offset+i]=days[i];i=i+1}

boxes array into parts of 7
with the correct number of days in each, centered around the current
display, but, obviously, you won’t have to do that, because you have

today = Date.today #use whatever date you want

offset = Date.new(today.year, today.month, 1).wday

^^^^^^ that was the line I screwed up at first

months_of_concern = (-1…1).inject([]) {|a, i| a << (today.month + i)}

month_ranges = months_of_concern.map {|month| 1…Date.new( today.year,
month, -1 ).day}

That’s one line and suppose to be today.month following today.year.

day_sets = month_ranges.map {|i| i.map}

offset.times {day_sets[1].unshift day_sets[0].pop}
p day_sets[1…2].flatten.slice(0…42)

Todd

thanks for all your help

but your code seems to break when changing years. showing january 2008
breaks when populating decemeber 2007 etc…

i believe this line has to take this into account:

month_ranges = months_of_concern.map {|month| 1…Date.new( today.year,
month, -1 ).day}

Mikkel

On Mon, Mar 3, 2008 at 1:02 PM, Todd B. [email protected] wrote:

offset.upto(days.length+offset){boxes[offset+i]=days[i];i=i+1}

boxes array into parts of 7
with the correct number of days in each, centered around the current
display, but, obviously, you won’t have to do that, because you have

today = Date.today #use whatever date you want

offset = Date.new(today.year, today.month, 1).wday

^^^^^^ that was the line I screwed up at first

months_of_concern = (-1…1).inject([]) {|a, i| a << (today.month + i)}

month_ranges = months_of_concern.map {|month| 1…Date.new( today.year,
month, -1 ).day}

That’s one line and suppose to be today.month following today.year.

day_sets = month_ranges.map {|i| i.map}

offset.times {day_sets[1].unshift day_sets[0].pop}
p day_sets[1…2].flatten.slice(0…42)

Todd

There’s a calendar_helper plugin if this is for a Rails app. Works well
for me.

–Jeremy

On Mon, Mar 3, 2008 at 3:05 AM, Mikkel B. [email protected] wrote:

#pad with previous

which works ok…untill i hit june 08…where the first week starts
with jun 2nd…

How are your algorithms working??


Posted via http://www.ruby-forum.com/.


http://jeremymcanally.com/
http://entp.com

Read my books:
Ruby in Practice (Ruby in Practice)
My free Ruby e-book (http://humblelittlerubybook.com/)

Or, my blogs:

http://rubyinpractice.com

On Mon, Mar 3, 2008 at 2:43 PM, Todd B. [email protected] wrote:

=> 2007-12-29
No, actually the same code works, you just have to turn the -1…1
range into a loop for 12, which can be done like this…

your_number % 12 + 1

so code would be…

require ‘date’
today = Date.new(2008, 1, 8)#use whatever date you want
offset = Date.new(today.year, today.month, 1).wday
months_of_concern = (-1…1).map.inject([]) {|arr, i| arr << (today.month

  • i)}
    months_of_concern.map! {|i| i % 12 + 1}
    month_ranges = months_of_concern.map {|month| 1…Date.new( today.year,
    month, -1 ).day}
    day_sets = month_ranges.map {|i| i.to_a}
    offset.times { day_sets[1].unshift day_sets[0].pop }
    p day_sets[1…2].flatten.slice(0…42)

Todd

On Mon, Mar 3, 2008 at 2:37 PM, Jeremy McAnally
[email protected] wrote:

There’s a calendar_helper plugin if this is for a Rails app. Works well for me.

–Jeremy

I agree. If this is to be productive, then you probably don’t have to
reinvent the wheel :slight_smile:

Todd

On Mon, Mar 3, 2008 at 2:14 PM, Mikkel B. [email protected] wrote:

That’s one line and suppose to be today.month following today.year.

Mikkel
Yeah, I just realized the edge cases of January and December fail. I
thought the Date object covered that with a -1 or 13 month.

I’m looking into it right now. You might be better off subtracting a
week and adding a week for your big array. For example…

puts Date.new(2008, 1, 5) - 7

=> 2007-12-29

Todd

On Mon, Mar 3, 2008 at 4:52 PM, Siep K. [email protected]
wrote:

and the first days of the next month

print "| "
week.each{|day| print day.to_s + "\t| "}
puts
end

The only thing noteworthy is the range of dates, which produces an
array.

Regards,

Siep

Mikkel, this is excellent use of the Date#- and Date#+ methods, and a
much better approach. I still think my solution is more fun :slight_smile:

Todd

Siep K. wrote:

Todd B. wrote:

On Mon, Mar 3, 2008 at 2:37 PM, Jeremy McAnally
[email protected] wrote:

There’s a calendar_helper plugin if this is for a Rails app. Works well for me.

–Jeremy

I agree. If this is to be productive, then you probably don’t have to
reinvent the wheel :slight_smile:

Todd

Yes. Here is my effort anyway:

require ‘date’
require ‘enumerator’

def days_to_show(year, month, num_week_rows)

returns an array of the dates in month,

with the last days of the preceding month

and the first days of the next month

first_of_month = Date.new(year, month, 1)
first = first_of_month - (first_of_month.wday)+1

+1 for weeks starting on monday, remove for weeks starting on sunday

last = first + (num_week_rows * 7)
(first…last).to_a
end

days = days_to_show(2008, 3, 6)
days.each_slice(7) do |week|
print "| "
week.each{|day| print day.to_s + "\t| "}
puts
end

The only thing noteworthy is the range of dates, which produces an
array.

Regards,

Siep

That is really an elegant solution and it works between years
also…but…

What happens when you run ays = days_to_show(2008, 6, 6) ??? it shows
june as starting on sunday 2 of june…

really wierd

Mikkel