Recalculate WPF DatePicker value to specific Noda Time DateTimeZone - c#

I'm using Noda Time library in my project to work with Dates. But I need to allow user to enter date/time using DatePicker in specific Noda timezone (non utc, non local/system). How can I achieve that?
Currently I binded my DatePicker to DateTime property and converting this value in property setter to unspecified kind
public DateTime SessionDate
{
get
{
return _sessionDate;
}
set
{
_sessionDate = new DateTime(value.Ticks, DateTimeKind.Unspecified);
OnPropertyChanged("SessionDate");
}
}
So, now I have value entered by user represented as DateTime structure with unspecified kind.
But I need to get the UTC value (Noda Instant) from my unspecified SessionDate by applying known DateTimeZone. I tried to use
var instant = new Instant(SessionDateTime.Ticks);
var offset = myTimeZone.GetUtcOffset(instant);
instant = instant.PlusTicks(- offset.Ticks);
but I'm not sure if this is a good approach

If your user is entering time with respect to a specific time zone, then you're not starting with an Instant. You're starting with a LocalDateTime and a DateTimeZone. You need to bind those together to get a ZonedDateTime before you can get to an Instant.
LocalDateTime ldt = LocalDateTime.FromDateTime(SessionDateTime);
ZonedDateTime zdt = ldt.InZoneLeniently(myTimeZone);
Instant instant = zdt.ToInstant();

Related

Is there a way to group by a range of dates or datetimeoffset?

I have a bunch of data with a DateTime that's in UTC, I want to group each data by date by using .GroupBy, but I want to group them according to CST or EST. Is there a way to do this using EF core? I haven't been successful at finding if this is possible by using DateTimeOffset or TimeSpan.
You can create a TimeZoneInfo object representing e.g. Eastern Standard Time with the TimeZoneInfo.FindSystemTimeZoneById method. (Note: this depends on the OS timezone rules, which e.g. in the case of Windows do not cover all DST history.)
var tzest = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
Then you can convert any given DateTime test that has DateTime.Kind of DateTimeKind.Utc to the new timezone:
var testest = TimeZoneInfo.ConvertTime(test, tzest);
If your DateTime has Kind as Unspecified you can use TimeZoneInfo.ConvertTimeFromUtc():
var testest = TimeZoneInfo.ConvertTimeFromUtc(test, tzest);
If test has TimeZoneKind.Local, this will throw an exception and you will need to override the TimeZoneKind:
var testest = TimeZoneInfo.ConvertTimeFromUtc(DateTime.SpecifyKind(test, DateTimeKind.Utc), tzest);

How to set timezone based timestamp to get current date and time in C#? [duplicate]

