﻿/*===================================================================================================
 > Lunar calendar by Ho Ngoc Duc (HoNgocDuc):
   data range 1800-2199 , 4 centuries. data out of range included as attached files
   https://www.informatik.uni-leipzig.de/~duc/amlich/JavaScript/
   https://www.informatik.uni-leipzig.de/~duc/amlich/


  > information from Ho Ngoc Duc javascript:
   Copyright 2004 Ho Ngoc Duc [http://come.to/duc]. All Rights Reserved.
   Permission to use, copy, modify, and redistribute this software and its
   documentation for personal, non-commercial use is hereby granted provided that
   this copyright notice appears in all copies.
 

  Jan24 vnav:
   lunarL0, lunarL1, lunarL2, lunarL3, lunarL4, lunarL5, lunarL6,lunarL7 are variables declared in the master script to stores lunar calendar information/
   IMHO, this lunar calendar script can converts any Gregorian solar date into Vietnamese lunar date and vice versa between 1200-2199. 
   file Encoded : UTF8 with BOM.	

  USE these scripts at your OWN RISK.
 =================================================================================================== */
 

//var ABOUT = "\u00C2m l\u1ECBch Vi\u1EC7t Nam - Version 0.8"+"\n\u00A9 2004 H\u1ED3 Ng\u1ECDc \u0110\u1EE9c [http://come.to/duc]";
var ABOUT = "\u00C2m l\u1ECBch Vi\u1EC7t Nam - Version 1.0"+"\n\u00A9 2004 H\u1ED3 Ng\u1ECDc \u0110\u1EE9c";

var PI = Math.PI;


//var FIRST_DAY = jdn(31, 1, 1200);
var FIRST_DAY = jdn(25, 1, 1800); // Tet lunar 1800
var LAST_DAY = jdn(31, 12, 2199);
var today = new Date();



/** Each Gregorian year will be ecrypted with 3 bytes hexadecimal (24 bits): 
* ie. 0x226da2 (hexadecimal) ~ 00100010 01101101 10100010 (bit) 
* -First 7 bits (hight): number of days between Western New Year and Lunar New Year days (1/1).
* -1 bit next: is 0 if the leap month has 29 days, is 1 if 30 days.
* -12 bits next: represents number of days for each 12 months (same as bit 8). (really? vnav)
* -Last 4 bits (low): is 0 if the lunar year is not a leap one, otherwise, it represents the leap month (ex: 4 means Apr).

* The following tables/arrays contain  Gregorian year-codes for centuries and been PRE-computed by Ho Ngoc Duc.
* please contact Ho Ngoc Duc for other centuries data. vnav.
*/

var TK13 = new Array(
	0x226da2, 0x4695d0, 0x3349dc, 0x5849b0, 0x42a4b0, 0x2aaab8, 0x506a50, 0x3ab540, 0x24bb44, 0x48ab6, 
	0x3495b0, 0x205372, 0x464970, 0x2e64f9, 0x5454b0, 0x3e6a50, 0x296c57, 0x4c5ac0, 0x36ab60, 0x2386e, 
	0x4892e0, 0x30c97c, 0x56c960, 0x40d4a0, 0x2adaa8, 0x4eb550, 0x3a56a0, 0x24adb5, 0x4c25d0, 0x3492e, 
	0x1ed2b2, 0x44a950, 0x2ed4d9, 0x52b2a0, 0x3cb550, 0x285757, 0x4e4da0, 0x36a5b0, 0x225574, 0x4852b, 
	0x33a93c, 0x566930, 0x406aa0, 0x2aada8, 0x50ab50, 0x3a4b60, 0x24aae4, 0x4aa570, 0x365270, 0x1f526, 
	0x42e530, 0x2e6cba, 0x5456a0, 0x3c5b50, 0x294ad6, 0x4e4ae0, 0x38a4e0, 0x20d4d4, 0x46d260, 0x30d53, 
	0x56b520, 0x3eb6a0, 0x2b56a9, 0x505570, 0x3c49d0, 0x25a1b5, 0x4aa4b0, 0x34aa50, 0x1eea51, 0x42b52, 
	0x2cb5aa, 0x52ab60, 0x3e95b0, 0x284b76, 0x4e4970, 0x3864b0, 0x22b4b3, 0x466a50, 0x306b3b, 0x565ac, 
	0x40ab60, 0x2b2ad8, 0x5049e0, 0x3aa4d0, 0x24d4b5, 0x48b250, 0x32b520, 0x1cf522, 0x42b5a0, 0x2c95e, 
	0x5295b0, 0x3e49b0, 0x28a576, 0x4ca4b0, 0x36aa50, 0x20ba54, 0x466d40, 0x2ead6c, 0x54ab60, 0x409370
); /* Years 1200-1299 */

var TK14 = new Array(
	0x2d49b8, 0x504970, 0x3a64b0, 0x246ca5, 0x48da50, 0x325aa0, 0x1cd6c1, 0x42a6e0, 0x2e92fb, 0x5292e, 
	0x3cc960, 0x26d557, 0x4cd4a0, 0x34d550, 0x215553, 0x4656a0, 0x30a6d0, 0x1aa5d1, 0x4092b0, 0x2aa5b, 
	0x50a950, 0x38b2a0, 0x23b2a5, 0x48ad50, 0x344da0, 0x1ccba1, 0x42a570, 0x2e52f9, 0x545270, 0x3c693, 
	0x266b37, 0x4c6aa0, 0x36ab50, 0x205753, 0x464b60, 0x30a67c, 0x56a2e0, 0x3ed160, 0x28e968, 0x4ed4a, 
	0x38daa0, 0x225ea5, 0x4856d0, 0x344ae0, 0x1f85d2, 0x42a2d0, 0x2cd17a, 0x52aa50, 0x3cb520, 0x24d74, 
	0x4aada0, 0x3655d0, 0x2253b3, 0x4645b0, 0x30a2b0, 0x1ba2b1, 0x40aa50, 0x28b559, 0x4e6b20, 0x38ad6, 
	0x255365, 0x489370, 0x344570, 0x1ea573, 0x4452b0, 0x2c6a6a, 0x50d950, 0x3c5aa0, 0x27aac7, 0x4aa6e, 
	0x3652e0, 0x20cae3, 0x46a560, 0x2ed2bb, 0x54d2a0, 0x3ed550, 0x2a5ad9, 0x4e56a0, 0x38a6d0, 0x2455d, 
	0x4a52b0, 0x32a8d0, 0x1ce552, 0x42b2a0, 0x2cb56a, 0x50ad50, 0x3c4da0, 0x26a7a6, 0x4ca570, 0x3651b, 
	0x21a174, 0x466530, 0x316a9c, 0x545aa0, 0x3eab50, 0x2a2bd9, 0x502b60, 0x38a370, 0x2452e5, 0x48d160
); /* Years 1300-1399 */

var TK15 = new Array(
	0x32e4b0, 0x1c7523, 0x40daa0, 0x2d5b4b, 0x5256d0, 0x3c2ae0, 0x26a3d7, 0x4ca2d0, 0x36d150, 0x1ed95, 
	0x44b520, 0x2eb69c, 0x54ada0, 0x3e55d0, 0x2b25b9, 0x5045b0, 0x3aa2b0, 0x22aab5, 0x48a950, 0x32b52, 
	0x1ceaa1, 0x40ab60, 0x2c55bc, 0x524b70, 0x3e4570, 0x265377, 0x4c52b0, 0x366950, 0x216954, 0x445aa, 
	0x2eab5c, 0x54a6e0, 0x404ae0, 0x28a5e8, 0x4ea560, 0x38d2a0, 0x22eaa6, 0x46d550, 0x3256a0, 0x1d95a, 
	0x4295d0, 0x2c4afb, 0x5249b0, 0x3ca4d0, 0x26d2d7, 0x4ab2a0, 0x34b550, 0x205d54, 0x462da0, 0x2e95b, 
	0x1b1571, 0x4049b0, 0x2aa4f9, 0x4e64b0, 0x386a90, 0x22aea6, 0x486b50, 0x322b60, 0x1caae2, 0x42937, 
	0x2f496b, 0x50c960, 0x3ae4d0, 0x266b27, 0x4adaa0, 0x345ad0, 0x2036d3, 0x4626e0, 0x3092e0, 0x18d2d, 
	0x3ec950, 0x28d4d9, 0x4eb4a0, 0x36b690, 0x2355a6, 0x4855b0, 0x3425d0, 0x1ca5b2, 0x4292b0, 0x2ca97, 
	0x526950, 0x3a74a0, 0x24b5a8, 0x4aab60, 0x3653b0, 0x202b74, 0x462570, 0x3052b0, 0x1ad2b1, 0x3e695, 
	0x286ad9, 0x4e5aa0, 0x38ab50, 0x224ed5, 0x484ae0, 0x32a370, 0x1f44e3, 0x40d2a0, 0x2bd94b, 0x50b550
); /* Years 1400-1499 */

var TK16 = new Array(
	0x3c56a0, 0x2497a7, 0x4a95d0, 0x364ae0, 0x20a9b4, 0x44a4d0, 0x2ed250, 0x19aaa1, 0x3eb550, 0x2856d, 
	0x4e2da0, 0x3895b0, 0x244b75, 0x484970, 0x32a4b0, 0x1cb4b4, 0x426a90, 0x2aad5c, 0x505b50, 0x3c2b6, 
	0x2695e8, 0x4a92f0, 0x364970, 0x206964, 0x44d4a0, 0x2cea5c, 0x52d690, 0x3e56d0, 0x2b2b5a, 0x4e26e, 
	0x3892e0, 0x22cad6, 0x48c950, 0x30d4a0, 0x1af4a2, 0x40b590, 0x2c56dc, 0x5055b0, 0x3c25d0, 0x2693b, 
	0x4c92b0, 0x34a950, 0x1fb155, 0x446ca0, 0x2ead50, 0x192b61, 0x3e4bb0, 0x2a25f9, 0x502570, 0x3852b, 
	0x22aaa6, 0x46e950, 0x326aa0, 0x1abaa3, 0x40ab50, 0x2c4b7b, 0x524ae0, 0x3aa570, 0x2652d7, 0x4ad26, 
	0x34d950, 0x1e5d55, 0x4456a0, 0x2e96d0, 0x1a55d2, 0x3e4ae0, 0x28a4fa, 0x4ea4d0, 0x38d250, 0x20d69, 
	0x46b550, 0x3235a0, 0x1caba2, 0x4095b0, 0x2d49bc, 0x524970, 0x3ca4b0, 0x24b2b8, 0x4a6a50, 0x346d4, 
	0x1fab54, 0x442ba0, 0x2e9370, 0x2e52f2, 0x544970, 0x3c64e9, 0x60d4a0, 0x4aea50, 0x373aa6, 0x5a56d, 
	0x462b60, 0x3185e3, 0x5692e0, 0x3ec97b, 0x64a950, 0x4ed4a0, 0x38daa8, 0x5cb550, 0x4856b0, 0x342da4
); /* Years 1500-1599 */

var TK17 = new Array(
	0x58a5d0, 0x4292d0, 0x2cd2b2, 0x52a950, 0x3cb4d9, 0x606aa0, 0x4aad50, 0x365756, 0x5c4ba0, 0x44a5b, 
	0x314573, 0x5652b0, 0x41a94b, 0x62e950, 0x4e6aa0, 0x38ada8, 0x5e9b50, 0x484b60, 0x32aae4, 0x58a4f, 
	0x445260, 0x2bd262, 0x50d550, 0x3d5a9a, 0x6256a0, 0x4a96d0, 0x3749d6, 0x5c49e0, 0x46a4d0, 0x2ed4d, 
	0x54d250, 0x3ed53b, 0x64b540, 0x4cb5a0, 0x3995a8, 0x5e95b0, 0x4a49b0, 0x32a974, 0x58a4b0, 0x42aa5, 
	0x2cea51, 0x506d40, 0x3aadbb, 0x622b60, 0x4c9370, 0x364af6, 0x5c4970, 0x4664b0, 0x3074a3, 0x52da5, 
	0x3e6b5b, 0x6456d0, 0x502ae0, 0x3893e7, 0x5e92e0, 0x48c960, 0x33d155, 0x56d4a0, 0x40da50, 0x2d355, 
	0x5256a0, 0x3aa6fa, 0x6225d0, 0x4c92d0, 0x36aab6, 0x5aa950, 0x44b4a0, 0x2ebaa4, 0x54ad50, 0x3f55a, 
	0x644ba0, 0x4ea5b0, 0x3b5278, 0x5e52b0, 0x486930, 0x327555, 0x586aa0, 0x40ab50, 0x2c5b52, 0x524b6, 
	0x3da56a, 0x60a4f0, 0x4c5260, 0x34ea66, 0x5ad530, 0x445aa0, 0x2eb6a3, 0x5496d0, 0x404ae0, 0x28c9d, 
	0x4ea4d0, 0x38d2d8, 0x5eb250, 0x46b520, 0x31d545, 0x56ada0, 0x4295d0, 0x2c55b2, 0x5249b0, 0x3ca4f9
); /* Years 1600-1699 */

var TK18 = new Array(
	0x62a4b0, 0x4caa50, 0x37b457, 0x5c6b40, 0x46ada0, 0x305b64, 0x569370, 0x424970, 0x2cc971, 0x5064b, 
	0x3a6aa8, 0x5eda50, 0x4a5aa0, 0x32aec5, 0x58a6e0, 0x4492f0, 0x3052e2, 0x52c960, 0x3dd49a, 0x62d4a, 
	0x4cd550, 0x365b57, 0x5c56a0, 0x46a6d0, 0x3295d4, 0x5692d0, 0x40a95c, 0x2ad4b0, 0x50b2a0, 0x38b5a, 
	0x5ead50, 0x4a4da0, 0x34aba4, 0x58a570, 0x4452b0, 0x2eb273, 0x546930, 0x3c6abb, 0x626aa0, 0x4cab5, 
	0x394b57, 0x5c4b60, 0x46a570, 0x3252e4, 0x56d160, 0x3ee93c, 0x64d520, 0x4edaa0, 0x3b5b29, 0x5e56d, 
	0x4a4ae0, 0x34a5d5, 0x5aa2d0, 0x42d150, 0x2cea52, 0x52b520, 0x3cd6ab, 0x60ada0, 0x4c55d0, 0x384bb, 
	0x5e45b0, 0x46a2b0, 0x30d2b4, 0x56aa50, 0x41b52c, 0x646b20, 0x4ead60, 0x3a55e9, 0x609370, 0x4a457, 
	0x34a575, 0x5a52b0, 0x446a50, 0x2d5a52, 0x525aa0, 0x3dab4b, 0x62a6e0, 0x4c92e0, 0x36c6e6, 0x5ca56, 
	0x46d4a0, 0x2eeaa5, 0x54d550, 0x4056a0, 0x2ad5a1, 0x4ea5d0, 0x3b52d9, 0x6052b0, 0x4aa950, 0x32d55, 
	0x58b2a0, 0x42b550, 0x2e6d52, 0x524da0, 0x3da5cb, 0x62a570, 0x4e51b0, 0x36a977, 0x5c6530, 0x466a90
); /* Years 1700-1799 */


var TK19 = new Array(
	0x30baa3, 0x56ab50, 0x422ba0, 0x2cab61, 0x52a370, 0x3c51e8, 0x60d160, 0x4ae4b0, 0x376926, 0x58daa0,
	0x445b50, 0x3116d2, 0x562ae0, 0x3ea2e0, 0x28e2d2, 0x4ec950, 0x38d556, 0x5cb520, 0x46b690, 0x325da4,
	0x5855d0, 0x4225d0, 0x2ca5b3, 0x52a2b0, 0x3da8b7, 0x60a950, 0x4ab4a0, 0x35b2a5, 0x5aad50, 0x4455b0,
	0x302b74, 0x562570, 0x4052f9, 0x6452b0, 0x4e6950, 0x386d56, 0x5e5aa0, 0x46ab50, 0x3256d4, 0x584ae0,
	0x42a570, 0x2d4553, 0x50d2a0, 0x3be8a7, 0x60d550, 0x4a5aa0, 0x34ada5, 0x5a95d0, 0x464ae0, 0x2eaab4,
	0x54a4d0, 0x3ed2b8, 0x64b290, 0x4cb550, 0x385757, 0x5e2da0, 0x4895d0, 0x324d75, 0x5849b0, 0x42a4b0,
	0x2da4b3, 0x506a90, 0x3aad98, 0x606b50, 0x4c2b60, 0x359365, 0x5a9370, 0x464970, 0x306964, 0x52e4a0,
	0x3cea6a, 0x62da90, 0x4e5ad0, 0x392ad6, 0x5e2ae0, 0x4892e0, 0x32cad5, 0x56c950, 0x40d4a0, 0x2bd4a3,
	0x50b690, 0x3a57a7, 0x6055b0, 0x4c25d0, 0x3695b5, 0x5a92b0, 0x44a950, 0x2ed954, 0x54b4a0, 0x3cb550,
	0x286b52, 0x4e55b0, 0x3a2776, 0x5e2570, 0x4852b0, 0x32aaa5, 0x56e950, 0x406aa0, 0x2abaa3, 0x50ab50
); /* Years 1800-1899 */

var TK20 = new Array(
	0x3c4bd8, 0x624ae0, 0x4ca570, 0x3854d5, 0x5cd260, 0x44d950, 0x315554, 0x5656a0, 0x409ad0, 0x2a55d2,
	0x504ae0, 0x3aa5b6, 0x60a4d0, 0x48d250, 0x33d255, 0x58b540, 0x42d6a0, 0x2cada2, 0x5295b0, 0x3f4977,
	0x644970, 0x4ca4b0, 0x36b4b5, 0x5c6a50, 0x466d50, 0x312b54, 0x562b60, 0x409570, 0x2c52f2, 0x504970,
	0x3a6566, 0x5ed4a0, 0x48ea50, 0x336a95, 0x585ad0, 0x442b60, 0x2f86e3, 0x5292e0, 0x3dc8d7, 0x62c950,
	0x4cd4a0, 0x35d8a6, 0x5ab550, 0x4656a0, 0x31a5b4, 0x5625d0, 0x4092d0, 0x2ad2b2, 0x50a950, 0x38b557,
	0x5e6ca0, 0x48b550, 0x355355, 0x584da0, 0x42a5b0, 0x2f4573, 0x5452b0, 0x3ca9a8, 0x60e950, 0x4c6aa0,
	0x36aea6, 0x5aab50, 0x464b60, 0x30aae4, 0x56a570, 0x405260, 0x28f263, 0x4ed940, 0x38db47, 0x5cd6a0,
	0x4896d0, 0x344dd5, 0x5a4ad0, 0x42a4d0, 0x2cd4b4, 0x52b250, 0x3cd558, 0x60b540, 0x4ab5a0, 0x3755a6,
	0x5c95b0, 0x4649b0, 0x30a974, 0x56a4b0, 0x40aa50, 0x29aa52, 0x4e6d20, 0x39ad47, 0x5eab60, 0x489370,
	0x344af5, 0x5a4970, 0x4464b0, 0x2c74a3, 0x50ea50, 0x3d6a58, 0x6256a0, 0x4aaad0, 0x3696d5, 0x5c92e0
); /* Years 1900-1999 */

var TK21 = new Array(
	0x46c960, 0x2ed954, 0x54d4a0, 0x3eda50, 0x2a7552, 0x4e56a0, 0x38a7a7, 0x5ea5d0, 0x4a92b0, 0x32aab5,
	0x58a950, 0x42b4a0, 0x2cbaa4, 0x50ad50, 0x3c55d9, 0x624ba0, 0x4ca5b0, 0x375176, 0x5c5270, 0x466930,
	0x307934, 0x546aa0, 0x3ead50, 0x2a5b52, 0x504b60, 0x38a6e6, 0x5ea4e0, 0x48d260, 0x32ea65, 0x56d520,
	0x40daa0, 0x2d56a3, 0x5256d0, 0x3c4afb, 0x6249d0, 0x4ca4d0, 0x37d0b6, 0x5ab250, 0x44b520, 0x2edd25,
	0x54b5a0, 0x3e55d0, 0x2a55b2, 0x5049b0, 0x3aa577, 0x5ea4b0, 0x48aa50, 0x33b255, 0x586d20, 0x40ad60,
	0x2d4b63, 0x525370, 0x3e49e8, 0x60c970, 0x4c54b0, 0x3768a6, 0x5ada50, 0x445aa0, 0x2fa6a4, 0x54aad0,
	0x4052e0, 0x28d2e3, 0x4ec950, 0x38d557, 0x5ed4a0, 0x46d950, 0x325d55, 0x5856a0, 0x42a6d0, 0x2c55d4,
	0x5252b0, 0x3ca9b8, 0x62a930, 0x4ab490, 0x34b6a6, 0x5aad50, 0x4655a0, 0x2eab64, 0x54a570, 0x4052b0,
	0x2ab173, 0x4e6930, 0x386b37, 0x5e6aa0, 0x48ad50, 0x332ad5, 0x582b60, 0x42a570, 0x2e52e4, 0x50d160,
	0x3ae958, 0x60d520, 0x4ada90, 0x355aa6, 0x5a56d0, 0x462ae0, 0x30a9d4, 0x54a2d0, 0x3ed150, 0x28e952
); /* Years 2000-2099 */

