Noda Time - Start/end of day with zone - c#

What's the proper and more concise way to get the ZonedDateTime(s) which represent the start and the end of the current day in the timezone set on the system on which the code runs?
Isn't the following code too much complicated?
ZonedDateTime nowInZone = SystemClock.Instance.Now.InZone(DateTimeZoneProviders.Bcl.GetSystemDefault());
ZonedDateTime start = new LocalDateTime(nowInZone.Year, nowInZone.Month, nowInZone.Day, 0, 0, 0).InZoneStrictly(DateTimeZoneProviders.Bcl.GetSystemDefault());
ZonedDateTime end = new LocalDateTime(nowInZone.Year, nowInZone.Month, nowInZone.Day, 23, 59, 59).InZoneStrictly(DateTimeZoneProviders.Bcl.GetSystemDefault());
Given those values, I need to test if another ZonedDateTime is between them.

The AtStartOfDay value on the DateTimeZone object has the magic you're looking for.
// Get the current time
IClock systemClock = SystemClock.Instance;
Instant now = systemClock.Now;
// Get the local time zone, and the current date
DateTimeZone tz = DateTimeZoneProviders.Tzdb.GetSystemDefault();
LocalDate today = now.InZone(tz).Date;
// Get the start of the day, and the start of the next day as the end date
ZonedDateTime dayStart = tz.AtStartOfDay(today);
ZonedDateTime dayEnd = tz.AtStartOfDay(today.PlusDays(1));
// Compare instants using inclusive start and exclusive end
ZonedDateTime other = new ZonedDateTime(); // some other value
bool between = dayStart.ToInstant() <= other.ToInstant() &&
dayEnd.ToInstant() > other.ToInstant();
A couple of points:
It's better to get in the habit of separating the clock instance from the call to Now. This makes it easier to replace the clock later when unit testing.
You only need to get the local time zone once. I prefer to use the Tzdb provider, but either provider will work for this purpose.
For the end of day, it's better to use the start of the next day. This prevents you from having to deal with granularity issues, such as whether you should take 23:59, 23:59:59, 23:59.999, 23:59:59.9999999, etc. Also, it makes it easier to get whole-number results when doing math.
In general, date+time ranges (or time-only ranges) should be treated as half-open intervals [start,end) - while date-only ranges should be treated as fully-closed intervals [start,end].
Because of this, the start is compared with <= but the end is compared with >.
If you know for certain that the other ZonedDateTime value is in the same time zone and uses the same calendar, you can omit the calls to ToInstant and just compare them directly.
Update
As Jon mentioned in comments, the Interval type may be a useful convenience for this purpose. It is already set up to work with a half-open range of Instant values. The following function will get the interval for a the current "day" in a particular time zone:
public Interval GetTodaysInterval(IClock clock, DateTimeZone timeZone)
{
LocalDate today = clock.Now.InZone(timeZone).Date;
ZonedDateTime dayStart = timeZone.AtStartOfDay(today);
ZonedDateTime dayEnd = timeZone.AtStartOfDay(today.PlusDays(1));
return new Interval(dayStart.ToInstant(), dayEnd.ToInstant());
}
Call it like this (using the same values from above):
Interval day = GetTodaysInterval(systemClock, tz);
And now comparison can be done with the Contains function:
bool between = day.Contains(other.ToInstant());
Note that you still have to convert to an Instant, as the Interval type is not time zone aware.

Related

In C#, how can I always treat a DateTime value as being local to Europe/London at the time represented by the DateTime, and convert this to UTC?

I am working with DateTime values in a SQL Server database I don't maintain, and I want to work with them in my code as UTC.
To assist in understanding the problem, the values I'm working with represent the time that actions took place in our CRM system.
When I retrieve the values from SQL Server, they have no time zone indication on them but I know that they always represent Europe/London - either UTC in the winter, or UTC+1 in the summer.
I understand that I can use DateTimeKind.Local to indicate that a DateTime value is expressed in local time, but I don't understand how I specify which time zone the DateTime applies to. For example, if I'm working with the DateTime 2021-01-01 12:34:56, I need to ensure that regardless of where or when my code is running, this date is correctly interpreted as 2021-01-01 12:34:56 +00:00. Equally, I need to ensure that regardless of where or when my code is running, I need to interpret 2021-05-01 12:34:56 as 2021-05-01 12:34:56 +01:00.
How can I indicate that my DateTime values always apply to Europe/London at the time they represent?
Check nodaTime is a powerful datetime library for C#.
The example in there homepage seems to be related to your case.
/ Instant represents time from epoch
Instant now = SystemClock.Instance.GetCurrentInstant();
// Convert an instant to a ZonedDateTime
ZonedDateTime nowInIsoUtc = now.InUtc();
// Create a duration
Duration duration = Duration.FromMinutes(3);
// Add it to our ZonedDateTime
ZonedDateTime thenInIsoUtc = nowInIsoUtc + duration;
// Time zone support (multiple providers)
var london = DateTimeZoneProviders.Tzdb["Europe/London"];
// Time zone conversions
var localDate = new LocalDateTime(2012, 3, 27, 0, 45, 00);
var before = london.AtStrictly(localDate);

