i've the following unit test, which fails on the machine of one of our developers (He get some ticks in the result variable, while the datetime variable is at zero ticks), but runs well on every other machine.
[TestMethod]
public void DateTimeStringDateTimeMinCurrentCultureToNullableDateTimeSuccessTest()
{
var dateTime = new DateTime(1, 1, 1);
string value = dateTime.ToString();
var result = value.ToNullableDateTime();
Assert.AreEqual(dateTime, result);
}
Here's the used extension method:
/// <summary>
/// Converts a string to a nullable DateTime. If the string is a invalid dateTime returns null.
/// Uses the current culture.
/// </summary>
public static DateTime? ToNullableDateTime(this string s)
{
//Don't use CultureInfo.CurrentCulture to override user changes of the cultureinfo.
return s.ToNullableDateTime(CultureInfo.GetCultureInfo(CultureInfo.CurrentCulture.Name));
}
/// <summary>
/// Converts a string to a nullable DateTime. If the string is a invalid dateTime returns null.
/// </summary>
public static DateTime? ToNullableDateTime(this string s, CultureInfo cultureInfo)
{
if (String.IsNullOrEmpty(s)) return null;
DateTime i;
if (DateTime.TryParse(s, cultureInfo, DateTimeStyles.None, out i)) return i;
return null;
}
I think this might be related to some windows date time settings he uses. In theory the ToNullableDateTime(string) should create a new culture info, which is user machine neutral. GetCultureInfo should call new CultureInfo(name, false). The only thing I could come up with, is that there's a cached culture info, which contains some kind of user machine related modified datetime in the s_NameCachedCultures which is checked in the GetCultureInfoHelper (http://referencesource.microsoft.com/#mscorlib/system/globalization/cultureinfo.cs,5fe58d4ecbba7689).
I know, that the CreateSpecificCulture method can return a user modified datetime, if you call it with the same culture than the windows machine. But I always thought, the GetDateTime would return a unmodified datetime in any case.
So there are two questions:
Could it be possible, that a modified CultureInfo be stored in the internal cache?
If so, is the only way to get a unmodified CultureInfo by manually calling new CultrueInfo("xy", false)?
When you do
string value = dateTime.ToString();
this will use CultureInfo.CurrentCulture. You then try and parse this string using...
CultureInfo.GetCultureInfo(CultureInfo.CurrentCulture.Name);
So you are specifically using a culture to parse the string that is different to the one you've created the string with. Of course there will be instances where this doesn't pass.
I'd suggest the issue is on most peoples machines
Assert.AreEqual(CultureInfo.CurrentCulture, CultureInfo.GetCultureInfo(CultureInfo.CurrentCulture.Name));
would pass but on the machine in question it doesn't and neither do your strings.
I'd suggest you probably want to use CultureInfo.InvariantCulture. So...
[TestMethod]
public void DateTimeStringDateTimeMinCurrentCultureToNullableDateTimeSuccessTest()
{
DateTime dateTime = new DateTime(1, 1, 1);
string value = dateTime.ToStringInvariant();
var result = value.ToNullableDateTime();
Assert.AreEqual(dateTime, result);
}
public static string ToStringInvariant(this DateTime? date)
{
if (date.HasValue)
return date.Value.ToStringInvariant();
return null;
}
public static string ToStringInvariant(this DateTime date)
{
return date.ToString(CultureInfo.InvariantCulture);
}
/// <summary>
/// Converts a string to a nullable DateTime. If the string is a invalid dateTime returns null.
/// Uses the current culture.
/// </summary>
public static DateTime? ToNullableDateTime(this string s)
{
//Don't use CultureInfo.CurrentCulture to override user changes of the cultureinfo.
return s.ToNullableDateTime(CultureInfo.InvariantCulture);
}
/// <summary>
/// Converts a string to a nullable DateTime. If the string is a invalid dateTime returns null.
/// </summary>
public static DateTime? ToNullableDateTime(this string s, CultureInfo cultureInfo)
{
if (String.IsNullOrEmpty(s)) return null;
DateTime i;
if (DateTime.TryParse(s, cultureInfo, DateTimeStyles.None, out i)) return i;
return null;
}
I've overseen a little detail. The problem wasn't related to the GetCultureInfo returning a modified datetime, the problem already startet with the dateTime.ToString(); which uses the Thread.CurrentThread.CultureInfo (which equals to the windows culture, which can be modified). The developers machine translates the DateTime(1, 1, 1) to 01.01.01 00:00:00. On any other machine the output is 01.01.0001 00:00:00. So an abbreviated version of the year is used, which seems to be interpreted as the "year 1 of the current century" in the TryParse method (quick sidenode: So it's not possible for users older than 100 years to tell their birthdate with the abbreviated year version).
It's actually an interesting behaviour..
for (int i = 0; i < 100; i++)
{
var year = 1900 + i;
DateTime date = new DateTime(year, 1, 1);
var parsedDate = DateTime.ParseExact(date.ToString("yy"), "yy", CultureInfo.InvariantCulture);
Console.WriteLine("{0}: {1}", year, parsedDate.ToString("yyyy"));
}
leads to:
[...]
1928: 2028
1929: 2029
1930: 1930
1931: 1931
[...]
So, the abbreviated birth date for anyone older 86 would lead to a date in the feature.. but this moves away from the questions context ..
I think there's no real solution the actual problem (beside telling developers to not use local CultureInfos outside of UI strings and never use abbreviated dates for inputs).
We don't have such problems in our code itself, because we use CultureInfo.InvariantCulture for all internal stuff. I just think about the unit test .. I think the test itself is correct. It shows, that the function actually doesn't work correct with abbreviated dates. I will change the behaviour of the ToNullableDateTime() to throw an exception, if a datetime string with a abbreviated year is recognized.
I guess, most probably the CultureInfo is in a cache. This would be very easy to check in a unit test (AreSame).
Instead of DateTime(1, 1, 1) use DateTime(1, 2, 3) and check the ghost ticks. Are they found in the string somewhere? Did you try the same culture (hard-coded culture name) on two machines which had different results?
Also check if the difference is the offset to UTC in your culture.
You probably can use TryParseExact.
Related
Having an issue whereby the date I wish to save is changing from the onscreen selected date if the users selects a timezone that is ahead x number of hours.
E.g. they choose UTC+2 Athens and date of 25/02/2016 from the calendar pop-up, then date recorded will be 24/02/2016.
I've narrowed the reasoning down to the fact that the selected datetime is recorded as for example 25/02/2016 00:00:00 and with the 2 hour offset, this takes it to 24/02/2016 22:00:00
Having never worked with timezones before, or UTC dates/times, this is highly confusing.
Here is the code -
oObject.RefDate = itTimeAndDate.ParseDateAndTimeNoUTCMap(Request, TextBox_RefDate.Text);
if (!string.IsNullOrEmpty(oObject.TimeZoneDetails))
{
TimeZoneInfo oTimeZone = TimeZoneInfo.FindSystemTimeZoneById(oObject.TimeZoneDetails);
oObject.RefDate = itTimeAndDate.GetUTCUsingTimeZone(oTimeZone, oObject.RefDate);
}
RefDate would equate to something like 25/02/2016 00:00:00 once returned from ParseDateAndTimeNoUTCMap * (code below)*
static public itDateTime ParseDateAndTimeNoUTCMap(HttpRequest oTheRequest, string sValue)
{
DateTime? oResult = ParseDateAndTimeNoUTCMapNull(oTheRequest, sValue);
if (oResult != null)
return new itDateTime(oResult.Value);
return null;
}
/// <summary>
/// Translate a string that has been entered by a user to a UTC date / time - mapping using the
/// current time zone
/// </summary>
/// <param name="oTheRequest">Request context</param>
/// <param name="sValue">Date / time string entered by a user</param>
/// <returns>UTC date / time object</returns>
static public DateTime? ParseDateAndTimeNoUTCMapNull(HttpRequest oTheRequest, string sValue)
{
try
{
if (string.IsNullOrEmpty(sValue))
return null;
sValue = sValue.Trim();
if (string.IsNullOrEmpty(sValue))
return null;
if (oTheRequest != null)
{
const DateTimeStyles iStyles = DateTimeStyles.AllowInnerWhite | DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite;
// Create array of CultureInfo objects
CultureInfo[] aCultures = new CultureInfo[oTheRequest.UserLanguages.Length + 1];
for (int iCount = oTheRequest.UserLanguages.GetLowerBound(0); iCount <= oTheRequest.UserLanguages.GetUpperBound(0);
iCount++)
{
string sLocale = oTheRequest.UserLanguages[iCount];
if (!string.IsNullOrEmpty(sLocale))
{
// Remove quality specifier, if present.
if (sLocale.Contains(";"))
sLocale = sLocale.Substring(0, sLocale.IndexOf(';'));
try
{
aCultures[iCount] = new CultureInfo(sLocale, false);
}
catch (Exception) { }
}
else
{
aCultures[iCount] = CultureInfo.CurrentCulture;
}
}
aCultures[oTheRequest.UserLanguages.Length] = CultureInfo.InvariantCulture;
// Parse input using each culture.
foreach (CultureInfo culture in aCultures)
{
DateTime oInputDate;
if (DateTime.TryParse(sValue, culture.DateTimeFormat, iStyles, out oInputDate))
return oInputDate;
}
}
return DateTime.Parse(sValue);
}
catch (Exception)
{
}
return null;
}
Once returned from the above, the following lines are executed -
TimeZoneInfo oTimeZone = TimeZoneInfo.FindSystemTimeZoneById(oObject.TimeZoneDetails);
oObject.RefDate = itTimeAndDate.GetUTCUsingTimeZone(oTimeZone, oObject.RefDate);
It is within GetUTCUsingTimeZone that the problem seems to occur to me.
static public itDateTime GetUTCUsingTimeZone(TimeZoneInfo oTimeZone, itDateTime oDateTime)
{
if (oDateTime == null || oTimeZone == null)
return oDateTime;
DateTime oLocal = DateTime.SpecifyKind(oDateTime.Value, DateTimeKind.Unspecified);
DateTime oResult = TimeZoneInfo.ConvertTimeToUtc(oLocal, oTimeZone);
return new itDateTime(oResult);
}
I have checked TimezoneInfo for the offset value, and oResult always equates to the oLocal param - the offset. So 25/02/2016 00:00:00 with a 3 hour offset would equate to 24/02/2016 21:00:00
When the offset is -hours, it goes in the other direct, so oResult = oLocal + the offset, if that makes sense. So the main issue of the date changing is not occurring in those instances.
Obviously this is not what I want. I want the date to be what the user has selected, for their timezone.
Has anyone seen something like this before? Any possible solution?
I'm not entirely sure what I've done wrong.
If you need to maintain the correct timezone, you should be using the DateTimeOffset type instead of DateTime type.
DateTimeOffset maintains the offset from UTC so you never lose your timezone information and has a lot of useful methods like
UtcDateTime
From the horses mouth:
https://msdn.microsoft.com/en-us/library/system.datetimeoffset(v=vs.110).aspx
https://learn.microsoft.com/en-us/dotnet/standard/datetime/choosing-between-datetime
The fix was to run the following after grabbing the value from the db and before redisplaying it -
static public itDateTime FixUTCUsingTimeZone(TimeZoneInfo oTimeZone, itDateTime oDateTime)
{
if (oDateTime == null || oTimeZone == null)
return oDateTime;
DateTime oTime = DateTime.SpecifyKind(oDateTime.Value, DateTimeKind.Unspecified);
DateTime oResult = TimeZoneInfo.ConvertTimeFromUtc(oTime, oTimeZone);
return new itDateTime(oResult);
}
So essentially just doing the reverse of the ConvertTimeToUtc performed earlier. Not sure why this wasn't done originally, but there you go.
i came across a situation that, i need to get only the Date out from DateTime.
i am having a DateTime? StartDate property (Nullable) used to hold the date value
i tried below,
var d = Convert.ToDateTime(StartDate).Date;
but its returning me d as eg. 6/22/2006 12:00:00AM
after doing var d = Convert.ToDateTime(StartDate).Date.ToString("d");
i'm able to get d as 6/22/2006..but i dont want to convert my DateTime? to String
is their any way to get only the Date without using the ToString("d")?
Use the Date property to get the Date component of DateTime instance:
DateTime dateTimeNow = DateTime.Now;
DateTime datePartOnly = dateTimeNow.Date; // Return 00/00/0000 00:00:00
With this approach, Date property will return the date at midnight. So the time part will be 00:00:00 in this case.
There are couple of alternate ways to get the just the Date part, but the return type of it will be a string:
1.) Using .ToString(string? format) where format can be standard or custom format string
string dateOnlyString = dateTimeNow.ToString("dd/MM/yyyy");
//Can also use .ToString("dd-MM-yyyy");
2.) Using .ToShortDateString() to return a culture sensitive date string
string dateOnlyString = dateTimeNow.ToShortDateString();
//Returns M/d/yyyy for "en-US" culture
//Returns yyyy/M/d for "ja-JP" culture
Reference: here.
try this:
string x = DateTime.Now.ToShortDateString().
this will get the date dd/mm/yy given to the string x.
I think you question is sort of... moot.
You ask for a date without a time, but get a DateTime, which has both. I really don't think that should be a problem in most cases though:
If you create a DateTime with a certain date, and compare it to another date, and both of these have their time set to midnight, your comparisons will be valid and correct. Eg:
var yesterday = new DateTime(2014, 3, 10);
var today = new DateTime(2014, 3, 11);
var tomorrow = new DateTime(2014, 3, 12);
Comparing and sorting these will work as you expect, and so will the following:
if(today == DateTime.Today){
Console.WriteLine("Today is the day!");
}
In other words, you should be perfectly fine just pretending like the time-part does not exist.
Also, as you touched upon yourself in the OP, you can use the property Date if you want to make sure to avoid any time-component:
// Note the addition of hours, minutes and seconds:
var today = new DateTime(2014, 3, 11, 14, 35, 33);
if(today == DateTime.Today){
Console.WriteLine("This never happened...");
}
if(today.Date == DateTime.Today){
Console.WriteLine("...But today is still the day!");
}
In C# 10 you can use DateOnly.
DateOnly date = DateOnly.FromDateTime(DateTime.Now);
A DateTime does have both a date and a time. You can decide with yourself that in a specific property you well never use the date part. It will just be 12:00 AM, but you won't use it.
In some situations it can be useful to write your own type that can never hold a time-of-day component. Here is a start:
struct Date : IFormattable
{
readonly DateTime value;
public Date(DateTime dateAndTime)
{
value = dateAndTime.Date;
}
public string ToString(string format, IFormatProvider formatProvider)
{
return value.ToString(format ?? "d", formatProvider);
}
public string ToString(string format)
{
return ToString(format, null);
}
public string ToString(IFormatProvider formatProvider)
{
return ToString(null, formatProvider);
}
public override string ToString()
{
return ToString(null, null);
}
public static implicit operator DateTime(Date date)
{
return date.value;
}
}
The field value does hold the 12 AM thing, but it is private and is not seen from the outside. The overloads of ToString() make sure that unless something else is requested, the Date is written out with the short date format of the current culture.
I have a function that checks for null values then converts dates if they are not null
the below function just had "08/09/13" sent to it (English format) and i got "String was not recognized as a valid DateTime."
anyone help me as to why? do i need to tell the something somewhere is uses English format?
Thanks
public static DateTime DateTimeCheck(object objDateTime)
{
if (objDateTime == null || objDateTime == "")
return default(DateTime);
return Convert.ToDateTime(objDateTime);
}
I don't understand why you passed an object as a parameter instead of string first of all.
Try this instead;
public static DateTime DateTimeCheck(object objDateTime)
{
...
return DateTime.ParseExact(objDateTime.ToString(),"dd/MM/yy",CultureInfo.InvariantCulture);
}
Of course, this throws exception if your object is not formatted same as with "dd/MM/yy".
Take a look at;
Custom Date and Time Format Strings
You can use the overloaded method that accepts the culture information:
Convert.ToDateTime(o, new CultureInfo("en-Gb"));
To get or set the current culture you can use:
System.Threading.Thread.CurrentThread.CurrentCulture
You might be in a different culture with a different default date format. However you can use ParseExact to parse in the expected format. For example:
CultureInfo provider = CultureInfo.InvariantCulture;
DateTime result = DateTime.ParseExact("25/12/82","dd/MM/yy",provider);
I know this not what you are looking for but that's how to be sure that some object has date time value in it something like that :
public static DateTime DateTimeCheck(object objDateTime)
{
DateTime dateTime ;
if (objDateTime != null)
{
if (DateTime.TryParse(objDateTime.ToString(), out dateTime))
{
return Convert.ToDateTime(objDateTime);
}
}
return default(DateTime);
}
Try this:
DateTime.ParseExact((string)objDateTime,"dd/MM/yyyy",CultureInfo.InvariantCulture);
I have the following method in order to verify whether a string is a valid datetime:
public bool isDate(string date)
{
bool check = false;
try
{
DateTime converted_date = Convert.ToDateTime(date);
check = true;
}
catch (Exception)
{
check = false;
}
return check;
}
Now, the exception "String was not recognized as valid datetime" is caught whenever I try to pass a string like this:
"12/31/2013 12:00:00 AM"
I cannot understand why this is happening. Can someone help me solve this please?
Instead of the try/catch block, try the built in TryParse method in the DateTime class. It takes your string as a parameter and if it converts successfully it will place the value in the "result" variable. It returns a boolean value representing whether it worked or not.
public bool isDate(string date)
{
var result = new DateTime();
return DateTime.TryParse(date, out result);
}
Most likely your current culture settings are different from the format date is provided in. You can try specifying the culture explicitly:
CultureInfo culture = new CultureInfo("en-US"); // or whatever culture you want
Convert.ToDateTime(date, culture);
You can also use DateTime.TryParseExact and pass a format string (eg. MM/dd/yy H:mm:ss zzz, see more here) to check if the date has a specific format.
Is there any way to validate datetime field before inserting it into appropriate table?
Trying to insert with try/catch block is not a way.
Thanks,
Not sure if I'm being overly pedantic there, but DateTime.TryParse will validate whether a value is a valid DateTime object. OP asked about verifying a value before inserting into SQL Server datetime. The range of acceptable values for a SQL Server datetime is "January 1, 1753, through December 31, 9999" That does not hold true for DateTime .NET objects. This script assigns a value of "1/1/0001 12:00:00 AM" to badDateTime and it successfully parses.
DateTime d = DateTime.MinValue;
string badDateTime = DateTime.MinValue.ToString();
Console.WriteLine(badDateTime);
DateTime.TryParse(badDateTime, out d);
However, if you attempted to store that into a datetime field, it would fail with "The conversion of a varchar data type to a datetime data type resulted in an out-of-range value."
A commenter asked why I used 997 for milliseconds, this is covered under SQL Server 2008 and milliseconds but saving you a click, 997 is the largest value you can store in a datetime datatype. 998 will be rounded up to 1 second with 000 milliseconds
/// <summary>
/// An initial pass at a method to verify whether a value is
/// kosher for SQL Server datetime
/// </summary>
/// <param name="someval">A date string that may parse</param>
/// <returns>true if the parameter is valid for SQL Sever datetime</returns>
static bool IsValidSqlDatetime(string someval)
{
bool valid = false;
DateTime testDate = DateTime.MinValue;
DateTime minDateTime = DateTime.MaxValue;
DateTime maxDateTime = DateTime.MinValue;
minDateTime = new DateTime(1753, 1, 1);
maxDateTime = new DateTime(9999, 12, 31, 23, 59, 59, 997);
if (DateTime.TryParse(someval, out testDate))
{
if (testDate >= minDateTime && testDate <= maxDateTime)
{
valid = true;
}
}
return valid;
}
This is probably a better approach as this will attempt to cast the DateTime object into an actual sql datetime data type
/// <summary>
/// An better method to verify whether a value is
/// kosher for SQL Server datetime. This uses the native library
/// for checking range values
/// </summary>
/// <param name="someval">A date string that may parse</param>
/// <returns>true if the parameter is valid for SQL Sever datetime</returns>
static bool IsValidSqlDateTimeNative(string someval)
{
bool valid = false;
DateTime testDate = DateTime.MinValue;
System.Data.SqlTypes.SqlDateTime sdt;
if (DateTime.TryParse(someval, out testDate))
{
try
{
// take advantage of the native conversion
sdt = new System.Data.SqlTypes.SqlDateTime(testDate);
valid = true;
}
catch (System.Data.SqlTypes.SqlTypeException ex)
{
// no need to do anything, this is the expected out of range error
}
}
return valid;
}
Try this without hardcoding sql dateTime value:
public bool IsValidSqlDateTime(DateTime? dateTime)
{
if (dateTime == null) return true;
DateTime minValue = (DateTime)System.Data.SqlTypes.SqlDateTime.MinValue;
DateTime maxValue = (DateTime)System.Data.SqlTypes.SqlDateTime.MaxValue;
if (minValue > dateTime.Value || maxValue < dateTime.Value)
return false;
return true;
}
This is another take on billinkc's answer. However, in this method the .Value property of the min/max is used to avoid parsing and try/catch. Someone mentioned they wanted to ensure they are inserting a valid date into SQL Server. So, I took the approach of returning a date that is valid for SQL Server. This could easily be changed to a boolean method that checks to see if the dateToVerify is a valid SQL Server date.
protected DateTime EnsureValidDatabaseDate(DateTime dateToVerify)
{
if (dateToVerify < System.Data.SqlTypes.SqlDateTime.MinValue.**Value**)
{
return System.Data.SqlTypes.SqlDateTime.MinValue.Value;
}
else if (dateToVerify > System.Data.SqlTypes.SqlDateTime.MaxValue.**Value**)
{
return System.Data.SqlTypes.SqlDateTime.MaxValue.Value;
}
else
{
return dateToVerify;
}
}
<asp:RangeValidator runat="server" ID="rgvalDate" ControlToValidate="txtDate" Text="[Invalid]" Type="Date" MinimumValue="1/1/1753" MaximumValue="12/31/9999" />
OR
custom validator:
protected void cvalDOB_ServerValidate(object sender, ServerValidateEventArgs e)
{
e.IsValid = IsValidSqlDateTime(e.Value);
}
public static bool IsValidSqlDateTime(object Date)
{
try
{
System.Data.SqlTypes.SqlDateTime.Parse(Date.ToString());
return true;
}
catch
{
return false;
}
}
Here is a class with an extension method to allow a check such as if(myDateTime.IsValidSqlDateTime()) { ... }:
public static class DateTimeExtensionMethods
{
public static bool IsValidSqlDateTime(this DateTime dateTime)
{
return !(dateTime < (DateTime) SqlDateTime.MinValue ||
dateTime > (DateTime) SqlDateTime.MaxValue);
}
}
Could you provide a bt more information on where the datetime value is coming from; a web form?
You could simply add a CompareValidator as follows
<asp:CompareValidator ID="CompareValidator1" runat="server"
ControlToValidate="txtDate"
Type="Date"
ErrorMessage="CompareValidator">
</asp:CompareValidator>
If you are mentioning about server side validation of your DateTime field, use DateTime.TryParse. A quick and dirty example will be
DateTime dateValue;
string dateString = "05/01/2009 14:57:32.8";
if (DateTime.TryParse(dateString, out dateValue))
{
// valid date comes here.
// use dateValue for this
}
else
{
// valid date comes here
}
DateTime.TryParse is the best validator
DateTime temp;
if(DateTime.TryParse(txtDate.Text, out temp))
//Works
else
// Doesnt work