var TK22 = new Array(
		0x4eb520, 0x38d727, 0x5eada0, 0x4a55b0, 0x362db5, 0x5a45b0, 0x44a2b0, 0x2eb2b4, 0x54a950, 0x3cb559,
		0x626b20, 0x4cad50, 0x385766, 0x5c5370, 0x484570, 0x326574, 0x5852b0, 0x406950, 0x2a7953, 0x505aa0,
		0x3baaa7, 0x5ea6d0, 0x4a4ae0, 0x35a2e5, 0x5aa550, 0x42d2a0, 0x2de2a4, 0x52d550, 0x3e5abb, 0x6256a0,
		0x4c96d0, 0x3949b6, 0x5e4ab0, 0x46a8d0, 0x30d4b5, 0x56b290, 0x40b550, 0x2a6d52, 0x504da0, 0x3b9567,
		0x609570, 0x4a49b0, 0x34a975, 0x5a64b0, 0x446a90, 0x2cba94, 0x526b50, 0x3e2b60, 0x28ab61, 0x4c9570,
		0x384ae6, 0x5cd160, 0x46e4a0, 0x2eed25, 0x54da90, 0x405b50, 0x2c36d3, 0x502ae0, 0x3a93d7, 0x6092d0,
		0x4ac950, 0x32d556, 0x58b4a0, 0x42b690, 0x2e5d94, 0x5255b0, 0x3e25fa, 0x6425b0, 0x4e92b0, 0x36aab6,
		0x5c6950, 0x4674a0, 0x31b2a5, 0x54ad50, 0x4055a0, 0x2aab73, 0x522570, 0x3a5377, 0x6052b0, 0x4a6950,
		0x346d56, 0x585aa0, 0x42ab50, 0x2e56d4, 0x544ae0, 0x3ca570, 0x2864d2, 0x4cd260, 0x36eaa6, 0x5ad550,
		0x465aa0, 0x30ada5, 0x5695d0, 0x404ad0, 0x2aa9b3, 0x50a4d0, 0x3ad2b7, 0x5eb250, 0x48b540, 0x33d556
); /* Years 2100-2199 */

var CAN = new Array("Gi\341p", "\u1EA4t", "B\355nh", "\u0110inh", "M\u1EADu", "K\u1EF7", "Canh", "T\342n", "Nh\342m", "Qu\375");
var CHI = new Array("T\375", "S\u1EEDu", "D\u1EA7n", "M\343o", "Th\354n", "T\u1EF5", "Ng\u1ECD", "M\371i", "Th\342n", "D\u1EADu", "Tu\u1EA5t", "H\u1EE3i");
var TUAN = new Array("Ch\u1EE7 nh\u1EADt", "Th\u1EE9 hai", "Th\u1EE9 ba", "Th\u1EE9 t\u01B0", "Th\u1EE9 n\u0103m", "Th\u1EE9 s\341u", "Th\u1EE9 b\u1EA3y");
var GIO_HD = new Array("110100101100", "001101001011", "110011010010", "101100110100", "001011001101", "010010110011");
var TIETKHI = new Array("Xu\u00E2n ph\u00E2n", "Thanh minh", "C\u1ED1c v\u0169", "L\u1EADp h\u1EA1", "Ti\u1EC3u m\u00E3n", "Mang ch\u1EE7ng",
	"H\u1EA1 ch\u00ED", "Ti\u1EC3u th\u1EED", "\u0110\u1EA1i th\u1EED", "L\u1EADp thu", "X\u1EED th\u1EED", "B\u1EA1ch l\u1ED9",
	"Thu ph\u00E2n", "H\u00E0n l\u1ED9", "S\u01B0\u01A1ng gi\u00E1ng", "L\u1EADp \u0111\u00F4ng", "Ti\u1EC3u tuy\u1EBFt", "\u0110\u1EA1i tuy\u1EBFt",
	"\u0110\u00F4ng ch\u00ED", "Ti\u1EC3u h\u00E0n", "\u0110\u1EA1i h\u00E0n", "L\u1EADp xu\u00E2n", "V\u0169 Th\u1EE7y", "Kinh tr\u1EADp"
);

/* decoded - used if file encoded as UTF8 with BOM (as this file).
   var TIETKHI = new Array("Xuân Phân","Thanh Minh","Cốc Vũ","Lập Hạ","Tiểu Mãn","Mang Chủng",
			"Hạ Chí","Tiểu Thử","Đại Thử","Lập Thu","Xử Thử","Bạch Lộ",
			"Thu Phân","Hàn Lộ","Sương Giáng","Lập Đông","Tiểu Tuyết","Đại Tuyết",
			"Đông Chí","Tiểu Hàn","Đại Hàn","Lập Xuân","Vũ Thủy","Kinh Trập");
*/


// Create lunar date object, stores (lunar) date, month, year, leap month indicator, and Julian date number

function LunarDateObj(dd, mm, yyyy, leap, jd) {
	this.day = dd;
	this.month = mm;
	this.year = yyyy;
	this.leap = leap;
	this.jd = jd;
}


// Discard the fractional part of a number, e.g., INT(3.2) = 3

function INT(d) {
	return Math.floor(d);
}


//tính số ngày  Julius từ ngày solar dd/mm/yyyy

function jdn(dd, mm, yy) {
	var a = INT((14 - mm) / 12);
	var y = yy+4800-a;
	var m = mm+12*a-3;
	var jd = dd + INT((153*m+2)/5) + 365*y + INT(y/4) - INT(y/100) + INT(y/400) - 32045;
	
	// 1.0 code
	if (jd < 2299161) {
		jd = dd + INT((153*m+2)/5) + 365*y + INT(y/4) - 32083;
	}
	// 1.0 code
	
	return jd;
}


// ngược lại, đổi ngày dd/mm/yyyy ra số ngày Julian 
function jdn2date(jd) { //convert Julian Date Number to date
	var Z, A, alpha, B, C, D, E, dd, mm, yyyy, F;
	Z = jd;
	if (Z < 2299161) {
	  A = Z;
	} else {
	  alpha = INT((Z-1867216.25)/36524.25);
	  A = Z + 1 + alpha - INT(alpha/4);
	}
	B = A + 1524;
	C = INT( (B-122.1)/365.25);
	D = INT( 365.25*C );
	E = INT( (B-D)/30.6001 );
	dd = INT(B - D - INT(30.6001*E));
	if (E < 14) {
	  mm = E - 1;
	} else {
	  mm = E - 13;
	}
	if (mm < 3) {
	  yyyy = C - 4715;
	} else {
	  yyyy = C - 4716;
	}
	// 0.8 	return new Array(dd, mm, yyyy);
	return new Array(dd, mm, yyyy, jd);
}



// vnav Jan24 =  Hàm tham khảo, không sử dụng ----------//

/* tính ngày Sóc thứ k (cho trước) kể từ điểm Sóc ngày 1/1/1900: k=INT((N-2415021)/29.530588853)   (số ngày Julius: 2415021 là ngày 1/1/1900)
 * để tính được âm lịch trước hết ta cần xác định các tháng âm lịch bắt đầu vào ngày nào.
 * Hàm sau tính ngày Sóc thứ k (cho trước) kể từ điểm Sóc ngày 1/1/1900. Kết quả trả về là số ngày Julius của ngày Sóc cần tìm.

 * Với hàm này ta có thể tính được tháng âm lịch chứa ngày N bắt đầu vào ngày nào: 
 * giữa ngày 1/1/1900 (số ngày Julius: 2415021) và ngày N có khoảng k=INT((N-2415021)/29.530588853) tháng âm lịch, 
 * như thế dùng hàm getNewMoonDay sẽ biết ngày đầu tháng âm lịch chứa ngày N, từ đó ta biết ngày N là mùng mấy âm lịch.

 * Tham khảo thêm tài liệu tiếng Anh: Astronomical Algorithms by Jean Meeus, 1998

function getNewMoonDay(k, timeZone) {
	var T, T2, T3, dr, Jd1, M, Mpr, F, C1, deltat, JdNew;
	T = k/1236.85; // Time in Julian centuries from 1900 January 0.5
	T2 = T * T;
	T3 = T2 * T;
	dr = PI/180;
	Jd1 = 2415020.75933 + 29.53058868*k + 0.0001178*T2 - 0.000000155*T3;
	Jd1 = Jd1 + 0.00033*Math.sin((166.56 + 132.87*T - 0.009173*T2)*dr); // Mean new moon
	M = 359.2242 + 29.10535608*k - 0.0000333*T2 - 0.00000347*T3; // Sun's mean anomaly
	Mpr = 306.0253 + 385.81691806*k + 0.0107306*T2 + 0.00001236*T3; // Moon's mean anomaly
	F = 21.2964 + 390.67050646*k - 0.0016528*T2 - 0.00000239*T3; // Moon's argument of latitude
	C1=(0.1734 - 0.000393*T)*Math.sin(M*dr) + 0.0021*Math.sin(2*dr*M);
	C1 = C1 - 0.4068*Math.sin(Mpr*dr) + 0.0161*Math.sin(dr*2*Mpr);
	C1 = C1 - 0.0004*Math.sin(dr*3*Mpr);
	C1 = C1 + 0.0104*Math.sin(dr*2*F) - 0.0051*Math.sin(dr*(M+Mpr));
	C1 = C1 - 0.0074*Math.sin(dr*(M-Mpr)) + 0.0004*Math.sin(dr*(2*F+M));
	C1 = C1 - 0.0004*Math.sin(dr*(2*F-M)) - 0.0006*Math.sin(dr*(2*F+Mpr));
	C1 = C1 + 0.0010*Math.sin(dr*(2*F-Mpr)) + 0.0005*Math.sin(dr*(2*Mpr+M));
	if (T < -11) {
		deltat= 0.001 + 0.000839*T + 0.0002261*T2 - 0.00000845*T3 - 0.000000081*T*T3;
	} else {
		deltat= -0.000278 + 0.000265*T + 0.000262*T2;
	};
	JdNew = Jd1 + C1 - deltat;
	return INT(JdNew + 0.5 + timeZone/24); // điều chỉnh theo local time . vnav 29jan24.
}

*/


function decodeLunarYear(yy, k) {

	var monthLengths, regularMonths, offsetOfTet, leapMonth, leapMonthLength, solarNY, currentJD, j, mm;

	var ly = new Array();
	monthLengths = new Array(29, 30);
	regularMonths = new Array(12);
	offsetOfTet = k >> 17;
	leapMonth = k & 0xf;
	leapMonthLength = monthLengths[k >> 16 & 0x1];
	solarNY = jdn(1, 1, yy);
	currentJD = solarNY+offsetOfTet;

	j = k >> 4;
	for(i = 0; i < 12; i++) {
		regularMonths[12 - i - 1] = monthLengths[j & 0x1];
		j >>= 1;
	}
	if (leapMonth == 0) {
		for(mm = 1; mm <= 12; mm++) {
			ly.push(new LunarDateObj(1, mm, yy, 0, currentJD));
			currentJD += regularMonths[mm-1];
		}
	} else {
		for(mm = 1; mm <= leapMonth; mm++) {
			ly.push(new LunarDateObj(1, mm, yy, 0, currentJD));
			currentJD += regularMonths[mm-1];
		}
	ly.push(new LunarDateObj(1, leapMonth, yy, 1, currentJD));
		currentJD += leapMonthLength;
		for(mm = leapMonth+1; mm <= 12; mm++) {
			ly.push(new LunarDateObj(1, mm, yy, 0, currentJD));
			currentJD += regularMonths[mm-1];
		}
	}
	return ly;
}


