using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
public static void Main()
var tripEndRoundingError = TimeSpan.FromMinutes(1.5);
var returnSet = new PendingBikeReturnRequestSet(tripEndRoundingError);
var now = DateTimeOffset.UtcNow;
var fiveMinutesAgo = now.AddMinutes(-5);
var fiveSecondsAgo = now.AddSeconds(-5);
var bikeOne = ByteString.CopyFromUtf8("01AB5D");
var bikeTwo = ByteString.CopyFromUtf8("325C19");
var bikeOneReturnNow = new KioskReturnRequest { DockNumber = 3, BikeRfidTag = bikeOne, TripEndTicks = now.ToUnixTimeMilliseconds() };
var bikeOneReturnFiveMinutesAgo = new KioskReturnRequest { DockNumber = 3, BikeRfidTag = bikeOne, TripEndTicks = fiveMinutesAgo.ToUnixTimeMilliseconds() };
var bikeOneReturnFiveSecondsAgo = new KioskReturnRequest { DockNumber = 3, BikeRfidTag = bikeOne, TripEndTicks = fiveSecondsAgo.ToUnixTimeMilliseconds() };
var bikeTwoReturnFiveSecondsAgo = new KioskReturnRequest { DockNumber = 3, BikeRfidTag = bikeTwo, TripEndTicks = fiveSecondsAgo.ToUnixTimeMilliseconds() };
returnSet.Add(bikeOneReturnNow);
returnSet.Add(bikeOneReturnFiveMinutesAgo);
returnSet.Add(bikeOneReturnFiveSecondsAgo);
returnSet.Add(bikeTwoReturnFiveSecondsAgo);
Console.WriteLine(string.Join($",{Environment.NewLine}", returnSet.Select(bikeReturn => bikeReturn.ToString())));
public class KioskReturnRequest
public int DockNumber { get; set; }
public ByteString BikeRfidTag { get; set; }
public long TripEndTicks { get; set; }
public override string ToString() => $"{{ DockNumber: {DockNumber}, BikeRfidTag: {BikeRfidTag.ToStringUtf8()}, TripEnd: {DateTimeOffset.FromUnixTimeMilliseconds(TripEndTicks).UtcDateTime.ToString("mm:ss")} }}";
public static class DateTimeExtensions
public static DateTime Round(this DateTime dateTime, TimeSpan roundBy)
long remainderTicks = dateTime.Ticks % roundBy.Ticks;
if (remainderTicks < roundBy.Ticks / 2)
return dateTime.AddTicks(-remainderTicks);
return dateTime.AddTicks(roundBy.Ticks - remainderTicks);
public class PendingBikeReturnRequestSet : IEnumerable<KioskReturnRequest>, ICollection<KioskReturnRequest>
private readonly HashSet<KioskReturnRequest> _hashSet;
public PendingBikeReturnRequestSet(TimeSpan tripEndDateRoundingError) => _hashSet = new HashSet<KioskReturnRequest>(
new KioskReturnRequestEquality(tripEndDateRoundingError)
public IEnumerator<KioskReturnRequest> GetEnumerator() => _hashSet.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _hashSet.GetEnumerator();
public int Count => _hashSet.Count;
public void Add(KioskReturnRequest item) => _hashSet.Add(item);
public void Clear() => _hashSet.Clear();
public bool Contains(KioskReturnRequest item) => _hashSet.Contains(item);
public void CopyTo(KioskReturnRequest[] array, int arrayIndex) => _hashSet.CopyTo(array, arrayIndex);
public bool Remove(KioskReturnRequest item) => _hashSet.Remove(item);
public bool IsReadOnly => false;
private class KioskReturnRequestEquality : EqualityComparer<KioskReturnRequest>
private readonly TimeSpan _tripEndDateRoundingError;
public KioskReturnRequestEquality(TimeSpan tripEndDateRoundingError) => _tripEndDateRoundingError = tripEndDateRoundingError;
public override bool Equals([DisallowNull] KioskReturnRequest x, [DisallowNull] KioskReturnRequest y) =>
x.DockNumber == y.DockNumber
&& string.Equals(x.BikeRfidTag?.ToStringUtf8(), y.BikeRfidTag?.ToStringUtf8(), StringComparison.OrdinalIgnoreCase)
&& RoundTripEnd(x).Equals(RoundTripEnd(y));
public override int GetHashCode([DisallowNull] KioskReturnRequest returnRequest) => HashCode.Combine(
returnRequest.DockNumber,
returnRequest.BikeRfidTag?.ToStringUtf8().ToLower(),
RoundTripEnd(returnRequest)
private DateTime RoundTripEnd(KioskReturnRequest returnRequest)
=> DateTimeOffset.FromUnixTimeMilliseconds(returnRequest.TripEndTicks)
.Round(_tripEndDateRoundingError);