period

package module
v1.0.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 10, 2024 License: BSD-3-Clause Imports: 9 Imported by: 3

README

period

GoDoc Go Report Card Issues

Package period has types that represent ISO-8601 periods of time.

The two core types are

  • ISOString - an ISO-8601 string
  • Period - a struct with the seven numbers years, months, weeks, days, hours, minutes and seconds.

These two can be converted to the other.

Period also allows various calculations to be made. Its fields each hold up to 19 digits precision.

Status

The basic API exists but may yet change.

Upgrading

The old version of this was github.com/rickb777/date/period, which had very limited number range and used fixed-point arithmetic.

The new version, here, depends instead on github.com/govalues/decimal, which gives a huge (but finite) number range. There is now a 'weeks' field, which the old version did not have (it followed time.Time API patterns, which don't have weeks).

These functions have changed:

  • New now needs one more input parameter for the weeks field (7 parameters in total)
  • NewYMD still exists; there is also NewYMWD, which will often be more appropriate.

These methods have changed:

  • YearsDecimal, MonthsDecimal, WeeksDecimal, DaysDecimal, HoursDecimal, MinutesDecimal and SecondsDecimal return the fields as decimal.Decimal. They replace the old YearsFloat, MonthsFloat, DaysFloat, HoursFloat, MinutesFloat and SecondsFloat methods. Years, Months, Weeks, Days, Hours, Minutes and Seconds still return int as before.
  • DaysIncWeeks and DaysIncWeeksDecimal were added to return d + w * 7, which provides the behaviour similar to the old Days and DaysFloat methods.
  • The old ModuloDays was dropped now that weeks are implemented fully.
  • OnlyYMD is now OnlyYMWD
  • Scale and ScaleWithOverflowCheck have been replaced with Mul, which returns the multiplication product and a possible error.

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

Constants

This section is empty.

Variables

View Source
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.

View Source
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.

func (ISOString) String added in v0.6.0

func (p ISOString) String() string

ISOString returns the string.

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

func Between(t1, t2 time.Time) Period

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

func MustNewDecimal(years, months, weeks, days, hours, minutes, seconds decimal.Decimal) Period

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

func MustParse[S ISOString | string](isoPeriod S) Period

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

func New(years, months, weeks, days, hours, minutes, seconds int) Period

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

func NewHMS(hours, minutes, seconds int) Period

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

func NewOf(duration time.Duration) Period

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

func NewYMD(years, months, days int) Period

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

func NewYMWD(years, months, weeks, days int) Period

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

func Parse[S ISOString | string](isoPeriod S) (Period, error)

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) Abs added in v0.6.0

func (period Period) Abs() Period

Abs converts a negative period to a positive period.

func (Period) Add added in v0.6.0

func (period Period) Add(other Period) (Period, error)

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

func (period Period) AddTo(t time.Time) (time.Time, bool)

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) Days added in v0.6.0

func (period Period) Days() int

Days gets the whole number of days in the period.

func (Period) DaysDecimal added in v1.0.5

func (period Period) DaysDecimal() decimal.Decimal

DaysDecimal gets the number of days in the period, including any fraction present.

func (Period) DaysIncWeeks added in v0.6.0

func (period Period) DaysIncWeeks() int

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

func (period Period) DaysIncWeeksDecimal() decimal.Decimal

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

func (period Period) Duration() (time.Duration, bool)

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

func (period Period) DurationApprox() time.Duration

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

func (period Period) Format() string

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

func (period *Period) Get() any

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) Hours added in v0.6.0

func (period Period) Hours() int

Hours gets the whole number of hours in the period.

func (Period) HoursDecimal added in v1.0.5

func (period Period) HoursDecimal() decimal.Decimal

HoursDecimal gets the number of hours in the period, including any fraction present.

func (Period) IsNegative added in v0.6.0

func (period Period) IsNegative() bool

IsNegative returns true if the period is negative.

func (Period) IsPositive added in v0.6.0

func (period Period) IsPositive() bool

IsPositive returns true if the period is positive or zero.

func (Period) IsZero added in v0.6.0

func (period Period) IsZero() bool

IsZero returns true if applied to a period of zero length.

func (Period) MarshalBinary added in v0.8.0

func (period Period) MarshalBinary() ([]byte, error)

MarshalBinary implements the encoding.BinaryMarshaler interface. This also provides support for gob encoding.