function getYearInfo(yyyy) {
	var yearCode;
	if (yyyy < 1300) {
		yearCode = TK13[yyyy - 1200];
	} else if (yyyy < 1400) {
		yearCode = TK14[yyyy - 1300];
	} else if (yyyy < 1500) {
		yearCode = TK15[yyyy - 1400];
	} else if (yyyy < 1600) {
		yearCode = TK16[yyyy - 1500];
	} else if (yyyy < 1700) {
		yearCode = TK17[yyyy - 1600];
	} else if (yyyy < 1800) {
		yearCode = TK18[yyyy - 1700];
	} else if (yyyy < 1900) {
		yearCode = TK19[yyyy - 1800];
	} else if (yyyy < 2000) {
		yearCode = TK20[yyyy - 1900];
	} else if (yyyy < 2100) {
		yearCode = TK21[yyyy - 2000];
	} else {
		yearCode = TK22[yyyy - 2100];
	}
	return decodeLunarYear(yyyy, yearCode);
}

function findLunarDate(jd, ly) {
	if (jd > LAST_DAY || jd < FIRST_DAY || ly[0].jd > jd) {
		return new LunarDateObj(0, 0, 0, 0, jd);
	}
	var i = ly.length-1;
	while (jd < ly[i].jd) {
		i--;
	}
	var off = jd - ly[i].jd;
	ret = new LunarDateObj(ly[i].day+off, ly[i].month, ly[i].year, ly[i].leap, jd);
	return ret;
}


function getLunarDate(dd, mm, yyyy) { //convert solar to lunar date
	var ly, jd;
	if (yyyy < 1200 || 2199 < yyyy) {
		//return new LunarDateObj(0, 0, 0, 0, 0);
	}
	ly = getYearInfo(yyyy);
	jd = jdn(dd, mm, yyyy);
	if (jd < ly[0].jd) {
		ly = getYearInfo(yyyy - 1);
	}
	return findLunarDate(jd, ly);
}


// 1.0 code -> convert lunar to solar date
function getSolarDate(dd, mm, yyyy) { 

	dd=parseInt(dd,10);
	mm=parseInt(mm,10);
	yyyy=parseInt(yyyy,10);

	if ( isNaN(dd) || isNaN(mm) || isNaN(yyyy)) {
		return new Array(1, 1, 1,"isNaL");
	}

	if (yyyy < 1200 || 2199 < yyyy) { //Vietnamese lunar year limited within this script
		return new Array(1, 1, 1,"isNaL");
	}

	if (dd<1 || dd >30 || mm <1 || mm > 12) {
		return new Array(1, 1, 1,"isNaL");
	}

	var ly = getYearInfo(yyyy);
	var lm = ly[mm-1];
	if (lm.month != mm) {
		lm = ly[mm];
	}
	var ld = lm.jd + dd - 1;
	return jdn2date(ld);
}

/* vnav Jan24: 
 compute days of a lunar month.
 It is said lunar-month length can be retrieved from yearcode table as pre-computed by HoNgocDuc (bit 8 eighth).
 as we can not verify it, then the following function is constructed instead of.
*/


function getLunarMonthLength(sdd, smm, syyyy) {
	var ld1,ld2,id,monthLength;
	
	ld1=ld2=getLunarDate(sdd,smm,syyyy).day;
	monthLength=getMonth(smm,syyyy).length;

	if (sdd<monthLength){
		for (var id=sdd+1; id<=monthLength; id++) {
			 ld2=getLunarDate(id,smm,syyyy).day;
		 	if (ld2 < ld1) {
				break;
			} else {ld1=ld2;}
		}
	}
	//if (typeof ld2 === 'undefined') ld2="dsds";
	if (ld1 == ld2) { // check next month
		var im=smm;
		var iy=syyyy;
		if (im <12) {
			im=im+1;
		} else {
			im=1;
			iy=iy+1;
		}	
		monthLength=getMonth(im,iy).length;
	
		for (var id=1; id<=monthLength; id++) {
			 ld2=getLunarDate(id,im,iy).day;
		 	if (ld2 < ld1) {
				break;
			} else {ld1=ld2;}
		}
	}
	//return  ("lMonth,lMonthLength : " + getLunarDate(sdd,smm,syyyy).month +" = "+ ld1); 
	return  ld1; 
}


/* Compute the longitude of the sun at any time.
   Parameter: floating number jdn, the number of days since 1/1/4713 BC noon
   Algorithm from: "Astronomical Algorithms" by Jean Meeus, 1998
 */

function SunLongitude(jdn) { //radian version
	var T, T2, dr, M, L0, DL, lambda, theta, omega;
	T = (jdn - 2451545.0 ) / 36525; // Time in Julian centuries from 2000-01-01 12:00:00 GMT
	T2 = T*T;
	dr = PI/180; // degree to radian
	M = 357.52910 + 35999.05030*T - 0.0001559*T2 - 0.00000048*T*T2; // mean anomaly, degree
	L0 = 280.46645 + 36000.76983*T + 0.0003032*T2; // mean longitude, degree
	DL = (1.914600 - 0.004817*T - 0.000014*T2)*Math.sin(dr*M);
	DL = DL + (0.019993 - 0.000101*T)*Math.sin(dr*2*M) + 0.000290*Math.sin(dr*3*M);
    theta = L0 + DL; // true longitude, degree
    // obtain apparent longitude by correcting for nutation and aberration
    omega = 125.04 - 1934.136 * T;
    lambda = theta - 0.00569 - 0.00478 * Math.sin(omega * dr);
    // Convert to radians
    lambda = lambda*dr;
	lambda = lambda - PI*2*(INT(lambda/(PI*2))); // Normalize to (0, 2*PI)
    return lambda;
}

/* Compute the sun segment at start (00:00) of the day with the given integral Julian day number.
   The time zone if the time difference between local time and UTC: 7.0 for UTC+7:00.
   The function returns a number between 0 and 23.
   From the day after March equinox and the 1st major term after March equinox, 0 is returned.
   After that, return 1, 2, 3 ...
 */

function getSunLongitude(dayNumber, timeZone) {
	return INT(SunLongitude(dayNumber - 0.5 - timeZone/24.0) / PI * 12);
}


function getMonth(mm, yyyy) {
	var ly1, ly2, tet1, jd1, jd2, mm1, yy1, result, i;
	if (mm < 12) {
		mm1 = mm + 1;
		yyyy1 = yyyy;
	} else {
		mm1 = 1;
		yyyy1 = yyyy + 1;
	}
	jd1 = jdn(1, mm, yyyy);
	jd2 = jdn(1, mm1, yyyy1);
	ly1 = getYearInfo(yyyy);
	//fb.ShowPopupMessage('1/'+mm+'/'+yyyy+' = '+jd1+'; 1/'+mm1+'/'+yyyy1+' = '+jd2,"alert");
	tet1 = ly1[0].jd;
	result = new Array();
	if (tet1 <= jd1) { /* tet(yyyy) = tet1 < jd1 < jd2 <= 1.1.(yyyy+1) < tet(yyyy+1) */
		for (i = jd1; i < jd2; i++) {
			result.push(findLunarDate(i, ly1));
		}
	} else if (jd1 < tet1 && jd2 < tet1) { /* tet(yyyy-1) < jd1 < jd2 < tet1 = tet(yyyy) */
		ly1 = getYearInfo(yyyy - 1);
		for (i = jd1; i < jd2; i++) {
			result.push(findLunarDate(i, ly1));
		}
	} else if (jd1 < tet1 && tet1 <= jd2) { /* tet(yyyy-1) < jd1 < tet1 <= jd2 < tet(yyyy+1) */
		ly2 = getYearInfo(yyyy - 1);
		for (i = jd1; i < tet1; i++) {
			result.push(findLunarDate(i, ly2));
		}
		for (i = tet1; i < jd2; i++) {
			result.push(findLunarDate(i, ly1));
		}
	}
	return result;
}

function getDayName(lunarDate) {
	if (lunarDate.day == 0) {
		return "";
	}
	var cc = getCanChi(lunarDate);
	var s = "Ng\u00E0y " + cc[0] +", th\341ng "+cc[1] + ", n\u0103m " + cc[2]; //ie. ngày Quí Sửu tháng Quý Mùi năm Quý Mão.
    	//var s = cc[0] +"/"+cc[1] + "/" + cc[2];
	return s;
}

function getYearCanChi(year) {
	return CAN[(year+6) % 10] + " " + CHI[(year+8) % 12];
}


// Can cua gio Chinh Ty (00:00) cua ngay voi JDN nay
 
function getCanHour0(jdn) {
	return CAN[(jdn-1)*2 % 10];
}

function getCanChi(lunar) {
	var dayName, monthName, yearName;
	dayName = CAN[(lunar.jd + 9) % 10] + " " + CHI[(lunar.jd+1)%12];
	monthName = CAN[(lunar.year*12+lunar.month+3) % 10] + " " + CHI[(lunar.month+1)%12];
	if (lunar.leap == 1) {
		monthName += " (nhu\u1EADn)";
	}
	yearName = getYearCanChi(lunar.year);
	return new Array(dayName, monthName, yearName);
}



function getMoreInforDay(lunarDate, solarDate, solarMonth, solarYear) {
    	var res = "Ng\u00E0y : ";
	
	if (solarDate == today.getDate() && solarMonth == today.getMonth()+1 && solarYear == today.getFullYear()) {
		res = "H\xF4m nay l\u00E0 : ";
		}
	
	if (todayIsDay) {
		res = "H\xF4m nay l\u00E0 : ";
		todayIsDay=false;
	}

	if (lunarDate.day == 1 && lunarDate.month == 1) { res += " T\u1EBFt "; }
	if (lunarDate.leap == 1) { res += " Lunar2 "; }
    
	var lunar = lunarDate.day;
	if (solarDate == 1 || lunar == 1) {
		lunar = lunarDate.day + "/" + lunarDate.month;
	}
	
	if (lunarDate != null) {
        //res = res + getDayName(lunarDate);
        res = res + dayInfo(lunarDate.day,lunarDate.month,lunarDate.year,lunarDate.leap,lunarDate.jd,solarDate,solarMonth,solarYear,res);
	}		
    return res;
}


function getCurrentTime() {
	today = new Date();
	var Std = today.getHours();
	var Min = today.getMinutes();
	var Sec = today.getSeconds();
	var s1  = ((Std < 10) ? "0" + Std : Std);
	var s2  = ((Min < 10) ? "0" + Min : Min);
	//var s3  = ((Sec < 10) ? "0" + Sec : Sec);
	//return s1 + ":" + s2 + ":" + s3;
	return s1 + ":" + s2;
}

