DataTable to CSV date format - c#

I'm trying to export a DataTable to CSV, with a number of columns. I would like that the date_time column is exported to CSV while retaining the database date_time format (YYY-MM-DD HH:mm:ss), or something like that.
This is my code:
private void DataTableToCsv(string path, DataTable dt)
{
File.Delete(path);
StringBuilder sb = new StringBuilder();
string[] columnNames = dt.Columns.Cast<DataColumn>().
Select(column => column.ColumnName).
ToArray();
sb.AppendLine(string.Join(",", columnNames));
foreach (DataRow row in dt.Rows)
{
string[] fields = row.ItemArray.Select(field => field.ToString()).
ToArray();
sb.AppendLine(string.Join(",", fields));
}
File.WriteAllText(path, sb.ToString());
}
The dates are appearing in a different format, which is giving me errors when trying to pick up from MySQL.

I know there is an accepted answer already, but...
Actually there is a way to control the formatting of dates in a consistent manner, without specifying the format for every type of data: you can use IFormatProvider
First, the method object.ToString(); with no parameters is formatting your object using current CultureInfo set in your thread/application/system. This is problematic when you need consistent formatting, for example when you try to save values into database. It isn't only problematic for dates but also for numbers - there are languages out there that use comma as decimal separator or use thousand separator in a number, for example, and databases do not like that very much. Now imagine your program is run on the computer that has a different language set...
Having said that, many of those problems can be avoided by using object.ToString(CultureInfo.InvariantCulture); instead. It is associated with the English language. Then, the result for 31th October 2014will be: 10/31/2014 08:35:52
Obviously still not what you want.
You can further control your formatting by modifying the culture a little yourself:
CultureInfo format = (CultureInfo) CultureInfo.InvariantCulture.Clone();
// those are the formats used by ToString() mathod:
format.DateTimeFormat.ShortDatePattern = "yyyy-MM-dd";
format.DateTimeFormat.ShortTimePattern = "HH:mm:ss";
Console.WriteLine(DateTime.Now.ToString(format));
double ddd=13.11;
Console.WriteLine(ddd.ToString(format));
string sss = "some string";
// although this may not be a good idea:
Console.WriteLine(sss.ToString(format));
result:
2014-10-31 08:39:53
13.11
some string

With DateTime values, you can control the output by calling
dateTimeField.ToString("yyyy-dd-MM");
Currently you're treating all fields equally, and using the default conversion:
field => field.ToString()
You can't specify a conversion as object.ToString() has no overloads to do so; you'd have to specify what conversion to use for each type separately, ie:
string[] fields = row.ItemArray.Select(field => ConvertToCsvValue(field)).ToArray();
and
static void ConvertToCsvValue(object value)
{
if (value is DateTime)
{
return ((DateTime)value).ToString("yyyy-dd-MM");
}
else
{
return value.ToString();
}
}

data in a database is not aware of any format; the behaviour you are experiencing is related to that and is correct.
when filling the csv your code is taking the default format for the env/os/locale and applying it to the values.
to solve the issue you have some possible solution:
you change the source sql code to produce a sql text field filled with formatted data (imho a bad choice)
you handle the format client side when preparing the csv; you have to modify your code to handle the fields one by one applying the correct format to date fields
you alter the datatable content applying the expected format to date fields (this may be possible or maybe not depending on datatype of the involved fields)
my preference goes to the second or third solution because i prefer to keep formatting on the client side but YMMV and in your post there is not enough information to make a proper choice.

Related

How to get the format string for a specified currency and culture

