jQuery UI datepicker
Each calendar in the calendars array is a localization object or a string that determines a localization object. Each localization object specifies a calendar system; the most common is the Gregorian calendar, though I created flexcal to be able to use the Jewish calendar. Note that Wikipiedia calls it the "Hebrew calendar", and that seems to be the Unicode standard as well. I am convinced that is wrong; Hebrew is the language, Jewish is the calendar. It would be inappropriate to call the Islamic calendar, for instance, the Arabic calendar.
Be that as it may, the localization object also specifies the names of the months and the days of the week, and the way to format date and year numbers. It is assumed that there are a limited number of months and days in a week (a seven-day week is not assumed) so an array of names is appropriate. Dates in a month and years are assumed to be numeric, but can use numbering systems other than Latin numerals.
Each localization object contains the elements that are locale-specific; anything not included is taken from thel10n option and its defaults. Most of the items and their names are taken from the jQuery UI datepicker.
nameName of the calendar to display in the tab bar.
'flexcal'
calendarmonthNamesArray of month names that are displayed on the calendar.
['January','February','March','April','May','June', 'July','August','September','October','November','December']
monthNamesShortArray of abbreviated month names. Not used in displaying the calendar, or in the simplest version of flexcal at all. I plan to expand the format method to allow using these.
['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
dayNamesMinArray of minimized names of the days of the week. This is what is displayed on the calendar.
['Su','Mo','Tu','We','Th','Fr','Sa']
dayNamesArray of names of the days of the week. Not used in displaying the calendar, or in the simplest version of flexcal at all. I plan to expand the format method to allow using these.
['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
dayNamesShortArray of abbreviated names of the days of the week. Not used in displaying the calendar, or in the simplest version of flexcal at all. I plan to expand the format method to allow using these.
['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
prevTextText to display on the "previous month" button. The stylesheet for the calendar replaces this with an icon and uses this text as the tooltip. Per the jQuery UI guidelines, it should not contain any arrows or chevrons.
'Previous'
nextTextText to display on the "next month" button. The stylesheet for the calendar replaces this with an icon and uses this text as the tooltip. Per the jQuery UI guidelines, it should not contain any arrows or chevrons.
'Next'
isRTLBoolean that should be true to display the calendar right-to-left.
false
firstDayNumber of the day of the week (0-indexed) that the calendar should start.
0
yearsFunction to convert a year number into a string to display. The caption of the calendar is monthNames[month]+' '+years(year)
function(n) {return n.toString()}
fromYearsFunction to convert a string into a year number. If undefined, then only numbers can be parsed as years. Meant to be the inverse of years. Not used in displaying the calendar, or in the simplest version of flexcal at all. I plan to expand the parse method to allow using this.
undefined
datesFunction to convert a date number into a string to display. Each <td> in the calendar has the text dates(date)
function(n) {return n.toString()}
fromdatesFunction to convert a string into a date number. If undefined, then only numbers can be parsed as dates. Meant to be the inverse of dates. Not used in displaying the calendar, or in the simplest version of flexcal at all. I plan to expand the parse method to allow using this.
undefined
dateFormatString that represents the format to use for converting from a string (the value of the attached text box) to a Date object. See the full description below.
'm/d/yyyy'
In addition to localizing the calendar, flexcal allows for localizing text in buttons and the like. Any element with a data attribute data-flexcal-l10n will have the value of that attribute + 'Text' used as the key into the localization object, and the result is used as the HTML of the element. Thus
<span data-flexcal-l10n=today ></span>
will have its content replaced with l10n.todayText for the current localization object.
The localizations included in flexcal include todayText and closeText (along with prevText and nextText used in the calendars themselves). jQuery UI datepicker calls the text for "today" currentText, but I think that's wrong.
If setting the attribute from javascript, you can use $(...).attr('data-flexcal-l10n', 'today') or use jQuery's data method: $(...).data('flexcalL10n', 'today'), remembering to camelCase the name of the data key to "flexcalL10n".
See the buttons page for examples.
A calendar system is represented by a function that takes a Date d object and returns an object with the following fields:
firstDate of the first day of the month containing d.
lastDate of the last day of the month containing d.
prevDate of the day exactly one month before d.
nextDate of the day exactly one month after d.
prevYearDate of the day exactly one year before d.
nextYearDate of the day exactly one year after d.
yNumber of the year of d, in this calendar system. This is passed to years for display.
mNumber (0-indexed) of the month of d, in this calendar system. This is the index into monthNames.
dNumber of the date of d, in this calendar system. This is passed to dates for display.
dow0-indexed number of the day of the week of the first day of the month (not of d. Note that this is not necessarily just first.getDay(), since this calendar system may use some other "week" system.
toDateFunction that takes an object {y: y, m: m, d: d} representing a date in this calendar and returns the corresponding Javascript Date. Not currently used; I plan to expand the parse method to allow using this. If the values given are invalid, should either return new Date(undefined) or do some error correcting the way new Date(y, m, d) does, e.g. turning 0 dates into the last day of the previous month.
I implemented two calendar systems that I wanted, and they are stored in $.bililite.flexcal.calendars: Gregorian and Jewish.
$.bililite.flexcal.calendars.gregorian
function(d){
var m = d.getMonth(), y = d.getFullYear(), date = d.getDate(), first = new Date (y, m, 1);
var prev = new Date (y, m-1, date), next = new Date (y, m+1, date);
if (prev.getDate() != date) prev = new Date (y, m, 0); // adjust for too-short months
if (next.getDate() != date) next = new Date (y, m+2, 0);
var nextYearDate = m == 1 && date == 29 ? 28 : date;
return {
first: first,
last: new Date (y, m+1, 0),
prev: prev,
next: next,
prevYear: new Date (y-1, m, nextYearDate),
nextYear: new Date (y+1, m, nextYearDate),
m: m,
y: y,
d: d,
dow: first.getDay(),
toDate: function (d) {return new Date (d.y, d.m, d.d)}
};
}
$.bililite.flexcal.calendars.jewishDefine a function
function addDay(d, n){
if (n === undefined) n = 1;
return new Date(d.getFullYear(), d.getMonth(), d.getDate()+n);
}
and functions civ2heb(d) that takes a Javascript Date and returns an object {y: y, m: m, d: d} representing the Jewish date, and heb2civ(h) which does the reverse. See the source code for details. The civ2heb function also includes a daysinmonth field that is the number of days in that Jewish month. Then the calendar function is:
function(d){
var h = civ2heb(d);
var roshchodesh = addDay(d, -h.d+1);
var daysinlastmonth = Math.max(civ2heb(addDay(roshchodesh,-1)).daysinmonth, h.d); // the min/max() correct for the possibility of other month being too short
var daysintonextmonth = Math.min(civ2heb(addDay(roshchodesh, h.daysinmonth)).daysinmonth, h.d);
return {
first: roshchodesh,
last: addDay(roshchodesh, h.daysinmonth-1),
prev: addDay(d, -daysinlastmonth),
next: addDay(roshchodesh, h.daysinmonth+daysintonextmonth-1),
prevYear: heb2civ($.extend({}, h, {y: h.y-1})),
nextYear: heb2civ($.extend({}, h, {y: h.y+1})),
m: h.m,
y: h.y,
d: h.d,
dow: roshchodesh.getDay(),
toDate: heb2civ
};
}
If a string is passed as the localization object, it is assumed to be the key into the $.bililite.flexcal.l10n, which comes with three entries predefined: 'en' (English localization of the Gregorian calendar), 'jewish' (English localization of the Jewish calendar), and 'he-jewish' (Hebrew localization of the Jewish calendar).
Note that the standard I am using is languageCode-calendarSystem, where calendarSystem is the index into the $.bililite.flexcal.calendars array.
datepickerDatepicker comes with a large number of localization files that can be easily used with flexcal. You need to include the localization file, with something like <script src="https://cdn.rawgit.com/jquery/jquery-ui/master/ui/i18n/datepicker-fr.js"></script> to get the French localization. flexcal will then interpret 'fr' as a reference to $.datepicker.regional['fr'].
Note that all the datepicker localizations are for the Gregorian calendar. Note also that including localization file sets the defaults for datepicker to that language, so if you want to actually use datepicker, you may want to call $.datepicker.setDefaults($.datepicker.regional['']) to set them back. Note also that not all the fields that flexcal uses are set in the datepicker localization files, especially name, so you may want to set that separately (see the calendars example).
calendarspickerKeith Wood is maintaining the jQuery plugin that eventually became the official jQuery UI datepicker. His version, however, handles multiple calendar systems, much like flexcal (though I like my plugin, of course). His code is on github, and the documentation is on his personal site. He has support for many calendar systems, and I wanted to let flexcal use that. I don't use his datepicker code, just the calendar systems.
To use it, include the basic calendar code with <script src="https://cdn.rawgit.com/kbwood/calendars/master/jquery.calendars.js"></script> and all the localization files. To use the Arabic localization, include <script src="https://cdn.rawgit.com/kbwood/calendars/master/jquery.calendars-ar.js"></script> and 'ar' as the localization object. To use the Islamic calendar with Arabic localization, include both <script src="https://cdn.rawgit.com/kbwood/calendars/master/jquery.calendars.islamic.js"></script> and <script src="https://cdn.rawgit.com/kbwood/calendars/master/jquery.calendars.islamic-ar.js"></script>, and use 'islamic' for the Islamic calendar with English localization, and 'ar-islamic' for the Islamic calendar with Arabic localization.
Some localization names use the 2-letter language code plus a country code, like 'en-GB'. flexcal is smart enough to handle that.
Note that the name field in these localization files is only the name of the calendar system, so you may want to set that separately.Note also that the prevText and nextText fields are not set in the calendar localization files, but in his datepicker localization files. So including that requires including the entire datepicker file, <script src="https://cdn.rawgit.com/kbwood/calendars/master/jquery.calendars.picker.js"></script> and the localization file, <script src="https://cdn.rawgit.com/kbwood/calendars/master/jquery.calendars.picker-ar.js"></script>. So as note to require including the whole datepicker (I assume you are using flexcal!) I created a stub that can be included instead, jquery.calendars.picker-mock.js. You still have to include the picker localization file, like jquery.calendars.picker-ar.js.
Any string that cannot be interpreted as an index into an array of localization objects will be interpreted as simply a name. So 'French' is interpreted as {name: 'French'}. All actual localization names start with a lower case letter.
tol10nAs a utility function, localization objects can be created with a standalone function, $.bililite.flexcal.tol10n(name, defaultL10n). name can be an object, which is taken as the actual localization object, or a string, which is interpreted as above. If it is an array, then each element is evaluated and the results $.extended together. defaultL10n must be an object, and it will be $.extended with the result of evaluating name. If defaultL10n is undefined, $.bililite.flexcal.prototype.options.l10n is used.
$.bililite.flexcal.tol10n('fr') (if the datepicker localization file is included) returns
{
calendar: function(d) {...} // $.bililite.flexcal.calendars.gregorian,
closeText: "Fermer", // used by datepicker, not flexcal
currentText: "Aujourd'hui",
dateFormat: "dd/mm/yy",
dates: function (n) {return n.toString()},
dayNames: ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"],
dayNamesMin: ["D","L","M","M","J","V","S"],
dayNamesShort: Array["dim.", "lun.", "mar.", "mer.", "jeu.", "ven.", "sam."],
firstDay: 1,
isRTL: false,
monthNames:["janvier","février","mars","avril","mai","juin","juillet","août","septembre","octobre","novembre","décembre"],
monthNamesShort: ["janv.","févr.","mars","avr.","mai","juin","juil.","août","sept.","oct.","nov.","déc."]
name: "flexcal", // from the default
nextText: "Suivant",
prevText: "Précédent",
showMonthAfterYear: false,
weekHeader: "Sem.",
yearSuffix: "",
years: function (n) {return n.toString()
}
And $.bililite.flexcal.tol10n(['fr', 'French', {dateFormat: 'yyyy-mm-dd'}]) returns
{
calendar: function(d) {...} // $.bililite.flexcal.calendars.gregorian,
closeText: "Fermer", // used by datepicker, not flexcal
currentText: "Aujourd'hui",
dateFormat: "yyyy-mm-dd", // from the third element
dates: function (n) {return n.toString()},
dayNames: ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"],
dayNamesMin: ["D","L","M","M","J","V","S"],
dayNamesShort: Array["dim.", "lun.", "mar.", "mer.", "jeu.", "ven.", "sam."],
firstDay: 1,
isRTL: false,
monthNames:["janvier","février","mars","avril","mai","juin","juillet","août","septembre","octobre","novembre","décembre"],
monthNamesShort: ["janv.","févr.","mars","avr.","mai","juin","juil.","août","sept.","oct.","nov.","déc."]
name: "French", // from the second element
nextText: "Suivant",
prevText: "Précédent",
showMonthAfterYear: false,
weekHeader: "Sem.",
yearSuffix: "",
years: function (n) {return n.toString()
}
flexcal as it stands has a minimal formatting/parsing capability. The formatting specification is based on that of Keith Wood's calendars, except for not using 2-digit years. It is also compatible with the jQuery UI formats.
The codes are:
d: Date numberdd: Date number, padded to two digitsm: Month number (1-indexed. Note that internally (as with Date) months are 0-based)mm: Month number, padded to two digitsyy: Year number. There is no option for a two-digit year.yyyy: Year number, padded to four digits (which really shouldn't be relevant)Any other string is interpreted literally.
formatThere are three utility functions, $.bililite.flexcal.format(d, format, l10n), $.bililite.flexcal.parse(s, format, l10n) and $.bililite.flexcal.localize(s, l10n).
$.bililite.flexcal.format(d, l10n) takes three arguments (none are optional), a Date d, a formatting string and a localization object l10n. l10n has to be an object; to use a string, run it through tol10n, as $.bililite.flexcal.format(new Date(2015,3,8), 'dd-mm-yyyy', $.bililite.flexcal.tol10n('fr')). It returns a string with the date formatted according to format. In the last example, the result would be '08-04-2015'.
parse$.bililite.flexcal.parse(s, format, l10n) is the reverse. It takes a string s and tries to parse it according to l10n.dateFormat. It tries to obey Postel's law, "Be conservative in what you send, be liberal in what you accept", and accept anything that looks like the format. It just looks for three numbers and interprets them in the order in the format string. A completely uninterpretable string returns new Date(NaN).
localize$.bililite.flexcal.localize(s, l10n) is for localizing individual strings. Just returns l10n[s+'Text'] || ''.
I hope to write a more complete formatter/parser in the future (Issue #4).
See the examples for the calendars option, and the examples in the introduction
The French Revolutionary calendar. This is just to show off how flexible the widget is; 10-day weeks and 5-day months are no problem.
archaicNumbersThe French Revolutionary Calendar example uses a utility function for translating normal numbers into Roman numerals. $.bililite.flexcal.archaicNumbers(arr) takes an array of equivalent numbers and returns an object {format: formatFunction(n), parse: parseFunction(s)}.
formatFunction(n) takes a number and returns a string that is the equivalent. This function can be used for the dates and years options. So:
var convertToRoman = $.bililite.flexcal.archaicNumbers([
[1000, 'M'],
[900, 'CM'],
[500, 'D'],
[400, 'CD'],
[100, 'C'],
[90, 'XC'],
[50, 'L'],
[40, 'XL'],
[10, 'X'],
[9, 'IX'],
[5, 'V'],
[4, 'IV'],
[1, 'I']
]);
assert.equal (convertToRoman.format(2015), 'MMXV');
parseFunction(s) does the reverse, parsing a string into a number. This function can be used for the fromDates and fromYears options.So:
assert.equal (convertToRoman.parse('MMXV'), 2015);
The parsing is very liberal in what it accepts and just ignores unrecognized characters. This may not be appropriate for some numeric systems and the output or input may need further processing.