I am trying to format Datetime string using
date.ToString(format)
If user feeds in wrong format, e.g. "YYYY MM DDr" I would like to know whether I can convert datetime using that format, rather than returning
2015 04 DDr
since
DateTime.ToString(format)
always returns a valid String.
For example, is there any method that perhaps throw an exception on failed conversion so that I can catch and decide not display my output string instead of displaying something like
2015 04 DDr
If you assume that all the letters inserted in your format are either separators or letters that should be converted to a DatePart, you can check if after converting the date you still have non separator chars that were not converted, as follows:
public static class DateTimeExtension
{
public static string ToStringExt(this DateTime p_Date, String format)
{
char[] separators = { ' ', '/', '-' };
String stringDate = p_Date.ToString(format);
foreach (char dateChar in format)
{
if (stringDate.Contains(dateChar) && !separators.Contains(dateChar))
{
throw new FormatException("Format Error");
}
}
return stringDate;
}
}
Edited after #Vladimir Mezentsev observation:
This code assumes that you are converting only to Numbers, if you are doing something that will convert to Day strings like Tuesday, the logic may fail. To address this scenario the code would get a little more complicated but can also be achieved with something like this:
public static string ToStringExt(this DateTime p_Date, String format)
{
foreach (string dateFormatPart in getFormatStrings(format))
{
if (p_Date.ToString(dateFormatPart) == dateFormatPart)
{
throw new FormatException("Format Error");
}
}
return p_Date.ToString(format);
}
private static IEnumerable<string> getFormatStrings(String format)
{
char[] separators = { ' ', '/', '-' };
StringBuilder builder = new StringBuilder();
char previous = format[0];
foreach (char c in format)
{
if (separators.Contains(c) || c != previous)
{
string formatPart = builder.ToString();
if (!String.IsNullOrEmpty(formatPart))
{
yield return formatPart;
builder.Clear();
}
}
if(!separators.Contains(c))
{
builder.Append(c);
}
previous = c;
}
if (builder.Length > 0)
yield return builder.ToString();
}
Have a look at https://msdn.microsoft.com/en-us/library/8kb3ddd4(v=vs.110).aspx. Particularly this part...
If you're wanting to validate the string used to format the DateTime object, then you'll probably have to write your own using the link provided to know what formats are acceptable, and treat any other characters as errors.
There is no invalid format, because you can parse formatted string with exact same format. Even if after parsing you have not loss in any part of date, that could not form the final decision - valid or invalid format.
You should carefully consider what can be appropriate for user, even give him an opportunity to construct format from some predefined blocks. Maybe show sample conversion with confirmation.
For specific formats you can create some extension method where you can apply your business rules and throw exceptions when you need it.
Related
In this code item.date_time looks like "2017-10-18T04:57:39.000Z". When I put this (or any other similar string) instead of item.date_time - it works well. But despite the fact that item.date_time is equal to strings like this - it calls System.FormatException when I use it.
static void Main(string[] args)
{
eventList = new List<CsvFile>();
StreamReader sr = new StreamReader("logs.csv");
string data = sr.ReadLine();
while (data != null)
{
string[] line = data.Split(',');
ReadString(line);
data = sr.ReadLine();
}
}
class CsvFile
{
public String date_time;
public DateTime datetime;
}
static void ReadString(String[] str)
{
CsvFile item = new CsvFile();
item.date_time = str[0];
item.datetime = DateTime.ParseExact(item.date_time, "yyyy-MM-ddTHH:mm:ss.000Z", CultureInfo.InvariantCulture);
eventList.Add(item);
}
I went through dozens of questions and answers about datetime issues today, but found nothing can solve this strange thing. Any ideas what is the problem with my code?
str[0] is equal "\"2017-10-18T04:57:39.000Z\""
Your string has quotes around it (hence the \" indicators). Trim those before parsing:
item.date_time = str[0].Trim('"');
item.datetime = DateTime.ParseExact(item.date_time, "yyyy-MM-ddTHH:mm:ss.000Z", CultureInfo.InvariantCulture);
You might consider using TryParseExact so you can determine if the parse was successful and show a better error message (like which record you're on, what the input value is, etc.) rather than throwing an exception.
Another alternative is to use a CSV parsing library that can handle both the quotes around values and the date/time parsing for you.
I am trying to read from a file that is structured as such:
VariableName:14326A6AC
Value:Long
Value:Long
I am trying to read it doing it as listed below, but I get a format error. When I add the formatting for hexadecimal (the format the longs are in) they are converted to decimal. Is there a way to keep them as a long so I don't have to do the long conversion from decimal to hex?
public static long returnLineValue(string lineName)
{
var lines = File.ReadLines(filePath);
foreach (var line in lines)
{
if (line != null)
{
char split = ':';
if(line.Contains(lineName))
{
string[] s = line.Split(split);
return Int64.Parse(s[1]);
}
}
}
return 0;
}
This is what you need:
return Convert.ToInt64(s[1], 16)
16 is base 16 (hexadecimal). This function convert from a hexadecimal string to a long.
You have to allow hexadecimal values in Parse:
...
// The same Parse but with hexadecimals allowed
return Int64.Parse(s[1], NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture);
...
whenever you want to represent Int64 in hexadecimal form, use formatting:
Int64 value = 255;
String result = value.ToString("X"); // "X" for hexadeimal, capital letters
// "FF"
Console.Write(result);
Try this
return Int64.Parse(s[1],System.Globalization.NumberStyles.HexNumber)
I have a long string with double-type values separated by # -value1#value2#value3# etc
I splitted it to string table. Then, I want to convert every single element from this table to double type and I get an error. What is wrong with type-conversion here?
string a = "52.8725945#18.69872650000002#50.9028073#14.971600200000012#51.260062#15.5859949000000662452.23862099999999#19.372202799999250800000045#51.7808372#19.474096499999973#";
string[] someArray = a.Split(new char[] { '#' });
for (int i = 0; i < someArray.Length; i++)
{
Console.WriteLine(someArray[i]); // correct value
Convert.ToDouble(someArray[i]); // error
}
There are 3 problems.
1) Incorrect decimal separator
Different cultures use different decimal separators (namely , and .).
If you replace . with , it should work as expected:
Console.WriteLine(Convert.ToDouble("52,8725945"));
You can parse your doubles using overloaded method which takes culture as a second parameter. In this case you can use InvariantCulture (What is the invariant culture) e.g. using double.Parse:
double.Parse("52.8725945", System.Globalization.CultureInfo.InvariantCulture);
You should also take a look at double.TryParse, you can use it with many options and it is especially useful to check wheter or not your string is a valid double.
2) You have an incorrect double
One of your values is incorrect, because it contains two dots:
15.5859949000000662452.23862099999999
3) Your array has an empty value at the end, which is an incorrect double
You can use overloaded Split which removes empty values:
string[] someArray = a.Split(new char[] { '#' }, StringSplitOptions.RemoveEmptyEntries);
Add a class as Public and use it very easily like convertToInt32()
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
/// <summary>
/// Summary description for Common
/// </summary>
public static class Common
{
public static double ConvertToDouble(string Value) {
if (Value == null) {
return 0;
}
else {
double OutVal;
double.TryParse(Value, out OutVal);
if (double.IsNaN(OutVal) || double.IsInfinity(OutVal)) {
return 0;
}
return OutVal;
}
}
}
Then Call The Function
double DirectExpense = Common.ConvertToDouble(dr["DrAmount"].ToString());
Most people already tried to answer your questions.
If you are still debugging, have you thought about using:
Double.TryParse(String, Double);
This will help you in determining what is wrong in each of the string first before you do the actual parsing.
If you have a culture-related problem, you might consider using:
Double.TryParse(String, NumberStyles, IFormatProvider, Double);
This http://msdn.microsoft.com/en-us/library/system.double.tryparse.aspx has a really good example on how to use them.
If you need a long, Int64.TryParse is also available: http://msdn.microsoft.com/en-us/library/system.int64.tryparse.aspx
Hope that helps.
private double ConvertToDouble(string s)
{
char systemSeparator = Thread.CurrentThread.CurrentCulture.NumberFormat.CurrencyDecimalSeparator[0];
double result = 0;
try
{
if (s != null)
if (!s.Contains(","))
result = double.Parse(s, CultureInfo.InvariantCulture);
else
result = Convert.ToDouble(s.Replace(".", systemSeparator.ToString()).Replace(",", systemSeparator.ToString()));
}
catch (Exception e)
{
try
{
result = Convert.ToDouble(s);
}
catch
{
try
{
result = Convert.ToDouble(s.Replace(",", ";").Replace(".", ",").Replace(";", "."));
}
catch {
throw new Exception("Wrong string-to-double format");
}
}
}
return result;
}
and successfully passed tests are:
Debug.Assert(ConvertToDouble("1.000.007") == 1000007.00);
Debug.Assert(ConvertToDouble("1.000.007,00") == 1000007.00);
Debug.Assert(ConvertToDouble("1.000,07") == 1000.07);
Debug.Assert(ConvertToDouble("1,000,007") == 1000007.00);
Debug.Assert(ConvertToDouble("1,000,000.07") == 1000000.07);
Debug.Assert(ConvertToDouble("1,007") == 1.007);
Debug.Assert(ConvertToDouble("1.07") == 1.07);
Debug.Assert(ConvertToDouble("1.007") == 1007.00);
Debug.Assert(ConvertToDouble("1.000.007E-08") == 0.07);
Debug.Assert(ConvertToDouble("1,000,007E-08") == 0.07);
In your string I see: 15.5859949000000662452.23862099999999 which is not a double (it has two decimal points). Perhaps it's just a legitimate input error?
You may also want to figure out if your last String will be empty, and account for that situation.
How do I go about displaying a format string as-is?
I have a property decorated with some data annotations:
[DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}")]
public blah blah { get; set; }
I now have a reference to the format string:
string format = metadata.DisplayFormatString; // == {0:MM/dd/yyyy}
I want to display this format string to the user without manually typing it in my view. Is there a nice way (not replace chars with empty string) to get MM/dd/yyyy from the string format?
I thing using replace is a nice way too. If you're concerned about clutter in the view, you could put this into an extension method (or even an HTML Helper extension method).
public static string ToUserFriendlyDateFormat(this string unfriendlyFormat) {
return unfriendlyFormat
.Replace("{0:", string.Empty)
.Replace("}", string.Empty);
}
I don't know what all your format strings look like, but if your format strings ever make it to String.Format() you already have to escape { and } characters (by doubling them). How about using a regular expression. Here's something simple to start with that will also undouble the { and } characters:
string DisplayFormat(string format) {
Regex rx = new Regex(#"\{\d+:([^}]+)}|\{\{|}}");
return rx.Replace(format, delegate(Match m) {
if (m.Value == "{{") {
return "{";
} else if (m.Value == "}}") {
return "}";
} else {
return m.Groups[1].Value;
}
});
}
Note that the above code will have to change if your format strings contain sequences like {0} and you want something special done with them as well.
I am trying to create a generic formatter/parser combination.
Example scenario:
I have a string for string.Format(), e.g. var format = "{0}-{1}"
I have an array of object (string) for the input, e.g. var arr = new[] { "asdf", "qwer" }
I am formatting the array using the format string, e.g. var res = string.Format(format, arr)
What I am trying to do is to revert back the formatted string back into the array of object (string). Something like (pseudo code):
var arr2 = string.Unformat(format, res)
// when: res = "asdf-qwer"
// arr2 should be equal to arr
Anyone have experience doing something like this? I'm thinking about using regular expressions (modify the original format string, and then pass it to Regex.Matches to get the array) and run it for each placeholder in the format string. Is this feasible or is there any other more efficient solution?
While the comments about lost information are valid, sometimes you just want to get the string values of of a string with known formatting.
One method is this blog post written by a friend of mine. He implemented an extension method called string[] ParseExact(), akin to DateTime.ParseExact(). Data is returned as an array of strings, but if you can live with that, it is terribly handy.
public static class StringExtensions
{
public static string[] ParseExact(
this string data,
string format)
{
return ParseExact(data, format, false);
}
public static string[] ParseExact(
this string data,
string format,
bool ignoreCase)
{
string[] values;
if (TryParseExact(data, format, out values, ignoreCase))
return values;
else
throw new ArgumentException("Format not compatible with value.");
}
public static bool TryExtract(
this string data,
string format,
out string[] values)
{
return TryParseExact(data, format, out values, false);
}
public static bool TryParseExact(
this string data,
string format,
out string[] values,
bool ignoreCase)
{
int tokenCount = 0;
format = Regex.Escape(format).Replace("\\{", "{");
for (tokenCount = 0; ; tokenCount++)
{
string token = string.Format("{{{0}}}", tokenCount);
if (!format.Contains(token)) break;
format = format.Replace(token,
string.Format("(?'group{0}'.*)", tokenCount));
}
RegexOptions options =
ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None;
Match match = new Regex(format, options).Match(data);
if (tokenCount != (match.Groups.Count - 1))
{
values = new string[] { };
return false;
}
else
{
values = new string[tokenCount];
for (int index = 0; index < tokenCount; index++)
values[index] =
match.Groups[string.Format("group{0}", index)].Value;
return true;
}
}
}
You can't unformat because information is lost. String.Format is a "destructive" algorithm, which means you can't (always) go back.
Create a new class inheriting from string, where you add a member that keeps track of the "{0}-{1}" and the { "asdf", "qwer" }, override ToString(), and modify a little your code.
If it becomes too tricky, just create the same class, but not inheriting from string and modify a little more your code.
IMO, that's the best way to do this.
It's simply not possible in the generic case. Some information will be "lost" (string boundaries) in the Format method. Assume:
String.Format("{0}-{1}", "hello-world", "stack-overflow");
How would you "Unformat" it?
Assuming "-" is not in the original strings, can you not just use Split?
var arr2 = formattedString.Split('-');
Note that this only applies to the presented example with an assumption. Any reverse algorithm is dependent on the kind of formatting employed; an inverse operation may not even be possible, as noted by the other answers.
A simple solution might be to
replace all format tokens with (.*)
escape all other special charaters in format
make the regex match non-greedy
This would resolve the ambiguities to the shortest possible match.
(I'm not good at RegEx, so please correct me, folks :))
After formatting, you can put the resulting string and the array of objects into a dictionary with the string as key:
Dictionary<string,string []> unFormatLookup = new Dictionary<string,string []>
...
var arr = new string [] {"asdf", "qwer" };
var res = string.Format(format, arr);
unFormatLookup.Add(res,arr);
and in Unformat method, you can simply pass a string and look up that string and return the array used:
string [] Unformat(string res)
{
string [] arr;
unFormatLoopup.TryGetValue(res,out arr); //you can also check the return value of TryGetValue and throw an exception if the input string is not in.
return arr;
}