now interval and c# datetime timezone datetimeoffset nodatime

c# - interval - datetimeoffset vs datetime



Estrategia de implementaciĆ³n para Noda Time en una aplicaciĆ³n MVC5 existente (1)

¡Primero, debo decir que estoy impresionado! Esta es una publicación muy bien escrita, y parece que exploró muchos de los temas relacionados con este tema.

Tu enfoque es bueno. Sin embargo, ofreceré lo siguiente para que lo considere como una mejora.

  • La carpeta modelo podría mejorarse.

    • Yo lo llamaría ZonedDateTimeModelBinder , ya que lo está aplicando para crear valores de ZonedDateTime .

    • bindingContext utilizar el bindingContext para obtener el valor, en lugar de esperar que la entrada siempre esté en request.Form.Get("DateTime") . Puede ver un ejemplo de esto en la carpeta de modelo de WebAPI que escribí para LocalDate . Los archivadores modelo MVC son similares.

    • También verá en ese ejemplo cómo uso las capacidades de análisis de Noda Time en lugar de DateTime.Parse . Puede considerar hacer algo así en el suyo, utilizando LocalDateTimePattern .

    • Asegúrese de comprender cómo funciona AtLeniently , y también de que hemos cambiado su comportamiento para la próxima versión 2.0 (por una buena razón). Consulte "Lenient resolver changes" en la parte inferior de la guía de migración . Si esto es importante en su dominio, es posible que desee considerar el uso del nuevo comportamiento hoy mediante la implementación de su propio resolver.

    • Podría considerar que podría haber contextos en los que la zona horaria del usuario actual no sea la de los datos con los que está trabajando actualmente. Tal vez un administrador está trabajando con los datos de otros usuarios. Por lo tanto, es posible que necesite una sobrecarga que tome la ID de zona horaria como parámetro.

  • Para el caso común, puede intentar registrar el archivador modelo de forma global, lo que le ahorrará algunas pulsaciones de teclas en sus controladores:

    ModelBinders.Binders.Add(typeof(ZonedDateTime), new ZonedDateTimeModelBinder());

    Siempre puedes usar el modo atribuido si hay un parámetro para pasar.

  • Hacia la parte inferior de su código, ZonedDateTime.FromDateTimeOffset(dto).ToInstant().InZone(tz) está bien, pero se puede hacer con menos código. Cualquiera de estos es equivalente:

    • ZonedDateTime.FromDateTimeOffset(dto).WithZone(tz)
    • Instant.FromDateTimeOffset(dto).InZone(tz)
  • Parece que es una aplicación de producción y, por lo tanto, me tomaría el tiempo ahora para configurar la capacidad de actualizar sus propios datos de zona horaria.

    • Consulte la guía del usuario sobre cómo usar archivos NZD en lugar de la copia incrustada en DateTimeZoneProviders.Tzdb .

    • Un buen enfoque es construir-inyectar IDateTimeZoneProvider y registrarlo en un contenedor DI de su elección.

    • Asegúrese de suscribirse a la lista de Anuncios de IANA para saber cuándo se publican nuevas actualizaciones de TZDB. Los archivos de Noda Time NZD suelen aparecer poco tiempo después.

    • O bien, puede hacerse elegante y escribir algo para buscar el último archivo .NZD y actualizar automáticamente su sistema, siempre que comprenda qué necesita (si es que algo) ocurre de su lado después de una actualización. (Esto entra en juego cuando una aplicación incluye la programación de eventos futuros).

  • Propiedades de compañero de WRT - Sí, acepto que son un PITA. Pero desafortunadamente EF no tiene un mejor enfoque en este momento, porque no admite asignaciones de tipos personalizados. EF6 probablemente nunca tendrá eso, pero está siendo rastreado en aspnet / EntityFramework # 242 para EF7.

Ahora, con todo lo dicho, puedes hacer las cosas de forma ligeramente diferente. He hecho lo anterior, y sí, es complejo. Un enfoque simplificado sería:

  • No use tipos de tiempo Noda en sus entidades. Solo use DateTimeOffset lugar de ZonedDateTime .

  • Involucre ZonedDateTime y la zona horaria del usuario solo en el punto donde está haciendo la lógica de la aplicación.