function getGioHoangDao(jd) {
	var chiOfDay = (jd+1) % 12;
	var gioHD = GIO_HD[chiOfDay % 6]; // same values for Ty' (1) and Ngo. (6), for Suu and Mui etc.
	var ret = "";
	var count = 0;
    try {
	for (var i = 0; i < 12; i++) {
		if (gioHD.charAt(i) == '1') {
			ret += CHI[i];
			ret += ' ('+(i*2+23)%24+'-'+(i*2+1)%24+')';
			if (count++ < 5) ret += ', ';
			//if (count == 3) ret += '\n';
		}
	}
    } catch (e) {}
	return ret;
}


// some big dates or festivals

//1. solar festival

function navi_sDate(n,dd,mm,yyyy){
    return new Date(new Date(yyyy,mm-1,dd).getTime() + ((n * 24) * 60 * 60 * 1000));
    //dayNext=(tomorrow.getDate()+""+(tomorrow.getMonth()+1)+""+tomorrow.getFullYear());
    //fb.trace("today, tomorrow -dayNext : "+ day1+", "+tomorrow + "-"+dayNext);    
// vnav Jan24
}

function lastDayMonthS(yyyy,mm) {
	return  new Date(yyyy, mm+1, 0).getDate(); // next month end, then go back 1 day
	// mm: the month is 0-indexed
	// parameter day = 0 : 1 day less than first day of the month mm+1, which is last day of the previous month (mm+1-1 = mm).
}

function getLastDayMonthS(yyyy,mm, ddR) {
	var ddRet=lastDayMonthS(yyyy,mm);
	ddRet = (ddRet > ddR) ? ddR : ddRet;
	return ddRet;
}

function chekSDate(dd,mm,yyyy) {
	
	/* check solar date
	  mm : 1 indexed as normal calendar month
	  partial algorithm source: https://www.w3resource.com/javascript/form/javascript-date-validation.php
	*/

	dd	= parseInt(dd,10);
	mm	= parseInt(mm,10);
	yyyy	= parseInt(yyyy,10);
	var res	= false;

	if ( isNaN(dd) || isNaN(mm) || isNaN(yyyy)) {
		return res;
	}

	if (dd<1 || dd >31 || mm <1 || mm > 12) {
		return res;
	}

	//1. overall check by JSengine uitilised Date() constructor.
	 
        var _ed = new Date(mm+"/"+dd+"/"+ yyyy); // Date("MM/dd/yyyy");
	if ( _ed.getDate() == dd && ( _ed.getMonth() + 1) == mm && _ed.getFullYear() == yyyy) return true;

	// 2. furthur check, as JSengine is subject to local time or hidden algorithm 

	var ListofDays = [31,28,31,30,31,30,31,31,30,31,30,31];
  	if (mm == 1 || mm > 2) {
  		if (dd > ListofDays[mm-1]) {  return res; }
  	}

  	if (mm == 2)  {
  		var lyear = false;
  		if ( (!(yyyy % 4) && yyyy % 100) || !(yyyy % 400)) { lyear = true; }
  
		if ((lyear == false) && (dd>=29)) { return res; }
  		if ((lyear == true) && (dd>29)) { return res; }
  	}
	return true;
// vnav Feb24
}


function checkSDate(df,mf,pd,nd,dd,mm){
	var ret="";
	var oneDay = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds
	var today= new Date();
	var tmonth=today.getMonth()+1, tyear = today.getFullYear();

	var df1=parseInt(df,10), mf1=parseInt(mf,10), dd1 =parseInt(dd,10), mm1 =parseInt(mm,10),pd1 =parseInt(pd,10),nd1 =parseInt(nd,10);
	var diffday1=diffday2=diffday3=diffDays=0;
	var theDate,festDate1,festDate2,festDate3;
	var thisYear=tyear,mf2,mf3,ty2,ty3;
	
	fb.trace("Solar festival date to check.Noted that fb2k-vnav adjusts end-month day if applicable : ["+ df +"/"+ mf + "]" + thisYear);

	if (( !isNaN(df1) && !isNaN(mf1) && !isNaN(dd1) && !isNaN(mm1) && !isNaN(pd1) && !isNaN(nd1)) && !( df1>31 || df1<1 || mf1>12 || mf1<0 || dd1>31 || dd1<1 || mm1>12 || mm1<1 || pd1<0 || nd1<0)) {
		// check solar date
		if (!chekSDate(df1, (mf1 ==0 ? mm1 : mf1) ,thisYear)) {
			fb.trace("========= check solar festival date ================  ");
			fb.trace("Festival day/month : " + df1 +"/"+ (mf1 ==0 ? mm1 : mf1));
			fb.trace("Festival date is not valid in this year : " + thisYear);
			fb.trace("Fb2k will try to attemp the date as last day month: " + getLastDayMonthS(thisYear,(mf1 ==0 ? mm1 : mf1)-1,df1) +"/"+ (mf1 ==0 ? mm1 : mf1));
		}

		theDate = (new Date(thisYear,mm1-1,dd1));
		if (mf1==0) { // monthly event -> check event prv, this and next month to get the 'real' gap
			tmonth=mm1;
			mf1 = tmonth;
			
			if (tmonth==1) { // prev 1 month
				mf2 =12;
				ty2= tyear-1;
			} else {
				mf2 = tmonth-1;
				ty2=tyear;
			}

			if (tmonth==12) { // next 1 month
				mf3 =1;
				ty3= tyear+1;
			} else {
				mf3 = tmonth+1;
				ty3=tyear;
			}
			 
			// check end-day month
			// if the day is beyond last day month (i.e 30 > 29 day of Feb 2024, then get 29 instead of)
			festDate1 = new Date(thisYear,mf1-1,getLastDayMonthS(thisYear,mf1-1,df1));// same month
			festDate2 = new Date(ty2,mf2-1,getLastDayMonthS(ty2,mf2-1,df1));  // prv 1 month
			festDate3 = new Date(ty3,mf3-1,getLastDayMonthS(ty3,mf3-1,df1)); // next 1 month
			 
		} else { // edge year
			 festDate1 = new Date(thisYear,mf1-1,getLastDayMonthS(thisYear,mf1-1,df1)) ; // same year
			 festDate2 = new Date(thisYear-1,mf1-1,getLastDayMonthS(thisYear-1,mf1-1,df1)) ; // prv 1 year
			 festDate3 = new Date(thisYear+1,mf1-1,getLastDayMonthS(thisYear+1,mf1-1,df1)) ; // next 1 year
		}
		
		diffday1 =  Math.round((festDate1 - theDate) / oneDay); 
		diffday2 =  Math.round((festDate2 - theDate) / oneDay); 
		diffday3 =  Math.round((festDate3 - theDate) / oneDay); 

		if (diffday1 ==0 ) diffDays=diffday1;
		if (diffday1 < 0 ) { diffDays = (diffday3 > Math.abs(diffday1)) ? diffday1 : diffday3; }
		if (diffday1 > 0 ) { diffDays= (diffday1 > Math.abs(diffday2)) ? diffday2 : diffday1; }

		ret="";

		if (diffDays < 0 && nd1>= Math.abs(diffDays)) {ret=" (đã qua khoảng "+  Math.abs(diffDays) +" ngày)";}
		if (diffDays == 0) {ret= " (hôm nay)";}
		if (diffDays > 0 && pd1 >= diffDays) {ret= " (còn khoảng "+  diffDays +" ngày)";}
				
	} else {
		ret = "\nSolar festival day/month :" + df +"/"+ mf ;
		ret += "\nDays reminded before :" + pd;
		ret += "\nDays reminded after :" + nd;
		fb.trace("========= check solar festival date ================  ");
		fb.trace("Festival date is not valid, please check festival.txt file");
		fb.trace(ret);
		ret="";
	}
	return ret;	
// vnav Jan24, mod Feb24
}

//2. lunar festival

function getLastDayMonthL(_ld, _lm, _lyyyy) {
	
	_ld	= parseInt(_ld,10);
	_lm	= parseInt(_lm,10);
	_lyyyy	= parseInt(_lyyyy,10);
	var res	= 0;

	if ( isNaN(_ld) || isNaN(_lm) || isNaN(_lyyyy)) {
		return res;
	}

	if (_lyyyy < 1200 || 2199 < _lyyyy) { //lunar year constraint as it is whithin this script
		return res;
	}

	if (_ld<1 || _ld >30 || _lm <1 || _lm > 12) {
		return res;
	}

	res=_ld;
	while (true) {
		if (chekLDate(res, _lm, _lyyyy)) 
		{ 
			break; 
		} else {
			res=res-1;	
		}
	}
	//if (res < _ld) return res;

	var sdate = getSolarDate(res, _lm, _lyyyy);  //convert the lunar date to solar date
	if (sdate[3] == "isNaL") return 0;
	var ret=getLunarMonthLength(sdate[0], sdate[1], sdate[2])

	return (ret < _ld ? ret : _ld);
// vnav Feb24
}

function chekLDate(dd,mm,yyyy) {

	/* vnav Feb24 
	   as Date() constructor solar date of JS engine, it can treat a wrong-human input date as 32-12-2024 and convert to a real solar date.
	   as such, this constructor can not be utilised to validate input date.
	   regexp can not cover all cases, or to some extent - too complicated.  
 	
	   Solution: straighforwards - 'trước sau như 1'
		step 1: let JSengine/lunar engine to convert input date to a solra Date
		step 2: reverse-engineering the output from step 1
		step 3: compare input date with/to output from step 2.	    
	*/

	//dd, mm, yyy : lunar date
	var sDate = getSolarDate(dd, mm, yyyy);//step1: convert lunar to solar date
	if (sDate[3] == "isNaL") return false;

	var lDate= getLunarDate(sDate[0], sDate[1], sDate[2]); //step 2: convert solar back to lunar date
	
	if (!(lDate.day==dd && lDate.month==mm && lDate.year == yyyy)) return false; // step 3: compare
	return true;
// vnav Feb24
}


