Criando um DateTime em um fuso horário específico em c #


162

Estou tentando criar um teste de unidade para testar o caso de quando o fuso horário muda em uma máquina porque foi definido incorretamente e depois corrigido.

No teste, preciso criar objetos DateTime em um fuso horário local nenhum para garantir que as pessoas que executam o teste possam fazê-lo com êxito, independentemente de onde estejam localizadas.

Pelo que posso ver no construtor DateTime, posso definir o Fuso Horário como o fuso horário local, o fuso horário UTC ou não especificado.

Como crio um DateTime com um fuso horário específico como o PST?


Respostas:


216

A resposta de Jon fala sobre o TimeZone , mas eu sugiro usar o TimeZoneInfo .

Pessoalmente, gosto de manter as coisas no UTC sempre que possível (pelo menos no passado; armazenar o UTC para o futuro tem possíveis problemas ), então sugiro uma estrutura como esta:

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

Você pode alterar os nomes "TimeZone" para "TimeZoneInfo" para tornar as coisas mais claras - prefiro os nomes mais breves.


5
Não conheço nenhuma construção equivalente do SQL Server, receio. Eu sugeriria ter o nome do fuso horário como uma coluna e o valor UTC em outra coluna. Busque-os separadamente e, em seguida, você poderá criar instâncias com bastante facilidade.
31909 Jon Skeet

2
Não tenho certeza sobre o uso esperado do construtor que usa DateTime e TimeZoneInfo, mas, como você está chamando o método dateTime.ToUniversalTime (), suspeito que você esteja imaginando que "talvez" esteja na hora local. Nesse caso, acho que você realmente deveria usar o TimeZoneInfo transmitido para convertê-lo em UTC, pois eles estão dizendo que deveria estar nesse fuso horário.
IDisposable

2
@ ChrisMoschini: Nesse ponto, você está apenas inventando seu próprio esquema de identificação - um esquema que ninguém mais usa no mundo. Vou continuar com a zona padrão da indústria, obrigado. (É difícil ver como "Europa / Londres" não tem sentido, por exemplo.)
Jon Skeet

2
@ ChrisMoschini: Exemplo diferente então: CST. Isso é UTC-5 ou UTC-6? E o IST - Israel, Índia ou Irlanda estão no seu banco de dados? (E mesmo que você saiba o deslocamento no momento, diferentes países que observam a mesma abreviação podem mudar em momentos diferentes. Portanto, ainda existe ambiguidade sobre qual fuso horário real significa. Fuso horário! = Deslocamento.) Voltando ao seu caso: você reivindica que usar abreviações melhor resolveu seu problema. Como o uso dos IDs de fuso horário padrão do setor teria sido pior?
Jon Skeet

6
@ ChrisMoschini: Bem, continuarei recomendando o uso de IDs de informações de zona inequívocas, padrão da indústria, em vez das abreviações ambíguas. Não se trata de cuja biblioteca é preferida - a autoria da biblioteca realmente não é um problema. Se alguém desejar usar outra biblioteca com uma boa escolha de identificador, tudo bem. A escolha do identificador para um fuso horário é importante, e acho que é muito importante que os leitores estejam cientes de que as abreviações são ambíguas, como mostrei no exemplo do IST.
21813 Jon Skeet

54

A estrutura DateTimeOffset foi criada para exatamente esse tipo de uso.

Vejo: http://msdn.microsoft.com/en-us/library/system.datetimeoffset.aspx

Aqui está um exemplo de criação de um objeto DateTimeOffset com um fuso horário específico:

DateTimeOffset do1 = new DateTimeOffset(2008, 8, 22, 1, 0, 0, new TimeSpan(-5, 0, 0));


1
Obrigado, esta é uma boa maneira de conseguir isso. Depois de obter o objeto DateTimeOffset no fuso horário correto, você pode usar a propriedade .UtcDateTime para obter um horário UTC para o que você criou. Se você armazenar as datas em UTC, em seguida, convertendo-os para a hora local de cada usuário não é grande coisa :)
Redth

2
Eu não acho que isso lida com o horário de verão corretamente, pois alguns fusos horários o respeitam, enquanto outros não. Também "no dia" o horário de verão começa / termina, partes desse dia estariam desativadas.
crokusek