func (Period) MarshalText added in v0.8.0

func (period Period) MarshalText() ([]byte, error)

MarshalText implements the encoding.TextMarshaler interface for Periods. This also provides support for JSON encoding.

func (Period) Minutes added in v0.6.0

func (period Period) Minutes() int

Minutes gets the whole number of minutes in the period.

func (Period) MinutesDecimal added in v1.0.5

func (period Period) MinutesDecimal() decimal.Decimal

MinutesDecimal gets the number of minutes in the period, including any fraction present.

func (Period) Months added in v0.6.0

func (period Period) Months() int

Months gets the whole number of months in the period.

func (Period) MonthsDecimal added in v1.0.5

func (period Period) MonthsDecimal() decimal.Decimal

MonthsDecimal gets the number of months in the period, including any fraction present.

func (Period) Mul added in v0.13.0

func (period Period) Mul(factor decimal.Decimal) (Period, error)

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) Negate added in v0.6.0

func (period Period) Negate() Period

Negate changes the sign of the period. Zero is not altered.

func (Period) Normalise added in v0.6.0

func (period Period) Normalise(precise bool) Period

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

func (period Period) NormaliseDaysToYears() Period

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

func (period Period) OnlyHMS() Period

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

func (period Period) OnlyYMWD() Period

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

func (period *Period) Parse(isoPeriod string) error

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

func (period Period) Period() ISOString

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

func (period *Period) Scan(value interface{}) (err error)

Scan parses some value, which can be either string or []byte. It implements sql.Scanner, https://golang.org/pkg/database/sql/#Scanner

func (Period) Seconds added in v0.6.0

func (period Period) Seconds() int

Seconds gets the whole number of seconds in the period.

func (Period) SecondsDecimal added in v1.0.5

func (period Period) SecondsDecimal() decimal.Decimal

SecondsDecimal gets the number of seconds in the period, including any fraction present.

func (*Period) Set added in v0.7.0

func (period *Period) Set(p string) error

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

func (period Period) SetField(value decimal.Decimal, field Designator) (Period, error)

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

func (period Period) Sign() int

Sign returns 1 if the period is positive, -1 if it is negative, or zero otherwise.

func (Period) Simplify added in v0.6.0

func (period Period) Simplify(precise bool) Period

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

func (period Period) SimplifyWeeks() Period

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

func (period Period) SimplifyWeeksToDays() Period

SimplifyWeeksToDays adds 7 * the weeks field to the days field, and sets the weeks field to zero. See also SimplifyWeeks.

func (Period) String

func (period Period) String() 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

func (period Period) Subtract(other Period) (Period, error)

Subtract subtracts one period from another. Arithmetic overflow will result in an error.

func (Period) TotalDaysApprox added in v0.15.0

func (period Period) TotalDaysApprox() int

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

func (period Period) TotalMonthsApprox() int

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) Type added in v0.7.0

func (period Period) Type() string

Type is for compatibility with the spf13/pflag library.

func (*Period) UnmarshalBinary added in v0.8.0

func (period *Period) UnmarshalBinary(data []byte) error

UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. This also provides support for gob decoding.

func (*Period) UnmarshalText added in v0.8.0

func (period *Period) UnmarshalText(data []byte) error

UnmarshalText implements the encoding.TextUnmarshaler interface for Periods. This also provides support for JSON decoding.

func (Period) Value added in v0.9.0

func (period Period) Value() (driver.Value, error)

Value converts the period to an ISO-8601 string. It implements driver.Valuer, https://golang.org/pkg/database/sql/driver/#Valuer

func (Period) Weeks added in v0.6.0

func (period Period) Weeks() int

Weeks gets the whole number of weeks in the period.

func (Period) WeeksDecimal added in v1.0.5

func (period Period) WeeksDecimal() decimal.Decimal

WeeksDecimal gets the number of weeks in the period, including any fraction present.

func (Period) WriteTo added in v0.6.0

func (period Period) WriteTo(w io.Writer) (int64, error)

WriteTo converts the period to ISO-8601 form.

func (Period) Years added in v0.6.0

func (period Period) Years() int

Years gets the whole number of years in the period.

func (Period) YearsDecimal added in v1.0.5

func (period Period) YearsDecimal() decimal.Decimal

YearsDecimal gets the number of years in the period, including any fraction present.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL