I have written an application to show the remaining time until the next big industry trade-show.
(it's about two years in the future at the time of writing)
I started off using the standard DateTime class but quickly ran into issues dealing with the varying number of days in each month, 2016 is a Leap Year and contains a Leap Day, Daylight Savings Time, etc.
Thankfully I discovered NodaTime. (Thanks #JonSkeet)
Not so thankfully, the way I am used to working with DateTime doesn't apply and I'm having a really hard time figuring out how to get the time remaining. (There aren't many examples floating around)
For example, the following code doesn't work because you can't subtract an instant from a LocalDateTime:
void example()
{
DateTime DT = Convert.ToDateTime("09/12/2016 10:00AM");
LocalDateTime NodaLocalDateTime = new LocalDateTime(
DT.Year, DT.Month, DT.Day, DT.Hour, DT.Minute, 0);
Period P = NodaLocalDateTime - SystemClock.Instance.Now;
}
So the question becomes:
How do you get the remaining time from now until some date?
To determine a "calendrical" amount of time between two events, you want Period as you've already discovered. However, that only deals with local dates and times.
To determine a "calendar-neutral" amount of time between two events, you can use Instant and Duration - but then you can't display the number of months left.
Both of these approaches have drawbacks, but basically they're fundamental to the way that time works. If you use the local time approach, then you'll find that the amount of time will jump back or forward an hour as you go over a DST transition. If you use the instant approach, you're restricted to days/months/hours/minutes etc - not months.
One option for between the two would be to use LocalDateTime and Period, but anchor both the event and the current time in UTC. That way there'll never be a discontinuity, as UTC is an unchanging base line, effectively. This also means that you'll always display the same amount of "time left" regardless of where in the world you look at the counter (or host the code, depending on exactly what you were planning to do).
If you want more details about why you can't get a Period between two ZonedDateTime values, I could think of examples which are fundamentally problematic. The bottom line is that calendrical arithmetic and time zones don't play nicely together though...
Just to give some actual code, I would have something like:
public sealed class EventCountdown
{
private readonly LocalDateTime eventTimeUtc;
private readonly IClock clock;
// It's probably most convenient to express the event time with the time zone
// in which it occurs. You could easily change this though.
public EventCountdown(ZonedDateTime zonedEventTime, IClock clock)
{
this.eventTimeUtc = zonedEventTime.WithZone(DateTimeZone.Utc).LocalDateTime;
this.clock = clock;
}
public Period GetPeriodRemaining()
{
return Period.Between(clock.Now.InUtc().LocalDateTime, eventTimeUtc);
}
}
Note that in Noda Time 2.0 the IClock.Now property is being changed to a GetCurrentInstant method... but in that case you'd probably use a ZonedClock in UTC and call GetCurrentLocalDateTime on it.
The solution I ended up using is Period.Between(), which seems do the trick.
using NodaTime;
DateTime EventDT;
LocalDateTime LocalizedEventDT;
Period TimeLeft;
public EventCountdown()
{
// Start with a date and time
EventDT = Convert.ToDateTime("09/12/2016 10:00AM");
// Localize it
LocalizedEventDT = new LocalDateTime(
EventDT.Year, EventDT.Month,
EventDT.Day, EventDT.Hour,
EventDT.Minute, 0);
}
// find out how much time is between now and the future date
public Period GetPeriodRemaining()
{
DateTime dt_Now = DateTime.Now;
return Period.Between(new LocalDateTime(
dt_Now.Year, dt_Now.Month, dt_Now.Day, dt_Now.Hour,
dt_Now.Minute, dt_Now.Second), LocalizedEventDT);
}
If anyone has a solution that does this while taking time zones (local vs where the event is taking place) into account that would be awesome. (I tried doing this using a ZonedDateTime in a similar fashion but ran into a brick wall).
Also, it's not clear to me if this method is taking the various days of the month, daylight savings time, leap year, etc. into account. Anyone know?
If it is not, obviously I'd welcome any solutions that do so gracefully.
Related
Can anyone explain why the comparisonTime variable is calculated using the logic like below, especially using the lines below:
local.GetAdjustmentRules()[local.GetAdjustmentRules().Length - 1].DaylightDelta;
and
TimeSpan comparisonTime = time + (offset - tz.BaseUtcOffset).Negate() + (delta - storeDelta).Negate();
The TimeSpan structure
using System;
public struct StoreInfo
{
public String store;
public TimeZoneInfo tz;
public TimeSpan open;
public TimeSpan close;
public bool IsOpenNow()
{
return IsOpenAt(DateTime.Now.TimeOfDay);
}
public bool IsOpenAt(TimeSpan time)
{
TimeZoneInfo local = TimeZoneInfo.Local;
TimeSpan offset = TimeZoneInfo.Local.BaseUtcOffset;
// Is the store in the same time zone?
if (tz.Equals(local)) {
return time >= open & time <= close;
}
else {
TimeSpan delta = TimeSpan.Zero;
TimeSpan storeDelta = TimeSpan.Zero;
// Is it daylight saving time in either time zone?
if (local.IsDaylightSavingTime(DateTime.Now.Date + time))
delta = local.GetAdjustmentRules()[local.GetAdjustmentRules().Length - 1].DaylightDelta;
if (tz.IsDaylightSavingTime(TimeZoneInfo.ConvertTime(DateTime.Now.Date + time, local, tz)))
storeDelta = tz.GetAdjustmentRules()[tz.GetAdjustmentRules().Length - 1].DaylightDelta;
TimeSpan comparisonTime = time + (offset - tz.BaseUtcOffset).Negate() + (delta - storeDelta).Negate();
return comparisonTime >= open & comparisonTime <= close;
}
}
}
TimeZoneInfo.GetAdjustmentRules returns an array of AdjustmentRule objects, each of which identifies time adjustments made for daylight savings time. This includes the start and end date as well as the amount of time the clocks are adjusted and when these rules go into effect (Rules change, for example the US used to have an April-October cycle but now its March-November). The rules are generally ordered by oldest to newest.
The code in question is incorrectly (see below) finding the last rule in the list (local.GetAdjustmentRules()[local.GetAdjustmentRules().Length - 1]) and figuring out how much the time is changed by that rule (DaylightDelta--in the US this is typically one hour, but parts of the world may adjust by other values such as 30 minutes)
The second bit of code you inquire about attempts to normalize the passed in time if either/both zone is currently respecting daylight savings. It does this by adding the differences in the two DaylightDelta (the amout the clocks get moved ahead/behind) and the two BaseUtcOffset (the zone's non-DST difference from UTC). It then adds that to the original time and uses that for comparisons instead.
The code is incorrect for several reasons (and this is perhaps not exhaustive):
It assumes the adjustment rules are ordered. The MSDN documentation says that they are "generally ordered" but makes no guarantees
It assumes that the last adjustment rule is the current one. As noted above, they aren't ordered. But they might also contain future rules that are not yet effective
It does not account for times that are near to midnight and might roll over to the next day. This holds true for both branches of the code. While it's possibly unlikely for a store to be open passed midnight it is a definite possibility (I don't know your business rules). If the store is open from 8am to 2am, the tests will fail for 1am when it should succeed
Similarly, for a store where the hours pass midnight, the daylight savings calculation could prove even more inaccurate if the times fall at the DST boundaries
TimeZoneInfo.Local can be manipulated by the system settings. In fact depending on whether "Automatically adjust for daylight savings" is checked in control panel the local time zone info could always return false for IsDaylightSavings and a zero length array for local.GetAdjustmentRules. This means that the same code run on two computers could return different results even if they are sitting next to each other!
It assumes that the system is up to date. Depending on where in the world you are, daylight savings rules may change often. In cases like these, it is common to have an Operating System that doesn't have all of the latest information.
Dealing with time zones and daylight savings is tough. The rules are changing all the time and the tools in the framework are quite clunky and difficult to work with (assuming you can understand them). What's worse is that they aren't always up to date!
I'm not going to attempt to provide you with fixed code. I tend to leave anything related to time and DST calculations to the experts. I'd highly suggest looking into the NodaTime library for calculations such as these. It's entirely devoted to the subject matter and the timezone rules are regularly updated. Their Why do we exist page calls out some of the things I've mentioned above as does this blog post written by one of the library's authors, Jon Skeet.
We are now in the summer time (UTC+01:00) but I need always winter time whereever I am.
For example now time is 08:05 and winter time is 07:05
I can find whether it is saving time or not by using this function
DateTime.Now.IsDaylightSavingTime()
and here is the delta (1 hour change)
TimeZoneInfo.Local.GetAdjustmentRules()[0].DaylightDelta;
so if I do something like that, is it correct?
var winterTime= DateTime.Now;
if (DateTime.Now.IsDaylightSavingTime())
{
winterTime = DateTime.Now.AddHours(-1 * delta.Hours);
}
or is there another way to do? (without using any 3rd solution)
Edit: The reason why I am asking this is that we are flashing a firmware to a nfc device and some dates in the devices should be in winter time. Thats why our tool should write winter time as paramter to the device.
In case DaylightDelta is not whole hours better use the TimeSpan directly. Also you need to find the rule in GetAdjustmentRules() that matches the current date. GetAdjustmentRules() returns both future and historical rules.
var now = DateTime.Today;
var rule = TimeZoneInfo.Local.GetAdjustmentRules().Where(x => now >= x.DateStart && now <= x.DateEnd).First();
winterTime = DateTime.Now - rule.DaylightDelta;
We are now in the summer time (UTC+01:00) but I need always winter time whereever I am.
...
... The reason why I am asking this is that we are flashing a firmware to a nfc device and some dates in the devices should be in winter time. Thats why our tool should write winter time as paramter to the device.
It sounds like you simply want to obtain the UTC time to set on the device. If so, just use DateTime.UtcNow. There is no need to concern yourself with time zones.
However, if you actually wanted to take the standard time (or winter time) for whatever local time zone you're in (that would seem to be a bit silly because that's not how local times work), but if you really are sure you want that, you can do it without querying adjustment rules. Instead, try this:
DateTime dt = DateTimeOffset.UtcNow.ToOffset(TimeZoneInfo.Local.BaseUtcOffset).DateTime;
This takes the current UTC time, applies the base offset of the local time zone, and then gives you back a DateTime with .Kind == DateTimeKind.Unspecified. The base offset is the offset from UTC that applies during standard time without any adjustment rules applied for DST.
I've read a few posts about similar subjects but nothing seems to answer this question. My database has the following information about a time
Day of the week (a number between 0-6)
Time (a number of milliseconds since midnight in the users local time)
UTC offset ( number of hours different to UTC )
DST Observed (boolean stating if DST is observed in that time zone)
This data represents opening hours. So there is a time for each day. I want to display that time in the users local time making the assumption that each day is in the future
int dayOffset = availability.Day - (int)now.DayOfWeek
if (dayOffset < 0)
dayOffset += 7;
I'm really struggling to get my head around time zones and handling when one time zone might be observing DST while another maybe DOES observe DST but hasn't yet.
My main issue at the moment is I think I need to create a DateTimeOffset object for the non-local time but I'm not sure how to do that as I don't know if DST is in effect or not.
I hope I'm making myself clear. It really is a mind-bending experience working with dates and time!
As indicated by other answers, the usual solution to handling DateTime across time zones would be to store UTC times.
However, considering that you are not referencing an absolute time at a specific date, but instead are referring to a time at an infinite number of days in a specific time zone; storing the time as an UTC time doesn't make sense anymore, since the UTC time (even if we discard the date) would be different depending on the date, due to DST.
The best way to store the time is fairly close to what you have done already.
Your problem is that the time zone information you are storing at the moment is ambiguous, as it does not refer to a specific time zone, but instead refers to properties of the time zone.
To solve this problem, simply store the time zone identifier instead of the UTC offset and DST boolean.
It is now possible for us to construct the DateTime object and convert it to any time zone by using the TimeZoneInfo class:
int dayOffset = availability.Day - (int)DateTime.Today.DayOfWeek;
if (dayOffset < 0)
{
dayOffset += 7;
}
var openingHourStart = DateTime
.SpecifyKind(DateTime.Today, DateTimeKind.Unspecified)
.AddDays(dayOffset)
.AddMilliseconds(availability.Time);
var sourceTimeZone = TimeZoneInfo.FindSystemTimeZoneById(availability.TimeZoneId);
var userTimeZone = TimeZoneInfo.Local;
var convertedOpeningHourStart = TimeZoneInfo.ConvertTime(openingHourStart,
sourceTimeZone,
userTimeZone);
Give a try to Quartz.NET.
It implements evaluation of CronExpressions, and even triggers to fire events at the given time. It can evaluate the next time an event will occur. This may help you out calculating the opening times.
Also, take a look at the cronmaker website: there you can understand the full potential of CronExpressions.
The CronExpressionDescriptor is a DotNET library for transforming CronExpressions into human readable strings.
Another library which I haven't tried yet is [HangFire].(https://www.hangfire.io/)
In this forum post you can find some discussion on how HangFire implements evaluation of RecurringJobs in local timezone with DST, which I believe is a solution for what you are looking for.
A comment to another answer made the problem a little bit more clear.
So, first and foremost, do store only UTC in your database. Really.
Now, since you are not interested in the actual dates, since you are storing working schedules that repeat weekly, the date only becomes relevant once you want to present your times - and when you put them in your database.
So let's first see how you get your times into your database correctly. I'm assuming a user will enter times in their own locale.
Make sure you first create a (localised) DateTime consisting of the current date and the given time (from the user), and transform that to a UTC DateTime (you can keep the current date, it doesn't matter):
var utcDateTime = DateTime.Now.Date
.AddHours(userHours)
.AddMinutes(userMinutes)
.ToUniversalTime();
Now when you are presenting these times to the user, simply go the other way:
var userDateTime = DateTime.Now.Date
.AddHours(utcDateTime.Hour)
.AddMinutes(utcDateTime.Minute)
.ToLocalTime();
And then you can use the userDateTime.Hour and .Minute for display purposes.
You should be leveraging DateTime.ToLocalTime() and TimeZoneInfo.ConvertTimeToUtc() in C# - see https://msdn.microsoft.com/en-us/library/system.datetime.tolocaltime(v=vs.110).aspx.
If you want to store only times that you're open from Monday to Sunday, fine. Have a simple data table to describe only the time for each day (0 = Sunday through 7 = Saturday -- this is .Net's DayOfWeek enumeration). Your lookup table might look like:
0 null
1 08:00:00
2 08:00:00
3 08:00:00
4 08:30:00
5 08:30:00
6 10:30:00
(Use whatever data type works for you--SQL Server 2008+ has a TIME data type, for example. Null can be used for Closed on that day--i.e., no open time.)
When it comes time to display YOUR time to any other user, use must create your UTC time on-the-fly at the moment you are displaying information to the local user.
Conyc provided one approach. My approach uses simple date/time strings. To use my approach, just store time values per day in your database. Then you can look up the open time for any given day. To express that time for another user in any locale, use this code to convert your time to UTC (you can substitute the "08:00:00 AM" string value with a string variable that you populated after looking up the open time in your database):
var StoreOpenTimeInUtc = TimeZoneInfo.ConvertTimeToUtc(Convert.ToDateTime("08:00:00 AM"));
To look up the open time in your database for a particular day in the future, you will need to concatenate the date to your time value, like this:
var StoreOpenTimeInUtc = TimeZoneInfo.ConvertTimeToUtc(Convert.ToDateTime("04/28/2018 08:00:00 AM"));
Once you have an accurate StoreOpenTimeInUtc variable, you can use that as the UTC value on someone else's machine who is anywhere else on planet earth. To convert that UTC value to their local time, use the .NET ToLocalTime() method:
var OpenTimeForLocalUser = StoreOpenTimeInUtc.ToLocalTime();
Note that this approach requires you to store only the open times as shown above. You don't have to worry about dates, local offsets from UTC, or anything else. Just leverage ConvertTimeToUtc() and ToLocalTime() as shown.
There are 2 events (start and stop of some job) in the past which happened in different time zones.
I need to compute duration of the job.
I can get difference between those time zones in minutes and compute duration like this:
var duration = endTime - startTime.AddMinutes(diff);
However, there is a thing which confuses me.
Suppose start event happend in California and end event was in say Israel.
Right now difference between these time zones is 9 hours - California just switched to the day light and Israel not yet.
Next week this difference will be 10 hours because Israel will switch.
So, duration will be different if computed now and on the next week.
What is the right way to compute it?
Use DateTimeOffset?
So, duration will be different if computed now and on the next week.
I think you're forgetting that its you who is moving through time. not the project start and end times. therefore results will always be the same.
Id be tempted though just to convert the second time into the zone of the first, so it's just a straight diff. to convert from utc to any other zone you can use the following code
DateTime timeUtc = DateTime.UtcNow;
TimeZoneInfo cstZone = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
DateTime cstTime = TimeZoneInfo.ConvertTimeFromUtc(timeUtc, cstZone);
Theres also good info at https://learn.microsoft.com/en-us/dotnet/standard/datetime/converting-between-time-zones
i want to know the time difference between two countries.
There is ofcourse the static time difference, but during some periods the daylight saving time comes in between.
As far as i know the dst period is also different for some countries, so june 1 the diff between country a and b can be 1 hour, 1 july it can be 2 hours due to DST, and 1 august it can be 1 again etc etc
Is there a framework function for it or do i have to calculate it myself?
Michel
You need to know:
Both time zones (use TimeZoneInfo from .NET 3.5, bearing in mind that one country can have several time zones)
An instant in time, e.g. a UTC DateTime or a DateTimeOffset.
At that point it's relatively easy: convert the UTC instant into the local time in both time zones using TimeZoneInfo.ConvertTime, and subtract one from the other. Alternatively, use TimeZoneInfo.GetUtcOffset for both of them, and subtract one offset from the other.
Here's an example to find the current difference between London and Mountain View:
using System;
class Test
{
static void Main()
{
var mountainView = TimeZoneInfo.FindSystemTimeZoneById
("Pacific Standard Time");
var london = TimeZoneInfo.FindSystemTimeZoneById
("GMT Standard Time");
DateTimeOffset now = DateTimeOffset.UtcNow;
TimeSpan mountainViewOffset = mountainView.GetUtcOffset(now);
TimeSpan londonOffset = london.GetUtcOffset(now);
Console.WriteLine(londonOffset-mountainViewOffset); // 8 hours
}
}
To find out any historical difference (ie. for a date in the past when local DST policies for one or both timezones were different) you will have to store past policies for DST start/end/offset for each timezone and work them out yourself.
If you're fine with knowing the difference based on the current set of rules loaded into Windows then the built-in .NET method is straightforward and easy.