Given a list of activities and their start time/date, output an ASCII-art calendar showing the activities on the appropriate days. All activities are guaranteed to be in the same month, no two activities will be on the same day, and all activities are guaranteed to fit within the calendar box.

The calendar has the date in the upper-left corner of each box, the boxes are 9 spaces wide by 5 spaces tall, surrounded by - and |. The two-letter abbreviation for the day of the week is centered above the first row, and the weeks start with Sunday.

Clarifications

The schedule words (matching [A-Za-z]+) will delimited by a single space between them (as in the example).

Splitting the text on word boundaries is sufficient. No need for hyphenating words.

If February starts on a Sunday in a non-leap-year, you only will have four calendar rows.

If a 31-day month (e.g., August) starts late in the week, you may have to output six calendar rows.

I/O and Rules

Your code must handle dates at least between 0001-01-01 and 9999-12-31 in the Gregorian calendar, including leap years as appropriate. For example, if given input 2016-02-13 9:00am Test, the output calendar should have February 29.

Input date format can be in any desired format. ISO 8601, a datetime object, a particularly-formatted string, etc. Input parsing is not the interesting part of this challenge.

\$\begingroup\$1.) Do you have to split the activity names on the word boundaries? 2.) When you have a non-leapyear February starting on a Sunday, do you have only 4 rows? 3.) When you would need 6 rows to show the month (ex. August starting on Saturday) what happens?\$\endgroup\$
– nedla2004Oct 10 '18 at 14:48

Month structure

The helper function \$G\$ builds a date from the input year \$y\$ and input month \$m\$ (which are constant) and the day \$d\$ (which is dynamic). Its parameter is a method name, which allows to extract either the day of the week or the month.

G = s => new Date(y, m, d)[s]()

It is first used to identify the day of the week of the first day of the month, so that we can move back to the last Sunday from there. It is then used to detect when we enter back the correct month and when we leave it (this information is stored in the Boolean \$x\$).

Try it online! Link is to verbose version of code. Takes dates in d/m/yyyy format. Explanation:

Ｓθ

Input the first event.

≔Ｉ⪪§⪪θ ⁰/η

Extract the date and split on /s.

≔⁻⁺×¹²⊟η⊟η²η

Convert to months since March, 1 BC. I want to calculate the day of week of the first of both next month and the current month, and working in months is easier than keeping the months and years separate and carrying at the end of the year, plus it also allows me to start counting months beginning at March instead of January, which is required by Zeller's congruence.

≔ＥＥ²⁻ηι﹪Σ⟦÷ι⁴⁸⁰⁰±÷ι¹²⁰⁰÷ι⁴⁸÷ι¹²÷×¹³⁺⁴﹪ι¹²¦⁵⟧⁷η

Use a modified Zeller's congruence to extract the day of the week of the first day of next month and this month. The basic part relies on the fact that the number of days from October 30th of the previous year to the 1st of a given month where m = 4 for March and m = 14 for January of the following year is given by the formula m * 153 / 5, however we can subtract 140 because we only care about the day of the week. It then remains to make adjustments due to the year; each year adds a day, each 4th year adds an extra day, each 100th year subtracts a day, and each 400th year adds a day again. (As I'm working in months these values are all multiplied by 12.) Rather conveniently this directly gives me the answer in terms of a Sunday-indexed week (normally you would add the day of month and start counting on Saturday).

≔±⊟ηζ

Negate the day of the week and save it as the current day of the month.

≔⁺²⁸﹪⁺⊟ηζ⁷ε

Calculate the number of days in the month from the day of the week of the two months.

⭆⪪SuMoTuWeThFrSa²◨◧ι⁶χ

Output the day headers.

↓←⁷¹

Print the top row of -s.

Ｗ‹ζε«

Loop until the last day of the month has been output.

↘

Move the cursor to the start of the next row.

Ｆ⁷«

Process 7 days at a time.

Ｐ↓⁵→

Print the column of |s to the left.

≦⊕ζ

Increment the current day of the month.

Ｆ⁼Ｉζ§⪪θ/⁰«

If the current day of the month is the day of the current event, ...

≔⪪θ - θ

... extract the other parts of the event, ...

≔⟦ω◨§θ¹¦⁹⟧δ

... pad the time to 9 spaces and save it and an empty string as a list, ...

