types

package
v2.2.7 Latest Latest
Warning

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

Go to latest
Published: May 8, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package types defines the CalcMark type system.

CalcMark supports several value types for calculations and data representation. All types implement the Type interface which provides string representation.

Core Types

  • Number: Arbitrary-precision decimal numbers
  • Currency: Monetary values with currency symbol/code
  • Quantity: Physical quantities with units (e.g., "5 kg", "10 meters")
  • Date: Calendar dates with date arithmetic
  • Time: Time of day with timezone support
  • Duration: Time durations (e.g., "5 days", "3 hours")
  • Boolean: True/false values

Number Type

Numbers use arbitrary-precision decimals via shopspring/decimal:

num := types.NewNumber(decimal.NewFromFloat(3.14159))
fmt.Println(num.String()) // "3.14159"

Supports thousands separators:

1,000
1_000_000

Currency Type

Currency values combine a decimal amount with a symbol or ISO code:

usd := types.NewCurrency(decimal.NewFromInt(100), "$")
eur := types.NewCurrencyFromCode(decimal.NewFromInt(50), "EUR")

Supported symbols: $, €, £, ¥ Supports ISO 4217 currency codes (e.g., USD, EUR, GBP, JPY)

Quantity Type

Physical quantities with units:

mass := types.NewQuantity(decimal.NewFromInt(5), "kg")
length := types.NewQuantity(decimal.NewFromInt(10), "meters")

Supports SI and US customary units:

  • Length: meters, feet, kilometers, miles, etc.
  • Mass: kilograms, pounds, grams, etc.
  • Volume: liters, gallons, cups, etc.

Date Type

Calendar dates with arithmetic operations:

today := types.NewDateFromTime(time.Now())
nextWeek := today.AddDays(7)
daysBetween := today.DaysBetween(nextWeek) // 7

Time Type

Time of day with timezone support:

morning := types.NewTime(9, 30, 0, false, 0) // 9:30 AM UTC
evening := types.NewTime(6, 0, 0, true, -300) // 6:00 PM EST

Duration Type

Time durations for date arithmetic:

week := types.NewDuration(7, types.DurationUnitDay)
hour := types.NewDuration(1, types.DurationUnitHour)

Boolean Type

Simple true/false values:

t := types.NewBoolean(true)
f := types.NewBoolean(false)

Type Conversions

The type system maintains type safety while allowing sensible conversions:

  • Currency + Number → Currency (preserves currency)
  • Currency + Currency (different) → Number (drops units)
  • Quantity + Quantity (compatible) → Quantity (first unit wins)

Performance

All type operations are designed for speed and use efficient decimal arithmetic. String formatting is optimized for display/debugging.

Index

Constants

View Source
const MaxDisplayDenominator = 1000

MaxDisplayDenominator is the threshold above which fractions fall back to decimal display.

Variables

View Source
var ArgTypeExamples = map[ArgType][]string{
	ArgTypeNumber:     {"42", "3.14", "1000", "1e6"},
	ArgTypeQuantity:   {"10 meters", "5 kg", "100 MB", "2.5 liters"},
	ArgTypeRate:       {"10 MB/s", "60 km/h", "100 requests/second", "5 GB/day"},
	ArgTypeDuration:   {"2 hours", "30 minutes", "1 day", "500 ms"},
	ArgTypePercentage: {"99.9%", "15%", "50%", "0.1%"},
	ArgTypeString:     {"ssd", "gzip", "gigabit"},
	ArgTypeAny:        {"<any value>"},
}

ArgTypeExamples provides example values for each argument type.

View Source
var CodeToSymbol = map[string]string{
	"USD": "$",
	"EUR": "€",
	"GBP": "£",
	"JPY": "¥",
}

CodeToSymbol maps ISO 4217 codes to display symbols. This is the reverse of SymbolToCode.

View Source
var DurationToSeconds = map[string]int64{
	"second":  1,
	"seconds": 1,
	"minute":  60,
	"minutes": 60,
	"hour":    3600,
	"hours":   3600,
	"day":     86400,
	"days":    86400,
	"week":    604800,
	"weeks":   604800,
	"month":   2592000,
	"months":  2592000,
	"year":    31536000,
	"years":   31536000,
}

DurationToSeconds provides conversion factors to seconds (approximate for months/years). Uses int64 for whole-second units. Milliseconds are handled separately in conversion functions.

