NodaTime conversions (Part 2). How to? - asp.net

Following my first post:
DateTime conversions using NodaTime on ASP.Net MVC 3 Razor website. How to?
I'm struggling to find an easy way to convert date/time between local and UTC (both ways), using NodaTime.
The current picture is:
I have the date/time saved as UTC in the database.
When displaying it to the user, I should consider the local time zone and convert it accordingly.
When the user provides date/time as a filter, I need to convert it back to UTC before sending to the SQL query.
What I have so far:
Extension to convert from UTC to local (this part is working fine):
public static DateTime UTCtoLocal(this DateTime dateTime)
{
IDateTimeZoneProvider timeZoneProvider = DateTimeZoneProviders.Tzdb;
var utcTimeZone = timeZoneProvider["UTC"];
var dateTimeFromDb = new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond);
var zonedDbDateTime = utcTimeZone.AtLeniently(LocalDateTime.FromDateTime(dateTimeFromDb));
var usersTimezoneId = "Europe/London"; //just an example
var usersTimezone = timeZoneProvider[usersTimezoneId];
var usersZonedDateTime = zonedDbDateTime.WithZone(usersTimezone);
return usersZonedDateTime.ToDateTimeUnspecified();
}
Extension to convert from local back to UTC (this part is the problem):
public static DateTime LocaltoUTC(this DateTime dateTime)
{
IDateTimeZoneProvider timeZoneProvider = DateTimeZoneProviders.Tzdb;
var usersTimezoneId = "Europe/London";
var usersTimezone = timeZoneProvider[usersTimezoneId];
var dateTimeFromDb = new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond);
var zonedDbDateTime = usersTimezone.AtLeniently(LocalDateTime.FromDateTime(dateTimeFromDb));
var utcTimezoneId = "UTC";
var utcTimezone = timeZoneProvider[utcTimezoneId];
var utcZonedDateTime = zonedDbDateTime.WithZone(utcTimezone);
return utcZonedDateTime.ToDateTimeUtc();
}
What am I doing wrong here?

Your UTCToLocal looks like it's doing more work than it needs to, to be honest.
It should just be:
// Note: the DateTime here must have a "Kind" of Utc.
public static DateTime UTCtoLocal(this DateTime dateTime)
{
Instant instant = Instant.FromDateTimeUtc(dateTime);
IDateTimeZoneProvider timeZoneProvider = DateTimeZoneProviders.Tzdb;
var usersTimezoneId = "Europe/London"; //just an example
var usersTimezone = timeZoneProvider[usersTimezoneId];
var usersZonedDateTime = instant.InZone(usersTimezone);
return usersZonedDateTime.ToDateTimeUnspecified();
}
Similarly your LocalToUTC should be along these lines:
// The DateTime here should have a "Kind" of Unspecified
public static DateTime LocaltoUTC(this DateTime dateTime)
{
LocalDateTime localDateTime = LocalDateTime.FromDateTime(dateTime);
IDateTimeZoneProvider timeZoneProvider = DateTimeZoneProviders.Tzdb;
var usersTimezoneId = "Europe/London";
var usersTimezone = timeZoneProvider[usersTimezoneId];
var zonedDbDateTime = usersTimezone.AtLeniently(localDateTime);
return zonedDbDateTime.ToDateTimeUtc();
}
You don't need to convert it to a different time zone: ZonedDateTime knows what the instant is, and ToDateTimeUtc will do the right thing. Note that there's no real dateTimeFromDb here, because if you're converting from an unspecified DateTime, that's presumably from the user...

Related

Why does JsonConvert change time of DateTimes with DateTimeKind.Unspecified when using DateTimeStyles.AssumeUniversal?

I'm building a web API and am having trouble with the JSON serialization of DateTimes. After doing some tests I can only conclude that the behavior of Newtonsoft.Json.JsonConvert and/or the Newtonsoft IsoDateTimeConverter is not what I expected.
Consider this:
// Arrange
var noonUtc = new DateTime(2016, 05, 12, 12, 0, 0, DateTimeKind.Utc);
var noon = new DateTime(2016, 05, 12, 12, 0, 0, DateTimeKind.Unspecified);
var settings = new JsonSerializerSettings();
settings.Converters.Add(new IsoDateTimeConverter
{
Culture = CultureInfo.InvariantCulture,
DateTimeStyles = DateTimeStyles.AdjustToUniversal
});
// Act
var utcJson = JsonConvert.SerializeObject(noonUtc, settings); // "\"2016-05-12T12:00:00Z\""
var json = JsonConvert.SerializeObject(noon, settings); // "\"2016-05-12T10:00:00Z\""
... // Assertions
Okay, so the time for the DateTime with DateTimeKind.Unspecified has been adjusted from 12 o'clock to 10 o'clock. I'm in Stockholm which is currently two hours ahead of UTC, so fair enough.
However, let's change the serializer settings to use DateTimeStyles.AssumeUniversal, like so:
settings.Converters.Add(new IsoDateTimeConverter
{
Culture = CultureInfo.InvariantCulture,
DateTimeStyles = DateTimeStyles.AssumeUniversal
});
This results in the exact same strings and thus also adjusts the DateTime with DateTimeKind.Unspecified by two hours! Should it not assume the date time was already UTC time and leave the time as it was? What am I missing here?
I don't think you're missing anything; this looks like it might be a bug in the IsoDateTimeConverter. Here is the relevant code from the source:
if ((_dateTimeStyles & DateTimeStyles.AdjustToUniversal) == DateTimeStyles.AdjustToUniversal
|| (_dateTimeStyles & DateTimeStyles.AssumeUniversal) == DateTimeStyles.AssumeUniversal)
{
dateTime = dateTime.ToUniversalTime();
}
As you can see, it only looks at whether _dateTimeStyles is set to AdjustToUniversal or AssumeUniversal before calling ToUniversalTime(); it never checks the date's Kind property.
And the documentation for DateTime.ToUniversalTime() says this:
Starting with the .NET Framework version 2.0, the value returned by the ToUniversalTime method is determined by the Kind property of the current DateTime object. The following table describes the possible results.
Kind | Results
----------- | ----------------------------------------------------------
Utc | No conversion is performed.
Local | The current DateTime object is converted to UTC.
Unspecified | The current DateTime object is assumed to be a local time,
| and the conversion is performed as if Kind were Local.
So yeah, it looks like the converter should definitely not be calling ToUniversalTime in this situation. You might want to report an issue.
For now, you can work around this issue by implementing a replacement converter (derived from the original) with the correct behavior. This is probably closer to what you would want:
public class CorrectedIsoDateTimeConverter : IsoDateTimeConverter
{
private const string DefaultDateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFFK";
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is DateTime)
{
DateTime dateTime = (DateTime)value;
if (dateTime.Kind == DateTimeKind.Unspecified)
{
if (DateTimeStyles.HasFlag(DateTimeStyles.AssumeUniversal))
{
dateTime = DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
}
else if (DateTimeStyles.HasFlag(DateTimeStyles.AssumeLocal))
{
dateTime = DateTime.SpecifyKind(dateTime, DateTimeKind.Local);
}
}
if (DateTimeStyles.HasFlag(DateTimeStyles.AdjustToUniversal))
{
dateTime = dateTime.ToUniversalTime();
}
string format = string.IsNullOrEmpty(DateTimeFormat) ? DefaultDateTimeFormat : DateTimeFormat;
writer.WriteValue(dateTime.ToString(format, Culture));
}
else
{
base.WriteJson(writer, value, serializer);
}
}
}

ServiceStack.Text in .NET not deserializing nested datetime

I am talking to a web service from Xamarin. It is sending dates down over the wire in JSON, in ISO-8601 format, like this:
"2016-05-13T16:02:47.480-07:00"
If I try to deserialize just a single date, it works fine. But when the DateTime is a member of class, it does not work. No error is given. Here is some example code:
using ServiceStack.Text;
class TestDate {
public DateTime testDate;
}
void Test() {
JsConfig.DateHandler = JsonDateHandler.ISO8601;
// this works just fine:
String dateString = "2016-05-13T16:02:47.480-07:00";
DateTime theDate = dateString.FromJson<DateTime> ();
// this does not deserialize
String testStr = "{\"testDate\":\"2016-05-13T16:02:51.923-07:00\"}";
TestDate tester = testStr.FromJson<TestDate> ();
}
Any ideas? I don't see any error, just get a new TestDate object without the date being set.
ServiceStack Text Serializer does serialize as expected:
JsConfig.DateHandler = ServiceStack.Text.DateHandler.ISO8601;
var dto = new TestDate { testDate = new DateTime(2016, 05, 13, 16, 02, 47, 480) };
var json = dto.ToJson();
json.Print(); //= {"testDate":"2016-05-13T16:02:47.4800000"}
and deserializes correctly:
var fromJson = json.FromJson<TestDate>();
Assert.That(fromJson.testDate, Is.EqualTo(dto.testDate)); //= true

