BACnet.Protocol.BACnetDate (bacstack v0.1.0-dev.1)

Copy Markdown View Source

A BACnet Date is a structured value that can represent either a specific calendar date or a date pattern containing wildcards and special values. It is one of the fundamental data types in BACnet (application tag 10) and is used pervasively for schedules, calendars, trend-log time ranges, and event timestamps.

Each of the four components (year, month, day, weekday) may be a concrete value or one of the special atoms :unspecified, :even, or :odd (plus :last for day). When any component is a special value the date acts as a pattern that can match many actual dates. A completely unspecified date (all fields :unspecified) is interpreted as "don't care / match anything".

BACnet Specification References

  • Encoding: ASHRAE 135-2012 Clause 20.2.12. Four contents octets: year-1900, month (January=1), day-of-month, weekday (Monday=1). Any octet may be 0xFF to indicate "unspecified".
  • Special pattern values (Clause 20.2.12): month 13=odd months, 14=even months; day 32=last day of month, 33=odd days, 34=even days. These shall not be used when conveying an actual date (e.g. the Device object's Local_Date or a TimeSynchronization-Request).
  • ASN.1 production (Clause 21): Date ::= [APPLICATION 10] OCTET STRING (SIZE(4)) (with comments describing the special values above).
  • Primary usage contexts: date_list property of Calendar objects (12.9), exception_schedule of Schedule objects (12.24), start_time/stop_time in ReadRange requests, Event Timestamps, and many "last changed" properties.

This module supplies ergonomic conversion helpers to and from Elixir Date while correctly handling the pattern semantics via a reference date.

Examples

Specific date

iex> date = %BACnetDate{year: 2024, month: 12, day: 25, weekday: 3}
iex> BACnetDate.specific?(date)
true
iex> BACnetDate.to_date!(date)
~D[2024-12-25]

Wildcard / pattern date (e.g. "every Christmas")

iex> christmas = %BACnetDate{year: :unspecified, month: 12, day: 25, weekday: :unspecified}
iex> BACnetDate.specific?(christmas)
false
iex> BACnetDate.to_date!(christmas, ~D[2025-06-01])
~D[2025-12-25]

Even/odd patterns

iex> even_months = %BACnetDate{year: 2025, month: :even, day: 1, weekday: :unspecified}
iex> BACnetDate.to_date!(even_months, ~D[2025-03-15])
~D[2025-02-01]

Edge cases

Day overflow falls back to the end of the month:

iex> feb30 = %BACnetDate{year: 2025, month: 2, day: 30, weekday: :unspecified}
iex> BACnetDate.to_date!(feb30, ~D[2025-01-01])
~D[2025-02-28]

day: :last counts as specific (as long as the other components are concrete values):

iex> last_day = %BACnetDate{year: 2025, month: 2, day: :last, weekday: 5}
iex> BACnetDate.specific?(last_day)
true
iex> BACnetDate.to_date!(last_day, ~D[2025-01-01])
~D[2025-02-28]

Warning

Per the BACnet specification, pattern dates (any special values) shall not be used when conveying actual dates, such as the Device object's Local_Date or in TimeSynchronization requests.

See Also

Summary

Types

t()

Represents a BACnet Date (application tag 10).

Functions

Compares two BACnet Date.

Encodes the given BACnet Date into an application tag.

Converts a Date to a BACnet Date.

Parses a BACnet Date from BACnet application tags encoding.

Checks whether the given BACnet Date is a specific date value (every component is a numeric value; day: :last counts as specific).

Converts the BACnet Date to a Date.

Creates a new BACnet Date with the current UTC date.

Validates whether the given BACnet date is in form valid.

Types

t()

@type t() :: %BACnet.Protocol.BACnetDate{
  day: 1..31 | :even | :odd | :last | :unspecified,
  month: 1..12 | :even | :odd | :unspecified,
  weekday: 1..7 | :unspecified,
  year: 1900..2154 | :unspecified
}

Represents a BACnet Date (application tag 10).

  • year - 1900-2154 or :unspecified (0xFF in encoding)
  • month - 1-12, :even, :odd, or :unspecified
  • day - 1-31, :even, :odd, :last, or :unspecified
  • weekday - 1 (Monday) … 7 (Sunday) or :unspecified

The special atoms create date patterns. A pattern matches any concrete date whose corresponding component satisfies the rule (e.g. month: :odd matches January, March, …). See specific?/1 and the conversion functions for how patterns are resolved against a reference date.

Functions

compare(date1, date2)

@spec compare(t(), t()) :: :gt | :eq | :lt

Compares two BACnet Date.

Returns :gt if first date is later than the second, and :lt for vice versa. If the two dates are equal, :eq is returned.

Note that this is achieved by converting to Date and then comparing them.

encode(date, opts \\ [])

@spec encode(t(), Keyword.t()) ::
  {:ok, BACnet.Protocol.ApplicationTags.encoding_list()} | {:error, term()}

Encodes the given BACnet Date into an application tag.

from_date(date)

@spec from_date(Date.t()) :: t()

Converts a Date to a BACnet Date.

parse(tags)

Parses a BACnet Date from BACnet application tags encoding.

specific?(time)

@spec specific?(t()) :: boolean()

Checks whether the given BACnet Date is a specific date value (every component is a numeric value; day: :last counts as specific).

to_date(date, ref_date \\ Date.utc_today())

@spec to_date(t(), Date.t()) :: {:ok, Date.t()} | {:error, term()}

Converts the BACnet Date to a Date.

If any of the fields are unspecified, the reference date (current UTC value) is used. In case of even or odd, either the current or the previous value of the reference date is used.

to_date!(date, ref_date \\ Date.utc_today())

@spec to_date!(t(), Date.t()) :: Date.t() | no_return()

Bang-version of to_date/1.

utc_today()

@spec utc_today() :: t()

Creates a new BACnet Date with the current UTC date.

valid?(t)

@spec valid?(t()) :: boolean()

Validates whether the given BACnet date is in form valid.

It only validates the struct is valid as per type specification, it does not validate that the day matches the weekday.