From 76e5fe5a87d7e60919075f96eee599f3c6255a9f Mon Sep 17 00:00:00 2001 From: Qais Patankar Date: Sun, 15 Mar 2020 22:43:46 +0000 Subject: Update vendor yaegashi/msgraph.go to v0.1.2 (2) --- vendor/github.com/rickb777/date/LICENSE | 27 + vendor/github.com/rickb777/date/period/doc.go | 44 ++ vendor/github.com/rickb777/date/period/format.go | 128 ++++ vendor/github.com/rickb777/date/period/marshal.go | 32 + vendor/github.com/rickb777/date/period/parse.go | 152 +++++ vendor/github.com/rickb777/date/period/period.go | 739 ++++++++++++++++++++++ vendor/github.com/rickb777/plural/.gitignore | 26 + vendor/github.com/rickb777/plural/.travis.yml | 11 + vendor/github.com/rickb777/plural/LICENSE | 27 + vendor/github.com/rickb777/plural/README.md | 28 + vendor/github.com/rickb777/plural/build+test.sh | 13 + vendor/github.com/rickb777/plural/doc.go | 20 + vendor/github.com/rickb777/plural/plural.go | 203 ++++++ 13 files changed, 1450 insertions(+) create mode 100644 vendor/github.com/rickb777/date/LICENSE create mode 100644 vendor/github.com/rickb777/date/period/doc.go create mode 100644 vendor/github.com/rickb777/date/period/format.go create mode 100644 vendor/github.com/rickb777/date/period/marshal.go create mode 100644 vendor/github.com/rickb777/date/period/parse.go create mode 100644 vendor/github.com/rickb777/date/period/period.go create mode 100644 vendor/github.com/rickb777/plural/.gitignore create mode 100644 vendor/github.com/rickb777/plural/.travis.yml create mode 100644 vendor/github.com/rickb777/plural/LICENSE create mode 100644 vendor/github.com/rickb777/plural/README.md create mode 100644 vendor/github.com/rickb777/plural/build+test.sh create mode 100644 vendor/github.com/rickb777/plural/doc.go create mode 100644 vendor/github.com/rickb777/plural/plural.go (limited to 'vendor/github.com/rickb777') diff --git a/vendor/github.com/rickb777/date/LICENSE b/vendor/github.com/rickb777/date/LICENSE new file mode 100644 index 00000000..b0280bc6 --- /dev/null +++ b/vendor/github.com/rickb777/date/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2015 The Go Authors & Rick Beton. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/rickb777/date/period/doc.go b/vendor/github.com/rickb777/date/period/doc.go new file mode 100644 index 00000000..c8a57b4a --- /dev/null +++ b/vendor/github.com/rickb777/date/period/doc.go @@ -0,0 +1,44 @@ +// Copyright 2016 Rick Beton. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package period provides functionality for periods of time using ISO-8601 conventions. +// This deals with years, months, weeks/days, hours, minutes and seconds. +// +// Because of the vagaries of calendar systems, the meaning of year lengths, month lengths +// and even day lengths depends on context. So a period is not necessarily a fixed duration +// of time in terms of seconds. +// +// See https://en.wikipedia.org/wiki/ISO_8601#Durations +// +// Example representations: +// +// * "P2Y" is two years; +// +// * "P6M" is six months; +// +// * "P4D" is four days; +// +// * "P1W" is one week (seven days); +// +// * "PT3H" is three hours. +// +// * "PT20M" is twenty minutes. +// +// * "PT30S" is thirty seconds. +// +// These can be combined, for example: +// +// * "P3Y6M4W1D" is three years, 6 months, 4 weeks and one day. +// +// * "P2DT12H" is 2 days and 12 hours. +// +// Also, decimal fractions are supported to one decimal place. To comply with +// the standard, only the last non-zero component is allowed to have a fraction. +// For example +// +// * "P2.5Y" is 2.5 years. +// +// * "PT12M7.5S" is 12 minutes and 7.5 seconds. +// +package period diff --git a/vendor/github.com/rickb777/date/period/format.go b/vendor/github.com/rickb777/date/period/format.go new file mode 100644 index 00000000..32cb6a83 --- /dev/null +++ b/vendor/github.com/rickb777/date/period/format.go @@ -0,0 +1,128 @@ +// Copyright 2015 Rick Beton. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package period + +import ( + "bytes" + "fmt" + "strings" + + "github.com/rickb777/plural" +) + +// Format converts the period to human-readable form using the default localisation. +func (period Period) Format() string { + return period.FormatWithPeriodNames(PeriodYearNames, PeriodMonthNames, PeriodWeekNames, PeriodDayNames, PeriodHourNames, PeriodMinuteNames, PeriodSecondNames) +} + +// FormatWithPeriodNames converts the period to human-readable form in a localisable way. +func (period Period) FormatWithPeriodNames(yearNames, monthNames, weekNames, dayNames, hourNames, minNames, secNames plural.Plurals) string { + period = period.Abs() + + parts := make([]string, 0) + parts = appendNonBlank(parts, yearNames.FormatFloat(absFloat10(period.years))) + parts = appendNonBlank(parts, monthNames.FormatFloat(absFloat10(period.months))) + + if period.days > 0 || (period.IsZero()) { + if len(weekNames) > 0 { + weeks := period.days / 70 + mdays := period.days % 70 + //fmt.Printf("%v %#v - %d %d\n", period, period, weeks, mdays) + if weeks > 0 { + parts = appendNonBlank(parts, weekNames.FormatInt(int(weeks))) + } + if mdays > 0 || weeks == 0 { + parts = appendNonBlank(parts, dayNames.FormatFloat(absFloat10(mdays))) + } + } else { + parts = appendNonBlank(parts, dayNames.FormatFloat(absFloat10(period.days))) + } + } + parts = appendNonBlank(parts, hourNames.FormatFloat(absFloat10(period.hours))) + parts = appendNonBlank(parts, minNames.FormatFloat(absFloat10(period.minutes))) + parts = appendNonBlank(parts, secNames.FormatFloat(absFloat10(period.seconds))) + + return strings.Join(parts, ", ") +} + +func appendNonBlank(parts []string, s string) []string { + if s == "" { + return parts + } + return append(parts, s) +} + +// PeriodDayNames provides the English default format names for the days part of the period. +// This is a sequence of plurals where the first match is used, otherwise the last one is used. +// The last one must include a "%v" placeholder for the number. +var PeriodDayNames = plural.FromZero("%v days", "%v day", "%v days") + +// PeriodWeekNames is as for PeriodDayNames but for weeks. +var PeriodWeekNames = plural.FromZero("", "%v week", "%v weeks") + +// PeriodMonthNames is as for PeriodDayNames but for months. +var PeriodMonthNames = plural.FromZero("", "%v month", "%v months") + +// PeriodYearNames is as for PeriodDayNames but for years. +var PeriodYearNames = plural.FromZero("", "%v year", "%v years") + +// PeriodHourNames is as for PeriodDayNames but for hours. +var PeriodHourNames = plural.FromZero("", "%v hour", "%v hours") + +// PeriodMinuteNames is as for PeriodDayNames but for minutes. +var PeriodMinuteNames = plural.FromZero("", "%v minute", "%v minutes") + +// PeriodSecondNames is as for PeriodDayNames but for seconds. +var PeriodSecondNames = plural.FromZero("", "%v second", "%v seconds") + +// String converts the period to ISO-8601 form. +func (period Period) String() string { + if period.IsZero() { + return "P0D" + } + + buf := &bytes.Buffer{} + if period.Sign() < 0 { + buf.WriteByte('-') + } + + buf.WriteByte('P') + + if period.years != 0 { + fmt.Fprintf(buf, "%gY", absFloat10(period.years)) + } + if period.months != 0 { + fmt.Fprintf(buf, "%gM", absFloat10(period.months)) + } + if period.days != 0 { + if period.days%70 == 0 { + fmt.Fprintf(buf, "%gW", absFloat10(period.days/7)) + } else { + fmt.Fprintf(buf, "%gD", absFloat10(period.days)) + } + } + if period.hours != 0 || period.minutes != 0 || period.seconds != 0 { + buf.WriteByte('T') + } + if period.hours != 0 { + fmt.Fprintf(buf, "%gH", absFloat10(period.hours)) + } + if period.minutes != 0 { + fmt.Fprintf(buf, "%gM", absFloat10(period.minutes)) + } + if period.seconds != 0 { + fmt.Fprintf(buf, "%gS", absFloat10(period.seconds)) + } + + return buf.String() +} + +func absFloat10(v int16) float32 { + f := float32(v) / 10 + if v < 0 { + return -f + } + return f +} diff --git a/vendor/github.com/rickb777/date/period/marshal.go b/vendor/github.com/rickb777/date/period/marshal.go new file mode 100644 index 00000000..c87ad5f6 --- /dev/null +++ b/vendor/github.com/rickb777/date/period/marshal.go @@ -0,0 +1,32 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package period + +// MarshalBinary implements the encoding.BinaryMarshaler interface. +// This also provides support for gob encoding. +func (period Period) MarshalBinary() ([]byte, error) { + // binary method would take more space in many cases, so we simply use text + return period.MarshalText() +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. +// This also provides support for gob encoding. +func (period *Period) UnmarshalBinary(data []byte) error { + return period.UnmarshalText(data) +} + +// MarshalText implements the encoding.TextMarshaler interface for Periods. +func (period Period) MarshalText() ([]byte, error) { + return []byte(period.String()), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface for Periods. +func (period *Period) UnmarshalText(data []byte) (err error) { + u, err := Parse(string(data)) + if err == nil { + *period = u + } + return err +} diff --git a/vendor/github.com/rickb777/date/period/parse.go b/vendor/github.com/rickb777/date/period/parse.go new file mode 100644 index 00000000..30b3a693 --- /dev/null +++ b/vendor/github.com/rickb777/date/period/parse.go @@ -0,0 +1,152 @@ +// Copyright 2015 Rick Beton. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package period + +import ( + "fmt" + "strconv" + "strings" +) + +// MustParse is as per Parse except that it panics if the string cannot be parsed. +// This is intended for setup code; don't use it for user inputs. +func MustParse(value string) Period { + d, err := Parse(value) + if err != nil { + panic(err) + } + return d +} + +// Parse parses strings that specify periods using ISO-8601 rules. +// +// In addition, a plus or minus sign can precede the period, e.g. "-P10D" +// +// The value is normalised, e.g. multiple of 12 months become years so "P24M" +// is the same as "P2Y". However, this is done without loss of precision, so +// for example whole numbers of days do not contribute to the months tally +// because the number of days per month is variable. +// +// The zero value can be represented in several ways: all of the following +// are equivalent: "P0Y", "P0M", "P0W", "P0D", "PT0H", PT0M", PT0S", and "P0". +// The canonical zero is "P0D". +func Parse(period string) (Period, error) { + if period == "" { + return Period{}, fmt.Errorf("cannot parse a blank string as a period") + } + + if period == "P0" { + return Period{}, nil + } + + result := period64{} + pcopy := period + if pcopy[0] == '-' { + result.neg = true + pcopy = pcopy[1:] + } else if pcopy[0] == '+' { + pcopy = pcopy[1:] + } + + if pcopy[0] != 'P' { + return Period{}, fmt.Errorf("expected 'P' period mark at the start: %s", period) + } + pcopy = pcopy[1:] + + st := parseState{period, pcopy, false, nil} + t := strings.IndexByte(pcopy, 'T') + if t >= 0 { + st.pcopy = pcopy[t+1:] + + result.hours, st = parseField(st, 'H') + if st.err != nil { + return Period{}, fmt.Errorf("expected a number before the 'H' marker: %s", period) + } + + result.minutes, st = parseField(st, 'M') + if st.err != nil { + return Period{}, fmt.Errorf("expected a number before the 'M' marker: %s", period) + } + + result.seconds, st = parseField(st, 'S') + if st.err != nil { + return Period{}, fmt.Errorf("expected a number before the 'S' marker: %s", period) + } + + st.pcopy = pcopy[:t] + } + + result.years, st = parseField(st, 'Y') + if st.err != nil { + return Period{}, fmt.Errorf("expected a number before the 'Y' marker: %s", period) + } + + result.months, st = parseField(st, 'M') + if st.err != nil { + return Period{}, fmt.Errorf("expected a number before the 'M' marker: %s", period) + } + + weeks, st := parseField(st, 'W') + if st.err != nil { + return Period{}, fmt.Errorf("expected a number before the 'W' marker: %s", period) + } + + days, st := parseField(st, 'D') + if st.err != nil { + return Period{}, fmt.Errorf("expected a number before the 'D' marker: %s", period) + } + + result.days = weeks*7 + days + //fmt.Printf("%#v\n", st) + + if !st.ok { + return Period{}, fmt.Errorf("expected 'Y', 'M', 'W', 'D', 'H', 'M', or 'S' marker: %s", period) + } + + return result.normalise64(true).toPeriod(), nil +} + +type parseState struct { + period, pcopy string + ok bool + err error +} + +func parseField(st parseState, mark byte) (int64, parseState) { + //fmt.Printf("%c %#v\n", mark, st) + r := int64(0) + m := strings.IndexByte(st.pcopy, mark) + if m > 0 { + r, st.err = parseDecimalFixedPoint(st.pcopy[:m], st.period) + if st.err != nil { + return 0, st + } + st.pcopy = st.pcopy[m+1:] + st.ok = true + } + return r, st +} + +// Fixed-point three decimal places +func parseDecimalFixedPoint(s, original string) (int64, error) { + //was := s + dec := strings.IndexByte(s, '.') + if dec < 0 { + dec = strings.IndexByte(s, ',') + } + + if dec >= 0 { + dp := len(s) - dec + if dp > 1 { + s = s[:dec] + s[dec+1:dec+2] + } else { + s = s[:dec] + s[dec+1:] + "0" + } + } else { + s = s + "0" + } + + return strconv.ParseInt(s, 10, 64) +} diff --git a/vendor/github.com/rickb777/date/period/period.go b/vendor/github.com/rickb777/date/period/period.go new file mode 100644 index 00000000..604dbfc1 --- /dev/null +++ b/vendor/github.com/rickb777/date/period/period.go @@ -0,0 +1,739 @@ +// Copyright 2015 Rick Beton. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package period + +import ( + "fmt" + "time" +) + +const daysPerYearE4 int64 = 3652425 // 365.2425 days by the Gregorian rule +const daysPerMonthE4 int64 = 304375 // 30.4375 days per month +const daysPerMonthE6 int64 = 30436875 // 30.436875 days per month + +const oneE4 int64 = 10000 +const oneE5 int64 = 100000 +const oneE6 int64 = 1000000 +const oneE7 int64 = 10000000 + +const hundredMs = 100 * time.Millisecond + +// reminder: int64 overflow is after 9,223,372,036,854,775,807 (math.MaxInt64) + +// Period holds a period of time and provides conversion to/from ISO-8601 representations. +// Therefore there are six fields: years, months, days, hours, minutes, and seconds. +// +// In the ISO representation, decimal fractions are supported, although only the last non-zero +// component is allowed to have a fraction according to the Standard. For example "P2.5Y" +// is 2.5 years. +// +// However, in this implementation, the precision is limited to one decimal place only, by +// means of integers with fixed point arithmetic. (This avoids using float32 in the struct, +// so there are no problems testing equality using ==.) +// +// The implementation limits the range of possible values to ± 2^16 / 10 in each field. +// Note in particular that the range of years is limited to approximately ± 3276. +// +// The concept of weeks exists in string representations of periods, but otherwise weeks +// are unimportant. The period contains a number of days from which the number of weeks can +// be calculated when needed. +// +// Note that although fractional weeks can be parsed, they will never be returned via String(). +// This is because the number of weeks is always inferred from the number of days. +// +type Period struct { + years, months, days, hours, minutes, seconds int16 +} + +// NewYMD creates a simple period without any fractional parts. The fields are initialised verbatim +// without any normalisation; e.g. 12 months will not become 1 year. Use the Normalise method if you +// need to. +// +// All the parameters must have the same sign (otherwise a panic occurs). +func NewYMD(years, months, days int) Period { + return New(years, months, days, 0, 0, 0) +} + +// NewHMS creates a simple period without any fractional parts. The fields are initialised verbatim +// without any normalisation; e.g. 120 seconds will not become 2 minutes. Use the Normalise method +// if you need to. +// +// All the parameters must have the same sign (otherwise a panic occurs). +func NewHMS(hours, minutes, seconds int) Period { + return New(0, 0, 0, hours, minutes, seconds) +} + +// New creates a simple period without any fractional parts. The fields are initialised verbatim +// without any normalisation; e.g. 120 seconds will not become 2 minutes. Use the Normalise method +// if you need to. +// +// All the parameters must have the same sign (otherwise a panic occurs). +func New(years, months, days, hours, minutes, seconds int) Period { + if (years >= 0 && months >= 0 && days >= 0 && hours >= 0 && minutes >= 0 && seconds >= 0) || + (years <= 0 && months <= 0 && days <= 0 && hours <= 0 && minutes <= 0 && seconds <= 0) { + return Period{ + int16(years) * 10, int16(months) * 10, int16(days) * 10, + int16(hours) * 10, int16(minutes) * 10, int16(seconds) * 10, + } + } + panic(fmt.Sprintf("Periods must have homogeneous signs; got P%dY%dM%dDT%dH%dM%dS", + years, months, days, hours, minutes, seconds)) +} + +// TODO NewFloat + +// NewOf converts a time duration to a Period, and also indicates whether the conversion is precise. +// Any time duration that spans more than ± 3276 hours will be approximated by assuming that there +// are 24 hours per day, 30.4375 per month and 365.2425 days per year. +func NewOf(duration time.Duration) (p Period, precise bool) { + var sign int16 = 1 + d := duration + if duration < 0 { + sign = -1 + d = -duration + } + + sign10 := sign * 10 + + totalHours := int64(d / time.Hour) + + // check for 16-bit overflow - occurs near the 4.5 month mark + if totalHours < 3277 { + // simple HMS case + minutes := d % time.Hour / time.Minute + seconds := d % time.Minute / hundredMs + return Period{0, 0, 0, sign10 * int16(totalHours), sign10 * int16(minutes), sign * int16(seconds)}, true + } + + totalDays := totalHours / 24 // ignoring daylight savings adjustments + + if totalDays < 3277 { + hours := totalHours - totalDays*24 + minutes := d % time.Hour / time.Minute + seconds := d % time.Minute / hundredMs + return Period{0, 0, sign10 * int16(totalDays), sign10 * int16(hours), sign10 * int16(minutes), sign * int16(seconds)}, false + } + + // TODO it is uncertain whether this is too imprecise and should be improved + years := (oneE4 * totalDays) / daysPerYearE4 + months := ((oneE4 * totalDays) / daysPerMonthE4) - (12 * years) + hours := totalHours - totalDays*24 + totalDays = ((totalDays * oneE4) - (daysPerMonthE4 * months) - (daysPerYearE4 * years)) / oneE4 + return Period{sign10 * int16(years), sign10 * int16(months), sign10 * int16(totalDays), sign10 * int16(hours), 0, 0}, false +} + +// Between converts the span between two times to a period. Based on the Gregorian conversion +// algorithms of `time.Time`, the resultant period is precise. +// +// The result is not normalised; for time differences less than 3276 days, it will contain zero in the +// years and months fields but the number of days may be up to 3275; this reduces errors arising from +// the variable lengths of months. For larger time differences, greater than 3276 days, the months and +// years fields are used as well. +// +// Remember that the resultant period does not retain any knowledge of the calendar, so any subsequent +// computations applied to the period can only be precise if they concern either the date (year, month, +// day) part, or the clock (hour, minute, second) part, but not both. +func Between(t1, t2 time.Time) (p Period) { + if t1.Location() != t2.Location() { + t2 = t2.In(t1.Location()) + } + + sign := 1 + if t2.Before(t1) { + t1, t2, sign = t2, t1, -1 + } + + year, month, day, hour, min, sec, hundredth := daysDiff(t1, t2) + + if sign < 0 { + p = New(-year, -month, -day, -hour, -min, -sec) + p.seconds -= int16(hundredth) + } else { + p = New(year, month, day, hour, min, sec) + p.seconds += int16(hundredth) + } + return +} + +func daysDiff(t1, t2 time.Time) (year, month, day, hour, min, sec, hundredth int) { + duration := t2.Sub(t1) + + hh1, mm1, ss1 := t1.Clock() + hh2, mm2, ss2 := t2.Clock() + + day = int(duration / (24 * time.Hour)) + + hour = int(hh2 - hh1) + min = int(mm2 - mm1) + sec = int(ss2 - ss1) + hundredth = (t2.Nanosecond() - t1.Nanosecond()) / 100000000 + + // Normalize negative values + if sec < 0 { + sec += 60 + min-- + } + + if min < 0 { + min += 60 + hour-- + } + + if hour < 0 { + hour += 24 + // no need to reduce day - it's calculated differently. + } + + // test 16bit storage limit (with 1 fixed decimal place) + if day > 3276 { + y1, m1, d1 := t1.Date() + y2, m2, d2 := t2.Date() + year = y2 - y1 + month = int(m2 - m1) + day = d2 - d1 + } + + return +} + +// IsZero returns true if applied to a zero-length period. +func (period Period) IsZero() bool { + return period == Period{} +} + +// IsPositive returns true if any field is greater than zero. By design, this also implies that +// all the other fields are greater than or equal to zero. +func (period Period) IsPositive() bool { + return period.years > 0 || period.months > 0 || period.days > 0 || + period.hours > 0 || period.minutes > 0 || period.seconds > 0 +} + +// IsNegative returns true if any field is negative. By design, this also implies that +// all the other fields are negative or zero. +func (period Period) IsNegative() bool { + return period.years < 0 || period.months < 0 || period.days < 0 || + period.hours < 0 || period.minutes < 0 || period.seconds < 0 +} + +// Sign returns +1 for positive periods and -1 for negative periods. If the period is zero, it returns zero. +func (period Period) Sign() int { + if period.IsZero() { + return 0 + } + if period.IsNegative() { + return -1 + } + return 1 +} + +// OnlyYMD returns a new Period with only the year, month and day fields. The hour, +// minute and second fields are zeroed. +func (period Period) OnlyYMD() Period { + return Period{period.years, period.months, period.days, 0, 0, 0} +} + +// OnlyHMS returns a new Period with only the hour, minute and second fields. The year, +// month and day fields are zeroed. +func (period Period) OnlyHMS() Period { + return Period{0, 0, 0, period.hours, period.minutes, period.seconds} +} + +// Abs converts a negative period to a positive one. +func (period Period) Abs() Period { + return Period{absInt16(period.years), absInt16(period.months), absInt16(period.days), + absInt16(period.hours), absInt16(period.minutes), absInt16(period.seconds)} +} + +func absInt16(v int16) int16 { + if v < 0 { + return -v + } + return v +} + +// Negate changes the sign of the period. +func (period Period) Negate() Period { + return Period{-period.years, -period.months, -period.days, -period.hours, -period.minutes, -period.seconds} +} + +// Add adds two periods together. Use this method along with Negate in order to subtract periods. +// +// The result is not normalised and may overflow arithmetically (to make this unlikely, use Normalise on +// the inputs before adding them). +func (period Period) Add(that Period) Period { + return Period{ + period.years + that.years, + period.months + that.months, + period.days + that.days, + period.hours + that.hours, + period.minutes + that.minutes, + period.seconds + that.seconds, + } +} + +// Scale a period by a multiplication factor. Obviously, this can both enlarge and shrink it, +// and change the sign if negative. The result is normalised. +// +// Bear in mind that the internal representation is limited by fixed-point arithmetic with one +// decimal place; each field is only int16. +// +// Known issue: scaling by a large reduction factor (i.e. much less than one) doesn't work properly. +func (period Period) Scale(factor float32) Period { + + if -0.5 < factor && factor < 0.5 { + d, pr1 := period.Duration() + mul := float64(d) * float64(factor) + p2, pr2 := NewOf(time.Duration(mul)) + return p2.Normalise(pr1 && pr2) + } + + y := int64(float32(period.years) * factor) + m := int64(float32(period.months) * factor) + d := int64(float32(period.days) * factor) + hh := int64(float32(period.hours) * factor) + mm := int64(float32(period.minutes) * factor) + ss := int64(float32(period.seconds) * factor) + + return (&period64{y, m, d, hh, mm, ss, false}).normalise64(true).toPeriod() +} + +// Years gets the whole number of years in the period. +// The result is the number of years and does not include any other field. +func (period Period) Years() int { + return int(period.YearsFloat()) +} + +// YearsFloat gets the number of years in the period, including a fraction if any is present. +// The result is the number of years and does not include any other field. +func (period Period) YearsFloat() float32 { + return float32(period.years) / 10 +} + +// Months gets the whole number of months in the period. +// The result is the number of months and does not include any other field. +// +// Note that after normalisation, whole multiple of 12 months are added to +// the number of years, so the number of months will be reduced correspondingly. +func (period Period) Months() int { + return int(period.MonthsFloat()) +} + +// MonthsFloat gets the number of months in the period. +// The result is the number of months and does not include any other field. +// +// Note that after normalisation, whole multiple of 12 months are added to +// the number of years, so the number of months will be reduced correspondingly. +func (period Period) MonthsFloat() float32 { + return float32(period.months) / 10 +} + +// Days gets the whole number of days in the period. This includes the implied +// number of weeks but does not include any other field. +func (period Period) Days() int { + return int(period.DaysFloat()) +} + +// DaysFloat gets the number of days in the period. This includes the implied +// number of weeks but does not include any other field. +func (period Period) DaysFloat() float32 { + return float32(period.days) / 10 +} + +// Weeks calculates the number of whole weeks from the number of days. If the result +// would contain a fraction, it is truncated. +// The result is the number of weeks and does not include any other field. +// +// Note that weeks are synthetic: they are internally represented using days. +// See ModuloDays(), which returns the number of days excluding whole weeks. +func (period Period) Weeks() int { + return int(period.days) / 70 +} + +// WeeksFloat calculates the number of weeks from the number of days. +// The result is the number of weeks and does not include any other field. +func (period Period) WeeksFloat() float32 { + return float32(period.days) / 70 +} + +// ModuloDays calculates the whole number of days remaining after the whole number of weeks +// has been excluded. +func (period Period) ModuloDays() int { + days := absInt16(period.days) % 70 + f := int(days / 10) + if period.days < 0 { + return -f + } + return f +} + +// Hours gets the whole number of hours in the period. +// The result is the number of hours and does not include any other field. +func (period Period) Hours() int { + return int(period.HoursFloat()) +} + +// HoursFloat gets the number of hours in the period. +// The result is the number of hours and does not include any other field. +func (period Period) HoursFloat() float32 { + return float32(period.hours) / 10 +} + +// Minutes gets the whole number of minutes in the period. +// The result is the number of minutes and does not include any other field. +// +// Note that after normalisation, whole multiple of 60 minutes are added to +// the number of hours, so the number of minutes will be reduced correspondingly. +func (period Period) Minutes() int { + return int(period.MinutesFloat()) +} + +// MinutesFloat gets the number of minutes in the period. +// The result is the number of minutes and does not include any other field. +// +// Note that after normalisation, whole multiple of 60 minutes are added to +// the number of hours, so the number of minutes will be reduced correspondingly. +func (period Period) MinutesFloat() float32 { + return float32(period.minutes) / 10 +} + +// Seconds gets the whole number of seconds in the period. +// The result is the number of seconds and does not include any other field. +// +// Note that after normalisation, whole multiple of 60 seconds are added to +// the number of minutes, so the number of seconds will be reduced correspondingly. +func (period Period) Seconds() int { + return int(period.SecondsFloat()) +} + +// SecondsFloat gets the number of seconds in the period. +// The result is the number of seconds and does not include any other field. +// +// Note that after normalisation, whole multiple of 60 seconds are added to +// the number of minutes, so the number of seconds will be reduced correspondingly. +func (period Period) SecondsFloat() float32 { + return float32(period.seconds) / 10 +} + +// AddTo adds the period to a time, returning the result. +// A flag is also returned that is true when the conversion was precise and false otherwise. +// +// When the period specifies hours, minutes and seconds only, the result is precise. +// Also, when the period specifies whole years, months and days (i.e. without fractions), the +// result is precise. However, when years, months or days contains fractions, the result +// is only an approximation (it assumes that all days are 24 hours and every year is 365.2425 days). +func (period Period) AddTo(t time.Time) (time.Time, bool) { + wholeYears := (period.years % 10) == 0 + wholeMonths := (period.months % 10) == 0 + wholeDays := (period.days % 10) == 0 + + if wholeYears && wholeMonths && wholeDays { + // in this case, time.AddDate provides an exact solution + stE3 := totalSecondsE3(period) + t1 := t.AddDate(int(period.years/10), int(period.months/10), int(period.days/10)) + return t1.Add(stE3 * time.Millisecond), true + } + + d, precise := period.Duration() + return t.Add(d), precise +} + +// DurationApprox converts a period to the equivalent duration in nanoseconds. +// When the period specifies hours, minutes and seconds only, the result is precise. +// however, when the period specifies years, months and days, it is impossible to be precise +// because the result may depend on knowing date and timezone information, so the duration +// is estimated on the basis of a year being 365.2425 days and a month being +// 1/12 of a that; days are all assumed to be 24 hours long. +func (period Period) DurationApprox() time.Duration { + d, _ := period.Duration() + return d +} + +// Duration converts a period to the equivalent duration in nanoseconds. +// A flag is also returned that is true when the conversion was precise and false otherwise. +// +// When the period specifies hours, minutes and seconds only, the result is precise. +// however, when the period specifies years, months and days, it is impossible to be precise +// because the result may depend on knowing date and timezone information, so the duration +// is estimated on the basis of a year being 365.2425 days and a month being +// 1/12 of a that; days are all assumed to be 24 hours long. +func (period Period) Duration() (time.Duration, bool) { + // remember that the fields are all fixed-point 1E1 + tdE6 := time.Duration(totalDaysApproxE7(period) * 8640) + stE3 := totalSecondsE3(period) + return tdE6*time.Microsecond + stE3*time.Millisecond, tdE6 == 0 +} + +func totalSecondsE3(period Period) time.Duration { + // remember that the fields are all fixed-point 1E1 + // and these are divided by 1E1 + hhE3 := time.Duration(period.hours) * 360000 + mmE3 := time.Duration(period.minutes) * 6000 + ssE3 := time.Duration(period.seconds) * 100 + return hhE3 + mmE3 + ssE3 +} + +func totalDaysApproxE7(period Period) int64 { + // remember that the fields are all fixed-point 1E1 + ydE6 := int64(period.years) * (daysPerYearE4 * 100) + mdE6 := int64(period.months) * daysPerMonthE6 + ddE6 := int64(period.days) * oneE6 + return ydE6 + mdE6 + ddE6 +} + +// TotalDaysApprox gets the approximate total number of days in the period. The approximation assumes +// a year is 365.2425 days and a month is 1/12 of that. Whole multiples of 24 hours are also included +// in the calculation. +func (period Period) TotalDaysApprox() int { + pn := period.Normalise(false) + tdE6 := totalDaysApproxE7(pn) + hE6 := (int64(pn.hours) * oneE6) / 24 + return int((tdE6 + hE6) / oneE7) +} + +// TotalMonthsApprox gets the approximate total number of months in the period. The days component +// is included by approximation, assuming a year is 365.2425 days and a month is 1/12 of that. +// Whole multiples of 24 hours are also included in the calculation. +func (period Period) TotalMonthsApprox() int { + pn := period.Normalise(false) + mE1 := int64(pn.years)*12 + int64(pn.months) + hE1 := int64(pn.hours) / 24 + dE1 := ((int64(pn.days) + hE1) * oneE6) / daysPerMonthE6 + return int((mE1 + dE1) / 10) +} + +// Normalise attempts to simplify the fields. It operates in either precise or imprecise mode. +// +// Because the number of hours per day is imprecise (due to daylight savings etc), and because +// the number of days per month is variable in the Gregorian calendar, there is a reluctance +// to transfer time too or from the days element. To give control over this, there are two modes. +// +// In precise mode: +// Multiples of 60 seconds become minutes. +// Multiples of 60 minutes become hours. +// Multiples of 12 months become years. +// +// Additionally, in imprecise mode: +// Multiples of 24 hours become days. +// Multiples of approx. 30.4 days become months. +// +// Note that leap seconds are disregarded: every minute is assumed to have 60 seconds. +func (period Period) Normalise(precise bool) Period { + const limit = 32670 - (32670 / 60) + + // can we use a quicker algorithm for HHMMSS with int16 arithmetic? + if period.years == 0 && period.months == 0 && + (!precise || period.days == 0) && + period.hours > -limit && period.hours < limit { + + return period.normaliseHHMMSS(precise) + } + + // can we use a quicker algorithm for YYMM with int16 arithmetic? + if (period.years != 0 || period.months != 0) && //period.months%10 == 0 && + period.days == 0 && period.hours == 0 && period.minutes == 0 && period.seconds == 0 { + + return period.normaliseYYMM() + } + + // do things the no-nonsense way using int64 arithmetic + return period.toPeriod64().normalise64(precise).toPeriod() +} + +func (period Period) normaliseHHMMSS(precise bool) Period { + s := period.Sign() + ap := period.Abs() + + // remember that the fields are all fixed-point 1E1 + ap.minutes += (ap.seconds / 600) * 10 + ap.seconds = ap.seconds % 600 + + ap.hours += (ap.minutes / 600) * 10 + ap.minutes = ap.minutes % 600 + + // up to 36 hours stays as hours + if !precise && ap.hours > 360 { + ap.days += (ap.hours / 240) * 10 + ap.hours = ap.hours % 240 + } + + d10 := ap.days % 10 + if d10 != 0 && (ap.hours != 0 || ap.minutes != 0 || ap.seconds != 0) { + ap.hours += d10 * 24 + ap.days -= d10 + } + + hh10 := ap.hours % 10 + if hh10 != 0 { + ap.minutes += hh10 * 60 + ap.hours -= hh10 + } + + mm10 := ap.minutes % 10 + if mm10 != 0 { + ap.seconds += mm10 * 60 + ap.minutes -= mm10 + } + + if s < 0 { + return ap.Negate() + } + return ap +} + +func (period Period) normaliseYYMM() Period { + s := period.Sign() + ap := period.Abs() + + // remember that the fields are all fixed-point 1E1 + if ap.months > 129 { + ap.years += (ap.months / 120) * 10 + ap.months = ap.months % 120 + } + + y10 := ap.years % 10 + if y10 != 0 && (ap.years < 10 || ap.months != 0) { + ap.months += y10 * 12 + ap.years -= y10 + } + + if s < 0 { + return ap.Negate() + } + return ap +} + +//------------------------------------------------------------------------------------------------- + +// used for stages in arithmetic +type period64 struct { + years, months, days, hours, minutes, seconds int64 + neg bool +} + +func (period Period) toPeriod64() *period64 { + return &period64{ + int64(period.years), int64(period.months), int64(period.days), + int64(period.hours), int64(period.minutes), int64(period.seconds), + false, + } +} + +func (p *period64) toPeriod() Period { + if p.neg { + return Period{ + int16(-p.years), int16(-p.months), int16(-p.days), + int16(-p.hours), int16(-p.minutes), int16(-p.seconds), + } + } + + return Period{ + int16(p.years), int16(p.months), int16(p.days), + int16(p.hours), int16(p.minutes), int16(p.seconds), + } +} + +func (p *period64) normalise64(precise bool) *period64 { + return p.abs().rippleUp(precise).moveFractionToRight() +} + +func (p *period64) abs() *period64 { + + if !p.neg { + if p.years < 0 { + p.years = -p.years + p.neg = true + } + + if p.months < 0 { + p.months = -p.months + p.neg = true + } + + if p.days < 0 { + p.days = -p.days + p.neg = true + } + + if p.hours < 0 { + p.hours = -p.hours + p.neg = true + } + + if p.minutes < 0 { + p.minutes = -p.minutes + p.neg = true + } + + if p.seconds < 0 { + p.seconds = -p.seconds + p.neg = true + } + } + return p +} + +func (p *period64) rippleUp(precise bool) *period64 { + // remember that the fields are all fixed-point 1E1 + + p.minutes = p.minutes + (p.seconds/600)*10 + p.seconds = p.seconds % 600 + + p.hours = p.hours + (p.minutes/600)*10 + p.minutes = p.minutes % 600 + + // 32670-(32670/60)-(32670/3600) = 32760 - 546 - 9.1 = 32204.9 + if !precise || p.hours > 32204 { + p.days += (p.hours / 240) * 10 + p.hours = p.hours % 240 + } + + if !precise || p.days > 32760 { + dE6 := p.days * oneE6 + p.months += dE6 / daysPerMonthE6 + p.days = (dE6 % daysPerMonthE6) / oneE6 + } + + p.years = p.years + (p.months/120)*10 + p.months = p.months % 120 + + return p +} + +// moveFractionToRight applies the rule that only the smallest field is permitted to have a decimal fraction. +func (p *period64) moveFractionToRight() *period64 { + // remember that the fields are all fixed-point 1E1 + + y10 := p.years % 10 + if y10 != 0 && (p.months != 0 || p.days != 0 || p.hours != 0 || p.minutes != 0 || p.seconds != 0) { + p.months += y10 * 12 + p.years = (p.years / 10) * 10 + } + + m10 := p.months % 10 + if m10 != 0 && (p.days != 0 || p.hours != 0 || p.minutes != 0 || p.seconds != 0) { + p.days += (m10 * daysPerMonthE6) / oneE6 + p.months = (p.months / 10) * 10 + } + + d10 := p.days % 10 + if d10 != 0 && (p.hours != 0 || p.minutes != 0 || p.seconds != 0) { + p.hours += d10 * 24 + p.days = (p.days / 10) * 10 + } + + hh10 := p.hours % 10 + if hh10 != 0 && (p.minutes != 0 || p.seconds != 0) { + p.minutes += hh10 * 60 + p.hours = (p.hours / 10) * 10 + } + + mm10 := p.minutes % 10 + if mm10 != 0 && p.seconds != 0 { + p.seconds += mm10 * 60 + p.minutes = (p.minutes / 10) * 10 + } + + return p +} diff --git a/vendor/github.com/rickb777/plural/.gitignore b/vendor/github.com/rickb777/plural/.gitignore new file mode 100644 index 00000000..49ef8df4 --- /dev/null +++ b/vendor/github.com/rickb777/plural/.gitignore @@ -0,0 +1,26 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj/ +_test/ +vendor/ + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof +*.out diff --git a/vendor/github.com/rickb777/plural/.travis.yml b/vendor/github.com/rickb777/plural/.travis.yml new file mode 100644 index 00000000..70996d4b --- /dev/null +++ b/vendor/github.com/rickb777/plural/.travis.yml @@ -0,0 +1,11 @@ +language: go + +go: + - tip + +install: + - go get -t -v . + - go get github.com/mattn/goveralls + +script: + - ./build+test.sh diff --git a/vendor/github.com/rickb777/plural/LICENSE b/vendor/github.com/rickb777/plural/LICENSE new file mode 100644 index 00000000..4faeca51 --- /dev/null +++ b/vendor/github.com/rickb777/plural/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2016, Rick Beton +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of plural nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/rickb777/plural/README.md b/vendor/github.com/rickb777/plural/README.md new file mode 100644 index 00000000..b73bc256 --- /dev/null +++ b/vendor/github.com/rickb777/plural/README.md @@ -0,0 +1,28 @@ +# plural - Simple Go API for Pluralisation. + +[![GoDoc](https://img.shields.io/badge/api-Godoc-blue.svg?style=flat-square)](https://godoc.org/github.com/rickb777/plural) +[![Build Status](https://travis-ci.org/rickb777/plural.svg?branch=master)](https://travis-ci.org/rickb777/plural) +[![Coverage Status](https://coveralls.io/repos/github/rickb777/plural/badge.svg?branch=master&service=github)](https://coveralls.io/github/rickb777/plural?branch=master) +[![Go Report Card](https://goreportcard.com/badge/github.com/rickb777/plural)](https://goreportcard.com/report/github.com/rickb777/plural) +[![Issues](https://img.shields.io/github/issues/rickb777/plural.svg)](https://github.com/rickb777/plural/issues) + +Package plural provides simple support for localising plurals in a flexible range of different styles. + +There are considerable differences around the world in the way plurals are handled. This is a simple +but competent API for catering with these differences when presenting to people formatted text with numbers. + +This package is able to format **countable things** and **continuous values**. It can handle integers +and floating point numbers equally and this allows you to decide to what extent each is appropriate. + +For example, `2 cars` might weigh `1.6 tonnes`; both categories are covered. + +This API is deliberately simple; it doesn't address the full gamut of internationalisation. If that's +what you need, you should consider products such as https://github.com/nicksnyder/go-i18n instead. + +## Installation + + go get -u github.com/rickb777/plural + +## Status + +This library has been in reliable production use for some time. Versioning follows the well-known semantic version pattern. diff --git a/vendor/github.com/rickb777/plural/build+test.sh b/vendor/github.com/rickb777/plural/build+test.sh new file mode 100644 index 00000000..d4a38342 --- /dev/null +++ b/vendor/github.com/rickb777/plural/build+test.sh @@ -0,0 +1,13 @@ +#!/bin/bash -e +cd $(dirname $0) +PATH=$HOME/gopath/bin:$GOPATH/bin:$PATH + +if ! type -p goveralls; then + echo go get github.com/mattn/goveralls + go get github.com/mattn/goveralls +fi + +echo date... +go test -v -covermode=count -coverprofile=date.out . +go tool cover -func=date.out +[ -z "$COVERALLS_TOKEN" ] || goveralls -coverprofile=date.out -service=travis-ci -repotoken $COVERALLS_TOKEN diff --git a/vendor/github.com/rickb777/plural/doc.go b/vendor/github.com/rickb777/plural/doc.go new file mode 100644 index 00000000..90adf6b5 --- /dev/null +++ b/vendor/github.com/rickb777/plural/doc.go @@ -0,0 +1,20 @@ +// Copyright 2016 Rick Beton. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package plural provides simple support for localising plurals in a flexible range of different styles. +// +// There are considerable differences around the world in the way plurals are handled. This is +// a simple but competent API for catering with these differences when presenting to people formatted text with numbers. +// +// This package is able to format countable things and continuous values. It can handle integers +// and floating point numbers equally and this allows you to decide to what extent each is appropriate. +// +// For example, "2 cars" might weigh "1.6 tonnes"; both categories are covered. +// +// This API is deliberately simple; it doesn't address the full gamut of internationalisation. If that's +// what you need, you should consider products such as https://github.com/nicksnyder/go-i18n instead. +// +// Please see the examples and associated api documentation. +// +package plural diff --git a/vendor/github.com/rickb777/plural/plural.go b/vendor/github.com/rickb777/plural/plural.go new file mode 100644 index 00000000..794a433f --- /dev/null +++ b/vendor/github.com/rickb777/plural/plural.go @@ -0,0 +1,203 @@ +package plural + +import ( + "fmt" + "strings" +) + +// Case is the inner element of this API and describes one case. When the number to be described +// matches the number here, the corresponding format string will be used. If the format string +// includes '%', then fmt.Sprintf will be used. Otherwise the format string will be returned verbatim. +type Case struct { + Number int + Format string +} + +// Plurals provides a list of plural cases in the order they will be searched. +// For plurals of continuous ranges (e.g. weight), the cases must be in ascending number order. +// For plurals of discrete ranges (i.e. integers), the cases can be in any order you require, +// but will conventionally be in ascending number order. +// If no match is found, the last case will be used. +type Plurals []Case + +// Format searches through the plural cases for the first match. If none is found, the last +// case is used. The value passed in can be any number type, or pointer to a number type, except +// complex numbers are not supported. The value will be converted to an int in order to +// find the first case that matches. +// The only possible error arises if value has a type that is not numeric. +// It panics if 'plurals' is empty. +func (plurals Plurals) Format(value interface{}) (string, error) { + switch x := value.(type) { + case int: + return plurals.FormatInt(x), nil + case int8: + return plurals.FormatInt(int(x)), nil + case int16: + return plurals.FormatInt(int(x)), nil + case int32: + return plurals.FormatInt(int(x)), nil + case int64: + return plurals.FormatInt(int(x)), nil + case uint8: + return plurals.FormatInt(int(x)), nil + case uint16: + return plurals.FormatInt(int(x)), nil + case uint32: + return plurals.FormatInt(int(x)), nil + case uint64: + return plurals.FormatInt(int(x)), nil + case float32: + return plurals.FormatFloat(x), nil + case float64: + return plurals.FormatFloat(float32(x)), nil + + case *int: + return plurals.FormatInt(*x), nil + case *int8: + return plurals.FormatInt(int(*x)), nil + case *int16: + return plurals.FormatInt(int(*x)), nil + case *int32: + return plurals.FormatInt(int(*x)), nil + case *int64: + return plurals.FormatInt(int(*x)), nil + case *uint: + return plurals.FormatInt(int(*x)), nil + case *uint8: + return plurals.FormatInt(int(*x)), nil + case *uint16: + return plurals.FormatInt(int(*x)), nil + case *uint32: + return plurals.FormatInt(int(*x)), nil + case *uint64: + return plurals.FormatInt(int(*x)), nil + case *float32: + return plurals.FormatFloat(*x), nil + case *float64: + return plurals.FormatFloat(float32(*x)), nil + + case nil: + return "", fmt.Errorf("Unexpected nil value for %s", plurals) + default: + return "", fmt.Errorf("Unexpected type %T for %v", x, value) + } +} + +// FormatInt expresses an int in plural form. It panics if 'plurals' is empty. +func (plurals Plurals) FormatInt(value int) string { + for _, c := range plurals { + if value == c.Number { + return c.FormatInt(value) + } + } + c := plurals[len(plurals)-1] + return c.FormatInt(value) +} + +// FormatFloat expresses a float32 in plural form. It panics if 'plurals' is empty. +func (plurals Plurals) FormatFloat(value float32) string { + for _, c := range plurals { + if value <= float32(c.Number) { + return c.FormatFloat(value) + } + } + c := plurals[len(plurals)-1] + return c.FormatFloat(value) +} + +// FormatInt renders a specific case with a given value. +func (c Case) FormatInt(value int) string { + if strings.IndexByte(c.Format, '%') < 0 { + return c.Format + } + return fmt.Sprintf(c.Format, value) +} + +// FormatFloat renders a specific case with a given value. +func (c Case) FormatFloat(value float32) string { + if strings.IndexByte(c.Format, '%') < 0 { + return c.Format + } + return fmt.Sprintf(c.Format, value) +} + +//------------------------------------------------------------------------------------------------- + +// String implements io.Stringer. +func (plurals Plurals) String() string { + ss := make([]string, 0, len(plurals)) + for _, c := range plurals { + ss = append(ss, c.String()) + } + return fmt.Sprintf("Plurals(%s)", strings.Join(ss, ", ")) +} + +// String implements io.Stringer. +func (c Case) String() string { + return fmt.Sprintf("{%v -> %q}", c.Number, c.Format) +} + +//------------------------------------------------------------------------------------------------- + +// ByOrdinal constructs a simple set of cases using small ordinals (0, 1, 2, 3 etc), which is a +// common requirement. It is an alias for FromZero. +func ByOrdinal(zeroth string, rest ...string) Plurals { + return FromZero(zeroth, rest...) +} + +// FromZero constructs a simple set of cases using small ordinals (0, 1, 2, 3 etc), which is a +// common requirement. It prevents creation of a Plurals list that is empty, which would be invalid. +// +// The 'zeroth' string becomes Case{0, first}. The rest are appended similarly. Notice that the +// counting starts from zero. +// +// So +// +// FromZero("nothing", "%v thing", "%v things") +// +// is simply a shorthand for +// +// Plurals{Case{0, "nothing"}, Case{1, "%v thing"}, Case{2, "%v things"}} +// +// which would also be valid but a little more verbose. +// +// This helper function is less flexible than constructing Plurals directly, but covers many common +// situations. +func FromZero(zeroth string, rest ...string) Plurals { + p := make(Plurals, 0, len(rest)+1) + p = append(p, Case{0, zeroth}) + for i, c := range rest { + p = append(p, Case{i+1, c}) + } + return p +} + +// FromOne constructs a simple set of cases using small positive numbers (1, 2, 3 etc), which is a +// common requirement. It prevents creation of a Plurals list that is empty, which would be invalid. +// +// The 'first' string becomes Case{1, first}. The rest are appended similarly. Notice that the +// counting starts from one. +// +// So +// +// FromOne("%v thing", "%v things") +// +// is simply a shorthand for +// +// Plurals{Case{1, "%v thing"}, Case{2, "%v things"}} +// +// which would also be valid but a little more verbose. +// +// Note the behaviour of formatting when the count is zero. As a consequence of Format evaluating +// the cases in order, FromOne(...).FormatInt(0) will pick the last case you provide, not the first. +// +// This helper function is less flexible than constructing Plurals directly, but covers many common +// situations. +func FromOne(first string, rest ...string) Plurals { + p := make(Plurals, 0, len(rest)+1) + p = append(p, Case{1, first}) + for i, c := range rest { + p = append(p, Case{i+2, c}) + } + return p +} -- cgit v1.2.3