function checkLDate(df,mf,pd,nd,dd,mm){
	var ret="";
	var oneDay = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds
	var today= new Date();
	var tmonth=today.getMonth()+1, tyear = today.getFullYear();

	var df1=parseInt(df,10), mf1=parseInt(mf,10), dd1 =parseInt(dd,10), mm1 =parseInt(mm,10),pd1 =parseInt(pd,10),nd1 =parseInt(nd,10);
	var diffday1=diffday2=diffday3=diffDays=0;
	var sd_theDate,sd_festDate1, sd_festDate2, sd_festDate3;
	var theDate,festDate1,festDate2,festDate3;
	var thisYear=tyear,mf2,mf3,ty2,ty3;
	
	//determine current lunar year
	var lDate=getLunarDate(today.getDate(), tmonth, tyear);
	tyear = thisYear = lDate.year; //convert these variables to lunar year
	tmonth = lDate.month;

	fb.trace("Lunar festival date to check. Noted that fb2k-vnav adjusts end-month day if applicable : ["+ df +"/"+ mf + "]" + thisYear);

	// we adjust lunar date end-month day as we do with solar date to investigate the exact date 
	if (( !isNaN(df1) && !isNaN(mf1) && !isNaN(dd1) && !isNaN(mm1) && !isNaN(pd1) && !isNaN(nd1)) && !( df1>31 || df1<1 || mf1>12 || mf1<0 || dd1>30 || dd1<1 || mm1>12 || mm1<1 || pd1<0 || nd1<0)) {
		// check lunar date
		if (!chekLDate(df1, (mf1 ==0 ? mm1 : mf1) ,thisYear)) {
			fb.trace("========= check lunar festival date ================  ");
			fb.trace("Festival day/month : " + df1 +"/"+ (mf1 ==0 ? mm1 : mf1));
			fb.trace("Festival date is not valid in this lunar year : " + thisYear);
			fb.trace("Fb2k will try to attemp the date as last day month: " + getLastDayMonthL(df1,(mf1==0 ? mm1 : mf1), thisYear)+"/"+ (mf1 ==0 ? mm1 : mf1));
		}

		sd_theDate = getSolarDate(dd1, mm1, thisYear);  //convert the lunar date to solar date
		theDate = (new Date(sd_theDate[2],sd_theDate[1]-1,sd_theDate[0]));

		if (mf1==0) { // monthly event -> check event prv, this and next month to get the 'real' gap
			tmonth=mm1;
			mf1 = tmonth;
			
			if (tmonth==1) { // prev 1 month
				mf2 =12;
				ty2= tyear-1;
			} else {
				mf2 = tmonth-1;
				ty2=tyear;
			}

			if (tmonth==12) { // next 1 month
				mf3 =1;
				ty3= tyear+1;
			} else {
				mf3 = tmonth+1;
				ty3=tyear;
			}

			 sd_festDate1 = getSolarDate(getLastDayMonthL(df1,mf1, thisYear), mf1, thisYear); //convert event-festival lunar date to solar date - same month
			 sd_festDate2 = getSolarDate(getLastDayMonthL(df1,mf2, thisYear), mf2, ty2); //convert event-festival lunar date to solar date - prv 1 month
			 sd_festDate3 = getSolarDate(getLastDayMonthL(df1,mf3, thisYear), mf3, ty3); //convert event-festival lunar date to solar date - next 1 month
		} else {
			 sd_festDate1 = getSolarDate(getLastDayMonthL(df1,mf1, thisYear), mf1, thisYear); //convert event-festival lunar date to solar date - same year
			 sd_festDate2 = getSolarDate(getLastDayMonthL(df1,mf1, (thisYear-1)), mf1, thisYear-1); //convert event-festival lunar date to solar date - prv year
			 sd_festDate3 = getSolarDate(getLastDayMonthL(df1,mf1, (thisYear+1)), mf1, thisYear+1); //convert event-festival lunar date to solar date - next year
		}

		festDate1 = (new Date(sd_festDate1[2],sd_festDate1[1]-1,sd_festDate1[0]));
		festDate2 = (new Date(sd_festDate2[2],sd_festDate2[1]-1,sd_festDate2[0]));
		festDate3 = (new Date(sd_festDate3[2],sd_festDate3[1]-1,sd_festDate3[0]));
			
		diffday1 = Math.round((festDate1 - theDate) / oneDay);
		diffday2 = Math.round((festDate2 - theDate) / oneDay);
		diffday3 = Math.round((festDate3 - theDate) / oneDay);

		if (diffday1 ==0 ) diffDays=diffday1;
		if (diffday1 < 0 ) { diffDays = (diffday3 > Math.abs(diffday1)) ? diffday1 : diffday3; }
		if (diffday1 > 0 ) { diffDays= (diffday1 > Math.abs(diffday2)) ? diffday2 : diffday1; }

		ret="";

		if (diffDays < 0 && nd1>= Math.abs(diffDays)) {ret=" (đã qua khoảng "+  Math.abs(diffDays) +" ngày)";}
		if (diffDays == 0) {ret= " (hôm nay)";}
		if (diffDays > 0 && pd1 >= diffDays) {ret= " (còn khoảng "+  diffDays +" ngày)";}
		
		
	} else {
		ret = "\nLunar festival day/month :" + df +"/"+ mf ;
		ret += "\nDays reminded before :" + pd;
		ret += "\nDays reminded after :" + nd;
		fb.trace("========= check lunar festival date ================  ");
		fb.trace("Lunar festival date is not valid, please check festival.txt file");
		fb.trace(ret);
		ret="";
	}

	return ret;	
// vnav Jan24, mod Feb24
}


// 1. lunar festival
function findEventsL(dd, mm) {

	var ret = new Array();
	var ret1="";
	
	for (var i = 0; i < lunarYEARLY_EVENTS.length; i++) {
		evt = lunarYEARLY_EVENTS[i];
		ret1="";
		ret1=checkLDate(evt.day,evt.month,evt.prvday,evt.nxtday,dd,mm);		
		if (ret1 !="") {
			evt.remind= ret1;
			ret.push(evt);
		}
	}

	return ret;
}

// 2. solar festival
function findEventsS(dd, mm) {
	var ret = new Array();
	var ret1="";
	
	for (var i = 0; i < solarYEARLY_EVENTS.length; i++) {
		evt = solarYEARLY_EVENTS[i];
		ret1="";
		ret1=checkSDate(evt.day,evt.month,evt.prvday,evt.nxtday,dd,mm);		
		if (ret1 !="") {
			evt.remind= ret1;
			ret.push(evt);
		}
	}
	
	return ret;
}


function getDayInfo(dd, mm, LoS) {
	var events;
	if (LoS =="lunar") events = findEventsL(dd, mm);  //dd,mm is lunar date
	else if (LoS =="solar") events = findEventsS(dd, mm); //dd,mm is solar date
	var ret = '';
	for (var i = 0; i < events.length; i++) {
		ret += events[i].info+events[i].remind+', ';
	}
	//ret += '';
	return ret;
}


function dayInfo(dd, mm, yyyy, leap, jd, sday, smonth, syear,whatIsToday) {
	var lunar = new LunarDateObj(dd, mm, yyyy, leap, jd);
    	lunarL1= whatIsToday+getAnyLunarDayBriefInfor(lunar, sday, smonth, syear, " t\u1EE9c l\u00E0 ",true,false) + " \u00E2m l\u1ECBch " + "(th\u00E1ng "+mm+" c\u00F3 " + getLunarMonthLength(sday,smonth,syear) +" ng\u00E0y)";
    	lunarL2="Nh\u1EB1m : "+ getDayName(lunar)
    	lunarL3="Gi\u1EDD \u0111\u1EA7u ng\u00E0y : " +getCanHour0(jd)+" "+CHI[0]; 
    	lunarL4="Ti\u1EBFt : "+TIETKHI[getSunLongitude(jd+1, 7.0)];
    	lunarL5="Gi\u1EDD ho\u00E0ng \u0111\u1EA1o : "+getGioHoangDao(jd);
    	lunarL6= getDayInfo(dd, mm, "lunar");
	lunarL7= getDayInfo(sday, smonth, "solar");
	
	/*
	var s = lunarL1;
	s += " \u00E2m l\u1ECBch";
	s += "\n"+lunarL2;
	s += "\n\n"+lunarL3;
	s += "\n"+lunarL4;
	s += "\n"+lunarL5;
	s += "\n"+lunarL6;
	s += "\n"+lunarL7;

	return s; // just in case to display in textbox JSP3
	*/
	return "";
}




//=============================== action =========================/

function getAnyLunarDayBriefInfor(lunar, solarDay, solarMonth, solarYear, strTXT, showDay,shortLeap) {
	var s;
	var dayOfWeek = TUAN[(lunar.jd + 1) % 7];
	s = (showDay ? dayOfWeek + ", " : "")+ (solarDay < 10 ? "0"+ solarDay : solarDay) + "/" + (solarMonth < 10 ? "0"+solarMonth : solarMonth) + "/" + solarYear;
	
	s += strTXT;
	//s = s + "Ng\u00E0y " + lunar.day+" th\341ng "+lunar.month;
	s = s + lunar.day+"/"+lunar.month+"/"+lunar.year;
	
	if (lunar.leap == 1) {
		s = s + (shortLeap ? "N" : " nhu\u1EADn");
	}
	return s;
}


function getAnyLunarDayRichInfor(dd, mm, yyyy, getJustaDay) {
	var i, j, k, solar;
	var currentMonth = getMonth(mm, yyyy);
	if (currentMonth.length == 0) return;

	var ld1 = currentMonth[0];
	var emptyCells = (ld1.jd + 1) % 7;

	var res = "";
	for (i = 0; i < 6; i++) {
		for (j = 0; j < 7; j++) {
			k = 7 * i + j;
			if (k < emptyCells || k >= emptyCells + currentMonth.length) {
				res += "";
			} else {
				solar = k - emptyCells + 1;
                if (getJustaDay) {
                    if (solar == dd) {
                        //fb.ShowPopupMessage("Date is " + dd ,"Just a date");
			ld1 = currentMonth[k - emptyCells];
			res += getMoreInforDay(ld1, solar, mm, yyyy);
                    } 
                } else {
			ld1 = currentMonth[k - emptyCells];
			res += getMoreInforDay(ld1, solar, mm, yyyy);
                	}                    
		}
		}
	}
	return res;
}



//getCurrentTime();
//getAnyLunarDayBriefInfor(getLunarDate(dd, mm, yyyy), dd, mm, yyyy ," = ", false, true);
//getAnyLunarDayRichInfor(dd,mm, yyyy, true); 

