diff --git a/data/Makefile.am b/data/Makefile.am
index 4e73e03..8d7eedf 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -25,6 +25,8 @@ dist_images_DATA =				\
 themedir = $(pkgdatadir)/theme
 dist_theme_DATA =				\
 	theme/add-workspace.svg			\
+	theme/calendar-arrow-left.svg		\
+	theme/calendar-arrow-right.svg		\
 	theme/close-window.svg			\
 	theme/close.svg				\
 	theme/corner-ripple.png			\
diff --git a/data/theme/calendar-arrow-left.svg b/data/theme/calendar-arrow-left.svg
new file mode 100644
index 0000000..d5d97b3
--- /dev/null
+++ b/data/theme/calendar-arrow-left.svg
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="16"
+   height="16"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48+devel r9942 custom"
+   sodipodi:docname="New document 4">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1"
+     inkscape:cx="8.984481"
+     inkscape:cy="5.6224906"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     borderlayer="true"
+     inkscape:showpageshadow="false"
+     inkscape:window-width="930"
+     inkscape:window-height="681"
+     inkscape:window-x="1892"
+     inkscape:window-y="272"
+     inkscape:window-maximized="0">
+    <inkscape:grid
+       type="xygrid"
+       id="grid17403"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1036.3622)">
+    <path
+       sodipodi:type="star"
+       style="fill:#5f5f5f;fill-opacity:1;stroke:#5f5f5f;stroke-width:0.43015847;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
+       id="path18028"
+       sodipodi:sides="3"
+       sodipodi:cx="84.5"
+       sodipodi:cy="337.5"
+       sodipodi:r1="5"
+       sodipodi:r2="2.5"
+       sodipodi:arg1="0.52359878"
+       sodipodi:arg2="1.5707963"
+       inkscape:flatsided="true"
+       inkscape:rounded="0"
+       inkscape:randomized="0"
+       d="M 88.830127,340 80.169873,340 84.5,332.5 z"
+       transform="matrix(0,1.3621708,0.99186247,0,-325.48222,929.32667)" />
+  </g>
+</svg>
diff --git a/data/theme/calendar-arrow-right.svg b/data/theme/calendar-arrow-right.svg
new file mode 100644
index 0000000..545da7e
--- /dev/null
+++ b/data/theme/calendar-arrow-right.svg
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="16"
+   height="16"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48+devel r9942 custom"
+   sodipodi:docname="arrow-left.svg">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1"
+     inkscape:cx="7.7366092"
+     inkscape:cy="6.4536271"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     borderlayer="true"
+     inkscape:showpageshadow="false"
+     inkscape:window-width="930"
+     inkscape:window-height="681"
+     inkscape:window-x="1892"
+     inkscape:window-y="272"
+     inkscape:window-maximized="0">
+    <inkscape:grid
+       type="xygrid"
+       id="grid17403"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1036.3622)">
+    <path
+       sodipodi:type="star"
+       style="fill:#5f5f5f;fill-opacity:1;stroke:#5f5f5f;stroke-width:0.43015847;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
+       id="path18028"
+       sodipodi:sides="3"
+       sodipodi:cx="84.5"
+       sodipodi:cy="337.5"
+       sodipodi:r1="5"
+       sodipodi:r2="2.5"
+       sodipodi:arg1="0.52359878"
+       sodipodi:arg2="1.5707963"
+       inkscape:flatsided="true"
+       inkscape:rounded="0"
+       inkscape:randomized="0"
+       d="M 88.830127,340 80.169873,340 84.5,332.5 z"
+       transform="matrix(0,1.3621708,-0.99186247,0,342.48324,929.32667)" />
+  </g>
+</svg>
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index 6104ac9..0d8d2a5 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -670,6 +670,14 @@ StTooltip StLabel {
 
 /* Calendar popup */
 
+.calendar-vertical-separator {
+    -gradient-width: 2px;
+    -gradient-start: rgba(8,8,8,0);
+    -gradient-end: #333333;
+    -margin-vertical: 1.5em;
+    width: 1em;
+}
+
 #calendarPopup {
     border-radius: 5px;
     background: rgba(0,0,0,0.9);
@@ -681,38 +689,183 @@ StTooltip StLabel {
     padding: 10px;
 }
 
+#calendarHBox {
+    padding: 10px 20px;
+}
+
+#calendarVBox1 {
+    spacing: 20px;
+}
+
 .calendar {
-    spacing-rows: 5px;
-    spacing-columns: 3px;
+    spacing-rows: 0px;
+    spacing-columns: 0px;
 }
 
 .calendar-change-month {
+    color: #666666;
+    font-size: 10px;
     padding: 2px;
 }
 
 .calendar-change-month:hover {
-    background: #314a6c;
+    background: #999999;
     border-radius: 5px;
 }
 
 .calendar-change-month:active {
-    background: #213050;
+    background: #aaaaaa;
     border-radius: 5px;
 }
 
