DateTime
Reviewed & published by Brayan K
Almost every program eventually needs to work with dates and times — timestamps, countdowns, "how long ago", scheduling. C#'s DateTime type handles all of it: creating dates, formatting them for humans, doing arithmetic, measuring durations with TimeSpan, and safely parsing dates from text. Get comfortable here and a whole category of tasks becomes easy.
Part of the free C# course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn
- Create dates with Now, Today, UtcNow and constructors
- Read parts like Year, Month, DayOfWeek
- Format dates with standard & custom specifiers
- Do date arithmetic with AddDays, AddYears
- Measure durations with TimeSpan
- Parse dates safely with TryParse and ParseExact
💡 Real-World Analogy
Think of a DateTime as a precise pin stuck into a calendar — a single instant. A TimeSpan, by contrast, is the distance between two pins — "three days and four hours" — with no fixed place on the calendar. Subtract one pin from another and you get the gap (a TimeSpan); add a gap to a pin and you land on a new pin (a DateTime). Keeping "an instant" and "a duration" separate in your head is the key to working with time.
1. Creating & Reading Dates
DateTime.Now, DateTime.Today and DateTime.UtcNow give you "right now" in different forms, while the constructor builds a specific date from year/month/day (and optionally a time). Once you have a date, you can read parts like .Year, .Month and .DayOfWeek.
using System;
class Program
{
static void Main()
{
// The current date and time on this machine:
DateTime now = DateTime.Now; // includes time zone offset
DateTime today = DateTime.Today; // midnight today (no time part)
DateTime utc = DateTime.UtcNow; // current time in UTC
// Build a specific date: year, month, day [, hour, minute, second]
DateTime launch = new DateTime(2025, 12, 25, 9, 30, 0);
Console.WriteLine(launch); // 25/12/2025 09:30:00 (locale dependent)
// Read individual parts:
Console.WriteLine(launch.Year); // 2025
Console.WriteLine(launch.Month); // 12
Console.WriteLine(launch.Day); // 25
Console.WriteLine(launch.DayOfWeek);// Thursday
}
}2. Formatting Dates
Humans want dates in many shapes. Standard format specifiers ("d", "D", "t") give common presets, while custom strings ("yyyy-MM-dd", "dd MMM yyyy") let you build any layout. Pass CultureInfo.InvariantCulture when you need stable, predictable output regardless of the machine's region.
using System;
using System.Globalization;
class Program
{
static void Main()
{
DateTime d = new DateTime(2025, 3, 9, 14, 5, 0);
// Standard format specifiers (with an invariant culture for stable output):
var ci = CultureInfo.InvariantCulture;
Console.WriteLine(d.ToString("d", ci)); // 03/09/2025 (short date)
Console.WriteLine(d.ToString("D", ci)); // Sunday, 09 March 2025 (long date)
Console.WriteLine(d.ToString("t", ci)); // 14:05 (short time)
// Custom format strings — you control every part:
Console.WriteLine(d.ToString("yyyy-MM-dd", ci)); // 2025-03-09
Console.WriteLine(d.ToString("dd MMM yyyy", ci)); // 09 Mar 2025
Console.WriteLine(d.ToString("HH:mm:ss", ci)); // 14:05:00
Console.WriteLine(d.ToString("dddd 'at' h:mm tt", ci));// Sunday at 2:05 PM
}
}3. Date Arithmetic & TimeSpan
DateTime is immutable, so AddDays, AddHours and friends return a new date. Subtracting two dates gives a TimeSpan — a duration you can read as .TotalDays, .Days, .Hours, and so on. Comparison operators (>, <) work directly on dates too.
using System;
class Program
{
static void Main()
{
DateTime start = new DateTime(2025, 1, 1);
// Add/subtract time — each returns a NEW DateTime (immutable).
DateTime later = start.AddDays(30).AddHours(6);
Console.WriteLine(later.ToString("yyyy-MM-dd HH:mm")); // 2025-01-31 06:00
DateTime nextYear = start.AddYears(1);
Console.WriteLine(nextYear.Year); // 2026
// The DIFFERENCE between two dates is a TimeSpan:
DateTime end = new DateTime(2025, 3, 1);
TimeSpan diff = end - start;
Console.WriteLine($"Days between: {diff.TotalDays}"); // Days between: 59
Console.WriteLine($"That's {diff.Days} days"); // That's 59 days
// Compare dates:
Console.WriteLine(end > start); // True
}
}Your turn — measure a tenure and compute the next review date.
using System;
class Program
{
static void Main()
{
// 🎯 YOUR TURN — replace each ___ then press "Try it Yourself".
DateTime hired = new DateTime(2020, 6, 1);
DateTime review = new DateTime(2025, 6, 1);
// 1) Get the TimeSpan between review and hired
TimeSpan tenure = ___; // 👉 review - hired
// 2) Work out whole years (approximate: total days / 365)
int years = (int)(tenure.TotalDays / 365);
Console.WriteLine($"Tenure: about {years} years");
// ✅ Expected output: Tenure: about 5 years
// 3) Add 90 days to 'review' to get the next review date
DateTime nextReview = ___; // 👉 review.AddDays(90)
Console.WriteLine(nextReview.ToString("yyyy-MM-dd")); // 2025-08-30
}
}4. Parsing Dates from Text
Dates often arrive as strings — from files, forms, or APIs. Parse throws on bad input, so for anything from a user prefer TryParse, which returns false instead of crashing. When you know the exact incoming format, ParseExact pins it down precisely.
using System;
using System.Globalization;
class Program
{
static void Main()
{
var ci = CultureInfo.InvariantCulture;
// Parse a date from text. Parse THROWS on bad input...
DateTime d1 = DateTime.Parse("2025-07-04", ci);
Console.WriteLine(d1.ToString("dd MMM yyyy", ci)); // 04 Jul 2025
// ...so prefer TryParse for anything from a user — it never crashes:
string input = "not a date";
if (DateTime.TryParse(input, ci, DateTimeStyles.None, out DateTime d2))
Console.WriteLine(d2);
else
Console.WriteLine("Invalid date entered"); // this runs
// ParseExact pins down the EXACT expected format:
DateTime d3 = DateTime.ParseExact("25/12/2025", "dd/MM/yyyy", ci);
Console.WriteLine(d3.ToString("D", ci)); // Thursday, 25 December 2025
}
}
// ✅ Expected output:
// 04 Jul 2025
// Invalid date entered
// Thursday, 25 December 2025🧩 Reorder Challenge
These lines compute how many days are left until a deadline, but they're scrambled. Order them so the program compiles and prints the day count.
A. TimeSpan left = deadline - start;
B. DateTime start = new DateTime(2025, 1, 1);
C. Console.WriteLine($"Days left: {left.Days}");
D. DateTime deadline = new DateTime(2025, 1, 15);B. DateTime start = new DateTime(2025, 1, 1);
D. DateTime deadline = new DateTime(2025, 1, 15);
A. TimeSpan left = deadline - start;
C. Console.WriteLine($"Days left: {left.Days}");
// Output: Days left: 14Both dates must exist before you subtract them, and the TimeSpan must exist before you read .Days.
🧠 Quick Recall
1. What does this print?
var d = new DateTime(2025, 1, 10);
Console.WriteLine(d.AddDays(5).Day);15 — AddDays returns a new date on the 15th; d itself is unchanged.
2. What does this print?
var a = new DateTime(2025, 1, 1);
var b = new DateTime(2025, 1, 8);
Console.WriteLine((b - a).Days);7 — the TimeSpan between the two dates is 7 days.
3. What does this print?
var d = new DateTime(2025, 3, 9);
Console.WriteLine(d.ToString("yyyy-MM-dd"));2025-03-09 — the custom format produces an ISO-style date.
Putting It Together: a Countdown
A "days until" countdown ties together creation, formatting, arithmetic, TimeSpan and DayOfWeek — the bread and butter of date code.
using System;
using System.Globalization;
class Program
{
static void Main()
{
// === A small "days until event" countdown ===
var ci = CultureInfo.InvariantCulture;
DateTime today = new DateTime(2025, 12, 1); // fixed for a stable demo
DateTime newYear = new DateTime(2026, 1, 1);
TimeSpan untilNewYear = newYear - today;
Console.WriteLine($"Today: {today.ToString("dddd, dd MMM", ci)}"); // Monday, 01 Dec
Console.WriteLine($"Days until New Year: {untilNewYear.Days}"); // 31
// Is the event on a weekend?
bool weekendLaunch = newYear.DayOfWeek == DayOfWeek.Saturday
|| newYear.DayOfWeek == DayOfWeek.Sunday;
Console.WriteLine($"New Year is a {newYear.DayOfWeek}"); // Thursday
Console.WriteLine($"Weekend launch? {weekendLaunch}"); // False
// Age in days for a membership that started 100 days ago:
DateTime joined = today.AddDays(-100);
Console.WriteLine($"Member for {(today - joined).Days} days"); // 100
}
}
// ✅ Expected output:
// Today: Monday, 01 Dec
// Days until New Year: 31
// New Year is a Thursday
// Weekend launch? False
// Member for 100 daysPro Tips
- 💡 Store and compare timestamps in UTC (DateTime.UtcNow) — convert to local time only when displaying to a user.
- 💡 Pass an explicit culture (CultureInfo.InvariantCulture) when formatting/parsing so output doesn't change by machine.
- 💡 Use TryParse for user input — never let a bad date string crash your program.
- 💡 Remember Add* methods return a new date — assign the result; the original is never mutated.
Common Errors (and the fix)
- "FormatException: String was not recognized as a valid DateTime": the text didn't match the expected format. Use TryParse or ParseExact with the right pattern.
- Forgetting to assign Add* results: d.AddDays(5); alone does nothing — write d = d.AddDays(5);.
- Wrong month/day order: "03/09/2025" is ambiguous across cultures. Use ISO "yyyy-MM-dd" for clarity.
- Comparing local and UTC times: mixing Now and UtcNow gives wrong gaps. Pick one (usually UTC) and stick with it.
📋 Quick Reference
| Task | Code |
|---|---|
| Now (local) | DateTime.Now |
| Now (UTC) | DateTime.UtcNow |
| Specific date | new DateTime(2025, 12, 25) |
| Add 7 days | d.AddDays(7) |
| Difference | (end - start).Days |
| Format | d.ToString("yyyy-MM-dd") |
| Safe parse | DateTime.TryParse(s, out var d) |
Mini-Challenge: Age Calculator
Compute someone's age in whole years from a birth date — and handle the "haven't had this year's birthday yet" edge case correctly. A classic interview-style date problem.
using System;
using System.Globalization;
class Program
{
static void Main()
{
// 🎯 MINI-CHALLENGE: age calculator
// 1. Given a birth date and a "today" date (use fixed dates):
DateTime birth = new DateTime(1995, 8, 20);
DateTime today = new DateTime(2025, 6, 17);
// 2. Compute the age in whole years. A correct approach:
// int age = today.Year - birth.Year;
// if (today < birth.AddYears(age)) age--; // not had birthday yet this year
// 3. Print: "Age: <age>"
//
// ✅ Example output:
// Age: 29
// your code here
}
}🎉 Lesson Complete
- ✅ Create dates with Now, Today, UtcNow and the constructor
- ✅ Format with standard ("d") and custom ("yyyy-MM-dd") specifiers
- ✅ Add/subtract returns new dates; subtracting two dates gives a TimeSpan
- ✅ Use TryParse/ParseExact for safe parsing
- ✅ Store UTC, format with an explicit culture
Practice quiz
What is the difference between DateTime.Now and DateTime.UtcNow?
- They are identical
- UtcNow includes the date but Now does not
- Now is the machine's local time; UtcNow is the current time in Coordinated Universal Time (UTC)
- Now is always one hour ahead of UtcNow
Answer: Now is the machine's local time; UtcNow is the current time in Coordinated Universal Time (UTC). DateTime.Now is local time (depends on the machine's time zone), while DateTime.UtcNow is UTC — preferred for storing and comparing timestamps.
What does DateTime.Today return?
- Midnight today (the date with no time-of-day part)
- The current time including hours and minutes
- Tomorrow's date
- The date in UTC
Answer: Midnight today (the date with no time-of-day part). DateTime.Today returns today's date at midnight, with no time-of-day component.
Is DateTime mutable? What do methods like AddDays do?
- DateTime is mutable; AddDays changes the original in place
- AddDays only works on DateTime.Now
- AddDays throws if the result is in the future
- DateTime is immutable; AddDays returns a NEW DateTime and leaves the original unchanged
Answer: DateTime is immutable; AddDays returns a NEW DateTime and leaves the original unchanged. DateTime is immutable. AddDays, AddHours, AddYears, etc. return a new DateTime, so you must assign the result to a variable.
What do you get when you subtract one DateTime from another?
- Another DateTime
- A TimeSpan representing the duration between them
- An int number of days
- A string
Answer: A TimeSpan representing the duration between them. Subtracting two DateTimes yields a TimeSpan — a duration you can read as .TotalDays, .Days, .Hours, and so on.
What does a TimeSpan represent?
- A duration — a length of time, not a point in time
- A single instant on the calendar
- A time zone
- A formatted date string
Answer: A duration — a length of time, not a point in time. A TimeSpan is a duration (e.g. 'three days and four hours'), with no fixed place on the calendar — unlike a DateTime, which is an instant.
Why pass CultureInfo.InvariantCulture when formatting or parsing dates?
- It makes formatting faster
- It is required for all DateTime methods
- For predictable, stable output that doesn't change with the machine's region
- It converts the date to UTC
Answer: For predictable, stable output that doesn't change with the machine's region. By default ToString uses the machine's culture, so output varies by region. InvariantCulture gives stable, predictable results.
When should you prefer DateTime.TryParse over DateTime.Parse?
- Always — Parse is deprecated
- For input from users or external sources, because TryParse returns false on bad input instead of throwing
- Only when parsing UTC dates
- Never — they behave identically
Answer: For input from users or external sources, because TryParse returns false on bad input instead of throwing. Parse throws on bad input. TryParse returns false instead of crashing, so it's the safe choice for user or external input.
What does DateTime.ParseExact let you do?
- Parse any format automatically
- Always parse in UTC
- Round the time to the nearest hour
- Pin down the exact expected format, e.g. ParseExact("25/12/2025", "dd/MM/yyyy", ci)
Answer: Pin down the exact expected format, e.g. ParseExact("25/12/2025", "dd/MM/yyyy", ci). ParseExact requires the input to match a specified format string exactly, useful when you know the precise incoming format.
Which custom format string produces an ISO-style date like 2025-03-09?
- "dd MMM yyyy"
- "yyyy-MM-dd"
- "D"
- "t"
Answer: "yyyy-MM-dd". The custom format 'yyyy-MM-dd' produces an ISO-style date such as 2025-03-09.
Which property tells you the day of the week for a DateTime?
- .Weekday
- .Day
- .DayOfWeek
- .DayName
Answer: .DayOfWeek. DateTime.DayOfWeek returns a DayOfWeek value (e.g. Thursday), letting you check whether a date falls on a weekend.
Continue this course
- Previous: Extension Methods
- Next: Regular Expressions — Match and extract text with Regex
- Quick reference: C# cheat sheet
Frequently asked questions
What's the difference between DateTime.Now and DateTime.UtcNow?
DateTime.Now is the current local time on the machine, which depends on its time zone and daylight-saving rules. DateTime.UtcNow is the current time in Coordinated Universal Time. For storing timestamps in a database or comparing events across regions, always prefer UtcNow.
What is a TimeSpan?
A TimeSpan represents a duration — a length of time, not a point in time. Subtracting two DateTimes gives a TimeSpan, and you can read it as .TotalDays, .Days, .Hours, .TotalMinutes and so on. You can also add a TimeSpan to a DateTime.
Why is my date formatted differently than I expected?
By default, DateTime.ToString() uses the current machine's culture, so 03/09/2025 could mean March 9th or September 3rd depending on the region. Pass CultureInfo.InvariantCulture (or a specific culture) for predictable, stable output.
Should I use Parse or TryParse?
Use TryParse for any date that came from a user or external source — it returns false on bad input instead of throwing. Reserve Parse (or ParseExact for a known fixed format) for values you fully control.
Is DateTime mutable? Do AddDays etc. change it?
DateTime is immutable. Methods like AddDays, AddHours, and AddYears return a NEW DateTime and leave the original unchanged — so remember to assign the result to a variable.