View Source
var FunctionSpecs = map[string]FunctionSpec{
	"avg": {
		Name: "avg",
		Params: []ParamSpec{
			{Name: "values", Type: ArgTypeAny, Variadic: true, Examples: []string{"1, 2, 3", "10, 20", "$100, $200"}},
		},
	},
	"sum": {
		Name: "sum",
		Params: []ParamSpec{
			{Name: "values", Type: ArgTypeAny, Variadic: true, Examples: []string{"1, 2, 3", "$100, $200", "1 kg, 500 g"}},
		},
	},
	"sqrt": {
		Name: "sqrt",
		Params: []ParamSpec{
			{Name: "value", Type: ArgTypeNumber, Examples: []string{"16", "2", "100"}},
		},
	},
	"number": {
		Name: "number",
		Params: []ParamSpec{
			{Name: "value", Type: ArgTypeAny, Examples: []string{"10 kg", "$100", "25%", "42"}},
		},
	},
	"accumulate": {
		Name: "accumulate",
		Params: []ParamSpec{
			{Name: "rate", Type: ArgTypeRate, Examples: []string{"10 MB/s", "500 req/s", "5 GB/day"}},
			{Name: "duration", Type: ArgTypeDuration, Examples: []string{"1 hour", "30 minutes", "1 day"}},
		},
	},
	"convert_rate": {
		Name: "convert_rate",
		Params: []ParamSpec{
			{Name: "rate", Type: ArgTypeRate, Examples: []string{"10 MB/s", "1 GB/hour", "500 req/s"}},
			{Name: "time_unit", Type: ArgTypeString, Examples: identifiers.TimeUnits, EnumValues: identifiers.TimeUnits},
		},
	},
	"downtime": {
		Name: "downtime",
		Params: []ParamSpec{
			{Name: "availability", Type: ArgTypePercentage, Examples: []string{"99.9%", "99.99%", "99%"}},
			{Name: "duration", Type: ArgTypeDuration, Examples: []string{"1 year", "1 month", "1 day"}},
		},
	},
	"rtt": {
		Name: "rtt",
		Params: []ParamSpec{
			{Name: "scope", Type: ArgTypeString, Examples: identifiers.NetworkScopes, EnumValues: identifiers.NetworkScopes},
		},
	},
	"throughput": {
		Name: "throughput",
		Params: []ParamSpec{
			{Name: "network_type", Type: ArgTypeString, Examples: identifiers.NetworkTypes, EnumValues: identifiers.NetworkTypes},
		},
	},
	"transfer_time": {
		Name: "transfer_time",
		Params: []ParamSpec{
			{Name: "size", Type: ArgTypeQuantity, Examples: []string{"1 GB", "100 MB", "10 TB"}},
			{Name: "scope", Type: ArgTypeString, Examples: identifiers.NetworkScopes, EnumValues: identifiers.NetworkScopes},
			{Name: "network_type", Type: ArgTypeString, Examples: identifiers.NetworkTypes, EnumValues: identifiers.NetworkTypes},
		},
	},
	"read": {
		Name: "read",
		Params: []ParamSpec{
			{Name: "size", Type: ArgTypeQuantity, Examples: []string{"1 MB", "4 KB", "1 GB"}},
			{Name: "storage_type", Type: ArgTypeString, Examples: identifiers.StorageTypes, EnumValues: identifiers.StorageTypes},
		},
	},
	"seek": {
		Name: "seek",
		Params: []ParamSpec{
			{Name: "storage_type", Type: ArgTypeString, Examples: identifiers.StorageTypes, EnumValues: identifiers.StorageTypes},
		},
	},
	"compress": {
		Name: "compress",
		Params: []ParamSpec{
			{Name: "size", Type: ArgTypeQuantity, Examples: []string{"1 MB", "100 KB", "1 GB"}},
			{Name: "compression_type", Type: ArgTypeString, Examples: identifiers.CompressionTypes, EnumValues: identifiers.CompressionTypes},
		},
	},
	"compound": {
		Name: "compound",
		Params: []ParamSpec{
			{Name: "principal", Type: ArgTypeAny, Examples: []string{"$10000", "1000 users", "$1M"}},
			{Name: "rate", Type: ArgTypePercentage, Examples: []string{"5%", "8.5%", "12%"}},
			{Name: "periods", Type: ArgTypeNumber, Examples: []string{"30", "12", "5"}},
			{Name: "period", Type: ArgTypeString, Optional: true, Examples: []string{"monthly", "quarterly", "yearly"}},
		},
	},
	"grow": {
		Name: "grow",
		Params: []ParamSpec{
			{Name: "amount", Type: ArgTypeAmount, Examples: []string{"$1000", "50 GB", "100 users"}},
			{Name: "increment", Type: ArgTypeAmount, Examples: []string{"$200", "10 GB", "50 users"}},
			{Name: "periods", Type: ArgTypeNumber, Examples: []string{"12", "6", "24"}},
		},
	},
	"depreciate": {
		Name: "depreciate",
		Params: []ParamSpec{
			{Name: "value", Type: ArgTypeAny, Examples: []string{"$50000", "$10000", "1000 units"}},
			{Name: "rate", Type: ArgTypePercentage, Examples: []string{"20%", "15%", "10%"}},
			{Name: "periods", Type: ArgTypeNumber, Examples: []string{"5", "10", "7"}},
			{Name: "salvage", Type: ArgTypeAny, Optional: true, Examples: []string{"$5000", "$1000"}},
		},
	},
	"capacity": {
		Name: "capacity",
		Params: []ParamSpec{
			{Name: "demand", Type: ArgTypeRate, Examples: []string{"1000 requests/second", "10 GB/day"}},
			{Name: "capacity_per_unit", Type: ArgTypeRate, Examples: []string{"100 requests/second", "1 GB/day"}},
			{Name: "unit", Type: ArgTypeString, Examples: []string{"server", "instance", "pod"}},
			{Name: "buffer", Type: ArgTypePercentage, Optional: true, Examples: []string{"20%", "50%"}},
		},
	},
}