Difference Between DateTime (UTC) Based on a Local Timezone

I have two DateTime objects which contain two UTC date/times and a users TimezoneId (tzdb) as a string. I'm trying to write a method that takes these three parameters and returns the total seconds (or Duration) between the two datetimes relative to the timezone.
public static double GetDurationForTimezone(DateTime startUtc, DateTime endUtc, string timezoneId)
{
var timezone = DateTimeZoneProviders.Tzdb.GetZoneOrNull(timezoneId);
// convert UTC to timezone
var startInstantUtc = Instant.FromDateTimeUtc(startUtc);
var startZonedDateTime = startInstantUtc.InZone(timezone);
var endInstantUtc = Instant.FromDateTimeUtc(endUtc);
var endZonedDateTime = endInstantUtc.InZone(timezone);
return endZonedDateTime.ToInstant().Minus(startZonedDateTime.ToInstant()).ToTimeSpan().TotalSeconds;
}
I want to do it w.r.t. the timezone, to ensure it takes into account any possible Daylight Saving changes that may occur throughout this period.
Example test:
// DST starts (25h day -- DST starts: 10/4 # 2am local time)
var result = GetDurationForTimezone(
new DateTime(2015, 10, 3, 15, 0, 0, DateTimeKind.Utc),
new DateTime(2015, 10, 4, 15, 0, 0, DateTimeKind.Utc),
"Australia/Sydney");
Assert.Equal(TimeSpan.FromHours(25).TotalSeconds, result);
But when running this test, it seems like the calls to .ToInstant() are not adhering to the ZonedDateTime versions, but rather the original UTC DateTime objects. Thus I'm seeing the result be 24 hours.
When determining the duration between UTC-based timestamps, the time zone is irrelevant.
UTC is Coordinated Universal Time. It is the same for everyone on the planet. It does not have daylight saving time, and it's offset is always zero (UTC+00:00).
Since you have already asserted that the input values are in UTC, you do not necessarily need to use Noda Time for this operation. Just subtract the two values.
TimeSpan duration = endUtc - startUtc;
If you do use Noda Time, a UTC value is best represented by an Instant, which makes it very easy to obtain a Duration.
Instant start = Instant.FromDateTimeUtc(startUtc);
Instant end = Instant.FromDateTimeUtc(endUtc);
Duration duration = end - start;
You could also represent them using ZonedDateTime values that happen to be "in UTC", however you'd quickly find that the API requires you convert them back to Instant values to obtain a Duration anyway.
ZonedDateTime start = LocalDateTime.FromDateTime(startUtc).InUtc();
ZonedDateTime end = LocalDateTime.FromDateTime(endUtc).InUtc();
Duration duration = end.ToInstant() - start.ToInstant();
You might think that just using LocalDateTime would be an option, but that structure represents a wall time, without any time zone information. You can't obtain a Duration between two of them. You could obtain a Period by using Period.Between, but that would represent the calendar/clock-value difference between the two representations - which is not the same as the actual amount of time that has elapsed.
As a thought exercise that will help understand the difference, consider these two values:
2015-11-01 00:30
2015-11-01 01:30
If I tell you that the values are in UTC, then there is one hour difference. However, if I tell you these are wall-clock values and they are in the US Eastern Time zone, then they might be one hour apart, or they might be two hours apart. It depends on whether or not the 01:30 is the one before the DST transition, or the one after - as there are two on this day.
Now if instead I gave you these values:
2015-11-01 00:30
2015-11-01 02:30
Again, if you interpret them as UTC they are exactly two hours apart. But if you interpret them in the same US Eastern time zone, then they are exactly three hours apart, because the range is inclusive of the DST transition. If you just subtract the local wall-time values then you'd get two hours, which would be incorrect.
Switching to utilize the LocalDateTime property of the ZonedDateTime allows for comparing the date/times relative to the timezone. This works for both prime test cases (23h and 25h days):
public static double GetDurationForTimezone(DateTime startUtc, DateTime endUtc, string timezoneId)
{
var timezone = DateTimeZoneProviders.Tzdb.GetZoneOrNull(timezoneId);
// convert UTC to timezone
var startInstantUtc = Instant.FromDateTimeUtc(startUtc);
var startZonedDateTime = startInstantUtc.InZone(timezone);
var startLocalDateTime = startZonedDateTime.LocalDateTime;
var endInstantUtc = Instant.FromDateTimeUtc(endUtc);
var endZonedDateTime = endInstantUtc.InZone(timezone);
var endLocalDateTime = endZonedDateTime.LocalDateTime;
return Period.Between(startLocalDateTime, endLocalDateTime, PeriodUnits.Seconds).Seconds;
}
Studying this page: ZonedDateTime.Comparer Members
it seems like you have to use property Local and not Instant to reflect the local daylight savings.

