Skip to content

Proposal: Native Build-Time Date Formatting via ht-datetimeformat #3

Description

@thoward

Overview

Currently, formatting date properties in HyperTemplates requires external JavaScript or manual string manipulation. This proposal introduces the ht-datetimeformat attribute, enabling high-performance, build-time date formatting using the industry-standard Unicode LDML (Locale Data Markup Language) microsyntax.

Unicode LDML is chosen because it:

  • is an internationally recognized standard
  • is natively supported in Swift with good support in JavaScript
  • supports a wide range of formatting options that other systems lack

Specification

Target Element

The ht-datetimeformat attribute is strictly availble only on the <time> element. This reinforces semantic HTML standards and ensures that date-related features are only used where they are accessibility-compliant.

Behavior

  1. Source Discovery: The engine looks for a date value via the datetime attribute on a <time> tag. Note that order of operations requires that ht-attrs="datetime:..." is evaluated FIRST so that the datetime attribute is populated before the ht-datetimeformat attribute is evaluated.
  2. Standard Compliance: The engine assumes an RFC3339 input string (e.g., 2026-04-12T14:00:00Z).
  3. Format Application: The innerHTML of the <time> tag is replaced at build-time using the LDML pattern provided.

Technical Standard: Unicode LDML Reference

To ensure 100% accuracy and cross-platform consistency with Swift and JavaScript, we will implement the following LDML subset. (See the end of this proposal for a more complete description of LDML date formatting options).

1. Date Components

Component Token Example Notes
Year yyyy 2026 4-digit year
yy 26 2-digit truncated year
Month MMMM April Full name
MMM Apr 3-letter abbreviation
MM 04 2-digit (zero-padded)
M 4 Numeric (no padding)
Day dd 12 2-digit (zero-padded)
d 12 Numeric (no padding)
Weekday EEEE Sunday Full name
EEE Sun 3-letter abbreviation

2. Time & Timezone Components

Component Token Example Notes
Hour HH 14 24-hour (00-23)
hh 02 12-hour (01-12)
Minute mm 30 2-digit (zero-padded)
Second ss 05 2-digit (zero-padded)
Period a PM AM/PM marker
Timezone ZZZZZ -07:00 ISO 8601 with colon
Z -0700 ISO 8601 without colon

Implementation Strategy (Go)

Go’s time package uses a unique "reference time" layout (2006-01-02 15:04:05). The HyperTemplates build engine will map LDML tokens to this reference system.

Full Go Mapping Table

NOTE: The implementation must replace longer tokens first to avoid partial matching (e.g., MMMM before MM).

LDML Token Go Reference Translation
yyyy 2006 Year (4-digit)
yy 06 Year (2-digit)
MMMM January Month (Full)
MMM Jan Month (Abbr)
MM 01 Month (Pad)
M 1 Month (No pad)
dd 02 Day (Pad)
d 2 Day (No pad)
EEEE Monday Weekday (Full)
EEE Mon Weekday (Abbr)
HH 15 24-Hour (Pad)
hh 03 12-Hour (Pad)
h 3 12-Hour (No pad)
mm 04 Minute (Pad)
ss 05 Second (Pad)
a PM AM/PM
ZZZZZ Z07:00 Timezone (Extended)

Example Go Translation Function

package main

import (
	"fmt"
	"strings"
	"time"
)

