该资料讨论在Kotlin中构建按周期步进的时间导航器(如按天、周、月查看历史区间)时,如何选择并坚持一种时间区间边界约定,以及如何管理周期偏移量。
主要能力与观点:
一、统一边界约定。整个导航器通过同一个函数rangeFor(range, offset)生成起止时间:offset按周期数向前计数,0表示当前周期,1表示上一个周期。与上一周期比较只是用offset+1再调用一次。资料强调,一个功能中混用两种约定是常见状态而非假设:周期边界用闭区间(atTime(0,0)到atTime(23,59,59)),而图表的日、周、月桶用半开区间(单元起点到下一个单元起点,月份还有12月分支)。这些边界随后又交给谓词为BETWEEN(两端都包含)的查询,形成隐患。
二、封闭上界23:59:59会丢失该秒剩余时间。时间戳按毫秒存储,23:59:59.001至23:59:59.999之间的事件不属于任何周期,在“最近发生了什么”列表中缺失的是最新一行。资料建议使用真正可表示的最后一刻,或改用半开区间并同步修改谓词。
三、半开边界配包含式谓词会在接缝处重复计数。例如[day, day+1]用BETWEEN会把恰好落在午夜的同一个事件计入两个桶。
四、推荐标准化为半开区间,代价是修改查询谓词:>= start AND < nextStart没有接缝、不丢余量、不依赖存储精度;BETWEEN无法表达这种语义,因此这是查询改动而非边界改动,只做一半会比任一约定单独使用更糟。
五、两种约定在夏令时切换日也有分歧:“一天的开始”按区域解析返回第一个存在的时刻,在午夜不存在的地区是01:00;而裸atTime(0,0)返回墙上时间值,没有事件能携带它。两者都不算错,只是在一年两次的某条路径上代表不同的日子。
六、偏移量是计数,粒度变化会静默沿用。7天粒度向前三步是21天前,90天粒度向前三步是九个月前,类型检查和抛错都不会发生。资料建议每次粒度变化时把偏移量重置为0,并在当前周期处钳制,钳制生效时提前返回,避免重复加载;向前可用的判断应由offset推导(如periodOffset > 0),不要单独维护布尔值。
七、周期内的桶另有边界规则,与步进是不同决策,两者的交互仅在于粒度变化时重置偏移量。固定长度区间按天步进,日历年按年步进并走不同分支,因此其lengthInDays声明后未使用且错误,要么不提供长度,要么把步进单位放进枚举。当前周期被截断而上一周期完整,会导致所有差值偏低;应明确标注当前周期进行中,或将上一周期按相同已过比例截断。
验证方式:用grep查找所有边界构造并读取其约定;统计查询实际如何比较(BETWEEN数量与>=…<数量);用SQL统计封闭边界丢弃的事件(timestamp % 86400000 > 86399000)以及恰好落在桶接缝上的事件(同一表达式=0);最后执行“先步进、再切换粒度”,确认区间标签显示当前周期,并在边界处向前步进时确认没有重新加载(出现加载指示说明缺少提前返回)。