UNPKG

@ghranek/moment-islamic-civil

Version:

A Hijri calendar (Based on islamic civil calculation) plugin for moment.js.

902 lines (805 loc) 25.5 kB
// moment-civil.js // author: Samar Sultan // license: MIT 'use strict'; /************************************ Expose Moment Hijri ************************************/ (function (root, factory) { /* global define */ if (typeof define === 'function' && define.amd) { define(['moment'], function (moment) { root.moment = factory(moment) return root.moment }) } else if (typeof exports === 'object') { module.exports = factory(require('moment')) } else { root.moment = factory(root.moment) } })(this, function (moment) { // jshint ignore:line if (moment == null) { throw new Error('Cannot find moment') } /************************************ Constants ************************************/ var GREGORIAN_EPOCH = 1721425.5, ISLAMIC_EPOCH = 1948439.5; var formattingTokens = /(\[[^\[]*\])|(\\)?i(Mo|MM?M?M?|Do|DDDo|DD?D?D?|w[o|w]?|YYYYY|YYYY|YY|gg(ggg?)?)|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|SS?S?|X|zz?|ZZ?|.)/g, localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g , parseTokenOneOrTwoDigits = /\d\d?/, parseTokenOneToThreeDigits = /\d{1,3}/, parseTokenThreeDigits = /\d{3}/, parseTokenFourDigits = /\d{1,4}/, parseTokenSixDigits = /[+\-]?\d{1,6}/, parseTokenWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i, parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/i, parseTokenT = /T/i, parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/ , symbolMap = { '1': '١', '2': '٢', '3': '٣', '4': '٤', '5': '٥', '6': '٦', '7': '٧', '8': '٨', '9': '٩', '0': '٠' } , numberMap = { '١': '1', '٢': '2', '٣': '3', '٤': '4', '٥': '5', '٦': '6', '٧': '7', '٨': '8', '٩': '9', '٠': '0' } , pluralForm = function (n) { return n === 0 ? 0 : n === 1 ? 1 : n === 2 ? 2 : n % 100 >= 3 && n % 100 <= 10 ? 3 : n % 100 >= 11 ? 4 : 5; }, plurals = { s : ['أقل من ثانية', 'ثانية واحدة', ['ثانيتان', 'ثانيتين'], '%d ثوان', '%d ثانية', '%d ثانية'], m : ['أقل من دقيقة', 'دقيقة واحدة', ['دقيقتان', 'دقيقتين'], '%d دقائق', '%d دقيقة', '%d دقيقة'], h : ['أقل من ساعة', 'ساعة واحدة', ['ساعتان', 'ساعتين'], '%d ساعات', '%d ساعة', '%d ساعة'], d : ['أقل من يوم', 'يوم واحد', ['يومان', 'يومين'], '%d أيام', '%d يومًا', '%d يوم'], M : ['أقل من شهر', 'شهر واحد', ['شهران', 'شهرين'], '%d أشهر', '%d شهرا', '%d شهر'], y : ['أقل من عام', 'عام واحد', ['عامان', 'عامين'], '%d أعوام', '%d عامًا', '%d عام'] }, pluralize = function (u) { return function (number, withoutSuffix, string, isFuture) { var f = pluralForm(number), str = plurals[u][pluralForm(number)]; if (f === 2) { str = str[withoutSuffix ? 0 : 1]; } return str.replace(/%d/i, number); }; } , unitAliases = { hm: 'imonth', hy: 'iyear' } , formatFunctions = {} , ordinalizeTokens = 'DDD w M D'.split(' '), paddedTokens = 'M D w'.split(' ') , formatTokenFunctions = { iM: function () { return this.iMonth() + 1 }, iMMM: function (format) { return this.localeData().iMonthsShort(this, format) }, iMMMM: function (format) { return this.localeData().iMonths(this, format) }, iD: function () { return this.iDate() }, iDDD: function () { return this.iDayOfYear() }, iw: function () { return this.iWeek() }, iYY: function () { return leftZeroFill(this.iYear() % 100, 2) }, iYYYY: function () { return leftZeroFill(this.iYear(), 4) }, iYYYYY: function () { return leftZeroFill(this.iYear(), 5) }, igg: function () { return leftZeroFill(this.iWeekYear() % 100, 2) }, igggg: function () { return this.iWeekYear() }, iggggg: function () { return leftZeroFill(this.iWeekYear(), 5) } }, i function padToken(func, count) { return function (a) { return leftZeroFill(func.call(this, a), count) } } function ordinalizeToken(func, period) { return function (a) { return this.localeData().ordinal(func.call(this, a), period) } } while (ordinalizeTokens.length) { i = ordinalizeTokens.pop() formatTokenFunctions['i' + i + 'o'] = ordinalizeToken(formatTokenFunctions['i' + i], i) } while (paddedTokens.length) { i = paddedTokens.pop() formatTokenFunctions['i' + i + i] = padToken(formatTokenFunctions['i' + i], 2) } formatTokenFunctions.iDDDD = padToken(formatTokenFunctions.iDDD, 3) /************************************ Helpers ************************************/ function extend(a, b) { var key for (key in b) if (b.hasOwnProperty(key)) a[key] = b[key] return a } function leftZeroFill(number, targetLength) { var output = number + '' while (output.length < targetLength) output = '0' + output return output } function isArray(input) { return Object.prototype.toString.call(input) === '[object Array]' } function normalizeUnits(units) { return units ? unitAliases[units] || units.toLowerCase().replace(/(.)s$/, '$1') : units } function setDate(moment, year, month, date) { var utc = moment._isUTC ? 'UTC' : '' moment._d['set' + utc + 'FullYear'](year) moment._d['set' + utc + 'Month'](month) moment._d['set' + utc + 'Date'](date) } function objectCreate(parent) { function F() {} F.prototype = parent return new F() } function getPrototypeOf(object) { if (Object.getPrototypeOf) return Object.getPrototypeOf(object) else if (''.__proto__) // jshint ignore:line return object.__proto__ // jshint ignore:line else return object.constructor.prototype } /************************************ Languages ************************************/ extend(getPrototypeOf(moment.localeData()), { _iMonths: ['Muharram' , 'Safar' , 'Rabi\' al-Awwal' , 'Rabi\' al-Thani' , 'Jumada al-Ula' , 'Jumada al-Alkhirah' , 'Rajab' , 'Sha’ban' , 'Ramadhan' , 'Shawwal' , 'Thul-Qi’dah' , 'Thul-Hijjah' ], iMonths: function (m) { return this._iMonths[m.iMonth()] } , _iMonthsShort: ['Muh' , 'Saf' , 'Rab-I' , 'Rab-II' , 'Jum-I' , 'Jum-II' , 'Raj' , 'Sha' , 'Ram' , 'Shw' , 'Dhu-Q' , 'Dhu-H' ], iMonthsShort: function (m) { return this._iMonthsShort[m.iMonth()] } , iMonthsParse: function (monthName) { var i, mom, regex if (!this._iMonthsParse) this._iMonthsParse = [] for (i = 0; i < 12; i += 1) { // Make the regex if we don't have it already. if (!this._iMonthsParse[i]) { mom = hMoment([2000, (2 + i) % 12, 25]) regex = '^' + this.iMonths(mom, '') + '$|^' + this.iMonthsShort(mom, '') + '$' this._iMonthsParse[i] = new RegExp(regex.replace('.', ''), 'i') } // Test the regex. if (this._iMonthsParse[i].test(monthName)) return i } }, _longDateFormat: { LT: 'HH:mm' , L: 'iYYYY/iMM/iDD' , LL: 'iD iMMMM iYYYY' , LLL: 'iD iMMMM iYYYY LT' , LLLL: 'dddd، iD iMMMM iYYYY LT' }, longDateFormat : function (key) { var output = this._longDateFormat[key]; if (!output && this._longDateFormat[key.toUpperCase()]) { output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) { return val.slice(1); }); this._longDateFormat[key] = output; } return output; } }); var iMonthNames = { iMonths: 'محرم_صفر_ربيع الأول_ربيع الثاني_جمادى الأولى_جمادى الآخرة_رجب_شعبان_رمضان_شوال_ذو القعدة_ذو الحجة'.split('_'), iMonthsShort: 'محرم_صفر_ربيع ١_ربيع ٢_جمادى ١_جمادى ٢_رجب_شعبان_رمضان_شوال_ذو القعدة_ذو الحجة'.split('_') }; // Default to the momentjs 2.12+ API if (typeof moment.updateLocale === 'function') { moment.updateLocale('ar-sa', iMonthNames); } else { var oldLocale = moment.locale(); moment.defineLocale('ar-sa', iMonthNames); moment.locale(oldLocale); } /************************************ Formatting ************************************/ function makeFormatFunction(format) { var array = format.match(formattingTokens), length = array.length, i for (i = 0; i < length; i += 1) if (formatTokenFunctions[array[i]]) array[i] = formatTokenFunctions[array[i]] return function (mom) { var output = '' for (i = 0; i < length; i += 1) output += array[i] instanceof Function ? '[' + array[i].call(mom, format) + ']' : array[i] return output } } /************************************ Parsing ************************************/ function getParseRegexForToken(token, config) { switch (token) { case 'iDDDD': return parseTokenThreeDigits case 'iYYYY': return parseTokenFourDigits case 'iYYYYY': return parseTokenSixDigits case 'iDDD': return parseTokenOneToThreeDigits case 'iMMM': case 'iMMMM': return parseTokenWord case 'iMM': case 'iDD': case 'iYY': case 'iM': case 'iD': return parseTokenOneOrTwoDigits case 'DDDD': return parseTokenThreeDigits case 'YYYY': return parseTokenFourDigits case 'YYYYY': return parseTokenSixDigits case 'S': case 'SS': case 'SSS': case 'DDD': return parseTokenOneToThreeDigits case 'MMM': case 'MMMM': case 'dd': case 'ddd': case 'dddd': return parseTokenWord case 'a': case 'A': return moment.localeData(config._l)._meridiemParse case 'X': return parseTokenTimestampMs case 'Z': case 'ZZ': return parseTokenTimezone case 'T': return parseTokenT case 'MM': case 'DD': case 'YY': case 'HH': case 'hh': case 'mm': case 'ss': case 'M': case 'D': case 'd': case 'H': case 'h': case 'm': case 's': return parseTokenOneOrTwoDigits default: return new RegExp(token.replace('\\', '')) } } function addTimeToArrayFromToken(token, input, config) { var a, datePartArray = config._a switch (token) { case 'iM': case 'iMM': datePartArray[1] = input == null ? 0 : ~~input - 1 break case 'iMMM': case 'iMMMM': a = moment.localeData(config._l).iMonthsParse(input) if (a != null) datePartArray[1] = a else config._isValid = false break case 'iD': case 'iDD': case 'iDDD': case 'iDDDD': if (input != null) datePartArray[2] = ~~input break case 'iYY': datePartArray[0] = ~~input + (~~input > 47 ? 1300 : 1400) break case 'iYYYY': case 'iYYYYY': datePartArray[0] = ~~input } if (input == null) config._isValid = false } function dateFromArray(config) { var g, h, hy = config._a[0], hm = config._a[1], hd = config._a[2] if ((hy == null) && (hm == null) && (hd == null)) return [0, 0, 1] hy = hy || 0 hm = hm || 0 hd = hd || 1 if (hd < 1 || hd > hMoment.iDaysInMonth(hy, hm)) config._isValid = false g = toGregorian(hy, hm, hd) h = toHijri(g.year, g.month, g.day) config._hDiff = 0 if (~~h.year !== hy) config._hDiff += 1 if (~~h.month !== hm) config._hDiff += 1 if (~~h.date !== hd) config._hDiff += 1 return [g.year, g.month, g.day] } function makeDateFromStringAndFormat(config) { var tokens = config._f.match(formattingTokens), string = config._i, len = tokens.length, i, token, parsedInput config._a = [] for (i = 0; i < len; i += 1) { token = tokens[i] parsedInput = (getParseRegexForToken(token, config).exec(string) || [])[0]; if (parsedInput) string = string.slice(string.indexOf(parsedInput) + parsedInput.length) if (formatTokenFunctions[token]) addTimeToArrayFromToken(token, parsedInput, config) } if (string) config._il = string return dateFromArray(config) } function makeDateFromStringAndArray(config, utc) { var len = config._f.length , i , format , tempMoment , bestMoment , currentScore , scoreToBeat if (len === 0) { return makeMoment(new Date(NaN)) } for (i = 0; i < len; i += 1) { format = config._f[i] currentScore = 0 tempMoment = makeMoment(config._i, format, config._l, utc) if (!tempMoment.isValid()) continue currentScore += tempMoment._hDiff if (tempMoment._il) currentScore += tempMoment._il.length if (scoreToBeat == null || currentScore < scoreToBeat) { scoreToBeat = currentScore bestMoment = tempMoment } } return bestMoment } function removeParsedTokens(config) { var string = config._i, input = '', format = '', array = config._f.match(formattingTokens), len = array.length, i, match, parsed for (i = 0; i < len; i += 1) { match = array[i] parsed = (getParseRegexForToken(match, config).exec(string) || [])[0] if (parsed) string = string.slice(string.indexOf(parsed) + parsed.length) if (!(formatTokenFunctions[match] instanceof Function)) { format += match if (parsed) input += parsed } } config._i = input config._f = format } /************************************ Week of Year ************************************/ function iWeekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) { var end = firstDayOfWeekOfYear - firstDayOfWeek, daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(), adjustedMoment if (daysToDayOfWeek > end) { daysToDayOfWeek -= 7 } if (daysToDayOfWeek < end - 7) { daysToDayOfWeek += 7 } adjustedMoment = hMoment(mom).add(daysToDayOfWeek, 'd') return { week: Math.ceil(adjustedMoment.iDayOfYear() / 7), year: adjustedMoment.iYear() } } /************************************ Top Level Functions ************************************/ function makeMoment(input, format, lang, utc) { var config = { _i: input , _f: format , _l: lang } , date , m , hm if (format) { if (isArray(format)) { return makeDateFromStringAndArray(config, utc) } else { date = makeDateFromStringAndFormat(config) removeParsedTokens(config) format = 'YYYY-MM-DD-' + config._f input = leftZeroFill(date[0], 4) + '-' + leftZeroFill(date[1] + 1, 2) + '-' + leftZeroFill(date[2], 2) + '-' + config._i } } if (utc) m = moment.utc(input, format, lang) else m = moment(input, format, lang) if (config._isValid === false) m._isValid = false m._hDiff = config._hDiff || 0 hm = objectCreate(hMoment.fn) extend(hm, m) return hm } function hMoment(input, format, lang) { return makeMoment(input, format, lang, false) } extend(hMoment, moment) hMoment.fn = objectCreate(moment.fn) hMoment.utc = function (input, format, lang) { return makeMoment(input, format, lang, true) } /************************************ hMoment Prototype ************************************/ hMoment.fn.format = function (format) { var i, replace, me = this if (format) { i = 5 replace = function (input) { return me.localeData().longDateFormat(input) || input } while (i > 0 && localFormattingTokens.test(format)) { i -= 1 format = format.replace(localFormattingTokens, replace) } if (!formatFunctions[format]) { formatFunctions[format] = makeFormatFunction(format) } format = formatFunctions[format](this) } return moment.fn.format.call(this, format) } hMoment.fn.iYear = function (input) { var lastDay, h, g if (typeof input === 'number') { h = toHijri(this.year(), this.month(), this.date()) lastDay = Math.min(h.date, hMoment.iDaysInMonth(input, h.month)) g = toGregorian(input, h.month, lastDay) setDate(this, g.year, g.month, g.day) moment.updateOffset(this) return this } else { return toHijri(this.year(), this.month(), this.date()).year } } hMoment.fn.iMonth = function (input) { var lastDay, h, g if (input != null) { if (typeof input === 'string') { input = this.localeData().iMonthsParse(input) if(input >= 0) { input -= 1 } else { return this } } h = toHijri(this.year(), this.month(), this.date()) lastDay = Math.min(h.date, hMoment.iDaysInMonth(h.year, input)) this.iYear(h.year + div(input, 12)) input = mod(input, 12) if (input < 0) { input += 12 this.iYear(this.iYear() - 1) } g = toGregorian(this.iYear(), input, lastDay) setDate(this, g.year, g.month, g.day) moment.updateOffset(this) return this } else { return toHijri(this.year(), this.month(), this.date()).month } } hMoment.fn.iDate = function (input) { var h, g if (typeof input === 'number') { h = toHijri(this.year(), this.month(), this.date()) g = toGregorian(h.year, h.month, input) setDate(this, g.year, g.month, g.day) moment.updateOffset(this) return this } else { return toHijri(this.year(), this.month(), this.date()).date } } hMoment.fn.iDayOfYear = function (input) { var dayOfYear = Math.round((hMoment(this).startOf('day') - hMoment(this).startOf('iYear')) / 864e5) + 1 return input == null ? dayOfYear : this.add(input - dayOfYear, 'd') } hMoment.fn.iDaysInMonth = function () { return parseInt(hMoment(this).endOf('iMonth').format('iDD')); } hMoment.fn.iWeek = function (input) { var week = iWeekOfYear(this, this.localeData()._week.dow, this.localeData()._week.doy).week return input == null ? week : this.add( (input - week) * 7, 'd') } hMoment.fn.iWeekYear = function (input) { var year = iWeekOfYear(this, this.localeData()._week.dow, this.localeData()._week.doy).year return input == null ? year : this.add(input - year, 'y') } hMoment.fn.add = function (val, units) { var temp if (units !== null && !isNaN(+units)) { temp = val val = units units = temp } units = normalizeUnits(units) if (units === 'iyear') { this.iYear(this.iYear() + val) } else if (units === 'imonth') { this.iMonth(this.iMonth() + val) } else { moment.fn.add.call(this, val, units) } return this } hMoment.fn.subtract = function (val, units) { var temp if (units !== null && !isNaN(+units)) { temp = val val = units units = temp } units = normalizeUnits(units) if (units === 'iyear') { this.iYear(this.iYear() - val) } else if (units === 'imonth') { this.iMonth(this.iMonth() - val) } else { moment.fn.subtract.call(this, val, units) } return this } hMoment.fn.startOf = function (units) { units = normalizeUnits(units) if (units === 'iyear' || units === 'imonth') { if (units === 'iyear') { this.iMonth(0) } this.iDate(1) this.hours(0) this.minutes(0) this.seconds(0) this.milliseconds(0) return this } else { return moment.fn.startOf.call(this, units) } } hMoment.fn.endOf = function (units) { units = normalizeUnits(units) if (units === undefined || units === 'milisecond') { return this } return this.startOf(units).add(1, (units === 'isoweek' ? 'week' : units)).subtract(1, 'milliseconds') } hMoment.fn.clone = function () { return hMoment(this) } hMoment.fn.iYears = hMoment.fn.iYear hMoment.fn.iMonths = hMoment.fn.iMonth hMoment.fn.iDates = hMoment.fn.iDate hMoment.fn.iWeeks = hMoment.fn.iWeek /************************************ hMoment Statics ************************************/ hMoment.iDaysInMonth = function (year, month) { return getDaysInIslamicMonth(year, month); } hMoment.iConvert = { toHijri: toHijri, toGregorian: toGregorian } hMoment.loadLocale = function (name){ var oldLocale = moment.locale(); requirejs(['./locale/'+ name],function (moment){ moment.locale(oldLocale); }); } return hMoment /************************************ Hijri Conversion ************************************/ function toHijri(gy, gm, gd){ var h = fromGregorian(new Date(gy ,gm ,gd)); return { year : h.year, month: h.month, date : h.date }; } function toGregorian(hy, hm, hd){ var julianDay = hd + Math.ceil(29.5 * hm) + (hy - 1) * 354 + Math.floor((3 + 11 * hy) / 30) + ISLAMIC_EPOCH - 1; var wjd = Math.floor(julianDay - 0.5) + 0.5, depoch = wjd - GREGORIAN_EPOCH, quadricent = Math.floor(depoch / 146097), dqc = mod(depoch, 146097), cent = Math.floor(dqc / 36524), dcent = mod(dqc, 36524), quad = Math.floor(dcent / 1461), dquad = mod(dcent, 1461), yindex = Math.floor(dquad / 365); var year = quadricent * 400 + cent * 100 + quad * 4 + yindex; if (!(cent === 4 || yindex === 4)) { year++; } var gYearStart = GREGORIAN_EPOCH + 365 * (year - 1) + Math.floor((year - 1) / 4) - Math.floor((year - 1) / 100) + Math.floor((year - 1) / 400); var yearday = wjd - gYearStart; var tjd = GREGORIAN_EPOCH - 1 + 365 * (year - 1) + Math.floor((year - 1) / 4) - Math.floor((year - 1) / 100) + Math.floor((year - 1) / 400) + Math.floor(739 / 12 + (isGregorianLeapYear(new Date(year, 3, 1)) ? -1 : -2) + 1); var leapadj = wjd < tjd ? 0 : isGregorianLeapYear(new Date(year, 3, 1)) ? 1 : 2; var month = Math.floor(((yearday + leapadj) * 12 + 373) / 367); var tjd2 = GREGORIAN_EPOCH - 1 + 365 * (year - 1) + Math.floor((year - 1) / 4) - Math.floor((year - 1) / 100) + Math.floor((year - 1) / 400) + Math.floor((367 * month - 362) / 12 + (month <= 2 ? 0 : isGregorianLeapYear(new Date(year, month - 1, 1)) ? -1 : -2) +1); var day = wjd - tjd2 + 1; var d = new Date(year, month - 1, day); return { year : d.getFullYear(), month : d.getMonth(), day : d.getDate() }; } function isGregorianLeapYear(date) { var y = date.getFullYear(); return y % 4 === 0 && y % 100 !== 0 || y % 400 === 0; } function isIslamicLeapYear(year){ return (14 + 11 * year) % 30 < 11; } function getIslamicYearStart(year) { // summary: // Return start of Islamic year. return (year - 1) * 354 + Math.floor((3 + 11 * year) / 30.0); } function getIslamicMonthStart(year, month) { // summary: // Return the start of Islamic Month. return Math.ceil(29.5 * month) + (year - 1) * 354 + Math.floor((3 + 11 * year) / 30.0); } /** * Returns the number of days in a specific Hijri month. * `month` is 1 for Muharram, 2 for Safar, etc. * `year` is any Hijri year. */ function getDaysInIslamicMonth(year, month) { year = year + Math.floor(month / 13); month = ((month - 1) % 12) + 1; var length = 29 + month % 2; if (month === 12 && isIslamicLeapYear(year)) { length++; } return length; } /** * Returns the equivalent islamic(civil) date value for a give input Gregorian date. * `gdate` is a JS Date to be converted to Hijri. */ function fromGregorian(gdate) { var date = new Date(gdate); var gYear = date.getFullYear(), gMonth = date.getMonth(), gDay = date.getDate(); var julianDay = GREGORIAN_EPOCH - 1 + 365 * (gYear - 1) + Math.floor((gYear - 1) / 4) + -Math.floor((gYear - 1) / 100) + Math.floor((gYear - 1) / 400) + Math.floor( (367 * (gMonth + 1) - 362) / 12 + (gMonth + 1 <= 2 ? 0 : isGregorianLeapYear(date) ? -1 : -2) + gDay); julianDay = Math.floor(julianDay) + 0.5; var days = julianDay - ISLAMIC_EPOCH; var hYear = Math.floor((30 * days + 10646) / 10631.0); var hMonth = Math.ceil((days - 29 - getIslamicYearStart(hYear)) / 29.5); hMonth = Math.min(hMonth, 11); var hDay = Math.ceil(days - getIslamicMonthStart(hYear, hMonth)) + 1; return { date : hDay, month : hMonth, year : hYear }; } /* Utility helper functions. */ function div(a, b) { return~~ (a / b) } function mod(a, b) { return a - ~~(a / b) * b } });