# Http\_date: RFC 9110 HTTP dates as one DCG that both parses and formats

**URL:** <https://swi-prolog.discourse.group/t/http-date-rfc-9110-http-dates-as-one-dcg-that-both-parses-and-formats/9808>\
**Category:** Pack\
**Tags:** http\
**Created:** [September 5, 2026, 9:34am UTC](https://swi-prolog.discourse.group/t/http-date-rfc-9110-http-dates-as-one-dcg-that-both-parses-and-formats/9808 "2026-09-05T09:34:31Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![bikallem](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/bikallem/32/8055_2.png) [@bikallem](https://swi-prolog.discourse.group/u/bikallem)\
**Post date:** [September 5, 2026, 9:34am UTC](https://swi-prolog.discourse.group/t/http-date-rfc-9110-http-dates-as-one-dcg-that-both-parses-and-formats/9808/1 "2026-09-05T09:34:31Z")

</div>

I ported [http-date-rs](https://github.com/bikallem/http-date-rs) to SWI-prolog as an exercise and the result was useful enough to be worth packaging.

RFC 9110 allows three date formats: IMF-fixdate, RFC 850 and asctime. The Rust version has a decoder (a Decoder struct with a position, expect/byte\_at, a method per field) and a separate encoder built from format! calls. Two tables of month names pointing in opposite directions, and a fuzz target whose job is to catch them disagreeing.

The Prolog version is one grammar:

```shell
http_date(imf_fixdate, datetime(Day, Date, Time)) -->
    day_name(Day, short), `, `, date1(Date), ` `, time(Time), ` GMT`.

```

decode/2 and encode/2 both call phrase/2 on it. The month table is seven facts read in whichever direction the caller needs.

Four things I liked:

- phrase/2 demands the whole list is consumed, which is the trailing-data check the Rust version does by hand

- digits//2 dispatches on var/1: parse when the value is unbound, format with leading zeros when it is bound. That is the only place reversibility needed help, because arithmetic only runs one way.

- The RFC 850 two-digit-year invariant needs no check. digits(2, 1994) fails, so the bad value is unrepresentable rather than rejected.

- The round trip test over every day and month pair are generated from the same tables the grammar reads. In rust - to do the same - I have had to separately define fuzz and property tests.

Lastly, the prolog version in significantly shorter - approximately about 20% of the rust version - while providing the same functionality.

Feedback welcome.

Bikal

---

<div class="post-metadata">

**Author:** ![brebs](https://avatars.discourse-cdn.com/v4/letter/b/e9c0ed/32.png) [@brebs](https://swi-prolog.discourse.group/u/brebs)\
**Post date:** [September 5, 2026, 6:07pm UTC](https://swi-prolog.discourse.group/t/http-date-rfc-9110-http-dates-as-one-dcg-that-both-parses-and-formats/9808/2 "2026-09-05T18:07:10Z")

</div>

The pack is: [http\_date](https://www.swi-prolog.org/pack/list?p=http_date)

I’m curious as to what benefit the [literal](https://github.com/bikallem/http_date/blob/1b574a7e6e8db4ccc6ad7c57c041d1baba4ecbdd/prolog/http_date.pl#L45) predicate brings? Could use simply `Cs` in e.g. [day\_name](https://github.com/bikallem/http_date/blob/1b574a7e6e8db4ccc6ad7c57c041d1baba4ecbdd/prolog/http_date.pl#L48), rather than `literal(Cs)`.

---

<div class="post-metadata">

**Author:** ![bikallem](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/bikallem/32/8055_2.png) [@bikallem](https://swi-prolog.discourse.group/u/bikallem)\
**Post date:** [September 7, 2026, 7:14am UTC](https://swi-prolog.discourse.group/t/http-date-rfc-9110-http-dates-as-one-dcg-that-both-parses-and-formats/9808/3 "2026-09-07T07:14:52Z")

</div>

Thanks. You are correct. `literal` is indeed redundant. I have removed it now.