I have SSRS reports that display currency amounts. They need to be both culture aware and currency aware. Some reports show different currencies in the same table. I have no trouble with culture awareness. It's currency formatting that's the trouble. Importantly, when I export to Excel, the values in these currency fields must be sortable as numbers. That means the cell values must be numbers, so I cannot use the normal .ToString("C", culture) functions that so many other posts end up with. I need to keep the numeric value in the field and to apply .NET's format string to the number (e.g. "'$'#,0.00;('$'#,0.00)"). This way, Excel will treat the value as a number for sorting purposes but display the correctly formatted currency.
Is it possible to use code to modify a NumberFormatInfo instance and then somehow return the string value of the formatter, such as "'€'#,0.00;('€'#,0.00)"?
var numberFormat = new CultureInfo("en-US").NumberFormat;
numberFormat.CurrencySymbol = "€";
return numberFormat.GetCurrencyFormatString(); //this is an imaginary function that I need to return "'€'#,0.00;('€'#,0.00)"
I have tried programmatically setting the currency symbol based on the currency information of each row. As far as I know, SSRS does not allow me to use an Expression to set the currency symbol. It only offers a dropdown list.
My users don't like it when I show the currency code (e.g. USD, CAD), so I'm stuck with showing the symbol (e.g. $, CA$).
As far as I can tell, you'll need to manually construct this format string using the CultureInfo class.
Using the docs on CurrencyPositivePattern and CurrencyNegativePattern (see here and here), I've put together something that works but might need some tweaking:
string GetCurrencyFormat(CultureInfo culture)
{
//we'll use string.Format later to replace {0} with the currency symbol
//and {1} with the number format
string[] negativePatternStrings = { "({0}{1})", "-{0}{1}", "{0}-{1}", "{0}{1}-", "({1}{0})",
"-{1}{0}", "{1}-{0}", "{1}{0}-", "-{1} {0}", "-{0} {1}",
"{1} {0}-", "{0} {1}-", "{0} -{1}", "{1}- {0}", "({0} {1})",
"({1} {0})" };
string[] positivePatternStrings = { "{0}{1}", "{1}{0}", "{0} {1}", "{1}{0}" };
var numberFormat = culture.NumberFormat;
//Generate 0's to fill in the format after the decimal place
var decimalPlaces = new string('0', numberFormat.CurrencyDecimalDigits);
//concatenate the full number format, e.g. #,0.00
var fullDigitFormat = $"#{numberFormat.CurrencyGroupSeparator}0{numberFormat.CurrencyDecimalSeparator}{decimalPlaces}";
//use string.Format on the patterns to get the positive and
//negative formats
var positiveFormat = string.Format(positivePatternStrings[numberFormat.CurrencyPositivePattern],
numberFormat.CurrencySymbol, fullDigitFormat);
var negativeFormat = string.Format(negativePatternStrings[numberFormat.CurrencyNegativePattern],
numberFormat.CurrencySymbol, fullDigitFormat);
//finally, return the full format
return $"{positiveFormat};{negativeFormat}";
}
This returns $#,0.00;($#,0.00) for en-US, £#,0.00;-£#,0.00 for en-GB, for example.

Get DateTime from FileName

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);
}

Add numeric strings in C# and preserve formating