This code has been working for a long time now but has broken now when I try to pass DateTime.Now as the outageEndDate parameter:
public Outage(DateTime outageStartDate, DateTime outageEndDate, Dictionary<string, string> weeklyHours, string province, string localProvince)
{
this.outageStartDate = outageStartDate;
this.outageEndDate = outageEndDate;
this.weeklyHours = weeklyHours;
this.province = province;
localTime = TimeZoneInfo.FindSystemTimeZoneById(timeZones[localProvince]);
if (outageStartDate < outageEndDate)
{
TimeZoneInfo remoteTime = TimeZoneInfo.FindSystemTimeZoneById(timeZones[province]);
outageStartDate = TimeZoneInfo.ConvertTime(outageStartDate, localTime, remoteTime);
outageEndDate = TimeZoneInfo.ConvertTime(outageEndDate, localTime, remoteTime);
The error message I am getting on the last line is that the Kind property is not set correctly on the DateTime parameter (outageEndDate). I've Googled and checked SO for examples but I don't really understand the error message.
Any advice is appreciated.
Regards.
EDIT - The exact error message is:
The conversion could not be completed because the supplied DateTime did not have the Kind
property set correctly. For example, when the Kind property is DateTimeKind.Local, the source
time zone must be TimeZoneInfo.Local. Parameter name: sourceTimeZone
EDIT: outageEndDate.Kind = Utc
Thanks for clarifying your question.
If the DateTime instance Kind is Local, then TimeZoneInfo.ConvertTime will expect the second parameter to be the local timezone of your computer.
If DateTime instance Kind is Utc, then TimeZoneInfo.ConvertTime will expect the second parameter to be the Utc timezone.
You need to convert outageEndDate to the right timezone first, just in case the localProvice timezone doesn't match the timezone on your computer.
outageEndDate = TimeZoneInfo.ConvertTime(outageEndDate, localTime);
here is an example of something that you could try
It depends on what you mean by "a GMT + 1 timezone". Do you mean permanently UTC+1, or do you mean UTC+1 or UTC+2 depending on DST?
If you're using .NET 3.5, use TimeZoneInfo to get an appropriate time zone, then use:
// Store this statically somewhere
TimeZoneInfo maltaTimeZone = TimeZoneInfo.FindSystemTimeZoneById("...");
DateTime utc = DateTime.UtcNow;
DateTime malta = TimeZoneInfo.ConvertTimeFromUtc(utc, maltaTimeZone );
You'll need to work out the system ID for the Malta time zone, but you can do that easily by running this code locally:
Console.WriteLine(TimeZoneInfo.Local.Id);
If you're not using .NET 3.5, you'll need to work out the daylight savings yourself. To be honest, the easiest way to do that is going to be a simple lookup table. Work out the DST changes for the next few years, then write a simple method to return the offset at a particular UTC time with that list hardcoded. You might just want a sorted List<DateTime> with the known changes in, and alternate between 1 and 2 hours until your date is after the last change:
// Be very careful when building this list, and make sure they're UTC times!
private static readonly IEnumerable<DateTime> DstChanges = ...;
static DateTime ConvertToLocalTime(DateTime utc)
{
int hours = 1; // Or 2, depending on the first entry in your list
foreach (DateTime dstChange in DstChanges)
{
if (utc < dstChange)
{
return DateTime.SpecifyKind(utc.AddHours(hours), DateTimeKind.Local);
}
hours = 3 - hours; // Alternate between 1 and 2
}
throw new ArgumentOutOfRangeException("I don't have enough DST data!");
}

Which Noda Time type should be used to calculate years/months/days between a specific date and today?

I'm building an application that needs to calculate things like 'how many weeks/months/years are there between two dates' and all my users are from a single time zone.
I wonder, is it better to use LocalDateTime for this (because I use only one time zone) or ZonedDateTime (to account for possible DST and offset changes)?
Also, am I right when doing
LocalDateTime.FromDateTime(DateTime.UtcNow)
For the first scenario, use the LocalDate type, along with the Period.Between method.
For the second scenario, you could construct a LocalDateTime like that, but then the resulting value would reflect the "UTC day" - which is an artificial construct.
Whenever you need "now":
Start from an IClock implementation, such as SystemClock.Instance.
Get an Instant by calling .Now on the clock.
Decide what time zone you want the "now" to reflect. Use .InZone on the instant to project to a ZonedDateTime in that zone.
From there, split off whatever component you need to satisfy the scenario you're using. For example, you could call .Date to get a LocalDate instance to use with the first scenario you asked about.
In general, try to avoid calling DateTime.Now or DateTime.UtcNow. Sure, there are places you could use that for short-cutting, but the API won't necessarily guide you to the right conclusion.
To summarize both scenarios:
// assuming you start with a DateTime, perhaps from a db.
DateTime dt = new DateTime(2016,1,1);
LocalDate ldt = LocalDateTime.FromDateTime(dt).Date;
DateTimeZone tz = DateTimeZoneProviders.Tzdb["America/New_York"];
ZonedDateTime now = SystemClock.Instance.Now.InZone(tz);
LocalDate today = now.Date;
Period period = Period.Between(today, ldt, PeriodUnits.YearMonthDay);
int years = (int) period.Years;
int months = (int) period.Months;
int days = (int) period.Days;
Also, note that in Noda Time 2.0 (currently in alpha), it gets a little bit simpler:
DateTime dt = new DateTime(2016, 1, 1);
LocalDate ldt = LocalDate.FromDateTime(dt); // 2.0
DateTimeZone tz = DateTimeZoneProviders.Tzdb["America/New_York"];
LocalDate today = SystemClock.Instance.InZone(tz).GetCurrentDate(); // 2.0
Period period = Period.Between(today, ldt, PeriodUnits.YearMonthDay);
int years = (int)period.Years;
int months = (int)period.Months;
int days = (int)period.Days;

Getting invalid result when using Noda Time c#

Assume Instant variable contains "4/8/2014 11:09:24 AM" when i pass this value to this method it gives me an output "4/8/2014 12:09:24 PM" i checked in some time zone calcluator plus in oracle i should be getting "4/8/2014 04:09:24 PM" or "4/8/2014 16:09:24 PM" depending on hour format. why is it not converting into proper time format?
public static string ConvertDateTimeToUserTimeZone()
{
DateTime dt = DateTime.Parse("4/8/2014 11:09:24 AM");
Instant now = Instant.FromDateTimeUtc(DateTime.SpecifyKind(dt,DateTimeKind.Utc));
DateTimeZone dtZone = DateTimeZoneProviders.Tzdb["Europe/London"];
ZonedDateTime zdt = now.InZone(dtZone);
return zdt.ToDateTimeOffset().ToString("G");
}
Reading through your comments, it would appear that you'd like to do the following:
Parse the input string
Assign it to the the US Eastern time zone ("America/New_York")
Convert it to the time zone for the UK ("Europe/London")
Format it as a string in the general format
I will show using Noda Time for all of these steps, rather than mixing in non-Noda mechanisms.
var generalPattern = LocalDateTimePattern.CreateWithCurrentCulture("G");
string inputString = "4/8/2014 11:09:24 AM";
string sourceTimeZone = "America/New_York";
string targetTimeZone = "Europe/London";
LocalDateTime ldt1 = generalPattern.Parse(inputString).Value;
DateTimeZone tz1 = DateTimeZoneProviders.Tzdb[sourceTimeZone];
ZonedDateTime zdt1 = ldt1.InZoneLeniently(tz1);
DateTimeZone tz2 = DateTimeZoneProviders.Tzdb[targetTimeZone];
ZonedDateTime zdt2 = zdt1.WithZone(tz2);
LocalDateTime ldt2 = zdt2.LocalDateTime;
string output = generalPattern.Format(ldt2);
Notice that I used CreateWithCurrentCulture when setting up the pattern. This assumes you mean to use whatever the current culture is for the machine the code is running on. If that's not the case, then you should set a specific culture instead. This is important when you realize that users in the US will use M/D/Y formatting, while users in the UK will use D/M/Y formatting. This applies to both dates regardless of time zone. (In other words, 4/8/2014 could be April 8th, or August 4th).
Also notice that I used InZoneLeniently when applying the source time zone. This has lenient behavior when it comes to ambiguous and invalid input values due to DST transitions. If you want different behavior, then you might instead use InZoneStrictly, or use InZone and provide your own algorithm.
And finally, it should be noted that I inferred from your comments that you were sourcing these from the US Eastern time zone, which would be either EDT or EST depending on what time of year it is. If you actually meant that the values were always EDT, even when EST is the norm, then you would do the following:
DateTimeZone tz1 = DateTimeZone.ForOffset(Offset.FromHours(-4));
The value you got back from the method is in fact correct.
Let's break it down using LINQPad:
void Main()
{
var t = new DateTime(2014, 8, 4, 11, 09, 24, DateTimeKind.Utc);
var i = Instant.FromDateTimeUtc(t);
var s = ConvertDateTimeToUserTimeZone(i);
s.Dump("User time zone value");
}
public static string ConvertDateTimeToUserTimeZone(Instant now)
{
DateTimeZone dtZone = DateTimeZoneProviders.Tzdb["Europe/London"];
dtZone.GetUtcOffset(now).Dump("TZ at instant");
ZonedDateTime zdt = now.InZone(dtZone);
return zdt.ToDateTimeOffset().ToString("G");
}
Output:
TZ at instant
+01
User time zone value
04.08.2014 12:09:24
The time zone at the point of that instant is thus UTC+1, which means that the value you gave the code gives you the correct value back, 12:09.
To get 16:09, you would need UTC+5, and Europe/London has never been that value.
So 12:09:24 is correct, your assumptions that it should be 16:09:24 is wrong.
You need to go back to the code and/or sites you used to get the "correct" value, the source of your confusion is there, not in this code.

How to represent the current UK time?

I'm facing an issue while converting dates between my server and client where both is running in Germany. The Regional settings on the client machines could be set to both UK or Germany.I recieve a date from the server which is CET format, and I need to represent this time on UI as UK time. For example a time recieved from server like say, 01/07/2010 01:00:00 should be represented on the UI as 01/07/2010 00:00:00. I have written a converter for this purpose, however while running it 'am getting a time difference of 2 hours.Below is the code, please can you help?
public class LocalToGmtConverter : IDateConverter
{
private readonly TimeZoneInfo timeZoneInfo;
public LocalToGmtConverter()
: this(TimeZoneInfo.Local)
{
}
public LocalToGmtConverter(TimeZoneInfo timeZoneInfo)
{
this.timeZoneInfo = timeZoneInfo;
}
public DateTime Convert(DateTime localDate)
{
var utcKind = DateTime.SpecifyKind(localDate, DateTimeKind.Utc);
return utcKind;
}
public DateTime ConvertBack(object fromServer)
{
DateTime serverDate = (DateTime)fromServer;
var utcOffset = timeZoneInfo.GetUtcOffset(serverDate);
var uiTime = serverDate- utcOffset;
return uiTime;
}
}
I think you're converting to UTC (instead of UK) time. Since there is still summer time in Central Europe (event if the temperatures say otherwise), the difference is +2 hours until October, 31st.
If you know that you're converting from Germany to UK (i.e. CEST to BST in summer and CET to GMT in winter), why you don't just subtract 1 hour?
If you want the time zone information for UK, you can construct it using
var britishZone = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
Then you could convert the date using
var newDate = TimeZoneInfo.ConvertTime(serverDate, TimeZoneInfo.Local, britishZone);
This is what I do:
var BritishZone = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
DateTime dt = DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Unspecified);
DateTime DateTimeInBritishLocal = TimeZoneInfo.ConvertTime(dt, TimeZoneInfo.Utc, BritishZone);
I needed to add the SpecifyKind part or the ConvertTime throws an exception
Use TimeZoneInfo.ConvertTime to convert original input timezone (CET) to target timezone (UK).
public static DateTime ConvertTime(
DateTime dateTime,
TimeZoneInfo sourceTimeZone,
TimeZoneInfo destinationTimeZone
)
Full guidance on MSDN here:
Converting Times Between Time Zones
Modified code sample from MSDN:
DateTime ceTime = new DateTime(2007, 02, 01, 08, 00, 00);
try
{
TimeZoneInfo ceZone = TimeZoneInfo.FindSystemTimeZoneById("Central Europe Standard Time");
TimeZoneInfo gmtZone = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
Console.WriteLine("{0} {1} is {2} GMT time.",
ceTime,
ceZone.IsDaylightSavingTime(ceTime) ? ceZone.DaylightName : ceZone.StandardName,
TimeZoneInfo.ConvertTime(ceTime, ceZone, gmtZone));
}
catch (TimeZoneNotFoundException)
{
Console.WriteLine("The registry does not define the required timezones.");
}
catch (InvalidTimeZoneException)
{
Console.WriteLine("Registry data on the required timezones has been corrupted.");
}
The better approach to deal with local times is store them in unified representation such as UTC.
So you can convert all input times to UTC (via .ToUniversalTime()), and store (or transmit) its value. When you need to show it just convert back by using .ToLocalTime().
So you avoid rquirements to know which time zone was original value and can easily show stored value in different timezones.
Also you can avoid incoming troubles where you have to write specific logic for processing time in next timezone trying to figure out how to convert them amongs all available.
I realize the question is for C#, but if all you want to do is a single conversion you can do this from the PowerShell command line:
$d = [DateTime]::Parse("04/02/2014 17:00:00")
$gmt = [TimeZoneInfo]::FindSystemTimeZoneById("GMT Standard Time");
[TimeZoneInfo]::ConvertTime($d, $gmt, [TimeZoneInfo]::Local)
This script would convert 17:00 UK time into your local time zone.
For me, that would be CST. It's interesting to note that if I had set the date to 03/27/2014, the result would be different because the UK daylight saving time kicks in on different dates that the US.

Categories