Checking if string is TimeSpan - c#

I am importing data from an Excel file and i need to check if the data i am importing is a TimeSpan or a regular string. Because the data can be either 07:00:00 or D2 07:00. In the excel file some of the fields are formatted to be tt:mm but others are plain text fields.
My code look like this:
public void ReadExcelFile()
{
string filename = #"C:\Temp\Copy2.xlsx";
using (OleDbConnection connection = new OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + filename + ";Extended Properties=\"Excel 12.0;HDR=YES;IMEX=1\""))
{
try
{
connection.Open();
string sqlCmd1 = "SELECT * FROM [Sheet1$]";
using (OleDbCommand command = new OleDbCommand(sqlCmd1, connection))
{
command.CommandType = System.Data.CommandType.Text;
using (OleDbDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
alias = "" + reader[3];
codeT = "" + reader[4];
dtTruck = "" + reader[5];
codeP = "" + reader[6];
dtPlane = "" + reader[7];
dtDealer = "" + reader[8];
TimeSpan ts;
bool TruckisValid = TimeSpan.TryParse(dtTruck, CultureInfo.InvariantCulture, out ts);
bool PlaneisValid = TimeSpan.TryParse(dtPlane, out ts);
if (TruckisValid )
{
truck = TimeSpan.FromDays(Convert.ToDouble(dtTruck));
}
else if (PlaneisValid)
{
plane = TimeSpan.FromDays(Convert.ToDouble(dtPlane));
}
else
{
}
if (dtTruck == "" && dtPlane == "")
{
dtTruck = "";
dtPlane = "";
}
else if (dtTruck != "")
{
truck = TimeSpan.FromDays(Convert.ToDouble(dtTruck));
dtPlane = "";
}
else if (dtPlane != "")
{
plane = TimeSpan.FromDays(Convert.ToDouble(dtPlane));
dtTruck = "";
}
SearchForAdrIDAndCustID(Convert.ToString(reader[0]), Convert.ToString(reader[3]));
InsertData(custID, "" + reader[3], adrID, truck, codeT, plane, codeP, dtDealer);
}
}
}
}
catch (Exception exception)
{
Console.WriteLine("ERROR in ReadExcelFile() method. Error Message : " + exception.Message);
}
}
}
As you can see i have tried to use a bool to determine if the imported field is a timespan or not. But the bool is always false.
can anyone help ?

Here's the relevant part of your question:
TimeSpan ts;
bool TruckisValid = TimeSpan.TryParse(dtTruck, CultureInfo.InvariantCulture, out ts);
bool PlaneisValid = TimeSpan.TryParse(dtPlane, out ts);
if (TruckisValid )
{
truck = TimeSpan.FromDays(Convert.ToDouble(dtTruck));
}
else if (PlaneisValid)
{
plane = TimeSpan.FromDays(Convert.ToDouble(dtPlane));
}
else
{
}
You're reusing the same TimeSpan variable ts for two different fields. I assume that you use it just to test if it can be parsed. Instead you should use the parsed TimeSpan.
You're testing for TimeSpan but you're converting it to double. It is either convertable to TimeSpan or to double not both.
So maybe this approach is better:
double d;
TimeSpan ts;
bool TruckisValidDouble = false;
bool TruckisValidTimeSpan = TimeSpan.TryParse(dtTruck, CultureInfo.InvariantCulture, out ts);
if(!TruckisValidTimeSpan)
{
TruckisValidDouble = double.TryParse(dtTruck, out d);
}
// use approapriate variables
From comments:
the reason for converting it to double is because when i import the
data from my excel file it has the percentage value of 07:00:00 witch
is 0,29166666667. So im converting the double value to a timespan.
But you cannot check for TimeSpan and parse to double or vice-versa. If you know that it's a TimeSpan via TryParse use the TimeSpan variable directly. Otherwise double.TryParse and use that variable afterwards.
For example:
if(!TruckisValidTimeSpan && TruckisValidDouble)
{
ts = TimeSpan.FromHours(d * 24);
}

You could use a regex to check if your value is a TimeSpan like so:
Regex reg = new Regex("^\d{2}:\d{2}:\d{2}$");
bool TruckIsValid = reg.IsMatch(dtTruck);

Related

