DateTime change timezone without converting - c#

I am passed a DateTime object and a string timezone variable separately. Is there a way to cast this DateTime as the timezone in the string without "converting"(changing the actual time)?

If your time zone string is very specifically one of the system-defined strings, you can definitely do this. The easiest way is to make the assumption that the DateTime passed in is already in UTC, and that the string passed in is one of the Id values listed in TimeZoneInfo.GetSystemTimeZones(). Your function would look something like this:
public DateTime ConvertUtcDateTime(DateTime utcDateTime, string timeZoneId)
{
var tz = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId);
var convertedDateTime = TimeZoneInfo.ConvertTimeFromUtc(utcDateTime, tz);
return convertedDateTime;
}
This would create a "new" DateTime value, and your original utcDateTime value would be unchanged. If you can't assume that the DateTime value is UTC, you'll have to require both the source and destination time zones and your function would change slightly:
public DateTime ConvertDateTime(DateTime currentDateTime, string sourceTimeZoneId, string destinationTimeZoneId)
{
var tzSource = TimeZoneInfo.FindSystemTimeZoneById(sourceTimeZoneId);
var tzDestination = TimeZoneInfo.FindSystemTimeZoneById(destinationTimeZoneId);
var convertedDateTime = TimeZoneInfo.ConvertTime(currentDateTime, tzSource, tzDestination);
return convertedDateTime;
}
Keep in mind that in both of these functions, you need to test if the time zone returned by TimeZoneInfo.FindSystemTimeZoneById is not null. If it is, your input string was bad and you will need to handle that. An exception would likely work well there, but I don't know your needs.
Edit:
As an afterthought, if your strings aren't exactly the same as the Id values from TimeZoneInfo.GetSystemTimeZones(), but they do kind of match up one-to-one, you could make some kind of Dictionary<string, string> to map your time zone strings to the system's Id values and run from that. That's a good bit of hard-coding, though, but sometimes you need something like this if your inputs can't be changed.

DateTime itself is immutable in C# so you can't really change it like you are wanting to.
Your best bet would be to store the object as a UTC DateTime and then based on the timezone string you are passed, add or subtract time from that to "correct" the UTC time stamp.

Use UTC Time.
https://time.is/UTC
To implelent it, follow this code.
var currentDate = DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc);

Related

Convert timestamp from any timezone to UTC, and optionally without DST

We have timestamps from CSV files that look like this:
06-02-2018 15:04:21
We do not control the delivery of them, nor the timezone.
What we do know so far is that we have seen so far is this:
Standard Romance Time
Standard Romance Time, but without DST compensation.
UTC
From this, we gather that we need an engine that can take any timestamp (written in patterns that DateTime.ParseExact understands), belong to any timezone, optionally ignore DST, and then convert it to UTC (the format we internally use).
I was hoping this could do it:
public DateTime ConvertToUtc(string fromTimestamp, DataReaderConfiguration dataReaderConfiguration)
{
DateTime readTime = DateTime.ParseExact(fromTimestamp, dataReaderConfiguration.TimestampFormat,
CultureInfo.InvariantCulture, DateTimeStyles.None);
DateTime utcTime = TimeZoneInfo.ConvertTimeToUtc(readTime,
TimeZoneInfo.FindSystemTimeZoneById(dataReaderConfiguration.TimeZone));
return utcTime;
}
but C# does not have timezones defined without DST (except for UTC).
So we need to expand on the method to allow for a timezone conversion without DST.
You can create this function yourself, by leveraging the TimeZoneInfo.BaseUtcOffset property and a DateTimeOffset.
public static DateTime ConvertTimeToUtc(DateTime dt, TimeZoneInfo tz, bool useDST)
{
if (useDST)
{
// the normal way (converts using the time in effect - standard or daylight)
return TimeZoneInfo.ConvertTimeToUtc(dt, tz);
}
// the way to convert with just the standard time (even when DST is in effect)
var dto = new DateTimeOffset(dt, tz.BaseUtcOffset);
return dto.UtcDateTime;
}
Then just pass whatever property from your DataReaderConfiguration object that indicates whether you want to use DST (or negate it if necessary).
Also note that this gives the standard time based on the current set of rules. If you are dealing with historical dates where the time zone's standard time has changed, things get a bit more complex. You'd have to figure out which adjustment rules were in place at the time, etc. You might even find edge cases where the Windows time zone data is insufficient.

C# DateTime timezone getting lost in parsing

I am storing datetime in a stringified JSON object in Redis cache like so:
"{\"email\":\"myemail#testdomain.com\", \"expiry\": \"2018-03-19T23:00:03.0658822+00:00\"}"
In C#, when I query this data from Redis and convert it to string, it loses its timezone value and gets automatically stripped off of its timezone information.
RedisValue cookie = GetRedisDatabase().StringGet("sessionhash");
JObject cookieValue = JObject.Parse(cookie.ToString());
var email = JObject.Parse(cookie.ToString())["email"];
var expiry = JObject.Parse(cookie.ToString())["expiry"].ToString();
The "expiry" string above only contains "2018/03/19 23:00:03". It seems like C# is automatically detecting the string to be of datetime format, and is stripping off timezone information from it.
How can I ensure the "expiry" string is "2018-03-19T23:00:03.0658822+00:00"?
DateTime does not know about timezones. Instead it has a DateTimeKind property which tells you if the time is machine local, UTC, or unknown. Methods ToLocalTime will convert a known UTC or unknown time to local time, and do nothing of already local.
You'll need to use something else that keeps the timezone information, i believe DateTimeOffset can track a time with a variable offset, but not the timezone.
NodaTime is a library which understands timezones.
internal class Program
{
private static void Main()
{
string expiry = "2018-03-19T23:00:03.0658822+00:00";
DateTime parsedExpiry = DateTime.Parse(expiry);
Console.WriteLine(parsedExpiry.ToString());
Console.ReadKey();
}
}
This code converts 19/3/2018 23:00 into 20/3/2018 7:00.
The reason it does this is because, as per above answers, DateTime doesn't hold on to any TimeZone information. The only information you have is DateTime.Kind, which in the case of my code, outputs Local. I can use parsedExpirey.ToUniversalTime() to get UTC.
You could do some extra parsing on the string representation and use the TimeZoneInfo class to maintain the timezone, but you'll likely need an extra column / storage space to store that info. You can use the Convert option, but then you'll be storing DateTimes in all different timezones, you'd be better off using ToUniversalTime and storing it all in UTC (best practice), then converting it to Local time for presentation to the user (or leave it UTC, depending on the application).
Your final ToString asked for the time without TZ info. Do this
RedisValue cookie = GetRedisDatabase().StringGet("sessionhash");
JObject cookieValue = JObject.Parse(cookie.ToString());
var email = JObject.Parse(cookie.ToString())["email"];
var expiry = JObject.Parse(cookie.ToString())["expiry"].ToString("O");
I have a few general rules regarding handling DateTimes:
Always store, retrieve and transmit the value in UTC. Windows is pretty good at translating any UTC value to whatever the current user picked as his favourite timezone. You do not want to deal with Timezones if you can avoid it at all.
Never store, retrieve and transmit the value as string.
In case 3 can not work, at least pick a fixed culture and string encoding at all endpoints. You do not want to add those to your worries.
In rare cases (Callendar Apps) it might be beneficial to store the "originally saved timezone".
Unfortunately you cannot determine the time zone from an ISO date/time string. You can only determine the offset. The time zone names and codes are not unique-- for example, "Arabia Standard Time" has an offset of UTC+03, but has the code "AST," which collides with "Atlantic Standard Time" (offset UTC-04). So while you can map in one direction, you can't reliably map in the other.
That being said, getting the offset isn't so bad if you use a DateTimeOffset instead of DateTime. If the field isn't a DateTimeOffset in your object model, you can create a temporary anonymous type as a template and get it that way. Example:
public static void Main()
{
var input = "{\"email\":\"myemail#testdomain.com\", \"expiry\": \"2018-03-19T23:00:03.0658822+01:00\"}";
var template = new { email = "", expiry = DateTimeOffset.Now };
var result = JsonConvert.DeserializeAnonymousType(input, template);
Console.WriteLine("DateTime (GMT): {0:R}\r\nOffset from GMT: {1}", result.expiry, result.expiry.Offset);
}
Output:
DateTime (GMT): Mon, 19 Mar 2018 22:00:03 GMT
Offset from GMT: 01:00:00
Code on DotNetFiddle

