LoginSignup
2
1

More than 1 year has passed since last update.

旧暦から新暦への変換プログラム

Last updated at Posted at 2020-09-26

はじめに

この記事では、以下のソースコードを実装します。

  • 旧暦から新暦への変換プログラム
  • 和暦から新暦への変換プログラム

.NET APIの利用

.NET APIには、JapaneseCalendarクラスやJapaneseLunisolarCalendarクラスが用意されており、これらを利用します。

D言語から.NET API呼び出しの実現方法

私が調べた限り、C++/CLIラッピングを経由する方法が簡単だと思いました。
今回はこの方法を採用します。

※参考までに、記事を書くために調べた情報リンクを最後に掲載します。
 C#でDLLを作成し、D言語から呼び出す方法です。

前準備1(C#での実装)

D言語で実装する前に、C#で旧暦や和暦を取得する処理を実装しました。
実行時にパラメータなしだと今日の日付の旧暦や和暦、年月日パラメータを指定するとその日付の旧暦や和暦を表示します。

JCalendar.cs
using System;
using System.Globalization;

public class JCalendar {
	static string[] sEra    = // 元号
		{ "", "明治", "大正", "昭和", "平成", "令和" };
	static string[] sRokuyo = // 六曜
		{ "大安", "赤口", "先勝", "友引", "先負", "仏滅" };
	static string[] sKanshi = // 天干
		{ "", "甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸" };
	static string[] sChishi = // 地支
		{ "", "子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥" };
	
	public static void Main(string[] args)
	{
		DateTime newDate;
		if ( args.Length >= 3 ){
			newDate = new DateTime(
				int.Parse(args[0]), int.Parse(args[1]), int.Parse(args[2]));
		} else {
			newDate = DateTime.Now;
		}
		Console.WriteLine("西暦 : {0:d}", newDate);
		if ( newDate >= new DateTime(1868, 9, 8) ){
			printJapaneseCalendar(newDate);
		}
		if ( newDate >= new DateTime(1960, 1, 28)
		  && newDate <= new DateTime(2050, 1, 22) ){
			printJapaneseLunisolarCalendar(newDate);
		}
	}
	
	static void printJapaneseCalendar(DateTime newDate)
	{
		JapaneseCalendar jc = new JapaneseCalendar();
		int era   = jc.GetEra(newDate);
		int year  = jc.GetYear(newDate);
		int month = jc.GetMonth(newDate);
		int day   = jc.GetDayOfMonth(newDate);
		DateTime jDate  = new DateTime(year, month, day);
		Console.WriteLine("和暦 : {0} {1:d}", sEra[era], jDate);
	}
	
	static void printJapaneseLunisolarCalendar(DateTime newDate)
	{
		JapaneseLunisolarCalendar jlc = new JapaneseLunisolarCalendar();
		int era   = jlc.GetEra(newDate);
		int year  = jlc.GetYear(newDate);
		int month = jlc.GetMonth(newDate);
		int day   = jlc.GetDayOfMonth(newDate);
		
		//閏月を取得
		string sLeap = "";
		if ( year > 0 ){
			int leapMonth = jlc.GetLeapMonth(year, era);
			if ( month == leapMonth ){
				sLeap = "(閏月)";
			}
			//閏月含む場合の月を補正
			if ( (leapMonth > 0) && (month >= leapMonth) ){
				month = month - 1;	//旧暦月の補正
			}
		}
		// 干支(天干、地支)
		int sy = jlc.GetSexagenaryYear(newDate);
		int tk = jlc.GetCelestialStem(sy);
		int ts = jlc.GetTerrestrialBranch(sy);
		
		// 六曜(大安・赤口・先勝・友引・先負・仏滅)
		// (月 + 日) % 6 の余り
		int rokuyo = (month + day) % 6;
		
		Console.WriteLine("旧暦 : {0} {1:d4}/{2:d2}/{3:d2} {4}",
			sEra[era], year, month, day, sLeap);
		Console.WriteLine("干支 : {0}{1}", sKanshi[tk], sChishi[ts]);
		Console.WriteLine("六曜 : {0}", sRokuyo[rokuyo]);
	}
}

Window10には、C#コンパイラ(csc.exe)が標準インストールされています。
どのバージョンがどのフォルダにインストールされているかは、それぞれの環境に依存します。
私の環境でのコンパイル、実行例を例示します。
旧暦には、うるう月(閏月)というものが存在します。GetLeapMonthを使って、その年のうるう月を算出し、月を補正します。
実行結果では、月を補正して正しく表示できています。※旧暦カレンダーで検証
干支(天干、地支)は、その年の干支を取得しています。月や日にも干支が存在するそうです。

実行結果
d:\Dev>C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe JCalendar.cs
Microsoft (R) Visual C# Compiler version 4.8.3752.0
for C# 5
Copyright (C) Microsoft Corporation. All rights reserved.

This compiler is provided as part of the Microsoft (R) .NET Framework, but only supports language versions up to C# 5, which is no longer the latest version. For compilers that support newer versions of the C# programming language, see http://go.microsoft.com/fwlink/?LinkID=533240

d:\Dev>JCalendar
西暦 : 2020/09/26
和暦 : 令和 0002/09/26
旧暦 : 令和 0002/08/10
干支 : 庚子
六曜 : 大安

d:\Dev>JCalendar 2020 5 23
西暦 : 2020/05/23
和暦 : 令和 0002/05/23
旧暦 : 令和 0002/04/01 (閏月)
干支 : 庚子
六曜 : 仏滅

前準備2(C++/CLIでの実装)

次に、C++/CLIで旧暦や和暦を取得する処理を実装しました。
C++/CLIに関する日本語での情報は、それほど多くない印象なので、参考になればと思います。
前準備1のC#のソースコードを移植したイメージです。
実行時にパラメータなしだと今日の日付の旧暦や和暦、年月日パラメータを指定するとその日付の旧暦や和暦を表示します。

C++/CLI特有のものとして、演算子 (^) があります。

ハンドル宣言子( ^ は "hat") は、オブジェクトがアクセス不可能であるとシステムが判断したときに、宣言されたオブジェクトが自動的に削除されることを意味する型指定子をに変更します。

オブジェクト演算子 (^) へのハンドル (C++/CLI および C++/CX)より引用

また、.NET APIのクラス生成では、gcnewを使います。

マネージド型 (参照型または値型) のメモリは gcnew によって割り当てられ、ガベージ コレクションによって解放されます。

ref new、gcnew (C++/CLI および C++/CX)より引用

JCal.cpp
using namespace System;
using namespace System::Globalization;

#include <string>

String^ getEraStr(int era)
{	// 元号
	array<String^>^ sEra = gcnew array<String^>
		{ "", "明治", "大正", "昭和", "平成", "令和" };
	return ( sEra[era] );
}

String^ getRokuyoStr(int rokuyo)
{	// 六曜
	array<String^>^ sRokuyo =
		{ "大安", "赤口", "先勝", "友引", "先負", "仏滅" };
	return ( sRokuyo[rokuyo] );
}

String^ getKanshiStr(int kanshi)
{	// 天干
	array<String^>^ sKanshi =
		{ "", "甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸" };
	return ( sKanshi[kanshi] );
}

String^ getChishiStr(int chishi)
{	// 地支
	array<String^>^sChishi =
		{ "", "子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥" };
	return ( sChishi[chishi] );
}

void printJapaneseCalendar(DateTime newDate)
{
	JapaneseCalendar^ jc = gcnew JapaneseCalendar();
	int era   = jc->GetEra(newDate);
	int year  = jc->GetYear(newDate);
	int month = jc->GetMonth(newDate);
	int day   = jc->GetDayOfMonth(newDate);
	DateTime jDate  = DateTime::DateTime(year, month, day);
	Console::WriteLine("和暦 : {0} {1:d}", getEraStr(era), jDate);
}

void printJapaneseLunisolarCalendar(DateTime newDate)
{
	JapaneseLunisolarCalendar^ jlc = gcnew JapaneseLunisolarCalendar();
	int era   = jlc->GetEra(newDate);
	int year  = jlc->GetYear(newDate);
	int month = jlc->GetMonth(newDate);
	int day   = jlc->GetDayOfMonth(newDate);
	
	//閏月を取得
	String^ sLeap = "";
	if ( year > 0 ){
		int leapMonth = jlc->GetLeapMonth(year, era);
		if ( month == leapMonth ){
			sLeap = "(閏月)";
		}
		//閏月含む場合の月を補正
		if ( (leapMonth > 0) && (month >= leapMonth) ){
			month = month - 1;	//旧暦月の補正
		}
	}
	// 干支(天干、地支)
	int sy = jlc->GetSexagenaryYear(newDate);
	int tk = jlc->GetCelestialStem(sy);
	int ts = jlc->GetTerrestrialBranch(sy);
	
	// 六曜(大安・赤口・先勝・友引・先負・仏滅)
	// (月 + 日) % 6 の余り
	int rokuyo = (month + day) % 6;
	
	Console::WriteLine("旧暦 : {0} {1:d4}/{2:d2}/{3:d2} {4}",
		getEraStr(era), year, month, day, sLeap);
	Console::WriteLine("干支 : {0}{1}", getKanshiStr(tk), getChishiStr(ts));
	Console::WriteLine("六曜 : {0}", getRokuyoStr(rokuyo));
}

int main(int argc, char* argv[])
{
	DateTime newDate = DateTime::Now;
	if ( argc > 3 ){
		newDate = DateTime::DateTime(
			atoi(argv[1]), atoi(argv[2]), atoi(argv[3]));
	}
	Console::WriteLine("西暦 : {0:d}", newDate);
	if ( newDate >= DateTime::DateTime(1868, 9, 8) ){
		printJapaneseCalendar(newDate);
	}
	if ( newDate >= DateTime::DateTime(1960, 1, 28)
	  && newDate <= DateTime::DateTime(2050, 1, 22) ){
		printJapaneseLunisolarCalendar(newDate);
	}
}

私の環境には、Visual C++ ビルドツール 2019がインストールされています。
※参考:Visual C++ ビルドツール 2019 インストール手順

VS2019用 x64 Native Tools コマンドプロンプトを起動します。
共通言語ランタイム (CLR) 機能を使用するために、コンパイルオプション/clrをつけてコンパイルします。

旧暦としては、令和は3月27日から始まるようです。※旧暦カレンダーで検証

実行結果
d:\Dev>cl /clr JCal.cpp
Microsoft(R) C/C++ Optimizing Compiler Version 19.24.28314
Microsoft (R) .NET Framework の場合 バージョン 4.08.4220.0
Copyright (C) Microsoft Corporation.  All rights reserved.

JCal.cpp
Microsoft (R) Incremental Linker Version 14.24.28314.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:JCal.exe
JCal.obj

d:\Dev>JCal.exe 2019 4 30
西暦 : 2019/04/30
和暦 : 平成 0031/04/30
旧暦 : 平成 0031/03/26
干支 : 己亥
六曜 : 仏滅

d:\Dev>JCal.exe 2019 5 1
西暦 : 2019/05/01
和暦 : 令和 0001/05/01
旧暦 : 令和 0001/03/27
干支 : 己亥
六曜 : 大安

D言語、C++/CLIラッピングのソースコード

前準備が長くなりましたが、ここからが本題です。
.NET APIを呼び出すためのC++/CLIラッピング処理の実装例です。
旧暦、和暦の取得結果をstruct JCALにセットするシンプルな関数です。
D言語から呼び出せるように、extern "C" __declspec(dllexport)属性を付与しています。

JCalDll.cpp
using namespace System;
using namespace System::Globalization;

typedef struct {
	int era;
	int year;
	int month;
	int day;
	int leapMonth;
	int zodiac;
	int kanshi;
	int chishi;
	int rokuyo;
} JCAL;

#define VC_DLL_EXPORTS extern "C" __declspec(dllexport)

VC_DLL_EXPORTS void __cdecl getJapaneseCalendar(int year, int month, int day, JCAL &jcal)
{
	DateTime newDate = DateTime::DateTime(year, month, day);
	JapaneseCalendar^ jc = gcnew JapaneseCalendar();
	jcal.era   = jc->GetEra(newDate);
	jcal.year  = jc->GetYear(newDate);
	jcal.month = jc->GetMonth(newDate);
	jcal.day   = jc->GetDayOfMonth(newDate);
}

VC_DLL_EXPORTS void __cdecl getJapaneseLunisolarCalendar(int year, int month, int day, JCAL &jcal)
{
	DateTime newDate = DateTime::DateTime(year, month, day);
	JapaneseLunisolarCalendar^ jlc = gcnew JapaneseLunisolarCalendar();
	jcal.era   = jlc->GetEra(newDate);
	jcal.year  = jlc->GetYear(newDate);
	jcal.month = jlc->GetMonth(newDate);
	jcal.day   = jlc->GetDayOfMonth(newDate);
	jcal.leapMonth = 0;
	//閏月を取得
	if ( jcal.year > 0 ){
		int leapMonth = jlc->GetLeapMonth(jcal.year, jcal.era);
		if ( jcal.month == leapMonth ){
			jcal.leapMonth = 1;
		}
		//閏月含む場合の月を補正
		if ( (leapMonth > 0) && (jcal.month >= leapMonth) ){
			jcal.month = jcal.month - 1;	//旧暦月の補正
		}
	}
	// 干支(天干、地支)
	jcal.zodiac = jlc->GetSexagenaryYear(newDate);
	jcal.kanshi = jlc->GetCelestialStem(jcal.zodiac);
	jcal.chishi = jlc->GetTerrestrialBranch(jcal.zodiac);
	
	// 六曜(大安・赤口・先勝・友引・先負・仏滅)
	// (月 + 日) % 6 の余り
	jcal.rokuyo = (jcal.month + jcal.day) % 6;
}

次にD言語C++/CLIラッピングを呼び出し、旧暦、和暦を表示するプログラムの実装例です。
取得したい情報をstructで定義、メモリを確保しC++/CLIに渡しています。
pragma(lib, "JCalDll")extern (Windows)~を書くだけで、C++/CLI側の関数を呼び出せるので、思っていたより簡単に実装できました。

oldcal.d
import std.algorithm;
import std.conv;
import std.datetime;
import std.format;
import std.range;
import std.stdio;
import core.sys.windows.windows;

struct JCAL {
	int era;
	int year;
	int month;
	int day;
	int leapMonth;
	int zodiac;
	int kanshi;
	int chishi;
	int rokuyo;
}

pragma(lib, "JCalDll");

extern (Windows) nothrow @nogc {
	void getJapaneseCalendar(int, int, int, ref JCAL);
	void getJapaneseLunisolarCalendar(int, int, int, ref JCAL);
}

string[] sEra =
	[ "", "明治", "大正", "昭和", "平成", "令和" ];
string[] sKanshi =
	[ "", "甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸" ];
string[] sChishi =
	[ "", "子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥" ];
string[] sRokuyo =
	[ "大", "赤", "勝", "友", "負", "仏" ];
//	[ "大安", "赤口", "先勝", "友引", "先負", "仏滅" ];

void main(string[] args)
{
	Date dt = Clock.currTime().to!Date;
	if ( args.length > 2 ){
		dt = Date(args[1].to!int, args[2].to!int, 1);
	} else {
		dt = Date(dt.year, dt.month, 1);
	}
	writef("\n%4d年 %2d月", dt.year, dt.month);
	JCAL jcal;
	string line1 = "     |".cycle.take(dt.dayOfWeek * 6).to!string;
	string line2 = "      |".cycle.take(dt.dayOfWeek * 7).to!string;
	with ( jcal ){
		getJapaneseCalendar(dt.year, dt.month, 1, jcal);
		writef("(%s %2d年) ", sEra[era], year);
		getJapaneseLunisolarCalendar(dt.year, dt.month, 1, jcal);
		writefln("[%s%s]", sKanshi[kanshi], sChishi[chishi]);
		writefln("%-(  %s  |%)", ["日", "月", "火", "水", "木", "金", "土" ]);
		for ( int d = 1; d <= dt.daysInMonth; d++ ){
			getJapaneseLunisolarCalendar(dt.year, dt.month, d, jcal);
			line1 ~= format("%2d %s |", d, sRokuyo[rokuyo]);
			line2 ~= format("%2d/%2d%s|", month, day,(leapMonth == 1) ? "*" : " ");
		}
	}
	int num = (cast(int)line2.length / (7 * 7)) + 1;
	string line0 = "-".cycle.take(num * 7 * 7).to!string;
	roundRobin(line0.chunks(7 * 7), line1.chunks(6 * 7), line2.chunks(7 * 7))
		.each!(s => writefln("%s", s));
}

D言語ソースコードの補足説明

実行時にパラメータなしだと今日の年月で処理します。argsパラメータで年月を指定することも可能です。

string line1には、全角文字が各日付ごとに1文字含まれます。このため1日の表示幅が6文字となります。
新暦の日付で2文字、半角スペース1文字、六曜表示または全角スペースで1文字、半角スペース1文字、|の1文字の順で合計6文字です。

string line2はすべて半角で、1日の表示幅が7文字となります。
旧暦の年月で5文字、半角スペースまたはうるう月は*で1文字、|の1文字の順で合計7文字です。

string line0は、横線-です。

line0 line1 line2を7日ごとに区切って改行するためにchunks、交互に表示するためにroundRobinを使います。
chunks
roundRobin

コンパイル

VS2019用 x64 Native Tools コマンドプロンプトを起動します。
C++/CLIコンパイル時に、オプション/clr/LDを付けます。JCalDll.libJCalDll.dllが生成されます。
JCalDll.libは、D言語ソースのコンパイル時に必要です。JCalDll.dllは、oldcal.exe実行時に必要です。

dmdでのコンパイル時は、64bitコード生成のためにオプション-m64をつけます。
ldc2をインストールしていれば、ldc2 oldcal.dでもOKです。

コンパイル
d:\Dev>cl /clr /LD JCalDll.cpp
Microsoft(R) C/C++ Optimizing Compiler Version 19.24.28314
Microsoft (R) .NET Framework の場合 バージョン 4.08.4220.0
Copyright (C) Microsoft Corporation.  All rights reserved.

JCalDll.cpp
Microsoft (R) Incremental Linker Version 14.24.28314.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:JCalDll.dll
/dll
/implib:JCalDll.lib
JCalDll.obj
   ライブラリ JCalDll.lib とオブジェクト JCalDll.exp を作成中

d:\Dev>dmd -m64 oldcal.d

実行結果

表示される日本語の文字コードはUTF-8なので、先にchcp 65001を実行してください。
Shift-JISで日本語出力したい場合は、文字コード変換処理をソースコードに加える必要があります。

私の環境のコマンドプロンプトでは、Cicaフォントを使っています。
このため、実行結果では全角スペースが四角の枠で表示されています。

これで、自分の誕生月の旧暦カレンダーも見られそうです。
ただし、JapaneseLunisolarCalendarクラスのGetLeapMonthの仕様で、西暦1960年1月27(旧暦の1959年)以前のうるう月を取得しようとするとExceptionが発生するため、西暦1960年2月以降の旧暦カレンダーしか表示できません。上限は西暦2049年12月です。
無題.png

今回使用した参考情報

JapaneseCalendarクラス
JapaneseLunisolarCalendarクラス
オブジェクト演算子 (^) へのハンドル (C++/CLI および C++/CX)
ref new、gcnew (C++/CLI および C++/CX)

C#のメソッドをC++から呼ぶ方法
C#をC++/CLIでラップしてC++アプリから呼ぶ

六曜・月齢・旧暦カレンダー
年・月・日の干支
六曜はどのように決まるのか?六曜の計算方法は?
c#で六曜の計算

今回使用していない情報だけど参考になりそうな情報

C++/CLIラッピング入門
C++のモジュールからC#のDLLを呼び出してみる
C++からC# DLLを直接利用する方法
Calling-NET-from-D

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1