I have a problem. I need to sum hours worked in an office in a code. The dates i get from SQL server thats no problem but i have different formats. For example: 2019. 09. 23. 14:54:23, 2019.09.23 14:54:23 or 2019-09-23 14:54:23; And i want to sum hours worked in result. No matter the year. Heres the example:
try
{
string betölt = "SELECT * from munkaorak where";
if (cbTech.Text != "")
{
betölt += " Munkaszam='" + cbMunka.Text + "' AND Részfolyamat='" + cbRész.Text + "' AND TechKod='" + cbTech.Text + "'";
}
else if (cbRész.Text != "")
{
betölt += " Munkaszam='" + cbMunka.Text + "' AND Részfolyamat='" + cbRész.Text + "'";
}
else if(cbMunka.Text !="")
{
betölt += " Munkaszam='" + cbMunka.Text + "'";
}
betölt += " order by ID DESC";
MySqlCommand name = new MySqlCommand(betölt, kapcsolat);
kapcsolat.Open();
olvasó = name.ExecuteReader();
int összora = 0;
if (olvasó.HasRows)
{
while (olvasó.Read())
{
if (olvasó.GetString(7) != "Befejezés: ")
{
string[] aha = olvasó.GetString(6).Split(' ');
string kezdes = aha[4];
string[] kezd = kezdes.Split(':');
int kezdoido = Convert.ToInt32(kezd[0]) * 60 * 60 + Convert.ToInt32(kezd[1]) * 60 + Convert.ToInt32(kezd[2]);
int befejezoido = 0;
string aha22 = "";
if (olvasó.GetString(7).IndexOf('-') >= 0)
{
string[] aha2 = olvasó.GetString(7).Split(' ');
string befejezes = aha2[1];
string[] bef = befejezes.Split(':');
aha22 = aha2[0].Split('-')[2];
befejezoido = Convert.ToInt32(bef[0]) * 60 * 60 + Convert.ToInt32(bef[1]) * 60 + Convert.ToInt32(bef[2]);
}
else
{
string[] aha2 = olvasó.GetString(7).Split(' ');
string befejezes = aha2[4];
string[] bef = befejezes.Split(':');
aha22 = aha2[3];
befejezoido = Convert.ToInt32(bef[0]) * 60 * 60 + Convert.ToInt32(bef[1]) * 60 + Convert.ToInt32(bef[2]);
}
string dolgozott = "";
if (aha[3].Replace(".", "") == aha22.Replace(".", ""))
{
dolgozott = mpbolora(befejezoido - kezdoido);
összora += befejezoido - kezdoido;
}
else
{
dolgozott = mpbolora((86400 - kezdoido) + befejezoido);
összora += (86400 - kezdoido) + befejezoido;
}
string validalo = "";
try
{
string[] validal = olvasó.GetString(9).Split(' ');
validalo = validal[0] + " " + validal[1] + " " + validal[2] + validal[3] + validal[4] + " " + validal[5];
}
catch
{
validalo = olvasó.GetString(9);
}
string munkafolyamat = olvasó.GetString(3) + "-" + olvasó.GetString(4) + "-" + olvasó.GetString(5);
string[] sorok = { olvasó.GetString(2), dolgozott, olvasó.GetString(6).Replace("Kezdés: ", ""), olvasó.GetString(7).Replace("Befejezés: ", ""), olvasó.GetString(8), validalo, munkafolyamat };
var lv = new ListViewItem(sorok);
lvStat.Items.Add(lv);
}
}
}
else
{
kapcsolat.Close();
MessageBox.Show("Nincs adat!", "Figyelem");
}
kapcsolat.Close();
lblÖssz.Text = "Összesen ledolgozott órák: " + mpbolora(összora);
}
catch (Exception a)
{
MessageBox.Show(a.Message);
kapcsolat.Close();
}
kapcsolat.Close();
It worked but when different formats appeared its not working because '-' or spaces. Please help!
In C#, there is a bunch of methods provided to convert strings that contain date times in many formats into a unified DateTime object. These methods can recognize quite a few standard date time formats, and if yours differ from them, you can even provide your own.
DateTime.Parse() - Converts a string to a DateTime object. If operation fails, it'll thrown an exception.
DateTime.TryParse() - Converts a string to a DateTime object only if possible. Returns true if successful, and false if it fails.
DateTime.TryParseExact() - Converts a string that is in the specified format into a DateTime object. Returns true if successful, and false otherwise.
In your case, you can use DateTime.TryParse() (which is recommended over simply using DateTime.Parse() unless you're absolutely sure the format is correct) like so:
var dtStr1 = " 2019. 09. 23. 14:54:23";
var dtStr2 = "2019.09.23 14:54:23";
var dtStr3 = "2019-09-23 14:54:23";
DateTime.TryParse(dtStr1, out DateTime dt1);
DateTime.TryParse(dtStr2, out DateTime dt2);
DateTime.TryParse(dtStr3, out DateTime dt3);
Once converted to a DateTime object, it no longer has a format associated with it. It's a structure, and hence only has member variables and methods. So to calculate total hours etc. you can use provided methods.
Say you want to calculate time between day's work start and end. You can convert those into DateTime objects, then subtract one from the others which will give you a TimeSpam object.
var dtStrStart = "2019.09.23 08:23:12";
var dtStrEnd = "2019.09.23 16:17:28";
DateTime.TryParse(dtStrStart, out DateTime dtStart);
DateTime.TryParse(dtStrEnd, out DateTime dtEnd);
var diff = dtEnd - dtStart;
Now the TimeSpan object, which is diff here, will give you a bunch of properties with difference in hours, minutes etc.
The TimeSpan.Days, TimeSpan.Minutes etc will give you the time in days, minutes etc.
Console.WriteLine(diff.Days);
Console.WriteLine(diff.Hours);
Console.WriteLine(diff.Minutes);
Console.WriteLine(diff.Seconds);
Console.WriteLine(diff.Milliseconds);
Output:
0
7
54
16
0
The TimeSpan.TotalMinutes etc will give you the entire time period in respective units.
Console.WriteLine(diff.TotalDays);
Console.WriteLine(diff.TotalHours);
Console.WriteLine(diff.TotalMinutes);
Console.WriteLine(diff.TotalSeconds);
Console.WriteLine(diff.TotalMilliseconds);
Output:
0.329351851851852
7.90444444444444
474.266666666667
28456
28456000
And conversely, when you're storing data in the database, you must again use a standard format, such as datetime or datetime2. It's advised you use datetime2, more info here.
Your code should look more like this:
try
{
MySqlCommand name = new MySqlCommand("SELECT * from munkaorak WHERE Munkaszam=#m", kapcsolat);
name.Parameters.AddWithValue("#m", cbMunka.Text);
if (cbRész.Text != "")
{
name.CommandText += " AND Részfolyamat=#r";
name.Parameters.AddWithValue("#r", cbRész.Text);
}
if (cbTech.Text != "")
{
name.CommandText += " AND TechKod=#t";
name.Parameters.AddWithValue("#t", cbTech.Text);
}
name.CommandText += " order by ID DESC"; //is it really necessary?
MySqlDataAdapter da = new MySqlDataAdapter(name);
DataTable dt = new DataTable();
da.Fill(dt);
foreach(DataRow ro in dt.Rows){
string fromStr = ro["YOUR_FROM_DATE_COLUMN_NAME"].ToString();
//cope with dates in varying formats
//by replacing all non-numeric chars with nothing
fromStr = Regex.Replace(fromStr, #"[^0-9]", "");
//now our dates of [2019. 09. 23. 14:54:23], [2019.09.23 14:54:23] or [2019-09-23 14:54:23]
//just become 20190923145423
DateTime fromDt = DateTime.ParseExact(fromStr, "yyyyMMddHHmmss", CultureInfo.InvariantCulture);
string toStr = ro["YOUR_TO_DATE_COLUMN_NAME"].ToString();
toStr = Regex.Replace(toStr, #"[^0-9]", "");
DateTime toDt = DateTime.ParseExact(toStr, "yyyyMMddHHmmss", CultureInfo.InvariantCulture);
//total hours worked
(toDt - fromDt).TotalHours;
}
}
Hopefully that looks a lot simpler
Here you see no..:
Risky SQL injection hack possibility - don't concatenate values into your SQL, ever. Always concatenate a parameter in and then give a value to the parameter. Always
Difficult to read, lengthy string concatenation - looks terrible, always avoid it if you can
DB Connection opening and closing - micromanaging the database connection isn't necessary when using a dataadapter because it opens and closes for you
DataReader code full of magic numbers - GetString(7), hmmm.. was that the time in or time out? GetInt(4) - was it the age? The year? Here we get rid of all the datareader GetXX calls with their column ordinals and fill a DataTable (something like a 2D array) with rows that can be indexed by string names. It's still not as good as it can be (strongly typed DataTables are better) but it's a huge leap better than filling code with magic numbers, and working with everything in the most obscure, weakly typed way possible
Awkward time handling - it's gone in favour of Date parsing, because pulling strings to bits number by number, converting them to int, multiplying them by seconds and hours so they can be manipulated is tedious and hard to read - do away with it all by parsing these strings to the data types that they should have been stored as in the first place; you need to record the date and times that things happen at. Try your best to get that DB converted so these things are stored properly, and until then convert your strings to DateTime
Diffing dates using seconds: utilising TimeSpan calculations means no need to convert things to seconds, do crude math, drop all notions of time zones, or daylight savings changes etc; by using dates subtracted from each other you get a time period between those dates that takes things like daylight saving clock changes into account. Or even the ability to have one date that is tomorrow, or X days into the future. Might not matter for this app, but one day it could..
If you have MySQL 8 you can do the regex replace in the DB. Could even get the DB to diff and sum the dates.. We can't really make any recommendations on this point though because we don't know the column names
Related
I'm trying to save a set of records in my desktop c# application and it appears to stop since one of the values was in the incorrect format.
Before saving, the system goes through these computations:
private void ComputeTotalWeight()
{
double TotalWeight;
TotalWeight = ((Convert.ToInt32(txtSmall.Text)) + (Convert.ToInt32(txtMedium.Text)) + (Convert.ToInt32(txtLarge.Text)) +
(Convert.ToInt32(txtExtralarge.Text))) * .285;
txtTotalweight.Text = String.Format("{0:#,##0}", TotalWeight);
}
private void ComputeTagsCollars()
{
int TagsCollars;
TagsCollars = Convert.ToInt32(txtSmall.Text) + Convert.ToInt32(txtMedium.Text)
+ Convert.ToInt32(txtLarge.Text) + Convert.ToInt32(txtExtralarge.Text);
txtTags.Text = String.Format("{0:#,##0}", TagsCollars);
txtCollars.Text = String.Format("{0:#,##0}", TagsCollars);
}
But once I save, it seems to be having a problem with the GrandTotal computation:
I suspect the error come from this computation:
private void ComputeGrandTotal()
{
double GrandTotal;
GrandTotal = (((Convert.ToInt32(txtSmall.Text) + Convert.ToInt32(txtMedium.Text) + Convert.ToInt32(txtLarge.Text) +
Convert.ToInt32(txtExtralarge.Text)) * .285) * 315);
double TagsCollars;
TagsCollars = Convert.ToInt32(txtSmall.Text) + Convert.ToInt32(txtMedium.Text) + Convert.ToInt32(txtLarge.Text) + Convert.ToInt32(txtExtralarge.Text);
txtTags.Text = String.Format("{0:#,##0}", TagsCollars);
txtCollars.Text = String.Format("{0:#,##0}", TagsCollars);
lblGrandtotal.Text = String.Format("{0:#,###,##0}", (GrandTotal + TagsCollars + TagsCollars));
}
I've tried commenting out all GrandTotal related values and functions, and the records begin to save with no problem. Here's a copy of my save function:
private void InsertNewRecord()
{
SqlCommand cmdInsert = new SqlCommand();
cmdInsert.Connection = cn;
cmdInsert.CommandType = CommandType.Text;
//cmdInsert.CommandType = CommandType.StoredProcedure;
cmdInsert.Parameters.AddWithValue("#QtySmall", Convert.ToInt32(txtSmall.Text));
cmdInsert.Parameters.AddWithValue("#QtyMedium", Convert.ToInt32(txtMedium.Text));
cmdInsert.Parameters.AddWithValue("#QtyLarge", Convert.ToInt32(txtLarge.Text));
cmdInsert.Parameters.AddWithValue("#QtyExtralarge", Convert.ToInt32(txtExtralarge.Text));
cmdInsert.Parameters.AddWithValue("#QtyTags", Convert.ToInt32(txtTags.Text));
cmdInsert.Parameters.AddWithValue("#QtyCollars", Convert.ToInt32(txtCollars.Text));
cmdInsert.Parameters.AddWithValue("#TotalWeight", Convert.ToInt32(txtTotalweight.Text));
cmdInsert.Parameters.AddWithValue("#NoWorkers", Convert.ToInt32(txtWorkersno.Text));
cmdInsert.Parameters.AddWithValue("#NoMachines", Convert.ToInt32(txtMachinesno.Text));
cmdInsert.Parameters.AddWithValue("#BomStatus", SqlDbType.VarChar).Value = txtStatus.SelectedItem.ToString();
cmdInsert.Parameters.AddWithValue("#StartDate", SqlDbType.DateTime).Value = dtpStart.Value;
cmdInsert.Parameters.AddWithValue("#EndDate", SqlDbType.DateTime).Value = dtpEnd.Value;
cmdInsert.Parameters.AddWithValue("#GrandTotal", Convert.ToInt32(lblGrandtotal.Text));
cmdInsert.CommandText = " INSERT INTO BillOfMaterials2 " + " (QtySmall, QtyMedium, QtyLarge, QtyExtralarge, QtyTags, QtyCollars, TotalWeight, NoWorkers, NoMachines, BomStatus, StartDate, EndDate, GrandTotal) VALUES (" + "#QtySmall, #QtyMedium, #QtyLarge, #QtyExtralarge, #QtyTags, #QtyCollars, #TotalWeight, #NoWorkers, #NoMachines, #BomStatus, #StartDate, #EndDate, #GrandTotal)";
//cmdInsert.CommandText = "spInsertBom";
cmdInsert.ExecuteNonQuery();
}
Any help would be much appreciated.
1st I would not attempt converting a textbox.text value to Int32 using convert; better to use
Int32 myint = 0;
Int32.TryParse(textbox.text, out myint) ;
This ensures that the text can be converted to an integer and if not you get 0 as a returned out value.
Then in your save method - your #GrandTotal parameter is trying to save to what datatype ? - what is the type in your database ?? do they match - if not you will get a format exception your data (Type) is not the same Format (type) as the column type.
The op does not have a valid number in the text box he has this:
lblGrandtotal.Text = String.Format("{0:#,###,##0}",
(GrandTotal + TagsCollars + TagsCollars));
This is why the code where he sets the parameter value = lblGrandtotal.Text it is not a number it has formatting commas etc.. he needs to remove those to make it work, using Int.TryParse would easily reveal this.
its starts here - first you are putting decimal in your format string with comma.
lblGrandtotal.Text = String.Format("**{0:#,###,##0}**", (GrandTotal + TagsCollars + TagsCollars));
also, later in your code you are storing INT int database, when its actually decimal.
and as mentioned by ken, use tryparse to convert the value from string to ....
There are several issues here some of the other posts touched on but, what really stands out (in my opinion) is you're not validating the input data, which is risky for many reasons, asking for future headaches and causing these issues. Also, there are standard numerical input controls you could use. If there's some reason you can't use them though, you should be validating the input and, if the data is not valid, handle it. Below is a quick way to validate and then handle invalid inputs.
private void ComputeGrandTotal()
{
//Since there are values that need to be validated and converted to integers for use in two calculations...
int smll, mdm, lg, xl;
//Validate the inputs can be converted and set the appropriate variable values at the same time
if (Int32.TryParse(txtSmall.Text, out smll) //using TryParse sets the integer variable values only if they can successfully be converted
&& Int32.TryParse(txtMedium.Text, out mdm)
&& Int32.TryParse(txtLarge.Text, out lg)
&& Int32.TryParse(txtExtralarge.Text, out xl)
)
{
int ttl = smll + mdm + lg + xl;
double GrandTotal, TagsCollars;
TagsCollars = ttl;
GrandTotal = TagsCollars * .285 * 315;
txtTags.Text = $"{TagsCollars:#,##0}"; //Resharper suggested simplification of String.Format("{0:#,##0}", TagsCollars)....I believe ReSharper
txtCollars.Text = $"{TagsCollars:#,##0}";
lblGrandtotal.Text = $"{(GrandTotal + TagsCollars + TagsCollars):#,###,##0}";
}
}
This will get the job done but it's pretty inflexible. Each input has to successfully convert to an integer or this will fail. A better, more time consuming approach would be something like this:
int smll;//, mdm, lg, xl;
try
{
smll = Convert.ToInt32(txtSmall.Text);
}
catch (FormatException)
{
smll = 0;
//txtSmall.Text value can't be converted to an integer
}
catch (Exception)
{
//some other issue occurred and you're probably better off just exiting entirely
return;
}
There are more flexible approaches out there, such as using number styles and such but, their flexibility comes at the price of you having to be more aware of the impact of what and how you're coding. Sometimes it's just safer to train your customers than write code you're not confident with.
You are trying to parse an integer in lblGrantotal.Text, but you are getting a FormatException, which means the text in lblGranTotal isn't recognized as a number. Maybe you are using comma , instead of point . as decimal separator, or something like that.
I have two columns in my table i-e Working Hours and Extra Hours for employee attendance and total time that employee has to work is nine (9) hours if somebody check in at 9 o ' clock and check out at 11 o ' clock then system automatically calculate his/her working hours and extra hours now in this case working hours will be 2 hours and extra hours will be in negative form e.g -7:00:00 hours because employee leave 7 hours before actual time and i stored this negative value as "varchar" in database for user facilitation to understand either employee worked more from given time or less.Now i need to add these extra hours of employee for creating summary of month but i don't know how to add these negative time span and when i convert it to timeofday format then system cannot convert it due to negative sign.
I calculate sum of working hours after reading it from database as:
wrkhrs=wrkhrs.Add(Convert.ToDateTime(dr["WorkHrs"].ToString()).TimeOfDay);
and i found some code for subtraction as:
TimeSpan exthrs = org_work.Subtract(tot_work);
but if timespan have negative sign then it don't work.
if any body have any idea then kindly share it . thanks in advance.
EDIT:
public Tuple<string,string> Calculate_Hours(int id,DateTime strt, DateTime end)
{
TimeSpan wrkhrs = new TimeSpan(0, 0, 0);
TimeSpan exthrs=new TimeSpan(0,0,0);
//select * from vw_Rept_Attend where UserID ='" + id + "' and convert(date,AtnDate) between '" + strt.Date + "' and '" + end.Date + "'
cmd = new SqlCommand("sp_Calculate_Hours_For_Report", conn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#id", id);
cmd.Parameters.AddWithValue("#startdate", strt.Date);
cmd.Parameters.AddWithValue("#end", end.Date);
conn.Open();
dr = cmd.ExecuteReader();
while (dr.Read())
{
if (dr["WorkHrs"].ToString().Length>0)
wrkhrs=wrkhrs.Add(Convert.ToDateTime(dr["WorkHrs"].ToString()).TimeOfDay);
if (!dr["ExtraHrs"].ToString().Contains("-") && dr["ExtraHrs"].ToString().Length > 0)
{
exthrs = exthrs.Add(Convert.ToDateTime(dr["ExtraHrs"].ToString()).TimeOfDay);
}
else if (dr["ExtraHrs"].ToString().Contains("-") && dr["ExtraHrs"].ToString().Length > 0)
{
string ext = dr["ExtraHrs"].ToString().Substring(dr["ExtraHrs"].ToString().LastIndexOf("-") +1);
exthrs = exthrs.Subtract(Convert.ToDateTime(ext).TimeOfDay);
}
}
conn.Close();
dr.Close();
return new Tuple<string, string>(string.Format("{0:00}:{1:00}:{2:00}", (int)TimeSpan.Parse(wrkhrs.ToString()).TotalHours, Math.Abs((int)TimeSpan.Parse(wrkhrs.ToString()).Minutes), Math.Abs((int)TimeSpan.Parse(wrkhrs.ToString()).Seconds)), string.Format("{0:00}:{1:00}:{2:00}", (int)TimeSpan.Parse(exthrs.ToString()).TotalHours, Math.Abs((int)TimeSpan.Parse(exthrs.ToString()).Minutes), Math.Abs((int)TimeSpan.Parse(exthrs.ToString()).Seconds)));
}
and called out put of above function as:
var mytuple = Calculate_Hours(id,Convert.ToDateTime(dtStart.Text), Convert.ToDateTime(dtEnd.Text));
string tot_workHrs= mytuple.Item1;
string tot_ExtHrs= mytuple.Item2;
above is my logic that i retrieve my correct output, if someone has any question then please feel free to ask me by creating new comment on this post.
hope now this will be helpful for all....
1.
Use TimeSpan.Duration():
https://msdn.microsoft.com/en-us/library/system.timespan.duration(v=vs.110).aspx
"Returns a new TimeSpan object whose value is the absolute value of
the current TimeSpan object"
2.
Or some code from HERE:
static string ToHMString(TimeSpan timespan) {
if (timespan.Ticks < 0) return "-" + ToHMString(timespan.Negate());
return timespan.TotalHours.ToString("#0") + ":" + timespan.Minutes.ToString("00");
}
Console.WriteLine(ToHMString(TimeSpan.FromHours(3))); //Prints "3:00"
Console.WriteLine(ToHMString(TimeSpan.FromHours(-27.75))); //Prints "-28:45"
First of all it is a bad idea to store a time/duration value as string in the database.
Second the .Net TimeSpan structure is perfectly able to represent negative durations and calculate with these correctly.
Suggestions:
Store your negative duration as a time/duration value in the databese. If your database has no duration type which supports negative values add a boolean "This value is negative"
In your C# code use TimeSpan and the usual operators: +and -
Does this do what you need?
string[] sample = new string[] { "2.1", "-7.0", "1.0" };
TimeSpan total =
sample
.Select(s => TimeSpan.FromHours(double.Parse(s)))
.Aggregate((ts0, ts1) => ts0.Add(ts1));
The total I get is -03:54:00 as a TimeSpan.
I have the following code:
int value = 0;
int day = 0;
int record = db.Orders.Where(x => x.Order_Date.Value.Year == DateTime.Now.Year
&& x.Order_Date.Value.Month == DateTime.Now.Month
&& x.Order_Date.Value.Day == DateTime.Now.Day).Count();
if (record > 0)
{
value = int.Parse(db.Orders.OrderByDescending(x => x.Order_Id).Select(y => y.Order_Id).First().ToString());
value += 1;
}
else
{
day = Convert.ToInt32(DateTime.Now.ToString("dd") + DateTime.Now.ToString("MM") + DateTime.Now.ToString("yy"));
value = day + 0001;
}
I want to save the order id in ddmmyysomevalue like 0501150001 if it is first order.
I do have above code but how to split 0501150001 like 050115 and 0001 and then increment 0002 for the next order and make it 0501150000 thereby 0501150009 to 0501150010 and make the above code meaningful.
You have to store value in string.
For number you have to use format like this.
int value = 1;
String finalnumber = DateTime.Now.ToString("dd") + DateTime.Now.ToString("MM") + DateTime.Now.ToString("yy") + value.ToString("0000");
Console.WriteLine(value.ToString("0000")); // to format number only.
// to get as a number
long finalnumber = Convert.ToInt64( DateTime.Now.ToString("dd") + DateTime.Now.ToString("MM") + DateTime.Now.ToString("yy") + value.ToString("0000"));
Because the code you've shown will always have the same length for each part of the Order Id you can split by length and cast the final part to an integer. Bear in mind this will break if any part of the orderId starts using a different length in future:
// get the date part of the string by length
string datePart = orderId.Substring(0, 6);
// get the count part of the string by length and cast to integer
int countPart = Int32.Parse(orderId.Substring(6, 4));
// increment the count
countPart++;
// convert the count back into a string and pad with zeroes to get length 4
string newCount = countPart.ToString().PadLeft(4, '0');
// combine with the datepart to get the new OrderId
string newOrderId = datePart + newCount;
This should point you in the right direction...
int existingTodayOrdersCount = db.Orders
.Where(x => x.Order_Date.Value.Date == DateTime.Now.Date)
.Count();
string newOrderId = string.Format(
"{0:yyMMdd}{1:0000}",
DateTime.Now,
existingTodayOrdersCount + 1);
When comparing two dates you can just call .Date on your DateTime objects. No need to explicitly compare the year, month and day
To concatenate numeric values or format a numeric value with leading zeros you need to treat it as a string
The above code uses a custom numeric format string (0000) and a custom date and time format string (yyMMdd).
Be aware of subtle bugs though...
What if you already have 9999 orders today?
What if this code is executed at midnight (could DateTime.Now wrap to the next day part way through execution?)
What if you delete an order record from the database, in which case newOrderId might clash with an existing order?
I am working on a C# project and I am facing an issue. The program allows the user to connect to a MySQL database and retrieve information from each selected table and write the data out to a file. The problem is because I have no idea what the schema is going to be like or what values its going to contain.
If the timestamp column contains the date 0000-00-00 00:00:00 I get the conversion error and no matter what I try it never works. I've tried converting to a string I've tried converting to a DateTime but I always get the error.
Below is how I am currently trying to get the data:
using (ConnectMySQLDB db = new ConnectMySQLDB(databaseSettings))
{
string query = string.Format("SELECT * FROM {0}.{1}", database, table);
Console.WriteLine("Query: {0}", query);
using (MySqlCommand cmd = new MySqlCommand(query, db.conn))
{
using (MySqlDataReader reader = cmd.ExecuteReader())
{
int i = 1;
while (reader.Read())
{
Console.WriteLine("ID: {0}", i);
fieldsAndValues = new Dictionary<string, string>();
foreach (ColumnDataTypes fieldAndType in fieldsAndTypes)
{
Console.WriteLine("Column: {0} Type: {1}", fieldAndType.field, fieldAndType.dataType);
string formattedValue = "";
if (fieldAndType.dataType == "timestamp")
{
DateTime date = DateTime.Parse(reader.GetDateTime(fieldAndType.field).ToString());
formattedValue = date.ToString("yyyyMMdd");
}
else
{
formattedValue = getDBFormattedValue(reader.GetString(fieldAndType.field), fieldAndType.dataType);
fieldsAndValues.Add(fieldAndType.field, formattedValue);
}
}
rows.Add(fieldsAndValues);
i++;
}
}
}
}
I also have added the allow zero date and convertzerodate to null option in the connector string as follows:
connString = "server=" + server + ";uid=" + username + ";pwd=" + password + ";port=" + port + ";Allow Zero Datetime=true;zeroDateTimeBehavior=convertToNull;Convert Zero Datetime=true";
Looking at this documentation, it looks like you're specifying two contradictory options (AllowZeroDateTime=true and ConvertZeroDateTime=true) and one which appears not to be listed (ZeroDateTimeBehavior=ConvertToNull).
I suggest that unless you have actual data which is DateTime.MinValue which you don't want to mix up with the "zero" value, you just specify ConvertZeroDateTime=true and detect if the result is DateTime.MinValue. You definitely shouldn't call reader.GetDateTime(), then convert the result to a string, and then back to a DateTime - you should avoid string conversions as far as you can, as they can mess things up for you pretty easily.
It's not really clear what string value you want for these "zero" values, but you should be able to special-case them with DateTime.MinValue fairly easily. Personally I'd actually try to keep the data in its "native" form as much as possible rather than converting everything to strings, but that's a different battle.
Currently, I'm returning feedback to the user in this form:
"<UserName> removed 07:00, 07:15, 07:30, 07:45, 08:00, 08:15, 08:30, 09:00, 09:15, 09:30, 09:45, 10:00, 10:15, 10:30, 11:15, (etc.)"
...Understandably, they want it to be more user-friendly, such as this instead:
<UserName> removed 07:00 - 8:30, 9:00 - 10:30, 11:15 - (etc.)
Rather than rework the whole method that concatenates these values into a StringBuilder, I'd like to take that first output and morph it into the second; something like:
sbQuarterHoursRemoved = CombineSucceedingQuarterHours(sbQuarterHoursRemoved);
Is this as tedious as it appears to me, or does somebody know a relatively painless way of accomplishing it?
UPDATE
I adapted the code below to this:
String QuarterHoursRemovedPrettified = PrettifyQuarterHoursRemoved(sbQuarterHoursRemoved);
. . .
private static string PrettifyQuarterHoursRemoved(StringBuilder sbQuarterHoursRemoved)
{
string[] times = sbQuarterHoursRemoved.ToString().Split(',');
DateTime prevDt = new DateTime(1);
string prevString = "";
StringBuilder output = new StringBuilder();
foreach (string time in times) {
DateTime dt = DateTime.ParseExact(time, "HH:mm", CultureInfo.InvariantCulture);
if (dt.Subtract(prevDt).TotalMinutes > 15) {
if (prevString != "")
output.Append(" " + prevString + ",");
output.Append(" " + time + " -");
}
prevString = time;
prevDt = dt;
}
output.Remove(output.Length - 1, 1);
return output.ToString();
}
With values in times like this (after the call to Split()):
00:45
01:00
01:15
22:45
23:00
It ALWAYS crashes the second pass through the loop. The first goes fine, but the second time, no matter what value, crashes.
e.g., the first time through, dt becomes:
dt = 6/26/2012 12:45 am
...after the call to ParseExact()
...but the second call to ParseExact() - with, for example, "01:00" as the value in "time" fails.
Some error details are:
*System.FormatException was unhandled
Message=String was not recognized as a valid DateTime.
Source=mscorlib
StackTrace:
at System.DateTimeParse.ParseExact(String s, String format, DateTimeFormatInfo dtfi, DateTimeStyles style)
at System.DateTime.ParseExact(String s, String format, IFormatProvider provider)
at TitanNextGen_Platypi.PlatypiMainForm.PrettifyQuarterHoursRemoved(StringBuilder sbQuarterHoursRemoved) in...*
UPDATED AGAIN
It works now - untrimmed vals were the problem. When the value was, for example, " 01:15" instead of "01:15" all Dallas broke loose (and I changed "hh:mm" to "HH:mm" in the call to ParseExact() to account for 24-hour time)
Well you will need to write your own function, because you are manipulating a string, and not raw data. That said, you will be able to do it in linear time (you will only have to parse the string once).
Steps include:
Tokenize the string (using commas as the delimiters)
Loop through these tokens, incrementing your response variables for each successive 15 minute interval.
7:00, 7:15, 7:30, 7:45, 8:30
7:00 - 7:30, 7:45, 8:30
7:00 - 7:45, 8:30
Just a general overview of the process. You'll need to look into a string tokenizer (i'm a java guy, myself, but I know there is one built in) You should be able to index into the tokens to reference the hours and the tens of minutes digits and use a switch statement to respond accordingly.
As you are processing the strings, you will need to keep track of where you are at and if you need to start a new sequence:
string[] times = { "07:00", "07:15", "07:30", "07:45", "08:00", "08:15", "08:30", "09:00", "09:15", "09:30", "09:45", "10:00", "10:15", "10:30", "11:15" };
private void Form1_Load(object sender, EventArgs e)
{
DateTime prevDt = new DateTime(1);
string prevString = "";
StringBuilder output = new StringBuilder("UserXyz Deleted ");
foreach (string time in times)
{
DateTime dt = DateTime.ParseExact(time,"hh:mm", CultureInfo.InvariantCulture);
if (dt.Subtract(prevDt).TotalMinutes > 15)
{
if (prevString != "")
output.Append(" " + prevString + ",");
output.Append(" " + time + " -");
}
prevString = time;
prevDt = dt;
}
output.Remove(output.Length - 1, 1);
MessageBox.Show(output.ToString());
}