/** @jsx React.DOM */ var LIFE_IN_YEARS = 120; function $(s) { return document.querySelector(s); } // Returns ISO 8601 week number and year Date.prototype.getFullWeek = function() { var d = new Date(this), y = d.getFullYear(), wday = d.getDay() || 7; d.setDate(d.getDate() + 4 - wday); // set to nearest Thursday var jan1 = new Date(y, 0, 1), w, day = 864e5; // one day in milliseconds w = Math.ceil((((d - jan1) / day) + 1) / 7); // full weeks to nearest Thursday if (!w) w = getWeeksInYear(y -= 1); // 1999-01-01 is 1998W53, for instance return { y: y, w: w, toISOWeek: function() { return y + 'W' + w; } }; }; // Returns ISO 8601 week number Date.prototype.getWeek = function() { return this.getFullWeek().w; }; function getWeeksInYear(y) { return new Date(y, 11, 28).getFullWeek().w; } // 2000-01-01 = 1999W52 => 1999W52 // 2003-12-31 = 2003W53 => 2004W1, since 2003 technically only has 52 full weeks function normalizeToWeek(date) { if (typeof date == 'string') { var d = getLocalDate(date); if (isNaN(d)) // given a yearless relative date like 'W2', for instance return d; else date = d; } var week = date.getFullWeek(); var year = week.y; var maxw = getWeeksInYear(year); return week.w > maxw ? (year + 1) + 'W1' : week.toISOWeek(); } function getLocalDate(date) { var tz = (new Date + '').split(' ').pop(); return new Date(date + ' ' + tz); } var WeekCal = React.createClass({ render: function() { var bday = getLocalDate(this.props.from); var week = bday.getFullWeek(); var y = week.y, w = week.w; var now = (new Date).getFullWeek(); var wpy = getWeeksInYear(y), age = 0, weeks = 0; var trs = [], tds = [getAgeHeader(0)]; if (w > 1) tds.push(<td colSpan={ w - 1 }/>); function getAgeHeader(age) { var id = 'age-' + age; return <th id={ id }><a href={ '#' + id }>{ age }</a></th>; } // same-closure bookkeeping utility destructive on y, w, age, wpy, trs & tds function addYear() { var id = 'year-' + y; if (!trs.length || y % 5 === 0) { if (wpy !== 53) tds.push(<td/>); // pad out non-existent week 53 tds.push(<th><a href={ '#' + id }>{ y }</a></th>); } trs.push(<tr id={ id }>{ tds }</tr>); w = 1; y += 1; tds = [getAgeHeader(age += 1)]; wpy = getWeeksInYear(y); } var lastBG, lastTitle = ''; while (age <= this.props.lifespan) { weeks += 1; var id = 'week-' + y + '-' + w; var checked = y < now.y || y == now.y && w <= now.w; var style = null; var event = events[y + 'W' + w] || dates['W' + weeks]; var title = event; var color = /^([\d-]{10}: )?(#[\da-f]{3,6}): (.*)/i.exec(event || ''); if (color) { var p = color[1]; title = lastTitle = color[3]; color = lastBG = color[2]; if (p) { lastTitle = ''; title = p + title; } } else if (event) color = 'red' else color = lastBG; var style = color && { background: color, outline: '1px solid ' + color}; title = title || (lastTitle ? lastTitle + ': ' : '') + 'Week ' + weeks; tds.push(<td id={ id } style={ style }> <input type="checkbox" checked={ checked } title={ title }/> </td>); if (y === now.y && w === now.w) { console.log('This is week ' + weeks); } if (w === wpy) addYear(); else w += 1; } return <table id={ 'cal' || this.props.id }>{ trs }</table>; } }); function isURL(u) { return /^https?:\/\//i.test(u); } function isYYYYMMDD(date) { return /^\d{4}-\d\d-\d\d$/.test(date || ''); } // inits from the global window.dates, or an url hash override JSON blob, or url function init() { var hash = location.hash.slice(1); // if we set a #YYYY-MM-DD date in the URL hash, let it win var yyyymmdd = isYYYYMMDD(hash) && hash; try { var json = JSON.parse(hash); dates = json; } catch(e) { try { var decoded = decodeURIComponent(hash); json = JSON.parse(decoded); dates = json; } catch(e) { window.dates = window.dates || {}; } } var url = isURL(hash) ? hash : isURL(decoded) ? decoded : false; if (url) // drop your json at http://myjson.com/ or anywhere with CORS headers fetch(url).then(function(xhr) { xhr.json().then(function(data) { render(window.dates = data); }) }); else render(window.dates, yyyymmdd); } function render(dates, yyyymmdd) { // otherwise, start at the first event or today var today = (new Date).toISOString().split('T')[0]; var first = today; for (first in dates) break; date.value = yyyymmdd || first; events = {}; for (var d in dates) { if (isYYYYMMDD(d)) events[normalizeToWeek(d)] = d +': '+ dates[d]; } // just draw next ten years, if today var life = date.value === today ? 10 : LIFE_IN_YEARS; cal = React.render( <WeekCal id="cal" lifespan={ life } from={ date.value }/>, parent ); date.addEventListener('change', function() { cal.setProps({ from: date.value, lifespan: LIFE_IN_YEARS }); location.hash = '#' + date.value; }); } var parent = $('#calendar'), date = $('#bday'), cal; var hash = location.hash.slice(1), events; init();