如何进行日期/时间比较?

huangapple go评论88阅读模式
英文:

How to do date/time comparison

问题

在Go语言中,有几种比较日期的选项。你可以根据日期和时间对数据进行排序,而且可以独立地进行排序。因此,你可以允许一个对象在一段日期范围内出现,只要它也在一段时间范围内出现即可。在这个模块中,你不能简单地选择最早的日期、最晚的日期、最晚的时间和Unix()秒来进行比较。我非常感谢任何建议。

最终,我编写了一个时间解析字符串比较模块,用于检查一个时间是否在一个范围内。然而,这个方法效果不太好,我遇到了一些严重的问题。我将在这里发布它,只是为了好玩,但我希望有更好的方法来比较时间。

package main

import (
    "strconv"
    "strings"
)

func tryIndex(arr []string, index int, def string) string {
    if index <= len(arr)-1 {
        return arr[index]
    }
    return def
}

/*
 * 接受两个格式为"hh:mm:ss"的字符串并进行比较。
 * 接受一个用于比较各个部分(由":"分割)的函数。
 * 注意:字符串实际上可以格式化为"h"、"hh"、"hh:m"、"hh:mm"等等。任何缺失的部分都将被懒惰地添加。
 */
func timeCompare(a, b string, compare func(int, int) (bool, bool)) bool {
    aArr := strings.Split(a, ":")
    bArr := strings.Split(b, ":")
    // 捕获边界情况。
    if b == a {
        return true
    }
    for i := range aArr {
        aI, _ := strconv.Atoi(tryIndex(aArr, i, "00"))
        bI, _ := strconv.Atoi(tryIndex(bArr, i, "00"))
        res, flag := compare(aI, bI)
        if res {
            return true
        } else if flag { // 需要捕获 a > b 且 a 是下限的情况
            return false
        }
    }
    return false
}

func timeGreaterEqual(a, b int) (bool, bool) { return a > b, a < b }
func timeLesserEqual(a, b int) (bool, bool) { return a < b, a > b }

/*
 * 对两个格式为"hh:mm:ss"的字符串返回true。
 * 注意:字符串实际上可以格式化为"h"、"hh"、"hh:m"、"hh:mm"等等。任何缺失的部分都将被懒惰地添加。
 */
func withinTime(timeRange, time string) bool {
    rArr := strings.Split(timeRange, "-")
    if timeCompare(rArr[0], rArr[1], timeLesserEqual) {
        afterStart := timeCompare(rArr[0], time, timeLesserEqual)
        beforeEnd := timeCompare(rArr[1], time, timeGreaterEqual)
        return afterStart && beforeEnd
    }
    // 捕获类似 `timeRange := "22:00:00-04:59:59"` 的情况,这将在从本地时间转换为UTC时发生。
    // 这是我认为有问题的部分。
    afterStart := timeCompare(rArr[0], time, timeLesserEqual)
    beforeEnd := timeCompare(rArr[1], time, timeGreaterEqual)
    return afterStart || beforeEnd
}

简而言之,我编写了一个withinTimeRange(range, time)函数,但它的工作并不完全正确。(实际上,主要是第二种情况,即时间范围跨越多天的情况出现了问题。原始部分是有效的,我只是意识到在从本地时间转换为UTC时需要考虑到这一点。)

如果有更好(最好是内置的)的方法,我很乐意听听!

注意:
只是作为一个例子,我用以下函数解决了JavaScript中的这个问题:

function withinTime(start, end, time) {
    var s = Date.parse("01/01/2011 " + start);
    var e = Date.parse("01/0" + (end == "24:00:00" ? "2" : "1") + "/2011 " + (end == "24:00:00" ? "00:00:00" : end));
    var t = Date.parse("01/01/2011 " + time);
    return s <= t && e >= t;
}

然而,我真的想在服务器端进行过滤。

英文:

Is there any options in doing date comparison in Go? I have to sort data based on date and time - independently. So I might allow an object that occurs within a range of dates so long as it also occurs within a range of times. In this model, I could not simply just select the oldest date, youngest time/latest date, latest time and Unix() seconds compare them. I'd really appreciate any suggestions.