FunctionSpecs defines the type signatures for all built-in functions. This is the authoritative source for parameter type information.

View Source
var SymbolToCode = map[string]string{
	"$": "USD",
	"€": "EUR",
	"£": "GBP",
	"¥": "JPY",
}

SymbolToCode maps currency symbols to their ISO 4217 codes. This allows normalization of common symbols to standard codes.

Functions

func GetCurrencySymbol

func GetCurrencySymbol(code string) string

GetCurrencySymbol returns the display symbol for a currency code. Returns the code itself if no symbol is defined.

func GetExamplesForType

func GetExamplesForType(argType ArgType) []string

GetExamplesForType returns example values for a given argument type.

func IsCurrencyCode

func IsCurrencyCode(s string) bool

IsCurrencyCode checks if a string is a known currency code or symbol.

func IsTimeUnit

func IsTimeUnit(unit string) bool

IsTimeUnit returns true if the given unit is a valid time unit.

func IsValidDurationUnit

func IsValidDurationUnit(unit string) bool

IsValidDurationUnit checks if the unit is a valid duration unit.

func NormalizeCurrencyCode

func NormalizeCurrencyCode(s string) string

NormalizeCurrencyCode converts a symbol or code to its ISO code. Returns the original string if not recognized.

func NormalizeTimeUnit

func NormalizeTimeUnit(unit string) string

NormalizeTimeUnit converts various time unit formats to canonical form. Handles abbreviations, plurals, and adjectival forms (e.g., "monthly" → "month"). Examples: "s" → "second", "seconds" → "second", "sec" → "second", "daily" → "day"

func PeriodToPeriodsPerYear

func PeriodToPeriodsPerYear(period string) (int, bool)

PeriodToPeriodsPerYear returns how many of the given period fit in one year. Used by growth functions to convert between annual rates and per-period rates. Returns 0 and false for unrecognized periods.

func TimeUnitToSeconds

func TimeUnitToSeconds(unit string) (decimal.Decimal, error)

TimeUnitToSeconds returns the number of seconds in a time unit. Used for accumulation and conversion calculations. Uses the shared DurationToSeconds map from duration.go

func ToDecimal

func ToDecimal(t Type) (decimal.Decimal, error)

ToDecimal extracts a decimal.Decimal value from various numeric types. Supports Number, Currency (returns the value), and Quantity (returns the value). Returns an error for non-numeric types.

Types

type ArgType

type ArgType string

ArgType represents the semantic type expected for a function argument. This defines what kinds of values are valid for each parameter position.

