Advanced Work with Dates and Time in C#: From Basic Operations to High-Performance Solutions
\nWorking with dates and time is one of the most common and simultaneously tricky tasks in development. An error in a time zone or incorrect formatting can lead to serious bugs in production. In this article, we will go beyond the basic DateTime constructors and explore advanced techniques: DateTimeOffset, working with time zones, high-precision measurements via Stopwatch, as well as performance and thread-safety considerations. The material is aimed at developers who are confident in C# and want to write reliable code for .NET.
1. DateTime, DateTimeOffset, and TimeSpan: Choosing the Right Type
\nMany developers use DateTime by default, but in the modern world of distributed systems, this is often not the best choice. Let's look at the key differences.
DateTime vs DateTimeOffset
\nDateTime stores a point in time without an explicit reference to a time zone. You can specify Kind (Local, Utc, Unspecified), but this is just a label, not complete offset information. DateTimeOffset, on the other hand, always stores the offset from UTC, making it unambiguous and preferable for storage in databases and APIs.
// DateTime — can be misleading\nDateTime localNow = DateTime.Now; // Kind = Local\nDateTime utcNow = DateTime.UtcNow; // Kind = Utc\n\n// DateTimeOffset — always an explicit offset\nDateTimeOffset preciseNow = DateTimeOffset.Now; // +03:00 (example)\nDateTimeOffset utcOffset = DateTimeOffset.UtcNow; // +00:00\n\n// Conversion between them\nDateTimeOffset fromDateTime = new DateTimeOffset(2024, 12, 25, 10, 0, 0, TimeSpan.FromHours(3));\nConsole.WriteLine(fromDateTime.Offset); // 03:00:00\n\nRecommendation: For storing a point in time (e.g., record creation date), use DateTimeOffset. For displaying to the user in local time, convert at the presentation layer.
TimeSpan for Intervals
\nTimeSpan does not store a date, only a time interval. It is an indispensable tool for date arithmetic and measuring operation duration.
DateTimeOffset start = DateTimeOffset.UtcNow;\n// ... some long-running process ...\nDateTimeOffset end = DateTimeOffset.UtcNow;\n\nTimeSpan duration = end - start;\nConsole.WriteLine($"Operation took {duration.TotalMilliseconds} ms");\n// Creating an interval\nTimeSpan workDay = new TimeSpan(8, 0, 0); // 8 hours\nDateTimeOffset deadline = DateTimeOffset.UtcNow.Add(workDay);\n\n\n2. Parsing and Formatting: Culture, ISO 8601, and Performance
\nIncorrect parsing is a source of bugs in applications working with different regional settings. Let's look at advanced methods.
\n\nParseExact and TryParseExact
\nUse ParseExact for strings with a known, strict format. This improves code reliability and readability.
string dateString = "2024-12-25T10:30:00+03:00";\n// ISO 8601 — universal format\nDateTimeOffset parsed = DateTimeOffset.ParseExact(\n dateString,\n "yyyy-MM-dd'T'HH:mm:sszzz",\n CultureInfo.InvariantCulture\n);\nConsole.WriteLine(parsed); // 25.12.2024 10:30:00 +03:00\n\n// Safe parsing with TryParseExact\nif (DateTimeOffset.TryParseExact(\n "2024/12/25",\n new[] { "yyyy/MM/dd", "dd.MM.yyyy" },\n CultureInfo.InvariantCulture,\n DateTimeStyles.None,\n out DateTimeOffset result))\n{\n Console.WriteLine($"Successfully parsed: {result}");\n}\n\nTip: For storing dates in logs and APIs, always use the ISO 8601 format ("o" specifier). This guarantees unambiguous reading by any system.
Culture-Aware Formatting
\nWhen displaying to the user, consider their locale, but for internal exchange, use invariant