/***********************************
Thuật toán tính âm lịch
------------------------
Hồ Ngọc Đức
web link: https://www.informatik.uni-leipzig.de/~duc/amlich/calrules.html

Bài viết sau giới thiệu cách tính âm lịch Việt Nam và mô tả một số thuật toán dùng để chuyển đổi giữa ngày dương lịch và ngày âm lịch. Các thuật toán mô tả ở đây đã được đơn giản hóa nhiều để bạn đọc tiện theo dõi và dễ dàng sử dụng vào việc lập trình, do đó độ chính xác của chúng thấp hơn độ chính xác của chương trình âm lịch trực tuyến tại http://www.informatik.uni-leipzig.de/~duc/amlich/. (Một phiên bản cũ của bài viết này giới thiệu vài thuật toán hơi khác, có thể khó thực hiện hơn một chút. Bản cũ này có thể xem tại đây https://www.informatik.uni-leipzig.de/~duc/amlich/calrules_v02.html.)
[If you cannot read Vietnamese: Old version in English]

1.Quy luật của âm lịch Việt Nam:

Âm lịch Việt Nam là một loại lịch thiên văn. Nó được tính toán dựa trên sự chuyển động của mặt trời, trái đất và mặt trăng. Ngày tháng âm lịch được tính dựa theo các nguyên tắc sau:
Ngày đầu tiên của tháng âm lịch là ngày chứa điểm Sóc
Một năm bình thường có 12 tháng âm lịch, một năm nhuận có 13 tháng âm lịch
Đông chí luôn rơi vào tháng 11 âm lịch
Trong một năm nhuận, nếu có 1 tháng không có Trung khí thì tháng đó là tháng nhuận. Nếu nhiều tháng trong năm nhuận đều không có Trung khí thì chỉ tháng đầu tiên sau Đông chí là tháng nhuận
Việc tính toán dựa trên kinh tuyến 105° đông.
Sóc là thời điểm hội diện, đó là khi trái đất, mặt trăng và mặt trời nằm trên một đường thẳng và mặt trăng nằm giữa trái đất và mặt trời. (Như thế góc giữa mặt trăng và mặt trời bằng 0 độ). Gọi là "hội diện" vì mặt trăng và mặt trời ở cùng một hướng đối với trái đất. Chu kỳ của điểm Sóc là khoảng 29,5 ngày. Ngày chứa điểm Sóc được gọi là ngày Sóc, và đó là ngày bắt đầu tháng âm lịch.

Trung khí là các điểm chia đường hoàng đạo thành 12 phần bằng nhau. Trong đó, bốn Trung khí giữa bốn mùa là đặc biệt nhất: Xuân phân (khoảng 20/3), Hạ chí (khoảng 22/6), Thu phân (khoảng 23/9) và Đông chí (khoảng 22/12).

Bởi vì dựa trên cả mặt trời và mặt trăng nên lịch Việt Nam không phải là thuần âm lịch mà là âm-dương-lịch. Theo các nguyên tắc trên, để tính ngày tháng âm lịch cho một năm bất kỳ trước hết chúng ta cần xác định những ngày nào trong năm chứa các thời điểm Sóc (New moon) . Một khi bạn đã tính được ngày Sóc, bạn đã biết được ngày bắt đầu và kết thúc của một tháng âm lịch: ngày mùng một của tháng âm lịch là ngày chứa điểm sóc. Sau khi đã biết ngày bắt đầu/kết thúc các tháng âm lịch, ta tính xem các Trung khí (Major solar term) rơi vào tháng nào để từ đó xác định tên các tháng và tìm tháng nhuận.

Đông chí luôn rơi vào tháng 11 của năm âm lịch. Bởi vậy chúng ta cần tính 2 điểm sóc: Sóc A ngay trước ngày Đông chí thứ nhất và Sóc B ngay trước ngày Đông chí thứ hai. Nếu khoảng cách giữa A và B là dưới 365 ngày thì năm âm lịch có 12 tháng, và những tháng đó có tên là: tháng 11, tháng 12, tháng 1, tháng 2, …, tháng 10. Ngược lại, nếu khoảng cách giữa hai sóc A và B là trên 365 ngày thì năm âm lịch này là năm nhuận, và chúng ta cần tìm xem đâu là tháng nhuận. Để làm việc này ta xem xét tất cả các tháng giữa A và B, tháng đầu tiên không chứa Trung khí sau ngày Đông chí thứ nhất là tháng nhuận. Tháng đó sẽ được mang tên của tháng trước nó kèm chữ "nhuận".

Khi tính ngày Sóc và ngày chứa Trung khí bạn cần lưu ý xem xét chính xác múi giờ. Đây là lý do tại sao có một vài điểm khác nhau giữa lịch Việt Nam và lịch Trung Quốc.Ví dụ, nếu bạn biết thời điểm hội diện là vào lúc yyyy-02-18 16:24:45 GMT thì ngày Sóc của lịch Việt Nam là 18 tháng 2, bởi vì 16:24:45 GMT là 23:24:45 cùng ngày, giờ Hà nội (GMT+7, kinh tuyến 105° đông). Tuy nhiên theo giờ Bắc Kinh (GMT+8, kinh tuyến 120° đông) thì Sóc là lúc 00:24:45 ngày yyyy-02-19, do đó tháng âm lịch của Trung Quốc lại bắt đầu ngày yyyy-02-19, chậm hơn lịch Việt Nam 1 ngày.

Ví dụ 1: Âm lịch năm 1984
Chúng ta áp dụng quy luật trên để tính âm lịch Việt nam năm 1984.
Sóc A (ngay trước Đông chí năm 1983) rơi vào ngày 4/12/1983, Sóc B (ngay trước Đông chí năm 1984) vào ngày 23/11/1984.
Giữa A và B là khoảng 355 ngày, như thế năm âm lịch 1984 là năm thường. Tháng 11 âm lịch của năm trước kéo dài từ 4/12/1983 đến 2/01/1984, tháng 12 âm từ 3/1/1984 đến 1/2/1984, tháng Giêng từ 2/2/1984 đến 1/3/1984 v.v.
Ví dụ 2: Âm lịch năm 2004
Sóc A - điểm sóc cuối cùng trước Đông chí 2003 - rơi vào ngày 23/11/2003. Sóc B (ngay trước Đông chí năm 2004) rơi vào ngày 12/12/2004.
Giữa 2 ngày này là khoảng 385 ngày, như vậy năm âm lịch 2004 là năm nhuận. Tháng 11 âm của năm 2003 bắt đầu vào ngày chứa Sóc A, tức ngày 23/11/2003.
Tháng âm lịch đầu tiên sau đó mà không chứa Trung khí là tháng từ 21/3/2004 đến 18/4/2004 (Xuân phân rơi vào 20/3/2004, còn Cốc vũ là 19/4/2004). Như thế tháng ấy là tháng nhuận.
Từ 23/11/2003 đến 21/3/2004 là khoảng 120 ngày, tức 4 tháng âm lịch: tháng 11, 12, 1 và 2. Như vậy năm 2004 có tháng 2 nhuận.

2. Thuật toán chuyển đổi giữa ngày dương và âm:

Trong tính toán thiên văn người ta lấy ngày 1/1/4713 trước công nguyên của lịch Julius (tức ngày 24/11/4714 trước CN theo lịch Gregorius) làm điểm gốc. Số ngày tính từ điểm gốc này gọi là số ngày Julius (Julian day number) của một thời điểm. Ví dụ, số ngày Julius của 1/1/2000 là 24515455.
Dùng các công thức sau ta có thể chuyển đổi giữa ngày/tháng/năm và số ngày Julius. Phép chia ở 2 công thức sau được hiểu là chia số nguyên, bỏ phần dư: 23/4=5.

Đổi ngày dd/mm/yyyy ra số ngày Julius jd
a = (14 - mm) / 12
y = yy+4800-a
m = mm+12*a-3

Lịch Gregory:

jd = dd + (153*m+2)/5 + 365*y + y/4 - y/100 + y/400 - 32045

Lịch Julius:

jd = dd + (153*m+2)/5 + 365*y + y/4 - 32083
Đổi số ngày Julius jd ra ngày dd/mm/yyyy
Lịch Gregory (jd lớn hơn 2299160):

a = jd + 32044;
b = (4*a+3)/146097;
c = a - (b*146097)/4;

Lịch Julius:

b = 0;
c = jd + 32082;

Công thức cho cả 2 loại lịch:

d = (4*c+3)/1461;
e = c - (1461*d)/4;
m = (5*e+2)/153;
dd = e - (153*m+2)/5 + 1;
mm = m + 3 - 12*(m/10);
yy = b*100 + d - 4800 + m/10;

Nếu ngôn ngữ lập trình bạn dùng không hỗ trợ phép chia số nguyên bỏ phần dư (VD: JavaScript), bạn có thể định nghĩa một hàm INT(x) để lấy số nguyên lớn nhất không vượt quá x: INT(5)=5, INT(3.2)=3, INT(-5)=-5, INT(-3.2)=-4 v.v. Khi đó, INT(m/10) sẽ trả lại kết quả của phép chia số nguyên. (Nhiều ngôn ngữ có sẵn hàm floor() cho phép làm việc này.)
Các phép chuyển đổi giữa ngày tháng và số ngày Julius có thể được thực hiện với mã JavaScript như sau:

function jdFromDate(dd, mm, yy)

var a, y, m, jd;
a = INT((14 - mm) / 12);
y = yy+4800-a;
m = mm+12*a-3;
jd = dd + INT((153*m+2)/5) + 365*y + INT(y/4) - INT(y/100) + INT(y/400) - 32045;
if (jd < 2299161) {
	jd = dd + INT((153*m+2)/5) + 365*y + INT(y/4) - 32083;
}
return jd;
function jdToDate(jd)

var a, b, c, d, e, m, day, month, year;
if (jd > 2299160) { // After 5/10/1582, Gregorian calendar
	a = jd + 32044;
	b = INT((4*a+3)/146097);
	c = a - INT((b*146097)/4);
} else {
	b = 0;
	c = jd + 32082;
}
d = INT((4*c+3)/1461);
e = c - INT((1461*d)/4);
m = INT((5*e+2)/153);
day = e - INT((153*m+2)/5) + 1;
month = m + 3 - 12*INT(m/10);
year = b*100 + d - 4800 + INT(m/10);
return new Array(day, month, year);

Trong các công thức sau, timeZone là số giờ chênh lệch giữa giờ địa phương và giờ UTC (hay GMT). (Để tính lịch Việt Nam, lấy timeZone = 7.0). Các phương pháp sau được giới thiệu với mã JavaScript. Bạn có thể tải thư viện JavaScript hoặc thư viện PHP hoàn chỉnh để tham khảo.
Tính ngày Sóc
Như trên đã nói, để tính được âm lịch trước hết ta cần xác định các tháng âm lịch bắt đầu vào ngày nào.
Thuật toán sau tính ngày Sóc thứ k kể từ điểm Sóc ngày 1/1/1900. Kết quả trả về là số ngày Julius của ngày Sóc cần tìm.

function getNewMoonDay(k, timeZone)

var T, T2, T3, dr, Jd1, M, Mpr, F, C1, deltat, JdNew;
T = k/1236.85; // Time in Julian centuries from 1900 January 0.5
T2 = T * T;
T3 = T2 * T;
dr = PI/180;
Jd1 = 2415020.75933 + 29.53058868*k + 0.0001178*T2 - 0.000000155*T3;
Jd1 = Jd1 + 0.00033*Math.sin((166.56 + 132.87*T - 0.009173*T2)*dr); // Mean new moon
M = 359.2242 + 29.10535608*k - 0.0000333*T2 - 0.00000347*T3; // Sun's mean anomaly
Mpr = 306.0253 + 385.81691806*k + 0.0107306*T2 + 0.00001236*T3; // Moon's mean anomaly
F = 21.2964 + 390.67050646*k - 0.0016528*T2 - 0.00000239*T3; // Moon's argument of latitude
C1=(0.1734 - 0.000393*T)*Math.sin(M*dr) + 0.0021*Math.sin(2*dr*M);
C1 = C1 - 0.4068*Math.sin(Mpr*dr) + 0.0161*Math.sin(dr*2*Mpr);
C1 = C1 - 0.0004*Math.sin(dr*3*Mpr);
C1 = C1 + 0.0104*Math.sin(dr*2*F) - 0.0051*Math.sin(dr*(M+Mpr));
C1 = C1 - 0.0074*Math.sin(dr*(M-Mpr)) + 0.0004*Math.sin(dr*(2*F+M));
C1 = C1 - 0.0004*Math.sin(dr*(2*F-M)) - 0.0006*Math.sin(dr*(2*F+Mpr));
C1 = C1 + 0.0010*Math.sin(dr*(2*F-Mpr)) + 0.0005*Math.sin(dr*(2*Mpr+M));
if (T < -11) {
	deltat= 0.001 + 0.000839*T + 0.0002261*T2 - 0.00000845*T3 - 0.000000081*T*T3;
} else {
	deltat= -0.000278 + 0.000265*T + 0.000262*T2;
};
JdNew = Jd1 + C1 - deltat;
return INT(JdNew + 0.5 + timeZone/24)

Với hàm này ta có thể tính được tháng âm lịch chứa ngày N bắt đầu vào ngày nào: giữa ngày 1/1/1900 (số ngày Julius: 2415021) và ngày N có khoảng k=INT((N-2415021)/29.530588853) tháng âm lịch, như thế dùng hàm getNewMoonDay sẽ biết ngày đầu tháng âm lịch chứa ngày N, từ đó ta biết ngày N là mùng mấy âm lịch.

3.Tính tọa độ mặt trời:

Để biết Trung khí nào nằm trong tháng âm lịch nào, ta chỉ cần tính xem mặt trời nằm ở khoảng nào trên đường hoàng đạo vào thời điểm bắt đầu một tháng âm lịch. Ta chia đường hoàng đạo làm 12 phần và đánh số các cung này từ 0 đến 11: từ Xuân phân đến Cốc vũ là 0; từ Cốc vũ đến Tiểu mãn là 1; từ Tiểu mãn đến Hạ chí là 2; v.v.. Cho jdn là số ngày Julius của bất kỳ một ngày, phương pháp sau này sẽ trả lại số cung nói trên.
function getSunLongitude(jdn, timeZone)

var T, T2, dr, M, L0, DL, L;
T = (jdn - 2451545.5 - timeZone/24) / 36525; // Time in Julian centuries from 2000-01-01 12:00:00 GMT
T2 = T*T;
dr = PI/180; // degree to radian
M = 357.52910 + 35999.05030*T - 0.0001559*T2 - 0.00000048*T*T2; // mean anomaly, degree
L0 = 280.46645 + 36000.76983*T + 0.0003032*T2; // mean longitude, degree
DL = (1.914600 - 0.004817*T - 0.000014*T2)*Math.sin(dr*M);
DL = DL + (0.019993 - 0.000101*T)*Math.sin(dr*2*M) + 0.000290*Math.sin(dr*3*M);
L = L0 + DL; // true longitude, degree
L = L*dr;
L = L - PI*2*(INT(L/(PI*2))); // Normalize to (0, 2*PI)
return INT(L / PI * 6)

Với hàm này ta biết được một tháng âm lịch chứa Trung khí nào. Giả sử một tháng âm lịch bắt đầu vào ngày N1 và tháng sau đó bắt đầu vào ngày N2 và hàm getSunLongitude cho kết quả là 8 với N1 và 9 với N2. Như vậy tháng âm lịch bắt đầu ngày N1 là tháng chứa Đông chí: trong khoảng từ N1 đến N2 có một ngày mặt trời di chuyển từ cung 8 (sau Tiểu tuyết) sang cung 9 (sau Đông chí). Nếu hàm getSunLongitude trả lại cùng một kết quả cho cả ngày bắt đầu một tháng âm lịch và ngày bắt đầu tháng sau đó thì tháng đó không có Trung khí và như vậy có thể là tháng nhuận.
Tìm ngày bắt đầu tháng 11 âm lịch
Đông chí thường nằm vào khoảng 19/12-22/12, như vậy trước hết ta tìm ngày Sóc trước ngày 31/12. Nếu tháng bắt đầu vào ngày đó không chứa Đông chí thì ta phải lùi lại 1 tháng nữa.
function getLunarMonth11(yy, timeZone)

var k, off, nm, sunLong;
off = jdFromDate(31, 12, yy) - 2415021;
k = INT(off / 29.530588853);
nm = getNewMoonDay(k, timeZone);
sunLong = getSunLongitude(nm, timeZone); // sun longitude at local midnight
if (sunLong >= 9) {
	nm = getNewMoonDay(k-1, timeZone);
}
return nm;

4.Xác định tháng nhuận:

Nếu giữa hai tháng 11 âm lịch (tức tháng có chứa Đông chí) có 13 tháng âm lịch thì năm âm lịch đó có tháng nhuận. Để xác định tháng nhuận, ta sử dụng hàm getSunLongitude như đã nói ở trên. Cho a11 là ngày bắt đầu tháng 11 âm lịch mà một trong 13 tháng sau đó là tháng nhuận. Hàm sau cho biết tháng nhuận nằm ở vị trí nào sau tháng 11 này.
function getLeapMonthOffset(a11, timeZone)

var k, last, arc, i;
k = INT((a11 - 2415021.076998695) / 29.530588853 + 0.5);
last = 0;
i = 1; // We start with the month following lunar month 11
arc = getSunLongitude(getNewMoonDay(k+i, timeZone), timeZone);
do {
	last = arc;
	i++;
	arc = getSunLongitude(getNewMoonDay(k+i, timeZone), timeZone);
} while (arc != last && i < 14);
return i-1;

Giả sử hàm getLeapMonthOffset trả lại giá trị 4, như thế tháng nhuận sẽ là tháng sau tháng 2 thường. (Tháng thứ 4 sau tháng 11 đáng ra là tháng 3, nhưng vì đó là tháng nhuận nên sẽ lấy tên của tháng trước đó tức tháng 2, và tháng thứ 5 sau tháng 11 mới là tháng 3).

5.Đổi ngày dương dd/mm/yyyy ra ngày âm:

Với các phương pháp hỗ trợ trên ta có thể đổi ngày dương dd/mm/yy ra ngày âm dễ dàng. Trước hết ta xem ngày monthStart bắt đầu tháng âm lịch chứa ngày này là ngày nào (dùng hàm getNewMoonDay như trên đã nói). Sau đó, ta tìm các ngày a11 và b11 là ngày bắt đầu các tháng 11 âm lịch trước và sau ngày đang xem xét. Nếu hai ngày này cách nhau dưới 365 ngày thì ta chỉ còn cần xem monthStart và a11 cách nhau bao nhiêu tháng là có thể tính được dd/mm/yy nằm trong tháng mấy âm lịch. Ngược lại, nếu a11 và b11 cách nhau khoảng 13 tháng âm lịch thì ta phải tìm xem tháng nào là tháng nhuận và từ đó suy ra ngày đang tìm nằm trong tháng nào.
function convertSolar2Lunar(dd, mm, yy, timeZone)

var k, dayNumber, monthStart, a11, b11, lunarDay, lunarMonth, lunarYear, lunarLeap;
dayNumber = jdFromDate(dd, mm, yy);
k = INT((dayNumber - 2415021.076998695) / 29.530588853);
monthStart = getNewMoonDay(k+1, timeZone);
if (monthStart > dayNumber) {
	monthStart = getNewMoonDay(k, timeZone);
}
a11 = getLunarMonth11(yy, timeZone);
b11 = a11;
if (a11 >= monthStart) {
	lunarYear = yy;
	a11 = getLunarMonth11(yy-1, timeZone);
} else {
	lunarYear = yy+1;
	b11 = getLunarMonth11(yy+1, timeZone);
}
lunarDay = dayNumber-monthStart+1;
diff = INT((monthStart - a11)/29);
lunarLeap = 0;
lunarMonth = diff+11;
if (b11 - a11 > 365) {
	leapMonthDiff = getLeapMonthOffset(a11, timeZone);
	if (diff >= leapMonthDiff) {
		lunarMonth = diff + 10;
		if (diff == leapMonthDiff) {
			lunarLeap = 1;
		}
	}
}
if (lunarMonth > 12) {
	lunarMonth = lunarMonth - 12;
}
if (lunarMonth >= 11 && diff < 4) {
	lunarYear -= 1;
}

6.Đổi âm lịch ra dương lịch:

Cách làm cũng tương tự như đổi ngày dương sang ngày âm.
function convertLunar2Solar(lunarDay, lunarMonth, lunarYear, lunarLeap, timeZone)

var k, a11, b11, off, leapOff, leapMonth, monthStart;
if (lunarMonth < 11) {
	a11 = getLunarMonth11(lunarYear-1, timeZone);
	b11 = getLunarMonth11(lunarYear, timeZone);
} else {
	a11 = getLunarMonth11(lunarYear, timeZone);
	b11 = getLunarMonth11(lunarYear+1, timeZone);
}
off = lunarMonth - 11;
if (off < 0) {
	off += 12;
}
if (b11 - a11 > 365) {
	leapOff = getLeapMonthOffset(a11, timeZone);
	leapMonth = leapOff - 2;
	if (leapMonth < 0) {
		leapMonth += 12;
	}
	if (lunarLeap != 0 && lunarMonth != leapMonth) {
		return new Array(0, 0, 0);
	} else if (lunarLeap != 0 || off >= leapOff) {
		off += 1;
	}
}
k = INT(0.5 + (a11 - 2415021.076998695) / 29.530588853);
monthStart = getNewMoonDay(k+off, timeZone);
return jdToDate(monthStart+lunarDay-1);

7.Tính ngày thứ và Can-Chi cho ngày và tháng âm lịch:

Ngày thứ lặp lại theo chu kỳ 7 ngày, như thế để biết một ngày d/m/y bất kỳ là thứ mấy ta chỉ việc tìm số dư của số ngày Julius của ngày này cho 7.
Để tính Can của năm Y, tìm số dư của Y+6 chia cho 10. Số dư 0 là Giáp, 1 là Ất v.v. Để tính Chi của năm, chia Y+8 cho 12. Số dư 0 là Tý, 1 là Sửu, 2 là Dần v.v.

Hiệu Can-Chi của ngày lặp lại theo chu kỳ 60 ngày, như thế nó cũng có thể tính được một cách đơn giản. Cho N là số ngày Julius của ngày dd/mm/yyyy. Ta chia N+9 cho 10. Số dư 0 là Giáp, 1 là Ất v.v. Để tìm Chi, chia N+1 cho 12; số dư 0 là Tý, 1 là Sửu v.v.

Trong một năm âm lịch, tháng 11 là tháng Tý, tháng 12 là Sửu, tháng Giêng là tháng Dần v.v. Can của tháng M năm Y âm lịch được tính theo công thức sau: chia Y*12+M+3 cho 10. Số dư 0 là Giáp, 1 là Ất v.v.

Ví dụ, Can-Chi của tháng 3 âm lịch năm Giáp Thân 2004 là Mậu Thìn: tháng 3 âm lịch là tháng Thìn, và (2004*12+3+3) % 10 = 24054 % 10 = 4, như vậy Can của tháng là Mậu.

Một tháng nhuận không có tên riêng mà lấy tên của tháng trước đó kèm thêm chữ "Nhuận", VD: tháng 2 nhuận năm Giáp Thân 2004 là tháng Đinh Mão nhuận.
*/