Ultimately, I wrote a time parsing string compare module to check if a time is within a range. However, this is not faring to well; I've got some gaping issues. I'll post that here just for fun, but I'm hoping there's a better way to time compare.

package main
import (
&quot;strconv&quot;
&quot;strings&quot;
)
func tryIndex(arr []string, index int, def string) string {
if index &lt;= len(arr)-1 {
return arr[index]
}
return def
}
/*
* Takes two strings of format &quot;hh:mm:ss&quot; and compares them.
* Takes a function to compare individual sections (split by &quot;:&quot;).
* Note: strings can actually be formatted like &quot;h&quot;, &quot;hh&quot;, &quot;hh:m&quot;,
* &quot;hh:mm&quot;, etc. Any missing parts will be added lazily.
*/
func timeCompare(a, b string, compare func(int, int) (bool, bool)) bool {
aArr := strings.Split(a, &quot;:&quot;)
bArr := strings.Split(b, &quot;:&quot;)
// Catches margins.
if (b == a) {
return true
}
for i := range aArr {
aI, _ := strconv.Atoi(tryIndex(aArr, i, &quot;00&quot;))
bI, _ := strconv.Atoi(tryIndex(bArr, i, &quot;00&quot;))
res, flag := compare(aI, bI)
if res {
return true
} else if flag { // Needed to catch case where a &gt; b and a is the lower limit
return false
}
}
return false
}
func timeGreaterEqual(a, b int) (bool, bool) {return a &gt; b, a &lt; b}
func timeLesserEqual(a, b int) (bool, bool) {return a &lt; b, a &gt; b}
/*
* Returns true for two strings formmated &quot;hh:mm:ss&quot;.
* Note: strings can actually be formatted like &quot;h&quot;, &quot;hh&quot;, &quot;hh:m&quot;,
* &quot;hh:mm&quot;, etc. Any missing parts will be added lazily.
*/
func withinTime(timeRange, time string) bool {
rArr := strings.Split(timeRange, &quot;-&quot;)
if timeCompare(rArr[0], rArr[1], timeLesserEqual) {
afterStart := timeCompare(rArr[0], time, timeLesserEqual)
beforeEnd := timeCompare(rArr[1], time, timeGreaterEqual)
return afterStart &amp;&amp; beforeEnd
}
// Catch things like `timeRange := &quot;22:00:00-04:59:59&quot;` which will happen
// with UTC conversions from local time.
// THIS IS THE BROKEN PART I BELIEVE
afterStart := timeCompare(rArr[0], time, timeLesserEqual)
beforeEnd := timeCompare(rArr[1], time, timeGreaterEqual)
return afterStart || beforeEnd
}

So TLDR, I wrote a withinTimeRange(range, time) function but it's not working totally correctly. (In fact, mostly just the second case, where a time range crosses over days is broken. The original part worked, I just realized I'd need to account for that when making conversions to UTC from local.)

If there's a better (preferably built in) way, I'd love to hear about it!

NOTE:
Just as an example, I solved this issue in Javascript with this function:

function withinTime(start, end, time) {
var s = Date.parse(&quot;01/01/2011 &quot;+start);
var e = Date.parse(&quot;01/0&quot;+(end==&quot;24:00:00&quot;?&quot;2&quot;:&quot;1&quot;)+&quot;/2011 &quot;+(end==&quot;24:00:00&quot;?&quot;00:00:00&quot;:end));
var t = Date.parse(&quot;01/01/2011 &quot;+time);
return s &lt;= t &amp;&amp; e &gt;= t;
}

However I really want to do this filter server-side.

答案1

得分: 150

使用Go语言的time包来处理时间信息。

可以使用Before、After和Equal方法来比较时间点。Sub方法可以计算两个时间点之间的时间间隔,返回一个Duration类型的值。Add方法可以将一个时间点和一个时间间隔相加,返回一个新的时间点。

