Code example of comparing times in different time zone in C# - c#

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.

Related

Convert time data to local time in C#

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.

C# UTC conversion and Daylight Saving Time in game save

So, I've made a game in which it is required to check the time when saving and loading.
The relevant chunk of loading code:
playerData = save.LoadPlayer();
totalSeconds = playerData.totalSeconds;
System.DateTime stamp = System.DateTime.MinValue;
if (!System.DateTime.TryParse(playerData.timeStamp, out stamp)) {
playerData.timeStamp = System.DateTime.UtcNow.ToString("o");
stamp = System.DateTime.Parse(playerData.timeStamp);
}
stamp = stamp.ToUniversalTime();
loadStamp = System.DateTime.UtcNow;
long elapsedSeconds = (long)(System.DateTime.UtcNow - stamp).TotalSeconds;
if (elapsedSeconds < 0) {
ui.Cheater();
}
Obviously, all this does is check to see if the currently saved timestamp can be parsed - if so, we make sure it's UTC, if not, we set the stamp to the current time and continue. If the elapsed time between the loaded timestamp and current time is negative, we know the player has messed with their clock to exploit the system.
The potential problem arises when the clocks move an hour back for DST.
This is the relevant code in the save function, if it matters:
if (loadStamp == System.DateTime.MinValue) {
loadStamp = System.DateTime.UtcNow;
}
playerData.timeStamp = loadStamp.AddSeconds(sessionSeconds).ToString("o");
My question is:
Will this currently used method potentially cause any problems when the clocks move back and falsely deem players cheaters?
Thank you in advance.
EDIT:
Forgot to add that it seems to not cause any problems on the computer when the time is set to when the clocks move back, but the game is mobile. Again, if that matters at all. Not quite sure. I've not done much with time-based rewards and stuff in games thus far.
Update I have significantly updated this answer in respect of comments made by #theMayer and the fact that while I was wrong, it may have highlighted a bigger issue.
I believe there is an issue here in the fact that the code is reading the UTC time in, converting it to local time, then converting it back to UTC.
The save routine records the value of loadStamp expressed with the Round Trip format specifier o, and as loadStamp is always set from DateTime.UtcNow, the value stored in the file will always be a UTC time with a trailing "Z" indicating UTC time.
For example:
2018-02-18T01:30:00.0000000Z ( = 2018-02-17T23:30:00 in UTC-02:00 )
The issue was reported in the Brazil time zone, with a UTC offset of UTC-02:00 (BRST) until 2018-02-18T02:00:00Z and a UTC offset of UTC-03:00 (BRT) after.
The code reaches this line:
if (!System.DateTime.TryParse(playerData.timeStamp, out stamp)) {
DateTime.TryParse() (which uses the same rules as DateTime.Parse()) will encounter this string. It will then convert the UTC time into a local time, and set stamp to equal:
2018-02-17T23:30:00 DateTimeKind.Local
The code then reaches:
stamp = stamp.ToUniversalTime();
At this point, stamp should represent an Ambiguous time, i.e. one that exists as a valid BRST and a valid BRT time, and MSDN states:
If the date and time instance value is an ambiguous time, this method assumes that it is a standard time. (An ambiguous time is one that can map either to a standard time or to a daylight saving time in the local time zone)
This means that .NET could be changing the UTC value of any ambiguous DateTime values that are converted to Local time and back again.
Although the documentation states this clearly, I have been unable to reproduce this behaviour in the Brazilian time zone. I am still investigating this.
My approach to this type of issue is to use the DateTimeOffset type instead of DateTime. It represents a point-in-time that is irrelevant of local time, time zones, or Daylight Savings.
An alternative approach to closing this hole would be to change:
if (!System.DateTime.TryParse(playerData.timeStamp, out stamp)) {
playerData.timeStamp = System.DateTime.UtcNow.ToString("o");
stamp = System.DateTime.Parse(playerData.timeStamp);
}
stamp = stamp.ToUniversalTime();
to
if (!System.DateTime.TryParse(playerData.timeStamp, null, DateTimeStyles.RoundtripKind, out stamp)) {
stamp = System.DateTime.UtcNow;
playerData.timeStamp = stamp.ToString("o");
}
Again assuming that the saved playerData.timeStamp will always be from a UTC date and therefore be in "Z" timezone, adding the DateTimeStyles.RoundtripKind should mean it gets parsed straight into DateTimeKind.Utc and not converted into Local time as DateTimeKind.Local. It also eliminates the need to call ToUniversalTime() on it to convert it back.
Hope this helps
On the surface, there does not appear to be anything obviously wrong with this code.
When doing DateTime comparison operations, it is important to ensure that the time zone of the compared DateTime's are consistent. The framework only compares the values of the instances, irrespective of whatever time zone you think they are in. It is up to you to ensure consistent time zones between DateTime instances. It looks like that is being done in this case, as times are converted from local to UTC prior to being compared with other UTC times:
stamp = stamp.ToUniversalTime();
elapsedSeconds = (long)(System.DateTime.UtcNow - stamp).TotalSeconds;
Some Caveats
One item that often trips people up is that the time value is repeatedly queried (each call to DateTime.UtcNow) - which could result in different values each time. However, the difference would be infinitesimal, and most of the time zero as this code will execute faster than the resolution of the processor clock.
Another fact, which I brought up in the comments, the "Round Trip Format Specifier" used to write the DateTime to string is intended to preserve time zone information - in this case, it should add a "Z" to the time to denote UTC. Upon conversion back (via TryParse), the parser will convert this time from the UTC to a local time if the Z is present. This can be a significant gotcha as it results in an actual DateTimevalue that is different from the one serialized to the string, and in a way is contrary to every other way that the .NET framework handles DateTime's (which is to ignore time zone info). If you have a case where the "Z" is not present in the incoming string, but the string is otherwise UTC, then you have a problem there because it will be comparing a UTC time whose value has been adjusted a second time (thus making it the time of UTC +2).
It should also be noted that in .Net 1.1, DateTime.ToUniversalTime() is NOT an idempotent function. It will offset the DateTime instance by the difference in time zone between the local time zone and UTC each time it is called. From the documentation:
This method assumes that the current DateTime holds the local time value, and not a UTC time. Therefore, each time it is run, the current method performs the necessary modifications on the DateTime to derive the UTC time, whether the current DateTime holds the local time or not.
Programs using a later version of the framework may or may not have to worry about this, depending on usage.

Convert DST rule to DateTime in C#

I have DST rules like this one:
"2,-1,1,3600000"
the 2 is the zero based month
The -1 is the week number containing the day, -1 means the last week in the month which contains the day
The 1 is the day of the week, 1 - Sunday through to 7 - Saturday
The 3600000 is the mS from midnight on the appointed day that the change to/from DST will take place and is expressed in local time including DST, so the end of DST switch time is in DST.
what's the proper way to transform it in a C# DateTime?
So far I've done this:
public static DateTime ConvertDstRule(int year, string rule, bool isEndRule)
{
const int DaysInWeek = 7;
var ruleName = isEndRule ? "endRule" : "startRule";
var startStrings = rule.Split(',');
var month = Convert.ToInt32(startStrings[0]);
if ((month < 0) || (month > 11))
{
throw new ArgumentOutOfRangeException(ruleName, "The month value must be between 0 and 11");
}
var week = Convert.ToInt32(startStrings[1]);
if ((week < -1) || (week > 5))
{
throw new ArgumentOutOfRangeException(ruleName, "The week value must be between -1 and 5");
}
if ((Convert.ToInt32(startStrings[2]) < 1) || (Convert.ToInt32(startStrings[2]) > 7))
{
throw new ArgumentOutOfRangeException(ruleName, "The day value must be between 1 and 7");
}
var day = (DayOfWeek)(Convert.ToInt32(startStrings[2]) - 1); // DayOfWeek is zero based so shift by one.
var timeOffset = Convert.ToInt64(startStrings[3]);
if ((timeOffset / 1000 / 60) > 86400)
{
throw new ArgumentOutOfRangeException(ruleName, "The time offset is limited to one day");
}
// Find the start of the relevant year.
var startTime = new DateTime(year, 1, 1);
// Add on the month to get to the start of the selected month.
startTime = startTime.AddMonths(month);
// If the week is negative then go to the first occurance of the day in
// the next month, adding a negative week number will jump back into
// the previous month.
if (week < 0)
{
startTime = startTime.AddMonths(1);
}
else
{
week = week - 1;
}
// Jump to the first occurence of the day to switch in that month.
var monthStartsOn = startTime.DayOfWeek;
var daysToSwitchDay = (int)day - (int)monthStartsOn;
// This is likely to be negative as most zones switch on a Sunday
if (daysToSwitchDay < 0)
{
daysToSwitchDay = DaysInWeek + daysToSwitchDay; // daysToSwitchDay is negative so add it.
}
startTime = startTime.AddDays(daysToSwitchDay); // Now on the correct day.
startTime = startTime.AddDays(week * 7); // Week counts from 1.
startTime = startTime.AddMilliseconds(timeOffset);
if (isEndRule)
{
startTime = startTime.AddHours(-1); // Take off the DST hour to convert it to UTC.
}
return startTime;
}
Does it takes into account half-hour DST changes like in India? Can you spot any bug in this code?
A few things:
The type of input you are describing is called a "Transition Rule". Or outside the context of time zones, it is a particular type of "Recurrence Rule". It simply describes a pattern for determining when a particular point in time occurs for a given year, based on month, week, and weekday.
A transition rule does not tell you everything you need to know to calculate values for time zones. In particular, it doesn't tell you what the offset from UTC is before or after the transition, or the direction that the offset is being adjusted. Also, you would need a set of these, as there is usually two of these per year, but there could also be any number of them. For example, in 2014, Russia had only one transition, and Egypt had four. Also consider that these rules have changed over time, so a single rule, or even a single pair of rules, will not tell you how to convert values for all points in time. For a given time zone, you need a set-of-sets-of-rules, which is what we mean when we say "a time zone".
In the .NET Framework, the TimeZoneInfo class is used for working with time zones. It contains subclasses, TimeZoneInfo.AdjustmentRule and TimeZoneInfo.TransitionTime. In particular, there's an internal function in TimeZoneInfo called TransitionTimeToDateTime which does exactly what you are asking. It takes TransitionTime and a year, and gives you the DateTime that the transition occurs within the year. You can find this in the .NET Reference Source here.
However, if you really think you want to try to implement time zone rules from your own data sources, you should be thinking about several things:
Do you really think you can do better than the hundreds of people that have come before you? Maybe so, but don't go into this with a "this is simple" attitude. I suggest you watch this short video, and do a LOT of research before attempting this.
Are you thinking about how you will keep things maintained? Time zone rules change often. Annually, there can be about a dozen changes worldwide. Are you planning to monitor news feeds, discussion forums, government press releases, and other sources? Are you prepared to act when a government doesn't give that much warning that it's DST rules are going to change?
How much impact will it have on your system when things aren't accurate? It could range from not much (ex: a blog or forum) to critical (ex: airline schedules, communications, medical, financial, etc.).
Additionally:
The suggestion of using Noda Time from the question's comments is a good one, but unfortunately Noda Time doesn't have any specific API for interpreting just a single transition rule. Instead, it's prefered that you use existing TZDB zones, such as "America/New_York". Alternatively, you can use the TimeZoneInfo class in the .NET Framework, with IDs like "Eastern Standard Time".
Internally, Noda Time handles transition rules through the ZoneYearOffset class. You can see how a TransitionRule maps to a ZoneYearOffset in this code.
In your code sample, the comment "Take off the DST hour to convert it to UTC." is highly misleading. The output of your function is in terms of local time. UTC has nothing to do with it. Nowhere else in the code you showed here are you tracking offsets from UTC, either with or without DST. I think you meant "... convert it to standard time", but I'm not sure why you would want to do that. This particular function shouldn't try to account for that.
You also asked: "Does it takes into account half-hour DST changes like in India?" The question is invalid, because India doesn't have DST. Its standard time zone offset has a half-hour in it (UTC+05:30), but there's no DST. The only place in the world that currently uses a half-hour DST bias is Lord Howe Island, which is represented by the "Australia/Lord_Howe" time zone in the tzdb, and currently has no Windows equivalent. Everywhere else in the world (except a few research stations in Antarctica), if DST is used the transition bias is one hour.

Noda Time Countdown

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.

Representation of a DateTime as the local to remote user

Hello!
I was confused in the problem of time zones. I am writing a web application that will contain some news with dates of publication, and I want the client to see the date of publication of the news in the form of corresponding local time. However, I do not know in which time zone the client is located.
I have three questions.
I have to ask just in case: does DateTimeOffset.UtcNow always returns the correct UTC date and time, regardless of whether the server is dependent on daylight savings time? For example, if the first time I get the value of this property for two minutes before daylight savings time (or before the transition from daylight saving time back) and the second time in 2 minutes after the transfer, whether the value of properties in all cases differ by only 4 minutes? Or here require any further logic? (Question #1)
Please see the following example and tell me what you think.
I posted the news on the site. I assume that DateTimeOffset.UtcNow takes into account the time zone of the server and the daylight savings time, and so I immediately get the correct UTC server time when pressing the button "Submit". I write this value to a MS SQL database in the field of type datetime2(0).
Then the user opens a page with news and no matter how long after publication. This may occur even after many years. I did not ask him to enter his time zone. Instead, I get the offset of his current local time from UTC using the javascript function following way:
function GetUserTimezoneOffset()
{
var offset = new Date().getTimezoneOffset();
return offset;
}
Next I make the calculation of the date and time of publication, which will show the user:
public static DateTime Get_Publication_Date_In_User_Local_DateTime(
DateTime Publication_Utc_Date_Time_From_DataBase,
int User_Time_Zone_Offset_Returned_by_Javascript)
{
int userTimezoneOffset = User_Time_Zone_Offset_Returned_by_Javascript; // For
// example Javascript returns a value equal to -300, which means the
// current user's time differs from UTC to 300 minutes. Ie offset
// is UTC +6. In this case, it may be the time zone UTC +5 which
// currently operates summer time or UTC +6 which currently operates the
// standard time.
// Right? (Question #2)
DateTimeOffset utcPublicationDateTime =
new DateTimeOffset(Publication_Utc_Date_Time_From_DataBase,
TimeSpan.Zero); // get an instance of type DateTimeOffset for the
// date and time of publication for further calculations
DateTimeOffset publication_DateTime_In_User_Local_DateTime =
utcPublicationDateTime.ToOffset(new TimeSpan(0, - userTimezoneOffset, 0));
return publication_DateTime_In_User_Local_DateTime.DateTime;// return to user
}
Is the value obtained correct? Is this the right approach to solving this problem? (Question #3)
UPDATED Oct 19 at 6:58 (I tried post it as a comment but it's too long by 668 characters)
Matt Johnson, Thank You for such a detailed answer despite that of the fact that you are doing this not the first time. Thank you for taking the time to explain this particular case, and not just provide links to other posts.
I have read the information that you have provided. Maybe I'm still not fully aware of all the details, but if I understand it right, for the right convertion of DateTime (which was written many years ago in the database) from UTC to the same user's moment, I need to know UTC offset which he had at that moment. And it is difficult taking into account that transfer rules for DST change constantly. And even now, though the platform ".NET" contains some TZDB that is used for the TimeZoneInfo type, I can't take advantage of it without the exact position of the user.
But what if I am only interested in the date and time of starting this year, and only in Russia, where DST was canceled in 2011? As I understand it, this means that if properly configured clock on user's computer, located in Russia, this approach will always give the correct result. And since 2011, the offset to UTC of user's clock should always be the same. Accordingly, the shift indicators in different browsers will not be different for the Russian user's.
Answer to Question 1
... does DateTimeOffset.UtcNow always returns the correct UTC date and time, regardless of whether the server is dependent on daylight savings time?
Yes. As long is your clock is set correctly, UtcNow always refers to the UTC time. The time zone settings of the server will not affect it. The value in your example will always be 4 minutes, regardless of DST.
Answer to Question 2
var offset = new Date().getTimezoneOffset();
Since new Date() returns the current date and time, this will return you the current offset. You then proceed to apply this current offset to some past value, which may or may not be the correct offset for that specific time. Please read the timezone tag wiki, especially the section titled "Time Zone != Offset".
Answer to Question 3
Is the value obtained correct? Is this the right approach to solving this problem?
No. This is not the correct approach. You have a few options:
First Option
Just pass the UTC value to JavaScript without modification.
Send it in ISO8601 format, such as 2013-10-18T12:34:56.000Z. You can get this in .Net easily using yourDateTime.ToString("o").
Be sure the DateTime you are starting with has .Kind == DateTimeKind.Utc, otherwise it won't get the Z on the end, which is essential.
If you are targeting older browsers that can't parse ISO8601, then you will need a library such as moment.js to parse it for you.
Alternatively, you could pass the number of milliseconds since 1/1/1970 UTC as a number and load that into a JavaScript Date instead of parsing.
Now you can just display the Date object using JavaScript. Let the browser convert it from UTC to the users local time.
Warning, with this approach, some conversions might be incorrect due to the problem I describe here.
Second Option
Like the first option, pass the UTC timestamp to JavaScript
Use that to get the offset for that timestamp.
Pass the offset back to the server in a postback or ajax call.
Apply the offset on the server
Output the local time zone
I don't particularly like this option because of the round trip. You might as well calculate it in JavaScript like the first option.
Third Option
Get the user's time zone. The best way is to ask them for it, usually on a user setting page.
Use it to convert from the UTC time to their local time completely on the server.
You can use Windows time zones and the TimeZoneInfo class to do the conversions.
Or you can use IANA/Olson time zones and the Noda Time library.
If you do this, you can optionally use a map-based timezone picker.
You can take a guess at the user's time zone with jsTimeZoneDetect.
This approach is more accurate, but requires more user interaction.
Also, please in future posts, ask just one question at a time. And please search before you post - I've written most of this in one form or another many times before.

Categories