const (
	ArgTypeNumber     ArgType = "number"     // Plain number: 42, 3.14, 1e6
	ArgTypeQuantity   ArgType = "quantity"   // Number with unit: 10 meters, 5 kg, 100 MB
	ArgTypeRate       ArgType = "rate"       // Quantity per time: 10 MB/s, 60 km/h
	ArgTypeDuration   ArgType = "duration"   // Time span: 2 hours, 30 minutes
	ArgTypePercentage ArgType = "percentage" // Percentage: 99.9%, 15%
	ArgTypeString     ArgType = "string"     // String literal: "datacenter", "ssd"
	ArgTypeAny        ArgType = "any"        // Any value type
	// ArgTypeAmount accepts any value that can be added to itself —
	// Number, Quantity, or Currency. Excludes Percentage, Duration,
	// and Rate, which produce nonsensical results when combined with
	// `amount + (increment × periods)` style expressions. Used by
	// `grow`'s `amount` and `increment` params to keep the autosuggest
	// dropdown free of percentage variables.
	ArgTypeAmount ArgType = "amount"
)

type Boolean

type Boolean struct {
	Value bool
}

Boolean represents a boolean value (true/false). CalcMark supports multiple boolean representations: true/false, yes/no, t/f, y/n.

func NewBoolean

func NewBoolean(value bool) *Boolean

NewBoolean creates a new Boolean with the given value.

func (*Boolean) String

func (b *Boolean) String() string

String returns "true" or "false".

type Currency

type Currency struct {
	Value    decimal.Decimal
	Symbol   string // Display symbol: "$", "€", "£", "¥", or ISO code like "USD"
	Code     string // Normalized ISO 4217 code: "USD", "EUR", "GBP", "JPY"
	IsNapkin bool   // true when produced by `as napkin` conversion
}

Currency represents a monetary value with a currency symbol and ISO code. It preserves both the display symbol (e.g., "$") and the normalized ISO code (e.g., "USD").

func NewCurrency

func NewCurrency(value decimal.Decimal, symbolOrCode string) *Currency

NewCurrency creates a new Currency with the given value and symbol/code. If symbolOrCode is a known symbol ($, €, £, ¥), it's mapped to the ISO code. Otherwise, it's used as both the symbol and code.

func NewCurrencyFromString

func NewCurrencyFromString(s string, symbolOrCode string) (*Currency, error)

NewCurrencyFromString creates a Currency from a string value and symbol/code. Returns an error if the string cannot be parsed as a valid number.

func (*Currency) IsSameCurrency

func (c *Currency) IsSameCurrency(other *Currency) bool

IsSameCurrency checks if two currencies have the same ISO code. This is used for validating currency arithmetic operations.

func (*Currency) String

func (c *Currency) String() string

String returns the precise string representation with symbol and value. This is the machine-readable form used by the JSON formatter. Unlike FormatCurrency() in format/display, String() does NOT add a space between ISO codes and amounts (e.g., "CNY1000.00" not "CNY 1,000.00").

type Date

type Date struct {
	Time    time.Time
	HasTime bool
}

Date represents a calendar date, optionally with time-of-day precision. Uses Go's time.Time internally for proper date handling (leap years, etc.). When HasTime is false, the time component is midnight and should not be displayed. When HasTime is true, the time was explicitly set and should be displayed.

func NewDate

func NewDate(year, month, day int) (*Date, error)

NewDate creates a Date from year, month, and day. If year is 0, uses the current year.

func NewDateFromTime

func NewDateFromTime(t time.Time) *Date

NewDateFromTime creates a date-only Date from a time.Time value. The time component is normalized to midnight UTC and HasTime is false.

func NewDateTime

func NewDateTime(t time.Time) *Date

NewDateTime creates a Date with full time precision from a time.Time value. The time component is preserved and HasTime is true.

func (*Date) AddDays

func (d *Date) AddDays(days int) *Date

AddDays adds the given number of days to the date.

func (*Date) DaysBetween

func (d *Date) DaysBetween(other *Date) int

DaysBetween returns the number of days from other to this date (d - other).

func (*Date) Format

func (d *Date) Format(layout string) string

Format formats the date using a Go time layout string

func (*Date) ShortString

func (d *Date) ShortString() string

ShortString returns a shorter date format

func (*Date) String

func (d *Date) String() string

String returns a human-readable date representation with day of week. USER REQUIREMENT: Show actual computed date, not just "today" Example: "Friday, November 22, 2024" not "today"

type Duration

type Duration struct {
	Value decimal.Decimal
	Unit  string // "days", "hours", "minutes", "seconds", "weeks", "months", "years"
}

Duration represents a time duration with a specific unit. Supports: days, hours, minutes, seconds, weeks, months, years.