14
Lição. DST é uma regra de um fuso horário específico. DateTimeOffset não está não, não está associado a nenhum fuso horário. Não confunda um valor de deslocamento UTC, como -5, com um fuso horário. Não é um fuso horário, é um deslocamento. O mesmo deslocamento é geralmente compartilhado por muitos fusos horários, portanto, é uma maneira ambígua de se referir a um fuso horário. Como DateTimeOffset está associado a um deslocamento, não a um fuso horário, não é possível aplicar regras de horário de verão. Assim, as 3h serão 3h em todos os dias do ano, sem exceção em uma estrutura DateTimeOffset (por exemplo, nas propriedades Hours e TimeOfDay).
Triynko 18/02

Onde você pode ficar confuso é se você olhar para a propriedade LocalDateTime do DateTimeOffset. Essa propriedade NÃO é um DateTimeOffset, é uma instância de DateTime cujo tipo é DateTimeKind.Local. Essa instância está associada a um fuso horário ... qualquer que seja o fuso horário do sistema local. Essa propriedade refletirá o horário de verão.
Triynko 18/02

4
Portanto, o verdadeiro problema do DateTimeOffset é que ele não inclui informações suficientes. Inclui um deslocamento, não um fuso horário. O deslocamento é ambíguo com vários fusos horários.
Triynko 18/02

41

As outras respostas aqui são úteis, mas não abordam como acessar o Pacífico especificamente - aqui está:

public static DateTime GmtToPacific(DateTime dateTime)
{
    return TimeZoneInfo.ConvertTimeFromUtc(dateTime,
        TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"));
}

Curiosamente, embora "Horário padrão do Pacífico" normalmente signifique algo diferente de "Horário de verão do Pacífico", neste caso, refere-se ao horário do Pacífico em geral. De fato, se você FindSystemTimeZoneByIdbuscá-lo, uma das propriedades disponíveis é um booleano informando se esse fuso horário está atualmente no horário de verão ou não.

Você pode ver exemplos mais generalizados disso em uma biblioteca que acabei lançando para lidar com o DateTimes de que preciso em diferentes fusos horários, com base no local de solicitação do usuário, etc:

https://github.com/b9chris/TimeZoneInfoLib.Net

Isso não funcionará fora do Windows (por exemplo, Mono no Linux), pois a lista de horários vem do Registro do Windows: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\

Abaixo, você encontrará chaves (ícones de pastas no Editor do Registro); os nomes dessas chaves são para os quais você passa FindSystemTimeZoneById. No Linux, você precisa usar um conjunto separado de definições de fuso horário, padrão do Linux, que não explorei adequadamente.


1
Além disso, há ConvertTimeBySystemTimeZoneId () ex: TimeZoneInfo.ConvertTimeBySystemTimeZoneId (DateTime.UtcNow, "Central Standard Time")
Brent

No Windows, a Lista de identificação do fuso horário também pode ver esta resposta: stackoverflow.com/a/24460750/4573839
yu yang Jian

7

Eu alterei Jon Skeet responder a um pouco para a web com método de extensão. Também funciona no azul como um encanto.

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

2

Você precisará criar um objeto personalizado para isso. Seu objeto personalizado conterá dois valores:

Não tenho certeza se já existe um tipo de dados fornecido pelo CLR, mas pelo menos o componente TimeZone já está disponível.


2

Gosto da resposta de Jon Skeet, mas gostaria de acrescentar uma coisa. Não tenho certeza se Jon esperava que o ctor sempre fosse passado no fuso horário local. Mas eu quero usá-lo para casos em que é algo diferente de local.

Estou lendo valores de um banco de dados e sei em que fuso horário esse banco de dados está. Portanto, no ctor, passo o fuso horário do banco de dados. Mas então eu gostaria do valor na hora local. O LocalTime de Jon não retorna a data original convertida em uma data de fuso horário local. Ele retorna a data convertida no fuso horário original (o que você tiver passado para o ctor).

Acho que esses nomes de propriedades esclarecem ...

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

0

O uso da classe TimeZones facilita a criação de uma data específica para o fuso horário.

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

1
Desculpe, mas não está disponível no Asp .NET Core 2.2 aqui, o VS2017 está me sugerindo a instalação de um pacote do Outlook Nuget.
Machado

exemplo => TimeZoneInfo.ConvertTime (DateTime.Now, TimeZoneInfo.FindSystemTimeZoneById ("Horário padrão do Pacífico"))
AZ_
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.