时间数据的控制与计算

对时间游戏充满了兴趣。时间这个话题在职业生涯中也伴随着很长时间,处理过复杂的时间计算。最后一个开源项目是关于能够在字段级别控制业务数据的生命周期。由于额外的业务需求,已建立的历史化和审计跟踪方法不再适用:数据是在字段级别而不是对象级别控制的;数据可以多次更改;数据可以追溯性地更改;数据更改可以安排;使用过去和(回到)未来的数据分析。

以员工工资为例,将展示是如何用时间数据解决这个问题的。在初始情况下,有员工name@domain.com,月薪5000。

{ "id": "name@domain.com", "salary": 5000 }

时间数据除了实际值外,还包含创建日期和值适用的时间范围。上述示例的时间数据和额外的时间限制直到2023年底:

{ "id": "name@domain.com", "salary": [ { "value": 5000, "created": "2023-01-01T00:00:00.0Z", "period": { "start": "2023-01-01T00:00:00.0Z", "end": "2023-12-31T23:59:59.9Z" } } ] }

时间数据是不可变的,所以下次调整工资时会创建一个新的时间值。在以下示例中,从3月到8月底,工资提高到6000。

{ "id": "name@domain.com", "salary": [ { "value": 5000, "created": "2023-01-01T00:00:00.0Z", "period": { "start": "2023-01-01T00:00:00.0Z" } }, { "value": 6000, "created": "2023-03-01T00:00:00.0Z", "period": { "start": "2023-03-01T00:00:00.0Z", "end": "2023-08-31T23:59:59.9Z" } } ] }

有了这些信息,可以在任何时间点确定值。时间数据确实开辟了新的视角:可以自由选择一个时间点,并从那个时间点开始查看数据。将时间值的观察日期命名为评估日期。

有了可变的评估日期,可以进行有趣的评估:过去视图 - 分析早期的数据;未来视图 - 分析未来的数据。下图显示了上述示例的不同评估日期:

在这个例子中,2023年3月1日工资发生了变化,追溯到2月。新的工资5500将一直有效到2023年4月中旬。

{ "id": "name@domain.com", "salary": [ { "value": 5000, "created": "2023-01-01T00:00:00.0Z", "period": { "start": "2023-01-01T00:00:00.0Z" } }, { "value": 5500, "created": "2023-03-01T00:00:00.0Z", "period": { "start": "2023-02-01T00:00:00.0Z", "end": "2023-04-15T23:59:59.9Z" } } ] }

评估时间数据值如下:

在这个场景中,2023年3月1日工资提高到4500。调整从9月到10月中旬有效。

{ "id": "name@domain.com", "salary": [ { "value": 5000, "created": "2023-01-01T00:00:00.0Z", "period": { "start": "2023-01-01T00:00:00.0Z" } }, { "value": 5500, "created": "2023-03-01T00:00:00.0Z", "period": { "start": "2023-08-01T00:00:00.0Z", "end": "2023-10-15T23:59:59.9Z" } } ] }

这导致以下时间数据值:

时间数据与C#

示例包含以下类型:

TimeValue - 包含时间数据 TimeField - 带有多个时间数据的时间字段 Employee - 带有工资作为时间数据的员工 Extensions - 时间数据计算 public class TimeValue { public DateTime Created { get; set; } public ValuePeriod Period { get; set; } public T Value { get; set; } } public class TimeField : Collection> { } public class Employee { public string? Id { get; set; } public TimeField? Salary { get; set; } } public static class Extensions { public static bool IsInside(this TimeValue value, DateTime evaluationDate) => evaluationDate >= value.Period.Start && evaluationDate <= value.Period.End; public static TimeValue? GetTimeValue(this TimeField values, DateTime evaluationDate) { if (!values.Any()) { return default; } var timeValues = values. // remove values created after the evaluation date Where(x => x.Created <= evaluationDate && // remove outside periods x.IsInside(evaluationDate)).ToList(); if (!timeValues.Any()) { return default; } // select the evaluated value (last created) var timeValue = timeValues.OrderByDescending(x => x.Created).First(); return timeValue; } }

计算当前时间值的算法GetTimeValue首先排除在评估时间之后创建的时间值或不相关的时间值(IsInside)。从剩余的时间值中,选择最近的一个。

在关系模型中,时间数据可以保存在单独的实体/表中。如果时间数据量小,最简单的场景是有一个单独的表包含时间数据,例如EmployeeSalary。对于大量时间数据,建议将它们分组到表中。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485