func NewDuration

func NewDuration(value decimal.Decimal, unit string) (*Duration, error)

NewDuration creates a new Duration with the given value and unit.

func NewDurationFromString

func NewDurationFromString(s string, unit string) (*Duration, error)

NewDurationFromString creates a Duration from string value and unit.

func (*Duration) Convert

func (d *Duration) Convert(targetUnit string) (*Duration, error)

Convert converts the duration to a different unit.

func (*Duration) String

func (d *Duration) String() string

String returns the string representation.

func (*Duration) ToSeconds

func (d *Duration) ToSeconds() decimal.Decimal

ToSeconds converts the duration to seconds. Note: Uses approximate conversions for months (30 days) and years (365 days).

type FYLabelMode

type FYLabelMode int

FYLabelMode controls which calendar year the fiscal-year label corresponds to. Documents declare it via the `calendar_year_offset` frontmatter key.

  • FYLabelByEndYear (default; `calendar_year_offset: before`) — the FY label = the calendar year the FY ENDS in. The bulk of the FY's days fall in the calendar year before the label. Used by the Australian government year, the US tax year for non- calendar fiscal periods, and most publicly traded companies.
  • FYLabelByStartYear (`calendar_year_offset: after`) — the FY label = the calendar year the FY STARTS in. The bulk of the FY's days fall in the calendar year after the label. Used by some companies that align their FY label to its starting CY.
const (
	// FYLabelByEndYear labels each FY by the calendar year it ends in.
	// This is the default when `calendar_year_offset` is omitted.
	FYLabelByEndYear FYLabelMode = iota
	// FYLabelByStartYear labels each FY by the calendar year it starts in.
	FYLabelByStartYear
)

type Fraction

type Fraction struct {
	Value    *big.Rat
	IsNapkin bool   // true if this value was rounded by "as napkin"
	Unit     string // empty for dimensionless fractions
}

Fraction represents an exact rational number using Go's math/big.Rat. Fractions are always stored in lowest terms (big.Rat normalizes automatically).

func NewFraction

func NewFraction(num, denom int64) (*Fraction, error)

NewFraction creates a new Fraction from numerator and denominator. Returns an error if denominator is zero. The fraction is automatically reduced to lowest terms by big.Rat.

func NewFractionFromRat

func NewFractionFromRat(rat *big.Rat) *Fraction

NewFractionFromRat creates a Fraction from an existing big.Rat.

func (*Fraction) Denom

func (f *Fraction) Denom() *big.Int

Denom returns the denominator of the fraction (after GCD reduction).

func (*Fraction) ExceedsComputationLimit

func (f *Fraction) ExceedsComputationLimit() bool

ExceedsComputationLimit returns true if the denominator exceeds 10^9 after GCD reduction, or if |numerator| exceeds 10^18 (int64 safety).

func (*Fraction) IsProper

func (f *Fraction) IsProper() bool

IsProper returns true if |numerator| < denominator.

func (*Fraction) Num

func (f *Fraction) Num() *big.Int

Num returns the numerator of the fraction (after GCD reduction).

func (*Fraction) String

func (f *Fraction) String() string

String returns the display representation of the fraction. Rules:

  1. denominator == 1 → integer
  2. denominator > 1000 → decimal fallback
  3. |numerator| > denominator → mixed number (e.g., "2 1/3")
  4. else → simple fraction (e.g., "2/3")
  5. IsNapkin → prefix "~"
  6. Unit → append " cup" etc.

func (*Fraction) ToDecimal

func (f *Fraction) ToDecimal() decimal.Decimal

ToDecimal converts the fraction to a shopspring/decimal.Decimal. Uses NewFromBigRat for exact conversion — never goes through float64.

type FunctionSpec

type FunctionSpec struct {
	Name   string      // Function name
	Params []ParamSpec // Parameter specifications
}

FunctionSpec describes a function's type signature for semantic analysis.

func GetFunctionSpec

func GetFunctionSpec(name string) *FunctionSpec

GetFunctionSpec returns the type specification for a function by name. Returns nil if the function is not found.

func (*FunctionSpec) GetParamAtIndex

func (f *FunctionSpec) GetParamAtIndex(index int) *ParamSpec

GetParamAtIndex returns the parameter spec for the given argument position. Handles variadic parameters (last param applies to all subsequent args).

type Number

type Number struct {
	Value    decimal.Decimal
	IsNapkin bool // true when produced by `as napkin` conversion
}

