0

I have a list very similar to this (But example is simplified):

public class TimeEntry
{
    public int entrydate { get; set; } //UNIX Timestamp
    public string name { get; set; }
    public string timespent { get; set; } //Be aware this is a string, not an int or date - [hh]:mm (Hours can go above 24)
}

List<TimeEntry> entryTable = new List<TimeEntry>();


//This layout is fictional and just a representation of my List<TimeEntry>
[entrydate: "1540364988", name: "Time1", timespent: "0:30"],
[entrydate: "1540304400", name: "Time2", timespent: "0:25"],
[entrydate: "1540307400", name: "Time1", timespent: "0:10"],
[entrydate: "1540300010", name: "Time3", timespent: "0:40"],
[entrydate: "1540365000", name: "Time1", timespent: "1:10"],
[entrydate: "1540367500", name: "Time3", timespent: "0:20"],
[entrydate: "1540354500", name: "Time4", timespent: "0:30"]

I would like to combine each entry so they have a total per day for each unique name entry.

So for example

[entrydate: "1540364988", name: "Time1", timespent: "0:30"],
[entrydate: "1540307400", name: "Time1", timespent: "0:10"],
[entrydate: "1540365000", name: "Time1", timespent: "1:10"]

would become

[entrydate: "1540252800", name: "Time1", timespent: "0:10"],
[entrydate: "1540339200", name: "Time1", timespent: "1:40"]

How would I do this in an efficient way? We're talking about 450,000 records, I'm thinking about just using a loop, but having a loop run 450,000 times where in each loop we need to run through all the records we've stored already, just to see if we should count up instead of add a new record sounds inefficient and slow.

No need for full completed code, but I'd love to hear someones opinion or solution on this. Although every little bit of help is appreciated Thanks

EDIT:

Since the question got put on hold for not enough information, here is some more information about my issue:

Goal: Combine each entry from the same day that share the same name

The timestamp is in UTC and a day would be from 00:00:00 to 23:59:99.

Isa
  • 248
  • 2
  • 9
  • 6
    What's the relation between your input and your desired output, it seems unclear to me. Also what have tried so far? – Salah Akbari Oct 24 '18 at 07:22
  • 4
    what type of format is the entrydate? – Jaques Oct 24 '18 at 07:25
  • Check the [linq.Join()](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/join-clause) it uses hashing and its performance is close to O(n) - no need for 2 nested loops O(n^2) – Vladi Pavelka Oct 24 '18 at 07:40
  • I don't understand how in your example you would decide to combine the first and third entry but leave the second alone, what's the criteria? – dumetrulo Oct 24 '18 at 08:12
  • @S.Akbari As said above, I want to combine the timespent from entries that happen on the same day. The result will be 2 rows instead of 3, as out of the 3 rows there are only 2 unique dates. I've not tried anything that had any note worthy results, other then just a loop that become a performance issue – Isa Oct 24 '18 at 10:27
  • @Jaques UNIX Timestamp, editted my post – Isa Oct 24 '18 at 10:27
  • Start by [working with `DateTime`](https://stackoverflow.com/a/250400/5265292) (you should probably use `ToUniversalTime` instead of `ToLocalTime`)... – grek40 Oct 24 '18 at 10:34
  • Can you describe `timespent` a bit more... I guess I could answer then question when it is reopened, but I don't know whether I look at `minutes:seconds`, `hours:minutes` or some different string format... – grek40 Oct 24 '18 at 11:03
  • @grek40 I'm very sorry, I totally overlooked the explanation for it! We're looking at [hh]:mm, so the hours will count up past 24 but the minutes turn over after 59 into a new hour – Isa Oct 24 '18 at 12:22

1 Answers1

1

Basically, you need to convert your entrydate to a whole-day value, group your entries by name and whole-day, then compute the timespent for each group.

The following code does all that.

  1. convert entrydate is done by TrimUnixTimestampToDate
  2. grouping is done with the linq GroupBy function using an aggregate key of name and date computing the timespent is done by converting each timespent to a TimeSpan via ToTimeSpan and adding them all together with AddTimespans
    • note I did not write proper error handling for the case of ill-formatted timespent values. You have to decide if you need it
  3. the resulting timespan is converted back to string
  4. a new object is created for the aggregate data

/

List<TimeEntry> TimeEntryProcessing()
{
    var entries = new List<TimeEntry>
    {
        new TimeEntry{ entrydate = 1540364988, name = "Time1", timespent = "0:30"},
        new TimeEntry{ entrydate = 1540304400, name = "Time2", timespent = "0:25"},
        new TimeEntry{ entrydate = 1540307400, name = "Time1", timespent = "0:10"},
        new TimeEntry{ entrydate = 1540300010, name = "Time3", timespent = "0:40"},
        new TimeEntry{ entrydate = 1540365000, name = "Time1", timespent = "1:10"},
        new TimeEntry{ entrydate = 1540367500, name = "Time3", timespent = "0:20"},
        new TimeEntry{ entrydate = 1540354500, name = "Time4", timespent = "0:30"}
    };

    return entries
        .GroupBy(x => new { x.name, date = TrimUnixTimestampToDate(x.entrydate) })
        .Select(x =>
        {
            var time = AddTimespans(x.Select(y => ToTimeSpan(y.timespent)));
            return new TimeEntry
            {
                entrydate = x.Key.date,
                name = x.Key.name,
                timespent = $"{(int)time.TotalHours}:{time.Minutes}"
            };
        })
        // in case you need an order... you didn't mention it
        .OrderBy(x => x.entrydate).ThenBy(x => x.name)
        .ToList();
}

TimeSpan AddTimespans(IEnumerable<TimeSpan> enumerable)
{
    return new TimeSpan(enumerable.Sum(x => x.Ticks));
}

TimeSpan ToTimeSpan(string timespent)
{
    var parts = timespent.Split(':');
    return new TimeSpan(int.Parse(parts[0]), int.Parse(parts[1]), 0);
}

int TrimUnixTimestampToDate(int entrydate)
{
    DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
    return (int)dtDateTime.AddSeconds(entrydate).Date.Subtract(dtDateTime).TotalSeconds;
}
grek40
  • 13,113
  • 1
  • 24
  • 50
  • My bad for taking a while to respond, this works like a charm! Had to change a few things as the code woudn't compile (some minor errors, nothing big) but you helped me out big time. – Isa Nov 14 '18 at 10:45