日付の情報を表示する

2023年5月7日

入力した日付に関する情報を表示します。
※ 年月日の区切り文字は「/」「空白」を使用できます。

入力例

  • 2023/1/25 … 2023年1月25日
  • 0/1/25 …… BC 1年1月25日
  • -1/1/25 …… BC 2年1月25日
  • 1/25 ……… 現在の年の1月25日
  • 20230125 … 2023年の1月25日
  • 230125 …… 2023年1月25日(最初の2桁が 0~69 -> 2000~2069年)
  • 900125 …… 1990年1月25日(最初の2桁が 70~99 -> 1970~1999年)
  • 0125 ……… 現在の年の1月25日
  • 25 ………… 現在の年月の25日

注意点

  • 西暦4年は閏年として扱います。よって、ユリウス暦1年1月1日は土曜日となります。

ソース

・html
<div id="p1822">
	<div class="v-flex">
		<div class="h-flex">
			<div>
				<select class="combo js-type">
					<option value="g" selected>グレゴリオ暦</option>
					<option value="j">ユリウス暦</option>
					<option value="jd">ユリウス通日</option>
				</select>
			</div>
			<div>
				<input type="tel" class="input js-in" spellcheck="false" placeholder="">
			</div>
			<div><button class="btn js-exec">実行</button></div>
		</div>
		<div><small>※ 年月日の区切り文字は「/」「空白」を使用できます。</small></div>
		<div>
			<table>
				<tbody class="js-out">
				</tbody>
			</table>
		</div></div>
</div>

・css
.v-flex { display: flex; flex-direction: column; }
.h-flex { display: flex; flex-direction: row; }

.js-out th, .js-out td {
	border-color: transparent;
	padding: 0.25rem 0.5rem;
}

.js-out th {
	background-color: transparent;
	text-align: right;
	vertical-align: top;
}

.combo {
	padding: 1em 0.5em;
	margin-right: 0.5em;
}

.input {
	padding: 0 1em;
	width: 8em;
	font-size: 24px;
}

.btn {
	margin-left: 0.5em;
	padding: 0.5em 2em;
}

.red {
	color: #F00;
}

・javascript
jQuery(($) => {
	let ok = true;
	main();

	function main() {
		$('.js-in').on('keydown', onKeyDown);
		$('.js-in').on('keyup', check);		// キー入力用チェック
		$('.js-in').on('change', check);	// ペースト用チェック
		$('.js-type').on('change', check);
		$('.js-exec').click(onClick);
		$('.js-out').html(htmlResults(null));
	}

	function onKeyDown(ev) {
		if (ev.keyCode == 13 && ok) {
			onClick();
		}
	}

	function check(ev) {
		const src = $('.js-in').val();
		if (parse(src) === null) {
			$('.js-in').addClass('red');
			$('.js-exec').prop('disabled', true);
			ok = false;
		} else {
			$('.js-in').removeClass('red');
			$('.js-exec').prop('disabled', false);
			ok = true;
		}
	}

	function onClick() {
		const src = $('.js-in').val();
		const out = [];
		let data;
		const j = parse(src);
		if (j === null) {
			data = null;
		} else {
			data = [];
			data.push(j.toLocaleString());

			let date = jd2gdate(j);
			let s = gdate2str(date);
			let youbi = jdGetDay(j);
			let sLeap = gLeap(date[0]) ? '閏年' : '平年';
			data.push(`${s}(${youbi})${sLeap} `);

			date = jd2jdate(j);
			s = jdate2str(date);
			youbi = jdGetDay(j);
			sLeap = jLeap(date[0]) ? '閏年' : '平年';
			data.push(`${s}(${youbi})${sLeap}`);

			const era = modernEraName(j);
			if (era.length == 0) {
				data.push('----');
			} else {
				let s = era[0];
				for (let i = 1; i < era.length; i++) {
					s += `\n(${era[i]})`;
				}
				data.push(s);
			}
		}
		$('.js-out').html(htmlResults(data));
		$('.js-in')[0].select();
	}

	function htmlResults(data) {
		const keys =  [
			'ユリウス通日',
			'西暦(グレゴリオ暦)',
			'西暦(ユリウス暦)',
			'和暦',
		];
		const out = [];
		for (let i = 0; i < keys.length; i++) {
			let value = data === null ? '' : data[i];
			value = value.replace(/(\r\n|[\r\n])/g, '<br>');
			out.push(`<tr><th>${keys[i]}</th><td>${value}</td></tr>`);
		}
		return out.join('\n');
	}

	function parse(s) {
		// 文字列の掃除
		const t = zen2han(s).toLowerCase().trim();
	
		const valueType = $('.js-type').val();

		// ユリウス通日?
		if (valueType == 'jd') {
			let jd = parseValue(t);
			return jd;
		}

		// 年月日部分の解析
		const ymd = parseDate(t);
		if (ymd === null) return null;

		let [yy, mm, dd] = ymd;
		const now = new Date();
		if (yy === null) yy = now.getFullYear();
		if (mm === null) mm = now.getMonth() + 1;
		if (dd === null) dd = now.getDate();

		return valueType == 'j' ? jdate2jd([yy, mm, dd]) : gdate2jd([yy, mm, dd]);
	}

	// 文字列を全角から半角に変換(0x20-0x7E 限定)
	function zen2han(s) {
		return s.replace(/[\u3000]/g, ' ').replace(/[\uFF01-\uFF5E]/g, (s) => {
			return String.fromCharCode(s.charCodeAt(0) - 0xFF01 + 0x21);
		});
	}

	function parseDate(s) {
		let yy = null;
		let mm = null;
		let dd = null;
		let m;

		s = s.replace(/[ ]/g, '/');
	
		if (m = s.match(/^(-?[0-9]+)\/([0-9]+)\/([0-9]+)$/)) {
			// y/m/d
			yy = m[1] | 0;
			mm = m[2] | 0;
			dd = m[3] | 0;

			return [yy, mm, dd];
		}

		if (m = s.match(/^([0-9]+)\/([0-9]+)$/)) {
			// m/d
			mm = m[1] | 0;
			dd = m[2] | 0;
			return [yy, mm, dd];
		}

		if (s.match(/^[0-9]*$/)) {
			if (m = s.match(/^([0-9]{4})([0-9]{2})([0-9]{2})$/)) {
				// yyyymmdd
				yy = m[1] | 0;
				mm = m[2] | 0;
				dd = m[3] | 0;
			}
			if (m = s.match(/^([0-9]{2})([0-9]{2})([0-9]{2})$/)) {
				// yymmdd
				yy = m[1] | 0;
				yy += yy >= 70 ? 1900 : 2000;
				mm = m[2] | 0;
				dd = m[3] | 0;
			}
			if (m = s.match(/^([0-9]{2})([0-9]{2})$/)) {
				// mmdd
				mm = m[1] | 0;
				dd = m[2] | 0;
			}
			if (m = s.match(/^[0-9]{1,2}$/)) {
				// d
				dd = m[0] | 0;
			}
			return [yy, mm, dd];
		}

		return null;
	}

	function parseValue(s) {
		let m = s.match(/^[0-9,]*$/);
		if (!m) return null;

		let t = m[0].replace(/[,]/g, '');
		return t == '' ? 0 : t | 0;
	}

	function modernEraName(jd) {
		const ret = [];
		const a = [
			[2458605, '令和'],	// 2019-5-1
			[2447535, '平成'],	// 1989-1-8
			[2424875, '昭和'],	// 1926-12-25
			[2419614, '大正'],	// 1912-7-30
			[2403357, '明治'],	// 1868-1-25
		];
		const yy = jd2gdate(jd)[0];

		for (let j = 0; j < a.length; j++) {
			if (jd >= a[j][0]) {
				for (let i = j; i < a.length; i++) {
					const [jd0, eraName] = a[i];
					const y0 = jd2gdate(jd0)[0];
					const y = yy - y0 + 1;
					const s = y == 1 ? '元' : (''+y);
					ret.push(`${eraName}${s}年`);
				}
				break;
			}
		}

		return ret
	}

	// ------------------------------------------------------- 日付関連の関数
	// 参考>https://en.wikipedia.org/wiki/Julian_day

	// グレゴリオ暦 -> ユリウス通日
	function gdate2jd(ymd) {
		const [yy, mm, dd] = ymd;
		const t = _idiv(mm - 14, 12)
		return _idiv(1461 * (yy + 4800 + t), 4)
			+ _idiv(367 * (mm - 2 -12 * t), 12)
			- _idiv(3 * _idiv(yy + 4900 + t, 100), 4)
			+ dd
			- 32075;
	}

	// ユリウス通日 -> グレゴリオ暦
	function jd2gdate(jd) {
		const j = 1401,
		B = 274277,
		C = -38,
		t = _idiv(4 * jd + B, 146097),
		t2 = _idiv(t * 3, 4),
		f = jd + j + t2 + C;
		return _f2date(f);
	}

	// グレゴリオ暦 -> 文字列
	function gdate2str(gdate) {
		let	[yy, mm, dd] = gdate;
		let bc = '';
		if (yy <= 0) {
			bc = 'BC';
			yy = -yy + 1;
		}
		return `${bc}${yy}年 ${mm}月 ${dd}日`;
	}

	// ユリウス暦 -> ユリウス通日
	function jdate2jd(ymd) {
		const [yy, mm, dd] = ymd,
			v1 = 367 * yy,
			t = _idiv(mm - 9, 7),
			v2 = _idiv(7 * (yy + 5001 + t), 4),
			v3 = _idiv(275 * mm, 9);
		return v1 - v2 + v3 + dd + 1729777;
	}

	// ユリウス通日 -> ユリウス暦
	function jd2jdate(jd) {
		return _f2date(1401 + jd);
	}

	// ユリウス暦 -> 文字列
	function jdate2str(gdate) {
		let	[yy, mm, dd] = gdate;
		let bc = '';
		if (yy <= 0) {
			bc = 'BC';
			yy = -yy + 1;
		}
		
		return `${bc}${yy}年 ${mm}月 ${dd}日`;
	}

	// ユリウス通日 -> 曜日(日曜=0)
	function jdGetDay(j) {
		return ['日', '月', '火', '水', '木', '金', '土'][(j+1) % 7];
	}

	// 閏年のチェック(ユリウス暦)
	function jLeap(y) {
		return (y % 4) == 0;
	}

	// 閏年のチェック(グレゴリオ暦)
	function gLeap(y) {
		if (y % 4) return false;
		if (y % 100) return true;
		return (y % 400) == 0;
	}

	// 整数除算(小数点以下切り捨て)
	//	  3.1 / 3 =  3
	//	  3.0 / 3 =  3
	//    2.9 / 3 =  2
	//   -2.9 / 3 = -2
	//	 -3.0 / 3 = -3
	//	 -3.1 / 3 = -3
	function _idiv(a, b) {
		return a*b >= 0 ? Math.floor(a / b) : -Math.floor(-a / b);
	}

	// 補助関数
	function _f2date(f) {
		const y = 4716,
			m = 2,
			n = 12,
			r = 4,
			p = 1461,
			v = 3,
			u = 5,
			s = 153,
			w = 2,

			e = r * f + v,
			g = _idiv(e % p, r),
			h = u * g + w,
			dd = 1 + _idiv(h % s, u),
			mm = 1 + ((_idiv(h, s) + m) % n),
			yy = _idiv(e, p) - y + _idiv(n + m - mm, n);
		return [yy, mm, dd];
	}

});

javascript,

Posted by plkl