NodaTime Invalid DateTime.Kind for Instant.FromDateTimeUtc

I'm trying to get ahold of this timezone issue we are having. We would like to store all DateTimes in UTC, and then convert the DateTime to the user's timezone.
We decided to use NodaTime for this, as it seems like the right approach. However, we are experiencing an issue with it.
This is how we convert the DateTime to UTC (note - I hardcoded the usersTimeZone for now):
public static DateTime ConvertLocaltoUTC(this DateTime dateTime)
{
LocalDateTime localDateTime = LocalDateTime.FromDateTime(dateTime);
IDateTimeZoneProvider timeZoneProvider = DateTimeZoneProviders.Tzdb;
var usersTimezoneId = "Europe/Copenhagen";
var usersTimezone = timeZoneProvider[usersTimezoneId];
var zonedDbDateTime = usersTimezone.AtLeniently(localDateTime);
var returnThis = zonedDbDateTime.ToDateTimeUtc();
return zonedDbDateTime.ToDateTimeUtc();
}
And here is how we convert it back:
public static DateTime ConvertUTCtoLocal(this DateTime dateTime)
{
Instant instant = Instant.FromDateTimeUtc(dateTime);
IDateTimeZoneProvider timeZoneProvider = DateTimeZoneProviders.Tzdb;
var usersTimezoneId = "Europe/Copenhagen"; //just an example
var usersTimezone = timeZoneProvider[usersTimezoneId];
var usersZonedDateTime = instant.InZone(usersTimezone);
return usersZonedDateTime.ToDateTimeUnspecified();
}
However, when we convert it back to local time, it throws an exception:
Argument Exception: Invalid DateTime.Kind for Instant.FromDateTimeUtc
at the first line of ConvertUTCtoLocal().
An example of the DateTime could be: "9/18/2017 5:28:46 PM" - yes this has been through the ConvertLocalToUTC method.
Am I providing an incorrect format? What am I doing wrong here?
The exception you show:
Argument Exception: Invalid DateTime.Kind for Instant.FromDateTimeUtc
Is thrown from this code:
Instant instant = Instant.FromDateTimeUtc(dateTime);
It means that dateTime.Kind needs to be DateTimeKind.Utc to be convertible to an Instant, and for whatever reason it is not.
If you look at the result of your ConvertLocaltoUTC method, you'll find that it does have .Kind == DateTimeKind.Utc.
So, the problem lies elsewhere in your code, wherever you created the dateTime you're passing in to ConvertUTCtoLocal.
You may find the solution could be any of the following:
You might need to call DateTime.SpecifyKind to set the kind to UTC, but be careful to only do this when your values are actually UTC and it's just not setting the kind. For example, use this when loading a UTC-based DateTime from a database.
You might need to call .ToUniversalTime(), but be careful to only do this if the machine's local time zone is relevant to your situation. For example, do this in desktop or mobile apps where a UI control is picking a date, but you meant it to mean UTC instead of local time.
You might need to change how you parse strings into DateTime values, such as by passing DateTimeStyles.RoundTripKind to a DateTime.Parse call (or any of its variants. For example, do this if you are reading data from text, csv, etc.
If you want to avoid having to decide, don't write functions that take DateTime as input or give DateTime as output. Instead, use DateTimeOffset, or use Noda-Time types like Instant, LocalDateTime, etc. as early as possible.
This is what worked for me:
Instant instant = Instant.FromDateTimeUtc(DateTime.SpecifyKind(datetime, DateTimeKind.Utc));

.NET Save DateTime and completely ignore timezone

Maybe the answer is so obvious, I'm not seeing it, but I have a question that I'll risk asking anyway.
I want to allow users of a .NET Web application to enter a date/time, store it in an Oracle database, and no matter which timezone they are in, it always displays as the raw, original version as typed in by the user. So if one user in California enters 2PM and another in Maryland enters 2PM, they would both show as 2PM to a user in Japan. Two types of clients are possible, a web user and a windows client user (connected via web service).
Think of it like I want to completely break all timezone smarts that most applications worry about.
I don't want to save as a string though. I want a datetime that I can add/remove hours and minutes from.
Edit:
This was basically the exact problem I was having.
You should always store DateTime in UTC format (universal). When you display it you can choose which ever timezone you wish, in your case this can be fixed for all users, rather than based on location.
// when recording date time
DateTime utcDateTime = DateTime.UtcNow;
// parse DateTime back out from string
DateTime utcDateTime = DateTime.SpecifyKind(DateTime.Parse(dateStr),
DateTimeKind.Utc);
// localized DateTime
DateTime localDate = utcDateTime.ToLocalTime();
// fixed DateTime based on timezone
string timeZoneKey = "New Zealand Standard Time";
TimeZoneInfo timeZone = TimeZoneInfo.FindSystemTimeZoneById(timeZoneKey);
DateTime nzlocalDate = TimeZoneInfo.ConvertTimeFromUtc(utcDateTime, timeZone);
This takes into account things like day-light savings which can trip you up if start saving localized dates.
If you just store the DateTime as entered/picked by the user then it will be stored as just that. There will be time zone informations stored in it, but you are in control of what you do with it.
For example, when you want to output it to the screen/file etc. you will need to format it to a string. If you use this ToString overload with CultureInfo.InvariantCulture then this should ignore the current culture and output the date as is:
DateTime date = DateTime.Now;
string output = date.ToString("d", CultureInfo.InvariantCulture);
Other operations will require different handling, but you will need to specify what happens in each case.
I had a time sensitive fix and the SpecifyKind did not allow me to make proper TimeSpan comparisons. In my situation, the quickest solution was to simply remove the Hours, Minutes, and Seconds from the time, as the DateTime I was comparing with was at midnight (00:00:00),
DateTime eventDateTime = (DateTime)row["event_date"];
eventDateTime = eventDateTime.AddHours(-1 * eventDateTime.Hour).AddMinutes(-1 * eventDateTime.Minute).AddSeconds(-1 * eventDateTime.Second);
If you really want discard time zone:
public static DateTime WithoutTimeZone(this DateTime dateTime)
{
if (dateTime.Kind != DateTimeKind.Unspecified)
return new DateTime(dateTime.Ticks, DateTimeKind.Unspecified);
return dateTime;
}

Function to return only date

I have made a function to convert a textbox value to date.
I want to store the converted date in a datetime field in my business object with only date and not time like in format(yyyy-MM-dd)
My function is returning date along with time
public static DateTime ExtractDate(string myDate)
{
DateTime result = DateTime.MinValue;
if (myDate != null)
{
try
{
result=DateTime.Parse(myDate, new System.Globalization.CultureInfo("en-CA", true), System.Globalization.DateTimeStyles.AdjustToUniversal).ToString("yyyy-MM-dd");
}
catch (Exception)
{
throw new ApplicationException("DateTime conversion error");
}
}
return (result.Date);
}
DateTime itself always includes a time, in the case when you're setting it equal to a 'date' then the time will be 00:00:00. When it comes to displaying the string you'll need to use a format string that includes just the date part.
Just use:
result = DateTime.Parse(...).Date;
Therre's no need to convert the date/time back to a string first. The resulting DateTime will just be midnight on the relevant date.
I see that you're adjusting to universal time - you need to be aware that that may change the date. Dates are inherently local - i.e. my August 25th may well start at a different time to yours due to time zones. Alternatively, you could parse it as if it were in UTC to start with and treat it that way. You just need to be careful with what you're doing - you could run into problems where midnight doesn't exist on some days in some time zones. (Been there, done that...)
I would also suggest using DateTime.TryParseExact and specifying the input format. In particular, if you only expect users to enter dates, then specify appropriate date formats. Using TryParseExact instead of ParseExact means you don't have to catch an exception to notice that the user hasn't entered a valid date.
EDIT: Just to clarify: .NET doesn't have a type representing "just a date". Noda Time does, but that's not ready for production yet :( Normally you'd just use a DateTime and ignore the time part.
Date property returns a DateTime with time set to "00.00:00". You can not remove time from a DateTime, you can avoid to show it in the GUI using string.Format("d", yourDate).
I would change the method and return a string instead of a DateTime, because there is always time attached (therefor also the name DateTIME ;-))
If you return a string, you can do something like:
return DateTime-object.ToString("yyyy/MM/dd");
Good luck!

Categories

Resources