【问题标题】:Convert all DateTime fields in a complex object from local time zone to UTC将复杂对象中的所有 DateTime 字段从本地时区转换为 UTC
【发布时间】:2012-09-22 00:00:39
【问题描述】:

我有一个客户对象,其中包含有关客户的所有详细信息 出生日期、加入日期等。

我想将所有日期字段转换为通用日期时间并存储在数据库中。

谁能帮助我如何从客户对象中获取所有日期字段并将其转换为通用日期时间?

我想要一个解决方案,当我调用 Customer.ChangeToUTC() 时,客户中存在的所有日期时间字段都应更改为世界时。

【问题讨论】:

  • 你试用过的代码怎么样?您使用的是哪个 DAL? EF 还是 ADO.NET?

标签: c# datetime utc


【解决方案1】:

我也遇到过同样的问题。在我的情况下,解决方案是必要的,因为我们有太多不同的复杂模型,带有DateTime 字段。 为了我的需要,我编写了下一个扩展:

public static class ObjectExtensions
{
    /// <summary>
    /// Convert all DateTime fields in a complex object from UTC to a destination time zone.
    /// </summary>
    /// <typeparam name="TInput">Type of an object that will be converted.</typeparam>
    /// <param name="obj">Object that will be deeply converted.</param>
    /// <param name="destTimeZone"><c>TimeZoneInfo</c> object of a destination time zone.</param>
    public static void DeepConvertFromUtc<TInput>(this TInput obj, TimeZoneInfo destTimeZone)
        where TInput : class
    {
        obj.DeepConvert(TimeZoneInfo.Utc, destTimeZone);
    }

    /// <summary>
    /// Convert all DateTime fields in a complex object from source time zone to UTC.
    /// </summary>
    /// <typeparam name="TInput">Type of an object that will be converted.</typeparam>
    /// <param name="obj">Object that will be deeply converted.</param>
    /// <param name="sourceTimeZone"><c>TimeZoneInfo</c> object of a source time zone.</param>
    public static void DeepConvertToUtc<TInput>(this TInput obj, TimeZoneInfo sourceTimeZone)
        where TInput : class
    {
        obj.DeepConvert(sourceTimeZone, TimeZoneInfo.Utc);
    }

    /// <summary>
    /// Convert all DateTime fields in a complex object from UTC to a destination time zone.
    /// </summary>
    /// <typeparam name="TInput">Type of an object that will be converted.</typeparam>
    /// <param name="obj">Object that will be deeply converted.</param>
    /// <param name="sourceTimeZone"><c>TimeZoneInfo</c> object of a source time zone.</param>
    /// <param name="destTimeZone"><c>TimeZoneInfo</c> object of a destination time zone.</param>
    public static void DeepConvertTime<TInput>(this TInput obj, TimeZoneInfo sourceTimeZone, TimeZoneInfo destTimeZone)
        where TInput : class
    {
        obj.DeepConvert(sourceTimeZone, destTimeZone);
    }

    private static void DeepConvert<TInput>(this TInput obj, TimeZoneInfo sourceTimeZone, TimeZoneInfo destTimeZone)
        where TInput : class
    {
        if (obj == null)
        {
            return;
        }

        var items = obj as ICollection;
        if (items != null)
        {
            foreach (var item in items)
            {
                item.DeepConvert(sourceTimeZone, destTimeZone);
            }

            return;
        }

        var props = obj.GetType().GetProperties();
        foreach (var prop in props.Where(prop => !IsIgnore(prop)))
        {
            if (prop.PropertyType == typeof(DateTime) || prop.PropertyType == typeof(DateTime?))
            {
                prop.ConvertDateTime(obj, sourceTimeZone, destTimeZone);
                continue;
            }

            var value = prop.GetValue(obj);
            var list = value as ICollection;
            if (list != null)
            {
                foreach (var item in list)
                {
                    item.DeepConvert(sourceTimeZone, destTimeZone);
                }

                continue;
            }

            // here I check that an object is located in one of my assemblies
            if (prop.PropertyType.Assembly.FullName.StartsWith("Should be your namespace"))
            {
                value.DeepConvert(sourceTimeZone, destTimeZone);
            }
        }
    }

    private static void ConvertDateTime<TInput>(this PropertyInfo prop, TInput obj, TimeZoneInfo sourceTimeZone, TimeZoneInfo destTimeZone)
        where TInput : class
    {
        var value = prop.GetValue(obj);
        if (value != null)
        {
            var dateTime = DateTime.SpecifyKind((DateTime)value, DateTimeKind.Unspecified);
            value = TimeZoneInfo.ConvertTime(dateTime, sourceTimeZone, destTimeZone);

            var setMethod = prop.SetMethod;
            if (setMethod != null)
            {
                setMethod.Invoke(obj, new[] { value });
            }
        }
    }

    private static bool IsIgnore(this PropertyInfo prop)
    {
        var attr = prop.GetCustomAttribute<IgnoreUtcConversionAttribute>();
        return attr != null;
    }
}

public class IgnoreUtcConversionAttribute : Attribute
{
}

你可以很简单地使用它:

yourComplexObject.DeepConvertToUtc(TimeZoneInfo.Local);

// a collection could be converted as well
collection.DeepConvertToUtc(TimeZoneInfo.Local);

你也可以从FindSystemTimeZoneById方法获得TimeZineInfo对象:

var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Belarus Standard Time");
yourComplexObject.DeepConvertToUtc(timeZoneInfo);

如果你的模型中有两种方式的引用,你必须用[IgnoreUtcConversion]装饰一个引用:

public class Customer
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    public DateTime Birth { get; set; }

    public Address Address { get; set; }
}

public class Address
{
    public string Street { get; set; }

    public DateTime LastUpdated { get; set; }

    [IgnoreUtcConversion]
    public Customer Customer { get; set; }
}

【讨论】:

    【解决方案2】:

    这很简单:

    TimeZone ltz = TimeZone.CurrentTimeZone;
    
    DateTime t1 = DateTime.Now;
    
    DateTime t2 = ltz.ToUniversalTime(t1);
    

    【讨论】:

    • 请注意,由于夏令时更改,从本地时间到通用时间的转换可能会产生歧义。框架在 "interesting" way 中处理这个问题
    • 不,它仍然会模棱两可。当时钟倒退时,某些本地时间会出现两次。 (我强烈建议这些天不要使用TimeZone;改用TimeZoneInfo。但它不会改变固有的歧义。)
    • 是的......非常感谢......但我不知道该怎么做......@JonSkeet
    【解决方案3】:

    谁能帮助我如何从客户对象中获取所有日期字段并将其转换为通用日期时间

    只需手动操作即可。但是,我不会通过重用原始 Customer 类型中的属性来做到这一点。我要么创建对象之前(当你第一次获取数据时)这样做,要么作为轻量级 DTO 的一部分来进行数据库传输。

    当您确实知道如何解释任何特定属性时,推理日期/时间属性就足够困难了 - 如果您不知道某个属性是通用的、本地的还是未指定的会更难。

    日期之类的东西甚至没有多大意义去考虑是否普遍。为了将它们存储在数据库中,您可能希望使用DateTimeKind.Utc 显式创建它们,但从根本上说,日期就是日期。 .NET 框架的问题之一是它没有单独的日期数据类型。

    我有一个名为Noda Time 的项目,它是.NET 的替代日期/时间API。一种选择当然是使用它,但假设您想坚持使用 BCL 类型,我仍然认为值得阅读我们的 conceptstype choices 文档,以帮助您以正确的方式思考日期和时间.最好弄清楚您对模型中的每个属性使用哪种类型,然后记录下来。您需要在代码中以不同方式处理它们。

    【讨论】:

      【解决方案4】:

      如果您熟悉日期时间,这很简单;

      customer.BirthDate = customer.BirthDate.ToUniversalTime();
      

      只需对您的所有日期字段执行上述操作,然后在您的客户 ID 号上使用 where 子句运行更新查询。

      【讨论】:

      • 问题是当我向客户添加一个新的日期时,我必须将其称为 customer.newdatefiled=new custome.nedatefile.ToUniversalTime()。相反,我想创建一个函数,这样当我调用 customer.ConvertToUST() 时,它应该将客户对象中的所有日期字段更改为通用日期时间
      • 首先感谢您提供的宝贵信息。问题是,当我向客户添加一个新日期时,我必须将其称为 customer.newdatefiled=new custome.nedatefile.ToUniversalTime()。相反,我想创建一个函数,这样当我调用 customer.ConvertToUST() 时,它应该将客户对象中的所有日期字段更改为通用日期时间
      • @AbdulAdhar:是的,当您向模型添加字段时,您必须做一些工作以确保您在整个应用程序中正确处理它。这是可以预料的。您不应该期望能够将相同的规则应用于所有与日期/时间相关的值。正如我在回答中所说,无论如何,我建议不要对通用日期和本地日期/时间使用相同的属性。
      • 如果您想要一个更改所有日期时间的函数,您需要编写一个在所有字段上使用 DateTime.ToUniversalTime() 的函数。 .Net 中没有内置方法可以解析您的对象以获取 DateTimes 并进行转换。您需要在所有日期时间调用此方法。
      猜你喜欢
      • 2016-06-11
      • 1970-01-01
      • 2020-03-27
      • 1970-01-01
      • 2011-04-14
      • 2011-09-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多