C#でExcel Interop(Microsoft.Office.Interop.Excel)のApplication.Evaluate()を使うと、TODAY()やNOW()が日付ではなく「44424」「44424.6139…」のような数値で返って戸惑うことがあります。これはExcelが日付/時刻を内部的にシリアル値(連番)で扱う仕様が原因で、C#側でOADate(OLE Automation Date)をDateTimeへ変換すれば意図通りに扱えます。
現象:Application.Evaluate("TODAY()") が日付ではなく数値になる
まずは起きていることを整理します。以下のようにApplication.Evaluate()で数式を評価すると、TODAY()やNOW()が「日付/時刻」ではなく「数値」で返ってくるケースがあります。
using Excel = Microsoft.Office.Interop.Excel;
public dynamic EvaluteFormula(string expressionFormula)
{
Excel.Application excelApp = new Excel.Application();
return excelApp.Evaluate(expressionFormula);
}
代表的な戻り値の例です。
| 評価した式 | 戻り値の例 | 見た目 | 実体 |
|---|---|---|---|
TODAY() | 44424 | 「日付なのに数値」 | 日付のシリアル値(整数部) |
NOW() | 44424.613976967594 | 「小数が付いてさらに謎」 | 日付(整数部)+時刻(小数部) |
結論から言うと、これは不具合ではありません。Excelの「日付」を計算機として扱うための仕様が、そのままC#側に露出している状態です。
原因:Excelの日付は内部的に「シリアル値(連番)」として管理されている
Excelでは、日付や時刻は“見た目”こそ「2026/01/05」「14:30」などですが、内部では次のような数値として扱われます。
- 整数部:基準日からの経過日数(1日=1)
- 小数部:時刻(1日=1.0とした割合)
この設計のおかげで、Excelでは「日付を足し算/引き算」「時刻差を計算」「締め日から何日後」などが、単純な数値計算で成立します。たとえば、セルに入れた日付同士を引けば日数差が出ますし、NOW()+1で「明日の今の時刻」が求まります。
小数部が時刻になるイメージ
“時刻が小数”と言われてもピンと来ない場合は、次の対応表を見ると理解しやすいです。
| 小数部 | 意味(1日=24時間) | 時刻の目安 |
|---|---|---|
| 0.0 | 0%(日付の開始) | 00:00:00 |
| 0.25 | 25% | 06:00:00 |
| 0.5 | 50% | 12:00:00 |
| 0.75 | 75% | 18:00:00 |
| 0.999988… | ほぼ100% | 23:59:59付近 |
NOW()が返す44424.6139...の「.6139…」は、今日の何時何分かを“1日を1.0”として表した値です。
最短で解決する方法:DateTime.FromOADate()でDateTimeへ変換
Interop経由で返ってきた日付シリアルは、.NET側でOADate(OLE Automation Date)として扱い、DateTime.FromOADate()で変換するのが最も手堅い方法です。
using Excel = Microsoft.Office.Interop.Excel;
Excel.Application app = new Excel.Application();
double oa = (double)app.Evaluate("NOW()");
DateTime dt = DateTime.FromOADate(oa);
これでdtは「Excelが返した日付/時刻」をDateTimeとして扱えるようになります。
ミリ秒が中途半端になることがある(浮動小数点の性質)
NOW()の結果はdoubleなので、変換後のDateTimeが「ミリ秒まで変な値」になることがあります。ログや画面表示で秒単位で十分なら、丸めてしまうと扱いやすいです。
DateTime dt = DateTime.FromOADate(oa);
dt = new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second);
逆に、ミリ秒まで必要ならそのまま使えばOKです。
DateTimeKindに注意(Unspecifiedで返る)
DateTime.FromOADate()が返すDateTimeは、KindがUnspecifiedになります。ExcelのNOW()は基本的にローカル時刻の概念なので、必要ならアプリ側の方針に合わせてDateTimeOffsetに寄せる、またはDateTime.SpecifyKindで明示すると事故が減ります。
DateTime dt = DateTime.FromOADate(oa);
dt = DateTime.SpecifyKind(dt, DateTimeKind.Local);
仕組みを見せたい場合:基準日にAddDaysして変換(ただし注意点あり)
「内部で何をしているのかを明示したい」「OADate変換の仕組みを理解したい」場合は、基準日から日数を足して変換する書き方もできます。
double n = (double)app.Evaluate("TODAY()");
DateTime baseDate = new DateTime(1899, 12, 30);
DateTime today = baseDate.AddDays(n);
ただし、Excelの日付シリアルには歴史的経緯があり、単純な基準日計算だけではズレる境界があります。特に有名なのが「1900年うるう年誤認(1900-02-29が存在する扱い)」です。
1900年うるう年バグと「基準日が1899-12-30」に見える理由
Excel(1900日付システム)は互換性の都合で、存在しない日付「1900-02-29」を日付シリアルに含めてしまっています。これにより、1900-03-01以降の日付は実際の暦と比べてシリアルが1日分ずれた状態になります。
この事情を込みで“COMで受け取ったdouble”を正しくDateTimeに変換するなら、やはりDateTime.FromOADate()に任せるのが安全です。自前実装だと、境界値(特に1900年前後)まで正確に合わせるのが地味に面倒です。
「DateTime」ではなく「表示用の文字列」が欲しい場合
求めているのが「日付としての型」ではなく「そのまま画面に出す文字列」なら、Excel側で整形して文字列として返す方法がシンプルなこともあります。
方法:TEXT関数で整形して返す
string s = (string)app.Evaluate("TEXT(NOW(), \"yyyy/mm/dd hh:mm:ss\")");
この方法のメリットは、C#側で日付書式を考えなくてよい点です。一方で、Excelの書式指定(TEXTの書式)と.NETの書式指定は別物なので、チーム内で混ざるとメンテが難しくなることがあります。運用ルールとして「表示はC#側で統一」「表示はExcel側の書式に合わせる」など、どちらかに寄せるのがおすすめです。
セルに書いて.Textを読む(Excel表示をそのまま取得)
「Excelで表示されている文字(ゼロ埋め、曜日、ローカライズ含む)をそのまま欲しい」なら、シートに数式を入れてRange.Textを読む方法もあります。
Excel.Workbook wb = app.Workbooks.Add();
Excel.Worksheet ws = (Excel.Worksheet)wb.Worksheets[1];
Excel.Range cell = ws.Range["A1"];
cell.NumberFormat = "yyyy/mm/dd hh:mm:ss";
cell.Formula = "=NOW()";
string displayed = cell.Text; // 例: 2021/08/16 14:44:07
ただし、この方法は「ワークブック/シート/セル」を作る分だけ処理コストが上がり、COMオブジェクトも増えるので、頻繁に呼ぶ用途では注意が必要です。
方法ごとの違いが一目で分かる比較表
「何を返したいのか(DateTimeか、シリアルか、表示文字列か)」で選ぶべき手段が変わります。迷ったときは次の表で整理すると決めやすいです。
| 手段 | 取得できるもの | 強み | 注意点 | おすすめ用途 |
|---|---|---|---|---|
Evaluate + FromOADate | DateTime | 速い・シンプル・型として扱える | double→DateTime変換が必要 | ロジックで日付計算したい |
Evaluate + TEXT | 文字列 | Excelの書式で即表示できる | Excel書式依存、引数区切りやエスケープ注意 | ログ/画面にそのまま出したい |
セルに式 → Range.Value | (環境により)DateTime/値 | Excelの“値”として取り出せる | シート操作が必要、COMオブジェクト増 | セル中心の処理のついでに取得 |
セルに式 → Range.Text | 表示文字列 | Excelの見た目と完全一致 | 遅くなりやすい・表示形式依存 | 帳票出力など見た目が重要 |
Interopでよくある落とし穴:日付変換だけで終わらないポイント
TODAY()/NOW()の変換自体は簡単ですが、実運用では別のところでハマりがちです。トラブルを減らすためのチェックポイントをまとめます。
Excelプロセスが残る:Quit()とCOM解放を確実に
new Application()を都度作って終わりにすると、Excel.exeが裏で残り続ける原因になります。最低限、Quit()とCOMオブジェクト解放をtry/finallyで保証しましょう。
using Excel = Microsoft.Office.Interop.Excel;
using System.Runtime.InteropServices;
public static object EvaluateOnce(string formula)
{
Excel.Application app = null;
try
{
app = new Excel.Application
{
Visible = false,
DisplayAlerts = false
};
// 先頭の "=" はあってもなくても通ることが多いが、統一しておくと事故が減る
string expr = formula.StartsWith("=") ? formula : "=" + formula;
return app.Evaluate(expr);
}
finally
{
if (app != null)
{
app.Quit();
Marshal.FinalReleaseComObject(app);
}
}
}
ワークブックやレンジを作った場合は、そちらも同様に解放対象になります(作った順と逆順で解放するのが基本です)。
STAスレッドで動かす
Office InteropはCOMの都合上、実行環境によってはSTAスレッドが必要です。WinForms/WPFなら通常問題になりませんが、コンソールやサービスで動かす場合は注意してください。
1904日付システムで「4年くらいズレる」ことがある
変換した日付がだいたい4年(正確には1462日)ズレる場合、ブックが「1904日付システム」になっている可能性があります。Excelには1900日付システムと1904日付システムがあり、同じ日付でもシリアル値が変わります。
Interopでブックを扱っているなら、次のプロパティで判定できます。
bool is1904 = workbook.Date1904; // Trueなら1904日付システム
もし1904日付システムのシリアル値を扱う必要があるなら、まず「その値がどちらのシステム基準か」を統一しましょう。複数ブックをまたぐ処理では、変換レイヤーを1か所に集約しておくと、後からの修正が楽です。
引数区切り文字(カンマ/セミコロン)に注意
TEXTやIFなど引数を取る関数は、環境の地域設定により区切り文字が「,」ではなく「;」になることがあります。式を組み立てて評価する場合は、Excel側が期待している区切り文字を取得してから組み立てると堅牢です。
// xlListSeparator の値は環境により異なるため、Excelから取得して使う
string sep = app.International[5].ToString(); // 5 = xlListSeparator
string expr = $"TEXT(NOW(){sep} \"yyyy/mm/dd\")";
「自分のPCでは動くのに、別のPCで#NAME?になる」タイプの不具合は、この手のロケール差が原因のことが多いです。
実践向け:Evaluate結果を“日付っぽい値ならDateTimeにする”ユーティリティ例
実務では「この式は日付が返る」と分かっているケースもあれば、汎用的にEvaluateして型を見て処理を分岐したいケースもあります。後者に向けて、最低限の安全策を入れた例を載せます。
using Excel = Microsoft.Office.Interop.Excel;
public static bool TryEvaluateAsDateTime(Excel.Application app, string formula, out DateTime value)
{
value = default;
object raw = app.Evaluate(formula);
// 配列が返るケース(範囲参照など)はここでは対象外にする
if (raw is object[,]) return false;
// 日付/時刻は多くの場合 double(OADate)で返る
if (raw is double oa)
{
value = DateTime.FromOADate(oa);
return true;
}
// 環境や状況により DateTime で返る可能性がある場合の保険
if (raw is DateTime dt)
{
value = dt;
return true;
}
return false;
}
この形にしておくと、式が増えても「日付系の扱い」を一か所で統一できます。さらに必要なら、1904日付システム対応や、秒丸め、タイムゾーン方針(Local/UTC)などもこの層に集約していくのがおすすめです。
補足:TODAY/NOWだけが欲しいなら、Excelに頼らない選択もある
今回の話題は「EvaluateでExcelの式を評価した結果がシリアルで返る」問題ですが、もし目的が単に“今日/今”を取得することだけなら、.NET側で完結できます。
DateTime.Today:今日の日付(00:00:00)DateTime.Now:現在日時(ローカル時刻)
ただし、処理全体がExcelの再計算とセットになっている、ブック側の設定(1904日付システム、丸め、表示形式、タイムゾーン差)に合わせたい、などの理由があるならInteropを継続する価値があります。重要なのは「Excelでやる理由」を明確にして、依存範囲を最小化することです。
まとめ
Application.Evaluate()でTODAY()/NOW()が数値になるのは、Excelが日付/時刻をシリアル値で管理している仕様- 整数部が日付、小数部が時刻(1日=1.0)
- C#側では
DateTime.FromOADate()で変換するのが最も安全で簡単 - 表示文字列が欲しい場合は
TEXTやセルの.Textを使う選択肢もある - 実運用ではCOM解放、1904日付システム、ロケール(区切り文字)などの周辺トラブルもセットで対策する

コメント