making a monthly calendar...

M

Mikkel Bruun

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
 
D

Daniel Finnie

[Note: parts of this message were removed to make it a legal post.]

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
 
T

Todd Benson

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

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
 
T

Todd Benson

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
 
M

Mikkel Bruun

Todd said:
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 ;-)
 
T

Todd Benson

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 ;-)

Okay, changing the code...

<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 :)

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.

</CODE>

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

Todd
 
T

Todd Benson

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 ;-)

Okay, changing the code...

<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 :)

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.

</CODE>

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
 
M

Mikkel Bruun

Todd said:
# 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...
 
T

Todd Benson

Todd said:
# 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...

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
 
T

Todd Benson

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

Okay, changing the code...

<CODE>

require 'enumerator'
# I need that for the each_slice method later
today = Date.today

offset = Date.new(today.year, today.month, 1).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 :)

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.

</CODE>

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
 
M

Mikkel Bruun

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+1}
i=0
#pad with previous
1.upto(offset){boxes=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+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??
 
T

Todd Benson

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+1}
i=0
#pad with previous
1.upto(offset){boxes=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+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??


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
 
T

Todd Benson

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+1}
i=0
#pad with previous
1.upto(offset){boxes=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+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??


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}


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
 
M

Mikkel Bruun

Todd said:
offset.upto(days.length+offset){boxes[offset+i]=days;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
 
J

Jeremy McAnally

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

--Jeremy

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+1}
i=0
#pad with previous
1.upto(offset){boxes=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+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??




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

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

Or, my blogs:
http://mrneighborly.com
http://rubyinpractice.com
 
T

Todd Benson

Todd said:
offset.upto(days.length+offset){boxes[offset+i]=days;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


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
 
T

Todd Benson

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

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
 
T

Todd Benson

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 :)

Todd
 
S

Siep Korteling

Todd said:
I agree. If this is to be productive, then you probably don't have to
reinvent the wheel :)

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
 
T

Todd Benson

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

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

Todd
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,581
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top