+.calendar-change-month-back {
+    width: 20px;
+    height: 20px;
+    background-image: url("calendar-arrow-left.svg");
+}
+.calendar-change-month-back:hover {
+    background: #999999;
+    background-image: url("calendar-arrow-left.svg");
+}
+.calendar-change-month-back:active {
+    background: #aaaaaa;
+    background-image: url("calendar-arrow-left.svg");
+}
+
+.calendar-change-month-forward {
+    width: 20px;
+    height: 20px;
+    background-image: url("calendar-arrow-right.svg");
+}
+.calendar-change-month-forward:hover {
+    background: #999999;
+    background-image: url("calendar-arrow-right.svg");
+}
+.calendar-change-month-forward:active {
+    background: #aaaaaa;
+    background-image: url("calendar-arrow-right.svg");
+}
+
+.datemenu-date-label {
+    font-size: 16px;
+    color: #ffffff;
+}
+
+.calendar-day-base {
+    font-size: 10px;
+    padding: 5px 3px;
+    text-align: center;
+}
+
+.calendar-day-base:hover {
+    background: #999999;
+}
+
+.calendar-day-base:active {
+    background: #666666;
+}
+
+.calendar-day-heading {
+    color: #666666;
+}
+
+.calendar-week-number {
+    color: #666666;
+    font-weight: bold;
+}
+
 .calendar-day {
-    padding: 1px 2px;
+    border: 1px solid #333333;
+    color: #cccccc;
+}
+
+.calendar-work-day {
+}
+
+.calendar-nonwork-day {
+    background: #181818;
 }
 
 .calendar-today {
+    color: #ffffff;
     font-weight: bold;
-    background: #ffffff;
-    color: black;
-    border-radius: 5px;
+    background-gradient-direction: vertical;
+    background-gradient-start: #3c3c3c;
+    background-gradient-end: #131313;
 }
 
 .calendar-other-month-day {
-    color: #cccccc;
+    color: #333333;
+}
+
+.calendar-day-with-events {
+    font-weight: bold;
+}
+
+.events-header-vbox {
+    spacing: 10px;
+    width: 300px;
+}
+
+.events-header {
+    height: 40px;
+}
+
+.events-header-hbox {
+    spacing: 8px;
+}
+
+.events-day-header {
+    font-size: 14px;
+    color: rgba(153, 153, 153, 1.0);
+}
+
+.events-day-dayname {
+    font-size: 12px;
+    color: rgba(153, 153, 153, 1.0);
+    text-align: left;
+}
+
+.events-day-time {
+    font-size: 12px;
+    font-weight: bold;
+    color: #fff;
+    text-align: right;
+}
+
+.events-day-task {
+    font-size: 12px;
+    color: rgba(153, 153, 153, 1.0);
+}
+
+.events-day-name-box {
+    width: 20px;
+}
+
+.events-time-box {
+    width: 70px;
+}
+
+.events-event-box {
+    width: 200px;
+}
+
+.events-no-events {
+    font-weight: bold;
+    padding-left: 40px;
+    padding-right: 40px;
+    font-size: 14px;
+    color: rgba(153, 153, 153, 1.0);
+}
+
+.open-calendar {
+    padding-bottom: 12px;
+    padding-left: 12px;
+    height: 40px;
+}
+
+.open-calendar:hover {
+    background: #666;
 }
 
 .url-highlighter {
@@ -891,10 +1044,6 @@ StTooltip StLabel {
     padding-left: 4px;
 }
 
-.calendar-calendarweek {
-    color: #666666;
-}
-
 /* App Switcher */
 #altTabPopup {
     padding: 8px;
diff --git a/js/Makefile.am b/js/Makefile.am
index 3a7a2da..21683af 100644
--- a/js/Makefile.am
+++ b/js/Makefile.am
@@ -19,6 +19,7 @@ nobase_dist_js_DATA = 	\
 	ui/chrome.js		\
 	ui/ctrlAltTab.js	\
 	ui/dash.js		\
+	ui/dateMenu.js		\
 	ui/dnd.js		\
 	ui/docDisplay.js	\
 	ui/endSessionDialog.js	\
diff --git a/js/ui/calendar.js b/js/ui/calendar.js
index 8f58f69..16c0961 100644
--- a/js/ui/calendar.js
+++ b/js/ui/calendar.js
@@ -4,8 +4,12 @@ const Clutter = imports.gi.Clutter;
 const Gio = imports.gi.Gio;
 const Lang = imports.lang;
 const St = imports.gi.St;
+const Signals = imports.signals;
 const Pango = imports.gi.Pango;
 const Gettext_gtk30 = imports.gettext.domain('gtk30');
+const Gettext = imports.gettext.domain('gnome-shell');
+const _ = Gettext.gettext;
+const Mainloop = imports.mainloop;
 
 const MSECS_IN_DAY = 24 * 60 * 60 * 1000;
 const WEEKDATE_HEADER_WIDTH_DIGITS = 3;
@@ -17,6 +21,14 @@ function _sameDay(dateA, dateB) {
             dateA.getYear() == dateB.getYear());
 }
 