I have an issue where I need to add two numeric strings "$1,234.56" and "$9,876.54" and get a string "$11,111.10"
I can convert the strings to numbers, perform the addition, but I don't know of a good way to preserve the formatting when I ToString() the result. I can add a couple of if statements along the lines: does the input have dollar sign, decimal point, percent sign and construct the format string accordingly, but this is clunky and will fail if we ever need to support more than one number format.
Does anyone know how to add numeric strings and preserve formatting?
EDIT: To answer the questions. The format of all strings being added at a given time is the same ie: I don't need to worry about adding $ and £ (in fact £ is not currently supported), However, there are several possible formats that are currently supported and more may be added in the future:
$1,234.00; $1,234; 1234; 1,234; 1,234.00; 1234%; 1,234%; 1,234.00%
I would suggest using the first numeric string as a template and create a number format from it:
var posshalves = firstNumericString.Split('.');
var fmthalves = new string[2] { posshalves[0], (posshalves.Length < 2 ? "" : "."+posshalves[1])};
var intfmt = Regex.Replace(fmthalves[0], #"[0-9]", "#");
intfmt = Regex.Replace(intfmt, #"#+", "#");
var decfmt = Regex.Replace(fmthalves[1], "[0-9]", "0");
var format = $"{intfmt}{decfmt}";

Loading dates from a text file in EPPLUS

I'm trying to create an Excel spreadsheet in my web application using a tab-delimited text file as the data source. The code that loads my data looks like this:
// Load the data into the cells
Int32 rowIdx = 1;
foreach (String line in tab.Lines)
{
String[] cellTexts = line.Split(TAB);
Int32 colIdx = 1;
foreach (String cellText in cellTexts)
{
sheet.Cells[rowIdx, colIdx].Value = cellText;
colIdx++;
}
rowIdx++;
}
That seems to work fine. Later, however, I add a NumberFormat of "mm/dd/yyyy" to the cells:
range.Style.Numberformat.Format = "mm/dd/yyyy";
However, this doesn't change the display of the data in the cells. (The dates look like 5/1/15 or 12/31/15 in the original text file, and remain that way after the format is applied.
I am pretty sure that this because I've put a text value into the cell. (While it looks like a date, it's still just a string of characters.) But from my reading, I need to put a double into the cell to meet Excel's expectation that dates are stored as a double. Because the cell contains a string and not a double, the format string isn't applied, leaving the original, unformatted text.
I want to add some code to
Check the type of data in each cell in the range to which I apply a
date format.
If it's not a double, attempt to convert it to a date.
If the date conversion is successful, then convert the .NET date to an OADate and put it back into the cell.
My question is: Is this the best (or at least a reasonable) approach, and if so, how do I do that?
This code doesn't work:
foreach (OfficeOpenXml.ExcelRangeBase oneCell in range)
{
if (typeof(oneCell.Value) == "System.String")
{
// date manipulations here
}
}
The red line appears under oneCell in the typeof(oneCell.Value) call with the message "The type or namespace 'oneCell' could not be found. (Are you missing a using directive or an assembly reference?)"
Note that I can't know in advance where the date fields will be because both the data and the cell formats are provided from an external source. (The external cell formats do indicate when the format being applied is for a date format as opposed to a regular number format or a string.)
As #mason suggested, I'm posting the code I used to get around this problem.
(I didn't get an answer to my original question, which is how to iterate cells in a range and check the data type of each cell's content, but with this solution, I no longer need to do that.)
Instead, I modified the loop that loads the data from the tab-delimited text file to use some TryParse() calls to detect dates, numbers, or regular text data, and then load the appropriately typed data into the cell. Note how it checks for a leading single quote character to suppress the data typing if the cell is actually text, but looks like a number or a date:
// Load the data into the cells
Int32 rowIdx = 1;
foreach (String line in tab.Lines)
{
String[] cellTexts = line.Split(TAB);
Int32 colIdx = 1;
foreach (String cellText in cellTexts)
{
DateTime dateValue;
Double doubleValue;
if(cellText.StartsWith("'"))
{
sheet.Cells[rowIdx, colIdx].Value = cellText.Substring(1);
}
else if(DateTime.TryParse(cellText,out dateValue))
{
sheet.Cells[rowIdx, colIdx].Value = dateValue;
}
else if (Double.TryParse(cellText, out doubleValue))
{
sheet.Cells[rowIdx, colIdx].Value = doubleValue;
}
else
{
sheet.Cells[rowIdx, colIdx].Value = cellText;
}
colIdx++;
}
rowIdx++;
}
With the data typed appropriately in the cells, the formats have the desired effect.

How can I format string values using a mask in a generic way?

Background: We have a system that receives data from another backend system. We handle the displaying of that data, and we have our own XML templates to control how certain things are displayed (i.e. we have our own column templates to dictate what the column headers are, etc.) One thing we would like to support is the ability to provide a mask for these column templates that would apply to the values coming from the backend. Below is a scenario that I'm having trouble with.
Problem: I can't seem to get a simple string format working. I'd like to format a STRING value of four digits (i.e. "1444") in a time format (i.e. "14:44"). I've tried:
String.Format("{0:00:00}", "1444")
Note the importance of the input being a STRING. If I supply an int value, the format will work. The reason I cannot use this is because all the data we receive from the backend is in string format, and we'd like for this to be generic (so casting isn't really an option).
By generic, I mean I'd like to specify a mask in our own XML templates, something like:
<MyColumnTemplate Id="MyColumn" Mask="00:00" />
and to use that mask in a string format call for string values? If the mask fails, we could just simply return the original value (as the String.Format() method already does by default).
Edit: To help clarify, here is a simplified version of what I'd like to be able to do in code:
string inputValue = "1444";
string maskValueFromXml = "00:00";
string mask = "{0:" + maskValueFromXml + "}";
string myDesiredEndResult = String.Format(mask, inputValue);
The thing is you are working string to string,since you ask for time and phone number they are all numbers then try this trick(if we can call it that :)):
string result = string.Format("{0:00:00}", int.Parse("1444"));
For phone number:
string result = string.Format("{0:000-000-0000}", int.Parse("1234560789"));
You can even place your desired masks in a dictionary for example and do this:
Dictionary<string, string> masks = new Dictionary<string, string>();
masks.Add("Phone", "{0:000-000-0000}");
masks.Add("Time", "{0:00:00}");
string test = "1234560789";
string result = string.Format(masks["Phone"], int.Parse(test));
Try with DateTime.TryParseExact, e.g:
DateTime dateEntered;
string input = "1444";
if (DateTime.TryParseExact(input, "HH:mm", System.Globalization.CultureInfo.CurrentCulture, System.Globalization.DateTimeStyles.None, out dateEntered))
{
MessageBox.Show(dateEntered.ToString());
}
else
{
MessageBox.Show("You need to enter valid 24hr time");
}
After that, you can use string.Format, predefined formats on MSDN.

Categories