C# Tips | 日付・時間処理:日付差ヒューマン表示

C# C#
スポンサーリンク

はじめに 「日付差ヒューマン表示」は“数字を感覚に変える翻訳機”

「2026/02/18 20:43:00 から 2026/02/18 21:10:00 まで 1620 秒です」
よりも、「27分前」「約30分前」のほうが、圧倒的に“ピンと来る”はずです。

この「時間差を人間の感覚に近い言葉に変換する」のが
「日付差ヒューマン表示(human readable)」のユーティリティです。

C# では DateTimeTimeSpan を使って差を取り、
その差を「秒・分・時間・日・月・年」といった単位に落とし込んで、
条件分岐で文字列を返す、という形で実装できます。


基本の考え方:まずは TimeSpan を作る

「今との差」を TimeSpan にする

一番よくあるのは、「ある日時が今から見てどれくらい前か(あるいは後か)」を表示するパターンです。

DateTime target = new DateTime(2026, 2, 18, 20, 10, 0);
DateTime now    = DateTime.Now;

TimeSpan diff = now - target;
C#

diff が正なら「過去」、負なら「未来」です。
この TimeSpanTotalSecondsTotalMinutes を見ながら、
「何秒前」「何分前」「何時間前」…といった文字列を決めていきます。


最小版の「〜前」表示を作ってみる

秒・分・時間・日だけに絞ったシンプル版

まずは、ざっくりした「〜前」表示を作ってみましょう。

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# プロジェクトに自然に組み込めるようになります。

タイトルとURLをコピーしました