+/* TODO: maybe needs config - right now we assume that Saturday and
+ * Sunday are work days (not true in e.g. Israel, it's Sunday and
+ * Monday there)
+ */
+function _isWorkDay(date) {
+    return date.getDay() != 0 && date.getDay() != 6;
+}
+
 function _getCalendarWeekForDate(date) {
     // Based on the algorithms found here:
     // http://en.wikipedia.org/wiki/Talk:ISO_week_date
@@ -43,12 +55,209 @@ function _getDigitWidth(actor){
     return width;
 }
 
-function Calendar() {
+function _getCalendarDayAbbreviation(dayNumber) {
+    let abbreviations = [
+        /* Translators: Calendar grid abbreviation for Sunday.
+         *
+         * NOTE: These abbreviations are always shown together and in
+         * order, e.g. "S M T W T F S".
+         */
+        _("S"),
+        /* Translators: Calendar grid abbreviation for Monday */
+        _("M"),
+        /* Translators: Calendar grid abbreviation for Tuesday */
+        _("T"),
+        /* Translators: Calendar grid abbreviation for Wednesday */
+        _("W"),
+        /* Translators: Calendar grid abbreviation for Thursday */
+        _("T"),
+        /* Translators: Calendar grid abbreviation for Friday */
+        _("F"),
+        /* Translators: Calendar grid abbreviation for Saturday */
+        _("S")
+    ];
+    return abbreviations[dayNumber];
+}
+
+function _getEventDayAbbreviation(dayNumber) {
+    let abbreviations = [
+        /* Translators: Event list abbreviation for Sunday.
+         *
+         * NOTE: These abbreviations are normally not shown together
+         * so they need to be unique (e.g. Tuesday and Thursday cannot
+         * both be 'T').
+         */
+        _("Su"),
+        /* Translators: Event list abbreviation for Monday */
+        _("M"),
+        /* Translators: Event list abbreviation for Tuesday */
+        _("T"),
+        /* Translators: Event list abbreviation for Wednesday */
+        _("W"),
+        /* Translators: Event list abbreviation for Thursday */
+        _("Th"),
+        /* Translators: Event list abbreviation for Friday */
+        _("F"),
+        /* Translators: Event list abbreviation for Saturday */
+        _("S")
+    ];
+    return abbreviations[dayNumber];
+}
+
+// Abstraction for an appointment/task in a calendar
+
+function CalendarTask(date, summary) {
+    this._init(date, summary);
+}
+
+CalendarTask.prototype = {
+    _init: function(date, summary) {
+        this.date = date;
+        this.summary = summary;
+    }
+};
+
+// Interface for appointments/tasks - e.g. the contents of a calendar
+//
+// TODO: write e.g. EvolutionEventSource
+
+// First, an implementation with no events
+function EmptyEventSource() {
+    this._init();
+}
+
+EmptyEventSource.prototype = {
+    _init: function() {
+    },
+
+    getTasks: function(begin, end) {
+        let result = [];
+        return result;
+    },
+
+    hasTasks: function(day) {
+        return false;
+    }
+};
+Signals.addSignalMethods(EmptyEventSource.prototype);
+
+// Second, an implementation with fake events
+function FakeEventSource() {
     this._init();
 }
 
-Calendar.prototype = {
+FakeEventSource.prototype = {
     _init: function() {
+
+        this._fakeTasks = [];
+
+        // Generate fake events
+        //
+        let now = new Date();
+        let summary = '';
+        now.setHours(0);
+        now.setMinutes(0);
+        now.setSeconds(0);
+
+        // '10-oclock pow-wow' is an event occuring IN THE PAST every four days at 10am
+        for(let n = 0; n < 10; n++) {
+            let t = new Date(now.getTime() - n * 4 * 86400 * 1000);
+            t.setHours(10);
+            summary = '10-oclock pow-wow (n=' + n + ')';
+            this._fakeTasks.push(new CalendarTask(t, summary));
+        }
+
+        // '11-oclock thing' is an event occuring every three days at 11am
+        for(let n = 0; n < 10; n++) {
+            let t = new Date(now.getTime() + n * 3 * 86400 * 1000);
+            t.setHours(11);
+            summary = '11-oclock thing (n=' + n + ')';
+            this._fakeTasks.push(new CalendarTask(t, summary));
+        }
+
+        // 'Weekly Meeting' is an event occuring every seven days at 1:45pm (two days displaced)
+        for(let n = 0; n < 5; n++) {
+            let t = new Date(now.getTime() + (n * 7 + 2) * 86400 * 1000);
+            t.setHours(13);
+            t.setMinutes(45);
+            summary = 'Weekly Meeting (n=' + n + ')';
+            this._fakeTasks.push(new CalendarTask(t, summary));
+        }
+
+        // 'Get Married' is an event that actually reflects reality (Dec 4, 2010) :-)
+        this._fakeTasks.push(new CalendarTask(new Date(2010, 11, 4, 16, 0), 'Get Married'));
+
+        // ditto for 'NE Patriots vs NY Jets'
+        this._fakeTasks.push(new CalendarTask(new Date(2010, 11, 6, 20, 30), 'NE Patriots vs NY Jets'));
+
+        // An event for tomorrow @6:30pm that is added/removed every five
+        // seconds (to check that the ::changed signal works)
+        let transientEventDate = new Date(now.getTime() + 86400*1000);
+        transientEventDate.setHours(18);
+        transientEventDate.setMinutes(30);
+        transientEventDate.setSeconds(0);
+        Mainloop.timeout_add(5000, Lang.bind(this, this._updateTransientEvent));
+        this._includeTransientEvent = false;
+        this._transientEvent = new CalendarTask(transientEventDate, 'A Transient Event');
+        this._transientEventCounter = 1;
+    },
+
+    _updateTransientEvent: function() {
+        this._includeTransientEvent = !this._includeTransientEvent;
+        this._transientEventCounter = this._transientEventCounter + 1;
+        this._transientEvent.summary = 'A Transient Event (' + this._transientEventCounter + ')';
+        this.emit('changed');
+        Mainloop.timeout_add(5000, Lang.bind(this, this._updateTransientEvent));
+    },
+
+    getTasks: function(begin, end) {
+        let result = [];
+        //log('begin:' + begin);
+        //log('end:  ' + end);
+        for(let n = 0; n < this._fakeTasks.length; n++) {
+            let task = this._fakeTasks[n];
+            if (task.date >= begin && task.date <= end) {
+                result.push(task);
+            }
+            //log('when:' + task.date + ' summary:' + task.summary);
+        }
+        if (this._includeTransientEvent && this._transientEvent.date >= begin && this._transientEvent.date <= end)
+            result.push(this._transientEvent);
+        return result;
+    },
+
+    hasTasks: function(day) {
+        let dayBegin = new Date(day.getTime());
+        let dayEnd = new Date(day.getTime());
+        dayBegin.setHours(0);
+        dayBegin.setMinutes(1);
+        dayEnd.setHours(23);
+        dayEnd.setMinutes(59);
+
+        let tasks = this.getTasks(dayBegin, dayEnd);
+
+        if (tasks.length == 0)
+            return false;
+
+        return true;
+    }
+
+};
+Signals.addSignalMethods(FakeEventSource.prototype);
+
+// Calendar:
+// @eventSource: is an object implementing the EventSource API, e.g. the
+// getTasks(), hasTasks() methods and the ::changed signal.
+function Calendar(eventSource) {
+    this._init(eventSource);
+}
+
+Calendar.prototype = {
+    _init: function(eventSource) {
+        this._eventSource = eventSource;
+
+        this._eventSource.connect('changed', Lang.bind(this, this._update));
+
         // FIXME: This is actually the fallback method for GTK+ for the week start;
         // GTK+ by preference uses nl_langinfo (NL_TIME_FIRST_WEEKDAY). We probably
         // should add a C function so we can do the full handling.
@@ -85,7 +294,7 @@ Calendar.prototype = {
         }
 
         // Start off with the current date
-        this.date = new Date();
+        this.selectedDate = new Date();
 
         this.actor = new St.Table({ homogeneous: false,
                                     style_class: 'calendar',
@@ -100,9 +309,10 @@ Calendar.prototype = {
 
     // Sets the calendar to show a specific date
     setDate: function(date) {
-        if (!_sameDay(date, this.date)) {
-            this.date = date;
+        if (!_sameDay(date, this.selectedDate)) {
+            this.selectedDate = date;
             this._update();
+            this.emit('selected-date-changed', new Date(this.selectedDate));
         }
     },
 
@@ -116,45 +326,36 @@ Calendar.prototype = {
                        { row: 0, col: 0, col_span: offsetCols + 7 });
 
         this.actor.connect('style-changed', Lang.bind(this, this._onStyleChange));
-        let [backlabel, forwardlabel] = ['&lt;', '&gt;'];
-        if (St.Widget.get_default_direction () == St.TextDirection.RTL) {
-            [backlabel, forwardlabel] = [forwardlabel, backlabel];
-        }
 
-        let back = new St.Button({ label: backlabel, style_class: 'calendar-change-month'  });
+        let back = new St.Button({ style_class: 'calendar-change-month-back' });
         this._topBox.add(back);
-        back.connect('clicked', Lang.bind(this, this._prevMonth));
+        back.connect('clicked', Lang.bind(this, this._onPrevMonthButtonClicked));
 
-        this._dateLabel = new St.Label();
+        this._dateLabel = new St.Label({style_class: 'calendar-change-month'});
         this._topBox.add(this._dateLabel, { expand: true, x_fill: false, x_align: St.Align.MIDDLE });
 
-        let forward = new St.Button({ label: forwardlabel, style_class: 'calendar-change-month' });
+        let forward = new St.Button({ style_class: 'calendar-change-month-forward' });
         this._topBox.add(forward);
-        forward.connect('clicked', Lang.bind(this, this._nextMonth));
+        forward.connect('clicked', Lang.bind(this, this._onNextMonthButtonClicked));
 
+        // Add weekday labels...
+        //
         // We need to figure out the abbreviated localized names for the days of the week;
         // we do this by just getting the next 7 days starting from right now and then putting
         // them in the right cell in the table. It doesn't matter if we add them in order
-        let iter = new Date(this.date);
+        let iter = new Date(this.selectedDate);
         iter.setSeconds(0); // Leap second protection. Hah!
         iter.setHours(12);
-
-        if (this._useWeekdate) {
-            this._weekdateHeader = new St.Label();
-            this.actor.add(this._weekdateHeader,
-                              { row: 1,
-                                col: 0,
-                                x_fill: false, x_align: St.Align.MIDDLE });
-            this._setWeekdateHeaderWidth();
-        } else {
-            this._weekdateHeader = null;
-        }
-
         for (let i = 0; i < 7; i++) {
-            this.actor.add(new St.Label({ text: iter.toLocaleFormat('%a') }),
+            // Could use iter.toLocaleFormat('%a') but that normally gives three characters
+            // and we want, ideally, a single character for e.g. S M T W T F S
+            let customDayAbbrev = _getCalendarDayAbbreviation(iter.getDay());
+            let label = new St.Label({ text: customDayAbbrev });
+            label.style_class = 'calendar-day-base calendar-day-heading';
+            this.actor.add(label,
                            { row: 1,
                              col: offsetCols + (7 + iter.getDay() - this._weekStart) % 7,
-                             x_fill: false, x_align: St.Align.END });
+                             x_fill: false, x_align: St.Align.MIDDLE });
             iter.setTime(iter.getTime() + MSECS_IN_DAY);
         }
 
@@ -178,33 +379,35 @@ Calendar.prototype = {
         switch (event.get_scroll_direction()) {
         case Clutter.ScrollDirection.UP:
         case Clutter.ScrollDirection.LEFT:
-            this._prevMonth();
+            this._onPrevMonthButtonClicked();
             break;
         case Clutter.ScrollDirection.DOWN:
         case Clutter.ScrollDirection.RIGHT:
-            this._nextMonth();
+            this._onNextMonthButtonClicked();
             break;
         }
     },
 
-    _prevMonth: function() {
-        if (this.date.getMonth() == 0) {
-            this.date.setMonth(11);
-            this.date.setFullYear(this.date.getFullYear() - 1);
+    _onPrevMonthButtonClicked: function() {
+        let newDate = new Date(this.selectedDate);
+        if (newDate.getMonth() == 0) {
+            newDate.setMonth(11);
+            newDate.setFullYear(newDate.getFullYear() - 1);
         } else {
-            this.date.setMonth(this.date.getMonth() - 1);
+            newDate.setMonth(newDate.getMonth() - 1);
         }
-        this._update();
+        this.setDate(newDate);
    },
 
-    _nextMonth: function() {
-        if (this.date.getMonth() == 11) {
-            this.date.setMonth(0);
-            this.date.setFullYear(this.date.getFullYear() + 1);
+    _onNextMonthButtonClicked: function() {
+        let newDate = new Date(this.selectedDate);
+        if (newDate.getMonth() == 11) {
+            newDate.setMonth(0);
+            newDate.setFullYear(newDate.getFullYear() + 1);
         } else {
-            this.date.setMonth(this.date.getMonth() + 1);
+            newDate.setMonth(newDate.getMonth() + 1);
         }
-        this._update();
+        this.setDate(newDate);
     },
 
     _onSettingsChange: function() {
@@ -214,7 +417,7 @@ Calendar.prototype = {
     },
 
     _update: function() {
-        this._dateLabel.text = this.date.toLocaleFormat(this._headerFormat);
+        this._dateLabel.text = this.selectedDate.toLocaleFormat(this._headerFormat);
 
         // Remove everything but the topBox and the weekday labels
         let children = this.actor.get_children();
@@ -222,7 +425,7 @@ Calendar.prototype = {
             children[i].destroy();
 
         // Start at the beginning of the week before the start of the month
-        let iter = new Date(this.date);
+        let iter = new Date(this.selectedDate);
         iter.setDate(1);
         iter.setSeconds(0);
         iter.setHours(12);
@@ -233,34 +436,168 @@ Calendar.prototype = {
 
         let row = 2;
         while (true) {
-            let label = new St.Label({ text: iter.getDate().toString() });
-            if (_sameDay(now, iter))
-                label.style_class = 'calendar-day calendar-today';
-            else if (iter.getMonth() != this.date.getMonth())
-                label.style_class = 'calendar-day calendar-other-month-day';
+            let button = new St.Button({ label: iter.getDate().toString() });
+
+            let iterStr = iter.toUTCString();
+            button.connect('clicked', Lang.bind(this, function() {
+                let newly_selectedDate = new Date(iterStr);
+                this.setDate(newly_selectedDate);
+            }));
+
+            let styleClass;
+            let hasTasks;
+            hasTasks = this._eventSource.hasTasks(iter);
+            styleClass = 'calendar-day-base calendar-day';
+            if (_isWorkDay(iter))
+                styleClass += ' calendar-work-day'
             else
-                label.style_class = 'calendar-day';
+                styleClass += ' calendar-nonwork-day'
+
+            if (_sameDay(now, iter))
+                styleClass += ' calendar-today';
+            else if (iter.getMonth() != this.selectedDate.getMonth())
+                styleClass += ' calendar-other-month-day';
+
+            if (_sameDay(this.selectedDate, iter))
+                button.add_style_pseudo_class('active');
+
+            if (hasTasks)
+                styleClass += ' calendar-day-with-events'
+
+            button.style_class = styleClass;
 
             let offsetCols = this._useWeekdate ? 1 : 0;
-            this.actor.add(label,
-                           { row: row, col: offsetCols + (7 + iter.getDay() - this._weekStart) % 7,
-                             x_fill: false, x_align: St.Align.END });
+            this.actor.add(button,
+                           { row: row, col: offsetCols + (7 + iter.getDay() - this._weekStart) % 7 });
 
             if (this._useWeekdate && iter.getDay() == 4) {
                 let label = new St.Label({ text: _getCalendarWeekForDate(iter).toString(),
-                                           style_class: 'calendar-day calendar-calendarweek'});
+                                           style_class: 'calendar-day-base calendar-week-number'});
                 this.actor.add(label,
-                              { row: row, col: 0,
-		                        x_fill: false, x_align: St.Align.MIDDLE });
+                               { row: row, col: 0, y_align: St.Align.MIDDLE });
             }
 
             iter.setTime(iter.getTime() + MSECS_IN_DAY);
             if (iter.getDay() == this._weekStart) {
                 // We stop on the first "first day of the week" after the month we are displaying
-                if (iter.getMonth() > this.date.getMonth() || iter.getYear() > this.date.getYear())
+                if (iter.getMonth() > this.selectedDate.getMonth() || iter.getYear() > this.selectedDate.getYear())
                     break;
                 row++;
             }
         }
     }
 };
+
+Signals.addSignalMethods(Calendar.prototype);
+
+function EventsList(eventSource) {
+    this._init(eventSource);
+}
+
+EventsList.prototype = {
+    _init: function(eventSource) {
+        this.actor = new St.BoxLayout({ vertical: true, style_class: 'events-header-vbox'});
+        this._date = new Date();
+        this._eventSource = eventSource;
+        this._eventSource.connect('changed', Lang.bind(this, this._update));
+        this._update();
+    },
+
+    _addEvent: function(dayNameBox, timeBox, eventTitleBox, includeDayName, day, time, desc) {
+        if (includeDayName) {
+            dayNameBox.add(new St.Label( { style_class: 'events-day-dayname',
+                                           text: day } ),
+                           { x_fill: false } );
+        }
+        timeBox.add(new St.Label( { style_class: 'events-day-time',
+                                    text: time} ),
+                    { x_fill: false } );
+        eventTitleBox.add(new St.Label( { style_class: 'events-day-task',
+                                          text: desc} ));
+    },
+
+    _addPeriod: function(header, begin, end, includeDayName) {
+        let tasks = this._eventSource.getTasks(begin, end);
+
+        if (tasks.length == 0)
+            return;
+
+        let vbox = new St.BoxLayout( {vertical: true} );
+        this.actor.add(vbox);
+
+        vbox.add(new St.Label({ style_class: 'events-day-header', text: header }));
+        let box = new St.BoxLayout({style_class: 'events-header-hbox'});
+        let dayNameBox = new St.BoxLayout({ vertical: true, style_class: 'events-day-name-box' });
+        let timeBox = new St.BoxLayout({ vertical: true, style_class: 'events-time-box' });
+        let eventTitleBox = new St.BoxLayout({ vertical: true, style_class: 'events-event-box' });
+        box.add(dayNameBox, {x_fill: false});
+        box.add(timeBox, {x_fill: false});
+        box.add(eventTitleBox, {expand: true});
+        vbox.add(box);
+
+        for (let n = 0; n < tasks.length; n++) {
+            let task = tasks[n];
+            let dayString = _getEventDayAbbreviation(task.date.getDay());
+            let timeString = task.date.toLocaleFormat('%I:%M %p'); // TODO: locale considerations
+            let summaryString = task.summary;
+            this._addEvent(dayNameBox, timeBox, eventTitleBox, includeDayName, dayString, timeString, summaryString);
+        }
+    },
+
+    _showOtherDay: function(day) {
+        this.actor.destroy_children();
+
+        let dayBegin = new Date(day.getTime());
+        let dayEnd = new Date(day.getTime());
+        dayBegin.setHours(0);
+        dayBegin.setMinutes(1);
+        dayEnd.setHours(23);
+        dayEnd.setMinutes(59);
+        this._addPeriod(day.toLocaleFormat('%A, %B %d, %Y'), dayBegin, dayEnd, false);
+    },
+
+    _showToday: function() {
+        this.actor.destroy_children();
+
+        let dayBegin = new Date();
+        let dayEnd = new Date();
+        dayBegin.setHours(0);
+        dayBegin.setMinutes(1);
+        dayEnd.setHours(23);
+        dayEnd.setMinutes(59);
+        this._addPeriod(_("Today"), dayBegin, dayEnd, false);
+
+        dayBegin.setDate(dayBegin.getDate() + 1);
+        dayEnd.setDate(dayEnd.getDate() + 1);
+        this._addPeriod(_("Tomorrow"), dayBegin, dayEnd, false);
+
+        if (dayEnd.getDay() == 6 || dayEnd.getDay() == 0) {
+            dayBegin.setDate(dayEnd.getDate() + 1);
+            dayEnd.setDate(dayBegin.getDate() + 6 - dayBegin.getDay());
+
+            this._addPeriod(_("Next week"), dayBegin, dayEnd, true);
+            return;
+        }
+        let d = 6 - dayEnd.getDay() - 1;
+        dayBegin.setDate(dayBegin.getDate() + 1);
+        dayEnd.setDate(dayEnd.getDate() + 1 + d);
+        this._addPeriod(_("This week"), dayBegin, dayEnd, true);
+    },
+
+    // Sets the event list to show events from a specific date
+    setDate: function(date) {
+        if (!_sameDay(date, this._date)) {
+            this._date = date;
+            this._update();
+        }
+    },
+
+    _update: function() {
+        let today = new Date();
+        if (_sameDay (this._date, today)) {
+            this._showToday();
+        } else {
+            this._showOtherDay(this._date);
+        }
+    }
+};
diff --git a/js/ui/dateMenu.js b/js/ui/dateMenu.js
new file mode 100644
index 0000000..6cf4c70
--- /dev/null
+++ b/js/ui/dateMenu.js
@@ -0,0 +1,204 @@
+/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
+
+const GLib = imports.gi.GLib;
+const Gio = imports.gi.Gio;
+const Lang = imports.lang;
+const Mainloop = imports.mainloop;
+const Cairo = imports.cairo;
+const Clutter = imports.gi.Clutter;
+const Shell = imports.gi.Shell;
+const St = imports.gi.St;
+const Gettext = imports.gettext.domain('gnome-shell');
+const _ = Gettext.gettext;
+
+const Util = imports.misc.util;
+const Main = imports.ui.main;
+const PanelMenu = imports.ui.panelMenu;
+const PopupMenu = imports.ui.popupMenu;
+const Calendar = imports.ui.calendar;
+
+// in org.gnome.desktop.interface
+const CLOCK_FORMAT_KEY        = 'clock-format';
+
+// in org.gnome.shell.clock
+const CLOCK_SHOW_DATE_KEY     = 'show-date';
+const CLOCK_SHOW_SECONDS_KEY  = 'show-seconds';
+
+function _onVertSepRepaint (area)
+{
+    let cr = area.get_context();
+    let themeNode = area.get_theme_node();
+    let [width, height] = area.get_surface_size();
+    let found, margin, gradientHeight;
+    [found, margin] = themeNode.lookup_length('-margin-vertical', false);
+    [found, gradientWidth] = themeNode.lookup_length('-gradient-width', false);
+    let startColor = new Clutter.Color();
+    themeNode.lookup_color('-gradient-start', false, startColor);
+    let endColor = new Clutter.Color();
+    themeNode.lookup_color('-gradient-end', false, endColor);
+
+    let gradientHeight = (height - margin * 2);
+    let gradientOffset = (width - gradientWidth) / 2;
+    let pattern = new Cairo.LinearGradient(gradientOffset, margin, gradientOffset + gradientWidth, height - margin);
+    pattern.addColorStopRGBA(0, startColor.red / 255, startColor.green / 255, startColor.blue / 255, startColor.alpha / 255);
+    pattern.addColorStopRGBA(0.5, endColor.red / 255, endColor.green / 255, endColor.blue / 255, endColor.alpha / 255);
+    pattern.addColorStopRGBA(1, startColor.red / 255, startColor.green / 255, startColor.blue / 255, startColor.alpha / 255);
+    cr.setSource(pattern);
+    cr.rectangle(gradientOffset, margin, gradientWidth, gradientHeight);
+    cr.fill();
+};
+
+function DateMenuButton() {
+    this._init();
+}
+
+DateMenuButton.prototype = {
+    __proto__: PanelMenu.Button.prototype,
+
+    _init: function() {
+        let item;
+        let hbox;
+        let vbox;
+
+        //this._eventSource = new Calendar.EmptyEventSource();
+        this._eventSource = new Calendar.FakeEventSource();
+        // TODO: write e.g. EvolutionEventSource
+
+        PanelMenu.Button.prototype._init.call(this, St.Align.START);
+
+        this._clock = new St.Label();
+        this.actor.set_child(this._clock);
+
+        hbox = new St.BoxLayout({name: 'calendarHBox'});
+        this.menu.addActor(hbox);
+
+        // Fill up the first column
+
+        vbox = new St.BoxLayout({vertical: true, name: 'calendarVBox1'});
+        hbox.add(vbox);
+
+        // Date
+        this._date = new St.Label();
+        this._date.style_class = 'datemenu-date-label';
+        vbox.add(this._date);
+
+        this._eventList = new Calendar.EventsList(this._eventSource);
+
+        // Calendar
+        this._calendar = new Calendar.Calendar(this._eventSource);
+        this._calendar.connect('selected-date-changed',
+                               Lang.bind(this, function(calendar, date) {
+                                   this._eventList.setDate(date);
+                               }));
+        vbox.add(this._calendar.actor);
+
+        // Add vertical separator
+
+        item = new St.DrawingArea({ style_class: 'calendar-vertical-separator',
+                                    pseudo_class: 'highlighted' });
+        item.set_width(25); // TODO: don't hard-code the width
+        item.connect('repaint', Lang.bind(this, _onVertSepRepaint));
+        hbox.add(item);
+
+        // Fill up the second column
+        //
+        // Event list
+        hbox.add(this._eventList.actor);
+
+        // Whenever the menu is opened, select today
+        this.menu.connect('open-state-changed', Lang.bind(this, function(menu, isOpen) {
+            if (isOpen) {
+                let now = new Date();
+                this._calendar.setDate(now);
+                // No need to update this._eventList as ::selected-date-changed
+                // signal will fire
+            }
+        }));
+
+        // Done with hbox for calendar and event list
+
+        // Add separator
+        item = new PopupMenu.PopupSeparatorMenuItem();
+        this.menu.addMenuItem(item);
+
+        // Add button to get to the Date and Time settings
+        this.menu.addAction(_("Date and Time Settings"),
+                            Lang.bind(this, this._onPreferencesActivate));
+
+        // Track changes to clock settings
+        this._desktopSettings = new Gio.Settings({ schema: 'org.gnome.desktop.interface' });
+        this._clockSettings = new Gio.Settings({ schema: 'org.gnome.shell.clock' });
+        this._desktopSettings.connect('changed', Lang.bind(this, this._updateClockAndDate));
+        this._clockSettings.connect('changed', Lang.bind(this, this._updateClockAndDate));
+
+        // Start the clock
+        this._updateClockAndDate();
+    },
+
+    _updateClockAndDate: function() {
+        let format = this._desktopSettings.get_string(CLOCK_FORMAT_KEY);
+        let showDate = this._clockSettings.get_boolean(CLOCK_SHOW_DATE_KEY);
+        let showSeconds = this._clockSettings.get_boolean(CLOCK_SHOW_SECONDS_KEY);
+
+        let clockFormat;
+        let dateFormat;
+
+        switch (format) {
+            case '24h':
+                if (showDate)
+	            /* Translators: This is the time format with date used
+                       in 24-hour mode. */
+                    clockFormat = showSeconds ? _("%a %b %e, %R:%S")
+                                              : _("%a %b %e, %R");
+                else
+	            /* Translators: This is the time format without date used
+                       in 24-hour mode. */
+                    clockFormat = showSeconds ? _("%a %R:%S")
+                                              : _("%a %R");
+                break;
+            case '12h':
+            default:
+                if (showDate)
+	            /* Translators: This is a time format with date used
+                       for AM/PM. */
+                    clockFormat = showSeconds ? _("%a %b %e, %l:%M:%S %p")
+                                              : _("%a %b %e, %l:%M %p");
+                else
+	            /* Translators: This is a time format without date used
+                       for AM/PM. */
+                    clockFormat = showSeconds ? _("%a %l:%M:%S %p")
+                                              : _("%a %l:%M %p");
+                break;
+        }
+
+        let displayDate = new Date();
+        let msecRemaining;
+        if (showSeconds) {
+            msecRemaining = 1000 - displayDate.getMilliseconds();
+            if (msecRemaining < 50) {
+                displayDate.setSeconds(displayDate.getSeconds() + 1);
+                msecRemaining += 1000;
+            }
+        } else {
+            msecRemaining = 60000 - (1000 * displayDate.getSeconds() +
+                                     displayDate.getMilliseconds());
+            if (msecRemaining < 500) {
+                displayDate.setMinutes(displayDate.getMinutes() + 1);
+                msecRemaining += 60000;
+            }
+        }
+
+        this._clock.set_text(displayDate.toLocaleFormat(clockFormat));
+
+        /* Translators: This is the date format to use */
+        dateFormat = _("%B %e, %Y");
+        this._date.set_text(displayDate.toLocaleFormat(dateFormat));
+
+        Mainloop.timeout_add(msecRemaining, Lang.bind(this, this._updateClockAndDate));
+        return false;
+    },
+
+    _onPreferencesActivate: function() {
+        Util.spawnDesktop('gnome-datetime-panel');
+    },
+};
diff --git a/js/ui/main.js b/js/ui/main.js
index 1d456af..fb293f3 100644
--- a/js/ui/main.js
+++ b/js/ui/main.js
@@ -265,10 +265,8 @@ function _relayout() {
 
     // To avoid updating the position and size of the workspaces
     // in the overview, we just hide the overview. The positions
-    // will be updated when it is next shown. We do the same for
-    // the calendar popdown.
+    // will be updated when it is next shown.
     overview.hide();
-    panel.hideCalendar();
 }
 
 // metacity-clutter currently uses the same prefs as plain metacity,
diff --git a/js/ui/panel.js b/js/ui/panel.js
index 97789d8..a7bfc9f 100644
--- a/js/ui/panel.js
+++ b/js/ui/panel.js
@@ -17,6 +17,7 @@ const Overview = imports.ui.overview;
 const PopupMenu = imports.ui.popupMenu;
 const PanelMenu = imports.ui.panelMenu;
 const StatusMenu = imports.ui.statusMenu;
+const DateMenu = imports.ui.dateMenu;
 const Main = imports.ui.main;
 const Tweener = imports.ui.tweener;
 
@@ -492,121 +493,6 @@ AppMenuButton.prototype = {
 
 Signals.addSignalMethods(AppMenuButton.prototype);
 
-function ClockButton() {
-    this._init();
-}
-
-ClockButton.prototype = {
-    _init: function() {
-        this.actor = new St.Bin({ style_class: 'panel-button',
-                                  reactive: true,
-                                  can_focus: true,
-                                  x_fill: true,
-                                  y_fill: false,
-                                  track_hover: true });
-        this.actor._delegate = this;
-        this.actor.connect('button-press-event',
-                           Lang.bind(this, this._toggleCalendar));
-
-        this._clock = new St.Label();
-        this.actor.set_child(this._clock);
-
-        this._calendarPopup = null;
-
-        this._desktopSettings = new Gio.Settings({ schema: 'org.gnome.desktop.interface' });
-        this._clockSettings = new Gio.Settings({ schema: 'org.gnome.shell.clock' });
-
-        this._desktopSettings.connect('changed', Lang.bind(this, this._updateClock));
-        this._clockSettings.connect('changed', Lang.bind(this, this._updateClock));
-
-        // Start the clock
-        this._updateClock();
-    },
-
-    closeCalendar: function() {
-        if (!this._calendarPopup || !this._calendarPopup.isOpen)
-            return;
-
-        this._calendarPopup.hide();
-
-        this.actor.remove_style_pseudo_class('pressed');
-    },
-
-    openCalendar: function() {
-        this._calendarPopup.show();
-
-        this.actor.add_style_pseudo_class('pressed');
-    },
-
-    _toggleCalendar: function() {
-        if (this._calendarPopup == null) {
-            this._calendarPopup = new CalendarPopup();
-            this._calendarPopup.actor.hide();
-        }
-
-        if (!this._calendarPopup.isOpen)
-            this.openCalendar();
-        else
-            this.closeCalendar();
-    },
-
-    _updateClock: function() {
-        let format = this._desktopSettings.get_string(CLOCK_FORMAT_KEY);
-        let showDate = this._clockSettings.get_boolean(CLOCK_SHOW_DATE_KEY);
-        let showSeconds = this._clockSettings.get_boolean(CLOCK_SHOW_SECONDS_KEY);
-
-        let clockFormat;
-        switch (format) {
-            case '24h':
-                if (showDate)
-	            /* Translators: This is the time format with date used
-                       in 24-hour mode. */
-                    clockFormat = showSeconds ? _("%a %b %e, %R:%S")
-                                              : _("%a %b %e, %R");
-                else
-	            /* Translators: This is the time format without date used
-                       in 24-hour mode. */
-                    clockFormat = showSeconds ? _("%a %R:%S")
-                                              : _("%a %R");
-                break;
-            case '12h':
-            default:
-                if (showDate)
-	            /* Translators: This is a time format with date used
-                       for AM/PM. */
-                    clockFormat = showSeconds ? _("%a %b %e, %l:%M:%S %p")
-                                              : _("%a %b %e, %l:%M %p");
-                else
-	            /* Translators: This is a time format without date used
-                       for AM/PM. */
-                    clockFormat = showSeconds ? _("%a %l:%M:%S %p")
-                                              : _("%a %l:%M %p");
-                break;
-        }
-
-        let displayDate = new Date();
-        let msecRemaining;
-        if (showSeconds) {
-            msecRemaining = 1000 - displayDate.getMilliseconds();
-            if (msecRemaining < 50) {
-                displayDate.setSeconds(displayDate.getSeconds() + 1);
-                msecRemaining += 1000;
-            }
-        } else {
-            msecRemaining = 60000 - (1000 * displayDate.getSeconds() +
-                                     displayDate.getMilliseconds());
-            if (msecRemaining < 500) {
-                displayDate.setMinutes(displayDate.getMinutes() + 1);
-                msecRemaining += 60000;
-            }
-        }
-
-        this._clock.set_text(displayDate.toLocaleFormat(clockFormat));
-        Mainloop.timeout_add(msecRemaining, Lang.bind(this, this._updateClock));
-        return false;
-    }
-};
-
 function Panel() {
     this._init();
 }
@@ -803,9 +689,9 @@ Panel.prototype = {
         this._menus.addMenu(appMenuButton.menu);
 
         /* center */
-
-        this._clockButton = new ClockButton();
-        this._centerBox.add(this._clockButton.actor, { y_fill: true });
+        this._dateMenu = new DateMenu.DateMenuButton();
+        this._centerBox.add(this._dateMenu.actor, { y_fill: true });
+        this._menus.addMenu(this._dateMenu.menu);
 
         /* right */
 
@@ -886,10 +772,6 @@ Panel.prototype = {
         this._rightBox.add(this._statusmenu.actor);
     },
 
-    hideCalendar: function() {
-        this._clockButton.closeCalendar();
-    },
-
     startupAnimation: function() {
         this.actor.y = -this.actor.height;
         Tweener.addTween(this.actor,
