はじめに
この記事では、以下のソースコードを実装します。
- 旧暦から新暦への変換プログラム
- 和暦から新暦への変換プログラム
.NET APIの利用
.NET API
には、JapaneseCalendarクラスやJapaneseLunisolarCalendarクラスが用意されており、これらを利用します。
D言語から.NET API呼び出しの実現方法
私が調べた限り、C++/CLIラッピング
を経由する方法が簡単だと思いました。
今回はこの方法を採用します。
※参考までに、記事を書くために調べた情報リンクを最後に掲載します。
C#
でDLLを作成し、D言語から呼び出す方法です。
前準備1(C#での実装)
D言語で実装する前に、C#
で旧暦や和暦を取得する処理を実装しました。
実行時にパラメータなしだと今日の日付の旧暦や和暦、年月日パラメータを指定するとその日付の旧暦や和暦を表示します。
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)より引用
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)
属性を付与しています。
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
側の関数を呼び出せるので、思っていたより簡単に実装できました。
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.lib
とJCalDll.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月です。
今回使用した参考情報
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