我正在尝试创建一个单元测试,以测试当机器上的时区因设置不正确而更改时的情况。

在测试中,我需要能够在非本地时区创建DateTime对象,以确保运行测试的人可以成功地这样做,而不管他们位于哪里。

从我可以从DateTime构造函数中看到,我可以将TimeZone设置为本地时区,UTC时区或未指定。

我如何创建一个日期时间与特定的时区,如PST?


当前回答

我扩展了乔恩·斯基特的回答,添加了一些额外的内容,让它感觉更接近日期时间。在很大程度上,这将简化比较、相等和转换。我发现datetimezone . now("")函数是特别有用的。

需要注意的一点是这个结构体是在。net 6中编写的。因此,如果您使用的是旧版本,您可能需要替换一些新语言功能的使用。

此外,操作符和接口的实现受到了GitHub上DateTime.cs的. net引用的启发。

/// <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;
}

其他回答

我改变了乔恩斯基特回答一点网络与扩展方法。它在天蓝色上也有魔力。

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);   
}
}

我扩展了乔恩·斯基特的回答,添加了一些额外的内容,让它感觉更接近日期时间。在很大程度上,这将简化比较、相等和转换。我发现datetimezone . now("")函数是特别有用的。

需要注意的一点是这个结构体是在。net 6中编写的。因此,如果您使用的是旧版本,您可能需要替换一些新语言功能的使用。

此外,操作符和接口的实现受到了GitHub上DateTime.cs的. net引用的启发。

/// <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;
}

使用TimeZones类可以很容易地创建时区特定的日期。

TimeZoneInfo.ConvertTime(DateTime.Now, TimeZoneInfo.FindSystemTimeZoneById(TimeZones.Paris.Id));

我喜欢乔恩·斯基特的回答,但我想补充一点。我不确定乔恩是否期待着ctor总是在本地时区通过。但我想把它用在本地以外的情况下。

我正在从数据库中读取值,并且我知道该数据库位于哪个时区。在ctor中,我将传入数据库的时区。但是我想知道当地时间的值。Jon的LocalTime不会返回转换为本地时区日期的原始日期。它返回转换成原始时区的日期(无论您传递给ctor的是什么)。

我认为这些属性名称清楚了…

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);
}

您必须为此创建一个自定义对象。您的自定义对象将包含两个值:

DateTime值 一个时区对象

不确定是否已经有clr提供的数据类型具有此功能,但至少TimeZone组件已经可用。