Number represents an arbitrary-precision decimal number. It uses the shopspring/decimal package for accurate decimal arithmetic.

func NewNumber

func NewNumber(value decimal.Decimal) *Number

NewNumber creates a new Number from a decimal.Decimal value.

func NewNumberFromString

func NewNumberFromString(s string) (*Number, error)

NewNumberFromString creates a Number from a string representation. Returns an error if the string cannot be parsed as a valid number.

func (*Number) String

func (n *Number) String() string

String returns the string representation of the number.

type ParamSpec

type ParamSpec struct {
	Name       string   // Parameter name (e.g., "rate", "duration")
	Type       ArgType  // Expected argument type
	Optional   bool     // True if parameter has a default value
	Variadic   bool     // True if parameter accepts multiple values
	Examples   []string // Example values showing how to create this type
	EnumValues []string // Canonical unquoted enum values when the parameter accepts a finite identifier set (nil for free-form params)
}

ParamSpec describes a function parameter with type constraints and examples.

type Percentage

type Percentage struct {
	Value decimal.Decimal
}

Percentage represents a percentage value. The Value field stores the fractional form (e.g., 0.32 for 32%). Percentage preserves its identity through variables and applies widening on + and - operators: value + pct = value * (1 + pct).

func NewPercentage

func NewPercentage(value decimal.Decimal) *Percentage

NewPercentage creates a Percentage from a fractional decimal value. For 32%, pass 0.32.

func (*Percentage) String

func (p *Percentage) String() string

String returns the percentage in human-readable form (e.g., "32%").

type Period

type Period struct {
	// Start is the first day of this period. Always populated.
	Start *Date

	// End is the last day of this period (closed interval, day
	// precision). Always populated by every factory at construction
	// — code consuming Period must not need to re-derive End from
	// Kind+context. PeriodCustom kinds carry End directly from the
	// caller (`between A and B` / `from A to B`); named kinds
	// (calendar/fiscal quarters, years, months, relatives) compute
	// End once at factory time.
	End *Date

	// Kind is the period's discriminant. Switch on this in any code
	// that needs kind-specific handling (formatting, JSON shape).
	Kind PeriodKind

	// QuarterIndex is 1-4 for PeriodCalendarQuarter and
	// PeriodFiscalQuarter; 0 for other kinds.
	QuarterIndex int

	// Year is the calendar/fiscal year label:
	//   - PeriodCalendarQuarter / PeriodCalendarYear / PeriodCalendarMonth: calendar year
	//   - PeriodFiscalQuarter / PeriodFiscalYear: fiscal year label (per FYLabelMode; default = year FY ends in)
	//   - Relative kinds: 0 (Direction is the relevant signal)
	//   - PeriodCustom: 0
	Year int

	// Month is the calendar month (1-12) for PeriodCalendarMonth and
	// PeriodNamedMonth; 0 for other kinds.
	Month int

	// Direction is -1 / 0 / +1 for last / this / next on the relative
	// kinds (PeriodRelativeWeek / Month / Year / Quarter /
	// FiscalQuarter / FiscalYear). 0 for non-relative kinds.
	Direction int
}

Period represents a span of time -- a thing whose start and end dates can be computed. The interpreter dispatches `end of` / `start of` operators by Period.Kind: each kind knows enough about its context (quarter index, year, fiscal-vs-calendar tag, direction) to compute its end without re-parsing source text.

Kinds in this enum cover every period-bearing keyword the lexer accepts today: calendar quarters/years/months, fiscal quarters/years, and the relative `this/next/last <period>` forms. Adding a new kind requires (1) a new const here, (2) a factory function, (3) an `evalEndOf` arm in impl/interpreter, (4) tests across all three.

Why a flat struct with conditional context fields rather than a type hierarchy: mirrors the existing `spec/types/Duration` flat shape (Value/Unit). Calcmark's value-type idiom is "one struct, kind tag, fields populated per kind." Keeps the upstream `types.Type` interface minimal (just String()) and avoids scattering Period's eval logic across many type files.

Dependency boundary: this package is `spec/types` -- it must not import `impl/`. Period carries DATA, not behavior. End-of / start-of arithmetic lives in impl/interpreter.

func NewCalendarMonth

func NewCalendarMonth(year int, month time.Month) *Period

NewCalendarMonth creates a Period for the 1st through last day of the given month + year. End handles leap years correctly via time.Date normalization (Feb 29 in leap years, Feb 28 otherwise).

func NewCalendarQuarter

func NewCalendarQuarter(year, quarter int) (*Period, error)

NewCalendarQuarter creates a Period for Q1-Q4 in the given year. Q1=Jan-Mar, Q2=Apr-Jun, Q3=Jul-Sep, Q4=Oct-Dec. Returns an error for quarters outside [1, 4].

func NewCalendarYear

func NewCalendarYear(year int) *Period

NewCalendarYear creates a Period for Jan 1 - Dec 31 of the given year.

func NewCustomPeriod

func NewCustomPeriod(start, end *Date) (*Period, error)

NewCustomPeriod creates a user-defined period spanning [start, end] inclusive (closed interval, day precision). Backs the `between A and B` / `from A to B` language forms.

Validates: both endpoints non-nil; end >= start (single-day periods where start == end are valid).

func NewFiscalQuarter

func NewFiscalQuarter(fyStart time.Time, quarter int) (*Period, error)

NewFiscalQuarter creates a Period for the Nth fiscal quarter starting from `fyStart` (the first day of FQ1) under the default FY labeling (FYLabelByEndYear). See NewFiscalQuarterWithMode for documents that declare `calendar_year_offset: after`.

func NewFiscalQuarterWithMode

func NewFiscalQuarterWithMode(fyStart time.Time, quarter int, mode FYLabelMode) (*Period, error)

NewFiscalQuarterWithMode creates a Period for the Nth fiscal quarter starting from `fyStart` (the first day of FQ1). FQ1 == fyStart; FQ2 = fyStart + 3 months; ... FQ4 = fyStart + 9 months. fyStart's day-of-month is preserved across all four quarters so a July-15 fiscal start yields FQ2 = Oct 15, FQ3 = Jan 15, etc. Returns an error for quarters outside [1, 4].

`mode` controls how Period.Year is labeled. Under FYLabelByEndYear (the default), Jul-start FY with fyStart=Jul 1 2025 → FY ends Jun 30 2026 → label 2026, so FQ1 reads "Fiscal Q1 2026". Under FYLabelByStartYear, the same FQ1 reads "Fiscal Q1 2025".

func NewFiscalYear

func NewFiscalYear(fyLabel int, fyStartMonth time.Month, fyStartDay int) *Period

NewFiscalYear creates a Period for the fiscal year identified by fyLabel under the default labeling (FYLabelByEndYear). FY2027 with July start = Jul 1 2026 → Jun 30 2027. See NewFiscalYearWithMode for documents that declare `calendar_year_offset: after`.

func NewFiscalYearWithMode

func NewFiscalYearWithMode(fyLabel int, fyStartMonth time.Month, fyStartDay int, mode FYLabelMode) *Period

NewFiscalYearWithMode creates a Period for the fiscal year identified by fyLabel. `mode` controls how the label maps to a calendar-year start:

  • FYLabelByEndYear: start is fyStartMonth/fyStartDay of (fyLabel-1) when fyStartMonth > 1, else fyLabel itself. FY2027 with July start = Jul 1 2026 → Jun 30 2027.
  • FYLabelByStartYear: start is fyStartMonth/fyStartDay of fyLabel itself. FY2026 with Feb 2 start = Feb 2 2026 → Feb 1 2027.

In both modes Period.Year stores the label as-given.

func NewRelativeFiscalQuarter

func NewRelativeFiscalQuarter(now time.Time, fyStartMonth time.Month, fyStartDay int, direction int) *Period

NewRelativeFiscalQuarter creates a Period for `this`/`next`/`last fiscal quarter` relative to `now`. Direction: -1 / 0 / +1. fyStartMonth + fyStartDay are the configured fiscal-year start.

func NewRelativeQuarter

func NewRelativeQuarter(now time.Time, direction int) *Period

NewRelativeQuarter creates a Period for `this`/`next`/`last quarter` relative to `now`. Direction: -1 for last, 0 for this, +1 for next. Start is the first day of the calendar quarter containing the (offset-adjusted) now.

func (*Period) String

func (p *Period) String() string

String returns a human-readable description. Used in interpreter diagnostics ("end of <period>") and debug output.

type PeriodKind

type PeriodKind int

PeriodKind discriminates Period values. Used by the interpreter's `evalEndOf` switch and by `spec/semantic` to recognize period-bearing AST shapes.

