C# Programming
Time Calculation
Relative Time
Coding Techniques
Software Development

Calculate relative time in C#

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Calculating relative time in C# means converting a DateTime or DateTimeOffset into a human-readable string like "5 minutes ago", "2 days ago", or "just now." This is common in social media feeds, comment timestamps, and notification systems. The core approach uses TimeSpan to compute the difference between the target time and the current time, then selects the appropriate unit (seconds, minutes, hours, days, months, years) based on the magnitude of the difference.

Basic Implementation

csharp
1public static string GetRelativeTime(DateTime dateTime)
2{
3    TimeSpan ts = DateTime.UtcNow - dateTime;
4
5    if (ts.TotalSeconds < 60)
6        return ts.Seconds <= 1 ? "just now" : $"{ts.Seconds} seconds ago";
7
8    if (ts.TotalMinutes < 60)
9        return ts.Minutes == 1 ? "1 minute ago" : $"{ts.Minutes} minutes ago";
10
11    if (ts.TotalHours < 24)
12        return ts.Hours == 1 ? "1 hour ago" : $"{ts.Hours} hours ago";
13
14    if (ts.TotalDays < 30)
15        return ts.Days == 1 ? "yesterday" : $"{ts.Days} days ago";
16
17    if (ts.TotalDays < 365)
18    {
19        int months = (int)(ts.TotalDays / 30);
20        return months == 1 ? "1 month ago" : $"{months} months ago";
21    }
22
23    int years = (int)(ts.TotalDays / 365);
24    return years == 1 ? "1 year ago" : $"{years} years ago";
25}
csharp
1// Usage
2Console.WriteLine(GetRelativeTime(DateTime.UtcNow.AddMinutes(-5)));
3// "5 minutes ago"
4
5Console.WriteLine(GetRelativeTime(DateTime.UtcNow.AddHours(-3)));
6// "3 hours ago"
7
8Console.WriteLine(GetRelativeTime(DateTime.UtcNow.AddDays(-1)));
9// "yesterday"

Supporting Future Dates

Extend the function to handle both past and future timestamps:

csharp
1public static string GetRelativeTime(DateTime dateTime)
2{
3    TimeSpan ts = DateTime.UtcNow - dateTime;
4    bool isFuture = ts.TotalSeconds < 0;
5
6    if (isFuture) ts = ts.Negate();
7
8    string suffix = isFuture ? "from now" : "ago";
9
10    if (ts.TotalSeconds < 60)
11        return isFuture ? "in a moment" : "just now";
12
13    if (ts.TotalMinutes < 60)
14    {
15        int mins = (int)ts.TotalMinutes;
16        return $"{mins} {(mins == 1 ? "minute" : "minutes")} {suffix}";
17    }
18
19    if (ts.TotalHours < 24)
20    {
21        int hours = (int)ts.TotalHours;
22        return $"{hours} {(hours == 1 ? "hour" : "hours")} {suffix}";
23    }
24
25    if (ts.TotalDays < 30)
26    {
27        int days = (int)ts.TotalDays;
28        if (days == 1) return isFuture ? "tomorrow" : "yesterday";
29        return $"{days} days {suffix}";
30    }
31
32    if (ts.TotalDays < 365)
33    {
34        int months = (int)(ts.TotalDays / 30);
35        return $"{months} {(months == 1 ? "month" : "months")} {suffix}";
36    }
37
38    int years = (int)(ts.TotalDays / 365);
39    return $"{years} {(years == 1 ? "year" : "years")} {suffix}";
40}
csharp
1Console.WriteLine(GetRelativeTime(DateTime.UtcNow.AddHours(2)));
2// "2 hours from now"
3
4Console.WriteLine(GetRelativeTime(DateTime.UtcNow.AddDays(1)));
5// "tomorrow"

Using DateTimeOffset for Time Zones

csharp
1public static string GetRelativeTime(DateTimeOffset dateTime)
2{
3    TimeSpan ts = DateTimeOffset.UtcNow - dateTime;
4    // Same logic as above...
5}
6
7// Works correctly with different time zones
8var tokyoTime = new DateTimeOffset(2025, 3, 1, 10, 0, 0, TimeSpan.FromHours(9));
9Console.WriteLine(GetRelativeTime(tokyoTime));

