{"id":22643189,"url":"https://github.com/hochfrequenz/time_slice_net","last_synced_at":"2025-10-28T12:35:35.221Z","repository":{"id":37856578,"uuid":"384053349","full_name":"Hochfrequenz/time_slice_net","owner":"Hochfrequenz","description":"C# / .NET package to model time slices (\"𝘡𝘦𝘪𝘵𝘴𝘤𝘩𝘦𝘪𝘣𝘦𝘯\") in an easily serializable and persistable way. We don't ignore time zones.","archived":false,"fork":false,"pushed_at":"2025-04-07T14:55:04.000Z","size":202,"stargazers_count":2,"open_issues_count":3,"forks_count":0,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-11T14:19:17.436Z","etag":null,"topics":["library"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Hochfrequenz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2021-07-08T08:22:18.000Z","updated_at":"2025-04-07T14:55:02.000Z","dependencies_parsed_at":"2024-03-04T16:10:42.433Z","dependency_job_id":"fadb7e85-d79c-4150-b1b0-6e8c0e68802a","html_url":"https://github.com/Hochfrequenz/time_slice_net","commit_stats":null,"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hochfrequenz%2Ftime_slice_net","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hochfrequenz%2Ftime_slice_net/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hochfrequenz%2Ftime_slice_net/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hochfrequenz%2Ftime_slice_net/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Hochfrequenz","download_url":"https://codeload.github.com/Hochfrequenz/time_slice_net/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248493491,"owners_count":21113269,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["library"],"created_at":"2024-12-09T05:09:37.424Z","updated_at":"2025-10-28T12:35:35.129Z","avatar_url":"https://github.com/Hochfrequenz.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# time_slice_net\r\n\r\n![Unittests Status Badge](https://github.com/Hochfrequenz/time_slice_net/workflows/Unittests%20and%20Coverage/badge.svg)\r\n![Linter Status Badge](https://github.com/Hochfrequenz/time_slice_net/workflows/ReSharper/badge.svg)\r\n![Formatter Status Badge](https://github.com/Hochfrequenz/time_slice_net/workflows/dotnet-format/badge.svg)\r\n\r\nTimeSlice.NET is a C# based .NET package to model time slices (\"𝘡𝘦𝘪𝘵𝘴𝘤𝘩𝘦𝘪𝘣𝘦𝘯\") in an easily serializable and persistable way.\r\nWe know and appreciate the [Itenso TimePeriodLibrary](https://github.com/Giannoudis/TimePeriodLibrary) library which is great f.e. to calculate overlaps and intersections of multiple time periods.\r\nHowever the focus of TimeSlice.NET is slightly different:\r\n\r\n- TimeSlice.NET focuses on modeling time-dependent relationships between entities (think of ownership or assignments).\r\n- TimeSlice.NET brings easy to use serialization (`System.Text.Json`) and persistence (Entity Framework) features for said time-dependent relationships.\r\n- However TimeSlice.NET does not support as many different kinds of time periods or time period chains/ranges/collections as Itenso TimePeriodLibrary.\r\n\r\nFurthermore:\r\n\r\n- The code is designed with time zones in mind. They exist and will cause problems if ignored.\r\n- All end date times are, if set to a finite value other than `MaxValue`, meant and treated as exclusive ([here's why](https://hf-kklein.github.io/exclusive_end_dates.github.io/))\r\n- All sub second times (milliseconds and ticks) are ignored because they tend to cause trouble on the database/ORM level and there's barely a business case that requires them\r\n\r\n## Code Quality / Production Readiness\r\n\r\n- The code has [at least a 95%](https://github.com/Hochfrequenz/time_slice_net/blob/main/.github/workflows/unittests_and_coverage.yml#L34) unit test coverage. ✔️\r\n- The bare TimeSlice.NET package has no extra dependencies. ✔️\r\n- The only dependency of the TimeSlice.NET Entity Framework Extensions package is EF Core itself. ✔️\r\n\r\n## Nuget Packages\r\nThis repository contains two package.\r\n1. `TimeSlice` for the core time slice functionalities\r\n    * ![Nuget Package](https://badgen.net/nuget/v/TimeSlice)\r\n    * ![Nuget Prerelease](https://badgen.net/nuget/v/TimeSlice/pre)\r\n2. `TimeSliceEntityFrameWorkExtensions` for the Entity Framework extensions\r\n    * ![Nuget Package](https://badgen.net/nuget/v/TimeSliceEntityFrameWorkExtensions)\r\n    * ![Nuget Prerelease](https://badgen.net/nuget/v/TimeSliceEntityFrameWorkExtensions/pre)\r\n\r\n## Release Workflow\r\n\r\nTo create a **pre-release** nuget package, create a tag of the form `prerelease-vx.y.z` where `x.y.z` is the semantic version of the pre-release. This will create and push nuget packages with the specified version `x.y.z` and a `-betaYYYYMMDDHHmmss` suffix.\r\n\r\nTo create a **release** nuget package, create a tag of the form `vx.y.z` where `x.y.z` is the semantic version of the release. This will create and push nuget packages with the specified version `x.y.z`.\r\n\r\n## Examples\r\n\r\n### Plain Time Slices\r\n\r\nThe easiest way to think of a time slice is something that just has a start and (maybe also) an end.\r\n\r\n```c#\r\nvar plainTimeSlice = new PlainTimeSlice\r\n{\r\n    Start = DateTimeOffset.UtcNow,\r\n    End = new DateTimeOffset(2030, 1, 1, 0, 0, 0, TimeSpan.Zero)\r\n}\r\n```\r\n\r\n### \"Open\" Time Slices\r\n\r\nTime slices are called \"open\", if their end is either not set or infinity.\r\n\r\n```c#\r\nvar openTimeSlice = new PlainTimeSlice\r\n{\r\n    Start = DateTimeOffset.UtcNow,\r\n    End = null\r\n};\r\nAssert.IsTrue(openTimeSlice.IsOpen()); // no end set =\u003e \"open\"\r\nopenTimeSlice.End = DateTimeOffset.MaxValue;\r\nAssert.IsTrue(openTimeSlice.IsOpen()); // end is infinity =\u003e \"open\"\r\n```\r\n\r\n## Relations\r\n\r\nA relation describes that a single \"parent\" has a single \"child\" assigned for a specific time range.\r\nFor a minimal, easy to understand example on relations, see the [gasoline pump ⬌ car relation tests](TimeSliceNet/TimeSliceTests/GasolinePumpCarRelationExampleTests.cs).\r\n\r\n### Relations that vary over time = Collections\r\n\r\nIn many business cases these relations vary over time; children are assigned and unassigned to/from parents at specific points in time.\r\nWe call these assignments \"time dependent collection\".\r\nThere are two main kinds:\r\n\r\n- overlaps are allowed = any number of children per point in time (easy to handle)\r\n- overlaps are forbidden = max. 1 child per point in time (harder to handle)\r\n\r\nFor a minimal, easy to understand example of collections with overlapping children see the [concert tests](TimeSliceNet/TimeSliceTests/ConcertOverlappingExampleTests.cs).\r\n\r\nFor a minimal, easy to understand example of collections of non-overlapping children see the [gasoline pump ⬌ car (non overlapping) collection tests](TimeSliceNet/TimeSliceTests/GasolinePumpCarNonOverlappingExampleTests.cs).\r\n\r\n## Storing the Collections on a Database using Entity Framework Core\r\n\r\nIn the [`TimeSliceEntityFrameworkExtensions`](TimeSliceNet/TimeSliceEntityFrameWorkExtensions) package you'll find extension classes that make your time slices, relations and collections of relations easily persistable using EF Core.\r\n\r\nTo make a relation persistable, simply change the interfaces known from above minimal working examples to:\r\n\r\n| Simple Interface/ Base Class | Interface/Base Class to Persist using EF Core                                   |\r\n| ---------------------------- | ------------------------------------------------------------------------------- |\r\n| _no constraints_             | Parents and Children used in relations have to implement `IHasKey\u003cTPrimaryKey\u003e` |\r\n| `IRelation`                  | `IPersistableRelation`                                                          |\r\n| `TimeDependentRelation`      | `PersistableTimeDependentReleation`                                             |\r\n| `TimeDependentCollection`    | `PersistableTimeDependentCollection`                                            |\r\n\r\nThe generics used may look a bit overcomplicated to simply define a primary key (which you can \"normally\" do by using the `[Key]` attribute) but the real advantage is, that all the primary and foreign key relations for the collection are then automatically set up using\r\n\r\n```c#\r\nprotected override void OnModelCreating(ModelBuilder modelBuilder)\r\n{\r\n    modelBuilder.SetupCollectionAndRelations\u003cMyCollectionType, MyRelationType, MyParent, MyParentsKey, MyChild, MyChildsKey\u003e(collection=\u003ecollection.YourKey);\r\n    // that's all.\r\n}\r\n```\r\n\r\nSee the [ExampleWebApplication 🠒 TimeSliceContext](TimeSliceNet/ExampleWebApplication/TimeSliceContext.cs) class for a working (and [unit tested](TimeSliceNet/TimeSliceTests/EntityFrameworkExtensionTests)) example.\r\n\r\n### From Scratch: Defining a Persistable Time Dependent 1:n and 1:1 Relations and Collections\r\n\r\nThere's a festival in town.\r\nThe persons attending the festival are either `Musician`s or `Listener`s.\r\nFor simplicity we model both of those types in separate, easily distinguishable classes.\r\nBoth `Musician` and `Listener` have a name that is, in both cases also used as primary key to store them on a database.\r\n\r\n```c#\r\npublic class Musician : IHasKey\u003cstring\u003e\r\n{\r\n    public string Name { get; set; } // e.g. Freddie Mercury\r\n    string IHasKey\u003cstring\u003e.Id =\u003e Name; // \u003c-- used a PK in musician table\r\n}\r\n\r\npublic class Listener : IHasKey\u003cstring\u003e\r\n{\r\n    public string Name { get; set; } // e.g. John Doe\r\n    string IHasKey\u003cstring\u003e.Id =\u003e Name; // \u003c-- used a PK in listener table\r\n}\r\n```\r\n\r\nIf a `Listener` attends a concert, this is modelled as a _relation_ where the `Musician` is a _parent_ to which the `Listener` is assigned as a _child_.\r\n\r\n```c#\r\npublic class ConcertVisit : PersistableTimeDependentRelation\u003cMusician, string, Listener, string\u003e\r\n{\r\n    // no class body needed, everything we need is already inherited from the base class\r\n}\r\n```\r\n\r\nAt a concert there is usually _`1`_ `Musician` playing for _`n`_ listeners.\r\nThis 1:n cardinality explains why the type `Musician` is referred to as \"_parent_\" and the type `Listener` is referred to as \"_child_\".\r\nIn the names used in this library the \"1\" side of a cardinality is always named \"parent\".\r\n\r\nThe entire `Concert`, that consists of multiple n `Listener` listening to the same 1 `Musician` at (possibly but not necessarily) the same time is defined as a `Collection` of n `ConcertVisit`s.\r\n\r\n```c#\r\npublic class Concert : PersistableTimeDependentCollection\u003cConcertVisit, Musician, string, Listener, string\u003e\r\n{\r\n    // each collection has to define if the children involved in it\r\n    // at a concert the visits of listeners may overlaps\r\n    public override TimeDependentCollectionType CollectionType =\u003e TimeDependentCollectionType.AllowOverlaps;\r\n\r\n    // the key of a collection is not enforced using generics, because it's not necessary.\r\n    // so we could use anything else as a key but choosing a Guid is definitly not a bad idea at all.\r\n    public Guid ConcertId { get; set; } // unique ID of the concert\r\n}\r\n```\r\n\r\nTo store concerts on a database we simply have to add one line to the `OnModelCreating` method:\r\n\r\n```c#\r\nprotected override void OnModelCreating(ModelBuilder modelBuilder)\r\n{\r\n    // ...\r\n    modelBuilder.SetupCollectionAndRelations\u003cConcert, ConcertVisit, Musician, string, Listener, string\u003e(concert=\u003econcert.ConcertId);\r\n    // This will set up:\r\n    // * the Primary Keys for Musicians and Listeners\r\n    // * the 1:n cardinality and unique constraints for the musician\u003c-\u003elistener relation\r\n    // * the table and keys for the concerts\r\n}\r\n```\r\n\r\nNow we can filling the concert hall:\r\n\r\n```c#\r\nvar freddy = new Musician { Name = \"Freddie Mercury\" };\r\nvar liveAtWembley = new Concert(freddy, new List\u003cConcertVisit\u003e\r\n{\r\n    new()\r\n    {\r\n        Start = DateTimeOffset.Parse(\"1986-07-12T19:00:00+00:00\"),\r\n        End = DateTimeOffset.Parse(\"1986-07-12T22:00:00+00:00\"),\r\n        Child = new Listener { Name = \"John Doe\" };,\r\n        Parent = freddy\r\n    },\r\n    new()\r\n    {\r\n        Start = DateTimeOffset.Parse(\"1986-07-12T19:30:00+00:00\"),\r\n        End = DateTimeOffset.Parse(\"1986-07-12T21:35:00+00:00\"),\r\n        Child = new Listener { Name = \"Erika Musterfrau\" },\r\n        Parent = freddy\r\n    }\r\n    // ... many more\r\n});\r\n// add to context and save on database\r\nawait context.Concerts.AddAsync(liveAtWembley);\r\nawait context.SaveChangesAsync();\r\n```\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhochfrequenz%2Ftime_slice_net","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhochfrequenz%2Ftime_slice_net","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhochfrequenz%2Ftime_slice_net/lists"}