const (
	PeriodCalendarQuarter PeriodKind = iota
	PeriodFiscalQuarter
	PeriodCalendarMonth
	PeriodCalendarYear
	PeriodFiscalYear
	PeriodNamedMonth
	PeriodRelativeWeek
	PeriodRelativeMonth
	PeriodRelativeYear
	PeriodRelativeQuarter
	PeriodRelativeFiscalQuarter
	PeriodRelativeFiscalYear
	// PeriodCustom — user-defined period via `between A and B` /
	// `from A to B`. Start and End come from the caller; no Year /
	// Month / QuarterIndex / Direction context.
	PeriodCustom
)

type Quantity

type Quantity struct {
	Value      decimal.Decimal
	Unit       string
	IsNapkin   bool // True if this is a napkin estimate (approximate value)
	IsExplicit bool // True if unit was explicitly chosen via `in`/`as` conversion
	IsPrecise  bool // True if full precision requested via `as precise`
}

Quantity represents a value with a physical unit. This is a stub for future implementation with full unit support. For now, it simply stores the value and unit string.

func NewQuantity

func NewQuantity(value decimal.Decimal, unit string) *Quantity

NewQuantity creates a new Quantity with the given value and unit.

func NewQuantityFromString

func NewQuantityFromString(s string, unit string) (*Quantity, error)

NewQuantityFromString creates a Quantity from a string value and unit.

func (*Quantity) String

func (q *Quantity) String() string

String returns the string representation.

type Rate

type Rate struct {
	// Amount is the numerator (quantity per time unit)
	// Can be any Quantity type (GB, requests, dollars, etc.)
	Amount *Quantity

	// PerUnit is the time unit denominator
	// Valid units: "second", "minute", "hour", "day", "week", "month", "year"
	PerUnit string
}

Rate represents a rate type: quantity per time period. Examples: 100 MB/s, $0.10/hour, 5 GB/day, 60 meters per second

Rate syntax:

  • Slash (no spaces): "100 MB/s", "5 GB/day"
  • Word "per" (with spaces): "100 MB per second", "5 GB per day"

Rates are first-class types that enable:

  • Accumulation: rate * time = quantity
  • Conversion: rate in different time units
  • Arithmetic: adding/subtracting compatible rates

func NewRate

func NewRate(amount *Quantity, perUnit string) *Rate

NewRate creates a new Rate from a quantity and time unit.

func (*Rate) Add

func (r *Rate) Add(other *Rate) (*Rate, error)

Add adds two compatible rates.

func (*Rate) CompoundUnit

func (r *Rate) CompoundUnit() string

CompoundUnit returns the compound unit string for this rate (e.g., "MB/s", "USD/h"). The numerator uses the amount's unit and the denominator uses the abbreviated time unit.

func (*Rate) IsCompatible

func (r *Rate) IsCompatible(other *Rate) bool

IsCompatible checks if two rates can be added/subtracted. Rates are compatible if their amounts have compatible units and same time periods.

func (*Rate) Multiply

func (r *Rate) Multiply(scalar decimal.Decimal) *Rate

Multiply multiplies a rate by a scalar.

func (*Rate) String

func (r *Rate) String() string

String returns the string representation of the rate. Examples: "100 MB/s", "$0.10/hour", "5 GB/day"

func (*Rate) Subtract

func (r *Rate) Subtract(other *Rate) (*Rate, error)

Subtract subtracts two compatible rates.

type Time

type Time struct {
	Time time.Time
}

Time represents a time of day with optional timezone offset. Uses Go's time.Time internally.

func NewTime

func NewTime(hour, minute, second int, isPM bool, utcOffsetMinutes int) (*Time, error)

NewTime creates a Time from hour, minute, second (optional), and UTC offset (optional). hour: 0-23 for 24-hour format, or 1-12 for 12-hour with isPM minute: 0-59 second: 0-59 (use -1 if not specified) isPM: true for PM in 12-hour format, false for AM or 24-hour format utcOffsetMinutes: offset from UTC in minutes (0 for UTC/local time)

func (*Time) String

func (t *Time) String() string

String returns a human-readable time representation.

type Type

type Type interface {
	// String returns a human-readable representation of the value.
	// This is used for display purposes and debugging.
	String() string
}

Type is the interface that all CalcMark value types implement. All types can be converted to a human-readable string representation.

type TypeError

type TypeError struct {
	Message string
}

TypeError represents a type error during evaluation.

func (*TypeError) Error

func (e *TypeError) Error() string

Jump to

Keyboard shortcuts

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