Documentation ¶
Overview ¶
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. The type time.Duration is measured in terms of (nano-) seconds. Periods can be converted to/from durations; depending on the length of period this may be calculated exactly or approximately.
The period defined in this API is specified by ISO-8601, but that uses the term 'duration' instead; see https://en.wikipedia.org/wiki/ISO_8601#Durations. In Go, time.Duration and this period.ISOString follow terminology similar to e.g. Joda Time in that a 'duration' is a definite number of seconds (or fractions of a second).
Example period.ISOString representations:
* "P2Y" is two years;
* "P6M" is six months;
* "P1W" is one week (seven days);
* "P4D" is four days;
* "PT3H" is three hours.
* "PT20M" is twenty minutes.
* "PT30S" is thirty seconds.
* "-PT30S" or "PT-30S" is minus thirty seconds (implies an "earlier" time).
These can be combined, for example:
* "P3Y11M4W1D" is 3 years, 11 months, 4 weeks and 1 day, which is nearly 4 years.
* "P2DT12H" is 2 days and 12 hours.
* "P1M-1D" is 1 month minus 1 day. Mixed signs are permitted but may not be widely supported elsewhere.
Also, decimal fractions are supported. To comply with the standard, only the last non-zero component is allowed to have a fraction. For example
* "P2.5Y" or "P2,5Y" is 2.5 years; both notations are allowed.
* "PT12M7.5S" is 12 minutes and 7.5 seconds.
Index ¶
- Variables
- type Designator
- type FormatLocalisation
- type ISOString
- type Period
- func Between(t1, t2 time.Time) Period
- func MustNewDecimal(years, months, weeks, days, hours, minutes, seconds decimal.Decimal) Period
- func MustParse[S ISOString | string](isoPeriod S) Period
- func New(years, months, weeks, days, hours, minutes, seconds int) Period
- func NewDecimal(years, months, weeks, days, hours, minutes, seconds decimal.Decimal) (period Period, err error)
- func NewHMS(hours, minutes, seconds int) Period
- func NewOf(duration time.Duration) Period
- func NewYMD(years, months, days int) Period
- func NewYMWD(years, months, weeks, days int) Period
- func Parse[S ISOString | string](isoPeriod S) (Period, error)
- func (period Period) Abs() Period
- func (period Period) Add(other Period) (Period, error)
- func (period Period) AddTo(t time.Time) (time.Time, bool)
- func (period Period) Days() int
- func (period Period) DaysDecimal() decimal.Decimal
- func (period Period) DaysIncWeeks() int
- func (period Period) DaysIncWeeksDecimal() decimal.Decimal
- func (period Period) Duration() (time.Duration, bool)
- func (period Period) DurationApprox() time.Duration
- func (period Period) Format() string
- func (period Period) FormatLocalised(config FormatLocalisation) string
- func (period *Period) Get() any
- func (period Period) GetField(field Designator) decimal.Decimal
- func (period Period) GetInt(field Designator) int
- func (period Period) Hours() int
- func (period Period) HoursDecimal() decimal.Decimal
- func (period Period) IsNegative() bool
- func (period Period) IsPositive() bool
- func (period Period) IsZero() bool
- func (period Period) MarshalBinary() ([]byte, error)
- func (period Period) MarshalText() ([]byte, error)
- func (period Period) Minutes() int
- func (period Period) MinutesDecimal() decimal.Decimal
- func (period Period) Months() int
- func (period Period) MonthsDecimal() decimal.Decimal
- func (period Period) Mul(factor decimal.Decimal) (Period, error)
- func (period Period) Negate() Period
- func (period Period) Normalise(precise bool) Period
- func (period Period) NormaliseDaysToYears() Period
- func (period Period) OnlyHMS() Period
- func (period Period) OnlyYMWD() Period
- func (period *Period) Parse(isoPeriod string) error
- func (period Period) Period() ISOString
- func (period *Period) Scan(value interface{}) (err error)
- func (period Period) Seconds() int
- func (period Period) SecondsDecimal() decimal.Decimal
- func (period *Period) Set(p string) error
- func (period Period) SetField(value decimal.Decimal, field Designator) (Period, error)
- func (period Period) SetInt(value int, field Designator) Period
- func (period Period) Sign() int
- func (period Period) Simplify(precise bool) Period
- func (period Period) SimplifyWeeks() Period
- func (period Period) SimplifyWeeksToDays() Period
- func (period Period) String() string
- func (period Period) Subtract(other Period) (Period, error)
- func (period Period) TotalDaysApprox() int
- func (period Period) TotalMonthsApprox() int
- func (period Period) Type() string
- func (period *Period) UnmarshalBinary(data []byte) error
- func (period *Period) UnmarshalText(data []byte) error
- func (period Period) Value() (driver.Value, error)
- func (period Period) Weeks() int
- func (period Period) WeeksDecimal() decimal.Decimal
- func (period Period) WriteTo(w io.Writer) (int64, error)
- func (period Period) Years() int
- func (period Period) YearsDecimal() decimal.Decimal
Constants ¶
This section is empty.
Variables ¶
var DefaultFormatLocalisation = FormatLocalisation{ ZeroValue: "zero", Negate: func(s string) string { return "minus " + s }, YearNames: plural.FromZero("", "%v year", "%v years"), MonthNames: plural.FromZero("", "%v month", "%v months"), WeekNames: plural.FromZero("", "%v week", "%v weeks"), DayNames: plural.FromZero("", "%v day", "%v days"), HourNames: plural.FromZero("", "%v hour", "%v hours"), MinuteNames: plural.FromZero("", "%v minute", "%v minutes"), SecondNames: plural.FromZero("", "%v second", "%v seconds"), }
DefaultFormatLocalisation provides the formatting strings needed to format Period values in vernacular English.
var Zero = Period{}
Zero is the zero period.
Functions ¶
This section is empty.
Types ¶
type Designator ¶ added in v0.13.0
type Designator int8
Designator enumerates the seven fields in a Period.
const ( Second Designator Minute Hour Day Week Month Year )
func (Designator) Byte ¶ added in v0.13.0
func (d Designator) Byte() byte
type FormatLocalisation ¶ added in v0.7.0
type FormatLocalisation struct { // ZeroValue is the string that represents a zero period "P0D". ZeroValue string // Negate alters a format string when the value is negative. Negate func(string) string // The plurals provide the localised format names for each field of the period. // Each is a sequence of plural cases where the first match is used, otherwise the last one is used. // The last one must include a "%v" placeholder for the number. YearNames, MonthNames, WeekNames, DayNames plural.Plurals HourNames, MinuteNames, SecondNames plural.Plurals }
type ISOString ¶ added in v0.6.0
type ISOString string
ISOString holds a period of time and provides conversion to/from ISO-8601 representations. Therefore, there are seven fields: years, months, weeks, 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.
const CanonicalZero ISOString = "P0D"
CanonicalZero is the zero length period in one of its possible representations.
type Period ¶
type Period struct {
// contains filtered or unexported fields
}
Period holds a period of time as a set of decimal numbers, one for each field in the ISO-8601 period.
By conventional, all the fields should have the same sign. However, this is not restricted, so each field after the first non-zero field can be independently positive or negative. Sometimes this makes sense, e.g. "P1YT-1S" is one second less than one year.
The precision is large: all fields are scaled decimals using int64 internally for calculations. The value of each field can have up to 19 digits (the range of int64), of which up to 19 digits can be a decimal fraction. So the range is much wider than that of time.Duration; be aware that periods more than 292 years or less than one nanosecond are outside the convertible range.
For convenience, the method inputs and outputs use int.
Fractions are supported on the least significant non-zero field only. It is an error for more-significant fields to have fractional values too.
Instances are immutable.
func Between ¶ added in v0.5.0
Between converts the span between two times to a period. Based on the Gregorian conversion algorithms of `time.Time`, the resultant period is precise.
If t2 is before t1, the result is a negative period.
The result just a number of seconds, possibly including a fraction. It is not normalised; see Normalise.
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 MustNewDecimal ¶ added in v0.11.0
MustNewDecimal creates a period from seven decimal values. The fields are trimmed but no normalisation is applied, e.g. 120 seconds will not become 2 minutes. Use Normalise if you need to.
Periods only allow the least-significant non-zero field to contain a fraction. If any of the more-significant fields is supplied with a fraction, this function panics.
func MustParse ¶
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 New ¶ added in v0.4.0
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 Normalise if you need to.
func NewDecimal ¶ added in v0.4.0
func NewDecimal(years, months, weeks, days, hours, minutes, seconds decimal.Decimal) (period Period, err error)
NewDecimal creates a period from seven decimal values. The fields are trimmed but no normalisation is applied, e.g. 120 seconds will not become 2 minutes. Use Normalise if you need to.
Periods only allow the least-significant non-zero field to contain a fraction. If any of the more-significant fields is supplied with a fraction, an error will be returned. This can be safely ignored for non-standard behaviour.
func NewHMS ¶ added in v0.4.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 Normalise if you need to.
func NewOf ¶ added in v0.4.0
NewOf converts a time duration to a Period. The result just a number of seconds, possibly including a fraction. It is not normalised; see Normalise.
func NewYMD ¶ added in v0.12.0
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 Normalise if you need to.
This function is equivalent to NewYMWD(years, months, 0, days)
func NewYMWD ¶ added in v0.4.0
NewYMWD 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 Normalise if you need to.
func Parse ¶
Parse parses strings that specify periods using ISO-8601 rules.
In addition, a plus or minus sign can precede the period, e.g. "-P10D"
It is possible to mix a number of weeks with other fields (e.g. P2M1W), although this would not be allowed by ISO-8601. See SimplifyWeeks.
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 (Period) Add ¶ added in v0.6.0
Add adds two periods together. Use this method along with Negate in order to subtract periods. Arithmetic overflow will result in an error.
func (Period) AddTo ¶ added in v0.10.0
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.
Similarly, when the period specifies whole years, months, weeks 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, as per Gregorian calendar rules).
Note: the use of AddDate has unintended consequences when considering the addition of a time only period. See <https://x.com/joelmcourtney/status/1803301619955904979>.
func (Period) DaysDecimal ¶ added in v1.0.5
DaysDecimal gets the number of days in the period, including any fraction present.
func (Period) DaysIncWeeks ¶ added in v0.6.0
DaysIncWeeks gets the whole number of days in the period, including all the weeks. The result is d + (w * 7), given d days and w weeks.
See also SimplifyWeeksToDays.
func (Period) DaysIncWeeksDecimal ¶ added in v1.0.5
DaysIncWeeksDecimal gets the number of days in the period, including all the weeks and including any fraction present. The result is d + (w * 7), given d days and w weeks.
See also SimplifyWeeksToDays.
func (Period) Duration ¶ added in v0.6.0
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, weeks 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 (as per Gregorian calendar rules) and a month being 1/12 of a that; days are all assumed to be 24 hours long.
For periods shorter than one nanosecond, the duration will be zero and the precise flag will be returned false.
Note that time.Duration is limited to the range 1 nanosecond to about 292 years maximum.
func (Period) DurationApprox ¶ added in v0.6.0
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, weeks 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 (as per Gregorian calendar rules) and a month being 1/12 of a that; days are all assumed to be 24 hours long.
Note that time.Duration is limited to the range 1 nanosecond to about 292 years maximum.
func (Period) Format ¶ added in v0.7.0
Format converts the period to human-readable form using DefaultFormatLocalisation. To adjust the result, see the Normalise, NormaliseDaysToYears, Simplify and SimplifyWeeksToDays methods.
func (Period) FormatLocalised ¶ added in v0.7.0
func (period Period) FormatLocalised(config FormatLocalisation) string
FormatLocalised converts the period to human-readable form in a localisable way. To adjust the result, see the Normalise, NormaliseDaysToYears, Simplify and SimplifyWeeksToDays methods.
func (*Period) Get ¶ added in v0.7.0
Get enables use of Period by the flag API. It is therefore possible to use Period values in flag parameters.
func (Period) GetField ¶ added in v0.13.0
func (period Period) GetField(field Designator) decimal.Decimal
GetField gets one field.
A panic arises if the field is unknown.
func (Period) GetInt ¶ added in v0.13.0
func (period Period) GetInt(field Designator) int
GetInt gets one field as a whole number.
A panic arises if the field is unknown.
func (Period) HoursDecimal ¶ added in v1.0.5
HoursDecimal gets the number of hours in the period, including any fraction present.
func (Period) IsNegative ¶ added in v0.6.0
IsNegative returns true if the period is negative.
func (Period) IsPositive ¶ added in v0.6.0
IsPositive returns true if the period is positive or zero.
func (Period) MarshalBinary ¶ added in v0.8.0
MarshalBinary implements the encoding.BinaryMarshaler interface. This also provides support for gob encoding.
func (Period) MarshalText ¶ added in v0.8.0
MarshalText implements the encoding.TextMarshaler interface for Periods. This also provides support for JSON encoding.
func (Period) MinutesDecimal ¶ added in v1.0.5
MinutesDecimal gets the number of minutes in the period, including any fraction present.
func (Period) MonthsDecimal ¶ added in v1.0.5
MonthsDecimal gets the number of months in the period, including any fraction present.
func (Period) Mul ¶ added in v0.13.0
Mul multiplies a period by a factor. Obviously, this can both enlarge and shrink it, and change the sign if the factor is negative. The result is not normalised.
func (Period) Normalise ¶ added in v0.6.0
Normalise simplifies the fields by propagating large values towards the more significant fields.
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 to or from the days element. To give control over this, there are two modes: it operates in either precise or approximate mode.
- Multiples of 60 seconds become minutes - both modes.
- Multiples of 60 minutes become hours - both modes.
- Multiples of 24 hours become days - approximate mode only
- Multiples of 7 days become weeks - both modes.
- Multiples of 12 months become years - both modes.
Note that leap seconds are disregarded: every minute is assumed to have 60 seconds.
If the calculations would lead to arithmetic errors, the current values are kept unaltered.
See also NormaliseDaysToYears().
func (Period) NormaliseDaysToYears ¶ added in v0.6.0
NormaliseDaysToYears tries to propagate large numbers of days (and corresponding weeks) to the years field. Based on the Gregorian rule, there are assumed to be 365.2425 days per year.
- Multiples of 365.2425 days become years
If the calculations would lead to arithmetic errors, the current values are kept unaltered.
A common use pattern would be to chain this after Normalise, i.e.
p.Normalise(false).NormaliseDaysToYears()
func (Period) OnlyHMS ¶ added in v0.15.0
OnlyHMS returns the period with only the hour, minute and second fields. The year, month, week and day fields are zeroed.
func (Period) OnlyYMWD ¶ added in v0.15.0
OnlyYMWD returns the period with only the year, month, week and day fields. The hour, minute and second fields are zeroed.
func (*Period) Parse ¶ added in v0.6.0
Parse parses strings that specify periods using ISO-8601 rules.
In addition, a plus or minus sign can precede the period, e.g. "-P10D"
It is possible to mix a number of weeks with other fields (e.g. P2M1W), although this would not be allowed by ISO-8601. See SimplifyWeeks.
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 (Period) Period ¶ added in v0.6.0
Period converts the period to ISO-8601 string form. If there is a decimal fraction, it will be rendered using a decimal point separator (not a comma).
func (*Period) Scan ¶ added in v0.9.0
Scan parses some value, which can be either string or []byte. It implements sql.Scanner, https://golang.org/pkg/database/sql/#Scanner
func (Period) SecondsDecimal ¶ added in v1.0.5
SecondsDecimal gets the number of seconds in the period, including any fraction present.
func (*Period) Set ¶ added in v0.7.0
Set enables use of Period by the flag API. It is therefore possible to use Period values in flag parameters.
func (Period) SetField ¶ added in v0.13.0
SetField sets one field in the period. Like NewDecimal, an error arises if the new period would have multiple fields with fractions.
A panic arises if the field is unknown.
func (Period) SetInt ¶ added in v0.13.0
func (period Period) SetInt(value int, field Designator) Period
SetInt sets one field in the period as a whole number.
A panic arises if the field is unknown.
func (Period) Sign ¶ added in v0.6.0
Sign returns 1 if the period is positive, -1 if it is negative, or zero otherwise.
func (Period) Simplify ¶ added in v0.6.0
Simplify simplifies the fields by propagating large values towards the less significant fields. This is akin to converting mixed fractions to improper fractions, across the group of fields. However, existing fields are not altered if they are a simple way of expressing their period already.
For example, "P2Y1M" simplifies to "P25M" but "P2Y" remains "P2Y".
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 to or from the days element. To give control over this, there are two modes: it operates in either precise or approximate mode.
- Years may become multiples of 12 months if the number of months is non-zero - both modes.
- Weeks - see SimplifyWeeks - both modes.
- Days may become multiples of 24 hours if the number of hours is non-zero - approximate mode only
- Hours may become multiples of 60 minutes if the number of minutes is non-zero - both modes.
- Minutes may become multiples of 60 seconds if the number of seconds is non-zero - both modes.
If the calculations would lead to arithmetic errors, the current values are kept unaltered.
func (Period) SimplifyWeeks ¶ added in v1.0.5
SimplifyWeeks adds 7 * the weeks field to the days field, and sets the weeks field to zero, but only if some other fields are non-zero.
This will increase compatibility with external systems that do not expect to receive a weeks component unless the other components are zero. This is because ISO-8601 periods contain either weeks or other fields but not both.
See also SimplifyWeeksToDays.
func (Period) SimplifyWeeksToDays ¶ added in v0.7.0
SimplifyWeeksToDays adds 7 * the weeks field to the days field, and sets the weeks field to zero. See also SimplifyWeeks.
func (Period) String ¶
ISOString converts the period to ISO-8601 string form. If there is a decimal fraction, it will be rendered using a decimal point separator. (not a comma).
func (Period) Subtract ¶ added in v0.6.0
Subtract subtracts one period from another. Arithmetic overflow will result in an error.
func (Period) TotalDaysApprox ¶ added in v0.15.0
TotalDaysApprox gets the approximate total number of days in the period. The approximation assumes a year is 365.2425 days as per Gregorian calendar rules) and a month is 1/12 of that. Whole multiples of 24 hours are also included in the calculation.
func (Period) TotalMonthsApprox ¶ added in v0.15.0
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 (as per Gregorian calendar rules) and a month is 1/12 of that. Whole multiples of 24 hours are also included in the calculation.
func (*Period) UnmarshalBinary ¶ added in v0.8.0
UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. This also provides support for gob decoding.
func (*Period) UnmarshalText ¶ added in v0.8.0
UnmarshalText implements the encoding.TextUnmarshaler interface for Periods. This also provides support for JSON decoding.
func (Period) Value ¶ added in v0.9.0
Value converts the period to an ISO-8601 string. It implements driver.Valuer, https://golang.org/pkg/database/sql/driver/#Valuer
func (Period) WeeksDecimal ¶ added in v1.0.5
WeeksDecimal gets the number of weeks in the period, including any fraction present.
func (Period) YearsDecimal ¶ added in v1.0.5
YearsDecimal gets the number of years in the period, including any fraction present.