summaryrefslogtreecommitdiff
path: root/timeutil
diff options
authorJohn R. Lenton <jlenton@gmail.com>2018-03-02 13:00:20 +0000
committerJohn R. Lenton <jlenton@gmail.com>2018-03-02 13:00:20 +0000
commitca103056d91e3ad66623c2c4c9a2edde48623816 (patch)
treeb6891643d1e188f99fe654ed3033457a43592997 /timeutil
parent6b02a9f529ac8f0a21beed97968a77ffd82a2f0e (diff)
parent1faa0f1c46717766c7c090ffe1d324b12bda9260 (diff)
Merge remote-tracking branch 'upstream/master' into i18n-ng-awkwardness
Diffstat (limited to 'timeutil')
-rw-r--r--timeutil/export_test.go1
-rw-r--r--timeutil/human.go69
-rw-r--r--timeutil/human_test.go79
3 files changed, 149 insertions, 0 deletions
diff --git a/timeutil/export_test.go b/timeutil/export_test.go
index 8feda29ac0..3a9bb34863 100644
--- a/timeutil/export_test.go
+++ b/timeutil/export_test.go
@@ -23,6 +23,7 @@ import "time"
var (
ParseClockSpan = parseClockSpan
+ HumanTimeSince = humanTimeSince
)
func MockTimeNow(f func() time.Time) (restorer func()) {
diff --git a/timeutil/human.go b/timeutil/human.go
new file mode 100644
index 0000000000..c432c7cd3e
--- /dev/null
+++ b/timeutil/human.go
@@ -0,0 +1,69 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2018 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package timeutil
+
+import (
+ "fmt"
+ "math"
+ "time"
+
+ "github.com/snapcore/snapd/i18n"
+)
+
+func noon(t time.Time) time.Time {
+ y, m, d := t.Date()
+ return time.Date(y, m, d, 12, 0, 0, 0, t.Location())
+}
+
+// Human turns the time into a relative expression of time meant for human
+// consumption.
+// Human(t) --> "today at 07:47 "
+func Human(then time.Time) string {
+ return humanTimeSince(then.Local(), time.Now().Local())
+}
+
+func ngd(d int) uint32 {
+ const max = 1000000
+ if d > max {
+ return uint32((d % max) + max)
+ }
+ return uint32(d)
+}
+
+func humanTimeSince(then, now time.Time) string {
+ d := int(math.Floor(noon(then).Sub(noon(now)).Hours() / 24))
+ switch {
+ case d < -1:
+ // TRANSLATORS: %d will be at least 2; the singular is only included to help gettext
+ return fmt.Sprintf(then.Format(i18n.NG("in %d day, at 15:04 MST", "%d days ago, at 15:04 MST", ngd(-d))), -d)
+ case d == -1:
+ return then.Format(i18n.G("yesterday at 15:04 MST"))
+ case d == 0:
+ return then.Format(i18n.G("today at 15:04 MST"))
+ case d == 1:
+ return then.Format(i18n.G("tomorrow at 15:04 MST"))
+ case d > 1:
+ // TRANSLATORS: %d will be at least 2; the singular is only included to help gettext
+ return fmt.Sprintf(then.Format(i18n.NG("in %d day, at 15:04 MST", "in %d days, at 15:04 MST", ngd(d))), d)
+ default:
+ // the following message is brought to you by Joel Armando, the self-described awesome and sexy mathematician.
+ panic("you have broken the law of trichotomy! ℤ is no longer totally ordered! chaos ensues!")
+ }
+}
diff --git a/timeutil/human_test.go b/timeutil/human_test.go
new file mode 100644
index 0000000000..bf58931a2c
--- /dev/null
+++ b/timeutil/human_test.go
@@ -0,0 +1,79 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2018 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package timeutil_test
+
+import (
+ "time"
+
+ "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/timeutil"
+)
+
+type humanSuite struct {
+ beforeDSTbegins, afterDSTbegins, beforeDSTends, afterDSTends time.Time
+}
+
+var _ = check.Suite(&humanSuite{})
+
+func (s *humanSuite) SetUpSuite(c *check.C) {
+ loc, err := time.LoadLocation("Europe/London")
+ c.Assert(err, check.IsNil)
+
+ s.beforeDSTbegins = time.Date(2017, 3, 26, 0, 59, 0, 0, loc)
+ // note this is actually 2:01am DST
+ s.afterDSTbegins = time.Date(2017, 3, 26, 1, 1, 0, 0, loc)
+
+ // apparently no way to straight out initialise a time inside the DST overlap
+ s.beforeDSTends = time.Date(2017, 10, 29, 0, 59, 0, 0, loc).Add(60 * time.Minute)
+ s.afterDSTends = time.Date(2017, 10, 29, 1, 1, 0, 0, loc)
+
+ // sanity check
+ c.Check(s.beforeDSTbegins.Format("MST"), check.Equals, s.afterDSTends.Format("MST"))
+ c.Check(s.beforeDSTbegins.Format("MST"), check.Equals, "GMT")
+ c.Check(s.afterDSTbegins.Format("MST"), check.Equals, s.beforeDSTends.Format("MST"))
+ c.Check(s.afterDSTbegins.Format("MST"), check.Equals, "BST")
+
+ // “The month, day, hour, min, sec, and nsec values may be outside their
+ // usual ranges and will be normalized during the conversion.”
+ // so you can always add or subtract 1 from a day and it'll just work \o/
+ c.Check(time.Date(2017, -1, -1, -1, -1, -1, 0, loc), check.DeepEquals, time.Date(2016, 10, 29, 22, 58, 59, 0, loc))
+ c.Check(time.Date(2017, 13, 32, 25, 61, 63, 0, loc), check.DeepEquals, time.Date(2018, 2, 2, 2, 2, 3, 0, loc))
+}
+
+func (s *humanSuite) TestHumanTimeDST(c *check.C) {
+ c.Check(timeutil.HumanTimeSince(s.beforeDSTbegins, s.afterDSTbegins), check.Equals, "today at 00:59 GMT")
+ c.Check(timeutil.HumanTimeSince(s.beforeDSTends, s.afterDSTends), check.Equals, "today at 01:59 BST")
+ c.Check(timeutil.HumanTimeSince(s.beforeDSTbegins, s.afterDSTends), check.Equals, "218 days ago, at 00:59 GMT")
+}
+
+func (*humanSuite) TestHuman(c *check.C) {
+ now := time.Now()
+ timePart := now.Format("15:04 MST")
+ y, m, d := now.Date()
+ H, M, S := now.Clock()
+ loc := now.Location()
+
+ c.Check(timeutil.Human(time.Date(y, m, d-2, H, M, S, 0, loc)), check.Equals, "2 days ago, at "+timePart)
+ c.Check(timeutil.Human(time.Date(y, m, d-1, H, M, S, 0, loc)), check.Equals, "yesterday at "+timePart)
+ c.Check(timeutil.Human(now), check.Equals, "today at "+timePart)
+ c.Check(timeutil.Human(time.Date(y, m, d+1, H, M, S, 0, loc)), check.Equals, "tomorrow at "+timePart)
+ c.Check(timeutil.Human(time.Date(y, m, d+2, H, M, S, 0, loc)), check.Equals, "in 2 days, at "+timePart)
+}