How to merge equal opening hours on consecutive days? - c#

I am trying to merge equal opening hours on consecutive days.
public class AgendaDay {
public string Day;
public string AM;
public string PM;
public AgendaDay(string day, string aM, string pM)
{
Day = day;
AM = aM;
PM = pM;
}
}
var agenda = new List<AgendaDay>() {
new AgendaDay("Mon", "0900-1200", "1400-1700"),
new AgendaDay("Tue", "0900-1200", "1400-1700"),
new AgendaDay("Wed", "", "1400-1700"),
new AgendaDay("Thu", "0900-1200", "1400-1700"),
new AgendaDay("Fri", "0900-1200", "1400-1700"),
new AgendaDay("Sat", "0900-1200", ""),
null
};
Reducing it to :
var expected = #"Mon-Tue: 09H00-12H00 / 14H00-17H00
Wed: 14H00-17H00
Thu-Fri: 09H00-12H00 / 14H00-17H00
Sat: 09H00-12H00";
For now I have:
public class ShortAgendaDay
{
public List<string> Days;
public string AM;
public string PM;
public ShortAgendaDay(List<string> days, string aM, string pM)
{
Days = days;
AM = aM;
PM = pM;
}
}
private static string ShrinkOpenningHour( List<AgendaDay> agenda)
{
var temp = agenda.Where(x=> x!=null).ToArray();
List<ShortAgendaDay> sum = new List<ShortAgendaDay>();
for (var i = 0; i < temp.Count(); i++)
{
if (i == 0) continue;
var last = temp[i - 1];
var curr = temp[i];
if (last.AM == curr.AM && last.PM == curr.PM) {
}
}
throw new NotImplementedException();
}
My idea was to reduce it to a list like:
{
day:["Mon","Tue"],
hour: "09H00-12H00 / 14H00-17H00"
},
{
day:["Wed"],
hour:"14H00-17H00"
},
{
day:["Thu","Fri"],
hour:"09H00-12H00 / 14H00-17H00"
}
Then to take only the First and the Last from the list of Day.
The format from "0900-1200" to "09h00-12h00" is not an issue you can ignore it in your answer.
"0900-1400", "1400-1700" is only equals to "0900-1400", "1400-1700". there is no trick like "0900-1000", "1000-1700". Overlapping timestamp do not really matter. It's a string and must be handled as a string, not timestamps with overlapping timespans.

Here is the merging:
private string ShrinkOpenningHour(List<AgendaDay> agenda)
{
var temp = agenda.Where(x => x != null).ToArray();
List<ShortAgendaDay> sum = new List<ShortAgendaDay>();
for (var i = 0; i < temp.Count(); i++)
{
if (i == 0 || (temp[i - 1].heureAM == temp[i].heureAM && temp[i - 1].heurePM == temp[i].heurePM))
{
var existingEntry = sum.Where(x => x.AM == temp[i].heureAM && x.PM == temp[i].heurePM);
if (existingEntry.Count() == 1)
{
existingEntry.First().Days.Add(temp[i].jour);
}
else
{
sum.Add(new ShortAgendaDay(new List<string>() { temp[i].jour }, temp[i].heureAM, temp[i].heurePM));
}
}
}
return string.Join(Environment.NewLine, sum.Select(x => FormatHorraire(x)));
}
And as bonus the formating:
private string FormatHorraire(ShortAgendaDay x)
{
string days = string.Join(" - ", new String[] { x.Days.First(), x.Days.Last() }.Where(ar => ar != null).Distinct());
string hour = FormatHorraire(x.AM, x.PM);
return days + " : " + hour;
}
private string FormatHorraire(string am, string pm)
{
return string.Join(" / ", new string[] { am, pm }.Where(y => !string.IsNullOrWhiteSpace(y) && y.Length > 7).Select(y => y.Insert(2, "H").Insert(8, "H")));
}

Related

How to get total annual sales