Is there a way to instantiate a datetime object in a timezone other than local?

I've been racking my brain all afternoon trying to figure this one out. Essentially, the problem itself seems simple. I'm given a date/time that is representative of a date and time in another time zone (not local). I want to convert this value to a UTC value to store in the database. However, all of the methods I find online seem to point to you either starting with UTC or starting with a local time zone. You can convert TO other time zones from these, but you can't start with anything other than those. As a result, it appears that I'll have to do some kind of convoluted offset math to do what I want. Here is an example of the problem:
var dateString = "8/20/2014 6:00:00 AM";
DateTime date1 = DateTime.Parse(dateString,
System.Globalization.CultureInfo.InvariantCulture);
var currentTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
// Now the server is set to Central Standard Time, so any automated offset calculation that it runs will come from that point of view:
var utcDate = date1.ToUniversalTime; // This is wrong
// Similarly, if I try to reverse-calculate it, it doesn't work either
var convertedDate = TimeZoneInfo.ConvertTime(date1, currentTimeZone);
utcDate = convertedDate.ToUniversalTime; // This is also wrong
In essence, I want to somehow tell the system that the datetime object I'm currently working with is from that time zone other than local, so that I know the conversion will be correct. I know that I'll eventually need to figure Daylight Savings Time in there, but that is a problem for another day.
Would this method be of any use to you ?
The TimeZoneInfo.ConvertTime method converts a time from one time zone
to another.
Alternatively, you could use the ConvertTimeToUtc method to simply convert any date (specifying the source time zone) to UTC.
var dateString = "8/20/2014 6:00:00 AM";
DateTime date1 = DateTime.Parse(dateString,
System.Globalization.CultureInfo.InvariantCulture);
var currentTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
var utcDate = TimeZoneInfo.ConvertTimeToUtc(date1, currentTimeZone);
The System.DateTime struct only has two bits for storing the "kind" information. That is why you can only have "local" or "universal" or "unknown" (or "magicl local").
Take a look at the System.DateTimeOffset struct. It is like a DateTime, but it also keeps the time zone (offset from (plus or minus) UTC).

Given a DateTimeZone and two Instants, determine if a LocalTime falls between the two Instants

For each of my users, I store a tzid which I convert to a DateTimeZone holding information about their local time zone.
I want to send the user a daily email at 8 AM local time; if 8 AM is ambiguous for whatever reason like a daylight savings shift, I just need to pick one of the 8 AM; I don't care which.
My job runs hourly, and I have an Instant containing the last run time of the job, and another Instant containing the next run time of the job.
Given these two Instant called previousRun and nextRun, and the DateTimeZone called tz how would I determine whether the localTime called eightAM falls between the bounds of this job run? If it does, I need to send the user an email.
Given these two Instant called previousRun and nextRun, and the DateTimeZone called tz how would I determine whether the localTime called eightAM falls between the bounds of this job run?
Doing this in a general way is somewhat tricky, I think. However, if you can rely on the time you want being well away from midnight and your job will run every hour (so you don't need to consider what happens if it hasn't run between midnight and 8am, for example) I think you could do something like this:
public static bool ShouldSendEmail(Instant previousRun, Instant nextRun,
DateTimeZone zone)
{
// Find the instant at which we should send the email for the day containing
// the last run.
LocalDate date = previousRun.InZone(zone).Date;
LocalDateTime dateTime = date + new LocalTime(8, 0);
Instant instant = dateTime.InZoneLeniently(zone).ToInstant();
// Check whether that's between the last instant and the next one.
return previousRun <= instant && instant < nextRun;
}
You can check the docs for InZoneLeniently to check exactly what result it will give, but it sounds like you don't really mind: this will still send exactly one email a day, in an hour which contains 8am.
I haven't parameterized this by the time of day precisely because it would be much harder to handle the general case where the time of day could be near midnight.
EDIT: If you can store the "next date to send" then it's easy - and you don't need the previousRun part:
public static bool ShouldSendEmail(LocalDateTime nextDate, Instant nextRun,
DateTimeZone zone, LocalTime timeOfDay)
{
LocalDateTime nextEmailLocal = nextDate + timeOfDay;
Instant nextEmailInstant = nextDateTime.InZoneLeniently(zone).ToInstant();
return nextRun > nextEmailInstant;
}
Basically that says, "Work out when we next want to send an email - and if the next run will be later than that, we should send it now."

