Advanced Work with Dates and Time in C#: From Theory to Real-World Tasks
Working with dates and time is one of the most common and simultaneously tricky tasks in development. At a basic level, we simply create DateTime.Now and output it to the console. But when a project outgrows toy-like boundaries, complexities arise: daylight saving time, different time zones, high-precision calculations, or the need to work with dates without a time zone. In this article, we will cover advanced techniques for working with dates and time in C# (.NET) that will help you write reliable and predictable code.
1. DateTime, DateTimeOffset, and TimeSpan: Choosing the Right Tool
Many developers use DateTime by default without thinking about its limitations. At an advanced level, it is important to understand the difference between the three main types.
DateTime
Stores a date and time. Its main problem is ambiguity. The Kind property (Local, Utc, Unspecified) is not always set explicitly, leading to errors during serialization or comparison.
DateTimeOffset
Stores a date, time, and offset from UTC. This is the recommended type for storing moments in time, as it unambiguously defines a point on the timeline. For example, 2025-03-15T14:00:00+03:00 is always a specific moment, regardless of the user's time zone.
TimeSpan
Represents a time interval, not a specific moment. Used for arithmetic: difference between dates, timeouts, operation duration.
// Example: DateTime vs DateTimeOffsetDateTime localNow = DateTime.Now; // Kind = LocalDateTime utcNow = DateTime.UtcNow; // Kind = Utc
DateTimeOffset preciseNow = DateTimeOffset.Now; // Always with offset indication
// Comparison: DateTimeOffset is more unambiguousConsole.WriteLine(preciseNow.Offset); // +03:00 or -05:00 etc.Tip: For new projects, use DateTimeOffset for storing moments in time, TimeSpan for intervals, and DateTime only when truly necessary (e.g., for compatibility with legacy code).
2. Parsing and Formatting: How to Avoid FormatException
Parsing dates from strings is a source of many errors, especially if the strings come from users or different cultures.
Culture-Dependent Parsing
Use CultureInfo.InvariantCulture for internal data exchange (e.g., JSON, API). For user input, consider the user's culture.
// Parsing with culture awarenessstring dateString = "15.03.2025";var ruCulture = new CultureInfo("ru-RU");DateTime parsedDate = DateTime.Parse(dateString, ruCulture);
// Invariant parsing (recommended for API)string isoString = "2025-03-15T14:00:00Z";DateTimeOffset parsedIso = DateTimeOffset.Parse(isoString, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind);TryParseExact for Reliability
If the string format is known, use TryParseExact with an explicit format. This eliminates unexpected exceptions.
string input = "2025-03-15 14:30";string format = "yyyy-MM-dd HH:mm";if (DateTime.TryParseExact(input, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime result)){ Console.WriteLine($"Success: {result}");}else{ Console.WriteLine("Parsing error");}Custom Formatting
Standard specifiers (d, D, t, T) are good, but for precise control, use custom format strings.
DateTime now = DateTime.Now;Console.WriteLine(now.ToString("yyyy-MM-dd HH:mm:ss.fff")); // 2025-03-15 14:30:45.123Console.WriteLine(now.ToString("dd MMMM yyyy, dddd")); // 15 March 2025, Saturday