Reputation: 5654
I'm trying to create a unit test to test the case for when the timezone changes on a machine because it has been incorrectly set and then corrected.
In the test I need to be able to create DateTime objects in a none local time zone to ensure that people running the test can do so successfully irrespective of where they are located.
From what I can see from the DateTime constructor I can set the TimeZone to be either the local timezone, the UTC timezone or not specified.
How do I create a DateTime with a specific timezone like PST?
Upvotes: 224
Views: 322216
Reputation: 3283
I altered Jon Skeet answer a bit for the web with extension method. It also works on azure like a charm.
public static class DateTimeWithZone
{
private static readonly TimeZoneInfo timeZone;
static DateTimeWithZone()
{
//I added web.config <add key="CurrentTimeZoneId" value="Central Europe Standard Time" />
//You can add value directly into function.
timeZone = TimeZoneInfo.FindSystemTimeZoneById(ConfigurationManager.AppSettings["CurrentTimeZoneId"]);
}
public static DateTime LocalTime(this DateTime t)
{
return TimeZoneInfo.ConvertTime(t, timeZone);
}
}
Upvotes: 6
Reputation: 1095
I have extended Jon Skeet's answer by adding some extras to make it feel a bit closer to a DateTime
. For the most part this will simplify comparison, equality, and conversion. I have found the DateTimeZoned.Now("")
function to be of particular use.
One item to note is this struct has been written in .NET 6. So if you are using an older version you may need to replace some of the usage of the newer language features.
Also, the implementation of operators and interfaces was inspired by the .NET reference for DateTime.cs on GitHub.
/// <summary>
/// This value type represents a date and time with a specific time zone applied. If no time zone is provided, the local system time zone will be used.
/// </summary>
public readonly struct DateTimeZoned : IComparable, IComparable<DateTimeZoned>, IEquatable<DateTimeZoned>
{
/// <summary>
/// Creates a new zoned <see cref="DateTime"/> with the system time zone.
/// </summary>
/// <param name="dateTime">The local <see cref="DateTime"/> to apply a time zone to.</param>
public DateTimeZoned(DateTime dateTime)
{
var local = DateTime.SpecifyKind(dateTime, DateTimeKind.Local);
UniversalTime = TimeZoneInfo.ConvertTimeToUtc(local, TimeZoneInfo.Local);
TimeZone = TimeZoneInfo.Local;
}
/// <summary>
/// Creates a new zoned <see cref="DateTime"/> with the specified time zone.
/// </summary>
/// <param name="dateTime">The <see cref="DateTime"/> to apply a time zone to.</param>
/// <param name="timeZone">The time zone to apply.</param>
/// <remarks>
/// Assumes the provided <see cref="DateTime"/> is from the specified time zone.
/// </remarks>
public DateTimeZoned(DateTime dateTime, TimeZoneInfo timeZone)
{
var unspecified = DateTime.SpecifyKind(dateTime, DateTimeKind.Unspecified);
UniversalTime = TimeZoneInfo.ConvertTimeToUtc(unspecified, timeZone);
TimeZone = timeZone;
}
/// <summary>
/// Creates a new zoned <see cref="DateTime"/> with the specified time zone.
/// </summary>
/// <param name="dateTime">The <see cref="DateTime"/> to apply a time zone to.</param>
/// <param name="timeZone">The time zone to apply.</param>
/// <remarks>
/// Assumes the provided <see cref="DateTime"/> is from the specified time zone.
/// </remarks>
public DateTimeZoned(DateTime dateTime, string timeZone)
{
var unspecified = DateTime.SpecifyKind(dateTime, DateTimeKind.Unspecified);
var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone);
UniversalTime = TimeZoneInfo.ConvertTimeToUtc(unspecified, timeZoneInfo);
TimeZone = timeZoneInfo;
}
/// <summary>
/// The UTC <see cref="DateTime"/> for the stored value.
/// </summary>
public DateTime UniversalTime { get; init; }
/// <summary>
/// The selected time zone.
/// </summary>
public TimeZoneInfo TimeZone { get; init; }
/// <summary>
/// The localized <see cref="DateTime"/> for the stored value.
/// </summary>
public DateTime LocalTime => TimeZoneInfo.ConvertTime(UniversalTime, TimeZone);
/// <summary>
/// Specifies whether UTC and localized values are the same.
/// </summary>
public bool IsUtc => UniversalTime == LocalTime;
/// <summary>
/// Returns a new <see cref="DateTimeZoned"/> with the current <see cref="LocalTime"/> converted to the target time zone.
/// </summary>
/// <param name="timeZone">The time zone to convert to.</param>
public DateTimeZoned ConvertTo(string timeZone)
{
var converted = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(LocalTime, TimeZone.Id, timeZone);
return new DateTimeZoned(converted, timeZone);
}
/// <summary>
/// Returns a new <see cref="DateTimeZoned"/> with the current <see cref="LocalTime"/> converted to the target time zone.
/// </summary>
/// <param name="timeZone">The time zone to convert to.</param>
public DateTimeZoned ConvertTo(TimeZoneInfo timeZone)
{
var converted = TimeZoneInfo.ConvertTime(LocalTime, TimeZone, timeZone);
return new DateTimeZoned(converted, timeZone.Id);
}
/// <summary>
/// Returns the value as a string in the round-trip date/time pattern.
/// </summary>
/// <remarks>
/// This applies the .ToString("o") option on <see cref="LocalTime"/>.
/// </remarks>
public string ToLocalString()
{
var local = new DateTimeOffset(LocalTime, TimeZone.BaseUtcOffset);
return local.ToString("o");
}
/// <summary>
/// Returns the value as a string in the universal sortable date/time pattern.
/// </summary>
/// <remarks>
/// This is applies the .ToString("u") option on <see cref="UniversalTime"/>.
/// </remarks>
public string ToUniversalString()
{
return UniversalTime.ToString("u");
}
/// <summary>
/// Returns a <see cref="DateTime"/> representing the current date and time adjusted to the system time zone.
/// </summary>
/// <remarks>
/// This is functionally equivalent to <see cref="DateTime.Now"/> and has been added for completeness.
/// </remarks>
public static DateTime Now() => TimeZoneInfo.ConvertTime(DateTime.UtcNow, TimeZoneInfo.Local);
/// <summary>
/// Returns a <see cref="DateTime"/> representing the current date and time adjusted to the specified time zone.
/// </summary>
/// <param name="timeZone">The time zone to apply.</param>
public static DateTime Now(TimeZoneInfo timeZone) => TimeZoneInfo.ConvertTime(DateTime.UtcNow, timeZone);
/// <summary>
/// Returns a <see cref="DateTime"/> representing the current date and time adjusted to the specified time zone.
/// </summary>
/// <param name="timeZone">The time zone to apply.</param>
public static DateTime Now(string timeZone)
{
var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone);
return TimeZoneInfo.ConvertTime(DateTime.UtcNow, timeZoneInfo);
}
/// <inheritdoc/>
public override bool Equals(object? value)
{
return value is DateTimeZoned d2 && this == d2;
}
/// <inheritdoc/>
public bool Equals(DateTimeZoned value)
{
return this == value;
}
/// <summary>
/// Compares two <see cref="DateTimeZoned"/> values for equality.
/// </summary>
/// <param name="d1">The first value to compare.</param>
/// <param name="d2">The second value to compare.</param>
/// <returns>
/// Returns <see langword="true"/> if the two <see cref="DateTimeZoned"/> values are equal, or <see langword="false"/> if they are not equal.
/// </returns>
public static bool Equals(DateTimeZoned d1, DateTimeZoned d2)
{
return d1 == d2;
}
/// <summary>
/// Compares two <see cref="DateTimeZoned"/> values, returning an integer that indicates their relationship.
/// </summary>
/// <param name="d1">The first value to compare.</param>
/// <param name="d2">The second value to compare.</param>
/// <returns>
/// Returns 1 if the first value is greater than the second, -1 if the second value is greater than the first, or 0 if the two values are equal.
/// </returns>
public static int Compare(DateTimeZoned d1, DateTimeZoned d2)
{
var ticks1 = d1.UniversalTime.Ticks;
var ticks2 = d2.UniversalTime.Ticks;
if (ticks1 > ticks2)
return 1;
else if (ticks1 < ticks2)
return -1;
else
return 0;
}
/// <inheritdoc/>
public int CompareTo(object? value)
{
if (value == null)
return 1;
if (value is not DateTimeZoned)
throw new ArgumentException(null, nameof(value));
return Compare(this, (DateTimeZoned)value);
}
/// <inheritdoc/>
public int CompareTo(DateTimeZoned value)
{
return Compare(this, value);
}
/// <inheritdoc/>
public override int GetHashCode()
{
var ticks = UniversalTime.Ticks;
return unchecked((int)ticks) ^ (int)(ticks >> 32);
}
public static TimeSpan operator -(DateTimeZoned d1, DateTimeZoned d2) => new(d1.UniversalTime.Ticks - d2.UniversalTime.Ticks);
public static bool operator ==(DateTimeZoned d1, DateTimeZoned d2) => d1.UniversalTime.Ticks == d2.UniversalTime.Ticks;
public static bool operator !=(DateTimeZoned d1, DateTimeZoned d2) => d1.UniversalTime.Ticks != d2.UniversalTime.Ticks;
public static bool operator <(DateTimeZoned d1, DateTimeZoned d2) => d1.UniversalTime.Ticks < d2.UniversalTime.Ticks;
public static bool operator <=(DateTimeZoned d1, DateTimeZoned d2) => d1.UniversalTime.Ticks <= d2.UniversalTime.Ticks;
public static bool operator >(DateTimeZoned d1, DateTimeZoned d2) => d1.UniversalTime.Ticks > d2.UniversalTime.Ticks;
public static bool operator >=(DateTimeZoned d1, DateTimeZoned d2) => d1.UniversalTime.Ticks >= d2.UniversalTime.Ticks;
}
Upvotes: 2
Reputation: 2639
For date/time with offset for a specific time zone (neither local, nor UTC) you can to use DateTimeOffset class:
var time = TimeSpan.Parse("9:00");
var est = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
var nationalDateTime = new DateTimeOffset(DateTime.Today.Ticks + time.Ticks, est.BaseUtcOffset);
Upvotes: 0
Reputation: 1585
Try TimeZoneInfo.ConvertTime(dateTime, sourceTimeZone, destinationTimeZone)
Upvotes: 5
Reputation: 2444
Using TimeZones class makes it easy to create timezone specific date.
TimeZoneInfo.ConvertTime(DateTime.Now, TimeZoneInfo.FindSystemTimeZoneById(TimeZones.Paris.Id));
Upvotes: 0
Reputation: 1499660
Jon's answer talks about TimeZone, but I'd suggest using TimeZoneInfo instead.
Personally I like keeping things in UTC where possible (at least for the past; storing UTC for the future has potential issues), so I'd suggest a structure like this:
public struct DateTimeWithZone
{
private readonly DateTime utcDateTime;
private readonly TimeZoneInfo timeZone;
public DateTimeWithZone(DateTime dateTime, TimeZoneInfo timeZone)
{
var dateTimeUnspec = DateTime.SpecifyKind(dateTime, DateTimeKind.Unspecified);
utcDateTime = TimeZoneInfo.ConvertTimeToUtc(dateTimeUnspec, timeZone);
this.timeZone = timeZone;
}
public DateTime UniversalTime { get { return utcDateTime; } }
public TimeZoneInfo TimeZone { get { return timeZone; } }
public DateTime LocalTime
{
get
{
return TimeZoneInfo.ConvertTime(utcDateTime, timeZone);
}
}
}
You may wish to change the "TimeZone" names to "TimeZoneInfo" to make things clearer - I prefer the briefer names myself.
Upvotes: 281
Reputation: 37947
The other answers here are useful but they don't cover how to access Pacific specifically - here you go:
public static DateTime GmtToPacific(DateTime dateTime)
{
return TimeZoneInfo.ConvertTimeFromUtc(dateTime,
TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"));
}
Oddly enough, although "Pacific Standard Time" normally means something different from "Pacific Daylight Time," in this case it refers to Pacific time in general. In fact, if you use FindSystemTimeZoneById
to fetch it, one of the properties available is a bool telling you whether that timezone is currently in daylight savings or not.
You can see more generalized examples of this in a library I ended up throwing together to deal with DateTimes I need in different TimeZones based on where the user is asking from, etc:
https://github.com/b9chris/TimeZoneInfoLib.Net
This won't work outside of Windows (for example Mono on Linux) since the list of times comes from the Windows Registry:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\
Underneath that you'll find keys (folder icons in Registry Editor); the names of those keys are what you pass to FindSystemTimeZoneById
. On Linux you have to use a separate Linux-standard set of timezone definitions, which I've not adequately explored.
Upvotes: 60
Reputation: 868
I like Jon Skeet's answer, but would like to add one thing. I'm not sure if Jon was expecting the ctor to always be passed in the Local timezone. But I want to use it for cases where it's something other then local.
I'm reading values from a database, and I know what timezone that database is in. So in the ctor, I'll pass in the timezone of the database. But then I would like the value in local time. Jon's LocalTime does not return the original date converted into a local timezone date. It returns the date converted into the original timezone (whatever you had passed into the ctor).
I think these property names clear it up...
public DateTime TimeInOriginalZone { get { return TimeZoneInfo.ConvertTime(utcDateTime, timeZone); } }
public DateTime TimeInLocalZone { get { return TimeZoneInfo.ConvertTime(utcDateTime, TimeZoneInfo.Local); } }
public DateTime TimeInSpecificZone(TimeZoneInfo tz)
{
return TimeZoneInfo.ConvertTime(utcDateTime, tz);
}
Upvotes: 4
Reputation: 9483
The DateTimeOffset structure was created for exactly this type of use.
See: http://msdn.microsoft.com/en-us/library/system.datetimeoffset.aspx
Here's an example of creating a DateTimeOffset object with a specific time zone:
DateTimeOffset do1 = new DateTimeOffset(2008, 8, 22, 1, 0, 0, new TimeSpan(-5, 0, 0));
Upvotes: 70
Reputation: 95432
You'll have to create a custom object for that. Your custom object will contain two values:
Not sure if there already is a CLR-provided data type that has that, but at least the TimeZone component is already available.
Upvotes: 2