Getting error string was not recognized as a valid using c#

when i am try to change date format from csv file 3/1/2021-3/31/2021 to Mar-01-2021-Mar-31-2021 getting error please help me solve the issue.
foreach (DataRow dr in dtDataTable.Rows)
{
for (int i = 0; i < dtDataTable.Columns.Count; i++)
{
if (ListInputDateColumns.Contains(dtDataTable.Columns[i].ColumnName))
{
if(dtDataTable.Columns[i].ColumnName == "Period")
{
sw.Write(dr[i] = Convert.ToDateTime(dtDataTable.Rows[i][dtDataTable.Columns[i].ColumnName.ToString()]).ToString("MMM-dd-yyyy-MMM-dd-yyyy"));//Here i am getting error
}
else
{
sw.Write(dr[i] = Convert.ToDateTime(dtDataTable.Rows[i][dtDataTable.Columns[i].ColumnName.ToString()]).ToString("MMM-dd-yyyy hh:mm:ss tt"));
}
//sw.Write(dr[i].ToString());
}
else
{
sw.Write(dr[i].ToString());
}
//sw.Write(dr[i].ToString());
sw.Write(",");
}
sw.Write(sw.NewLine);
}
sw.Close();
}
}
You have to parse that string by splitting it by the delimiter and then parse each token to DateTime. Then you can convert it back to string with the desired format:
string targetFormat = "MMM-dd-yyyy";
string s = " 3/1/2021-3/31/2021";
string[] tokens = s.Split('-');
if(tokens.Length == 2)
{
bool validFrom = DateTime.TryParse(tokens[0], CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime fromDate);
bool validTo = DateTime.TryParse(tokens[1], CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime toDate);
if (validFrom && validTo)
{
string result = $"{fromDate.ToString(targetFormat, CultureInfo.InvariantCulture)}-{toDate.ToString(targetFormat, CultureInfo.InvariantCulture)}";
}
}
OK, we can do this in one line of code if you want, but it gets pretty ugly as far as readability goes. For a one liner, simply change this line:
sw.Write(dr[i] = Convert.ToDateTime(dtDataTable.Rows[i][dtDataTable.Columns[i].ColumnName.ToString()]).ToString("MMM-dd-yyyy-MMM-dd-yyyy"));//Here i am getting error
to
sw.Write(dr[i] = Convert.ToDateTime((dtDataTable.Rows[i][dtDataTable.Columns[i].ColumnName.ToString()]).Substring(0, (dtDataTable.Rows[i][dtDataTable.Columns[i].ColumnName.ToString()]).IndexOf('-'))).ToString("MMM-dd-yyyy") + "-" + Convert.ToDateTime((dtDataTable.Rows[i][dtDataTable.Columns[i].ColumnName.ToString()]).Substring((dtDataTable.Rows[i][dtDataTable.Columns[i].ColumnName.ToString()]).IndexOf('-') + 1)).ToString("MMM-dd-yyyy");
but for maintainability, I would recommend creating a quick method for parsing "Period" like this
string ParsePeriod(string period)
{
DateTime from = Convert.ToDateTime(period.Substring(0, period.IndexOf('-')));
DateTime to = Convert.ToDateTime(period.Substring(period.IndexOf('-') + 1));
return from.ToString("MMM-dd-yyyy") + "-" + to.ToString("MMM-dd-yyyy");
}
then call it like this by changing this:
sw.Write(dr[i] = Convert.ToDateTime(dtDataTable.Rows[i][dtDataTable.Columns[i].ColumnName.ToString()]).ToString("MMM-dd-yyyy-MMM-dd-yyyy"));//Here i am getting error
to this:
sw.Write(dr[i] = ParsePeriod(Convert.ToString(dtDataTable.Rows[i][dtDataTable.Columns[i].ColumnName.ToString()])));
I trust this solves your problem. ;-)

How to do unit test on a method that has StreamReader and Database access

