我正在try 将时间范围内的元素数组正常化.假设您有20笔发生在2022年1月1日的银行交易

transaction  1 - 2022/01/01
transaction  2 - 2022/01/01
...
transaction 20 - 2022/01/01

除了它们发生的日期之外,我们没有其他数据,但我们仍然希望为它们分配一天中的一个小时,因此它们的结尾为:

transaction  1 - 2022/01/01 00:00
transaction  2 - 2022/01/01 ??:??
...
transaction 20 - 2022/01/01 23:59

在Go中,我有这个函数,它try 计算元素数组中索引的归一化时间:

func normal(start, end time.Time, arraySize, index float64) time.Time {
    delta := end.Sub(start)
    minutes := delta.Minutes()

    duration := minutes * ((index+1) / arraySize)

    return start.Add(time.Duration(duration) * time.Minute)
}

但是,对于从2022/1/1 00:00到2022/1/1 23:59的时间范围内的4个元素的数组中的索引0,我得到了一个意外的计算结果2022/1/1 05:59,而我希望看到的是2022/1/1 00:00.唯一在这些条件下运行良好的是索引3.

那么,我的正常化做错了什么呢?

编辑:

以下是@icza修复的功能

func timeIndex(min, max time.Time, entries, position float64) time.Time {
    delta := max.Sub(min)
    minutes := delta.Minutes()

    if position < 0 {
        position = 0
    }

    duration := (minutes * (position / (entries - 1)))

    return min.Add(time.Duration(duration) * time.Minute)
}

这里有一个例子:假设我们的开始和结束日期是2022/01/01 00:00-2022/01/01 00:03,我们的银行交易数组中有3个条目,我们想要获得交易n:3(数组中的2)的标准化时间:

result := timeIndex(time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC), time.Date(2022, time.January, 1, 0, 3, 0, 0, time.UTC), 3, 2)

由于开始时间和结束时间(从00:0000:03)之间只有4分钟,并且想要找到数组(大小3)中最后一个条目(索引2)的归一化时间,因此结果应该是:

fmt.Printf("%t", result.Equal(time.Date(2022, time.January, 1, 0, 3, 0, 0, time.UTC))
// prints "true"

或者是最后一分钟,也就是00:03分.

下面是一个可重复使用的例子:https://go.dev/play/p/EzwkqaNV1at

推荐答案

n个点之间有n-1个线段.这意味着,如果要在插补中包括startend,则时间段数(为delta)为arraySize - 1.

另外,如果将1index相加,则不可能得到start(您将跳过00:00).

所以正确的算法是:

func normal(start, end time.Time, arraySize, index float64) time.Time {
    minutes := end.Sub(start).Minutes()

    duration := minutes * (index / (arraySize - 1))

    return start.Add(time.Duration(duration) * time.Minute)
}

Go Playground号公路上试试.

还要注意,如果您有多个事务(按一天中的分钟数约为1000分钟的顺序),您可能很容易得到具有相同时间戳(相同的小时和分钟)的多个事务.如果要避免这种情况,请使用比分钟更小的精度,例如秒或毫秒:

func normal(start, end time.Time, arraySize, index float64) time.Time {
    sec := end.Sub(start).Seconds()

    duration := sec * (index / (arraySize - 1))

    return start.Add(time.Duration(duration) * time.Second)
}

是的,这将导致时间戳中的秒也不一定是零,但将确保更高的交易号具有不同的、唯一的时间戳.

如果您的事务数量级接近一天中的秒数(即86400),则可以完全删除此"单位"并使用time.Duration本身(这是纳秒数).这将保证时间戳的唯一性,即使对于最高数量的交易也是如此:

func normal(start, end time.Time, arraySize, index float64) time.Time {
    delta := float64(end.Sub(start))

    duration := delta * (index / (arraySize - 1))

    return start.Add(time.Duration(duration))
}

用100万笔交易来测试这一点,以下是前15个时间部分(它们只推迟到次秒级部分):

0 - 00:00:00.00000
1 - 00:00:00.08634
2 - 00:00:00.17268
3 - 00:00:00.25902
4 - 00:00:00.34536
5 - 00:00:00.43170
6 - 00:00:00.51804
7 - 00:00:00.60438
8 - 00:00:00.69072
9 - 00:00:00.77706
10 - 00:00:00.86340
11 - 00:00:00.94974
12 - 00:00:01.03608
13 - 00:00:01.12242
14 - 00:00:01.20876
15 - 00:00:01.29510
16 - 00:00:01.38144
17 - 00:00:01.46778
18 - 00:00:01.55412
19 - 00:00:01.64046

Go Playground号公路上试试这件吧.

Go相关问答推荐

Websocket服务器实现与x/net库trowing 403

Golang内置打印(Ln)函数&S行为怪异

如何在Golang中使用ECHO服务器实现Socket.IO服务器

我们如何保证取消的上下文会导致 goroutine 终止?

使用goroutines在Golang中验证 struct

Global Thread-local Storage 在 Go 中的可行性和最佳实践

hyperledger fabric - go:在 $PATH 中找不到可执行文件

当我的 go build 成功时,如何修复我的 docker build 失败? Dockerfile 包括 go mod 下载

Go Template if 条件

如何在 Golang 中打印 2 列表?

创建新对象后如何返回嵌套实体?

如何在 golang revel 中获取动态应用程序配置

从 Makefile 运行时权限被拒绝

每次有人进入我的网站时如何运行特定功能?

当有多个同名包时如何在vscode中显示golang包完整路径?

从golang中的url加载图像

如何使路径/不匹配 Golang net/http 中所有其他不匹配的路径

GOENV 只能使用 OS 环境设置

comparable和any有什么区别?

并发 map map导致的 Golang 数据竞争