示例代码如下:

package main

import (
	"fmt"
	"time"
)

func inTimeSpan(start, end, check time.Time) bool {
	return check.After(start) && check.Before(end)
}

func main() {
	start, _ := time.Parse(time.RFC822, "01 Jan 15 10:00 UTC")
	end, _ := time.Parse(time.RFC822, "01 Jan 16 10:00 UTC")

	in, _ := time.Parse(time.RFC822, "01 Jan 15 20:00 UTC")
	out, _ := time.Parse(time.RFC822, "01 Jan 17 10:00 UTC")

	if inTimeSpan(start, end, in) {
		fmt.Println(in, "在", start, "和", end, "之间。")
	}

	if !inTimeSpan(start, end, out) {
		fmt.Println(out, "不在", start, "和", end, "之间。")
	}
}

你可以在这里运行这个示例代码。

英文:

Use the time package to work with time information in Go.

> Time instants can be compared using the Before, After, and Equal
> methods. The Sub method subtracts two instants, producing a Duration.
> The Add method adds a Time and a Duration, producing a Time.

Play example:

package main
import (
&quot;fmt&quot;
&quot;time&quot;
)
func inTimeSpan(start, end, check time.Time) bool {
return check.After(start) &amp;&amp; check.Before(end)
}
func main() {
start, _ := time.Parse(time.RFC822, &quot;01 Jan 15 10:00 UTC&quot;)
end, _ := time.Parse(time.RFC822, &quot;01 Jan 16 10:00 UTC&quot;)
in, _ := time.Parse(time.RFC822, &quot;01 Jan 15 20:00 UTC&quot;)
out, _ := time.Parse(time.RFC822, &quot;01 Jan 17 10:00 UTC&quot;)
if inTimeSpan(start, end, in) {
fmt.Println(in, &quot;is between&quot;, start, &quot;and&quot;, end, &quot;.&quot;)
}
if !inTimeSpan(start, end, out) {
fmt.Println(out, &quot;is not between&quot;, start, &quot;and&quot;, end, &quot;.&quot;)
}
}

答案2

得分: 34

对于比较两个时间,请使用time.Sub()函数。

// utc life
loc, _ := time.LoadLocation("UTC")

// 设置开始时间和结束时间
createdAt := time.Now().In(loc).Add(1 * time.Hour)
expiresAt := time.Now().In(loc).Add(4 * time.Hour)

// 获取时间差
diff := expiresAt.Sub(createdAt)
fmt.Printf("生命周期为 %+v", diff)

程序输出:

生命周期为 3h0m0s

http://play.golang.org/p/bbxeTtd4L6

英文:

For comparison between two times use time.Sub()

// utc life
loc, _ := time.LoadLocation(&quot;UTC&quot;)
// setup a start and end time
createdAt := time.Now().In(loc).Add(1 * time.Hour)
expiresAt := time.Now().In(loc).Add(4 * time.Hour)
// get the diff
diff := expiresAt.Sub(createdAt)
fmt.Printf(&quot;Lifespan is %+v&quot;, diff)

The program outputs:

Lifespan is 3h0m0s

http://play.golang.org/p/bbxeTtd4L6

答案3

得分: 21

当你的时间间隔的“end”日期不包含小时,例如“从2017-01-01到2017-01-16整天”,最好将时间间隔的“end”调整为第二天的午夜,以包括所有毫秒,代码如下:

if now.After(start) && now.Before(end.Add(24 * time.Hour).Truncate(24 * time.Hour)) {
    ...
}
英文:

For case when your interval's end date doesn't contains hours like
"from 2017-01-01 to whole day of 2017-01-16" it's better to adjust interval's end to midnight of the next day to include all milliseconds like this:

if now.After(start) &amp;&amp; now.Before(end.Add(24 * time.Hour).Truncate(24 * time.Hour)) {
...
}

答案4

得分: 8

可以使用以秒为粒度的Unix纪元的int64来比较日期。如果你需要更精确的比较,比如毫秒或微秒等,我认为@Oleg Neumyvakin的答案是完美的。