I have never done unit tests before. I'd like to learn how to do it. I'd like to use Visual Studio unit test and moq.
My project is transferring data from interbase to SQL Server. Firstly, I extract data from interbase into a plain text file. The layout is FieldName + some spaces up to 32 char length + field value. Then, I write a method that reads the text file line by line; once it reaches the next record, it inserts the current record into SQL Server.
So it involves in stream reader and SQL database insertion. For the stream reader, I read some post on the Internet and I pass the Stream reader as the method's parameter; but the SQL Server part, I have no idea how to simplify my method so that it can be tested.
I really need your help.
public partial class TableTransfer
{
#region declare vars
public string FirstFldName = "";
public string ErrorMsg = "";
public List<MemoBlobTrio> MemoBlobs = null;
public string SqlServerTableName = "";
#endregion
public bool DoTransfer(System.IO.StreamReader sr, Func<TransferShare, string, string, bool> TransferTable)
{
#region declare var
bool DoInsert = true;
TransferShare transferShare = null;
string line = string.Empty;
string blobLines = string.Empty;
string fldName = string.Empty;
string value = string.Empty;
bool Is1stLine = true;
bool isMemoFld = false;
MemoBlobTrio memoBlobTrio = null;
int idx = 0;
#endregion
try
{
using(sr)
{
transferShare = new TransferShare();
ConnectSQLServer(transferShare);
transferShare.StartInsert(SqlServerTableName);
bool readNext = true;
do
{
try
{
if (readNext)
line = sr.ReadLine();
if ((line != null) && (line.Trim() != ""))
{
fldName = line.Length > 30 ? line.Substring(0, 31).TrimEnd() : "";
Is1stLine = fldName == FirstFldName;
if (Is1stLine)
{
if (DoInsert)
EndInsert(transferShare, line);
else
transferShare.ClearSQL();
DoInsert = true;
}
idx = 0;
isMemoFld = false;
while (idx < MemoBlobs.Count)
{
if (fldName == (MemoBlobs[idx] as MemoBlobTrio).fbFldName)
{
memoBlobTrio = MemoBlobs[idx] as MemoBlobTrio;
line = InsertMemoBlob(transferShare, sr, memoBlobTrio.ssFldName, fldName, memoBlobTrio.fbNextFldName);
readNext = false;
isMemoFld = true;
}
idx++;
}
if (!isMemoFld)
{
if (line.Length > 31)
value = line.Remove(0, 31);
else
value = "";
if (!TransferTable(transferShare, fldName, value))
DoInsert = false;
readNext = true;
}
}
}
catch (Exception err)
{
HandleError(err, line);
}
} while (line != null);
if (DoInsert)
EndInsert(transferShare, line);
}
}
finally
{
transferShare.SQLConn.Dispose();
}
return true;
}
private static void ConnectSQLServer(TransferShare transferShare)
{
TransferShare.SQLServerConnStr = "Data Source=" + Environment.MachineName + "\\SQLEXPRESS;Initial Catalog=MyDB;Integrated Security=True";
transferShare.SQLConn.ConnectionString = TransferShare.SQLServerConnStr;
transferShare.SQLConn.Open();
}
}
public class TransferShare
{
public void StartInsert(string TableName)
{
tableName = TableName;
}
public void EndInsert(TransferShare transferShare, string line)
{
SqlCommand Cmd = null;
try
{
sqlInsFld = sqlInsFld.Remove(sqlInsFld.Length - 1);
sqlInsValue = sqlInsValue.Remove(sqlInsValue.Length - 1);
sqlInsFld = "Insert into " + tableName + " (" + sqlInsFld + ")";
sqlInsValue = " Values (" + sqlInsValue + ")";
Cmd = new SqlCommand(sqlInsFld + sqlInsValue, SQLConn);
Cmd.ExecuteNonQuery();
}
catch (Exception err)
{
throw (new Exception(err.Message));
}
finally
{
sqlInsFld = "";
sqlInsValue = "";
}
}
}

C# get offset for ISO8601 date from Windows settings

I have to maximise the use of ISO8601 dates in a C# project I'm using, especially when it comes to saving dates in the database.
In the same project I am creating Outlook appointments.
List<string> Recipients = new List<string>();
Recipients.Add("person#place.com");
string AppointmentSubject = "test subject";
string AppointmentBody = "test body";
string AppointmentLocation = "test location"; // Where do I get a list of meeting rooms?
string Offset = GetISO8601OffsetForThisMachine();
DateTime AppointmentStart = DateTime.Parse("2016-10-07T08:00:00" + Offset);
DateTime AppointmentEnd = DateTime.Parse("2016-10-07T10:00:00" + Offset);
Boolean IsAtDesk = true;
CreateOutlookAppointment(Recipients, AppointmentLocation, AppointmentSubject, AppointmentBody, AppointmentStart, AppointmentEnd, IsAtDesk);
Being in UK, our offset is either +1 or +0 due to daylight saving.
Is there a way to get the offset programatically based on the Windows locale settings?
You can probably do something like :
DateTime AppointmentStart = DateTime.Parse("2016-10-07T08:00:00").ToLocalTime();
Calculate the offset with the following:
public static string GetISO8601OffsetForThisMachine()
{
string MethodResult = null;
try
{
TimeSpan OffsetTimeSpan = TimeZoneInfo.Local.GetUtcOffset(DateTime.Now);
string Offset = (OffsetTimeSpan < TimeSpan.Zero ? "-" : "+") + OffsetTimeSpan.ToString(#"hh\:mm");
MethodResult = Offset;
}
catch //(Exception ex)
{
//ex.HandleException();
}
return MethodResult;
}

Unknown error - cmdlet Invoke-SCScript

So this is what I have (full add-in code):
using Microsoft.SystemCenter.VirtualMachineManager;
using Microsoft.SystemCenter.VirtualMachineManager.UIAddIns;
using Microsoft.SystemCenter.VirtualMachineManager.UIAddIns.ContextTypes;
using Microsoft.VirtualManager.Remoting;
using System;
using System.AddIn;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Forms;
namespace Microsoft.VirtualManager.UI.AddIns.BackupAddIn
{
[AddIn("Make Backup")]
public class BackupAddIn : ActionAddInBase
{
protected const string PowershellPath = "%WINDIR%\\System32\\WindowsPowershell\\v1.0\\powershell.exe";
protected const string DefaultDirectory = "C:\\ClusterStorage\\Volume2";
protected const string WildcardVMName = "{{VMName}}";
protected const string WildcardError = "{{Error}}";
protected const string BackupDirectoryBase = "{{Base}}";
protected const string BackupDirectoryName = "{{Name}}";
protected const string BackupDirectoryDate = "{{Date}}";
public enum JobState { Initialized, Success, Fail };
protected const string BackupDirectoryTemplate = BackupDirectoryBase + "\\" + BackupDirectoryName + "\\" + BackupDirectoryDate + "\\";
protected static readonly ReadOnlyCollection<string> AllowedBackupStates = new ReadOnlyCollection<string>(new string[] { "PowerOff", "Paused"/*, "Saved"*/});
public override bool CheckIfEnabledFor(IList<ContextObject> contextObjects)
{
if (contextObjects != null && contextObjects.Count > 0)
{
foreach (var host in contextObjects.OfType<HostContext>())
{
if (host.ComputerState != ComputerState.Responding)
{
return false;
}
}
return true;
}
return false;
}
public override void PerformAction(IList<ContextObject> contextObjects)
{
if (contextObjects != null)
{
// check if we have VMs selected
var VMs = contextObjects.OfType<VMContext>();
if (VMs != null && VMs.Count() > 0)
{
// check if VMs are in a good state
var badVMs = VMs.Where(vm => AllowedBackupStates.Contains(vm.Status.ToString()) == false).ToArray();
if (badVMs != null && badVMs.Length > 0)
{
MessageBox.Show("Backup not possible!\r\nThe following VMs are still running:\r\n\r\n" + string.Join(", ", badVMs.Select(vm => vm.Name)));
}
else
{
// ask for backup directory
string backupDir = Microsoft.VisualBasic.Interaction.InputBox("Enter a path on the host to export the selected virtual machine(s) to.", "Export path", DefaultDirectory);
if (string.IsNullOrEmpty(backupDir) == false)
{
if (backupDir.EndsWith("\\"))
{
backupDir = backupDir.Substring(0, backupDir.Length - 1);
}
// go
/*foreach (var vm in VMs)
{
exportVM(vm, backupDir);
}*/
// testing to export multiple vms in one invoke
exportVMs(VMs, backupDir);
}
}
}
}
}
public string getDate()
{
var date = DateTime.Now;
return date.Year.ToString()
+ (date.Month < 10 ? "0" : "") + date.Month.ToString()
+ (date.Day < 10 ? "0" : "") + date.Day.ToString()
+ "_"
+ (date.Hour < 10 ? "0" : "") + date.Hour.ToString()
+ (date.Minute < 10 ? "0" : "") + date.Minute.ToString();
}
public void ManageJob(string name, JobState state, string message = null)
{
string command;
if (state == JobState.Initialized)
{
command = string.Format("New-SCExternalJob -Name \"{0}\"", name);
}
else if (state == JobState.Success)
{
command = string.Format("Set-SCExternalJob -Job (Get-SCJob -Name \"{0}\")[0] -Complete -InfoMessage \"" + (string.IsNullOrEmpty(message) ? "Backup successfully started." : message.Replace("\"", "'")) + "\"", name);
}
else
{
command = string.Format("Set-SCExternalJob -Job (Get-SCJob -Name \"{0}\")[0] -Failed -InfoMessage \"" + (string.IsNullOrEmpty(message) ? "Backup FAILED." : message.Replace("\"", "'")) + "\"", name);
}
//MessageBox.Show(command);
PowerShellContext.ExecuteScript<Host>(
command,
(profiles, error) =>
{
if (error != null)
{
MessageBox.Show("Cannot modify job state\r\nError: " + error.Problem);
}
}
);
}
public void exportVMs(IEnumerable<VMContext> VMs, string backupDir)
{
string date = getDate();
string VMS = "";
string fullBackupDirS = BackupDirectoryTemplate.Replace(BackupDirectoryBase, backupDir).Replace(BackupDirectoryName, "_VMBackups").Replace(BackupDirectoryDate, date);
VMS = "'" + string.Join("', '", VMs.Select(vm => vm.Name).ToArray()) + "'";
string command = string.Format("Export-VM -Name {0} -Path '{1}'", VMS, fullBackupDirS);
MessageBox.Show(command);
// We need to manager jobs in another thread probably --------------------------------------------------------------!!!
string jobname = "Starting_backup_of_multiple_machines";
mkShortcuts(backupDir, date, VMs.Select(vm => vm.Name).ToArray(), VMs.First());
//! execPSScript(jobname, scvmmPsCommand(command, VMs.First()), VMs.First(), WildcardVMName + ": Backup successful.", WildcardVMName + ": Backup FAILED!\r\nError: " + WildcardError, backupDir, date, VMs.Select(vm => vm.Name).ToArray());
}
public String scvmmPsCommand(string command, VMContext vm, string appPath = PowershellPath)
{
return string.Format("Invoke-SCScriptCommand -Executable {0} -VMHost (Get-SCVMHost -ID \"{1}\") -CommandParameters \"{2}\" -RunAsynchronous -TimeoutSeconds 360000", appPath, vm.VMHostId.ToString(), command);
}
// Make a shortcut from the machines backup directory to the backup in the "_VMBackups"-folder
public void mkShortcuts(string path, string date, string[] names, VMContext vm)
{
string command = "$shell = New-Object -ComObject WScript.Shell;";
foreach (var n in names)
{
command = command + string.Format(" $shortc = $shell.CreateShortcut('{0}\\{1}\\{2}.lnk'); $shortc.TargetPath = '{0}\\_VMBackup\\{2}\\{1}'; $shortc.Save();", path, n, date);
}
string fullCommand = scvmmPsCommand(command, vm);
MessageBox.Show(fullCommand);
execPSScript("Create_ShortcutS", fullCommand, vm, "Shortcut(s) created.", "FAILED to create Shortcut(s)!");
}
public void execPSScript(string jobname, string command, VMContext vm, string successMessage, string errorMessage, string path = "", string date = "", string[] names = null)
{
ManageJob(jobname, JobState.Initialized);
PowerShellContext.ExecuteScript<Host>(
command,
(vms, error) =>
{
if (error != null)
{
ManageJob(jobname, JobState.Fail, errorMessage.Replace(WildcardVMName, vm.Name).Replace(WildcardError, error.Problem));
}
else
{
ManageJob(jobname, JobState.Success, successMessage.Replace(WildcardVMName, vm.Name));
if (string.IsNullOrEmpty(path) == false)
{
//mkShortcuts(path, date, names, vm);
}
}
}
);
}
}
}
When I run the plugin I get an error Box that says sth like: "Unknown script-error. Expression not closed - ")" missing.
+ ... andard'; .Save();
+ ~
An expression was expected after '('.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordEx
ception
+ FullyQualifiedErrorId : ExpectedExpression". Weitere Informationen finden Sie im Standardfehlerprotokoll "C:\Windows\TEMP\gce_stderrord07b04547c74493caa6bdba9087df444.log".
But I can't find the error in my code since it worked when typed manually into the powershell on the host and .Save() takes no arguments. Do you have any idea?
OK, Instead of
command2 = command2 + string.Format(" ${1} = $shell.CreateShortcut(\"C:\\ClusterStorage\\Volume2\\test.lnk\"); ${1}.TargetPath = \"{0}\\_VMBackup\\{2}\\{1}\"; ${1}.Save();", backupDir, vm.Name, date);
Try
command2 = command2 + string.Format(" $shortc = $shell.CreateShortcut(\"C:\\ClusterStorage\\Volume2\\test.lnk\"); $shortc.TargetPath = \"{0}\\_VMBackup\\{2}\\{1}\"; $shortc.Save();", backupDir, vm.Name, date);
command2 = "'" + command2 + "'"
This did not work and neither did a lot of other things.
Basically, the problem is the way the command string is passed to PowerShell to execute.
PowerShell will examine the sting and attempt to enumerate all variables. This is not the desired behaviour. Tried to enclose the entire string in single quotes, however this did not help.
In the end the solution was to use the PowerShell escape character “`” to mask all variable names.
command2 = command2 + string.Format(" `$shortc = `$shell.CreateShortcut(\"C:\\ClusterStorage\\Volume2\\test.lnk\"); `$shortc.TargetPath = \"{0}\\_VMBackup\\{2}\\{1}\"; `$shortc.Save();", backupDir, vm.Name, date);

c# why wont this display to label

I want to be able to display balance to label and my code is not working. This is code I got so far:
SqlDataReader readdata;
{
sqlCommandbalance.Connection.Open();
sqlCommandbalance.Parameters["#accountID"].Value = show.accoundID;
readdata = sqlCommandbalance.ExecuteReader();
string balanceDB = null;
while (readdata.Read())
{
balanceDB = readdata["balance"].ToString();
}
sqlCommandbalance.Connection.Close();
balanceShow.Text += " " + balanceDB.ToString();
}
On this line - balanceShow.Text += " " + balanceDB.ToString(); got a error saying Object reference not set to an instance of an object.
You're calling balanceDB.ToString(), when balanceDB will be null if the data reader contains no rows.
Note that balanceDB.ToString() is redundant, since balanceDB is already a string. Just take out the ToString call.
You could also initialize balanceDB as
string balanceDB = "";
Originally you set balanceDB = null;.
If no data was found, then no assignment will be made to balanceDB in the while loop, so it stays undefined.
It's because of string balanceDB = null;
Set that to string balanceDB = string.Empty; and you should be OK
Readdata.read probably returned false, making the while to never run. Thus balanceDB is still null.
Looks like your query is returning an empty set. You should check for that before you set the balanceShow.Text:
if (balanceDB != null) {
balanceShow.Text += " " + balanceDB.ToString();
} else {
// tell the user that id: show.accoundID has no balance
}
Probably your code is returing null in dataRader and consequentily you're tanking an null value on balanceDB. I recomend you do something like this:
public static string GetBalance() {
string result = string.Emtpy;
using (var connection = /* Build your SqlConnection */) {
using (var cmd = new SqlCommand("your select command here", connection) {
try
{
connection.Open();
// you don't need a reader to take just a single value
object value = cmd.ExecuteScalar();
if (value != null)
result = value.ToString();
}
catch(Exception ex)
{
/* you've got an error */
}
finally
{
connection.Close();
}
}
}
return result;
}
and in your page:
string balance = GetBalance();
if (!string.IsNullOrEmpty(balance))
balanceShow.Text += string.Contat(" ", balance);
if you wish to concat the balanceShow label, use '+=' operator, instead of this you just '='.
PS: Sorry for my english!

Categories