diff options
| author | John R. Lenton <jlenton@gmail.com> | 2018-03-02 13:00:20 +0000 |
|---|---|---|
| committer | John R. Lenton <jlenton@gmail.com> | 2018-03-02 13:00:20 +0000 |
| commit | ca103056d91e3ad66623c2c4c9a2edde48623816 (patch) | |
| tree | b6891643d1e188f99fe654ed3033457a43592997 /timeutil | |
| parent | 6b02a9f529ac8f0a21beed97968a77ffd82a2f0e (diff) | |
| parent | 1faa0f1c46717766c7c090ffe1d324b12bda9260 (diff) | |
Merge remote-tracking branch 'upstream/master' into i18n-ng-awkwardness
Diffstat (limited to 'timeutil')
| -rw-r--r-- | timeutil/export_test.go | 1 | ||||
| -rw-r--r-- | timeutil/human.go | 69 | ||||
| -rw-r--r-- | timeutil/human_test.go | 79 |
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) +} |
