Tengo un sitio web alojado en una zona horaria diferente a la de los usuarios que usan la aplicación. Además de esto, los usuarios pueden tener una zona horaria específica. Me preguntaba cómo abordan esto otros usuarios y aplicaciones de SO. La parte más obvia es que dentro de la base de datos, la fecha / hora se almacena en UTC. Cuando está en el servidor, todas las fechas / horas deben tratarse en UTC. Sin embargo, veo tres problemas que estoy tratando de superar:
Obtener la hora actual en UTC (resuelto fácilmente con
DateTime.UtcNow
).Extraer fechas / horas de la base de datos y mostrarlas al usuario. Potencialmente, hay muchas llamadas para imprimir fechas en diferentes vistas. Estaba pensando en alguna capa entre la vista y los controladores que podrían resolver este problema. O tener un método de extensión personalizado
DateTime
(ver más abajo). El principal inconveniente es que en cada ubicación de uso de una fecha y hora en una vista, ¡se debe llamar al método de extensión!Esto también agregaría dificultad para usar algo como el
JsonResult
. Ya no podría llamar fácilmenteJson(myEnumerable)
, tendría que ser asíJson(myEnumerable.Select(transformAllDates))
. ¿Quizás AutoMapper podría ayudar en esta situación?Obteniendo información del usuario (Local a UTC). Por ejemplo, PUBLICAR un formulario con una fecha requeriría convertir la fecha a UTC anterior. Lo primero que viene a la mente es crear una costumbre
ModelBinder
.
Aquí están las extensiones que pensé usar en las vistas:
public static class DateTimeExtensions
{
public static DateTime UtcToLocal(this DateTime source,
TimeZoneInfo localTimeZone)
{
return TimeZoneInfo.ConvertTimeFromUtc(source, localTimeZone);
}
public static DateTime LocalToUtc(this DateTime source,
TimeZoneInfo localTimeZone)
{
source = DateTime.SpecifyKind(source, DateTimeKind.Unspecified);
return TimeZoneInfo.ConvertTimeToUtc(source, localTimeZone);
}
}
Creo que tratar con zonas horarias sería algo tan común considerando que muchas aplicaciones ahora están basadas en la nube, donde la hora local del servidor podría ser muy diferente a la zona horaria esperada.
¿Se ha resuelto con elegancia esto antes? ¿Hay algo que me falta? Ideas y pensamientos son muy apreciados.
EDITAR: Para aclarar la confusión, pensé agregar algunos detalles más. El problema en este momento no es cómo almacenar las horas UTC en la base de datos, sino más bien el proceso de pasar de UTC-> Local y Local-> UTC. Como señala @Max Zerbini, obviamente es inteligente poner el código UTC-> Local en la vista, pero ¿está usando DateTimeExtensions
realmente la respuesta? Al recibir información del usuario, ¿tiene sentido aceptar fechas como la hora local del usuario (ya que eso es lo que JS estaría usando) y luego usar a ModelBinder
para transformar a UTC? La zona horaria del usuario se almacena en la base de datos y se recupera fácilmente.
fuente
ModelBinder
.Respuestas:
No es que esto sea una recomendación, es más compartir un paradigma, pero la forma más agresiva que he visto de manejar la información de zona horaria en una aplicación web (que no es exclusiva de ASP.NET MVC) fue la siguiente:
Todas las horas en el servidor son UTC. Eso significa usar, como dijiste,
DateTime.UtcNow
.Intente confiar en que el cliente pase las fechas al servidor lo menos posible. Por ejemplo, si necesita "ahora", no cree una fecha en el cliente y luego páselo al servidor. Cree una fecha en su GET y páselo al ViewModel o en POST do
DateTime.UtcNow
.Hasta ahora, tarifa bastante estándar, pero aquí es donde las cosas se ponen "interesantes".
Si tiene que aceptar una fecha del cliente, use javascript para asegurarse de que los datos que está publicando en el servidor estén en UTC. El cliente sabe en qué zona horaria se encuentra, por lo que puede convertir con precisión razonable los tiempos en UTC.
Al representar vistas, usaban el
<time>
elemento HTML5 , nunca representaban las fechas y horas directamente en ViewModel. Fue implementado como unaHtmlHelper
extensión, algo asíHtml.Time(Model.when)
. Rendiría<time datetime='[utctime]' data-date-format='[datetimeformat]'></time>
.Luego usarían javascript para traducir la hora UTC a la hora local del cliente. El script buscaría todos los
<time>
elementos y usaría ladate-format
propiedad de datos para formatear la fecha y llenar el contenido del elemento.De esta manera, nunca tuvieron que hacer un seguimiento, almacenar o administrar la zona horaria de un cliente. Al servidor no le importaba en qué zona horaria estaba el cliente, ni tenía que hacer ninguna traducción de zona horaria. Simplemente escupió UTC y dejó que el cliente lo convirtiera en algo que fuera razonable. Lo cual es fácil desde el navegador, porque sabe en qué zona horaria se encuentra. Si el cliente cambia su zona horaria, la aplicación web se actualizará automáticamente. Lo único que almacenaron fue la cadena de formato de fecha y hora para la configuración regional del usuario.
No digo que haya sido el mejor enfoque, pero era diferente y no lo había visto antes. Tal vez obtendrá algunas ideas interesantes de él.
fuente
<time>
idea es muy bueno. La única desventaja es que significa que necesito para consultar todo el DOM para estos elementos, los cuales no es exactamente agradable solo para transformar fechas (¿qué pasa con JS deshabilitado?). Gracias de nuevo!Después de varios comentarios, esta es mi solución final, que creo que es limpia y simple y cubre los problemas de horario de verano.
1 - Manejamos la conversión a nivel de modelo. Entonces, en la clase Modelo, escribimos:
2 - En un ayudante global hacemos nuestra función personalizada "ToLocalTime":
3 - Podemos mejorar esto aún más, guardando la identificación de la zona horaria en cada perfil de usuario para poder recuperarla de la clase de usuario en lugar de usar la "hora estándar de China" constante:
4 - Aquí podemos obtener la lista de zonas horarias para mostrar al usuario para seleccionar desde un cuadro desplegable:
Entonces, ahora a las 9:25 AM en China, sitio web alojado en EE. UU., Fecha guardada en UTC en la base de datos, aquí está el resultado final:
EDITAR
Gracias a Matt Johnson por señalar las partes débiles de la solución original, y perdón por eliminar la publicación original, pero tuve problemas para obtener el formato de visualización de código correcto ... resultó que el editor tiene problemas para mezclar "viñetas" con "pre código", así que eliminado los bulles y estuvo bien.
fuente
En la sección de eventos en sf4answers , los usuarios ingresan una dirección para un evento, así como una fecha de inicio y una fecha de finalización opcional. Estos tiempos se traducen en un
datetimeoffset
servidor SQL que representa el desplazamiento desde UTC.Este es el mismo problema al que se enfrenta (aunque está adoptando un enfoque diferente, ya que está utilizando
DateTime.UtcNow
); tiene una ubicación y necesita traducir una hora de una zona horaria a otra.Hay dos cosas principales que hice que funcionaron para mí. Primero, usa la
DateTimeOffset
estructura , siempre. Representa la compensación de UTC y si puede obtener esa información de su cliente, le facilitará un poco la vida.En segundo lugar, al realizar las traducciones, suponiendo que conozca la ubicación / zona horaria en la que se encuentra el cliente, puede usar la base de datos de zona horaria de información pública para traducir una hora de UTC a otra zona horaria (o triangular, si lo desea, entre dos zonas horarias). Lo mejor de la base de datos tz (a veces denominada base de datos Olson ) es que representa los cambios en las zonas horarias a lo largo de la historia; obtener una compensación es una función de la fecha en la que desea obtener la compensación (solo mire la Ley de Política Energética de 2005 que cambió las fechas cuando el horario de verano entra en vigencia en los EE. UU. . .).
Con la base de datos en la mano, puede usar la API .NET de ZoneInfo (base de datos tz / base de datos Olson) . Tenga en cuenta que no hay una distribución binaria, deberá descargar la última versión y compilarla usted mismo.
Al momento de escribir esto, actualmente analiza todos los archivos en la última distribución de datos (en realidad lo ejecuté contra el archivo ftp://elsie.nci.nih.gov/pub/tzdata2011k.tar.gz el 25 de septiembre, 2011; en marzo de 2017, lo obtendría a través de https://iana.org/time-zones o de ftp://fpt.iana.org/tz/releases/tzdata2017a.tar.gz ).
Entonces, en sf4answers, después de obtener la dirección, se geocodifica en una combinación de latitud / longitud y luego se envía a un servicio web de terceros para obtener una zona horaria que corresponde a una entrada en la base de datos tz. A partir de ahí, los tiempos de inicio y finalización se convierten en
DateTimeOffset
instancias con el desplazamiento UTC adecuado y luego se almacenan en la base de datos.En cuanto a tratarlo en SO y sitios web, depende de la audiencia y de lo que intente mostrar. Si observa, la mayoría de los sitios web sociales (y SO, y la sección de eventos en sf4answers) muestran eventos en relación tiempo o, si se usa un valor absoluto, generalmente es UTC.
Sin embargo, si su audiencia espera horas locales, entonces usar
DateTimeOffset
junto con un método de extensión que tome la zona horaria para convertir estaría bien; el tipo de datos SQLdatetimeoffset
se traduciría a .NET,DateTimeOffset
que luego puede obtener el tiempo universal para usar elGetUniversalTime
método . A partir de ahí, simplemente use los métodos en laZoneInfo
clase para convertir de UTC a hora local (tendrá que hacer un poco de trabajo para convertirlo en unDateTimeOffset
, pero es lo suficientemente simple).¿Dónde hacer la transformación? Es un costo que tendrá que pagar en alguna parte , y no hay una "mejor" forma. Sin embargo, optaría por la vista, con el desplazamiento de la zona horaria como parte del modelo de vista presentado a la vista. De esa manera, si los requisitos para la vista cambian, no tiene que cambiar su modelo de vista para acomodar el cambio. Su
JsonResult
simplemente contener un modelo con el y el desplazamiento.IEnumerable<T>
¿En el lado de entrada, usando un modelo de carpeta? Yo diría absolutamente de ninguna manera. No puede garantizar que todas las fechas (ahora o en el futuro) tengan que ser transformadas de esta manera, debe ser una función explícita de su controlador realizar esta acción. Nuevamente, si los requisitos cambian, no tiene que ajustar una o varias
ModelBinder
instancias para ajustar su lógica de negocios; y es lógica de negocios, lo que significa que debería estar en el controlador.fuente
datetimeoffset
SQL Server yDateTimeOffset
.NET aquí, realmente simplifican enormemente las cosas) que creo que .NET no maneja adecuadamente en absoluto por los motivos descritos anteriormente. Si tiene una fecha en Nueva York que se ingresó en 2003, y luego desea que se traduzca a una fecha en LA en 2011, .NET falla en este caso.DateTimeOffset
mitigará esto, IMO).Esta es solo mi opinión, creo que la aplicación MVC debería separar bien el problema de presentación de datos de la gestión del modelo de datos. Una base de datos puede almacenar datos en la hora del servidor local, pero es un deber de la capa de presentación representar la fecha y hora utilizando la zona horaria del usuario local. Este me parece el mismo problema que I18N y el formato de número para diferentes países. En su caso, su aplicación debería detectar la
Culture
zona horaria y del usuario y cambiar la vista que muestra diferentes presentaciones de texto, número y fecha de hora, pero los datos almacenados pueden tener el mismo formato.fuente
DateTimeExtensions
.Para la salida, cree una plantilla de visualización / editor como esta
Puede vincularlos según los atributos de su modelo si solo desea que ciertos modelos usen esas plantillas.
Consulte aquí y aquí para obtener más detalles sobre la creación de plantillas de editor personalizadas.
Alternativamente, dado que desea que funcione tanto para la entrada como para la salida, sugeriría extender un control o incluso crear el suyo propio. De esa manera puede interceptar tanto la entrada como las salidas y convertir el texto / valor según sea necesario.
Con suerte, este enlace lo empujará en la dirección correcta si desea seguir ese camino.
De cualquier manera, si quieres una solución elegante, será un poco de trabajo. En el lado positivo, una vez que lo haya hecho, puede guardarlo en su biblioteca de códigos para usarlo en el futuro.
fuente
DisplayFor
yEditorFor
funcionará todo el tiempo. +1Probablemente sea un mazo para romper una tuerca, pero podría inyectar una capa entre la UI y las capas de negocios que convierte de forma transparente las fechas y horas a la hora local en los gráficos de objetos devueltos, y a UTC en los parámetros de fecha y hora de entrada.
Me imagino que esto podría lograrse usando PostSharp o alguna inversión del contenedor de control.
Personalmente, simplemente convertiría explícitamente tus fechas en la interfaz de usuario ...
fuente