Date and time in C#
last modified October 18, 2026
In this article we show how to work with date and time in C#.
A datetime represents an instant in time. The string representation of a datetime is culture-specific and depends both on the conventions used for displaying date and time and on its calendar. A calendar is a system of organizing datetime units.
C# has several types for dates and times:
DateTime- a date and a time of day, with aKindthat says whether it is local, UTC, or unspecifiedDateTimeOffset- a date and a time together with the offset from UTC; it identifies an exact instantDateOnlyandTimeOnly(since .NET 6) - a date without a time and a time without a dateTimeSpan- a duration; see the TimeSpan tutorialTimeZoneInfo- information about time zones and conversions between themTimeProvider(since .NET 8) - an abstraction of the clock, which makes time-dependent code testable
C# DateTime
The DateTime value type represents dates and times with values ranging
from 00:00:00 (midnight), January 1, 0001 Anno Domini (Common Era) through
11:59:59 P.M., December 31, 9999 A.D. (C.E.) in the Gregorian calendar. The value
is stored as a number of ticks, one tick being 100 nanoseconds.
The examples were run on a machine in the Central European time zone, with the English (US) culture. Your output will differ in the time zone and the format.
C# today's date
In our first example, we get today's date and time.
DateTime now = DateTime.Now;
Console.WriteLine(now.ToString("F"));
DateOnly today = DateOnly.FromDateTime(now);
TimeOnly time = TimeOnly.FromDateTime(now);
Console.WriteLine(today);
Console.WriteLine(time.ToString("HH:mm"));
Console.WriteLine(DateTime.Today.ToString("d"));
The example prints the current date and time in several forms.
DateTime now = DateTime.Now;
With the Now property of DateTime, we get the current
date and time in local time. DateTime.Today returns only the date, with
the time set to midnight.
Console.WriteLine(now.ToString("F"));
With the ToString method, we format the date. The F
specifier creates a full date and time pattern.
DateOnly today = DateOnly.FromDateTime(now); TimeOnly time = TimeOnly.FromDateTime(now);
When we need only a date or only a time, DateOnly and
TimeOnly express this more precisely than a DateTime with
an unused half. They are covered in a later section.
$ dotnet run Sunday, October 18, 2026 8:09:55 PM 10/18/2026 20:09 10/18/2026
C# DateTime properties
DateTime represents an instant in time. Its properties provide
various aspects of the date and time. In this example we use a fixed value so that
the output is reproducible.
string[] months = {"January", "February", "March", "April", "May",
"June", "July", "August", "September", "October", "November", "December"};
DateTime now = new DateTime(2026, 10, 18, 14, 9, 12, 410);
Console.WriteLine("Today's date: {0}", now.Date);
Console.WriteLine("Today is {0} day of {1}", now.Day, months[now.Month - 1]);
Console.WriteLine("Today is {0} day of {1}", now.DayOfYear, now.Year);
Console.WriteLine("Today's time: {0}", now.TimeOfDay);
Console.WriteLine("Hour: {0}", now.Hour);
Console.WriteLine("Minute: {0}", now.Minute);
Console.WriteLine("Second: {0}", now.Second);
Console.WriteLine("Millisecond: {0}", now.Millisecond);
Console.WriteLine("The day of week: {0}", now.DayOfWeek);
Console.WriteLine("Kind: {0}", now.Kind);
Console.WriteLine("Ticks: {0}", now.Ticks);
The example examines the properties of the DateTime object.
DateTime now = new DateTime(2026, 10, 18, 14, 9, 12, 410);
A DateTime object is created from the year, month, day, hour, minute,
second, and millisecond. The month and day are one-based.
Console.WriteLine("Today's date: {0}", now.Date);
The Date property gets the date component, with the time set to
midnight.
Console.WriteLine("Today is {0} day of {1}", now.Day, months[now.Month - 1]);
The Day property gets the day of the month. The Month
property returns the month component, a value between 1 and 12. The month name
is better taken from the culture with ToString("MMMM") than from our
own array; the array is here only for illustration.
Console.WriteLine("Today is {0} day of {1}", now.DayOfYear, now.Year);
The DayOfYear property gets the day of the year, and Year
gets the year.
Console.WriteLine("Today's time: {0}", now.TimeOfDay);
The TimeOfDay property gets the time of day as a
TimeSpan.
Console.WriteLine("Kind: {0}", now.Kind);
The Kind property indicates whether the time is based on local time,
Coordinated Universal Time (UTC), or neither. A value created with a constructor is
Unspecified.
$ dotnet run Today's date: 10/18/2026 12:00:00 AM Today is 18 day of October Today is 291 day of 2026 Today's time: 14:09:12.4100000 Hour: 14 Minute: 9 Second: 12 Millisecond: 410 The day of week: Sunday Kind: Unspecified Ticks: 639279293524100000
C# add and subtract DateTime
DateTime has methods for time arithmetic. Because the structure is
immutable, each method returns a new value.
DateTime dt = new DateTime(2019, 2, 22, 14, 0, 0);
Console.WriteLine(dt.AddSeconds(55).ToString("F"));
Console.WriteLine(dt.AddMinutes(30).ToString("F"));
Console.WriteLine(dt.AddHours(72).ToString("F"));
Console.WriteLine(dt.AddDays(65).ToString("F"));
Console.WriteLine(dt.AddDays(-65).ToString("F"));
Console.WriteLine(dt.AddMonths(3).ToString("F"));
Console.WriteLine(dt.AddYears(4).ToString("F"));
// month arithmetic clamps the day to the end of the month
Console.WriteLine(new DateTime(2026, 1, 31).AddMonths(1).ToString("d"));
Console.WriteLine(new DateTime(2024, 2, 29).AddYears(1).ToString("d"));
// differences are TimeSpan values
var diff = new DateTime(2026, 12, 24) - new DateTime(2026, 10, 18);
Console.WriteLine($"{diff.TotalDays} days until Christmas Eve");
Console.WriteLine(DateTime.DaysInMonth(2026, 2));
Console.WriteLine(DateTime.DaysInMonth(2024, 2));
The example presents the methods of the DateTime structure.
Console.WriteLine(dt.AddSeconds(55).ToString("F"));
The AddSeconds method returns a new DateTime with the
specified number of seconds added.
Console.WriteLine(dt.AddDays(65).ToString("F"));
Console.WriteLine(dt.AddDays(-65).ToString("F"));
The AddDays method adds days. We can provide both positive and negative
values; there is no separate subtract method.
Console.WriteLine(new DateTime(2026, 1, 31).AddMonths(1).ToString("d"));
Console.WriteLine(new DateTime(2024, 2, 29).AddYears(1).ToString("d"));
Month and year arithmetic clamps the day to the last day of the target month, so January 31 plus a month is February 28, and February 29 plus a year is February 28.
var diff = new DateTime(2026, 12, 24) - new DateTime(2026, 10, 18);
Subtracting two datetimes gives a TimeSpan.
$ dotnet run Friday, February 22, 2019 2:00:55 PM Friday, February 22, 2019 2:30:00 PM Monday, February 25, 2019 2:00:00 PM Sunday, April 28, 2019 2:00:00 PM Wednesday, December 19, 2018 2:00:00 PM Wednesday, May 22, 2019 2:00:00 PM Wednesday, February 22, 2023 2:00:00 PM 2/28/2026 2/28/2025 67 days until Christmas Eve 28 29
C# UTC time and DateTimeKind
Our planet is a sphere that revolves around its axis. The Earth rotates towards the east, so the Sun rises at different times in different locations. The Earth rotates once in about 24 hours, so the world was divided into time zones. In each time zone, there is a different local time, which is often further modified by daylight saving time.
There is a pragmatic need for one global time. It helps to avoid confusion about time zones and daylight saving time. UTC (Coordinated Universal Time) was chosen as the primary time standard. Unlike local time, UTC does not change with the seasons. As a rule, programs should store and exchange times in UTC (or with an offset) and convert them to local time only when displaying.
DateTime local = DateTime.Now;
DateTime utc = DateTime.UtcNow;
DateTime unspecified = new DateTime(2026, 10, 18, 12, 0, 0);
Console.WriteLine($"{local.Kind} {utc.Kind} {unspecified.Kind}");
Console.WriteLine($"UTC time {utc:HH:mm:ss}");
Console.WriteLine($"Local time {local:HH:mm:ss}");
// conversions depend on the Kind
DateTime asUtc = DateTime.SpecifyKind(unspecified, DateTimeKind.Utc);
Console.WriteLine(asUtc.ToLocalTime().ToString("HH:mm"));
DateTime asLocal = DateTime.SpecifyKind(unspecified, DateTimeKind.Local);
Console.WriteLine(asLocal.ToUniversalTime().ToString("HH:mm"));
// an Unspecified value is treated as local by ToUniversalTime and as UTC by ToLocalTime
Console.WriteLine(unspecified.ToUniversalTime().ToString("HH:mm"));
Console.WriteLine(unspecified.ToLocalTime().ToString("HH:mm"));
The example prints the current UTC time and the local time, and shows how the
Kind of a DateTime affects conversions.
DateTime utc = DateTime.UtcNow;
With the UtcNow property, we get the UTC time. Its Kind is
Utc. For DateTime.Now it is Local.
DateTime asUtc = DateTime.SpecifyKind(unspecified, DateTimeKind.Utc);
Console.WriteLine(asUtc.ToLocalTime().ToString("HH:mm"));
SpecifyKind labels a value without changing its numbers. The label
decides how the conversions work: a UTC value converted to local time moves two
hours forward (in the Central European summer time).
Console.WriteLine(unspecified.ToUniversalTime().ToString("HH:mm"));
Console.WriteLine(unspecified.ToLocalTime().ToString("HH:mm"));
An Unspecified value is a trap: ToUniversalTime assumes it
is local, and ToLocalTime assumes it is UTC. For this reason, prefer
DateTimeOffset for values that represent instants.
$ dotnet run Local Utc Unspecified UTC time 18:09:55 Local time 20:09:55 14:00 10:00 10:00 14:00
C# DateTimeOffset
A DateTimeOffset stores a date, a time, and the offset from UTC. It
identifies one exact instant and cannot be misinterpreted. It does not store the
time zone; the offset is only the difference at that instant.
var utcNow = DateTimeOffset.UtcNow;
var local = DateTimeOffset.Now;
Console.WriteLine(local.Offset);
Console.WriteLine(utcNow.Offset);
var meeting = new DateTimeOffset(2026, 10, 18, 15, 0, 0, TimeSpan.FromHours(2));
Console.WriteLine(meeting);
Console.WriteLine(meeting.UtcDateTime.ToString("HH:mm"));
Console.WriteLine(meeting.ToOffset(TimeSpan.FromHours(-5)));
// two values with different offsets are equal when they are the same instant
var other = new DateTimeOffset(2026, 10, 18, 13, 0, 0, TimeSpan.Zero);
Console.WriteLine(meeting == other);
Console.WriteLine(meeting.ToString("o"));
The example creates offset-aware values and converts them.
var meeting = new DateTimeOffset(2026, 10, 18, 15, 0, 0, TimeSpan.FromHours(2));
Console.WriteLine(meeting.UtcDateTime.ToString("HH:mm"));
Console.WriteLine(meeting.ToOffset(TimeSpan.FromHours(-5)));
We create a meeting at 15:00 in UTC+2. The same instant is 13:00 in UTC and 08:00
at UTC-5; ToOffset shows it with another offset.
Console.WriteLine(meeting == other);
Comparisons use the instant, so 15:00+02:00 equals 13:00+00:00.
$ dotnet run 02:00:00 00:00:00 10/18/2026 3:00:00 PM +02:00 13:00 10/18/2026 8:00:00 AM -05:00 True 2026-10-18T15:00:00.0000000+02:00
C# localized date
The ToString method accepts a CultureInfo, which allows us
to display the date and time in a specific culture.
using System.Globalization;
Console.OutputEncoding = System.Text.Encoding.UTF8;
DateTime now = new DateTime(2026, 10, 18, 14, 12, 2);
foreach (var name in new[] { "en-US", "sk-SK", "de-DE", "fr-FR", "ja-JP" })
{
var ci = new CultureInfo(name);
Console.WriteLine($"{name}: {now.ToString("F", ci)}");
}
// short date patterns differ in the order of the parts
Console.WriteLine(now.ToString("d", new CultureInfo("en-US")));
Console.WriteLine(now.ToString("d", new CultureInfo("en-GB")));
Console.WriteLine(now.ToString("d", CultureInfo.InvariantCulture));
The example prints a date in several cultures.
Console.OutputEncoding = System.Text.Encoding.UTF8;
To output accented and non-Latin characters correctly, we set the console output encoding to UTF-8. On Linux and macOS this is already the default.
Console.WriteLine(now.ToString("d", CultureInfo.InvariantCulture));
The invariant culture is neutral, based on English, and does not change with the machine's settings. Use it for text that is parsed by programs. The culture-specific output is for people.
$ dotnet run en-US: Sunday, October 18, 2026 2:12:02 PM sk-SK: nedeľa 18. októbra 2026 14:12:02 de-DE: Sonntag, 18. Oktober 2026 14:12:02 fr-FR: dimanche 18 octobre 2026 14:12:02 ja-JP: 2026年10月18日日曜日 14:12:02 10/18/2026 18/10/2026 10/18/2026
C# Unix time
The Unix time is the number of seconds since the Unix epoch, 00:00:00 UTC on
January 1, 1970. It is widely used in computing, in APIs, tokens, and file
formats. DateTimeOffset converts to and from Unix time.
long unixTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); Console.WriteLine(unixTime > 1_700_000_000); var epoch = DateTimeOffset.FromUnixTimeSeconds(0); Console.WriteLine(epoch); var dt = DateTimeOffset.FromUnixTimeSeconds(1_700_000_000); Console.WriteLine(dt); Console.WriteLine(dt.ToUnixTimeMilliseconds()); var back = new DateTimeOffset(2023, 11, 14, 22, 13, 20, TimeSpan.Zero); Console.WriteLine(back.ToUnixTimeSeconds());
The example converts between Unix time and datetimes.
long unixTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
We get the current Unix time with ToUnixTimeSeconds. There is also
ToUnixTimeMilliseconds, which many web APIs and JavaScript use. The
value is always based on UTC, so the machine's time zone does not matter.
$ dotnet run True 1/1/1970 12:00:00 AM +00:00 11/14/2023 10:13:20 PM +00:00 1700000000000 1700000000
C# TimeSpan with dates
A TimeSpan structure represents a time interval. Subtracting two
datetimes gives one. See the TimeSpan tutorial for
more.
string startTime = "7:00 AM";
string endTime = "8:30 PM";
var inv = System.Globalization.CultureInfo.InvariantCulture;
TimeSpan elapsed = DateTime.Parse(endTime, inv).Subtract(DateTime.Parse(startTime, inv));
Console.WriteLine($"Time elapsed: {elapsed}");
DateTime today = new DateTime(2026, 10, 18);
DateTime borodinoBattle = new DateTime(1812, 9, 7);
TimeSpan diff = today - borodinoBattle;
Console.WriteLine($"{diff.TotalDays} days have passed since the Battle of Borodino.");
// age in years
DateOnly birth = new DateOnly(1990, 12, 24);
DateOnly day = new DateOnly(2026, 10, 18);
int age = day.Year - birth.Year;
if (birth > day.AddYears(-age)) age--;
Console.WriteLine(age);
In the example, we subtract two time values, count the days since a historical event, and compute an age.
TimeSpan elapsed = DateTime.Parse(endTime, inv).Subtract(DateTime.Parse(startTime, inv));
The Subtract method subtracts two datetimes. A time without a date
is parsed as a time on the current day, which cancels out in the subtraction.
Console.WriteLine($"{diff.TotalDays} days have passed since the Battle of Borodino.");
The TotalDays property has the number of days of the elapsed time. The
output is for October 18, 2026.
int age = day.Year - birth.Year; if (birth > day.AddYears(-age)) age--;
There is no method for a person's age. We subtract the years, and if the birthday has not yet occurred this year, we subtract one more. Dividing the days by 365.25 is not exact.
$ dotnet run Time elapsed: 13:30:00 78203 days have passed since the Battle of Borodino. 35
C# format time
A date and time format string defines the text representation of a
DateTime or DateTimeOffset value that results from a
formatting operation. There are two types of format specifiers: standard and custom.
A standard format string consists of a single character; a custom format string
consists of two or more characters.
DateTime now = new DateTime(2026, 10, 18, 14, 16, 12, 948);
Console.WriteLine(now.ToString("d"));
Console.WriteLine(now.ToString("D"));
Console.WriteLine(now.ToString("f"));
Console.WriteLine(now.ToString("F"));
Console.WriteLine(now.ToString("g"));
Console.WriteLine(now.ToString("G"));
Console.WriteLine(now.ToString("M"));
Console.WriteLine(now.ToString("o"));
Console.WriteLine(now.ToString("R"));
Console.WriteLine(now.ToString("s"));
Console.WriteLine(now.ToString("t"));
Console.WriteLine(now.ToString("T"));
Console.WriteLine(now.ToString("u"));
Console.WriteLine(now.ToString("Y"));
The example prints a date using the standard format specifiers.
$ dotnet run 10/18/2026 Sunday, October 18, 2026 Sunday, October 18, 2026 2:16 PM Sunday, October 18, 2026 2:16:12 PM 10/18/2026 2:16 PM 10/18/2026 2:16:12 PM October 18 2026-10-18T14:16:12.9480000 Sun, 18 Oct 2026 14:16:12 GMT 2026-10-18T14:16:12 2:16 PM 2:16:12 PM 2026-10-18 14:16:12Z October 2026
| Specifier | Meaning |
|---|---|
d, D | short and long date pattern |
t, T | short and long time pattern |
f, F | full date and time (without and with seconds) |
g, G | general date and time (without and with seconds) |
M | month and day |
Y | year and month |
o | round-trip (ISO 8601) pattern |
s | sortable pattern, ISO 8601 without fractions |
u | universal sortable pattern with a Z suffix |
R | RFC 1123 pattern (HTTP date) |
Only o, s, u, and R are
culture-independent, so these are the ones to use in files and protocols. The
others change with the culture. Note that the R and u
formats print the number as is and just append "GMT" or "Z"; they do not convert the
time. Call ToUniversalTime first if the value is local. The o
format appends the offset (or Z) only when the Kind is local or UTC, as in
this unspecified value there is none.
Custom format specifiers allow us to create customized date and time patterns.
DateTime now = new DateTime(2026, 10, 18, 14, 15, 36);
Console.WriteLine(now.ToString("ddd MMM %d, yyyy"));
Console.WriteLine(now.ToString("hh:mm:ss tt"));
Console.WriteLine(now.ToString("yyyy-MM-dd HH:mm:ss"));
Console.WriteLine(now.ToString("dd/MM/yy"));
Console.WriteLine(now.ToString("dddd, d MMMM yyyy"));
Console.WriteLine(now.ToString("H 'h' m 'min'"));
Console.WriteLine(now.ToString("yyyyMMdd_HHmmss"));
Console.WriteLine($"{now:yyyy-MM-dd}");
Console.WriteLine(now.ToString("MM/dd/yyyy")); // '/' is the culture's date separator
Console.WriteLine(now.ToString("MM/dd/yyyy", System.Globalization.CultureInfo.InvariantCulture));
The example prints dates and times using custom specifiers.
Console.WriteLine(now.ToString("ddd MMM %d, yyyy"));
The ddd specifier is the abbreviated name of the day of the week,
MMM is the abbreviated name of the month, and d is the
day of the month, from 1 through 31. A single-character custom specifier must be
preceded with the % character, otherwise it would be taken as a standard
format string. yyyy is the year as a four-digit number.
Console.WriteLine(now.ToString("hh:mm:ss tt"));
The hh specifier is the hour on a 12-hour clock from 01 to 12,
HH would be the 24-hour clock, mm is the minute,
ss is the second, and tt is the AM/PM designator.
Console.WriteLine(now.ToString("H 'h' m 'min'"));
Literal text is enclosed in apostrophes. Without them, letters such as h
would be read as specifiers.
Console.WriteLine(now.ToString("MM/dd/yyyy"));
In a custom format string, / stands for the date separator of the
current culture, and : for the time separator. In a culture that uses a
period, the output would be 10.18.2026. Pass
CultureInfo.InvariantCulture to get the characters literally.
$ dotnet run Sun Oct 18, 2026 02:15:36 PM 2026-10-18 14:15:36 18/10/26 Sunday, 18 October 2026 14 h 15 min 20261018_141536 2026-10-18 10/18/2026 10/18/2026
C# parse time
The Parse method converts the string representation of a date and time
to its DateTime equivalent. Since the interpretation of a date string
depends on the culture, we should always pass the culture, and for fixed formats use
ParseExact. For input that may be wrong, use the TryParse
variants.
using System.Globalization;
var inv = CultureInfo.InvariantCulture;
string dateString = "11/5/2019";
DateTime dt = DateTime.Parse(dateString, new CultureInfo("en-US"));
Console.WriteLine($"{dt:d MMMM, yyyy}");
DateTime dt2 = DateTime.Parse(dateString, new CultureInfo("en-GB"));
Console.WriteLine($"{dt2:d MMMM, yyyy}");
// ParseExact removes the guessing
DateTime dt3 = DateTime.ParseExact("2019-11-05 14:30", "yyyy-MM-dd HH:mm", inv);
Console.WriteLine(dt3);
// TryParse for user input
foreach (var s in new[] { "2026-02-30", "2026-02-28", "hello", "" })
{
if (DateTime.TryParseExact(s, "yyyy-MM-dd", inv, DateTimeStyles.None, out var d))
{
Console.WriteLine($"'{s}' -> {d:D}");
}
else
{
Console.WriteLine($"'{s}' is not a valid date");
}
}
// ISO 8601 with offset
DateTimeOffset dto = DateTimeOffset.Parse("2026-10-18T14:30:00+02:00", inv);
Console.WriteLine(dto.UtcDateTime);
// the round-trip format keeps the Kind
DateTime utc = new DateTime(2026, 10, 18, 12, 0, 0, DateTimeKind.Utc);
string text = utc.ToString("o");
DateTime again = DateTime.Parse(text, inv, DateTimeStyles.RoundtripKind);
Console.WriteLine($"{text} {again.Kind}");
The program parses dates in several ways.
string dateString = "11/5/2019";
DateTime dt = DateTime.Parse(dateString, new CultureInfo("en-US"));
DateTime dt2 = DateTime.Parse(dateString, new CultureInfo("en-GB"));
The same text means November 5 in the US and May 11 in Great Britain. The culture decides.
DateTime dt3 = DateTime.ParseExact("2019-11-05 14:30", "yyyy-MM-dd HH:mm", inv);
ParseExact accepts only the given format, which removes the guessing.
if (DateTime.TryParseExact(s, "yyyy-MM-dd", inv, DateTimeStyles.None, out var d))
TryParseExact returns false for text that does not match,
including impossible dates such as February 30, instead of throwing an
exception.
DateTime again = DateTime.Parse(text, inv, DateTimeStyles.RoundtripKind);
The round-trip format (o) together with
DateTimeStyles.RoundtripKind restores the Kind of the
original value, which is why UTC values written to text and read back remain UTC.
$ dotnet run 5 November, 2019 11 May, 2019 11/5/2019 2:30:00 PM '2026-02-30' is not a valid date '2026-02-28' -> Saturday, February 28, 2026 'hello' is not a valid date '' is not a valid date 10/18/2026 12:30:00 PM 2026-10-18T12:00:00.0000000Z Utc
The DateTimeOffset.Parse of the ISO 8601 text with the offset +02:00
gives 12:30 in UTC, shown in the penultimate line.
C# DateOnly and TimeOnly
The DateOnly structure represents a calendar date without a time, such
as a birthday or a due date. TimeOnly represents a time of day, such as
an opening time, without a date or a time zone. They avoid the bugs that come from a
forgotten time part or from implicit time zone conversions.
DateOnly d = new DateOnly(2026, 10, 18);
TimeOnly t = new TimeOnly(14, 30);
Console.WriteLine(d);
Console.WriteLine(t);
Console.WriteLine(d.AddDays(30));
Console.WriteLine(d.DayOfWeek);
Console.WriteLine(t.AddHours(10)); // wraps around midnight
Console.WriteLine(t.IsBetween(new TimeOnly(9, 0), new TimeOnly(17, 0)));
DateTime combined = d.ToDateTime(t);
Console.WriteLine(combined);
Console.WriteLine(DateOnly.Parse("2026-12-24").DayNumber - d.DayNumber);
Console.WriteLine(DateOnly.ParseExact("24.12.2026", "dd.MM.yyyy"));
var open = new TimeOnly(22, 0);
var close = new TimeOnly(2, 0);
Console.WriteLine(close - open); // the span from 22:00 to 02:00 the next day
The example uses both types.
Console.WriteLine(t.AddHours(10));
A TimeOnly wraps around midnight: 14:30 plus ten hours is 00:30.
Console.WriteLine(DateOnly.Parse("2026-12-24").DayNumber - d.DayNumber);
The DayNumber property counts days since January 1, 0001, which makes
the difference of two dates a simple subtraction.
Console.WriteLine(close - open);
Subtracting two times of day gives the time span from the first to the second, going through midnight if needed: from 22:00 to 02:00 is four hours.
$ dotnet run 10/18/2026 2:30 PM 11/17/2026 Sunday 12:30 AM True 10/18/2026 2:30:00 PM 67 12/24/2026 04:00:00
C# Time zones
A time zone is a region throughout which the same standard time is used. The relationship between UTC and local time is described by the offset (also called the bias):
UTC = local time - offset
The offset can change during the year because of daylight saving time (DST).
TimeZoneInfo is the class for working with time zones in C#.
TimeZoneInfo localZone = TimeZoneInfo.Local;
Console.WriteLine("Id: {0}", localZone.Id);
Console.WriteLine("Standard name: {0}", localZone.StandardName);
Console.WriteLine("Daylight name: {0}", localZone.DaylightName);
Console.WriteLine("Bias: {0}", localZone.BaseUtcOffset);
Console.WriteLine("Supports DST: {0}", localZone.SupportsDaylightSavingTime);
var summer = new DateTime(2026, 7, 1, 12, 0, 0);
var winter = new DateTime(2026, 1, 1, 12, 0, 0);
Console.WriteLine(localZone.GetUtcOffset(summer));
Console.WriteLine(localZone.GetUtcOffset(winter));
Console.WriteLine(localZone.IsDaylightSavingTime(summer));
The program prints information about the local time zone.
TimeZoneInfo localZone = TimeZoneInfo.Local;
Using the Local property, we get the local time zone.
Console.WriteLine("Standard name: {0}", localZone.StandardName);
Console.WriteLine("Daylight name: {0}", localZone.DaylightName);
Console.WriteLine("Bias: {0}", localZone.BaseUtcOffset);
The StandardName gives the standard name of the time zone and the
DaylightName its daylight saving name. The BaseUtcOffset
is the offset from UTC without DST.
Console.WriteLine(localZone.GetUtcOffset(summer)); Console.WriteLine(localZone.GetUtcOffset(winter));
GetUtcOffset returns the actual offset at a given time, including DST:
two hours in July and one hour in January.
$ dotnet run Id: Europe/Bratislava Standard name: Central European Standard Time Daylight name: Central European Summer Time Bias: 01:00:00 Supports DST: True 02:00:00 01:00:00 True
On Linux and macOS, the id of a zone is an IANA name; on Windows, the ids are names
such as "Central Europe Standard Time". Since .NET 6, both kinds can be used on all
platforms, given ICU on Windows, and the
TimeZoneInfo.TryConvertIanaIdToWindowsId method converts between them.
C# convert between time zones
The FindSystemTimeZoneById method finds a time zone, and
ConvertTimeFromUtc and ConvertTime convert times between
zones, taking DST into account.
// IANA ids (America/New_York) work on Linux and macOS, and on Windows since .NET 6 with ICU
DateTime utc = new DateTime(2026, 10, 18, 12, 0, 0, DateTimeKind.Utc);
foreach (var id in new[] { "UTC", "Europe/Bratislava", "America/New_York", "Asia/Tokyo", "Australia/Sydney" })
{
TimeZoneInfo zone = TimeZoneInfo.FindSystemTimeZoneById(id);
DateTime converted = TimeZoneInfo.ConvertTimeFromUtc(utc, zone);
Console.WriteLine($"{id,-20} {converted:yyyy-MM-dd HH:mm} UTC{zone.GetUtcOffset(utc):hh\\:mm}");
}
// convert a wall-clock time in one zone to another
var ny = TimeZoneInfo.FindSystemTimeZoneById("America/New_York");
var bratislava = TimeZoneInfo.FindSystemTimeZoneById("Europe/Bratislava");
DateTime call = new DateTime(2026, 10, 20, 9, 0, 0); // 09:00 in New York
Console.WriteLine(TimeZoneInfo.ConvertTime(call, ny, bratislava));
// the ids that exist on this system
Console.WriteLine(TimeZoneInfo.GetSystemTimeZones().Count > 100);
Console.WriteLine(TimeZoneInfo.TryConvertIanaIdToWindowsId("Europe/Bratislava", out var win) ? win : "?");
The program shows the same instant in five time zones, and converts a time in New York to Bratislava.
TimeZoneInfo zone = TimeZoneInfo.FindSystemTimeZoneById(id); DateTime converted = TimeZoneInfo.ConvertTimeFromUtc(utc, zone);
The method throws a TimeZoneNotFoundException if the id is unknown, so
use TryFindSystemTimeZoneById for ids that come from user input.
Console.WriteLine(TimeZoneInfo.ConvertTime(call, ny, bratislava));
The call is at 09:00 in New York on October 20; the clocks in New York still have DST while Bratislava has already left it, so the difference is six hours and the result is 15:00.
$ dotnet run UTC 2026-10-18 12:00 UTC00:00 Europe/Bratislava 2026-10-18 14:00 UTC02:00 America/New_York 2026-10-18 08:00 UTC04:00 Asia/Tokyo 2026-10-18 21:00 UTC09:00 Australia/Sydney 2026-10-18 23:00 UTC11:00 10/20/2026 3:00:00 PM True Central Europe Standard Time
The GetSystemTimeZones method returns a sorted collection of all time
zones available on the system. A container image without time zone data
(tzdata) has few or none of them, and the lookup fails; the .NET
runtime images and the full SDK images include the data.
C# daylight saving time pitfalls
Daylight saving time makes some local times invalid, others ambiguous, and some days longer or shorter than 24 hours.
var zone = TimeZoneInfo.FindSystemTimeZoneById("Europe/Bratislava");
// 2026-03-29 02:30 does not exist in Central Europe: the clocks jump from 02:00 to 03:00
var gap = new DateTime(2026, 3, 29, 2, 30, 0);
Console.WriteLine(zone.IsInvalidTime(gap));
// 2026-10-25 02:30 exists twice: the clocks go back from 03:00 to 02:00
var overlap = new DateTime(2026, 10, 25, 2, 30, 0);
Console.WriteLine(zone.IsAmbiguousTime(overlap));
foreach (var offset in zone.GetAmbiguousTimeOffsets(overlap))
{
Console.WriteLine(offset);
}
// a day is not always 24 hours
var day1 = new DateTime(2026, 10, 25, 0, 0, 0);
var day2 = day1.AddDays(1);
var start = new DateTimeOffset(day1, zone.GetUtcOffset(day1));
var end = new DateTimeOffset(day2, zone.GetUtcOffset(day2));
Console.WriteLine(end - start);
Console.WriteLine(day2 - day1);
The program examines the changes in the Central European time zone in 2026.
var gap = new DateTime(2026, 3, 29, 2, 30, 0); Console.WriteLine(zone.IsInvalidTime(gap));
When the clocks go forward from 02:00 to 03:00, the local time 02:30 never happens.
var overlap = new DateTime(2026, 10, 25, 2, 30, 0); Console.WriteLine(zone.IsAmbiguousTime(overlap));
When the clocks go back from 03:00 to 02:00, the local time 02:30 happens twice,
once with the offset +02:00 and once with +01:00. A local time alone cannot tell
which one is meant, which is another reason to store UTC or a
DateTimeOffset.
Console.WriteLine(end - start); Console.WriteLine(day2 - day1);
October 25, 2026 is 25 hours long in Central Europe. The subtraction of instants
shows the real elapsed time, 1 day and 1 hour, while the subtraction of the local
DateTime values reports exactly one day. If the exact duration matters,
convert to UTC first.
$ dotnet run True True 01:00:00 02:00:00 1.01:00:00 1.00:00:00
The two offsets of the ambiguous time are 01:00:00 and 02:00:00 (listed with the standard offset first).
C# time zone information from format specifiers
The offset can also be formatted from a DateTime or
DateTimeOffset with the zzz specifier. For a
DateTime it uses the time zone of the machine, so it only makes sense
for local values; DateTimeOffset holds a real offset.
DateTimeOffset.Now.ToString("zzz") // +02:00
DateTime.Now.ToString("%K") // +02:00 (for a local DateTime)
DateTime.UtcNow.ToString("%K") // Z
Calendar
A calendar is a system of organizing datetime units. Cultures use different
calendars, and some cultures can use several of them. The .NET calendar classes
such as PersianCalendar, HebrewCalendar,
HijriCalendar, and ThaiBuddhistCalendar convert dates and
format them. Internally, a DateTime always uses the Gregorian
calendar; the calendar affects only the conversion to and from text and the
calendar-specific methods.
using System.Globalization;
Console.OutputEncoding = System.Text.Encoding.UTF8;
var perCi = new CultureInfo("fa-IR");
Console.WriteLine($"Default calendar: {perCi.Calendar}");
DateTime now = new DateTime(2026, 10, 18, 13, 35, 18);
foreach (var oc in perCi.OptionalCalendars)
{
perCi.DateTimeFormat.Calendar = oc;
Console.WriteLine(now.ToString("F", perCi));
}
var hc = new HebrewCalendar();
var d1 = new DateTime(5776, 10, 7, hc);
Console.WriteLine($"{d1:d}");
Console.WriteLine($"{hc.GetYear(d1)}/{hc.GetMonth(d1)}/{hc.GetDayOfMonth(d1)}");
var thai = new ThaiBuddhistCalendar();
Console.WriteLine(thai.GetYear(now));
The Persian culture uses the Persian calendar as the default and the Gregorian and Hijri calendars as optional ones. We display one datetime in all supported calendars, then work with the Hebrew and the Thai Buddhist calendar.
foreach (var oc in perCi.OptionalCalendars)
{
perCi.DateTimeFormat.Calendar = oc;
Console.WriteLine(now.ToString("F", perCi));
}
We go through the array of supported calendars, set
DateTimeFormat.Calendar to the current one, and print the datetime.
var hc = new HebrewCalendar(); var d1 = new DateTime(5776, 10, 7, hc);
The calendar is passed as the last argument of the DateTime
constructor. The year 5776 in the Hebrew calendar is 2016 in the Gregorian one, and
GetYear, GetMonth, and GetDayOfMonth of the
calendar return the Hebrew components.
$ dotnet run Default calendar: System.Globalization.PersianCalendar 1405 مهر 26, یکشنبه 13:35:18 2026 اکتبر 18, یکشنبه 13:35:18 1448 جمادیالاول 7, یکشنبه 13:35:18 6/13/2016 5776/10/7 2569
C# Leap year
A leap year is a year containing an additional day. The reason for the extra day in the calendar is the difference between the astronomical and the calendar year (about 365.2425 days).
int[] years = [2000, 2002, 2004, 2008, 2012, 2016, 2020, 2024, 2026, 1900, 1800, 1600];
foreach (int year in years)
{
string answer = DateTime.IsLeapYear(year) ? "is a leap year" : "is not a leap year";
Console.WriteLine($"{year} {answer}");
}
// the rule: divisible by 4, except centuries, unless divisible by 400
static bool IsLeap(int y) => y % 4 == 0 && (y % 100 != 0 || y % 400 == 0);
Console.WriteLine(years.All(y => IsLeap(y) == DateTime.IsLeapYear(y)));
We have an array of years and check whether they are leap years. The
IsLeapYear method determines whether a year is a leap year in the
Gregorian calendar.
static bool IsLeap(int y) => y % 4 == 0 && (y % 100 != 0 || y % 400 == 0);
This is the rule: a year is a leap year if it is divisible by 4, except for century years, which must be divisible by 400. That is why 1900 and 1800 are not leap years and 1600 and 2000 are. The last check confirms that the manual rule agrees with the library.
$ dotnet run 2000 is a leap year 2002 is not a leap year 2004 is a leap year 2008 is a leap year 2012 is a leap year 2016 is a leap year 2020 is a leap year 2024 is a leap year 2026 is not a leap year 1900 is not a leap year 1800 is not a leap year 1600 is a leap year True
C# week numbers and calendar calculations
The ISOWeek class works with ISO 8601 weeks, which start on Monday; the
first week of a year is the one with its first Thursday. Common calculations, such
as the first and last day of a month, are short.
using System.Globalization;
DateTime d = new DateTime(2026, 10, 18);
Console.WriteLine(ISOWeek.GetWeekOfYear(d));
Console.WriteLine(ISOWeek.GetYear(d));
Console.WriteLine(ISOWeek.ToDateTime(2026, 43, DayOfWeek.Monday).ToString("d"));
Console.WriteLine(ISOWeek.GetWeeksInYear(2026));
// first and last day of the month
var first = new DateTime(d.Year, d.Month, 1);
var last = first.AddMonths(1).AddDays(-1);
Console.WriteLine($"{first:d} - {last:d}");
// next Monday
int daysUntilMonday = ((int)DayOfWeek.Monday - (int)d.DayOfWeek + 7) % 7;
Console.WriteLine(d.AddDays(daysUntilMonday == 0 ? 7 : daysUntilMonday).ToString("D"));
// the days of a week from the culture's first day of the week
var fdow = CultureInfo.GetCultureInfo("en-US").DateTimeFormat.FirstDayOfWeek;
Console.WriteLine(fdow);
The program computes the week number, the month bounds, and the date of the next Monday.
Console.WriteLine(ISOWeek.GetWeekOfYear(d)); Console.WriteLine(ISOWeek.GetYear(d));
The week-based year can differ from the calendar year near the end of December and
the beginning of January, so ISOWeek.GetYear must be used together with
the week number.
var last = first.AddMonths(1).AddDays(-1);
The last day of a month is the day before the first day of the next month; this works without knowing the length of the month.
int daysUntilMonday = ((int)DayOfWeek.Monday - (int)d.DayOfWeek + 7) % 7;
The modulo expression gives the number of days until the next Monday; if today is
Monday, we add a full week. In the United States culture, the week starts on Sunday,
which is why the last line prints Sunday.
$ dotnet run 42 2026 10/19/2026 53 10/1/2026 - 10/31/2026 Monday, October 19, 2026 Sunday
The year 2026 has 53 ISO weeks.
C# TimeProvider
Code that calls DateTime.Now directly is hard to test, because the
result changes with every run. Since .NET 8, the abstract TimeProvider
class is the standard way to get the current time. In production, we use
TimeProvider.System; in tests, we pass a fake that returns the time we
want.
// TimeProvider (.NET 8) makes code that depends on "now" testable
var clock = new FakeClock(new DateTimeOffset(2026, 10, 18, 9, 0, 0, TimeSpan.Zero));
var service = new Greeter(clock);
Console.WriteLine(service.Greet());
clock.Advance(TimeSpan.FromHours(10));
Console.WriteLine(service.Greet());
// the real clock
Console.WriteLine(TimeProvider.System.GetUtcNow().Year >= 2026);
// measure elapsed time without Stopwatch.StartNew allocations
long start = TimeProvider.System.GetTimestamp();
await Task.Delay(100);
Console.WriteLine(TimeProvider.System.GetElapsedTime(start).TotalMilliseconds >= 100);
class Greeter(TimeProvider time)
{
public string Greet() => time.GetUtcNow().Hour < 12 ? "Good morning" : "Good afternoon";
}
class FakeClock(DateTimeOffset start) : TimeProvider
{
private DateTimeOffset now = start;
public override DateTimeOffset GetUtcNow() => now;
public void Advance(TimeSpan by) => now += by;
}
The program injects the clock into a class and uses a fake clock to control the time.
class FakeClock(DateTimeOffset start) : TimeProvider
{
private DateTimeOffset now = start;
public override DateTimeOffset GetUtcNow() => now;
public void Advance(TimeSpan by) => now += by;
}
Overriding GetUtcNow is enough to define the clock. The test advances
it by ten hours, and the greeting changes. The Microsoft.Extensions.TimeProvider.Testing
package contains a ready-made FakeTimeProvider.
long start = TimeProvider.System.GetTimestamp(); Console.WriteLine(TimeProvider.System.GetElapsedTime(start).TotalMilliseconds >= 100);
GetTimestamp and GetElapsedTime measure intervals with the
high-resolution timer, like Stopwatch, but through the same abstraction.
$ dotnet run Good morning Good afternoon True True
Source
In this article we have worked with date and time in C#.
Author
List all C# tutorials.