Difference between System.DateTime.Now and System.DateTime.Today

Can anyone explain the difference between System.DateTime.Now and System.DateTime.Today in C#.NET? Pros and cons of each if possible.
DateTime.Now returns a DateTime value that consists of the local date and time of the computer where the code is running. It has DateTimeKind.Local assigned to its Kind property. It is equivalent to calling any of the following:
DateTime.UtcNow.ToLocalTime()
DateTimeOffset.UtcNow.LocalDateTime
DateTimeOffset.Now.LocalDateTime
TimeZoneInfo.ConvertTime(DateTime.UtcNow, TimeZoneInfo.Local)
TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, TimeZoneInfo.Local)
DateTime.Today returns a DateTime value that has the same year, month, and day components as any of the above expressions, but with the time components set to zero. It also has DateTimeKind.Local in its Kind property. It is equivalent to any of the following:
DateTime.Now.Date
DateTime.UtcNow.ToLocalTime().Date
DateTimeOffset.UtcNow.LocalDateTime.Date
DateTimeOffset.Now.LocalDateTime.Date
TimeZoneInfo.ConvertTime(DateTime.UtcNow, TimeZoneInfo.Local).Date
TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, TimeZoneInfo.Local).Date
Note that internally, the system clock is in terms of UTC, so when you call DateTime.Now it first gets the UTC time (via the GetSystemTimeAsFileTime function in the Win32 API) and then it converts the value to the local time zone. (Therefore DateTime.Now.ToUniversalTime() is more expensive than DateTime.UtcNow.)
Also note that DateTimeOffset.Now.DateTime will have similar values to DateTime.Now, but it will have DateTimeKind.Unspecified rather than DateTimeKind.Local - which could lead to other errors depending on what you do with it.
So, the simple answer is that DateTime.Today is equivalent to DateTime.Now.Date.
But IMHO - You shouldn't use either one of these, or any of the above equivalents.
When you ask for DateTime.Now, you are asking for the value of the local calendar clock of the computer that the code is running on. But what you get back does not have any information about that clock! The best that you get is that DateTime.Now.Kind == DateTimeKind.Local. But whose local is it? That information gets lost as soon as you do anything with the value, such as store it in a database, display it on screen, or transmit it using a web service.
If your local time zone follows any daylight savings rules, you do not get that information back from DateTime.Now. In ambiguous times, such as during a "fall-back" transition, you won't know which of the two possible moments correspond to the value you retrieved with DateTime.Now. For example, say your system time zone is set to Mountain Time (US & Canada) and you ask for DateTime.Now in the early hours of November 3rd, 2013. What does the result 2013-11-03 01:00:00 mean? There are two moments of instantaneous time represented by this same calendar datetime. If I were to send this value to someone else, they would have no idea which one I meant. Especially if they are in a time zone where the rules are different.
The best thing you could do would be to use DateTimeOffset instead:
// This will always be unambiguous.
DateTimeOffset now = DateTimeOffset.Now;
Now for the same scenario I described above, I get the value 2013-11-03 01:00:00 -0600 before the transition, or 2013-11-03 01:00:00 -0700 after the transition. Anyone looking at these values can tell what I meant.
I wrote a blog post on this very subject. Please read - The Case Against DateTime.Now.
Also, there are some places in this world (such as Brazil) where the "spring-forward" transition happens exactly at Midnight. The clocks go from 23:59 to 01:00. This means that the value you get for DateTime.Today on that date, does not exist! Even if you use DateTimeOffset.Now.Date, you are getting the same result, and you still have this problem. It is because traditionally, there has been no such thing as a Date object in .Net. So regardless of how you obtain the value, once you strip off the time - you have to remember that it doesn't really represent "midnight", even though that's the value you're working with.
If you really want a fully correct solution to this problem, the best approach is to use NodaTime. The LocalDate class properly represents a date without a time. You can get the current date for any time zone, including the local system time zone:
using NodaTime;
...
Instant now = SystemClock.Instance.Now;
DateTimeZone zone1 = DateTimeZoneProviders.Tzdb.GetSystemDefault();
LocalDate todayInTheSystemZone = now.InZone(zone1).Date;
DateTimeZone zone2 = DateTimeZoneProviders.Tzdb["America/New_York"];
LocalDate todayInTheOtherZone = now.InZone(zone2).Date;
If you don't want to use Noda Time, there is now another option. I've contributed an implementation of a date-only object to the .Net CoreFX Lab project. You can find the System.Time package object in their MyGet feed. Once added to your project, you will find you can do any of the following:
using System;
...
Date localDate = Date.Today;
Date utcDate = Date.UtcToday;
Date tzSpecificDate = Date.TodayInTimeZone(anyTimeZoneInfoObject);
Time. .Now includes the 09:23:12 or whatever; .Today is the date-part only (at 00:00:00 on that day).
So use .Now if you want to include the time, and .Today if you just want the date!
.Today is essentially the same as .Now.Date
The DateTime.Now property returns the current date and time, for example 2011-07-01 10:09.45310.
The DateTime.Today property returns the current date with the time compnents set to zero, for example 2011-07-01 00:00.00000.
The DateTime.Today property actually is implemented to return DateTime.Now.Date:
public static DateTime Today {
get {
DateTime now = DateTime.Now;
return now.Date;
}
}
DateTime.Today represents the current system date with the time part set to 00:00:00
and
DateTime.Now represents the current system date and time
I thought of Adding these links -
A brief History of DateTime - By Anthony Moore by BCL team
Choosing between Datetime and DateTime Offset - by MSDN
Do not forget SQL server 2008 onwards has a new Datatype as DateTimeOffset
The .NET Framework includes the DateTime, DateTimeOffset, and
TimeZoneInfo types, all of which can be used to build applications
that work with dates and times.
Performing Arithmetic Operations with Dates and Times-MSDN
Coming back to original question , Using Reflector i have explained the difference in code
public static DateTime Today
{
get
{
return DateTime.Now.Date; // It returns the date part of Now
//Date Property
// returns same date as this instance, and the time value set to 12:00:00 midnight (00:00:00)
}
}
private const long TicksPerMillisecond = 10000L;
private const long TicksPerDay = 864000000000L;
private const int MillisPerDay = 86400000;
public DateTime Date
{
get
{
long internalTicks = this.InternalTicks; // Date this instance is converted to Ticks
return new DateTime((ulong) (internalTicks - internalTicks % 864000000000L) | this.InternalKind);
// Modulo of TicksPerDay is subtracted - which brings the time to Midnight time
}
}
public static DateTime Now
{
get
{
/* this is why I guess Jon Skeet is recommending to use UtcNow as you can see in one of the above comment*/
DateTime utcNow = DateTime.UtcNow;
/* After this i guess it is Timezone conversion */
bool isAmbiguousLocalDst = false;
long ticks1 = TimeZoneInfo.GetDateTimeNowUtcOffsetFromUtc(utcNow, out isAmbiguousLocalDst).Ticks;
long ticks2 = utcNow.Ticks + ticks1;
if (ticks2 > 3155378975999999999L)
return new DateTime(3155378975999999999L, DateTimeKind.Local);
if (ticks2 < 0L)
return new DateTime(0L, DateTimeKind.Local);
else
return new DateTime(ticks2, DateTimeKind.Local, isAmbiguousLocalDst);
}
}
DateTime dt = new DateTime();// gives 01/01/0001 12:00:00 AM
DateTime dt = DateTime.Now;// gives today date with current time
DateTime dt = DateTime.Today;// gives today date and 12:00:00 AM time
DateTime.Today is DateTime.Now with time set to zero.
It is important to note that there is a difference between a DateTime value, which represents the number of ticks that have elapsed since midnight of January 1, 0000, and the string representation of that DateTime value, which expresses a date and time value in a culture-specific-specific format:
https://msdn.microsoft.com/en-us/library/system.datetime.now%28v=vs.110%29.aspx
DateTime.Now.Ticks is the actual time stored by .net (essentially UTC time), the rest are just representations (which are important for display purposes).
If the Kind property is DateTimeKind.Local it implicitly includes the time zone information of the local computer. When sending over a .net web service, DateTime values are by default serialized with time zone information included, e.g. 2008-10-31T15:07:38.6875000-05:00, and a computer in another time zone can still exactly know what time is being referred to.
So, using DateTime.Now and DateTime.Today is perfectly OK.
You usually start running into trouble when you begin confusing the string representation with the actual value and try to "fix" the DateTime, when it isn't broken.
DateTime.Now.ToShortDateString() will display only the date part

Categories