DateTimeOffset includes timezone information, making comparisons across time zones correct. Prefer DateTimeOffset over DateTime when dealing with user-submitted timestamps.

Extension Method Pattern

csharp
1public static class DateTimeExtensions
2{
3    public static string ToRelativeTime(this DateTime dateTime)
4    {
5        return GetRelativeTime(dateTime);
6    }
7
8    public static string ToRelativeTime(this DateTimeOffset dateTime)
9    {
10        return GetRelativeTime(dateTime.UtcDateTime);
11    }
12
13    private static string GetRelativeTime(DateTime dateTime)
14    {
15        TimeSpan ts = DateTime.UtcNow - dateTime;
16        // ... implementation
17    }
18}
19
20// Usage becomes clean and fluent
21var posted = DateTime.UtcNow.AddHours(-2);
22Console.WriteLine(posted.ToRelativeTime());  // "2 hours ago"

Using Humanizer Library

The Humanizer NuGet package handles relative time with localization support:

bash
dotnet add package Humanizer
csharp
1using Humanizer;
2
3DateTime.UtcNow.AddHours(-3).Humanize();
4// "3 hours ago"
5
6DateTime.UtcNow.AddDays(-1).Humanize();
7// "yesterday"
8
9DateTime.UtcNow.AddMonths(-2).Humanize();
10// "2 months ago"
11
12// With precision
13DateTime.UtcNow.AddHours(-2).AddMinutes(-30).Humanize(precision: 2);
14// "2 hours, 30 minutes ago"
15
16// Localization
17DateTime.UtcNow.AddHours(-3).Humanize(culture: new CultureInfo("es"));
18// "hace 3 horas"

Humanizer handles edge cases, pluralization, and localization that a hand-rolled solution would need to implement manually.

Formatting for Display

csharp
1public static string GetSmartTimestamp(DateTime dateTime)
2{
3    TimeSpan ts = DateTime.UtcNow - dateTime;
4
5    // Less than a day: relative time
6    if (ts.TotalHours < 24)
7        return GetRelativeTime(dateTime);
8
9    // Less than a week: day name and time
10    if (ts.TotalDays < 7)
11        return dateTime.ToString("dddd 'at' h:mm tt");
12    // "Tuesday at 3:45 PM"
13
14    // Same year: month and day
15    if (dateTime.Year == DateTime.UtcNow.Year)
16        return dateTime.ToString("MMM d");
17    // "Feb 15"
18
19    // Different year: full date
20    return dateTime.ToString("MMM d, yyyy");
21    // "Dec 3, 2024"
22}

This pattern (used by Twitter, Facebook, and other platforms) shows relative time for recent events and absolute dates for older ones.

Common Pitfalls

  • Using DateTime.Now instead of DateTime.UtcNow: DateTime.Now uses the server's local time zone. If the server and client are in different zones, the relative time is wrong. Always compute with UTC and convert for display.
  • Integer division for months and years: Dividing TotalDays by 30 or 365 is approximate. February has 28-29 days, and months vary from 28-31. For precise month/year calculations, compare the DateTime properties directly (e.g., year and month fields).
  • Not handling negative TimeSpan: If the input time is in the future, TimeSpan is negative. Without a check, you get "-5 minutes ago." Either reject future dates or handle them with "from now" wording.
  • Forgetting to handle the singular form: "1 minutes ago" looks wrong. Always check for the singular case and use "1 minute ago" instead. The Humanizer library handles this automatically.
  • Not updating displayed timestamps: In SPAs and real-time apps, relative timestamps become stale. A "5 minutes ago" label never updates to "10 minutes ago" unless you refresh. Use a timer to periodically recalculate displayed timestamps.

Summary

  • Use TimeSpan to compute the difference between now and the target DateTime
  • Check thresholds in order (seconds, minutes, hours, days, months, years) and return the appropriate string
  • Always use DateTime.UtcNow or DateTimeOffset.UtcNow for correct cross-timezone behavior
  • Use the Humanizer NuGet package for production-grade relative time with localization and pluralization
  • Combine relative time for recent events with absolute dates for older events for the best user experience

Course illustration
Course illustration

All Rights Reserved.