Could you please check below possible solution? It can be enhanced again if it does not meet your needs or if it has missing parts.
[v7] 2020/01/15 UTC 10:41 - Some further fixes are done.
using System;
using System.Collections.Generic;
using System.Linq;
public class Block
{
public Block(String ID, DateTime Start, DateTime End) { this.ID = ID; this.Start = Start; this.End = End; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
public String ID { get; set; }
}
public class Presence
{
public Presence(DateTime Arrival, DateTime Departure) { this.Arrival = Arrival; this.Departure = Departure; }
public DateTime Arrival { get; set; }
public DateTime Departure { get; set; }
public TimeSpan FlightTime;
}
public class Worker
{
public Worker(string ssn) { this.ssn = ssn; }
public String ssn { get; set; }
public List<Block> blocksWorked = new List<Block>();
}
public static class PresenceExtensions
{
public static IEnumerable<Presence> CalculateFlightTimes(this IEnumerable<Presence> presences, Block block, Worker worker)
{
int iterationIndex = 0;
if (presences.Count() == 0)
{
Presence presence = new Presence(block.Start, block.End);
presence.FlightTime = block.End - block.Start;
presences = presences.Concat(new[] { presence });
}
else
{
IEnumerable<Presence> absentAfterOneBlockEndPresences = presences.Where(p => p.Arrival > block.End);
IEnumerable<Presence> absentBeforeOneBlockStartPresences = presences.Where(p => p.Departure < block.Start);
presences = presences.Except(absentAfterOneBlockEndPresences).Except(absentBeforeOneBlockStartPresences);
foreach (Presence presence in presences)
{
if (iterationIndex == 0)
{
presence.FlightTime = presence.Arrival > block.Start ? presence.Arrival - block.Start : TimeSpan.Zero;
if (iterationIndex == presences.Count() - 1){
presence.FlightTime += presence.Departure < block.End ? block.End - presence.Departure : TimeSpan.Zero;
}
}
else
{
presence.FlightTime = presence.Arrival - presences.ElementAt(iterationIndex - 1).Departure;
if (iterationIndex == presences.Count() - 1){
presence.FlightTime += presence.Departure < block.End ? block.End - presence.Departure : TimeSpan.Zero;
}
}
iterationIndex++;
}
worker.blocksWorked.Add(block);
// Calculating arrival after block ends
if (absentAfterOneBlockEndPresences.Count() > 0)
{
// If there are no presences related to current block
if (presences.Count() == 0)
{
Presence firstPresence = absentAfterOneBlockEndPresences.ElementAt(0);
firstPresence.FlightTime = firstPresence.Arrival - block.End;
presences = presences.Concat(new[] { firstPresence });
// Remove the block from worked blocks because worker is absent
if (worker.blocksWorked.Contains(block))
{
worker.blocksWorked.Remove(block);
}
}
}
// Calculating departure before block starts (**This may not be needed**. If it is not needed, this code part can be safely disabled)
// See last presence (new Presence(new DateTime(1899, 12, 6, 8, 0, 0), new DateTime(1899, 12, 6, 12, 59, 0)))
// Ex. 08:00 - 12:59
if (absentBeforeOneBlockStartPresences.Count() > 0)
{
// If there are no presences related to current block
if (presences.Count() == 0)
{
Presence lastPresence = absentBeforeOneBlockStartPresences.ElementAt(absentBeforeOneBlockStartPresences.Count() - 1);
lastPresence.FlightTime = block.Start - lastPresence.Departure;
presences = presences.Concat(new[] { lastPresence });
// Remove the block from worked blocks because worker is absent
if (worker.blocksWorked.Contains(block))
{
worker.blocksWorked.Remove(block);
}
}
}
}
return presences;
}
public static TimeSpan GetTotalFlightTime(this IEnumerable<Presence> presences)
{
return new TimeSpan(presences.Sum(r => r.FlightTime.Ticks));
}
public static long TicksToMinutes(this TimeSpan ts)
{
return (ts.Ticks / (60 * 10000000));
}
public static bool SameDayControl(this DateTime dateTime1, DateTime dateTime2)
{
return dateTime1.Date.Equals(dateTime2.Date);
}
}
public static class BlockExtensions
{
public static Dictionary<Block, IEnumerable<Presence>> FilterPresences(this IEnumerable<Block> blocks, IEnumerable<Presence> presences)
{
Dictionary<Block, IEnumerable<Presence>> result = new Dictionary<Block, IEnumerable<Presence>>();
foreach (Block block in blocks)
{
// Filtering same date block and presences regarding absent times before block starts and after block ends
List<Presence> ieP = new List<Presence>();
ieP.AddRange(presences.Where(p => block.End.SameDayControl(p.Arrival.Date) && block.Start.SameDayControl(p.Departure.Date) && (p.Arrival <= block.End && p.Departure >= block.Start)));
Presence after = presences.Where(p => block.End.SameDayControl(p.Arrival.Date) && block.Start.SameDayControl(p.Departure.Date) && (p.Arrival > block.End)).OrderBy(p => p.Arrival).FirstOrDefault();
if(after != null)
{
ieP.Add(after);
}
Presence before = presences.Where(p => block.End.SameDayControl(p.Arrival.Date) && block.Start.SameDayControl(p.Departure.Date) && (p.Departure < block.Start)).OrderBy(p => p.Departure).LastOrDefault();
if(before != null)
{
ieP.Add(before);
}
result.Add(block, (IEnumerable<Presence>) ieP);
}
return result;
}
}
public static class ListExtensions
{
public static String ToStr(this List<Block> blockList)
{
String str = "";
int blockIndex = 0;
foreach (Block block in blockList)
{
str += block.ID + ", ";
if (blockIndex == blockList.Count - 1)
{
str = str.Substring(0, str.Length - 2);
}
blockIndex++;
}
return str;
}
}
public class AbsentDetectionProgram
{
public static void Main()
{
Console.WriteLine("Process started..\n");
List<Block> blocks = new List<Block>();
// First blocks example
blocks.Add(new Block("1", new DateTime(1899, 12, 30, 8, 30, 0), new DateTime(1899, 12, 30, 11, 45, 0)));
blocks.Add(new Block("2", new DateTime(1899, 12, 30, 13, 15, 0), new DateTime(1899, 12, 30, 17, 0, 0)));
blocks.Add(new Block("3", new DateTime(1899, 12, 30, 18, 15, 0), new DateTime(1899, 12, 30, 22, 0, 0)));
// Second blocks example
blocks.Add(new Block("4", new DateTime(1899, 12, 1, 8, 30, 0), new DateTime(1899, 12, 1, 11, 45, 0)));
blocks.Add(new Block("5", new DateTime(1899, 12, 1, 13, 15, 0), new DateTime(1899, 12, 1, 17, 0, 0)));
blocks.Add(new Block("6", new DateTime(1899, 12, 2, 8, 30, 0), new DateTime(1899, 12, 2, 11, 45, 0)));
blocks.Add(new Block("7", new DateTime(1899, 12, 2, 13, 15, 0), new DateTime(1899, 12, 2, 17, 0, 0)));
blocks.Add(new Block("8", new DateTime(1899, 12, 3, 8, 30, 0), new DateTime(1899, 12, 3, 11, 45, 0)));
blocks.Add(new Block("9", new DateTime(1899, 12, 3, 13, 15, 0), new DateTime(1899, 12, 3, 17, 0, 0)));
blocks.Add(new Block("10", new DateTime(1899, 12, 4, 8, 30, 0), new DateTime(1899, 12, 4, 11, 45, 0)));
blocks.Add(new Block("11", new DateTime(1899, 12, 4, 13, 15, 0), new DateTime(1899, 12, 4, 17, 0, 0)));
blocks.Add(new Block("12", new DateTime(1899, 12, 5, 8, 30, 0), new DateTime(1899, 12, 5, 11, 45, 0)));
blocks.Add(new Block("13", new DateTime(1899, 12, 5, 13, 15, 0), new DateTime(1899, 12, 5, 17, 0, 0)));
blocks.Add(new Block("14", new DateTime(1899, 12, 6, 8, 30, 0), new DateTime(1899, 12, 6, 11, 45, 0)));
blocks.Add(new Block("15", new DateTime(1899, 12, 6, 13, 15, 0), new DateTime(1899, 12, 6, 17, 0, 0)));
// Only two worker is calculated
Worker worker1 = new Worker("1234567890");
Worker worker2 = new Worker("1234567891");
List<Presence> presencesForWorker1 = new List<Presence>();
presencesForWorker1.Add(new Presence(new DateTime(1899, 12, 30, 8, 3, 0), new DateTime(1899, 12, 30, 9, 21, 0)));
presencesForWorker1.Add(new Presence(new DateTime(1899, 12, 30, 9, 36, 0), new DateTime(1899, 12, 30, 10, 34, 0)));
presencesForWorker1.Add(new Presence(new DateTime(1899, 12, 30, 10, 45, 0), new DateTime(1899, 12, 30, 12, 5, 0)));
presencesForWorker1.Add(new Presence(new DateTime(1899, 12, 30, 13, 3, 0), new DateTime(1899, 12, 30, 14, 24, 0)));
presencesForWorker1.Add(new Presence(new DateTime(1899, 12, 30, 14, 34, 0), new DateTime(1899, 12, 30, 16, 14, 0)));
presencesForWorker1.Add(new Presence(new DateTime(1899, 12, 30, 16, 27, 0), new DateTime(1899, 12, 30, 18, 2, 0)));
List<Presence> presencesForWorker2 = new List<Presence>();
presencesForWorker2.Add(new Presence(new DateTime(1899, 12, 1, 7, 0, 0), new DateTime(1899, 12, 1, 12, 0, 0)));//
presencesForWorker2.Add(new Presence(new DateTime(1899, 12, 1, 13, 0, 0), new DateTime(1899, 12, 1, 16, 55, 0)));
presencesForWorker2.Add(new Presence(new DateTime(1899, 12, 2, 8, 40, 0), new DateTime(1899, 12, 2, 11, 39, 0)));
presencesForWorker2.Add(new Presence(new DateTime(1899, 12, 2, 13, 0, 0), new DateTime(1899, 12, 2, 16, 55, 0)));
presencesForWorker2.Add(new Presence(new DateTime(1899, 12, 3, 8, 30, 0), new DateTime(1899, 12, 3, 9, 0, 0)));
presencesForWorker2.Add(new Presence(new DateTime(1899, 12, 3, 9, 10, 0), new DateTime(1899, 12, 3, 10, 0, 0)));
presencesForWorker2.Add(new Presence(new DateTime(1899, 12, 3, 10, 15, 0), new DateTime(1899, 12, 3, 12, 1, 0)));
presencesForWorker2.Add(new Presence(new DateTime(1899, 12, 3, 13, 0, 0), new DateTime(1899, 12, 3, 16, 55, 0)));
presencesForWorker2.Add(new Presence(new DateTime(1899, 12, 4, 8, 0, 0), new DateTime(1899, 12, 4, 14, 0, 0)));
presencesForWorker2.Add(new Presence(new DateTime(1899, 12, 5, 12, 1, 0), new DateTime(1899, 12, 5, 17, 0, 0)));
presencesForWorker2.Add(new Presence(new DateTime(1899, 12, 6, 8, 0, 0), new DateTime(1899, 12, 6, 12, 59, 0)));
Dictionary<Worker, List<Presence>> workerPresenceDict = new Dictionary<Worker, List<Presence>>();
workerPresenceDict.Add(worker1, presencesForWorker1);
workerPresenceDict.Add(worker2, presencesForWorker2);
foreach (KeyValuePair<Worker, List<Presence>> workerPresencePair in workerPresenceDict)
{
Worker worker = workerPresencePair.Key;
List<Presence> presences = workerPresencePair.Value;
Dictionary<Block, IEnumerable<Presence>> filteredList = blocks.FilterPresences(presences);
foreach (KeyValuePair<Block, IEnumerable<Presence>> pair in filteredList)
{
Block block = pair.Key;
IEnumerable<Presence> presencesIEN = pair.Value;
presencesIEN = presencesIEN.CalculateFlightTimes(block, worker);
long minuteDiff = presencesIEN.GetTotalFlightTime().TicksToMinutes();
// If there is more than 15 minutes difference and if the difference is smaller than the whole block time
// If the minute difference is equals to whole block time, then we are assuming that worker didn't work in that block, so do not log
if (minuteDiff > 15 && minuteDiff < (block.End - block.Start).TicksToMinutes())
{
Console.WriteLine("Worker #" + worker.ssn + " - Total Worker Flight Time: " + minuteDiff);
Console.WriteLine("Worker #" + worker.ssn + " - Worker Work Time Details: [Block Start: " + block.Start + " Block End: " + block.End + " In-Out Count: " + presencesIEN.Count() + "] The worker is out of the block for more than 15 min");
}
}
// Printing worked and not worked blocks (Logging purpose only)
Console.WriteLine("\nSummary");
Console.WriteLine("-------");
if (worker.blocksWorked.Count == 0)
{
Console.WriteLine("Worker #" + worker.ssn + " didn't work in any block");
}
else
{
Console.WriteLine("Worker #" + worker.ssn + " worked in block(s) " + worker.blocksWorked.ToStr());
Console.WriteLine("Worker #" + worker.ssn + " didn't work in block(s) " + blocks.Except(worker.blocksWorked).ToList().ToStr() + "\n");
}
}
Console.WriteLine("Process finished..");
}
}
Output:

Live Example: https://dotnetfiddle.net/YDDPSK