Consider the following code to parse a date. (Note that I'm in the EN-gb locale):
const DateTimeStyles DATE_TIME_STYLES = DateTimeStyles.NoCurrentDateDefault | DateTimeStyles.AllowWhiteSpaces;
DateTime dt;
// Current culture for this example is "EN-gb".
if (DateTime.TryParse("31/12", CultureInfo.CurrentUICulture, DATE_TIME_STYLES, out dt))
Console.WriteLine("Parsed correctly"); // Do not want!
else
Console.WriteLine("Did not parse correctly.");
I'm deliberately omitting the year. However, TryParse() will parse this without any errors, and will substitute the current year.
I'd like to be able to force the user to enter ALL the components of the date (using their local format), so I'd like the above parsing to fail - or to be able to detect that the user didn't enter a year.
I don't really want to use DateTime.TryParseExact() because then I would have to add code to specify all the different valid formats for all the different supported locales, which is non-trivial and likely error-prone. I suspect this may well be my only sane option, though.
Anyone got any ideas? (Someone here at work has already implemented a "solution" which involves not allowing the current year, which is clearly not a good solution...)
You could query for the culture's patterns, filter out those without a year and then use TryParseExact on the remaining patterns.
var allPatterns = culture.DateTimeFormat.GetAllDateTimePatterns();
var patternsWithYear = allPatterns.Where(s => s.Contains("y")).ToArray();
bool success = TryParseExact(input, patternsWithYear, culture, styles, out dateTime);
Known bug: This doesn't take escaping into account, you'll need to replace the Contains("y") call with proper parsing to fix this.
Alternatively you could go with just LongDatePattern and ShortDatePattern if you're fine with stricter format constraints.
You can use parse exact like this and catch the exception.
CurrentUICulture.DateTimeFormat.ShortDatePattern will give you the cultures short date pattern.
There is also DateTime.TryParseExact
DateTime.ParseExact(value.ToString(), cultureInfo.CurrentUICulture.DateTimeFormat.ShortDatePattern.ToString, cultureInfo.CurrentUICulture)
The more I think about this the more I think it's a bad solution but given you're getting no other answers I'll post it anyway.
DateTime temp;
DateTime.TryParse(input, CultureInfo.CurrentUICulture, DATE_TIME_STYLES, out temp);
if (temp.Year == DateTime.Now.Year)
{
if (!input.Contains(DateTime.Now.Year))
{
if (temp.Days != int.Parse(DateTime.Now.Year.ToString().SubString(2)))
{
// my god that's gross but it tells you if the day is equal to the last two
// digits of the current year, if that's the case make sure that value occurs
// twice, if it doesn't then we know that no year was specified
}
}
}
Also, as others have suggested in comments now, checking the number of tokens or the strings length could also be useful like;
char[] delims = new char[] { '/', '\', '-', ' '); //are there really any others?
bool yearFound = false;
foreach (char delim in delims)
{
if (input.Split(delim).Count == 3)
{
yearFound = true;
break;
}
}
if (yearFound)
//parse
else
// error
These are just a couple of ideas, neither is truly sound. They're obviously both hacks, only you can know if they'll suffice. At least they beat your co-workers if (dt.Year == 2014) //discard input "solution".
Related
I have a file named test-2000_01_02-10_12_14.xml.
How do I only get the date from the file?
I was able to get the date if the file has this name: 2000_01_02-10_12_14
with this (b is a StorageFile):
DateTime dateVal;
bool parsed = DateTime.TryParseExact(b.DisplayName,
"yyyy_MM_dd-H_mm_ss",
System.Globalization.CultureInfo.InvariantCulture,
System.Globalization.DateTimeStyles.None,
out dateVal);
I then tried to change yyyy_MM_dd-H_mm_ss to something like this *-yyyy_MM-dd-H-mm_ss but it does not seem to be the solution
There are a boatload of ways to do this, it really rather depends on how regular the naming of your files is - is there always some junk text followed by a hyped, then the year?
Post up another 10 different examples if you want more tailored advice. Here's a way for the one you've posted:
DateTime.TryParseExact(
Path.GetFileNameWithoutExtension(b.DisplayName.Substring(b.DisplayName.IndexOf('-')+1)),
"yyyy_MM_dd-H_mm_ss",
System.Globalization.CultureInfo.InvariantCulture,
System.Globalization.DateTimeStyles.None,
out dateVal
);
This uses Substring with only one argument (no length) to remove everything after the first hyphen up to the end of the string, and GetFileNameWithoutExtension to remove the .xml - this effectively turns anythinghere-2000_01_01-00_00_00.xml into 2000_01_01-00_00_00 ready for parsing
I could also have gone for a .Remove("last index of period") type thing but it does get a bit messy because you have to subtract the start Index of the hyphen etc
MJWill's comment about splitting on hyphen is also a good one - you could split then take the [1] and [2] indexes and join then back together for parsing..
Lastly don't forget that the file itself might have a created date that is already a good candidate for the date of creation rather than the filename (which might be mangled by humans) so long as it hasn't been transmitted somewhere and re-saved. Take a look at the FileInfo.CreationTime property for that - https://learn.microsoft.com/en-us/dotnet/api/system.io.fileinfo?view=netframework-4.8
First, we have to extract (match) the datetime part from a file name:
using System.Text.RegularExpressions;
...
// Aggravated task: dots and minuses within file's name
string source = #"bla-bla-bla-test.me-2000_01_02-10_12_14.xml";
string datetime = Regex.Match(
Path.GetFileNameWithoutExtension(source),
"[0-9]{4}_[0-9]{2}_[0-9]{2}-[0-9]{2}_[0-9]{2}_[0-9]{2}$").Value;
Then we can parse it
if (DateTime.TryParseExact(
datetime,
"yyyy_MM_dd-H_m_s",
CultureInfo.InvariantCulture,
DateTimeStyles.AssumeLocal,
out DateTime result) {
// result is the parsed date
}
else {
// File doesn't contain valid date and time
}
I would suggest you to use regular expression assuming that your file name will be always following the same format you can do something like this:
var pattern = #"\d{4}_\d{2}_\d{2}-\d{2}_\d{2}_\d{2}";
var fileName = "test-2000_01_02-10_12_14.xml";
var match = new Regex(pattern);
var result = match.Match(fileName);
if (result.Success)
{
DateTime.TryParseExact(result.Value,
"yyyy_MM_dd-HH_mm_ss",
System.Globalization.CultureInfo.InvariantCulture,
System.Globalization.DateTimeStyles.None,
out DateTime dateVal);
}
I've been given the task to make a textbox where you enter your personal code (something we in Latvia use). I need it to be validated before saving the information. For validation I've been using Regex but so far got no result.
Our personal code is like this : XXYYZZ-ABCDE, where
XXYYZZ is date format as in DAYMONTHYEAR and ABCDE are random numbers.
if (per_kods.Text.Trim() != string.Empty)
{
mRegxExpression = new Regex("${day}-${month}-${year}-#####$");
if (!mRegxExpression.IsMatch(per_kods.Text.Trim()))
{
label7.Text = "";
}
else
{
label7.ForeColor = Color.Red;
label7.Text = "Personas kods ievadīts nepareizi!";
pareizi = false;
}
}
this currently is my code. It basically enables a label above the textbox pointing out that the information entered is wrong. If the information is right, the label continues to be disabled. But right now the new Regex part is a problem. I know it might seem totally wrong, but I've just started learning Regex and don't know what's wrong and what's right.
If you don't care about date validation (so for example 31st of February will be accepted, you can do
new Regex(#"^(0[1-9]|[1-2]\d|3[0-1])(0[1-9]|1[0-2])(\d{2})-(\d{5})$");
If you want to understand what this string means, take a look at the MSDN reference.
Now for date validation, so filtering out dates like 310298 that don't exist, I'd recommend you do it manually afterwards - regex is not the best tool for such logic-validation.
EDIT:
You can accomplish that using DateTime.TryParse.
DateTime resultDateTime;
var isValid = DateTime.TryParse(string.Format("{0}-{1}-{2}", 2010, 2, 31), out resultDateTime);
// isValid is false, because 31st of February 2010 does not exist.
var isValid = DateTime.TryParse(string.Format("{0}-{1}-{2}", 2010, 2, 27), out resultDateTime);
// isValid is true, and resultDateTime has been set to 27-2-2010.
Note that DateTime.TryParse is culture sensitive. Depending on the target culture you might need to change the input string. See MSDN reference for TryParse.
EDIT2:
So to connect this with your existing code:
mRegxExpression = new Regex(#"^(0[1-9]|[1-2]\d|3[0-1])(0[1-9]|1[0-2])(\d{2})-(\d{5})$");
var match = mRegxExpression.Match(per_kods.Text.Trim()));
if(!Validate(match))
{
// Handle invalid.
}
else
{
// Handle valid.
}
Where Validate would be:
private static bool Validate(Match match)
{
if(!match.Success)
{
return false;
}
var day = match.Groups[1].ToString();
var month = match.Groups[2].ToString();
var year = match.Groups[3].ToString();
return DateTime.TryParse($"{day}-{month}-{year}", out _);
}
Because our regex begins with ^ and ends with $, there will be always at most one match. The Success property tells us whether there was any match at all, and later the Groups property gives us the capture groups. Groups[0] will be the entire matched string, and then every next one will be the substring that matches one of the parentheses enclosed groups from regex - so the first one is (0[1-9]|[1-2]\d|3[0-1]) which represents days, the second will be months, and so on. Then we just check if the date is valid (again, culture sensitive!). Also, we can neatly use the C#7 discard syntax (_) for the out parameter, as we don't need it.
You can get help from the code below to check validation.
public bool CheckValidation(string input)
{
input = input.Trim();
if (input == string.Empty) return false;
var mRegxExpression = new Regex("^([0-2][0-9]|(3)[0-1])(((0)[0-9])|((1)[0-2]))\\d{2}(\\-)\\d{5}$");
return mRegxExpression.IsMatch(input);
}
I just ran into something very strange, and was just wondering if I was missing something.
I was trying to parse a string (with thousand separators) into a double, and found the below issue.
CultureInfo ci = CultureInfo.CurrentCulture; // en-ZA
string numberGroupSeparator = CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator; //numberGroupSeparator = ,
string numberDecimalSeparator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;//numberDecimalSeparator = .
string strValue = "242,445.24";
double try1;
double try2;
bool btry1 = Double.TryParse(strValue, out try1); //try1 = 242445.24 : btry1 = true
bool btry2 = Double.TryParse(strValue, NumberStyles.Any, null, out try2); //try2 = 0.0 : btry2 = false <- STRANGE
double try3 = Convert.ToDouble(strValue); //try3 = 242445.24
Now the reason why I didnt just want to use Convert.ToDouble is due to scientific notation which has given me some problems before.
Does anybody know why this might be?
EDIT:
I have update my current culture info.
Its working on my machine as expected, so I believe it has to do with the Current Culture. Try using CultureInfo.InvariantCulture instead of null in your TryParse
Double.TryParse(strValue, NumberStyles.Any,CultureInfo.InvariantCulture, out try2);
It is failing for your current specified culture en-ZA, I tried the following code and try2 is holding 0.0
Double.TryParse(strValue, NumberStyles.Any,new CultureInfo("en-ZA"), out try2);
Updated (correct) answer, after much digging
You say that your current culture is en-ZA, but checking
new System.Globalization.CultureInfo("en-ZA").NumberFormat.NumberGroupSeparator
we see that the value is the empty string and not "," as the question states. So if we set CultureInfo.CurrentCulture to new CultureInfo("en-ZA") then parsing fails even for try1.
After manually setting it to "," with
Thread.CurrentThread.CurrentCulture.NumberFormat.NumberGroupSeparator = ",";
it transpires that parsing into try1 is successful. Parsing into try2 still fails.
For the TryParse overload used in try2 the documentation is pretty clear that the current thread culture is used when the format provider is null, so something else must be going on...
After carefully comparing InvariantCulture.NumberFormat to that of the en-ZA culture, I noticed that the cultures also differ in their currency formats. Trying
Thread.CurrentThread.CurrentCulture.NumberFormat.CurrencyGroupSeparator = ",";
Thread.CurrentThread.CurrentCulture.NumberFormat.CurrencyDecimalSeparator = ".";
hit the jackpot: parsing succeeds! So what's really going on is that when using NumberStyles.All, the parse treats the number as currency.
The hypothesis can be verified if you try
double.TryParse(strValue,
NumberStyles.Any & ~NumberStyles.AllowCurrencySymbol, null, out try2);
which succeeds without needing to mess with the currency separators (of course the NumberGroupSeparator does have to be appropriate)!
The documentation says that 0.0 is returned, when the conversation fails.
Most likely TryParse returns false, and you should try calling Parse, to get an exception message that might tell you what is wrong.
Is it possible to format a double, so he doesn't chance the text 2140.76 to 214076 but instead letting it be 2140.76?
I can't use ',' for the decimal numbers, since the entire text file that I'm reading are numbers using '.' for separating the decimals, 10000 records, every day, so ...
EDIT:
double natMB = 0;
boolean check = double.TryParse(splitline[8], NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out natMB);
if (check == false)
{
natMB = 0;
}
else
{
natMB = natMB * 1024;
}
double intMB = 0;
boolean check2 = double.TryParse(splitline[9], NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out intMB);
if (check2==false)
{
intMB=0;
}
else
{
intMB = intMB * 1024;
}
The 0 value is necessary since I need to enter these values in an SQL statement, and they need to show up as 0, not as null.
Your question is not clear, Do you want to parse a double from a string with dot decimal separator ?
If yes try with this :
double.Parse("2140.76", CultureInfo.InvariantCulture)
You can use the invariant culture to format a number with a decimal period, regardless of your local culture settings:
string formatted = someDouble.ToString(CultureInfo.InvariantCulture);
Start reading here:
http://msdn.microsoft.com/en-us/library/system.globalization.numberformatinfo.aspx
Basically, you create an NumberFormatInfo that you can customise to use with String.Format to use any format you want.
Is it possible to format a double so he doesn't chance the text 2140.76 to 214076 but instead
letting it be 2140.76?
Yes. Let me play ignorant - I have no idea how you can even ask that and have the poroblem given the extensive formatting methods.
I can't use ',' for the decimal numbers, since the entire text file that i'm reading are numbers
using '.' for separating the decimals, 10000 records, every day, so ...
So the problem likely is that you ahve a culture issue at hand and an ignorant developer on the other side. Ignorant because files exchagned should always be english formatted always, to avoid that.
Anyhow, basically:
* Change your culture info in the thread to english or
* Use english culture info for parsing and text generation.
Read the documentation for the methods you use -there areo verlaods that allow you to tune the formatting.
I have written a small function in C# which isn't my main launguage so is coming across a little foreign to me.
public bool CheckForKey(string key)
{
string strKeyTime = Decode(key);
//valid key will be current time +- 5 minutes
string strTheTime = DateTime.Now.ToString("HH:mm:ss tt");
if (strKeyTime == strTheTime)
{
return true;
}
else
{
return false;
}
}
I need to alter this to allow for 5 minutes, so
if (strKeyTime == strTheTime)
needs to be
if (strKeyTime == strTheTime + or - 5 minutes)
my problem is matching the times as they are strings, perhaps convert key(original time) back to a date first and then do it, but I am pretty new to c#
If you convert (or keep) them both to DateTimes you can use TimeSpan:
TimeSpan delta = dateTime1 - dateTime2;
if (Math.Abs(delta.TotalMinutes) <= 5) { ... }
Look into using the DateTime.ParseExact (or any of the Parse... methods) to parse your strKeyTime, and then do something similar to the above.
To convert your sent string to the equivalent DateTime value, use the following code:
var keyDateTime = Convert.ToDateTime(strKeyTime);
var strTheTime = DateTime.Now
from here, you can use this value to compare with your original time value as the following:
if (keyDateTime == strTheTime || (keyDateTime > strTheTime && keyDateTime < strTheTime.AddMinutes(5))
{
return true;
}
the previous block of code will first check if we got an exact match, or the time sent to the method is between the original time and a time shift of additional 5 minutes.
that's it, if this is not what you need, let me know so I may update my answer for you, thanks.
-- if my answer is correct, don't forget to "Mark as answer".
"perhaps convert key(original time) back to a date first and then do it" sounds like a sound solution. I'd do it this way:
Convert both strings to DateTime instances.
Store difference in TimeSpan instance using DateTime.Subtract(DateTime value) ( http://msdn.microsoft.com/en-us/library/8ysw4sby.aspx )
Check if TimeSpanInstance.TotalMinutes is less than or equal to 5.
The first step is something I can't really give you any advice on without information concerning how your string is formatted. However, once this is done, the rest should be easy.