if expirationDate.Unix() > time.Now().Unix() {
...
}
英文:

It's possible to compare date using int64 of Unix epoch with seconds granularity. If you need more exact comparison like milisecons or microseconds etc. I guess that
@Oleg Neumyvakin's answer is perfect.

if expirationDate.Unix() &gt; time.Now().Unix() {
...
}

答案5

得分: 3

如果你想比较一个时间是否接近另一个时间以进行测试,你可以使用testify的assert.WithinDuration。例如:

expectedTime := time.Now()
actualTime := expectedTime.Add(100*time.Millisecond)
assert.WithinDuration(t, expectedTime, actualTime, 1*time.Second) // 通过
assert.WithinDuration(t, expectedTime, actualTime, 1*time.Millisecond) // 失败

否则,你可以在你的代码中重用assert.WithinDuration的实现来确定两个时间有多接近(通过将一个日期减去另一个日期得到时间差):

func WithinDuration(expected, actual time.Time, delta time.Duration) bool {
dt := expected.Sub(actual)
return dt >= -delta && dt <= delta
}
英文:

If you're interested in comparing whether a time is close to another for test purposes, you can use testify assert.WithinDuration for this. For example:

expectedTime := time.Now()
actualTime := expectedTime.Add(100*time.Millisecond)
assert.WithinDuration(t, expectedTime, actualTime, 1*time.Second) // pass
assert.WithinDuration(t, expectedTime, actualTime, 1*time.Millisecond) // fail

Otherwise the implementation of assert.WithinDuration can be re-used in your code to determine how close two times are (subtracting one date from the other gives the time difference):

func WithinDuration(expected, actual time.Time, delta time.Duration) bool {
dt := expected.Sub(actual)
return dt &gt;= -delta &amp;&amp; dt &lt;= delta
}

答案6

得分: 2

根据提案 time: add Time.Compare 和相关提交,time.Compare 将在新版本(Go 1.20)中添加。

