I am extracting data from an Excel spreadsheet using interop in C# and I have a small problem that I cant think of an answer for.
When I extract the data for date cell using this code:
string _date = xlWorksheet.get_Range("B3", "B3").Value2.ToString().Trim();
I get a value of 40694 which wont go directly in to SQL using my insert statemwnt.
I have also tried:
DateTime _date = Convert.ToDateTime(xlWorksheet.get_Range("B3", "B3").Value2.ToString().Trim());
But that comes back with an error saying that it cant convert it.
Can anyone advise me on how to do it?
Excel's internal date values are "days since the epoch", which depends on if it's in PC or Mac mode (PC version uses 1/1/1900, Mac version uses 1/1/1904), and then there's an extra setting to be bug-compatible with Lotus 1-2-3 which has some leapyear issues. Converting this number realiably requires that you check if the spreadsheet is Windows- or Mac-based, and if the 1-2-3 compat flag is on.
You might be better of having Excel format the string into an unambiguous string (like 1-jan-1904) and then parse that back to a datetime value in SQL server, rather than trying to duplicate Excel's complicated date handling logic.
Use DateTime.FromOADate()
Using your example:
DateTime _date = DateTime.FromOADate(Double.Parse(xlWorksheet.get_Range("B3", "B3").Value2))
Use DateTime.FromOADate(double d):
DateTime.FromOADate((double)(xlWorksheet.get_Range("B3", "B3").Value2))
Ran into the same thing, here's the conversion
/// <summary>
/// Seriously? For the loss
/// <see cref="http://www.debugging.com/bug/19252"></see>
/// </summary>
/// <param name="excelDate">Number of days since 1900-01-01</param>
/// <returns>The converted days to date</returns>
public static DateTime ConvertXlsdtToDateTime(int excelDate)
{
DateTime dt = new DateTime(1899, 12, 31);
// adjust for 29 Feb 1900 which Excel considers a valid date
if (excelDate >= 60)
{
excelDate--;
}
return dt.AddDays(excelDate);
}
Excel stores dates as a floating point number counting the number of days since the day before 1900-01-01 (or 1904-01-01 for Mac). There is also a leap-year issue you have to take into account if the date is before 1900-03-01.
The following code will do the conversion:
DateTime ConvertToDateTime(Double date) {
if (date < 1)
throw new ArgumentException("Excel dates cannot be smaller than 1.");
var epoch = new DateTime(1900, 1, 1);
if (date > 60D)
date -= 2;
else
date -= 1;
return epoch.AddDays(date);
}
Related
I am currently using the Excel C# libraries (Microsoft.Office.Interop.Excel) to read an excel spreadsheet into my C# application.
I initially tried to read all the cells as their raw data, but found that Date-formatted cells were giving me a 5-digit integer, and time-formatted cells were returning a decimal. So I then found out that you can use a date-conversion method built into Excel's C# library, like so:
DateTime excelDate = (DateTime)ExcelCalcValue.ExcelDateToDateTime(workbook, Double.Parse(cell.Value.ToString()));
output = excelDate.ToString("yyyy-MM-dd HH:mm");
Through debugging my application with various test sheets, I have been able to record the various format strings that cells return when they are formatted in different ways. These are below:
(WorksheetCell.CellFormat.FormatString)
Times
[$-F400]h:mm:ss\\ AM/PM
hh:mm:ss;#
h:mm:ss;#
[$-409]hh:mm:ss\\ AM/PM;#
[$-409]h:mm:ss\\ AM/PM;#
Dates
m/d/yy
[$-F800]dddd\\,\\ mmmm\\ dd\\,\\ yyyy
dd/mm/yyyy;#
dd/mm/yy;#
d/m/yy;#
d\\.m\\.yy;#
yyyy\\-mm\\-dd;#
[$-809]dd\\ mmmm\\ yyyy;#
[$-809]d\\ mmmm\\ yyyy;#
Using these, I can now reliably determine the formatting style of a cell in excel. Using the earlier code, I can detect a date-formatted cell and return the proper data in DateTime format. However, I cannot see an equivalent function for converting time-formatted cells.
I get a result of 0.58368055555555554 when I read a cell time-formatted as [$-F400]h:mm:ss\\ AM/PM. I have absolutely no idea how to convert this into a DateTime, or indeed what this float represents.
Can anyone suggest a method of converting time-formatted excel cells (which are stored as a strange float) into the correct DateTime variable?
As FrankPI said, use DateTime.FromOADate(). You would use this function with the raw data from an Excel cell - there is no need to parse the format.
Excel encodes its dates and times in a double. The integral portion represents the days after January 1, 1900. The fraction part represents the time since midnight of the day referenced. For example:
1.5 is January 1, 1900 # Noon
and
41507.25 = August 21, 2013 # 6:00 am
Refer to the MSDN docs on this function for more information:
http://msdn.microsoft.com/en-us/library/system.datetime.fromoadate.aspx
The "strange float" can probably be converted too a DateTime via the DateTime.FromOADate() method. Actually, it is the number of days since January, 1, 1900 with the time as fractions, e. g. 0.04236 = 1/24 + 1/(24 * 60) for 1:01 am.
I wrote this function to handle a date input from Excel into C#. It handles a number of data type possibilities for a date cell:
/// <summary>
/// Returns DateTime?
/// Excel dates are double values, and sometimes, they're typical dd/mm/yyyy dates.
/// This function handles both possibilities, and the possibility of a blank date input.
/// ///
/// </summary>
/// <param name="inputDate"></param>
/// <returns></returns>
private static DateTime? ResolveExcelDateInput(string inputDate)
{
double incomingDate = 0;
DateTime incomingDateDate = new DateTime();
// If the incoming date is a double type, parse it into DateTime
if (Double.TryParse(inputDate, out incomingDate))
{
return DateTime.FromOADate(incomingDate);
}
// IF the incoming date value is a date type, parse it.
var parseDateResult = DateTime.TryParse(inputDate, out incomingDateDate);
if(parseDateResult)
{
// If the parse is successful return the date
return incomingDateDate;
}
else
{
// If the parse isn't successful; check if this a blank value and set to a default value.
if(string.IsNullOrWhiteSpace(inputDate))
{
return new DateTime(1901, 1, 1);
}
else
{
// Otherwise return null value so that is then handled by the validation logic.
// log a validation result because inputDate is likely an invalid string value that has nothing to do with dates.
return null;
}
}
}
If you want that either date value is in double format or in date format it converts it to date format then try to use following code. datestringvalue should be your input value.
DateTime dateNow = DateTime.Now;
DateTime formatedDate = DateTime.TryParse("datestringvalue", out dateNow) ? Convert.ToDateTime("datestringvalue") : DateTime.FromOADate(Convert.ToDouble("datestringvalue"));
I have a textfield that has a date with the format "12/23/2010".Is there away for me to get the number 23 using watin ie get number from textfield;i'm gonna use it like this.
1.Get datetime 12/23/2010 and get number '23'
2.substract 2 from 23 and store it somewhere[ie: 23 - 2 = 21]
3.Insert the new datetime number [ie:12/21/2010 ]
string myDate = browser.TextField(Find.ByName("myTextField")).Value;
DateTime time = = new DateTime();
time2 = time - 2;
browser.TextField(Find.ByName("myTextField")).TypeText(time2);
Is this possible?or should i be looking to another way.Ask the user to insert the data instead.
You should use DateTime.Parse, DateTime.TryParse, DateTime.ParseExact or DateTime.TryParseExact to parse from text to a DateTime.
If a failure to parse indicates a failure in the code somewhere (which is probably the case here, given that it's a test) I suspect DateTime.ParseExact is the most appropriate approach, providing the expected format, culture etc.
if what you want is to subtract 2 days from a date I would do it like this:
DateTime dt = DateTime.Parse(myDate)-TimeSpan.FromDays(2);
//its steps 1,2 & 3 in one easy to read line :)
This is of course if you are sure the string you have IS a valid date. If it might not be, then you should do what the Skeet recommends, which is using first a try parse, checking if the return value is true, and if it is, then do the rest, and if it is not, send an error message.
consider writing
DateTime dt = Convert.ToDateTime(myDate);
DateTime dtNew = new DateTime(dt.Year, dt.Month, dt.Day - 2);
browser.TextField(Find.ByName("myTextField")).TypeText(dtNew.ToShortDateString());
Try getting the value of the date as string
Convert it to datetime and use AddDays we can use negative or positive value
And insert it into textbox
string myDate = this.Elements.textfield.Value;
DateTime dt = Convert.ToDateTime(myDate);
DateTime dtNew = dt.AddDays(-3);
this.Elements.ChangeDateActive.TypeText(dtNew.ToShortDateString());
That's it thanks
I have two fields:
string date1 = "04/26/10";
string date2 = "04/25/10";
How can I compare these two fields like so?:
if (date2 <= date1)
{
// perform some code here
}
Can this be done without first converting the fields to a separate date-type variable?
EDIT: I should mention that these values are coming from a database table where the date values are in a string format to begin with. Old legacy code...
No, but it is not difficult to convert to a date in C#.
if ( DateTime.Parse(date2,CultureInfo.InvariantCulture) <= DateTime.Parse(date1,CultureInfo.InvariantCulture))
{
// perform some code here
}
CultureInfo depends on the format the string dates have in the legacy DB. See: DateTime formats used in InvariantCulture
If your dates are actually stored as strings in the database, it seems like you can't be sure they'll be in a valid format before parsing. For that reason I'd suggest a small variation on jle's answer:
DateTime d1, d2;
if (DateTime.TryParse(date1, out d1) &&
DateTime.TryParse(date2, out d2) &&
d2 <= d1)
{
// perform some code here
}
else
{
// strings didn't parse, but hey,
//at least you didn't throw an exception!
}
At the very least you need to pick apart the strings in order to compare them in the right order.
If you want to leave them as strings, then you need to reorder them with LARGEST->SMALLEST units, so this:
yyyy/mm/dd
can be compared directly, but not the format you have. With your format, you need to split it, and either recombine it like above, or compare the individual pieces in the right order.
Having said that, it is rather easy to convert the strings to DateTime using DateTime.ParseExact.
Generally it is a bad idea to compare date as strings.
But if your strings are in the same format (e.g. yyyy/mm/dd means years, then monthes then days) then the comparison may be valid.
It could be done with string manipulation, but it would come down to effectively comparing three sets of integers, which as strings would induce more overhead than converting to datetimes. Why would you want to do that?
No. Let the .net framework sort that out for you. It will correctly identify the user date settings and format (using system settings, current thread) and determine which is the month, year and day - especially if that data comes from eg a sql server.
It's preferred to have the date formatted before doing the comparison. Depending in your cultureinfo, the safest way to compare dates is to format the date string to "yyyy-mm-dd".
DateTime d1, d2;
string date1 = "04/26/10";
string date2 = "04/25/10";
d1 = DateTime.Parse(date1.ToString("yyyy-MM-dd"));
d2 = DateTime.Parse(date2.ToString("yyyy-MM-dd"));
if (d1 > d2)
{
//do something
}
Best practice is to avoid comparing date as string types and compare with the official DateTime object of C#.
If your use-case requires the comparison using string objects then:
1st, make sure the date string format is yyyymmdd as recommend by Lasse V. Karlsen.
2nd, use the string method CompareTo to compare between the dates.
Here's how the method works in your case (Used C# Online Compiler to test this):
using System;
public class Program
{
public static void Main()
{
string fmt = "yyyymmdd";
string min = "20201206";
string max = "20210810";
Console.WriteLine(max.CompareTo(min)); // Output : 1
Console.WriteLine(min.CompareTo(max)); // Output : -1
string same1 = "20001212";
string same2 = "20001212";
Console.WriteLine(same1.CompareTo(same2)); // Output : 0
Console.WriteLine(same2.CompareTo(same1)); // Output : 0
// Summary:
// 1 = Greater than string param
// -1 = lesser than string param
// 0 = equals to string param
}
}
I am creating an Excel document using owc11. I am providing the dates in dd/mm/yyyy format. I am doing something like this in the code:
for (..looping through results..)
{
Range c = EmailStats.putCellValue(sheet, row, 1, val);
c.set_NumberFormat("dd/mm/yyyy");
}
private static Range putCellValue(Worksheet sheet, int row, int col, String val)
{
Range cell = (Range) sheet.Cells[row, col];
cell.set_Value(XlRangeValueType.xlRangeValueDefault, val);
return cell;
}
Now when for the val argument I set the date format as "dd/mm/yyyy" or not set it at all, the behaviour I get is mm/dd/yyyy from the 1st of a month up and till the 12th and then it swaps back to dd/mm/yyyy. So owc11 thinks that it knows better and swaps the days and month around (like in the US format) and when it is over the 12th date it sticks to the UK format.
Val is declared as String because it may not always be a date. It may be a date, a day, a user name, group name etc depending on how we group/sort our data. Also it may be a selection of days.
After experimenting a while I figured out that the only way to solve this is to use the universal date format of yyyy/mm/dd. However that may create other integration problems. So I was hoping that there may be a way to enforce the dd/mm/yyyy format, so please any suggestions are welcome.
Try to set val as not string. Try assign val as DateTime.
cell.set_Value(XlRangeValueType.xlRangeValueDefault, val);
I have the following code:
DataTable t = new DataTable();
t.Locale = CultureInfo.InvariantCulture;
t.Columns.Add("Date", typeof(DateTime));
DateTime today = DateTime.Now;
DateTime yesterday = today.AddDays(-1);
DateTime tomorow = today.AddDays(1);
t.Rows.Add(yesterday);
t.Rows.Add(today);
t.Rows.Add(tomorow);
string filter = string.Format(CultureInfo.InvariantCulture,
"Date >= #{0}# AND Date <= #{1}#", yesterday, tomorow);
t.DefaultView.RowFilter = filter;
foreach (DataRowView v in t.DefaultView)
Console.WriteLine(v["date"]);
I'm expecting that the filtered t.DefaultView now contains all three "days". But for some reason the last date from the range isn't included. It seems <= operator for DateTime type works like a <.
Where is the problem? Is that a bug? Any suggestions how to overcome that?
Update.
Got some responses about DateTime type and comparison operators. Thanks.
But now I want to direct attention to filter expression.
Ok, say I have the folloving loop:
foreach (DataRow r in t.Rows)
{
DateTime date = (DateTime)r["Date"];
if (yesterday <= date && date <= tomorow)
Console.WriteLine(date);
}
This loop should show the same result like
foreach (DataRowView v in t.DefaultView)
Console.WriteLine(v["date"]);
from the previous example, yes? No! Here <= works as I'm expecting and the result is all three days. Why?
Update #2: solution.
As Joe has noted - the problem is about fractions of a second.
If I format upper and lower bounds with Round-trip date/time pattern (to preserve fractions of a second) - everything works just fine:
string filter = string.Format(CultureInfo.InvariantCulture,
"Date >= '{0}' AND Date <= '{1}'",
yesterday.ToString("o", CultureInfo.InvariantCulture),
tomorow.ToString("o", CultureInfo.InvariantCulture));
The date comparison takes the time into account. So, for instance, "today at midday" is greater than just "today". If you use DateTime.Now, the time is included. So, if DateTime.Now is "today at midday", then tomorrow = today.AddDays(1) is less than "tomorrow at 3PM"... So you need to ignore the time part of the date. You can do that by formatting the date without the time. Also, if you want to check that a date is "less or equal than tomorrow" (regardless of the time), check that it is "strictly less than the day after tomorrow" :
string filter = string.Format(CultureInfo.InvariantCulture,
"Date >= #{0:MM/dd/yyyy}# AND Date < #{1:MM/dd/yyyy}#",
yesterday,
tomorrow.AddDays(1));
The code you posted in your update is not equivalent to the row filter.
Your row filter formats the date using the general format for the current culture. This probably does not include fractions of a second - therefore unless you happen to call DateTime.Now on a second boundary, your tomorrow value will be some fractions of a second beyond the range specified by the filter.
I.e. if your tomorrow value is '2009-12-23 01:02:03.456', your row filter is only taking values up to and including '2009-12-23 01:02:03', a few fractions of a second before the value specified by tomorrow.
If you only want to compare dates, you should use DateTime.Date to truncate the time component from your dates (and use DateTime.Today rather than DateTime.Now for the current date).
Try with
DateTime today = DateTime.Today;
if does not solve, check whether your date field contains time also. there lies your problem.
Update: your second comment.
when you compare with DateTime.Now e.g. Date <= 21.12.2009 14:35:35, it will take all before 14:35 hours and will ignore later rows. Hope this helps you.
See following article to get more idea
http://dotnetguts.blogspot.com/2007/06/understanding-datetime-and-timespan-in.html