cejs
Version:
A JavaScript module framework that is simple to use.
1,737 lines (1,453 loc) • 261 kB
JavaScript
/**
* @name CeL function for calendrical calculations.
*
* If you need a online demo of these calendars, please visit:
* http://lyrics.meicho.com.tw/lib/JS/_test%20suite/era.htm
*
* @fileoverview
* 本檔案包含了曆法轉換的功能。
*
* @since 2014/4/12 15:37:56
*/
// https://www.funaba.org/cc
// http://www.fourmilab.ch/documents/calendar/
// http://the-light.com/cal/converter/
// http://keith-wood.name/calendars.html
// http://www.cc.kyoto-su.ac.jp/~yanom/pancanga/index.html
/*
TODO:
https://en.wikipedia.org/wiki/Vikram_Samvat
the official calendar of Nepal
*/
'use strict';
if (typeof CeL === 'function')
CeL.run(
{
name : 'data.date.calendar',
// |application.astronomy.
// data.math.find_root
require : 'data.code.compatibility.|data.native.set_bind|data.date.String_to_Date|data.date.is_leap_year|data.date.Julian_day|data.math.',
code : function(library_namespace) {
// requiring
var set_bind = this.r('set_bind'), String_to_Date = this.r('String_to_Date'), is_leap_year = this.r('is_leap_year'), Julian_day = this.r('Julian_day');
/**
* null module constructor
* @class calendars 的 functions
*/
var _// JSDT:_module_
= function() {
// null module constructor
};
/**
* for JSDT: 有 prototype 才會將之當作 Class
*/
_// JSDT:_module_
.prototype = {
};
//----------------------------------------------------------------------------------------------------------------------------------------------------------//
// 工具函數。
var
// copy from data.date.
/** {Number}一整天的 time 值。should be 24 * 60 * 60 * 1000 = 86400000. */
ONE_DAY_LENGTH_VALUE = new Date(0, 0, 2) - new Date(0, 0, 1);
var
// 24 hours
ONE_DAY_HOURS = (new Date(1, 1, 1, -1)).getHours() | 0,
// set weekday name converter.
KEY_WEEK = 'week';
function _Date(year, month, date) {
if (year < 100) {
// 僅使用 new Date(0) 的話,會含入 timezone offset (.getTimezoneOffset)。
// 因此得使用 new Date(0, 0)。
var d = new Date(0, 0);
d.setFullYear(year, month, date);
return d;
}
return new Date(year, month, date);
}
function _time(year, month, date, hour) {
if (year < 100) {
// 僅使用 new Date(0) 的話,會含入 timezone offset (.getTimezoneOffset)。
// 因此得使用 new Date(0, 0)。
var d = new Date(0, 0);
d.setFullYear(year, month, date, hour);
return d;
}
return new Date(year, month, date, hour);
}
/**
* format 回傳處理。
*
* <code>
* API:
Date_to_Calendar({Date}, {Object}options)
options.format = 'serial':
return 數字序號 (numerical serial №) [ {Integer}year, {Natural}month, {Natural}date ]
options.format = 'item':
一般會:
return [ {Integer}year, {String}month name, {Natural}date ]
return [ {Integer}year, {String}month name, {Natural}date, {小數}餘下不到一日之時間值 remainder (單位:日) ]
options.format = 'text':
return {String} 當地語言之表現法。常是 "weekday, date month year"。
others:
default: text
* </code>
*
* @param {Array}date [
* {Integer}year, {Natural}month, {Natural}date ],<br />
* date[KEY_WEEK] = {Integer}weekday
* @param {Object}[options]
* options that called
* @param {Array|Function}to_name [
* {Function}year serial to name, {Function}month serial to name,
* {Function}date serial to name, {Function}weekday serial to name ]
* @returns
*/
function _format(date, options, to_name, is_leap, combine) {
var format = options && options.format;
if (format === 'serial')
return date;
if (typeof to_name === 'function')
// 當作 month_to_name。
date[1] = to_name(date[1], is_leap, options);
else if (Array.isArray(to_name))
to_name.forEach(function(func, index) {
date[ index === 3 ? KEY_WEEK : index ]
//
= func(date[index], is_leap, index);
});
else
library_namespace.warn('_format: 無法辨識之 to_name: ' + to_name);
if (format === 'item')
return date;
if (options && typeof options.numeral === 'function') {
date[0] = options.numeral(date[0]);
date[2] = options.numeral(date[2]);
}
if (typeof combine === 'function') {
format = combine(date);
} else {
format = date.slice(0, 3);
// direction
if (combine !== true)
format = format.reverse();
format = format.join(' ');
}
if (options) {
if (options.postfix)
format += options.postfix;
if (options.prefix)
format = options.prefix + format;
}
// add weekday name
if (date[KEY_WEEK])
format = date[KEY_WEEK] + ', ' + format;
return format;
}
/**
* 創建測試器。<br />
* test: 經過正反轉換運算,應該回到相同的日子。
*
* @param {Function}to_Calendar
* @param {Function}to_Date
* @param {Object}[options]
*
* @returns {Function}測試器。
*/
function new_tester(to_Calendar, to_Date, options) {
options = Object.assign(Object.create(null),
new_tester.default_options, options || {});
var epoch = (options.epoch || to_Date.epoch) - 0 || 0,
//
month_days = options.month_days, CE_format = options.CE_format,
//
continued_month = options.continued_month,
//
get_month_serial = options.month_serial;
return function(begin_Date, end_Date, error_limit) {
begin_Date = typeof begin_Date === 'number' ? epoch + (begin_Date | 0)
* ONE_DAY_LENGTH_VALUE : begin_Date - 0;
var tmp = typeof end_Date === 'string'
&& end_Date.trim().match(/^\+(\d+)$/);
end_Date = tmp || typeof end_Date === 'number' ? (tmp ? begin_Date
: epoch)
+ end_Date * ONE_DAY_LENGTH_VALUE : end_Date - 0;
if (isNaN(begin_Date) || isNaN(end_Date))
return;
var begin = Date.now(), last_show = begin, date_name, old_date_name, error = [];
if (!(0 < error_limit && error_limit < 1e9))
error_limit = new_tester.default_options.error_limit;
for (; begin_Date <= end_Date && error.length < error_limit; begin_Date += ONE_DAY_LENGTH_VALUE) {
// 正解: Date → calendar date
date_name = to_Calendar(new Date(begin_Date), options);
if (old_date_name
//
&& (date_name[2] - old_date_name[2] !== 1 || old_date_name[1] !== date_name[1])) {
if (date_name[0] !== old_date_name[0]
// 每世紀記錄一次使用時間。
&& date_name[0] % 100 === 0 && Date.now() - last_show > 20000) {
console.log((begin_Date - epoch) / ONE_DAY_LENGTH_VALUE
+ ' days: ' + date_name.join() + ' ('
+ (new Date(begin_Date)).format(CE_format) + ')'
+ ', 使用時間 ' + ((last_show = Date.now()) - begin)
+ ' ms.');
}
// 確定 old_date_name 的下一個天為 date_name。
// 月差距
tmp = get_month_serial(date_name)
- get_month_serial(old_date_name);
if (date_name[2] - old_date_name[2] === 1)
tmp = tmp !== 0
&& !continued_month(date_name[1], old_date_name[1])
&& '隔日(日期名接續),但月 serial 差距 !== 0';
else if (date_name[2] !== 1)
tmp = '日期名未接續: 隔月/隔年,但日期非以 1 起始';
else if (!(old_date_name[2] in month_days))
tmp = '日期名未接續: 前一月末日數 ' + old_date_name[2]
+ '未設定於 month_days 中';
else if (tmp !== 1 && (tmp !== 0
// 這邊不再檢查年份是否差一,因為可能是閏月。
// || date_name[0] - old_date_name[0] !== 1
) && !continued_month(date_name[1], old_date_name[1]))
tmp = '月名未接續 (' + old_date_name[1] + '→' + date_name[1]
+ ': 相差' + tmp + ')';
else if (date_name[2] === old_date_name[2])
tmp = '前後日期名相同';
else if (date_name[0] !== old_date_name[0]
&& date_name[0] - old_date_name[0] !== 1
// Skip last day of -1 → first day of 1
&& date_name[0] !== 1 && old_date_name[0] !== -1)
tmp = '前後年份不同: ' + old_date_name[0] + '→' + date_name[0];
else
// 若 OK,必得設定 tmp!
tmp = false;
if (tmp) {
error.push(tmp + ': 前一天 ' + old_date_name.join('/')
+ ' ⇨ 隔天 ' + date_name.join('/') + ' ('
+ (new Date(begin_Date)).format(CE_format) + ', '
+ begin_Date + ')');
}
}
old_date_name = date_name;
// 反解: calendar date → Date
tmp = to_Date(date_name[0], date_name[1], date_name[2]);
if (begin_Date - tmp !== 0) {
tmp = '正反解到了不同日期: ' + (new Date(begin_Date)).format(CE_format)
+ ', ' + (begin_Date - epoch) / ONE_DAY_LENGTH_VALUE
+ ' days → ' + date_name.join(',') + ' → '
+ (tmp ? tmp.format(CE_format) : tmp);
error.push(tmp);
if (error.length < 9)
console.error(tmp);
}
}
library_namespace.info((new Date - begin) + ' ms, error '
+ error.length + '/' + error_limit);
if (true || error.length)
return error;
};
}
new_tester.default_options = {
// length of the months
month_days : {
29 : '陰陽曆大月',
30 : '陰陽曆小月'
},
CE_format : {
parser : 'CE',
format : '%Y/%m/%d %HH CE'
},
// 延續的月序,月序未中斷。continued/non-interrupted month serial.
continued_month : function(month, old_month) {
return month === 1 && (old_month === 12 || old_month === 13);
},
// get month serial
// 其他方法: 見 Hindu_Date.test
month_serial : function(date_name) {
var month = date_name[1];
if (isNaN(month)) {
var matched = month.match(/^\D?(\d{1,2})$/);
if (!matched)
throw 'tester: Illegal month name: ' + month;
month = matched[1] | 0;
}
return month;
},
// get 數字序號 (numerical serial).
format : 'serial',
error_limit : 20
};
function continued_month_中曆(month, old_month) {
var matched;
if (typeof old_month === 'string')
return (matched = old_month.match(/^閏(\d{1,2})$/))
&& (matched[1] - month === 1 || month === 1 && matched[1] == 12);
if (typeof month === 'string')
return (matched = month.match(/^閏(\d{1,2})$/))
&& (matched[1] - old_month === 0 || matched[1] == 1
&& old_month === 12);
return month === 1 && old_month === 12;
}
/**
* 提供 to calendar date 之 front-end (wrapper)
*
* @param {Function}calendar_Date
* to calendar date
* @param {Function}[new_year_Date]
* to calendar new year's day
*
* @returns {Function} parser
*/
function _parser(calendar_Date, new_year_Date) {
return function(date, minute_offset, options) {
var period_end = options && options.period_end;
if (!isNaN(date)) {
if (new_year_Date)
// use the new year's day
return new_year_Date(date);
// use year/1/1
// String → Number
date |= 0;
return calendar_Date(period_end ? 1 + date : date, 1, 1);
}
if (date = date.match(/(-?\d{1,4})[\/\-](\d{1,2})(?:[\/\-](\d{1,2}))?/)) {
if (period_end)
date[date[3] ? 3 : 2]++;
return calendar_Date(date[1] | 0, date[2] | 0, date[3] && (date[3] | 0) || 1);
}
};
}
//----------------------------------------------------------------------------------------------------------------------------------------------------------//
// 長曆: 伊斯蘭曆
// گاهشماری هجری قمری
// https://fa.wikipedia.org/wiki/%DA%AF%D8%A7%D9%87%E2%80%8C%D8%B4%D9%85%D8%A7%D8%B1%DB%8C_%D9%87%D8%AC%D8%B1%DB%8C_%D9%82%D9%85%D8%B1%DB%8C
// تقويم هجري
// https://ar.wikipedia.org/wiki/%D8%AA%D9%82%D9%88%D9%8A%D9%85_%D9%87%D8%AC%D8%B1%D9%8A
// Tabular Islamic calendar / lunar Hijri calendar (AH, A.H. = anno hegirae), lunar Hejrī calendar / التقويم الهجري المجدول /
// http://en.wikipedia.org/wiki/Tabular_Islamic_calendar
// 伊斯蘭曆(回回曆)
// 陳垣編的《中西回史日曆》(中華書局1962年修訂重印)。
// 陈氏中西回史日历冬至订误,鲁实先
// There are 11 leap years in a 30 year cycle.
var Tabular_cycle_years = 30 | 0, Tabular_half_cycle = 15 | 0,
// 平年日數。6=(12個月 / 2)
// 每年有12個月。奇數個月有30天,偶數個月有29天,除第12/最後一個月在閏年有30天。
Tabular_common_year_days = (30 + 29) * 6 | 0,
// 每一30年周期內設11閏年。
Tabular_leaps_in_cycle = 11 | 0,
//
Tabular_cycle_days = Tabular_common_year_days * Tabular_cycle_years
+ Tabular_leaps_in_cycle,
// Tabular_leap_count[shift + Tabular_cycle_years]
// = new Array( 30 : [ 各年於30年周期內已累積 intercalary days ] )
Tabular_leap_count = [],
// 各月1日累積日數。
// = [0, 30, 30+29, 30+29+30, ..]
// Tabular_month_days.length = 12
Tabular_month_days = [ 0 ];
(function() {
for (var month = 0, count = 0; month < 12;)
Tabular_month_days.push(count += (month++ % 2 === 0 ? 30 : 29));
// assert: Tabular_common_year_days === Tabular_month_days.pop();
})();
function Tabular_list_leap() {
for (var s = -Tabular_cycle_years; s <= Tabular_cycle_years; s++) {
for (var year = 1, shift = s, leap = []; year <= Tabular_cycle_years; year++)
if ((shift += Tabular_leaps_in_cycle) > Tabular_half_cycle)
shift -= Tabular_cycle_years, leap.push(year);
library_namespace.log(s + ': ' + leap);
}
}
// 0: 2,5,7,10,13,16,18,21,24,26,29
// -3: 2,5,8,10,13,16,19,21,24,27,29
// 1: 2,5,7,10,13,15,18,21,24,26,29
// -5: 2,5,8,11,13,16,19,21,24,27,30
// shift: 小餘, -30–30.
function get_Tabular_leap_count(shift, year_serial) {
if (0 < (shift |= 0))
shift %= Tabular_cycle_years;
else
shift = 0;
// + Tabular_cycle_years: 預防有負數。
if (!((shift + Tabular_cycle_years) in Tabular_leap_count))
// 計算各年於30年周期內已累積 intercalary days。
for (var year = 0, count = 0,
// new Array(Tabular_cycle_years)
intercalary_days_count = Tabular_leap_count[shift + Tabular_cycle_years] = [ 0 ];
//
year < Tabular_cycle_years; year++) {
if ((shift += Tabular_leaps_in_cycle) > Tabular_half_cycle)
shift -= Tabular_cycle_years, count++;
intercalary_days_count.push(count);
}
return Tabular_leap_count[shift + Tabular_cycle_years][year_serial];
}
// Tabular date to Date.
function Tabular_Date(year, month, date, shift) {
return new Date(Tabular_Date.epoch +
// 計算距離 Tabular_Date.epoch 日數。
(Math.floor((year = year < 0 ? year | 0 : year > 0 ? year - 1 : 0)
// ↑ No year 0.
/ Tabular_cycle_years) * Tabular_cycle_days
// 添上閏年數。
+ get_Tabular_leap_count(shift,
// 確認 year >=0。
(year %= Tabular_cycle_years) < 0 ? (year += Tabular_cycle_years) : year)
// 添上整年之日數。
+ year * Tabular_common_year_days
// 添上整月之日數。
+ Tabular_month_days[(month || 1) - 1]
// 添上月初至 date 日數。
+ (date || 1) - 1) * ONE_DAY_LENGTH_VALUE);
}
// 622/7/15 18:0 Tabular begin offset
// 伊斯蘭曆每日以日落時分日。例如 AH 1/1/1 可與公元 622/7/16 互換,
// 但 AH 1/1/1 事實上是從 622/7/15 的日落時算起,一直到 622/7/16 的日落前為止。
// '622/7/16'.to_Date('CE').format(): '622/7/19' === new Date(622, 7 - 1, 19)
Tabular_Date.epoch = String_to_Date('622/7/16', {
parser : 'CE'
}).getTime();
var Tabular_month_name_of_serial = '|محرم|صفر|ربيع الأول|ربيع الثاني|جمادى الأول|جمادى الآخر|رجب |شعبان|رمضان|شوال|ذو القعدة|ذو الحجة'.split('|');
Tabular_Date.month_name = function(month_serial) {
return Tabular_month_name_of_serial[month_serial];
};
Tabular_Date.is_leap = function(year, shift) {
// 轉正。保證變數值非負數。
if ((year %= Tabular_cycle_years) < 1)
year += Tabular_cycle_years;
return get_Tabular_leap_count(shift, year)
- get_Tabular_leap_count(shift, year - 1);
};
// 有更快的方法。
function Date_to_Tabular(date, options) {
var month,
// 距離 Tabular_Date.epoch 的日數。
tmp = (date - Tabular_Date.epoch) / ONE_DAY_LENGTH_VALUE,
//
delta = tmp - (date = Math.floor(tmp)),
// 距離 Tabular_Date.epoch 的30年周期之年數。
year = Math.floor(date / Tabular_cycle_days) * Tabular_cycle_years;
// 本30年周期內之日數。
date %= Tabular_cycle_days;
// 保證 date >=0。
if (date < 0)
date += Tabular_cycle_days;
// month: 本30年周期內之積年數: 0–30。
// 30: 第29年年底。
month = (date / Tabular_common_year_days) | 0;
year += month;
date %= Tabular_common_year_days;
// 不動到原 options。
options = Object.assign({
postfix:' هـ'
},options);
// 求出為本年第幾天之序數。
// 減去累積到第 month 年首日,應該有幾個閏日。
tmp = get_Tabular_leap_count(options.shift, month);
if (date < tmp)
// 退位。
year--, date += Tabular_common_year_days
//
- get_Tabular_leap_count(options.shift, month - 1);
else
date -= tmp;
// 至此確定年序數與求出本年第幾天之序數。
// 這邊的計算法為 Tabular Islamic calendar 特殊設計過,並不普適。
// 理據: 每月日數 >=29 && 末月累積日數 - 29*月數 < 29 (不會 overflow)
// tmp 可能是本月,或是下月累積日數。
tmp = Tabular_month_days[month = (date / 29) | 0];
if (date < tmp
// assert: month === 12: 年內最後一天。
|| month === 12)
// tmp 是下月累積日數。
tmp = Tabular_month_days[--month];
// 本月日數。
date -= tmp;
// 日期序數→日期名。year/month/date index to serial.
// There is no year 0.
if (year >= 0)
year++;
// [ year, month, date, 餘下時間值(單位:日) ]
return _format([ year, month + 1, date + 1, delta ], options,
Tabular_Date.month_name);
}
/*
CeL.run('data.date.calendar');
CeL.Tabular_Date.test(-2e4, 4e6, 4).join('\n') || 'OK';
// "OK"
'624/6/23'.to_Date('CE').to_Tabular({format : 'serial'})
// [2, 12, 30, 0]
CeL.Tabular_Date(3, 7, 1).format('CE')
*/
Tabular_Date.test = new_tester(Date_to_Tabular, Tabular_Date);
_.Tabular_Date = Tabular_Date;
//----------------------------------------------------------------------------------------------------------------------------------------------------------//
// 長曆: הַלּוּחַ הָעִבְרִי / Hebrew calendar / Jewish Calendar / 希伯來曆 / 猶太曆計算
// https://en.wikipedia.org/wiki/Hebrew_calendar
// http://www.stevemorse.org/jcal/rules.htm
// http://www.jewishgen.org/infofiles/m_calint.htm
// Hebrew_month_serial[month_name] = month serial (1–12 or 13)
var Hebrew_month_serial = Object.create(null),
// Hour is divided into 1080 parts called haliq (singular of halaqim)
Hebrew_1_HOUR = 1080 | 0,
// hour length in halaqim
Hebrew_1_DAY = 24 * Hebrew_1_HOUR | 0,
// month length in halaqim
// The Jewish month is defined to be 29 days, 12 hours, 793 halaqim.
Hebrew_1_MONTH = 29 * Hebrew_1_DAY + 12 * Hebrew_1_HOUR + 793 | 0,
// Metonic cycle length in halaqim
// Metonic cycle = 235 months (about 19 years): Hebrew calendar 採十九年七閏法
//Hebrew_1_cycle = (19 * 12 + 7) * Hebrew_1_MONTH | 0,
ONE_HOUR_LENGTH_VALUE = Date.parse('1/1/1 2:0') - Date.parse('1/1/1 1:0'),
// 1 hour of Date / 1 hour of halaqim (Hebrew calendar).
// (length of halaqim) * halaqim_to_Date_ratio = length value of Date
halaqim_to_Date_ratio = ONE_HOUR_LENGTH_VALUE / Hebrew_1_HOUR,
// http://www.stevemorse.org/jcal/rules.htm
// Molad of Tishri in year 1 occurred on Monday at 5hr, 204hq (5hr, 11mn, 20 sc)
// i.e., evening before Monday daytime at 11 min and 20 sec after 11 PM
Hebrew_epoch_halaqim = 5 * Hebrew_1_HOUR + 204 | 0,
// https://en.wikipedia.org/wiki/Molad
// The traditional epoch of the cycle was 5 hours 11 minutes and 20 seconds
// after the mean sunset (considered to be 6 hours before midnight) at the epoch
// of the Hebrew calendar (first eve of Tishrei of Hebrew year 1).
Hebrew_epoch_shift_halaqim = -6 * Hebrew_1_HOUR | 0,
// for reduce error.
Hebrew_epoch_shift = Math.round(Hebrew_epoch_shift_halaqim
* halaqim_to_Date_ratio),
// 1/Tishri/1: Julian -3761/10/7
//
// https://en.wikipedia.org/wiki/Hebrew_calendar
// The Jewish calendar's epoch (reference date), 1 Tishrei AM 1, is equivalent
// to Monday, 7 October 3761 BC/BCE in the proleptic Julian calendar, the
// equivalent tabular date (same daylight period) and is about one year before
// the traditional Jewish date of Creation on 25 Elul AM 1, based upon the Seder
// Olam Rabbah.
//
// http://www.stevemorse.org/jcal/jcal.html
// http://www.fourmilab.ch/documents/calendar/
Hebrew_epoch = String_to_Date('-3761/10/7', {
parser : 'Julian'
}).getTime();
// ---------------------------------------------------------------------------//
// Hebrew to Date
// Hebrew year, month, date
// get_days: get days of year
function Hebrew_Date(year, month, date, get_days) {
// no year 0. year: -1 → 0
if (year < 0 && !Hebrew_Date.year_0)
year++;
var is_leap = Hebrew_Date.is_leap(year),
//
days = isNaN(date) ? 0 : date - 1 | 0,
// days diff (year type)
// add_days = -1 (defective) / 0 (normal) / 1 (complete)
add_days = Hebrew_Date.days_of_year(year) - 354 | 0;
if (add_days > 1)
add_days -= 30;
if (!month)
// month index 0
month = 0;
else if (isNaN(month = Hebrew_Date.month_index(month, is_leap)))
return;
// month: month index (0–11 or 12)
if (month > 2 || month === 2 && add_days > 0) {
// 所有後面的月份皆須加上此 add_days。
days += add_days;
if (is_leap && month > 5)
// subtract the 30 days of leap month Adar I.
month--, days += 30;
}
days += (month >> 1) * (30 + 29);
if (month % 2 === 1)
days += 30;
// days: days from new year day
return get_days ? days : Hebrew_Date.new_year_days(year, days, true);
}
// Are there year 0?
// 警告:除了 Hebrew_Date(), Date_to_Hebrew() 之外,其他函數皆假定有 year 0!
Hebrew_Date.year_0 = false;
//---------------------------------------------------------------------------//
// tools for month name
// https://en.wikipedia.org/wiki/Hebrew_Academy
// Academy name of specified month serial.
// common year: Nisan: 1, Iyyar: 2, .. Adar: 12
// leap year: Nisan: 1, Iyyar: 2, .. (Adar I): 12, (Adar II/Adar/Ve'Adar): 13
// Tishri: 下半年的開始。 http://en.wikipedia.org/wiki/Babylonian_calendar
(Hebrew_Date.month_name_of_serial = "|Nisan|Iyyar|Sivan|Tammuz|Av|Elul|Tishri|Marẖeshvan|Kislev|Tevet|Shvat|Adar"
.split('|'))
//
.forEach(function(month_name, month_serial) {
if (month_serial > 0)
Hebrew_month_serial[month_name.toLowerCase()] = month_serial;
});
// other common names.
// all should be in lower case!
Object.assign(Hebrew_month_serial, {
nissan : 1,
iyar : 2,
siwan : 3,
tamuz : 4,
ab : 5,
tishrei : 7,
heshvan : 8,
marcheshvan : 8,
cheshvan : 8,
'marẖeshwan' : 8,
chisleu : 9,
chislev : 9,
tebeth : 10,
shevat : 11,
shebat : 11,
sebat : 11,
// 'adar 1':12,
// 'adar 2':12,
// Occasionally instead of Adar I and Adar II, "Adar" and "Ve'Adar" are used
// (Ve means 'and' thus: And Adar).
veadar : 13,
"ve'adar" : 13
});
// return Academy name of specified month serial.
// common year: 1: Nisan, 2: Iyyar, .. 12: Adar
// leap year: 1: Nisan, 2: Iyyar, .. 12: Adar, 13: Adar II
Hebrew_Date.month_name = function(month_serial, is_leap_year) {
if (month_serial >= 12 && (month_serial === 13 || is_leap_year))
return month_serial === 12 ? 'Adar I' : 'Adar II';
return Hebrew_Date.month_name_of_serial[month_serial];
};
// return month serial.
// common year: Nisan: 1, Iyyar: 2, .. Adar: 12
// leap year: Nisan: 1, Iyyar: 2, .. (Adar I): 12, (Adar II/Adar/Ve'Adar): 13
Hebrew_Date.month_serial = function(month_name, is_leap_year) {
if (typeof month_name === 'string') {
// normalize month name.
month_name = month_name.trim().toLowerCase();
if (/^adar\s*[1i]$/i.test(month_name))
// Only in Leap years.
return 12;
if (/^adar\s*(2|ii)$/i.test(month_name))
// Only in Leap years.
return 13;
if (month_name === 'adar')
if (is_leap_year)
return 13;
else if (is_leap_year === undefined) {
if (library_namespace.is_debug(2))
library_namespace
.warn('May be 12, but will return 13 for Adar.');
return 13;
}
if (month_name in Hebrew_month_serial)
return Hebrew_month_serial[month_name];
}
library_namespace.error('Hebrew_Date.month_serial: Unknown month name: '
+ month_name);
return month_name;
};
// month: month name or serial
//
// return 0: Tishri, 1: Heshvan, ..
//
// common year: 0–11
// leap year: 0–12
//
// for numeral month name (i.e. month serial):
// Hebrew year begins on 7/1, then month 8, 9, .. 12, 1, 2, .. 6.
//
// common year: 7→0 (Tishri), 8→1, .. 12→5 (Adar),
// 1→6 (Nisan), 2→7, .. 6→11 (Elul)
//
// leap year: 7→0 (Tishri), 8→1, .. 12→5 (Adar I), 13→6 (Adar II),
// 1→7 (Nisan), 2→8, .. 6→12 (Elul)
Hebrew_Date.month_index = function(month, is_leap_year) {
if (isNaN(month))
// month name to serial
month = Hebrew_Date.month_serial(month, is_leap_year);
if (month === (month | 0))
if (month === 13)
// Adar II
return 6;
else if (1 <= month && month <= 12 && (month -= 7) < 0)
// leap 1→-6→7, ..
// common: 1→-6→6, ..
month += is_leap_year ? 13 : 12;
if (Number.isNaN(month))
library_namespace.error('Hebrew_Date.month_index: Unknown month: '
+ month);
return month;
};
//---------------------------------------------------------------------------//
/*
for (y = 0; y < 19; y++)
if (Hebrew_Date.is_leap(y))
console.log(y);
*/
// the years 3, 6, 8, 11, 14, 17, and 19
// are the long (13-month) years of the Metonic cycle
Hebrew_Date.is_leap = function(year) {
year = (7 * (year | 0) + 1) % 19;
// 轉正。保證變數值非負數。
if (year < 0)
year += 19;
return year < 7;
};
/*
累積 leap:
(7 * year - 6) / 19 | 0
Simplify[12*(year - 1) + (7*year - 6)/19]
1/19 (-234 + 235 year)
*/
// 累積 months of new year begins (7/1)
Hebrew_Date.month_count = function(year, month_index) {
return Math.floor((235 * year - 234 | 0) / 19) + (month_index | 0);
};
// halaqim of molad from Hebrew_epoch
// month_index 0: Tishri
// CeL.Hebrew_Date.molad(1,0,true).format('CE')==="-3761/10/6 23:11:20.000"
Hebrew_Date.molad = function(year, month_index, get_Date) {
year = Hebrew_Date.month_count(year, month_index) * Hebrew_1_MONTH
+ Hebrew_epoch_halaqim;
return get_Date ? new Date(Hebrew_epoch + Hebrew_epoch_shift + year
* halaqim_to_Date_ratio) : year;
};
// return [ week_day (0:Sunday, 1:Monday, .. 6),
// hour (0–23 from sunset 18:0 of previous day), halaqim (0–) ]
// @see
// http://www.stevemorse.org/jcal/molad.htm?year=1
Hebrew_Date.molad_date = function(year, month_index) {
var halaqim = Hebrew_Date.molad(year, month_index),
// the first day of 1/1/1 is Monday, index 1.
week_day = (Math.floor(halaqim / Hebrew_1_DAY) + 1) % 7 | 0;
// 轉正。保證變數值非負數。
if (week_day < 0)
week_day += 7;
halaqim %= Hebrew_1_DAY;
if (halaqim < 0)
halaqim += Hebrew_1_DAY;
return [ week_day, halaqim / Hebrew_1_HOUR | 0, halaqim % Hebrew_1_HOUR | 0 ];
};
// cache
var Hebrew_delay_days = [], Hebrew_new_year_days = [];
/*
test:
for (year = 0; year < 1e4; year++)
if (CeL.Hebrew_Date.delay_days(year) === 2
&& (!CeL.Hebrew_Date.delay_days(year - 1) || !CeL.Hebrew_Date
.delay_days(year + 1)))
throw year;
*/
// return 0, 1, 2
Hebrew_Date.delay_days = function(year) {
if ((year |= 0) in Hebrew_delay_days)
return Hebrew_delay_days[year];
var delay_days = Hebrew_Date.molad_date(year),
//
week_day = delay_days[0] | 0, hour = delay_days[1] | 0, halaqim = delay_days[2] | 0;
// phase 1
// http://www.stevemorse.org/jcal/rules.htm
// Computing the beginning of year (Rosh Hashanah):
if (delay_days =
// (2) If molad Tishri occurs at 18 hr (i.e., noon) or later, Tishri 1 must
// be delayed by one day.
hour >= 18
// (3) If molad Tishri in a common year falls on Tuesday at 9 hr 204 hq
// (i.e., 3:11:20 AM) or later, then Tishri 1 is delayed by one day
|| week_day === 2 && (hour > 9 || hour === 9 && halaqim >= 204)
&& !Hebrew_Date.is_leap(year)
// (4) If molad Tishri following a leap year falls on Monday at 15
// hr 589 hq (9:32:43 1/3 AM) or later, Tishri 1 is delayed by one
// day
|| week_day === 1 && (hour > 15 || hour === 15 && halaqim >= 589)
&& Hebrew_Date.is_leap(year - 1)
// default: no delayed
? 1 : 0)
week_day++;
// phase 2
// (1) If molad Tishri occurs on Sunday, Wednesday, or Friday, Tishri 1 must
// be delayed by one day
//
// since the molad Tishri of year 2 falls on a Friday, Tishri 1 of that year
// should have been delayed by rule 1 so that Yom Kippor wouldn't be on the
// day after the Sabbath. However Adam and Eve would not yet have sinned as
// of the start of that year, so there was no predetermined need for them to
// fast on that first Yom Kippor, and the delay rule would not have been
// needed. And if year 2 was not delayed, the Sunday to Friday of creation
// would not have been from 24-29 Elul but rather from 25 Elul to 1 Tishri.
// In other words, Tishri 1 in the year 2 is not the first Sabbath, but
// rather it is the day that Adam and Eve were created.
//
// http://www.stevemorse.org/jcal/jcal.js
// year 3 wants to start on Wed according to its molad, so delaying year
// 3 by the WFS rule would require too many days in year 2, therefore
// WFS must be suspended for year 3 as well
if (3 * week_day % 7 < 3 && 3 < year)
delay_days++;
return Hebrew_delay_days[year] = delay_days | 0;
};
// return days of year's first day.
Hebrew_Date.new_year_Date_original = function(year, days) {
days = new Date(Hebrew_epoch + Hebrew_Date.molad(year)
* halaqim_to_Date_ratio
+ (Hebrew_Date.delay_days(year) + (days | 0))
* ONE_DAY_LENGTH_VALUE);
// set to 0:0 of the day
days.setHours(0, 0, 0, 0);
return days;
};
// calculate days from 1/1/1.
// simplify from Hebrew_Date.new_year_Date_original()
// new_year_days(1) = 0
// new_year_days(2) = 354
// new_year_days(3) = 709
// CeL.Hebrew_Date.new_year_days(1,0,true).format('CE')
Hebrew_Date.new_year_days = function(year, days, get_Date) {
if (!(year in Hebrew_new_year_days))
Hebrew_new_year_days[year] = Math.floor(Hebrew_Date.molad(year)
/ Hebrew_1_DAY)
//
+ Hebrew_Date.delay_days(year) | 0;
year = Hebrew_new_year_days[year] + (days | 0);
return get_Date ? new Date(Hebrew_epoch + year * ONE_DAY_LENGTH_VALUE)
: year;
};
// return days of year's first day.
// Please use Hebrew_Date.new_year_days(year, days, true) instead.
if (false)
Hebrew_Date.new_year_Date = function(year, days) {
return Hebrew_Date.new_year_days(year, days, true);
};
/*
test:
for (var year = 0, d, d2; year <= 1e5; year++) {
d = CeL.Hebrew_Date.days_of_year_original(year);
d2 = CeL.Hebrew_Date.days_of_year(year);
if (d !== d2)
throw year + ': ' + d + '!==' + d2;
// common year has 353 (defective), 354 (normal), or 355 (complete) days
d -= 354;
if (d > 1)
d -= 30;
if (d !== Math.sign(d))
throw year + ': ' + d2 + ' days';
}
console.log('OK');
*/
// day count of year.
Hebrew_Date.days_of_year_original = function(year) {
// common year has 353 (defective), 354 (normal), or 355 (complete) days
// leap year has 383 (defective), 384 (normal), or 385 (complete) days
return (Hebrew_Date.new_year_Date_original(year + 1) - Hebrew_Date
.new_year_Date_original(year))
/ ONE_DAY_LENGTH_VALUE | 0;
};
// day count of year.
// days_of_year(1) = 354
// days_of_year(2) = 354
// days_of_year(3) = 384
// common year has 353 (defective), 354 (normal), or 355 (complete) days
// leap year has 383 (defective), 384 (normal), or 385 (complete) days
Hebrew_Date.days_of_year = function(year) {
return Hebrew_Date.new_year_days(year + 1)
- Hebrew_Date.new_year_days(year);
};
// month days of normal common year
var Hebrew_normal_month_days = [];
(function() {
for (var m = 0; m < 12; m++)
Hebrew_normal_month_days.push(m % 2 === 0 ? 30 : 29);
})();
Hebrew_Date.year_data = function(year) {
var days = Hebrew_Date.days_of_year(year) | 0,
// copy from normal
data = Hebrew_normal_month_days.slice();
data.days = days;
days -= 354;
if (days > 1)
days -= 30, data.leap = true, data.splice(5, 0, 30);
// assert: days = -1 (defective) / 0 (normal) / 1 (complete)
data.add_days = days;
if (days > 0)
data[1]++;
else if (days < 0)
data[2]--;
return Object.assign(data, {
delay_days : Hebrew_Date.delay_days(year),
new_year_days : Hebrew_Date.new_year_days(year)
});
};
//---------------------------------------------------------------------------//
// Date to Hebrew
/*
days = new_year_days + Δ
# 0 <= Δ < 385
new_year_days = days - Δ
Math.floor(Hebrew_Date.molad(year) / Hebrew_1_DAY) + Hebrew_Date.delay_days(year)
= new_year_days
→
Math.floor((Hebrew_Date.month_count(year, month_index) * Hebrew_1_MONTH + Hebrew_epoch_halaqim) / Hebrew_1_DAY) + Hebrew_Date.delay_days(year)
= new_year_days
→
Math.floor((Math.floor((235 * year - 234 | 0) / 19) * Hebrew_1_MONTH + Hebrew_epoch_halaqim) / Hebrew_1_DAY) + Hebrew_Date.delay_days(year)
= new_year_days
→
(((235 * year - 234) / 19 + Δ2) * Hebrew_1_MONTH + Hebrew_epoch_halaqim) / Hebrew_1_DAY + Δ1 + delay_days
= new_year_days
# 0 <= (Δ1, Δ2) <1
# delay_days = 0, 1, 2
→
year
= ((((days - Δ - Δ1 - delay_days) * Hebrew_1_DAY - Hebrew_epoch_halaqim) / Hebrew_1_MONTH - Δ2) * 19 + 234) / 235
<= ((days * Hebrew_1_DAY - Hebrew_epoch_halaqim) / Hebrew_1_MONTH * 19 + 234) / 235
< (days * Hebrew_1_DAY - Hebrew_epoch_halaqim) / Hebrew_1_MONTH * 19 / 235 + 1
test:
var begin = new Date;
for (var year = 0, new_year_days, days = 0; year <= 1e5; year++)
for (new_year_days = CeL.Hebrew_Date.new_year_days(year + 1); days < new_year_days; days++)
if (CeL.Hebrew_Date.year_of_days(days) !== year)
throw 'CeL.Hebrew_Date.year_of_days(' + days + ') = '
+ CeL.Hebrew_Date.year_of_days(days) + ' != ' + year;
console.log('CeL.Hebrew_Date.year_of_days() 使用時間: ' + (new Date - begin) / 1000);
//CeL.Hebrew_Date.year_of_days() 使用時間: 154.131
*/
// return year of the day;
Hebrew_Date.year_of_days = function(days) {
// 即使預先計算參數(coefficient),以加快速度,也不會顯著加快。@ Chrome/36
var year = Math.ceil((days * Hebrew_1_DAY - Hebrew_epoch_halaqim)
/ Hebrew_1_MONTH * 19 / 235) + 1 | 0;
// assert: 最多減兩次。
// 經測試 0–4e6,96% 皆為減一次。
// [ 139779, 3859350, 871 ]
while (days < Hebrew_Date.new_year_days(year))
year--;
return year;
};
/*
d = '-3761/10/7'.to_Date('CE').to_Hebrew();
*/
function Date_to_Hebrew(date, options) {
var tmp, month, days = date - Hebrew_epoch - Hebrew_epoch_shift,
//
hour = days % ONE_DAY_LENGTH_VALUE,
//
year = Hebrew_Date.year_of_days(days = Math.floor(days
/ ONE_DAY_LENGTH_VALUE) | 0),
//
is_leap = Hebrew_Date.is_leap(year),
//
add_days = Hebrew_Date.days_of_year(year) - 354 | 0;
// 轉正。保證變數值非負數。
if (hour < 0)
hour += ONE_DAY_LENGTH_VALUE;
days -= Hebrew_Date.new_year_days(year);
// assert: days: days from new year day
if (add_days > 1)
// assert: is_leap === true
add_days -= 30;
// assert: add_days = -1 (defective) / 0 (normal) / 1 (complete)
// 將 days 模擬成 normal common year.
// 因此需要作相應的處理:
// 從前面的日期處理到後面的,
// 自開始被影響,與 normal common year 不同的那天起將之改成與 normal common year 相同。
// days → month index / days index of month
if (add_days !== 0)
if (add_days === 1) {
// 30 + 29: complete year 開始被影響的一日。
if (days === 30 + 29)
// 因為 normal common year 沒有辦法表現 8/30,須特別處理 8/30。
month = 1, days = 29, tmp = true;
else if (days > 30 + 29)
days--;
} else if (days >= 30 + 29 + 29)
// 30 + 29 + 29: defective year 開始被影響的一日。
// assert: add_days === -1
days++;
if (!tmp) {
// is_leap 還會用到,因此將 tmp 當作暫用值。
// 3 * (30 + 29): leap year 開始被影響的一日。
if (tmp = is_leap && days >= 3 * (30 + 29))
days -= 30;
// 計算有幾組 (30 + 29) 月份。
month = days / (30 + 29) | 0;
// 計算 date in month。
days -= month * (30 + 29);
// 每組 (30 + 29) 月份有 2個月。
month <<= 1;
// normal common year 每組 (30 + 29) 月份,首月 30日。
if (days >= 30)
month++, days -= 30;
if (tmp)
// 加上 leap month.
month++;
}
// 日期序數→日期名。year/month/date index to serial.
// index 0 → serial 7
month += 7;
// add_days: months of the year.
tmp = is_leap ? 13 : 12;
if (month > tmp)
month -= tmp;
if (year <= 0 && !Hebrew_Date.year_0)
// year: 0 → -1
--year;
// 前置處理。
if (!library_namespace.is_Object(options))
if (options === true)
options = {
// month serial to name
month_name : true
};
else
options = Object.create(null);
return _format([ year, month, days + 1,
// hour
Math.floor(hour / ONE_HOUR_LENGTH_VALUE) | 0,
// halaqim
(hour % ONE_HOUR_LENGTH_VALUE) / halaqim_to_Date_ratio ], options,
Hebrew_Date.month_name, is_leap);
}
/*
CeL.Hebrew_Date.test(-2e4, 4e6, 4).join('\n') || 'OK';
// "OK"
'-3762/9/18'.to_Date('CE').to_Hebrew({format : 'serial'})
// -1/6/29
CeL.Hebrew_Date(3, 7, 1).format('CE')
*/
Hebrew_Date.test = new_tester(Date_to_Hebrew, Hebrew_Date, {
epoch : Hebrew_epoch
});
_.Hebrew_Date = Hebrew_Date;
//----------------------------------------------------------------------------------------------------------------------------------------------------------//
// 長曆: Mesoamerican Long Count calendar / 馬雅長紀曆
// <a href="https://en.wikipedia.org/wiki/Mesoamerican_Long_Count_calendar" accessdate="2014/4/28 22:15" title="Mesoamerican Long Count calendar">中美洲長紀曆</a>
// GMT correlation: starting-point is equivalent to August 11, 3114 BCE in the proleptic Gregorian calendar
// https://en.wikipedia.org/wiki/Template:Maya_Calendar
// GMT 584283
// GMT+2 584285
// Thompson (Lounsbury) 584,285
// 注意:據 mayaman@ptt 言,<q>目前比較流行的是GMT+2 如果你要統治者紀年的話</q>。
Maya_Date.epoch = (new Date(-3114 + 1, 8 - 1, 11/* + 2*/)).getTime();
// Era Base date, the date of creation is expressed as 13.13.13.13.13.13.13.13.13.13.13.13.13.13.13.13.13.13.13.13.0.0.0.0 4 Ajaw 8 Kumk'u
/*
// get offset:
// 4 Ajaw → 3/13, 19/20
for (i = 0, d = 3, l = 20; i < l; i++, d += 13)
if (d % l === 19)
throw d;
// 159
*/
var
Tzolkin_day_offset = 159,
Tzolkin_day_period = 13 * 20,
/*
// get offset:
// 8 Kumk'u → 348/(20 * 18 + 5)
365 - 17 = 348
*/
Haab_day_offset = 348, Haab_day_period = 20 * 18 + 5;
function Maya_Date(date, minute_offset, options) {
if (typeof date === 'string')
date = date.split(/[,.]/);
else if (!Array.isArray(date))
return new Date(NaN);
var days = 0, length = date.length - 1, i = 0,
// e.g., 8.19.15.3.4 1 K'an 2 K'ayab'
matched = date[length].match(/^(\d{1,2})\s/);
if (matched)
date[length] = matched[1] | 0;
if (matched = date[0].match(/\s(\d{1,2})$/))
date[0] = matched[1] | 0;
length--;
while (i < length)
days = days * 20 + (date[i++] | 0);
days = (days * 18 + (date[i] | 0)) * 20 + (date[++i] | 0);
if (options && options.period_end)
days++;
return new Date(days * ONE_DAY_LENGTH_VALUE + Maya_Date.epoch);
}
Maya_Date.days = function(date) {
return Math.floor((date - Maya_Date.epoch) / ONE_DAY_LENGTH_VALUE);
};
Maya_Date.to_Long_Count = function(date, get_Array) {
var days = Maya_Date.days(date),
// Mesoamerican Long Count calendar.
Long_Count;
if (!Number.isFinite(days))
// NaN
return;
if (days <= 0)
// give a 13.0.0.0.0
// but it should be:
// 13.13.13.13.13.13.13.13.13.13.13.13.13.13.13.13.13.13.13.13.0.0.0.0
days += 13 * 20 * 20 * 18 * 20;
Long_Count = [ days % 20 ];
days = Math.floor(days / 20);
Long_Count.unshift(days % 18);
days = Math.floor(days / 18) | 0;
while (days > 0 || Long_Count.length < 5) {
Long_Count.unshift(days % 20);
days = days / 20 | 0;
}
return get_Array ? Long_Count : Long_Count.join('.');
};
Maya_Date.Tzolkin_day_name = "Imix'|Ik'|Ak'b'al|K'an|Chikchan|Kimi|Manik'|Lamat|Muluk|Ok|Chuwen|Eb'|B'en|Ix|Men|Kib'|Kab'an|Etz'nab'|Kawak|Ajaw"
.split('|');
// <a href="https://en.wikipedia.org/wiki/Tzolk%27in" accessdate="2014/4/30
// 18:56">Tzolk'in</a>
// type = 1: {Array} [ 13-day cycle index, 20-day cycle index ] (index: start
// from 0),
//
// 2: {Array} [ 13-day cycle name, 20-day cycle ordinal/serial: start from 1,
// 20-day cycle name ],
//
// 3: {Array} [ 13-day cycle name, 20-day cycle name ],
// others (default): {String} (13-day cycle name) (20-day cycle name)
Maya_Date.to_Tzolkin = function(date, type) {
var days = Maya_Date.days(date) + Tzolkin_day_offset;
// 轉正。保證變數值非負數。
if (days < 0)
days = days % Tzolkin_day_period + Tzolkin_day_period;
// 20: Maya_Date.Tzolkin_day_name.length
days = [ days % 13, days % 20 ];
if (type === 1)
return days;
days[0]++;
var day_name = Maya_Date.Tzolkin_day_name[days[1]];
if (type === 2) {
days[1]++;
days[2] = day_name;
} else if (type === 3)
days[1] = day_name;
else
// 先日子,再月份。
days = days[0] + ' ' + day_name;
return days;
};
Maya_Date.Haab_month_name = "Pop|Wo'|Sip|Sotz'|Sek|Xul|Yaxk'in'|Mol|Ch'en|Yax|Sak'|Keh|Mak|K'ank'in|Muwan'|Pax|K'ayab|Kumk'u|Wayeb'"
.split('|');
// <a href="https://en.wikipedia.org/wiki/Haab%27" accessdate="2014/4/30
// 18:56">Haab'</a>
// type = 1: {Array} [ date index, month index ] (index: start from 0),
//
// 2: {Array} [ date ordinal/serial: start from 1, month ordinal/serial: start
// from 1, date name, month name ],
//
// 3: {Array} [ date name, month name ],
// others (default): {String} (date name) (month name)
Maya_Date.to_Haab = function(date, type) {
var days = (Maya_Date.days(date) + Haab_day_offset) % Haab_day_period;
// 轉正。保證變數值非負數。
if (days < 0)
days += Haab_day_period;
// 20 days in a month.
days = [ days % 20, days / 20 | 0 ];
if (type === 1)
return days;
// Day numbers began with a glyph translated as the "seating of" a named
// month, which is usually regarded as day 0 of that month, although a
// minority treat it as day 20 of the month preceding the named month. In
// the latter case, the seating of Pop is day 5 of Wayeb'. For the majority,
// the first day of the year was Seating Pop. This was followed by 1 Pop, 2
// Pop ... 19 Pop, Seating Wo, 1 Wo and so on.
days[2] = days[0] === 0 ? 'Seating' : days[0];
days[3] = Maya_Date.Haab_month_name[days[1]];
if (type === 2)
days[1]++;
else {
days.splice(0, 2);
if (type !== 3)
// 先日子,再月份。
days = days.join(' ');
}
return days;
};
_.Maya_Date = Maya_Date;
//----------------------------------------------------------------------------------------------------------------------------------------------------------//
// 長曆: 西雙版納傣曆計算。傣历 / Dai Calendar
// 適用範圍: 傣曆 714年(1352/3/28–)至 3190年期間內。
/*
基本上按张公瑾:《西双版纳傣文〈历法星卜要略〉历法部分译注》、《傣历中的纪元纪时法》計算公式推算,加上過去暦書多有出入,因此與實暦恐有一兩天差距。
《中国天文学史文集 第三集》
http://blog.sina.com.cn/s/blog_4131e58f0101fikx.html
傣曆和農曆一樣,用干支紀年和紀日。傣曆干支約於東漢時由漢地傳入,使用年代早於紀元紀時的方法。不過傣族十二地支所代表的對象和漢族不完全相同,如「子」不以表鼠而代表大象,「辰」不代表龍,而代表蛟或大蛇。
[张公瑾,陈久金] 傣历中的干支及其与汉历的关系 (傣曆中的干支及其與漢曆的關係, 《中央民族学院学报》1977年第04期)
值得注意的是, 傣历中称干支日为“腕乃” 或‘婉傣” , 意思是“ 里面的日子” 或“傣族的日子” , 而一周一匕日的周日, 明显地是从外面传进来的, 则称为“腕诺” 或,’m 命” , 即“外面的日子· 或“ 你的日子’, , 两者你我相对, 内外有8lJ, 是很清楚的。很明显, 傣历甲的干支纪年与纪日是从汉历中吸收过来的, 而且已经成了傣历中不可分害少的组成部分。在傣文的两本最基本的推算历法书‘苏定》和《苏力牙》中, 干支纪年与纪日的名称冠全书之首, 可见汉历成份在傣历中的重要性。
《中央民族學院學報》 1979年03期
傣曆中的紀元紀時法
張公瑾
傣曆中的紀元紀時法,與公曆的紀時法相近似,即以某一個時間為傣曆紀元開始累計的時間,以後就順此按年月日往下記,至今年(1979年)10月1日(農曆己未年八月十一)為傣曆1341年12月月出11日,這是一種情況。
還有一種情況是:公元1979年10月1日又是傣曆紀元的第1341年、傣曆紀元的第16592月,並是傣曆紀元的第489982日。對這種年月日的累計數,現譯稱為傣曆紀元年數、紀元積月數和紀元積日數。
TODO:
有極少例外,如1190年未閏(八月滿月),而1191年閏。
*/
/*
year:
傣曆紀元年數。
應可處理元旦,空日,除夕,閏月,後六月,後七月等。
Dai_Date(紀元積日數)
Dai_Date(紀元年數, 特殊日期)
特殊日期: 元旦/除夕/空1/空2
Dai_Date(紀元年數, 0, 當年日序數)
Dai_Date(紀元年數, 月, 日)
月: 1–12/閏/後6/後7
元旦:
Dai_Date(year, 0)
Dai_Date(year, '元旦')
當年日序 n:
Dai_Date(year, 0, n)
空日(當年元旦之前的):
Dai_Date(year, '空1日')
Dai_Date(year, '空2日')
Dai_Date(year, 0, -1)
Dai_Date(year, 0, -2)
除夕(當年元旦之前的):
Dai_Date(year, '除夕')
閏月:
Dai_Date(year, '閏9', date)
Dai_Date(year, '雙9', date)
Dai_Date(year, '閏', date)
後六月:
Dai_Date(year, '後6', date)
後七月:
Dai_Date(year, '後7', date)
注意:由於傣曆元旦不固定在某月某日,因此同一年可能出現相同月分與日期的日子。例如傣曆1376年(公元2014年)就有兩個六月下五日。
為了維持獨一性,此處以"後六月"稱第二次出現的六月同日。
*/
function Dai_Date(year, month, date, get_days) {
if (isNaN(year = Dai_Date.to_valid_year(year)))
return get_days ? NaN : new Date(NaN);
var days = typeof date === 'string'
&& (date = date.trim()).match(/^(\D*)(\d+)/), is_leap;
// 處理如「六月下一日」或「六月月下一日」即傣曆6月16日。
if (days) {
date = days[2] | 0;
if (/月?上/.test(days[1]))
date += 15;
} else
date |= 0;
if (typeof month === 'string')
if (/^[閏雙後][9九]?月?$/.test(month))
month = 9, is_leap = true;
else if (days = month.match(/^後([67])/))
month = days[1];
if (isNaN(month) || month < 1 || 12 < month) {
// 確定元旦之前的空日數目。
days = Dai_Date.vacant_days(year - 1);
switch (month) {
case '空2日':
// 若有空2日,其必為元旦前一日。
date--;
case '空日':
case '空1日':
date -= days;
case '除夕':
date -= days + 1;
}
// 當作當年日序。
days = Dai_Date.new_year_days(year) + date | 0;
} else {
// 將 (month) 轉成月序:
// 6月 → 0
// 7月 → 1
// ...
// 12月 → 6
// 1月 → 7
if ((month -= 6) < 0
// 後6月, 後7月
|| days)
month += 12;
// 處理應為年末之6月, 7月的情況。
if (month < 2 && 0 < date
// 七月: 7/1 → 6/30, 7/2 → 6/31..
&& (month === 0 ? date : 29 + date) <
//
Dai_Date.new_year_date_serial(year))
month += 12;
days = Dai_Date.days_6_1(year) + date - 1
//
+ (month >> 1) * (29 + 30) | 0;
if (month % 2 === 1)
days += 29;
if ((month > 3 || month === 3 && is_leap)
// 處理閏月。
&& Dai_Date.is_leap(year))
days += 30;
if (month > 2 && Dai_Date.is_full8(year))
days++;
}
return get_days ? days : new Date(Dai_Date.epoch + days
* ONE_DAY_LENGTH_VALUE);
}
// 適用範圍: 傣曆 0–103295 年
Dai_Date.to_valid_year = function(year, ignore_range) {
if (false && year < 0)
library_namespace.warn('Dai_Date.to_valid_year: 公式不適用於過小之年分:' + year);
return !isNaN(year) && (ignore_range ||
// 一般情況
// -1e2 < year && year < 103296
// from new_year_date_serial()
0 <= year && (year < 2 || 714 <= year && year <= 3190)
//
) && year == (year | 0) ? year | 0 : NaN;
};
// 傣曆採十九年七閏法,平年有12個月,閏年有13個月。閏月固定在9月,所以閏年又稱為「雙九月」年
// 閏9月, 閏九月。
// 適用範圍: 傣曆 0– 年
Dai_Date.is_leap = function(year) {
// 傣曆零年當年九月置閏月。
return year == 0 ||
// 攝 = (year + 1) % 19;
(((7 * year | 0) - 6) % 19 + 19) % 19 < 7;
};
// 當年日數。365 or 366.
Dai_Date.year_days = function(year) {
return Dai_Date.new_year_days(year + 1) - Dai_Date.new_year_days(year);
};
// 當年空日數目。1 or 2.
// 注意:這邊之年分,指的是當年除夕後,即明年(隔年)元旦之前的空日數目。與 Dai_Date() 不同!
// e.g., Dai_Date.vacant_days(100) 指的是傣曆100年除夕後,即傣曆101年元旦之前的空日數目。
// 依 Dai_Date.date_of_days() 的做法,空日本身會被算在前一年內。
Dai_Date.vacant_days = function(year) {
// 傣曆潑水節末日之元旦(新年的第一天)與隔年元旦間,一般為365日(有「宛腦」一天)或366日(有「宛腦」兩天)。
return Dai_Date.year_days(year) - 364;
};
/*
傣历算法剖析
原法@曆法星卜要略, 傣曆中的紀元紀時法:
x := year + 1
y := Floor[(year + 4)/9]
z := Floor[(year - y)/3]
r := Floor[(x - z)/2]
R := year - r + 49049
S := Floor[(36525875 year + R)/100000]
d := S + 1
Simplify[d]
1 + Floor[(
49049 + 36525876 year -
Floor[1/2 (1 + year - Floor[1/3 (year - Floor[(4 + year)/9])])])/
100000]
簡化法:
x := year + 1
y := ((year + 4)/9)
z := ((year - y)/3)
r := ((x - z)/2)
R := year - r + 49049
S := ((36525875 year + R)/100000)
d := S + 1
Simplify[d]
(1609723 + 394479457 year)/1080000
// test 簡化法 @ Javascript:
for (var year = -1000000, days; year <= 1000000; year++) {
if (CeL.Dai_Date.new_year_days(year) !== CeL.Dai_Date
.new_year_days_original(year))
console.error('new_year_days: ' + year);
var days = CeL.Dai_Date.new_year_days(year);
if (CeL.Dai_Date.year_of_days(days) !== year
|| CeL.Dai_Date.year_of_days(days - 1) !== year - 1)
console.error('year_of_days: ' + year);
}
// get:
-976704
-803518
-630332
-523297
-350111
-176925
-69890
103296
276482
449668
556703
729889
903075
*/
// 元旦紀元積日數, accumulated days
// 原法@曆法星卜要略:
Dai_Date.new_year_days_original = function(year) {
return 1 + Math
.floor((49049 + 36525876 * year - Math.floor((1 + year - Math
.floor((year - Math.floor((4 + year) / 9)) / 3)) / 2)) / 100000);
};
// 元旦紀元積日數, accumulated days
// 簡化法:適用於 -69889–103295 年
Dai_Date.new_year_days = function(year, get_remainder) {
// 防止 overflow。但效果相同。
// var v = 365 * year + 1 + (279457 * year + 529723) / 1080000,
var v = (394479457 * year + 1609723) / 1080000 | 0,
//
f = Math.floor(v);
// 餘數
return get_remainder ? v - f : f;
};
// 簡化法:適用於 -3738–1000000 年
Dai_Date.year_of_days = function(days) {
return Math.floor((1080000 * (days + 1) - 1609723) / 394479457) | 0;
};
// 紀元積月數, accumulated month
/*
原法@傣曆中的紀元紀時法:
day = 元旦紀元積日數
b := 11 day + 633
c := Floor[(day + 7368)/8878]
d := Floor[(b - c)/692]
dd := day + d
e := Floor[dd/30]
f := Mod[dd, 30]
Simplify[e]
Simplify[f]
e:
Floor[1/30 (day +
Floor[1/692 (633 + 11 day - Floor[(7368 + day)/8878])])]
f:
Mod[day + Floor[1/692 (633 + 11 day - Floor[(7368 + day)/8878])], 30]
*/
// cache
var new_year_date_serial = [ 30 ];
// 元旦之當月日序基數
// d = 30–35: 7/(d-29)
// others: 6/d
Dai_Date.new_year_date_serial = function(year, days, ignore_year_limit) {
if (year in new_year_date_serial)
return new_year_date_serial[year];
if (isNaN(year = Dai_Date.to_valid_year(year, ignore_year_limit)))
return NaN;
// days: 元旦紀元積日數。
if (isNaN(days))
days = Dai_Date.new_year_days(year) | 0;
else if (days < 0)
library_namespace.warn('Dai_Date.new_year_date_serial: 輸入負數日數 [' + days + ']!');
// 參考用元旦之當月日序基數:常常須作調整。
var date = (days +
// 小月補足日數
Math.floor((633 + 11 * days - Math.floor((7368 + days) / 8878)) / 692)
// (date / 30 | 0) 是元旦所在月的紀元積月數
) % 30 | 0;
// 年初之6/1累積日數
var days_diff
// 平年年初累積日數
= year * 354
// 閏月年初累積日數 = 30 * (年初累積閏月數 (7r-6)/19+1=(7r+13)/19)
+ 30 * (((7 * (year - 1) - 6) / 19) + 2 | 0)
// 八月滿月年初累積日數。.194: 經手動測試,誤差=0 or 1日@部分0–1400年
+ (.194 * year | 0)
// 為傣曆紀元始於 7/1,而非 6/1;以及 date 由 6/1 而非 6/0 起始而調整。
- 30
// 至上方為年初之6/1累積日數,因此需要再加上元旦之當月日序基數,才是元旦紀元積日數。
+ date
// 計算兩者差距。
- days | 0;
// assert: -31 < days_diff < 2
// for (var i = 0, j, y; i < 1200; i++) if ((j = CeL.Dai_Date.new_year_date_serial(i)) > 1 || j < -31) y = i;
// 599
// for (var i = 1200, j, y; i < 103296; i++) if ((j = CeL.Dai_Date.new_year_date_serial(i)) > 1 || j < -31) throw i;
// 3191
// return days_diff;
if (false && library_namespace.is_debug(3)
&& !(-31 < days_diff && days_diff < 2))
library_namespace.warn('days_diff of ' + year + ': ' + days_diff);
// 判斷 date 在 6月 或 7月:選擇與應有日數差距較小的。
if (Math.abs(days_diff) > Math.abs(days_diff + 30))
// 七月. 7/date0 → 6/30, 7/date1 → 6/31..
date += 30;
// 微調:當前後年 6/1 間不是指定的日數時,應當前後移動一兩日。但據調查發現,除前一年是雙九月暨八月滿月外,毋須微調。
// 六月出一日與隔年六月出一日間,平年354天(八月小月)或355天(八月滿月),雙九月之年384天。
if (Dai_Date.is_leap(year - 1)) {
var last_days = D