はじめに 「日付差ヒューマン表示」は“数字を感覚に変える翻訳機”
「2026/02/18 20:43:00 から 2026/02/18 21:10:00 まで 1620 秒です」
よりも、「27分前」「約30分前」のほうが、圧倒的に“ピンと来る”はずです。
この「時間差を人間の感覚に近い言葉に変換する」のが
「日付差ヒューマン表示(human readable)」のユーティリティです。
C# では DateTime や TimeSpan を使って差を取り、
その差を「秒・分・時間・日・月・年」といった単位に落とし込んで、
条件分岐で文字列を返す、という形で実装できます。
基本の考え方:まずは TimeSpan を作る
「今との差」を TimeSpan にする
一番よくあるのは、「ある日時が今から見てどれくらい前か(あるいは後か)」を表示するパターンです。
DateTime target = new DateTime(2026, 2, 18, 20, 10, 0);
DateTime now = DateTime.Now;
TimeSpan diff = now - target;
C#diff が正なら「過去」、負なら「未来」です。
この TimeSpan の TotalSeconds や TotalMinutes を見ながら、
「何秒前」「何分前」「何時間前」…といった文字列を決めていきます。
最小版の「〜前」表示を作ってみる
秒・分・時間・日だけに絞ったシンプル版
まずは、ざっくりした「〜前」表示を作ってみましょう。
public static class HumanTimeDiff
{
public static string ToHumanReadableAgo(DateTime target, DateTime? now = null)
{
DateTime baseTime = now ?? DateTime.Now;
TimeSpan diff = baseTime - target;
if (diff.TotalSeconds < 0)
{
return ToHumanReadableFromNow(target, baseTime);
}
double seconds = diff.TotalSeconds;
if (seconds < 60)
{
return "たった今";
}
double minutes = diff.TotalMinutes;
if (minutes < 60)
{
int m = (int)Math.Floor(minutes);
return $"{m}分前";
}
double hours = diff.TotalHours;
if (hours < 24)
{
int h = (int)Math.Floor(hours);
return $"{h}時間前";
}
double days = diff.TotalDays;
if (days < 7)
{
int d = (int)Math.Floor(days);
return $"{d}日前";
}
int approxDays = (int)Math.Floor(days);
return $"{approxDays}日前";
}
private static string ToHumanReadableFromNow(DateTime target, DateTime baseTime)
{
TimeSpan diff = target - baseTime;
double seconds = diff.TotalSeconds;
if (seconds < 60)
{
return "まもなく";
}
double minutes = diff.TotalMinutes;
if (minutes < 60)
{
int m = (int)Math.Floor(minutes);
return $"{m}分後";
}
double hours = diff.TotalHours;
if (hours < 24)
{
int h = (int)Math.Floor(hours);
return $"{h}時間後";
}
double days = diff.TotalDays;
int d = (int)Math.Floor(days);
return $"{d}日後";
}
}
C#使い方の例です。
DateTime now = DateTime.Now;
Console.WriteLine(HumanTimeDiff.ToHumanReadableAgo(now.AddSeconds(-10), now)); // たった今
Console.WriteLine(HumanTimeDiff.ToHumanReadableAgo(now.AddMinutes(-5), now)); // 5分前
Console.WriteLine(HumanTimeDiff.ToHumanReadableAgo(now.AddHours(-2), now)); // 2時間前
Console.WriteLine(HumanTimeDiff.ToHumanReadableAgo(now.AddDays(-3), now)); // 3日前
Console.WriteLine(HumanTimeDiff.ToHumanReadableAgo(now.AddMinutes(10), now)); // 10分後
Console.WriteLine(HumanTimeDiff.ToHumanReadableAgo(now.AddDays(2), now)); // 2日後
C#ここでの重要ポイントは、「まず TimeSpan を作り、閾値(60秒、60分、24時間…)で段階的に分岐する」という構造です。
この“段階分け”さえ理解できれば、あとは表現を増やしていくだけです。
もう少しリッチに:週・月・年も扱う
「3週間前」「2か月前」「1年前」まで広げる
日単位を超えると、「何日前」よりも「何週間前」「何か月前」「何年前」のほうが自然なことが多いです。
ざっくりでよければ、日数から近似してしまって構いません。
public static string ToHumanReadableAgoRich(DateTime target, DateTime? now = null)
{
DateTime baseTime = now ?? DateTime.Now;
TimeSpan diff = baseTime - target;
if (diff.TotalSeconds < 0)
{
return ToHumanReadableFromNowRich(target, baseTime);
}
double seconds = diff.TotalSeconds;
if (seconds < 60)
{
return "たった今";
}
double minutes = diff.TotalMinutes;
if (minutes < 60)
{
int m = (int)Math.Floor(minutes);
return $"{m}分前";
}
double hours = diff.TotalHours;
if (hours < 24)
{
int h = (int)Math.Floor(hours);
return $"{h}時間前";
}
double days = diff.TotalDays;
if (days < 7)
{
int d = (int)Math.Floor(days);
return $"{d}日前";
}
if (days < 30)
{
int w = (int)Math.Floor(days / 7);
return $"{w}週間前";
}
if (days < 365)
{
int months = (int)Math.Floor(days / 30);
return $"{months}か月前";
}
int years = (int)Math.Floor(days / 365);
return $"{years}年前";
}
private static string ToHumanReadableFromNowRich(DateTime target, DateTime baseTime)
{
TimeSpan diff = target - baseTime;
double seconds = diff.TotalSeconds;
if (seconds < 60)
{
return "まもなく";
}
double minutes = diff.TotalMinutes;
if (minutes < 60)
{
int m = (int)Math.Floor(minutes);
return $"{m}分後";
}
double hours = diff.TotalHours;
if (hours < 24)
{
int h = (int)Math.Floor(hours);
return $"{h}時間後";
}
double days = diff.TotalDays;
if (days < 7)
{
int d = (int)Math.Floor(days);
return $"{d}日後";
}
if (days < 30)
{
int w = (int)Math.Floor(days / 7);
return $"{w}週間後";
}
if (days < 365)
{
int months = (int)Math.Floor(days / 30);
return $"{months}か月後";
}
int years = (int)Math.Floor(days / 365);
return $"{years}年後";
}
C#ここでは「30日を1か月」「365日を1年」としてかなりラフに近似しています。
厳密な暦(うるう年・月ごとの日数)を気にする用途なら、DateTime の年・月・日を直接比較する別ロジックが必要ですが、
「SNSの“◯か月前”」のような用途なら、このくらいのラフさで十分なことが多いです。
しきい値設計が“使いやすさ”を決める
「59分前」か「1時間前」か、どこで切り替えるか
ヒューマン表示で一番センスが出るのが「どこで表現を切り替えるか」です。
例えば、
59分前までは「◯分前」、60分を超えたら「◯時間前」にするのか。
90分までは「1時間前」、それ以降を「2時間前」にするのか。
など、細かいところで印象が変わります。
実装としては、
if (minutes < 60) のような閾値を、
要件に合わせて調整していくことになります。
最初はシンプルに「60で切り替え」で十分です。
実際に画面に出してみて、「ここは“約1時間前”のほうが自然だな」と感じたら、
条件を微調整していく、という育て方でOKです。
ローカライズと表現のバリエーション
「約」「ちょうど」「さっき」などのニュアンス
今までの例では、すべて「◯分前」「◯時間前」と機械的な表現にしていますが、
日本語としては「約◯時間前」「さっき」「少し前」など、
もう少し柔らかい表現もあり得ます。
例えば、
1分未満 → 「たった今」
1〜5分 → 「数分前」
5〜59分 → 「◯分前」
1〜2時間 → 「約◯時間前」
それ以上 → 「◯時間前」
のように、範囲ごとに文言を変えることもできます。
ここは完全に“プロダクトのキャラクター”の話なので、
ユーティリティ側は「どの範囲か」を返し、
文言は別の層で決める、という設計もアリです。
実務での注意点:基準時刻を引数で受け取れるようにする
テストしやすさと再現性のために
サンプルでは DateTime.Now をその場で呼んでいますが、
実務では「基準時刻(now)」を引数で受け取れるようにしておくとテストがしやすくなります。
今回のコードも、DateTime? now = null として、
テスト時には固定の now を渡せるようにしていました。
public static string ToHumanReadableAgo(DateTime target, DateTime? now = null)
{
DateTime baseTime = now ?? DateTime.Now;
...
}
C#これをやっておくと、
「2026/02/18 20:00 を基準に、2026/02/18 19:30 は“30分前”と表示される」
といったテストを、いつ実行しても同じ結果で確認できます。
まとめ 「日付差ヒューマン表示ユーティリティ」は“時間差を感覚に変える翻訳レイヤー」
日付差ヒューマン表示は、
単に「秒数を文字列にする」のではなく、
「人間がパッと理解できる粒度と表現に変換する」ためのレイヤーです。
押さえておきたいポイントは次の通りです。
差は必ず TimeSpan として取り、TotalSeconds / TotalMinutes / TotalHours / TotalDays を使って段階的に分岐する。
「〜前」と「〜後」を両方サポートするなら、符号(過去か未来か)で処理を分ける。
秒・分・時間・日・週・月・年の“しきい値”をどう設計するかが、使いやすさと自然さを決める。
ローカライズやニュアンス(約・さっき・まもなく)は、範囲ごとに文言を変えることで表現できる。DateTime.Now にベタ依存せず、「基準時刻」を引数で受け取れるようにしておくと、テストと再現性がぐっと良くなる。
ここまで押さえておけば、
「生の日時をそのまま表示する」だけの世界から一歩進んで、
“ユーザーの感覚に寄り添った、実務で使える日付差ヒューマン表示ユーティリティ”を
自分の C# プロジェクトに自然に組み込めるようになります。