// Compare 比较时间点 t 和 u。如果 t 在 u 之前,返回 -1;
// 如果 t 在 u 之后,返回 +1;如果它们相同,返回 0。
func (t Time) Compare(u Time) int {

示例

var t1, t2 Time

result := t1.Compare(t2)
英文:

Per proposal time: add Time.Compare and related commit, time.Compare will be added in the new release (Go 1.20)

// Compare compares the time instant t with u. If t is before u, it returns -1;
// if t is after u, it returns +1; if they&#39;re the same, it returns 0.
func (t Time) Compare(u Time) int {

Sample

var t1, t2 Time

result := t1.Compare(t2)

答案7

得分: 1

最近的协议更倾向于使用RFC3339,根据golang时间包文档的说明。

一般情况下,对于坚持使用RFC1123格式的服务器,应该使用RFC1123Z,对于新的协议,应该优先选择RFC3339。RFC822、RFC822Z、RFC1123和RFC1123Z用于格式化;当与time.Parse一起使用时,它们不接受RFC中允许的所有时间格式。

cutOffTime, _ := time.Parse(time.RFC3339, "2017-08-30T13:35:00Z")
// POSTDATE是数据库(数据存储)中的日期时间字段
query := datastore.NewQuery("db").Filter("POSTDATE >=", cutOffTime).
英文:

Recent protocols prefer usage of RFC3339 per golang time package documentation.

> In general RFC1123Z should be used instead of RFC1123 for servers that insist on that format, and RFC3339 should be preferred for new protocols. RFC822, RFC822Z, RFC1123, and RFC1123Z are useful for formatting; when used with time.Parse they do not accept all the time formats permitted by the RFCs.

cutOffTime, _ := time.Parse(time.RFC3339, &quot;2017-08-30T13:35:00Z&quot;)
// POSTDATE is a date time field in DB (datastore)
query := datastore.NewQuery(&quot;db&quot;).Filter(&quot;POSTDATE &gt;=&quot;, cutOffTime).

答案8

得分: 0

根据这个线程中的解释,我们可以在测试中使用github.com/google/go-cmp/cmp包来比较日期。

func TestDates(t *testing.T) {
    date, _ := time.Parse(time.RFC3339, "2021-11-05T12:00:00+02:00")
    dateEqual, _ := time.Parse(time.RFC3339, "2021-11-05T11:00:00+01:00")
    dateNotEqual, _ := time.Parse(time.RFC3339, "2021-11-05T12:00:01+02:00")

    assertDates(t, date, dateEqual)    //通过
    assertDates(t, date, dateNotEqual) //失败
}

func assertDates(t *testing.T, expected, actual time.Time) {
    t.Helper()

    if diff := cmp.Diff(expected, actual); diff != "" {
        t.Errorf("不匹配 (-expected +actual):\n%s", diff)
    }
}
英文:

As explained in the theread we could use github.com/google/go-cmp/cmp package for dates comparison in tests.

func TestDates(t *testing.T) {
date, _ := time.Parse(time.RFC3339, &quot;2021-11-05T12:00:00+02:00&quot;)
dateEqual, _ := time.Parse(time.RFC3339, &quot;2021-11-05T11:00:00+01:00&quot;)
dateNotEqual, _ := time.Parse(time.RFC3339, &quot;2021-11-05T12:00:01+02:00&quot;)
assertDates(t, date, dateEqual)    //pass
assertDates(t, date, dateNotEqual) //fail
}
func assertDates(t *testing.T, expected, actual time.Time) {
t.Helper()
if diff := cmp.Diff(expected, actual); diff != &quot;&quot; {
t.Errorf(&quot;mismatch (-expected +actual):\n%s&quot;, diff)
}
}

答案9

得分: 0

package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println("Hello World")
	maxRep := 5
	repPeroid := 6
	expiry := maxRep * repPeroid
	fmt.Println("Expiry: ", expiry)
	fmt.Println(time.Now())
	CorrIdtime := time.Now().Add(time.Second * time.Duration(expiry)).Format(time.RFC3339)
	Notifytime := time.Now().Add(2 * time.Second * time.Duration(expiry)).Format(time.RFC3339)
	
	fmt.Println(CorrIdtime)
	fmt.Println(Notifytime)

	if CorrIdtime < Notifytime {
		fmt.Println("Discarded")
	} else {
		fmt.Println("Accepted")
		
	}
}
package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println("你好,世界")
	maxRep := 5
	repPeroid := 6
	expiry := maxRep * repPeroid
	fmt.Println("过期时间:", expiry)
	fmt.Println(time.Now())
	CorrIdtime := time.Now().Add(time.Second * time.Duration(expiry)).Format(time.RFC3339)
	Notifytime := time.Now().Add(2 * time.Second * time.Duration(expiry)).Format(time.RFC3339)
	
	fmt.Println(CorrIdtime)
	fmt.Println(Notifytime)

	if CorrIdtime < Notifytime {
		fmt.Println("被丢弃")
	} else {
		fmt.Println("被接受")
		
	}
}

以上是给定的代码的中文翻译。

英文:
package main
import (
&quot;fmt&quot;
&quot;time&quot;
)
func main() {
fmt.Println(&quot;Hello World&quot;)
maxRep := 5
repPeroid := 6
expiry := maxRep * repPeroid
fmt.Println(&quot;Expiry: &quot;, expiry)
fmt.Println(time.Now())
CorrIdtime := time.Now().Add(time.Second * time.Duration(expiry)).Format(time.RFC3339)
Notifytime := time.Now().Add(2 * time.Second * time.Duration(expiry)).Format(time.RFC3339)
fmt.Println(CorrIdtime)
fmt.Println(Notifytime)
if CorrIdtime &lt; Notifytime {
fmt.Println(&quot;Discarded&quot;)
} else {
fmt.Println(&quot;Accepted&quot;)
}
}

huangapple
  • 本文由 发表于 2014年1月5日 01:35:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/20924303.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定