Documentation
¶
Overview ¶
Package tcf implements CCSDS Time Code Formats per CCSDS 301.0-B-4.
A time code is a P-field (preamble, 1 or 2 octets) followed by a T-field (the time value):
+----------------------+--------------------------------------+ | P-Field (Preamble) | T-Field (Time Code) | | 1 or 2 octets | Variable length | +----------------------+--------------------------------------+
P-field first octet:
+---+-------+-------------------+ | E | ID(3) | Format-specific(4)| +---+-------+-------------------+ E = Extension flag (0 = last octet, 1 = another octet follows) ID = Time code identification
Supported formats:
CUC - CCSDS Unsegmented Time Code (binary counter) CDS - CCSDS Day Segmented Time Code (day + ms + optional sub-ms) CCS - CCSDS Calendar Segmented Time Code (BCD calendar fields) ASCII - Text-based time codes (Type A and Type B)
Each binary format also has T-field-only ("implicit P-field") APIs ( EncodeTField and DecodeCUCTField / DecodeCDSTField / DecodeCCSTField) for contexts such as Space Packet secondary headers where the format is agreed out of band and no P-field is transmitted.
TAI, UTC, and leap seconds ¶
The CCSDS recommended (Level 1) epoch is 1958-01-01T00:00:00 on the TAI time scale. TAI is continuous; UTC inserts leap seconds, so the TAI-UTC offset has grown from 10 s (1972) to 37 s (since 2017-01-01).
CUC Level 1 codes count true TAI seconds. This package embeds the full historical table of integer TAI-UTC offsets (see TAIUTCOffsetAt) and applies it automatically:
- Encoding (NewCUC with the CCSDS epoch): the coarse count is the UTC elapsed seconds since 1958-01-01 plus the TAI-UTC offset in effect at the encoded instant.
- Decoding (CUC.Time for Level 1): the offset in effect at the decoded instant is subtracted again, yielding UTC.
Boundary behavior, by design:
- Instants before 1972-01-01 UTC use an offset of 0. Between 1958 and 1972 UTC used fractional "rubber-second" adjustments that have no integer representation; this package treats TAI and UTC as identical in that era. Consequently TAI second counts that fall inside the 10 s step at 1972-01-01 do not round-trip exactly.
- A TAI instant that falls inside an inserted leap second (UTC 23:59:60) is reported by CUC.Time as the following 00:00:00 UTC, because Go's time.Time cannot represent second 60.
CUC Level 2 (agency-defined epoch) codes are purely arithmetic: the coarse count is the elapsed seconds between the epoch and the instant with no leap-second correction in either direction.
CDS codes are day/millisecond arithmetic against their epoch in both levels; no leap-second table is applied. See the CDS type documentation for leap-second-day behavior. CCS and ASCII codes carry UTC calendar fields directly.
Index ¶
Constants ¶
const ( // ASCIITypeA is the calendar date-time format: YYYY-MM-DDThh:mm:ss.dddZ ASCIITypeA = "A" // ASCIITypeB is the ordinal date-time format: YYYY-DDDThh:mm:ss.dddZ ASCIITypeB = "B" )
ASCII time code format types per CCSDS 301.0-B-4 clause 3.5.
const ( TimeCodeCUCLevel1 uint8 = 0x01 // 001: CUC with CCSDS epoch (Level 1) TimeCodeCUCLevel2 uint8 = 0x02 // 010: CUC with agency-defined epoch (Level 2) TimeCodeCDS uint8 = 0x04 // 100: CDS (Level 1 or 2, determined by bit 4) TimeCodeCCS uint8 = 0x05 // 101: CCS (always Level 1, UTC) )
Time code identification values (P-field bits 1-3) per Table B-3.
Variables ¶
var ( // ErrDataTooShort indicates the provided data is too short for decoding. ErrDataTooShort = errors.New("provided data is too short to decode time code") // ErrInvalidPField indicates the P-field does not conform to CCSDS 301.0-B-4. ErrInvalidPField = errors.New("invalid P-field: does not conform to CCSDS 301.0-B-4") // ErrInvalidTimeCodeID indicates an unrecognized time code identification. ErrInvalidTimeCodeID = errors.New("invalid time code ID: must be a recognized CCSDS code type") // ErrInvalidCoarseOctets indicates the coarse time octet count is out of range. ErrInvalidCoarseOctets = errors.New("invalid coarse time: must be 1-4 basic octets (up to 7 with extension)") // ErrInvalidFineOctets indicates the fine time octet count is out of range. ErrInvalidFineOctets = errors.New("invalid fine time: must be 0-3 basic octets (up to 10 with extension)") // ErrInvalidDaySegment indicates the day count is negative or out of range. ErrInvalidDaySegment = errors.New("invalid day segment: day count out of range") // ErrInvalidMilliseconds indicates the milliseconds-of-day value is out of range. ErrInvalidMilliseconds = errors.New("invalid milliseconds: must be in range 0-86399999") // ErrInvalidCalendarTime indicates a calendar field is out of range. ErrInvalidCalendarTime = errors.New("invalid calendar time: field value out of range") // ErrInvalidASCIIFormat indicates the ASCII time string does not match the expected format. ErrInvalidASCIIFormat = errors.New("invalid ASCII time code: format does not match CCSDS Type A or Type B") // ErrEpochRequired indicates a custom epoch is required for Level 2 codes but was not provided. ErrEpochRequired = errors.New("agency-defined epoch required for Level 2 time code") // ErrOverflow indicates the time value exceeds the representable range. ErrOverflow = errors.New("time value exceeds representable range for the configured octet width") // ErrReservedSubmsCode indicates the CDS P-field carried the reserved // sub-millisecond code '11' (CCSDS 301.0-B-4 clause 3.3.2). ErrReservedSubmsCode = errors.New("reserved CDS sub-millisecond code '11' in P-field") // ErrInvalidSubmilliseconds indicates the CDS sub-millisecond value is out // of range for its declared resolution: 0-999 for the 16-bit microsecond // field, 0-999999999 for the 32-bit picosecond field. ErrInvalidSubmilliseconds = errors.New("invalid sub-milliseconds: value out of range for declared resolution") // ErrInvalidBCD indicates a T-field octet contained a nibble greater // than 9, which is not a valid binary-coded-decimal digit. ErrInvalidBCD = errors.New("invalid BCD digit: nibble greater than 9") )
var CCSDSEpoch = time.Date(1958, 1, 1, 0, 0, 0, 0, time.UTC)
CCSDSEpoch is the CCSDS recommended epoch: 1958-01-01T00:00:00 TAI. It is the reference for Level 1 CUC and CDS time codes.
The value is expressed here as a Go time.Time in UTC. Leap-second corrections between the TAI and UTC scales are applied by the CUC Level 1 encode/decode paths (see the package documentation); CDS treats the epoch arithmetically.
Functions ¶
func TAIUTCOffsetAt ¶ added in v0.3.0
TAIUTCOffsetAt returns the TAI-UTC offset, in whole seconds, in effect at the given instant. The instant is interpreted on the UTC time scale.
For instants before 1972-01-01T00:00:00Z the function returns 0: the pre-1972 fractional UTC adjustments are not modeled, and times in that era are treated as if TAI and UTC coincided.
The embedded table is complete through the leap second of 2017-01-01 (offset 37 s), which is still current. Append to the table when the IERS announces a new leap second.
Types ¶
type ASCIIOption ¶
ASCIIOption configures an ASCII time code.
func WithASCIIPrecision ¶
func WithASCIIPrecision(n int) ASCIIOption
WithASCIIPrecision sets the number of fractional second digits (0-9).
type ASCIITime ¶
type ASCIITime struct {
Type string // "A" or "B"
Precision int // Number of decimal digits for fractional seconds (0-9)
}
ASCIITime represents a CCSDS ASCII time code per CCSDS 301.0-B-4 clause 3.5.
Type A: YYYY-MM-DDThh:mm:ss.d...dZ (calendar date-time) Type B: YYYY-DDDThh:mm:ss.d...dZ (ordinal date-time)
These are fixed-field subsets of ISO 8601: every field has a fixed width, the separators are mandatory, the fraction (if present) is 1-9 digits, and the trailing Z is optional. Decode enforces the subset strictly.
func NewASCIITime ¶
func NewASCIITime(typ string, opts ...ASCIIOption) (*ASCIITime, error)
NewASCIITime creates an ASCIITime encoder/decoder. typ must be ASCIITypeA or ASCIITypeB. Defaults to 3 digits of fractional seconds.
func (*ASCIITime) Decode ¶
Decode parses a CCSDS ASCII time string into a Go time.Time value.
The clause 3.5 subsets are enforced strictly: fixed field widths (Type A "YYYY-MM-DDThh:mm:ss[.d...d][Z]", Type B "YYYY-DDDThh:mm:ss[.d...d][Z]"), digits only, mandatory separators, a fraction of 1-9 digits when present, and value ranges checked against the calendar (month 1-12, day valid for the month and year, day-of-year valid for the year's leap status, hour 0-23, minute 0-59).
Second 60 is accepted only at 23:59:60 (a positive leap second); because Go's time.Time cannot represent second 60, the returned value is normalized to 00:00:00 of the following day.
type CCS ¶
type CCS struct {
PField PField // Preamble field
Year uint16 // Calendar year (0-9999)
Month uint8 // Month (1-12), only for Month/Day variant
DayOfMonth uint8 // Day of month (1-31), only for Month/Day variant
DayOfYear uint16 // Day of year (1-366), only for Day-of-Year variant
Hour uint8 // Hour (0-23)
Minute uint8 // Minute (0-59)
Second uint8 // Second (0-60, 60 only for the leap second at 23:59)
SubSecond [6]uint8 // Sub-second BCD octets (each holds 2 decimal digits, 0-99)
SubSecBytes uint8 // Number of sub-second octets (0-6)
MonthDay bool // true = Month/Day variant (bit 4=0), false = Day-of-Year variant (bit 4=1)
}
CCS represents a CCSDS Calendar Segmented Time Code per CCSDS 301.0-B-4 clause 3.4.
All segments use Binary Coded Decimal (BCD) encoding where each 8-bit segment represents two decimal digits.
Day-of-Year variant (bit 4 = 1):
+----------+--------+------+------+------+------------------+ | Year(16) | DOY(16)| H(8) | M(8) | S(8) | Sub-s(0-6 oct) | +----------+--------+------+------+------+------------------+
Month/Day variant (bit 4 = 0):
+----------+------+------+------+------+------+------------------+ | Year(16) | Mo(8)| Dom(8)| H(8) | M(8) | S(8) | Sub-s(0-6 oct) | +----------+------+------+------+------+------+------------------+
Sub-second resolution: 0 to 6 additional octets, each containing 2 BCD digits, giving 10^-2 to 10^-12 second resolution.
CCS is always Level 1 UTC. The Second field may carry 60 during a positive leap second (UTC 23:59:60); see IsLeapSecond and Time for how that maps to Go's time.Time.
func DecodeCCS ¶
DecodeCCS parses a byte slice into a CCS time code (P-field + T-field). Octets containing nibbles greater than 9 are rejected with ErrInvalidBCD.
func DecodeCCSTField ¶ added in v0.3.0
DecodeCCSTField parses a T-field-only (implicit P-field) CCS time code. The format parameters (calendar variant and sub-second octet count) must be supplied by the caller, as they are in contexts where no P-field is transmitted.
func NewCCS ¶
NewCCS creates a CCS time code from a Go time.Time value. Defaults to Day-of-Year variant with no sub-second precision.
func (*CCS) Encode ¶
Encode serializes the CCS time code into bytes (P-field + T-field). All segments are BCD-encoded per clause 3.4.1.
func (*CCS) EncodeTField ¶ added in v0.3.0
EncodeTField serializes only the T-field (no P-field). Use this for implicit-P-field contexts where the format parameters are agreed out of band.
func (*CCS) IsLeapSecond ¶ added in v0.3.0
IsLeapSecond reports whether the code represents an instant inside a positive leap second (Second == 60, i.e. UTC 23:59:60). Go's time.Time cannot represent second 60, so Time normalizes such a code to 00:00:00 of the following day; callers that must preserve the distinction should check this flag before converting.
func (*CCS) Time ¶
Time converts the CCS time code to a Go time.Time value.
A leap-second code (Second == 60, see IsLeapSecond) is normalized to 00:00:00 of the following day, because Go's time.Time cannot represent UTC second 60. Sub-second digits below one nanosecond are truncated.
func (*CCS) Validate ¶
Validate checks that the CCS fields conform to CCSDS 301.0-B-4, including calendar cross-checks: the year must fit four BCD digits, the day of month must exist in the given month and year, and the day of year must respect the year's leap status. Second 60 is accepted only at 23:59 (the only instant a positive leap second can occur).
type CCSOption ¶
CCSOption configures a CCS time code.
func WithCCSMonthDay ¶
func WithCCSMonthDay() CCSOption
WithCCSMonthDay selects the Month/Day variant instead of Day-of-Year.
func WithCCSSubSecBytes ¶
WithCCSSubSecBytes sets the number of sub-second octets (0-6). Each octet provides two additional decimal digits of precision.
type CDS ¶
type CDS struct {
PField PField // Preamble field
Day uint32 // Day count since epoch (16 or 24 bits)
Milliseconds uint32 // Milliseconds of day (0-86399999)
Submilliseconds uint32 // Sub-millisecond value (interpretation depends on SubmsBytes)
DayBytes uint8 // Day segment width: 2 (16-bit) or 3 (24-bit)
SubmsBytes uint8 // Sub-millisecond width: 0, 2, or 4
Epoch time.Time // Reference epoch (CCSDSEpoch for Level 1)
}
CDS represents a CCSDS Day Segmented Time Code per CCSDS 301.0-B-4 clause 3.3.
The T-field is segmented into a day count, milliseconds of day, and optional sub-millisecond precision.
+-------------+-----------------+----------------------+ | Day (16/24) | Milliseconds(32)| Sub-ms (0/16/32 bit) | +-------------+-----------------+----------------------+
Sub-millisecond precision:
0 bytes: none 2 bytes: microseconds within the millisecond (0-999) 4 bytes: picoseconds within the millisecond (0-999999999)
Time scale and leap seconds: CDS conversions are purely arithmetic in both levels. The day count is elapsed 86400-second days since the epoch and no leap-second table is applied. The segmentation cannot represent the inserted leap second itself: milliseconds-of-day is capped at 86399999, so UTC 23:59:60 has no encoding, and on a leap-second day the code is effectively a UTC day/time-of-day label rather than a true elapsed-time count (across a real leap second the arithmetic day boundary and the UTC midnight differ by the inserted second). This matches the common convention of treating CDS day/millisecond fields as UTC; missions requiring true TAI elapsed time should use CUC Level 1 instead.
func DecodeCDS ¶
DecodeCDS parses a byte slice into a CDS time code (P-field + T-field). If epoch is zero-value, Level 1 (CCSDS epoch) is assumed.
The reserved sub-millisecond code '11' (clause 3.3.2) is rejected with ErrReservedSubmsCode: its T-field length is undefined, so decoding cannot proceed safely.
func DecodeCDSTField ¶ added in v0.3.0
DecodeCDSTField parses a T-field-only (implicit P-field) CDS time code. The format parameters (day segment width (2 or 3), sub-millisecond width (0, 2, or 4) and the epoch) must be supplied by the caller, as they are in contexts like Space Packet secondary headers where no P-field is transmitted.
If epoch is zero-value, Level 1 (CCSDS epoch) is assumed; otherwise the code is treated as Level 2 with the given agency-defined epoch.
func NewCDS ¶
NewCDS creates a CDS time code from a Go time.Time value. Defaults to Level 1 (CCSDS epoch), 16-bit day, no sub-milliseconds. The conversion is purely arithmetic; see the CDS type documentation for leap-second-day behavior.
func (*CDS) EncodeTField ¶ added in v0.3.0
EncodeTField serializes only the T-field (no P-field). Use this for implicit-P-field contexts, e.g. Space Packet secondary headers, where the format parameters are agreed out of band.
type CDSOption ¶
CDSOption configures a CDS time code.
func WithCDSDayBytes ¶
WithCDSDayBytes sets the day segment width (2 for 16-bit, 3 for 24-bit).
func WithCDSEpoch ¶
WithCDSEpoch sets a custom epoch for Level 2 CDS codes.
func WithCDSSubmsBytes ¶
WithCDSSubmsBytes sets the sub-millisecond width (0, 2, or 4 bytes).
type CUC ¶
type CUC struct {
PField PField // Preamble field
CoarseTime uint64 // Seconds since epoch (TAI seconds for Level 1)
FineTime uint64 // Most significant fine octets (up to 8)
FineTimeExt uint16 // Least significant fine octets 9-10 (FineBytes > 8 only)
CoarseBytes uint8 // Number of coarse time octets (1-4, up to 7 with extension)
FineBytes uint8 // Number of fine time octets (0-3, up to 10 with extension)
Epoch time.Time // Reference epoch (CCSDSEpoch for Level 1)
}
CUC represents a CCSDS Unsegmented Time Code per CCSDS 301.0-B-4 clause 3.2.
The T-field is a single binary counter split into coarse time (seconds since epoch) and fine time (fractional seconds as binary fractions of a second).
+------------------+------------------+ | Coarse (1-4 oct) | Fine (0-3 oct) | | up to 7 extended | up to 10 extended| +------------------+------------------+
Fine time resolution is 2^-(8*n) seconds for n fine octets:
0 octets: 1 s 1 octet: ~3.9 ms (2^-8 s) 2 octets: ~15.3 µs (2^-16 s) 3 octets: ~59.6 ns (2^-24 s) ... 10 octets: 2^-80 s
Because up to 10 fine octets (80 bits) are allowed, the fine counter is held in two fields: FineTime carries the most significant octets (up to 8), and FineTimeExt carries the remaining least significant octets (only used when FineBytes > 8).
Time scale: with the CCSDS epoch (Level 1) the coarse count is true TAI seconds since 1958-01-01T00:00:00 TAI. NewCUC and Time apply the embedded leap-second table (see TAIUTCOffsetAt and the package documentation) when converting to and from Go's UTC-based time.Time. With an agency-defined epoch (Level 2) the count is purely arithmetic elapsed seconds and no leap-second correction is applied.
Rounding: NewCUC truncates the fractional second toward zero to the configured fine-time resolution; it does not round to nearest. Time likewise truncates sub-nanosecond fine time (including all of FineTimeExt, whose resolution is below 2^-64 s) toward zero.
func DecodeCUC ¶
DecodeCUC parses a byte slice into a CUC time code (P-field + T-field). If epoch is zero-value, Level 1 (CCSDS epoch) is assumed.
func DecodeCUCTField ¶ added in v0.3.0
DecodeCUCTField parses a T-field-only (implicit P-field) CUC time code. The format parameters (coarse and fine octet counts and the epoch) must be supplied by the caller, as they are in contexts like Space Packet secondary headers where no P-field is transmitted.
If epoch is zero-value, Level 1 (CCSDS epoch) is assumed; otherwise the code is treated as Level 2 with the given agency-defined epoch.
func NewCUC ¶
NewCUC creates a CUC time code from a Go time.Time value. Defaults to Level 1 (CCSDS epoch), 4 coarse octets, 0 fine octets.
For Level 1 the TAI-UTC offset in effect at t is added so that the coarse count is true TAI seconds since the 1958 TAI epoch; see the package documentation. Fractional seconds are truncated toward zero to the configured fine-time resolution.
func (*CUC) EncodeTField ¶ added in v0.3.0
EncodeTField serializes only the T-field (no P-field). Use this for implicit-P-field contexts, e.g. Space Packet secondary headers, where the format parameters are agreed out of band.
func (*CUC) Time ¶
Time converts the CUC time code to a Go time.Time value in UTC.
For Level 1 codes the coarse count is TAI seconds since the 1958 TAI epoch; the TAI-UTC offset in effect at the decoded instant is subtracted (see the package documentation, including the treatment of pre-1972 times and of instants inside an inserted leap second). Level 2 codes are converted arithmetically.
Fine time below one nanosecond is truncated toward zero: Go's time.Time carries nanoseconds, so only the most significant fine bits contribute, and FineTimeExt (resolution below 2^-64 s) never does.
type CUCOption ¶
CUCOption configures a CUC time code.
func WithCUCCoarseBytes ¶
WithCUCCoarseBytes sets the number of coarse time octets (1-4 basic, up to 7 with extension).
func WithCUCEpoch ¶
WithCUCEpoch sets a custom epoch for Level 2 CUC codes.
func WithCUCFineBytes ¶
WithCUCFineBytes sets the number of fine time octets (0-3 basic, up to 10 with extension, per clause 3.2.2).
type PField ¶
type PField struct {
Extension bool // Extension flag: true if a second octet follows
TimeCodeID uint8 // Time code identification (3 bits)
Detail uint8 // Format-specific detail bits (4 bits from first octet)
ExtDetail uint8 // Extension detail bits (7 bits from second octet)
}
PField represents the preamble field of a CCSDS time code. The P-field identifies the time code format, precision, and epoch level.
First octet layout: +---+-------+-------------------+ | E | ID(3) | Format-specific(4)| +---+-------+-------------------+
Second octet (if extension flag is set): +---+-------------------------------+ | 0 | Extension-specific (7 bits) | +---+-------------------------------+
func (*PField) Decode ¶
Decode deserializes 1 or 2 bytes into a PField.
Bit 0 of the second octet is itself an extension flag reserved by CCSDS 301.0-B-4 for future P-field octets. No third octet is defined by the standard, so a set bit is rejected with ErrInvalidPField rather than silently misparsing the following octet as T-field data.