I'm confused with the naming of LocalDateTime in NodaTime.
Reading the documentation, it appears that it says:
A LocalDateTime value does not represent an instant on the global time line, because it has no associated time zone
Therefore, I would interpret this as:
"If I create a LocalDateTime in daylight savings, it doesn't matter, because this LocalDateTime has no knowledge of TimeZone and therefore no known offset from UTC."
Therefore, if I want to convert that LocalDateTime to UTC, I must do this:
// We can't convert directly to UTC because it doesn't know its own offset from UTC.
var dt = LocalDateTime.FromDateTime(DateTime.SpecifyKind(myDt, DateTimeKind.Unspecified));
// We specify the timezone as Melbourne Time,
// because we know this time refers to Melbourne time.
// This will attach a known offset and create a ZonedDateTime.
// Remember that if the system time variable (dt) is in daylight savings, it doesn't
// matter, because LocalDateTime does not contain such information.
// Therefore 8:00 PM in Daylight Savings and 8:00 PM outside of daylight savings
// are both 8:00 PM when we created the ZonedDateTime as below.
var melbCreatedTime = createdTime.InZoneLeniently(DateTimeZoneProviders.Tzdb["Australia/Melbourne"]);
// Now we can get to UTC, because we have a ZonedDateTime and therefore
// have a known offset from UTC:
sharedB.CreatedUTC = melbCreatedTime.ToDateTimeUtc();
Is this correct? DateTime logic is by far my weakest area, and I just want to make sure I understand what's happening here.
Without the comments:
var dateCreatedLocal = LocalDateTime.FromDateTime(DateTime.SpecifyKind(result.DateCreated.Value, DateTimeKind.Unspecified));
var dateCreatedUtc = dateCreatedLocal.InZoneLeniently(DateTimeZoneProviders.Tzdb["Australia/Melbourne"]).ToDateTimeUtc();
sharedB.CreatedUTC = dateCreatedUtc;
Thanks.
There's no concept of a "LocalDateTime in daylight savings". It's just a date and time, with no reference to any time zone that might or might not be observing DST.
However, if your aim is just to go from a DateTime with a Kind of Unspecified to a DateTime with a Kind of Utc using a particular time zone, I'd normally suggest using TimeZoneInfo - unless you specifically need IANA time zone ID support.
In general, I'd suggest using the Noda Time types everywhere - or if you're going to use the BCL types everywhere, don't use Noda Time at all. There are some cases where it makes sense to use Noda Time just for an isolated piece of code, and if you really need to do that here, then I believe your code should be fine - but be aware of the implications of InZoneLeniently when it comes to ambiguous or skipped local times around daylight saving changes (or other offset changes).
Related
I am trying to set a time to EST, and then find what it UCT time is. (We have our reasons).. I have read that "Eastern Standard Time" should take into account the Daylight savings time. But when we check the date, and we know it falls within Daylight Savings time, it still tries to convert for 5 hours instead of 4. Is there any method I am missing? Or do we have to do some manipulation.
DateTimeOffset convertDateTime = new DateTimeOffset(
subbmission.EntryDate,
TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time").BaseUtcOffset);
I added the following bu the boss is not a fan of using this..so any ideas would be greatly appreciated.
if (zone.IsDaylightSavingTime(convertDateTime.DateTime))
{
currentDateTime = convertDateTime.DateTime.AddHours(-1);
}
else
{
currentDateTime = convertDateTime.DateTime;
}
Fundamentally, you're using the wrong approach - if you want to convert to UTC, use TimeZoneInfo.ConvertTimeToUtc(DateTime, TimeZoneInfo). You'll want to be careful around times that are either invalid (because they were skipped) or ambiguous (because the clock went back, and the same local time happened twice).
If you actually want a DateTimeOffset so that you can get both the local and UTC times, you could use call ConvertTimeToUtc then compute the difference between them. It's a shame that TimeZoneInfo doesn't appear to have a "construct a DateTimeOffset based on this DateTime in this time zone" but I can't see it...
As an alternative, you could use my Noda Time project which makes all of this a lot clearer, of course :)
To get the correct offset for a date in the timezone (with or without daylight saing time), i use something like
DateTimeOffset utcOffset = new DateTimeOffset(subbmission.EntryDate.ToUniversalTime(), TimeSpan.Zero);
var zone TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
var correctOffset = zone.GetUtcOffset(utcOffset);
I know there are bunch of solutions on converting timezone to timezone, but what I'd like to know whether we can get eastern time without making conversion from our local time.
Well yes and no, if you represent using GMT always then you don't need to convert until you need to show locality.
Time is always in GMT and services should run under the GMT Timezone because of this reason among others.
Receiving and Storing a time in anything other than GMT / UTC requires conversions.
Please note the Timezone of the server should also be set to GMT for things to work as I indicated.
When you set things up like this it thus becomes much easier to reference and compare times and dates in different calendar date formats also.
This is the reason why DateTime offset made its way into the framework and sql server. if not careful when the Timezone on a server changes so does all stored and loaded dates in local format.
A date comparison is a date comparison. DateTime just wraps a memory structure which is defined in ticks and makes methods to access commonly used parts of the memory e.g. day or year or Time or TimeOfDay etc.
Furthermore conversion is only possible if you know both the source and destination offsets and then the calculation is always as given is given by -1 * (sourceOffset - destOffset)
Where the part in parenthesis represents the time zone difference.
Where the DateTime you want to convert is in UTC and called theDate
DateTime eastern = TimeZoneInfo
.ConvertTimeFromUtc(
theDate,
TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time"))
If theDate has a Kind of Local this will throw, only accepting Kind values of Utc or Unspecified. Of course, moving from local to UTC is easy and we could even check and do this conversion when required, but since you say you want this conversion "without making conversion from our local time" I'm assuming you have your times in UTC and hence having the exception would be better as it would warn you that times that should be UTC are being treat as local and a bug could pop up elsewhere.
This will use Eastern Daylight Time when theDate is a time when EDT is in effect, as that is the normal rule for EST. To use EST even when it is the summer you can create your own TimeZoneInfo:
TimeZoneInfo
.ConvertTimeFromUtc(
theDate,
TimeZoneInfo.CreateCustomTimeZone(
"Eastern Standard Time No Daylight Savings",
new TimeSpan(-5, 0, 0),
"Eastern Standard Time No Daylight Savings",
"Eastern Standard Time No Daylight Savings"))
My application is hosted on Windows Azure, which has all servers set to UTC.
I need to know when any given DateTime is subject to daylight savings time. For simplicity, we can assume that my users are all in the UK (so using Greenwich Mean Time).
The code I am using to convert my DateTime objects is
public static DateTime UtcToUserTimeZone(DateTime dateTime)
{
dateTime = DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
var timeZone = TimeZoneInfo.FindSystemTimeZoneById("Greenwich Standard Time");
var userDateTime = TimeZoneInfo.ConvertTime(dateTime, timeZone);
return DateTime.SpecifyKind(userDateTime, DateTimeKind.Local);
}
However, the following test fails at the last line; the converted DateTime does not know that it should be in daylight savings time.
[Test]
public void UtcToUserTimeZoneShouldWork()
{
var utcDateTime = new DateTime(2014, 6, 1, 12, 0, 0, DateTimeKind.Utc);
Assert.IsFalse(utcDateTime.IsDaylightSavingTime());
var dateTime = Utils.UtcToUserTimeZone(utcDateTime);
Assert.IsTrue(dateTime.IsDaylightSavingTime());
}
Note that this only fails when my Windows time zone is set to (UTC) Co-ordinated Universal Time. When it is set to (UTC) Dublin, Edinburgh, Lisbon, London (or any other northern-hemisphere time zone that observes daylight savings), the test passes. If you change this setting in Windows a restart of Visual Studio is required in order for the change to fully take effect.
What am I missing?
Your last line specifies that the value is in the local time zone of the system it's running in - but it's not really... it's converted using a different time zone.
Basically DateTime is somewhat broken, in my view - which makes it hard to use correctly. There's no way of saying "This DateTime value is in time zone x" - you can perform a conversion to find a specific local time, but that doesn't remember the time zone.
For example, from the docs of DateTime.IsDaylightSavingTime (emphasis mine):
This method determines whether the current DateTime value falls within the daylight saving time range of the local time zone, which is returned by the TimeZoneInfo.Local property.
Obviously I'm biased, but I'd recommend using my Noda Time project for this instead - quite possibly using a ZonedDateTime. You can call GetZoneInterval to obtain the ZoneInterval for that ZonedDateTime, and then use ZoneInterval.Savings to check whether the current offset contains a daylight savings component:
bool daylight = zonedDateTime.GetZoneInterval().Savings != Offset.Zero;
This is slightly long-winded... I've added a feature request to consider adding an IsDaylightSavingTime() method to ZonedDateTime as well...
The main thing you are missing is that "Greenwich Standard Time" is not the TimeZoneInfo id for London. That one actually belongs to "(UTC) Monrovia, Reykjavik".
You want "GMT Standard Time", which maps to "(UTC) Dublin, Edinburgh, Lisbon, London"
Yes, Windows time zones are weird. (At least you don't live in France, which gets strangely labeled as "Romance Standard Time"!)
Also, you should not be doing this part:
return DateTime.SpecifyKind(userDateTime, DateTimeKind.Local);
That will make various other code think it came from the local machine's time zone. Leave it with the "Unspecified" kind. (Or even better, use a DateTimeOffset instead of a DateTime)
Then you also need to use the .IsDaylightSavingsTime method on the TimeZoneInfo object, rather than the one on the .DateTime object. (There are two different methods, with the same name, on different objects, with differing behavior. Ouch!)
But I wholeheartedly agree that this is way too complicated and error prone. Just use Noda Time. You'll be glad you did!
I am trying to program a world clock using Nodatime, and I have searched the web for samples on how to use the library and I have read the documentation, and it says that the class Instant is simply a number of "ticks" since some arbitrary epoch the Unix epoch, which corresponds to midnight on January 1st 1970 UTC. Well, I empirically guessed that if I used as an Instant a GMT value, then I could calculate with it the time values for each time zone (creating the world clock), and it worked. The problem that I have, is that I don't know a simple way to calculate the GMT time (or GMT instant) from the local time, my time zone is "America/Mexico_City", so my question is, is there a shortcut already defined in Nodatime to get the GMT time from a local time, or in the other hand, is there a simple way to implement the "Instant GetInstantGMT()" function (the function has to take in count the day light saving time issues)?
If you are just looking for the "current" instant, such represents now, then use:
Instant now = SystemClock.Instance.Now;
Calling it a "GMT instant" is redundant, since the Instant type is representing a universal moment in time without regard to time zone. It is (mostly) equivalent to UTC - which is essentially the same as GMT. In other words, you couldn't create an Instant that wasn't GMT.
Another way to think about an Instant is as if it were a DateTime whose .Kind property was permanently fixed to DateTimeKind.Utc and could not represent anything else.
Also, depending on exactly how your application is architected, you may find it useful to use the IClock interface instead:
IClock clock = SystemClock.Instance;
Instant now = clock.Now;
This would allow you to replace the system clock with a fake clock during unit testing.
Regarding how to go from a specific local time to an Instant, you would do something like this:
LocalDateTime ldt = new LocalDateTime(2013, 1, 1, 0, 0, 0);
DateTimeZone tz = DateTimeZoneProviders.Tzdb["America/Mexico_City"];
ZonedDateTime zdt = ldt.InZoneLeniently(tz);
Instant instant = zdt.ToInstant();
Note that I used InZoneLeniently in the conversion. This makes certain assumptions about how to translate from a local time that might be invalid or ambiguous due to daylight saving time. This might be acceptable, or you might instead prefer to use InZoneStrictly which will throw exceptions, or InZone which allows you to pass a resolver function so you can provide your own logic.
I am storing date/times in the database as UTC and computing them inside my application back to local time based on the specific timezone. Say for example I have the following date/time:
01/04/2010 00:00
Say it is for a country e.g. UK which observes DST (Daylight Savings Time) and at this particular time we are in daylight savings. When I convert this date to UTC and store it in the database it is actually stored as:
31/03/2010 23:00
As the date would be adjusted -1 hours for DST. This works fine when your observing DST at time of submission. However, what happens when the clock is adjusted back? When I pull that date from the database and convert it to local time that particular datetime would be seen as 31/03/2010 23:00 when in reality it was processed as 01/04/2010 00:00.
Correct me if I am wrong but isn't this a bit of a flaw when storing times as UTC?
Example of Timezone conversion
Basically what I am doing is storing the date/times of when information is being submitted to my system in order to allow users to do a range report. Here is how I am storing the date/times:
public DateTime LocalDateTime(string timeZoneId)
{
var tzi = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId);
return TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tzi).ToUniversalTime().ToLocalTime();
}
Storing as UTC:
var localDateTime = LocalDateTime("AUS Eastern Standard Time");
WriteToDB(localDateTime.ToUniversalTime());
You don't adjust the date for DST changes based on whether you're currently observing them - you adjust it based on whether DST is observed at the instant you're describing. So in the case of January, you wouldn't apply the adjustment.
There is a problem, however - some local times are ambiguous. For example, 1:30am on October 31st 2010 in the UK can either represent UTC 01:30 or UTC 02:30, because the clocks go back from 2am to 1am. You can get from any instant represented in UTC to the local time which would be displayed at that instant, but the operation isn't reversible.
Likewise it's very possible for you to have a local time which never occurs - 1:30am on March 28th 2010 didn't happen in the UK, for example - because at 1am the clocks jumped forward to 2am.
The long and the short of it is that if you're trying to represent an instant in time, you can use UTC and get an unambiguous representation. If you're trying to represent a time in a particular time zone, you'll need the time zone itself (e.g. Europe/London) and either the UTC representation of the instant or the local date and time with the offset at that particular time (to disambiguate around DST transitions). Another alternative is to only store UTC and the offset from it; that allows you to tell the local time at that instant, but it means you can't predict what the local time would be a minute later, as you don't really know the time zone. (This is what DateTimeOffset stores, basically.)
We're hoping to make this reasonably easy to handle in Noda Time, but you'll still need to be aware of it as a possibility.
EDIT:
The code you've shown is incorrect. Here's why. I've changed the structure of the code to make it easier to see, but you'll see it's performing the same calls.
var tzi = TimeZoneInfo.FindSystemTimeZoneById("AUS Eastern Standard Time");
var aussieTime = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tzi);
var serverLocalTime = aussieTime.ToLocalTime();
var utcTime = serverLocalTime.ToUniversalTime();
So, let's think about right now - which is 13:38 in my local time (UTC+1, in London), 12:38 UTC, 22:39 in Sydney.
Your code will give:
aussieTime = 22:39 (correct)
serverLocalTime = 23:39 (*not* correct)
utcTime = 22:39 (*not* correct)
You should not call ToLocalTime on the result of TimeZoneInfo.ConvertTimeFromUtc - it will assume that it's being called on a UTC DateTime (unless it's actually got a kind of DateTimeKind.Local, which it won't in this case).
So if you're accurately saving 22:39 in this case, you aren't accurately saving the current time in UTC.
It's good that you are attempting to store the dates and times as UTC. It is generally best and easiest to think of UTC as the actual date and time and local times are just pseudonyms for that. And UTC is absolutely critical if you need to do any math on the date/time values to get timespans. I generally manipulate dates internally as UTC, and only convert to local time when displaying the value to the user (if it's necessary).
The bug that you are experiencing is that you are incorrectly assigning the local time zone to the date/time values. In January in the UK it is incorrect to interpret a local time as being in a Summertime time zone. You should use the time zone that was in effect at the time and location that the time value represents.
Translating the time back for display depends entirely on the requirements of the system. You could either display the times as the user's local time or as the source time for the data. But either way, Daylight Saving/Summertime adjustments should be applied appropriately for the target time zone and time.
You could work around this by also storing the particular offset used when converting to UTC. In your example, you'd store the date as something like
31/12/2009 23:00 +0100
When displaying this to the user, you can use that same offset to convert, or their current local offset, as you choose.
This approach also comes with its own problems. Time is a messy thing.
The TimeZoneInfo.ConvertTimeFromUtc() method will solve your problem:
using System;
class Program {
static void Main(string[] args) {
DateTime dt1 = new DateTime(2009, 12, 31, 23, 0, 0, DateTimeKind.Utc);
TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
Console.WriteLine(TimeZoneInfo.ConvertTimeFromUtc(dt1, tz));
DateTime dt2 = new DateTime(2010, 4, 1, 23, 0, 0, DateTimeKind.Utc);
Console.WriteLine(TimeZoneInfo.ConvertTimeFromUtc(dt2, tz));
Console.ReadLine();
}
}
Output:
12/31/2009 11:00:00 PM
4/2/2010 12:00:00 AM
You'll need .NET 3.5 or better and run on an operating system that keeps historical daylight saving time changes (Vista, Win7 or Win2008).
Correct me if I am wrong but isn't
this a bit of a flaw when storing
times as UTC?
Yes it is. Also, days of the adjustment will have either 23 or 25 hours so the idiom of the prior day at the same time being local time - 24 hours is wrong 2 days a year.
The fix is picking one standard and sticking with it. Storing dates as UTC and displaying as local is pretty standard. Just don't use a shortcut of doing calculations local (+- somthing) = new time and you are OK.
This is a huge flaw but it isn't a flaw of storing times in UTC (because that is the only reasonable thing to do -- storing local times is always a disaster). This is a flaw is the concept of daylight savings time.
The real problem is that the time zone information changes. The DST rules are dynamic and historic. They time when DST starting in USA in 2010 is not the same when it started in 2000. Until recently Windows did not even contain this historic data, so it was essentially impossible to do things correctly. You had to use the tz database to get it right. Now I just googled it and it appears that .NET 3.5 and Vista (I assume Windows 2008 too) has done some improvement and the System.TimeZoneInfo actually handles historic data. Take a look at this.
But basically DST must go.