In my ASP.NET Core 6 Web API, I have used the code below to calculate Total Annual Sales (from Transactions):
private async Task<decimal> GetAllTotalMonthlyTransactions()
{
string transactionMonth = DateTime.Now.ToString("MM");
decimal totalMonthlyTransactions = 0;
var sales = await _dbContext.Sales.ToListAsync();
foreach (var item in sales)
{
var salesDate = item.CreatedAt.ToString();
var salesMonth = salesDate.Substring(3, 2);
if (transactionMonth == salesMonth)
{
totalMonthlyTransactions += item.Amount;
}
}
return totalMonthlyTransactions;
}
How to I re-write the code above to get TotalAnnualSales (Transactions)?
private async Task<decimal> GetAllTotalMonthlyTransactions()
{
int currentYear = DateTime.Now.Year;
return (await dbContext.Sales.Where(t=>t.CreatedAt.Year==currentYear).ToListAsync()).Sum(item=>item.Amount);
}
private async Task<decimal> GetAllTotalMonthlyTransactions()
{
decimal totalAnnualTransactions = 0;
int _year = DateTime.Now.Year;
var sales = await dbContext.Sales.Where(y=>y.CreatedAt.Year==_year).ToListAsync();
// The first method
foreach (var item in sales)
{
totalAnnualTransactions += item.Amount;
}
// The second method
//double totalAnnualTransactions= sales.Sum(item => item.Amount);
return totalAnnualTransactions;
}
Instead of converting the datetimes to strings, try accessing them as ints. You can do this with .Month or .Year respectively
You can try to calculate from and to date range based on a specified month, then query/filter sales data with these from and to date range, like below.
var date1 = DateTime.Now;
var days = DateTime.DaysInMonth(date1.Year, date1.Month);
var from = new DateTime(date1.Year, date1.Month, 1, 0, 0, 0);
var to = new DateTime(date1.Year, date1.Month, days, 23, 59, 59);
var totalMonthlyTransactions = sales.AsEnumerable().Where(s => s.CreatedAt >= from && s.CreatedAt <= to).Select(s => s.Amount).Sum();
I hope following code will work for you...
private async Task<IEnumerable<object>> GetMonthlyTransactions(int year)
{
var starting = new DateTime(year, 1, 1).Date;
var sales = await context.Sales.Where(f => f.SalesDate >= starting).Select(s => new
{
Date = s.SalesDate.ToString("MM-yyyy"),
Amount = s.NetAmount
}).ToListAsync();
var finaldata = sales.GroupBy(s => s.Date, (d, a) => new { Date = d, Am = a }).Select(l => new
{
Date = l.Date,
Amount = l.Am.Sum(h => h.Amount)
});
return finaldata;
}

Get opposite of collection date times within the same day

I am wondering if anyone can help me, I have a collection of work start times and end times within a single day. I want to show the opposite also, so the times to which the person has not worked on that day. The issue I am having with my code is that it is carrying onto the next day whereas I just want that day.
public class WorkDay
{
public DateTime Day { get; set; }
public List<Worked> WorkingTimes { get; set; }
}
public class Worked
{
public DateTime Start { get; set; }
public DateTime End { get; set; }
}
static class Program
{
static void Main()
{
var now = DateTime.Now;
var year = now.Year;
var month = now.Month;
var day = now.Day;
var Day = new WorkDay
{
Day = now,
WorkingTimes = new List<Worked>
{
new Worked { Start = new DateTime(year,month, day,8,30,0), End = new DateTime(year,month, day,10,30,0) },
new Worked { Start = new DateTime(year,month, day,10,45,0), End = new DateTime(year,month, day,14,30,0) },
new Worked { Start = new DateTime(year,month, day,14,50,0), End = new DateTime(year,month, day,14,50,0).AddHours(10) }
}
};
foreach (var time in Day.WorkingTimes)
Console.WriteLine($"Start {time.Start} " + $"End {time.End}");
Day.WorkingTimes = Day.WorkingTimes.OrderBy(x => x.Start).ToList();
var opposite = Opposite(Day);
foreach (var time in opposite)
Console.WriteLine($"Start {time.Start} " + $"End {time.End}");
Console.WriteLine("Hello World!");
}
public static IEnumerable<Worked> Opposite(WorkDay workDay)
{
var rested = new List<Worked>();
for (var i = workDay.WorkingTimes.Count(); i-- > 0;)
if (i - 1 != -1 && workDay.WorkingTimes[i - 1].End != workDay.WorkingTimes[i].Start)
rested.Add(new Worked { Start = workDay.WorkingTimes[i - 1].End, End = workDay.WorkingTimes[i].Start });
rested = rested.OrderBy(x => x.Start).ToList();
var lastEntry = rested.Last().End.Date;
var lastTime = new DateTime(lastEntry.Year, lastEntry.Month, lastEntry.Day, 23, 59, 59, 59);
if (lastTime > rested.Last().End)
rested.Add(new Worked
{ Start = workDay.WorkingTimes.Last().End, End = lastTime });
return rested;
}
}
So the output would be:
Worked:
Start 21/09/2020 08:30:00 End 21/09/2020 10:30:00
Start 21/09/2020 10:45:00 End 21/09/2020 14:30:00
Start 21/09/2020 14:50:00 End 22/09/2020 00:50:00
Opposite:
Start 21/09/2020 10:30:00 End 21/09/2020 10:45:00
Start 21/09/2020 14:30:00 End 21/09/2020 14:50:00
Start **22/09/2020** 00:50:00 End 21/09/2020 23:59:59
What I am trying to do is not go into the next day calculating the difference. So in the WorkDay class there is the Day and all dates must be from that day.
So that the 24 hours of that day is accounted for (either worked or not), so if they did not work it should be accounted for within the result of the Opposite method.
I would tackle this by modelling a period of time and then provide a method that could Cut out a section of time from that period. Then it becomes trivial to work out the remainder of the day.
Let's start with the Period class:
private sealed class Period : IEquatable<Period>
{
public DateTime StartTime { get; private set; }
public DateTime EndTime { get; private set; }
public Period(DateTime startTime, DateTime endTime)
{
this.StartTime = startTime;
this.EndTime = endTime;
}
public override bool Equals(object obj)
{
if (obj is Period)
return Equals((Period)obj);
return false;
}
public bool Equals(Period obj)
{
if (obj == null)
return false;
if (!EqualityComparer<DateTime>.Default.Equals(this.StartTime, obj.StartTime))
return false;
if (!EqualityComparer<DateTime>.Default.Equals(this.EndTime, obj.EndTime))
return false;
return true;
}
public override int GetHashCode()
{
int hash = 0;
hash ^= EqualityComparer<DateTime>.Default.GetHashCode(this.StartTime);
hash ^= EqualityComparer<DateTime>.Default.GetHashCode(this.EndTime);
return hash;
}
public override string ToString()
{
return String.Format("{{ StartTime = {0}, EndTime = {1} }}",
this.StartTime, this.EndTime);
}
public IEnumerable<Period> Cut(Period that)
{
if (that.StartTime <= this.StartTime)
{
if (that.EndTime <= this.StartTime)
{
yield return this;
}
else if (that.EndTime < this.EndTime)
{
yield return new Period(that.EndTime, this.EndTime);
}
}
else if (that.StartTime < this.EndTime)
{
if (that.EndTime < this.EndTime)
{
yield return new Period(this.StartTime, that.StartTime);
yield return new Period(that.EndTime, this.EndTime);
}
else
{
yield return new Period(this.StartTime, that.StartTime);
}
}
else
{
yield return this;
}
}
}
Now we can express the current working times like this:
var workingTimes = new[]
{
new Period(new DateTime(year, month, day, 8, 30, 0), new DateTime(year, month, day, 10, 30, 0)),
new Period(new DateTime(year, month, day, 10, 45, 0), new DateTime(year, month, day, 14, 30, 0)),
new Period(new DateTime(year, month, day, 14, 50, 0), new DateTime(year, month, day, 14, 50, 0).AddHours(10)),
};
Then the whole day is:
var whole = new Period(now.Date, now.Date.AddDays(1.0));
Now we can compute the remainder of the day simply like this:
var result = new [] { whole };
foreach (var d in workingTimes)
{
result = result.SelectMany(r => r.Cut(d)).ToArray();
}
My end result is:
2020/09/21 00:00 - 2020/09/21 08:30
2020/09/21 10:30 - 2020/09/21 10:45
2020/09/21 14:30 - 2020/09/21 14:50
A simple solution might be to filter the rested result by only those values where the Start and End match the Day
// last line of method "Opposite"
return rested.Where(o => o.Start.Date == workDay.Day.Date && o.End.Date == workDay.Day.Date);