La desventaja de este enfoque es que enturbia las aguas con respecto a su dominio. A veces, la lógica empresarial se abre paso en los servicios en lugar de permanecer en las entidades a las que pertenece. O si permanece en una entidad, ahora debe pasar un parámetro timeZoneId a varios métodos en los que, de lo contrario, no estaría pensando en ello. A veces eso es aceptable, pero a veces no. Solo depende de la cantidad de trabajo que cree para ti.

Por último, abordaré esta parte:

Ahora tenemos una aplicación basada en Noda Time. El objeto ZonedDateTime hace que sea más fácil hacer cálculos ad-hoc y consultas controladas por zonas horarias.

¿Es esta una suposición correcta?

Si y no. Antes de profundizar en la aplicación de todo lo anterior a su aplicación, es posible que desee intentar algunas operaciones de forma aislada con ZonedDateTime .

En primer lugar, ZonedDateTime garantiza que se tenga en cuenta la zona horaria al realizar la conversión hacia y desde otros tipos, y al realizar operaciones matemáticas que implican tiempo instantáneo (utilizando objetos de Duration ).

Donde realmente no ayuda es cuando se trabaja con tiempo calendario. Por ejemplo, si quiero "agregar un día", necesito pensar si eso significa "agregar una duración de 24 horas" o "agregar un período de un día calendario". Para la mayoría de los días, eso será lo mismo, pero no en los días que contienen transiciones DST. Allí, podrían tener 23, 23.5, 24, 24.5 o 25 horas de duración, dependiendo de la zona horaria. ZonedDateTime no le permitirá agregar un Period directamente. En su lugar, debe obtener LocalDateTime , luego agregar el período y luego volver a aplicar la zona horaria para volver a ZonedDateTime .

Entonces, piense cuidadosamente si lo necesita de la misma manera en todas partes o no. Si la lógica de su aplicación es estrictamente de días calendario, entonces puede encontrarla mejor escrita exclusivamente en términos de LocalDate . Puede que tenga que trabajar con las diversas propiedades y métodos para usar realmente esa lógica, pero al menos la lógica se modela en su forma más pura.

Espero que esto ayude, y espero que sea una publicación útil para otros lectores. Buena suerte, y siéntete libre de llamarme para pedir ayuda.

Nuestra aplicación es una gran aplicación ASP.NET MVC de n niveles que depende en gran medida de fechas y horarios (locales). Hasta ahora, hemos utilizado DateTime para todos nuestros modelos, lo que funcionó bien porque durante años fuimos estrictamente un sitio web nacional, tratando con una sola zona horaria.

Ahora las cosas han cambiado y estamos abriendo nuestras puertas para una audiencia internacional. El primer pensamiento fue "Oh, mierda. ¡Necesitamos refactorizar toda nuestra solución!"

TimeZoneInfo

Abrimos LinQPad y comenzamos a esbozar varios conversores para transformar objetos regulares de DateTime en objetos DateTimeOffset , en función de un objeto TimeZoneInfo que se creó en función del valor de identificación del TimeZone del usuario del perfil de dicho usuario.

Pensamos que cambiaríamos todas las propiedades de DateTime en los modelos en DateTimeOffset y terminaríamos con eso. Después de todo, ahora teníamos toda la información que necesitábamos para almacenar y mostrar la fecha y hora local del usuario.

Gran parte de los fragmentos de código se inspiraron en la publicación del blog de Rick Strahl sobre el tema.

NodaTime y DateTimeOffset

Pero luego leí el excelente comentario de Matt Johnson . Él validó mi intención de cambiar a DateTimeOffset afirmando: "DateTimeOffset es esencial en una aplicación web" .

Con respecto al tiempo de Noda, Matt dice:

Hablando de Noda Time, no estaré de acuerdo con usted en que tiene que reemplazar todo en su sistema. Claro, si lo haces, tendrás muchas menos oportunidades de cometer errores, pero ciertamente puedes usar Noda Time donde tenga sentido. Personalmente trabajé en sistemas que necesitaban hacer conversiones de zonas horarias utilizando husos horarios de IANA (por ejemplo, "América / Los_Angeles"), pero seguí todo lo demás en los tipos DateTime y DateTimeOffset. En realidad, es bastante común ver que Noda Time se usa mucho en la lógica de la aplicación, pero se deja completamente fuera de los DTO y las capas de persistencia. En algunas tecnologías, como Entity Framework, no se podía usar Noda Time directamente si se quería, porque no hay dónde conectarlo.

