Skip to content

Release 1.6.0: return parsed EPP dates in UTC - #59

Merged
ademar merged 1 commit into
masterfrom
fix/utc-dates
Sep 25, 2026
Merged

ademar merged 1 commit into
masterfrom
fix/utc-dates

Conversation

@ademar

@ademar ademar commented Sep 24, 2026

Copy link
Copy Markdown
Member

What the specs say

  • RFC 3730 has been replaced twice: RFC 4930 replaced it, and RFC 5730 (STD 69) replaced RFC 4930. RFC 5731 (domain), 5732 (host) and 5733 (contact) replaced their 49xx counterparts. The IETF datatracker lists no later RFC that updates or replaces any of these.
  • The UTC rule: RFC 5731 §2.4, RFC 5732 §2.4 and RFC 5733 §2.7 each say "Date and time attribute values MUST be represented in Universal Coordinated Time (UTC)", using the upper-case T/Z form. RFC 5730 §2.4 says svDate is UTC. curExpDate is an xs:date, with no time.
  • Launch phase (RFC 8334): notAfter/acceptedDate are dateTime, and every example is UTC. The library already sends these as UTC; unchanged.
  • Nominet: the documentation examples are inconsistent. Some dates end in Z and some don't, sometimes within one message (in Registrar Change, qDate has Z but crDate/exDate don't). There is no timezone statement, and svDate is documented as UTC.
  • Other extensions: SecDNS, IDN, IIS, CIRA, Fury and Namestore have no date fields that the library parses.

Changes

  • New internal EppDateTime helper. It parses with the invariant culture and AssumeUniversal | AdjustToUniversal, and returns DateTimeKind.Utc. Values with an offset are converted to UTC. Values without a zone designator are taken as UTC, per the EPP rule.
  • Values now returned in UTC (previously converted to the machine's local time):
    • DomainRenewResponse.ExDate
    • Nominet DataQuality.DateCommenced and DateToSuspend
    • AbuseNotification.Date
    • DomainsSuspendedNotification.CancelDate
  • DomainRenew: curExpDate was derived from a local-time conversion, so a full exDate such as 2000-04-03T22:00:00.0Z became 2000-04-04 east of UTC, and the registry would reject the renew.
  • Smallregistry birthDate: now formatted with the invariant culture. It previously depended on the current culture's calendar.
  • Core fields unchanged: crDate, upDate, exDate on info responses, qDate and so on are exposed as raw strings and stay as they were.
  • Version 1.6.0. The release notes flag the UTC change as breaking.

Testing

  • New tests:
    • TestDomainRenewResponse1: ExDate is UTC. This replaces an Inconclusive placeholder.
    • TestDomainRenewCommandWithUtcDateTime: curExpDate stays 2000-04-03.
  • The Nominet tests now also assert DateTimeKind.Utc.
  • 57 passed, 22 skipped with TZ set to Asia/Tokyo, America/New_York, UTC and Pacific/Kiritimati.
  • Against the previous library code with TZ=Asia/Tokyo, all 5 date tests fail.

🤖 Generated with Claude Code

RFC 5731 s2.4, 5732 s2.4 and 5733 s2.7 require every date-time to be
UTC. The library parsed dates with DateTime.TryParse, which converts
values with a Z to the machine's local time, so ExDate and the Nominet
dates depended on where the code ran. Parse them through a shared
EppDateTime helper that returns DateTimeKind.Utc, converts offsets to
UTC and treats values without a zone designator (some of Nominet's
examples omit the Z) as UTC.

DomainRenew parsed curExpDate the same way before truncating it to a
date, so a full exDate such as 2000-04-03T22:00:00.0Z became
2000-04-04 east of UTC and the registry would reject the renew.

Also format the Smallregistry birthDate with the invariant culture.

Bump to 1.6.0: callers now get UTC values instead of local time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ademar
ademar merged commit 7a2de4c into master Sep 25, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant