public static async Task Main(string[] args)
        {
            Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;

            if (args.Length < 2)
            {
                Console.WriteLine("Missing command line arguments.");
                return;
            }

            var sourcePath      = args[0];
            var destinationPath = args[1];

            var services = new ServiceCollection();

            IocConfig.Configure(services);
            var serviceProvider = services.BuildServiceProvider();

            var chargingSessionsToCsv = serviceProvider.GetRequiredService <IModel3ChargingSessionsToCsv>();

            var options = new ChargingSessionTransformationOptions
            {
                MinimumChargingSessionDuration = TimeSpan.FromMinutes(15),
                IncludeSubdirectories          = true
            };
            await chargingSessionsToCsv.Transform(sourcePath, destinationPath, options);
        }
        public async Task Transform(string sourcePath, string destinationPath, ChargingSessionTransformationOptions options)
        {
            var tasks = new List <Task>();

            await foreach (var timeline in _canBusLogPathReader.LoadTimelines(sourcePath, options.IncludeSubdirectories))
            {
                Console.Write('.');

                tasks.Add(Task.Run(async() => {
                    try
                    {
                        await ProcessTimeline(destinationPath, timeline, options);
                    }
                    catch (Exception ex)
                    {
                        await Console.Error.WriteLineAsync(ex.ToString());
                    }
                }));
            }

            await Task.WhenAll(tasks);

            Console.WriteLine();
        }
        private async Task ProcessTimeline(string destinationPath, MessageTimeline timeline, ChargingSessionTransformationOptions options)
        {
            foreach (var chargingSession in new ChargingSessionFilter().GetChargingSessions(timeline))
            {
                if (chargingSession.StartTime == default || chargingSession.EndTime == default)
                {
                    continue;
                }

                if (options.MinimumChargingSessionDuration > TimeSpan.Zero &&
                    chargingSession.EndTime - chargingSession.StartTime < options.MinimumChargingSessionDuration)
                {
                    continue;
                }

                var csvFileName = GetChargingSessionCsvFileName(destinationPath, chargingSession);
                await using var writer = File.CreateText(csvFileName);
                await _rowWriter.WriteHeader(writer);

                DateTime           lastTimestamp = default;
                ChargingSessionRow lastRow       = null;
                var row = new ChargingSessionRow();

                foreach (var timedMessage in chargingSession.Where(m => !(m.Value is UnknownMessage)))
                {
                    var timestamp = timedMessage.Timestamp ?? default;
                    var message   = timedMessage.Value;

                    ParseMessage(message, row);

                    if (timestamp == default || timestamp == lastTimestamp)
                    {
                        continue;
                    }

                    if (lastTimestamp == default)
                    {
                        lastTimestamp = timestamp;
                        row           = new ChargingSessionRow
                        {
                            Timestamp = timestamp
                        };
                        continue;
                    }

                    EnrichMemoizedValues(row, lastRow);

                    if (row.ShouldWriteRow)
                    {
                        await _rowWriter.WriteLine(writer, row);
                    }

                    lastTimestamp = timestamp;
                    lastRow       = row;
                    row           = new ChargingSessionRow
                    {
                        Timestamp = timestamp
                    };
                }
            }
        }