Esto podría haberse dirigido directamente a nosotros, ya que estamos en ese escenario exacto en este momento, incluida nuestra opción de utilizar las zonas horarias de IANA.

Nuestro plan, bueno o malo?

Nuestro objetivo principal es crear el flujo de trabajo menos complejo para tratar fechas y horas en varias zonas horarias. Evite los cálculos de zona horaria tanto como sea posible en nuestros Servicios, Repositorios y Controladores.

En pocas palabras, el plan es aceptar fechas y horas locales desde nuestro front-end, convirtiéndolas lo más pronto posible en un ZonedDateTime y convertirlas a DateTimeOffset más tarde posible, justo antes de guardar la información en la base de datos.

El factor clave para determinar el ZonedDateTime correcto es la propiedad ZonedDateTime en el modelo de Usuario.

public class ApplicationUser : IdentityUser { [Required] public string TimezoneId { get; set; } }

Hora local a NodaTime

Para evitar una gran cantidad de código duplicado, nuestro plan es crear ModelBinders personalizados que conviertan DateTime local en ZonedDateTime .

public class LocalDateTimeModelBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { HttpRequestBase request = controllerContext.HttpContext.Request; // Get the posted local datetime string dt = request.Form.Get("DateTime"); DateTime dateTime = DateTime.Parse(dt); // Get the logged in User IPrincipal p = controllerContext.HttpContext.User; var user = p.ApplicationUser(); // Convert to ZonedDateTime LocalDateTime localDateTime = LocalDateTime.FromDateTime(dateTime); IDateTimeZoneProvider timeZoneProvider = DateTimeZoneProviders.Tzdb; var usersTimezone = timeZoneProvider[user.TimezoneId]; var zonedDbDateTime = usersTimezone.AtLeniently(localDateTime); return zonedDbDateTime; } }

Podemos ensuciar nuestros controladores con estos Carpetas Modelo.

[HttpPost] [Authorize] public ActionResult SimpleDateTime([ModelBinder(typeof (LocalDateTimeModelBinder))] ZonedDateTime dateTime) { // Do stuff with the ZonedDateTime object }

¿Estamos pensando demasiado en esto?

Almacenamiento de DateTimeOffset en la base de datos

Utilizaremos el concepto de propiedades de Buddy . Para ser sincero, no soy un gran admirador de esto, debido a la confusión que crea. Los nuevos desarrolladores probablemente tendrán dificultades con el hecho de que tenemos más de 1 forma de guardar una fecha de creación.

Sugerencias sobre cómo mejorar esto son bienvenidas. He leído comentarios sobre cómo ocultar las propiedades de IntelliSense para establecer las propiedades reales en private .

public class Item { public int Id { get; set; } public string Title { get; set; } // The "real" property public DateTimeOffset DateCreated { get; private set; } // Buddy property [NotMapped] public ZonedDateTime CreatedAt { get { // DateTimeOffset to NodaTime, based on User''s TZ return ToZonedDateTime(DateCreated); } // NodaTime to DateTimeOffset set { DateCreated = value.ToDateTimeOffset(); } } public string OwnerId { get; set; } [ForeignKey("OwnerId")] public virtual ApplicationUser Owner { get; set; } // Helper method public ZonedDateTime ToZonedDateTime(DateTimeOffset dateTime, string tz = null) { if (string.IsNullOrEmpty(tz)) { tz = Owner.TimezoneId; } IDateTimeZoneProvider timeZoneProvider = DateTimeZoneProviders.Tzdb; var usersTimezoneId = tz; var usersTimezone = timeZoneProvider[usersTimezoneId]; var zonedDate = ZonedDateTime.FromDateTimeOffset(dateTime); return zonedDate.ToInstant().InZone(usersTimezone); } }

Todo en el medio

Ahora tenemos una aplicación basada en Noda Time. El objeto ZonedDateTime hace que sea más fácil hacer cálculos ad-hoc y consultas controladas por zonas horarias.

¿Es esta una suposición correcta?