find range in a range list C#

I have this price range, formed in a string like this:
100:105 , 110:120 , 150:200 , 240:245
This means the prices range from
100 to 105
and
110 to 120
and
150 to 200
and
240 to 245
I want to check if a new price range is within our acceptable price range. Here is my code so far:
int ibegin = 110
int iend = 115
string BeginEnd = "100:105,110:120,150:200,240:245";
string[] strBeginEnd = BeginEnd.Split(',');
int pos = Array.IndexOf(strBeginEnd, ibegin+":"+ iend);
if (pos > -1)
{
RepText = RepText + ScriptBefore + TextIn + ScriptAfter + TextAfter;
}
This code works if the price start and end matches the start and end of the range.
For example, if ibegin = 110 and iend = 120 then my code will find it based on if 110:120 exists in the list.
However, if the iend = 115 it won't find it.
public static void Main(string[] args) {
string BeginEnd = "100:105,110:120,150:200,240:245";
Dictionary<string, int[]> data = new Dictionary<string, int[]>();
foreach (var val in BeginEnd.Split(",")) {
var start = int.Parse(val.Split(":")[0]);
var end = int.Parse(val.Split(":")[1]);
var range = new int[end - start+1];
for (var i = 0; i < range.Length; i++) {
range[i] = start + i;
}
data.Add(val, range);
}
var test1 = check(110);
var test2 = check(115);
var test3 = check(200);
var test4 = check(240);
Console.WriteLine("test1 {0} ,test2 {1} ,test3 {2} ,test4 {3} ",test1,test2,test3,test4);
string check(int value) => data.FirstOrDefault(pair => pair.Value.Contains(value)).Key;
}
You need to split the ranges, parse the numbers and check them:
var search = new {from = 110, to= 115};
var found = "100:105,110:120,150:200,240:245"
.Split(',') //split in parts like 100:105
.Select(p => {
var parts = p.Split(':'); // split the range from/to
var from = int.Parse(parts[0]); //convert from/to to ints
var to = int.Parse(parts[1]);
return new { from, to };
})
//check if any range matches the search values
.Any(range => search.from >= range.from && search.to <= range.to);
Console.WriteLine($"Found: {found}");
bool IsInRange(int ibegin, int iend, string BeginEnd)
{
string[] strBeginEnd = BeginEnd.Split(',');
foreach (var range in strBeginEnd)
{
var rangeArray = range.Split(':');
if (ibegin >= int.Parse(rangeArray[0]) && iend <= int.Parse(rangeArray[1]))
{
return true;
}
}
return false;
}

How can I get the date of selected week which are in the date range selected

how can I get the date of selected week which are in the date range selected in C#.
I am new in C#, please help me out to save these dates in DB, which comes under the date range
You could make all the possible Dates between the ranges and then check against a list of DaysOfWeeks:
using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
var selectedDayOfWeeks = new List<DayOfWeek>{DayOfWeek.Thursday, DayOfWeek.Saturday};
var startDate = new DateTime(2017, 10, 24);
var endDate = new DateTime(2017, 10, 28);
var possibleDates = new List<DateTime>();
for(var current = startDate; current <= endDate; current= current.AddDays(1))
{
if(selectedDayOfWeeks.Contains(current.DayOfWeek))
{
possibleDates.Add(current);
}
}
foreach(var d in possibleDates){
Console.WriteLine(d);
}
}
}
This is one way...
var from = DateTime.Parse("10/24/2018");
var to = DateTime.Parse("11/14/2018");
var dayList = new List<DateTime>();
for (var day = from.Date; day.Date <= to.Date; day = day.AddDays(1))
{
if
(
day.DayOfWeek == DayOfWeek.Monday ||
day.DayOfWeek == DayOfWeek.Wednesday ||
day.DayOfWeek == DayOfWeek.Friday ||
day.DayOfWeek == DayOfWeek.Sunday
)
{
dayList.Add(day);
}
}
DayOfWeek is an enumeration in the system namespace, you can determine your day of week check box and add a corresponding enum value to a list of DayOfWeek and use as Kevin Smith demonstrated
var selectedDaysOfWeek = new List<DayOfWeek>();
var from = DateTime.Parse("10/24/2018");
var to = DateTime.Parse("11/14/2018");
var dayList = new List<DateTime>();
if (checkBoxMonday.IsChecked) { selectedDaysOfWeek.Add(DayOfWeek.Monday); }
if (checkBoxTuesday.IsChecked) { selectedDaysOfWeek.Add(DayOfWeek.Tuesday); }
if (checkBoxWednesday.IsChecked) { selectedDaysOfWeek.Add(DayOfWeek.Wednesday); }
if (checkBoxThursday.IsChecked) { selectedDaysOfWeek.Add(DayOfWeek.Thursday); }
if (checkBoxFriday.IsChecked) { selectedDaysOfWeek.Add(DayOfWeek.Friday); }
if (checkBoxSaturday.IsChecked) { selectedDaysOfWeek.Add(DayOfWeek.Saturday); }
if (checkBoxSunday.IsChecked) { selectedDaysOfWeek.Add(DayOfWeek.Sunday); }
for (var day = from.Date; day.Date <= to.Date; day = day.AddDays(1))
{
if (selectedDaysOfWeek.Contains(day.DayOfWeek))
{
dayList.Add(day);
}
}
foreach(var day in dayList)
{
// Add to day to DB
}

How to map 2d array in csv file to poco collection or dictionary of dictionary using FileHelpers?

I have the following data structure in my csv file:
I want to either parse it into the following data structure :
[DelimitedRecord(","), IgnoreFirst(1)]
public class FxConversionRate
{
[FieldConverter(ConverterKind.Date, "d/M/yyyy")]
public DateTime Date;
public string Currency;
public double Rate;
}
Or else want to parse it into a Dictionary<string, Dictionary<DateTime, double>>
How can I accomplish either way? I do not want to modify the source csv table layout and believe I need to customize the import and mapping.
Thanks
EDIT
The following code snippet both, reads data from csv into a 2D array and also into a data structure (Dictionary of Dictionary in this case but could as well be the above proposed data structure FxConversionRate):
public class FxConversionTable
{
public Dictionary<Currency, Dictionary<DateTime, double>> FxConversionRates{ get; set; } //key1 = Currency, key2 = DateTime, value = double
public string[,] String2DArray{ get; set; }
public FxConversionTable()
{
FxConversionRates = new Dictionary<Currency, Dictionary<DateTime, double>>();
}
public void ReadFxConversionRatesFromCsvFile(string pathFileName)
{
var strings = new List<List<string>>();
using (var reader = new StreamReader(File.OpenRead(pathFileName)))
{
//read symbol rows and parse
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (string.IsNullOrEmpty(line))
continue;
var values = line.Split(',');
//populate string array
strings.Add(values.ToList());
//header
if (strings.Count == 1)
{
foreach (var currencyString in values.Skip(1))
{
Currency ccy = (Currency) Enum.Parse(typeof (Currency), currencyString);
FxConversionRates.Add(ccy, new Dictionary<DateTime, double>());
}
continue;
}
//populate data collection
var date = DateTime.ParseExact(values[0], "d/M/yyyy", CultureInfo.InvariantCulture);
for (int i = 1; i < values.Count(); i++)
{
var ccy = (Currency) Enum.Parse(typeof (Currency), strings[0][i]);
FxConversionRates[ccy].Add(date, Convert.ToDouble(values[i]));
}
}
}
String2DArray = FileIO.ListOfListTo2DArray<string>(strings);
}
}
I am, however, still looking for a more generic solution via FileHelpers...
You can use some fancy LINQ.
Helpful note: with FileHelpers it's easier to separate the class which defines the file format (FxConversionRateSpec) from the destination class (FxConversionRate) and map between the two.
// destination object
public class FxConversionRate
{
public DateTime Date { get; set; }
public string Currency { get; set; }
public double Rate { get; set; }
}
// file format specification (FileHelpers class)
[DelimitedRecord(","), IgnoreFirst(1)]
public class FxConversionRateSpec
{
[FieldConverter(ConverterKind.Date, "d/M/yyyy")]
public DateTime Date;
public double[] Rates;
}
class Program
{
static void Main(string[] args)
{
// trimmed down contents...
var contents =
#"DATE,AUD,CAD,CHF" + Environment.NewLine +
#"1/1/2000,88,71,3" + Environment.NewLine +
#"2/1/2000,82,83,86";
// get the records
var engine = new FileHelperEngine<FxConversionRateSpec>();
var records = engine.ReadString(contents);
// get the header
var currencies = contents
.Substring(0, contents.IndexOf(Environment.NewLine)) // take the first line
.Split(',') // split into currencies
.Skip(1); // skip the 'Date' column
// as IEnumerable<FxConversionRate>
var rates = records.SelectMany( // for each record of Date, Double[]
record => currencies.Zip(record.Rates, (c, r) => new { Currency = c, Rate = r}) // combine the rates array with the currency labels
.Select( // for each of the anonymous typed records Currency, Double
currencyRate =>
new FxConversionRate
{
Date = record.Date,
Currency = currencyRate.Currency,
Rate = currencyRate.Rate
}));
Assert.AreEqual(6, rates.Count(), "Exactly 6 records were expected");
Assert.That(rates.Single(x => x.Date == new DateTime(2000, 1, 1) && x.Currency == "AUD" && x.Rate == 88d) != null);
Assert.That(rates.Single(x => x.Date == new DateTime(2000, 1, 1) && x.Currency == "CAD" && x.Rate == 71d) != null);
Assert.That(rates.Single(x => x.Date == new DateTime(2000, 1, 1) && x.Currency == "CHF" && x.Rate == 3d) != null);
Assert.That(rates.Single(x => x.Date == new DateTime(2000, 1, 2) && x.Currency == "AUD" && x.Rate == 82d) != null);
Assert.That(rates.Single(x => x.Date == new DateTime(2000, 1, 2) && x.Currency == "CAD" && x.Rate == 83d) != null);
Assert.That(rates.Single(x => x.Date == new DateTime(2000, 1, 2) && x.Currency == "CHF" && x.Rate == 86d) != null);
Console.WriteLine("All tests passed OK.");
Console.ReadKey();
}
}
Note it would be quite feasible to create a Dictionary instead, especially with the ToDictionary() LINQ extension.
This should do the trick for you. It's not the most elegant solution but it works. You will need to add plenty of error checking for things like missing columns or data or source file corruption etc.
private static void Main(string[] args)
{
var fileData = File.ReadAllBytes("Data.csv");
var tableData = CreateDataTableFromFile(fileData);
DataColumn dateColumn = tableData.Columns["Date"];
Dictionary<string, List<FxConversionRate>> rates = new Dictionary<string, List<FxConversionRate>>();
foreach (DataColumn column in tableData.Columns)
{
if (column != dateColumn)
{
foreach (DataRow row in tableData.Rows)
{
FxConversionRate rate = new FxConversionRate();
rate.Currency = column.ColumnName;
rate.Date = DateTime.Parse(row[dateColumn].ToString());
rate.Rate = double.Parse(row[column].ToString());
if (!rates.ContainsKey(column.ColumnName))
rates.Add(column.ColumnName, new List<FxConversionRate>());
rates[column.ColumnName].Add(rate);
}
}
}
foreach (var key in rates.Keys)
{
Console.WriteLine($"Found currency: {key}");
foreach (var rate in rates[key])
{
Console.WriteLine($" {rate.Date.ToShortDateString()} : {rate.Rate:###,###,##0.00}");
}
}
Console.WriteLine("Press any key");
Console.ReadKey();
}
private static DataTable CreateDataTableFromFile(byte[] importFile)
{
var cb = new DelimitedClassBuilder("temp", ",") { IgnoreFirstLines = 0, IgnoreEmptyLines = true, Delimiter = "," };
var ms = new MemoryStream(importFile);
var sr = new StreamReader(ms);
var headerArray = sr.ReadLine().Split(',');
foreach (var header in headerArray)
{
cb.AddField(header, typeof(string));
cb.LastField.FieldQuoted = true;
cb.LastField.QuoteChar = '"';
}
var engine = new FileHelperEngine(cb.CreateRecordClass());
return engine.ReadStreamAsDT(sr);
}
Note that the CreateDataTableFromFile routine was taken from https://stackoverflow.com/a/6045923/697159

Categories

Resources