Date giving exception in US server

I am getting a date string from the javascript and converting that to Datetime and save that to the database.
But in the Indian server my code working fine. But when I upload my code to US based server it's giving exception. Is there any common way to make my code runnable to all the server.
My code is like below
[WebMethod(EnableSession = true)]
public static bool submitDate( string date ) // format is dd-mm-yyyy 20-01-2011
{
DateTime DOBdate = DateTime.Now;
double age = 0.0;
if (DateTime.TryParse(date , out DOBdate))
{
age = (DateTime.Now - DOBdate).Days / 365;
}
dbcmd.Parameters.Add("#DateOfBirth", SqlDbType.DateTime).Value = Convert.ToDateTime(DOBdate);
}
Please help me.
the exception which is showing is
SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM.
You should use TryParseExact, I guess the default datetime format of the server is not dd-mm-yyyy. And you should handle the if of the parse function:
public static bool submitDate( string date ) // format is dd-mm-yyyy 20-01-2011
{
DateTime DOBdate = DateTime.Now;
double age = 0.0;
if (DateTime.TryParseExact(date , {"dd-MM-yyyy"},
null,
DateTimeStyles.None,
out DOBdate))
{
age = (DateTime.Now - DOBdate).Days / 365;
}
else
{
// Handle this case!
}
dbcmd.Parameters.Add("#DateOfBirth", SqlDbType.DateTime).Value = Convert.ToDateTime(DOBdate);
}
BTW your age function is not a realy good indication of the age. The older your person, the more faulty it gets.

How to work with several fields in DateTime?

public DateTime EnterDeparture()
{
Console.WriteLine("Enter Year:");
return new DateTime().AddYears(int.Parse(Console.ReadLine()));
}
// This will return new DateTime(Without assigned Year) Cause DateTime is value type.
public DateTime EnterDeparture()
{
DateTime EnterDeparture = new DateTime();
Console.WriteLine("Enter Year:");
EnterDeparture.AddYears(int.Parse(Console.ReadLine()));
return EnterDeparture;
}
How to work with several fields in DateTime ? (Year,Days for example) Default constructors aren't suitable.
The DateTime.AddXXX methods return new DateTime instances, the existing struct does not change. Since each method returns a new instance, you can chain the method calls together. At the very least, you want to capture each return value into a variable. For example:
DateTime myDate = DateTime.Today;
DateTime tomorrowAtNoon = myDate.AddDays(1).AddHours(12);
You could have also written it like
DateTime tomorrow = myDate.AddDays(1);
DateTime tomorrowAtNoon = tomorrow.AddHours(12);
Follow?

Linq2EF: Return DateTime with a TimeZone Offset

Our database has all times stored as UTC, and we know the user's current timezone, so want to return it relative to that. So we want to incorporate the offset in a LINQ projection as so:
var users = from u in this.Context.Users
select new UserWithCorrectedDate
{
Id = u.Id,
FirstName = u.FirstName,
LastName = u.LastName,
RegistrationDate = u.RegistrationDate.Value.AddHours(-5)
};
Of course, Linq2EF cannot convert "AddHours" into a canonical function. Is there another way to do this?
UPDATE:
Another thought, if the timezone offset was stored in the database as another column, would there be a way to have the DB perform the calculation (date + offset)?
Linq to Entities supports SqlFunctions. If you know the offset number of hours you could do something like this:
var users = from u in this.Context.Users
select new UserWithCorrectedDate
{
Id = u.Id,
FirstName = u.FirstName,
LastName = u.LastName,
RegistrationDate = SqlFunctions.DateAdd("hour", -5, u.RegistrationDate.Value)
};
If you want to be more precise by using the Timezone function, you could do something like this (this is a bit of a workaround):
public static DateTime UtcToNewYorkTime(DateTime utcDateTime)
{
TimeZoneInfo tzi = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
DateTime converted = TimeZoneInfo.ConvertTimeFromUtc(utcDateTime, tzi);
return converted;
}
And then in your POCO UserWithCorrectedDate object, have a get only property for the Timezoned date ie.
public class UserWithCorrectDate
{
public DateTime UTCDate {get;set;}
public DateTime NYDate
{
get
{
return Utilities.UtcToNewYorkTime(this.UTCDate);
}
}
}
The quick and dirty way to do this is to convert to a list, and just linq to object to get it done:
from u in this.Context.Users.ToList()
select new { ... }

Resources