Ｆ⪪⊟θ

... split the description on spaces and loop over them, ...

⊞δ⎇‹⁺Ｌ§δ±¹Ｌμ⁹⁺⁺⊟δ μμ

... adding each word to the previous word if it will fit; ...

Ｐ⪫δ¶

... output the time and description (Ｐδ doesn't work, might be a Charcoal bug?), ...

Ｓθ»

... and input the next event.

◨×››ζ⁰›ζεＩζ⁹»

If the current day of the month is between 1 and the last day of the month then output it, otherwise just output enough spaces to move to the next day.

↓⁵←⁷¹

At the end of the week, print the right column of |s and the bottom row of -s.

\$\begingroup\$Maybe I skipped over it in your verbose TIO code, but are you sure your Zeller's congruence implementation is complete? It seems to be correct for the months March through December, but for the months January and February year-1 should be used instead of year and month+12 should be used instead of month. Or did you somehow simplify the algorithm that I mentioned in this 05AB1E answer of mine which is equal to the one from Wikipedia?\$\endgroup\$
– Kevin CruijssenOct 12 '18 at 12:39

\$\begingroup\$@KevinCruijssen This is basically why I calculate the number of months since March, 1BC, but it's too complicated to explain further in a comment.\$\endgroup\$
– NeilOct 12 '18 at 13:04

\$\begingroup\$Thanks! That's indeed a nice modified formula, and I now understand the reasoning behind it. Thanks a lot for adding it to the explanation. +1 from me.\$\endgroup\$
– Kevin CruijssenOct 13 '18 at 15:25

(y,m,M)->{ // Lambda taking input as a year, month and map
var C=java.util.Calendar.getInstance(); // Creates a new Calendar instance
C.set(y,m-1,1); // Sets the calendar to the first of the month in the given year
String r=",Su,,Mo,,Tu,,We,,Th,,Fr,,Sa\n" // Creates the header row by replacing
.replace(","," "),e; // commas with 4 sets of spaces
for( // Creates 7 rows for a calendar row
int x=C.getActualMaximum(5) // Stores last day of the month
,l=0,b=0,j,c,i=0;i<7; // Initialises other integers
r+="\n", // Add new line each row
l+=b<=x&++i>6 // If end of a calendar row is reached, and current day is less than max
?7*(i=1) // Set i to 1 and add 7 to line count to create another calendar row
:0) // Otherwise do nothing
for(j=0;j<71; // Loop 71 times, appending characters to create a row
b=l+j/10+2-C.get(7), // Determine the day of the box we're in
e=(e=M.get(b))!=null? // Get the event for this day from the map and if not null
e.replaceAll("([^-]{1,9})(-| |$)","$1-") // Do some regex to separate the line entries by hyphens
+" - " // Append extra hyphen to prevent array out of bounds
:null, // Otherwise do nothing
r+=e=i%6<1?"-": // If it's the first line of a calendar row, append -
c<1?"|": // If it's the first column of a box, append |
c*i<2&b>0&b<=x?b+"": // If it's the second column of a box, the second row,
// and less than the max day, append the day
c<2&e!=null?e.split("-")[i-2]: // If it's any other row and there is an event then split and append correct line
" ", // Else just append a space
j+=e.length()) // Increase the row character count by the length to append
c=j%10; // Set the column of box (this is the only thing in the loop so happens first)
return r; // return the calendar string!
}

Your Answer

If this is an answer to a challenge…

…Be sure to follow the challenge specification. However, please refrain from exploiting obvious loopholes. Answers abusing any of the standard loopholes are considered invalid. If you think a specification is unclear or underspecified, comment on the question instead.

…Try to optimize your score. For instance, answers to code-golf challenges should attempt to be as short as possible. You can always include a readable version of the code in addition to the competitive one.
Explanations of your answer make it more interesting to read and are very much encouraged.

…Include a short header which indicates the language(s) of your code and its score, as defined by the challenge.

More generally…

…Please make sure to answer the question and provide sufficient detail.

…Avoid asking for help, clarification or responding to other answers (use comments instead).

Code Golf Stack Exchange is a site for recreational programming competitions, not general programming questions. Challenges must have an objective scoring criterion, and it is highly recommended to first post proposed challenges in the Sandbox.