// FormatDateTime takes an RFC3339 date string and an LDML format,
// and returns the human-readable formatted string.
func FormatDateTime(isoDate, ldmlFormat string) (string, error) {
	// 1. Parse the input RFC3339 string
	t, err := time.Parse(time.RFC3339, isoDate)
	if err != nil {
		// Fallback for dates without timezone/seconds if strictly needed
		t, err = time.Parse("2006-01-02T15:04:05", isoDate)
		if err != nil {
			return "", err
		}
	}

	// 2. Translate LDML tokens to Go reference layout
	// Important: Replace longer tokens first to prevent partial matching.
	replacer := strings.NewReplacer(
		"ZZZZZ", "Z07:00",
		"yyyy", "2006",
		"MMMM", "January",
		"EEEE", "Monday",
		"MMM", "Jan",
		"EEE", "Mon",
		"SSS", ".000",
		"yy", "06",
		"MM", "01",
		"dd", "02",
		"HH", "15",
		"hh", "03",
		"mm", "04",
		"ss", "05",
		"y", "2006",
		"M", "1",
		"d", "2",
		"H", "15",
		"h", "3",
		"a", "PM",
		"Z", "-0700",
	)
	
	goLayout := replacer.Replace(ldmlFormat)

	// 3. Output the formatted string
	return t.Format(goLayout), nil
}

func main() {
	// Example usage 
	formatted, _ := FormatDateTime("2026-04-12T14:30:00Z", "dd MMMM yyyy 'at' hh:mm a")
	fmt.Println(formatted) // Output: 12 April 2026 at 02:30 PM
}

Unicode LDML (more complete explanation)

LDML is notoriously dense because it tries to cover every calendar system on Earth. For a web-targeting templating system, you only need the Gregorian subset. Even within that, the range of capabilities is large. Here's the full range of LDML date formatting, of which the above recommendation for HyperTemplates is is only a subset of:

1. The Year (y)

Token Meaning Example (2024) Notes
y Numeric 2024 Minimum digits.
yy 2-digit 24 Forced padding/truncation.
yyyy 4-digit 2024 Standard ISO/W3C format.

2. The Month (M or L)

Use M for standard dates. (Use L only if your language requires a different spelling for months when they stand alone).

Token Meaning Example Notes
M Numeric 9 No leading zero.
MM 2-digit 09 Standard W3C/ISO.
MMM Abbreviated Sep Short name.
MMMM Wide September Full name.
MMMMM Narrow S First letter only (may not be unique).

3. The Day (d)

Token Meaning Example Notes
d Numeric 2 No leading zero.
dd 2-digit 02 Standard W3C/ISO.

4. The Weekday (E or e)

Token Meaning Example Notes
E, EE, EEE Abbreviated Tue Standard short name.
EEEE Wide Tuesday Full name.
EEEEE Narrow T Single letter.
e Numeric 3 Local day of week (1-7).

5. The Hour (H vs h)

Crucial: H is 24-hour (0-23), h is 12-hour (1-12).

Token Meaning Example Notes
H 24-hr 13 No leading zero.
HH 24-hr (2-digit) 13 Standard W3C/ISO.
h 12-hr 1 Used with AM/PM.
hh 12-hr (2-digit) 01 Used with AM/PM.

6. Minutes and Seconds (m, s, S)

Token Meaning Example Notes
m Minute 5 No leading zero.
mm Minute (2-digit) 05 Standard W3C/ISO.
s Second 9 No leading zero.
ss Second (2-digit) 09 Standard W3C/ISO.
SSS Fraction 456 Milliseconds (use 3 S).

7. The Period (a)

Token Meaning Example Notes
a AM/PM PM Use with h or hh.

8. Timezone (z or Z or X)

Token Meaning Example Notes
z Specific PST Short name.
zzzz Specific Pacific Standard Time Full name.
Z ISO 8601 -0800 Basic format.
XXXXX ISO 8601 -08:00 Standard W3C/ISO (includes colon).
x ISO 8601 -08 Hours only if zero minutes.

Additional Implementation Rules:

  1. Escaping: If you want to include literal text in your template (like the word "at"), wrap it in single quotes: yyyy-MM-dd 'at' HH:mm.
  2. Case Sensitivity: M is Month, m is Minute. D is "Day of Year" (1-365), whereas d is "Day of Month" (1-31). Always use lowercase d for standard numerical dates.
  3. The ISO "T": In LDML, the T separator is a literal. You should represent it as 'T' not a space .

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions