Skip to content

Commit 4e51358

Browse files
committed
docs: Update to Readme and GoDoc regarding custom formats
1 parent 5f4577a commit 4e51358

2 files changed

Lines changed: 48 additions & 0 deletions

File tree

README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ For instance:
5151
- To customize any aspect of `http.Client` used to perform requests, use `HttpClient` option, otherwise `http.DefaultClient` will be used
5252
- To pass custom headers, make use of `Headers` option.
5353
- To not fail parsing when unmapped fields exist in RPC responses, use `SkipUnknownFields(true)` option (default is `false`)
54+
- To change how `<dateTime.iso8601>` values are encoded and decoded, use `TimeFormat` option (default is `time.RFC3339`) - see [Time formats](#time-formats)
5455

5556
### Argument encoding
5657

@@ -103,6 +104,50 @@ If XML-RPC response contains no value for well-known data-types, it will be deco
103104
As per XML-RPC specification, `<struct>` may not have an empty list of `<member>` elements, thus no default "empty" value is defined for it.
104105
Similarly, `<array/>` is considered invalid.
105106

107+
### Time formats
108+
109+
The XML-RPC specification defines `dateTime.iso8601` as ISO8601, but implementations disagree in practice - compact and extended forms, present or absent timezone offsets and fractional seconds are all encountered.
110+
By default this library encodes and decodes using `time.RFC3339`. The `TimeFormat` option changes that:
111+
112+
```go
113+
c, err := xmlrpc.NewClient("https://example.com/rpc", xmlrpc.TimeFormat(&xmlrpc.LayoutTimeFormatter{
114+
// Encode using the compact form from the specification's example: 19980717T14:08:55
115+
FormatLayout: xmlrpc.LayoutISO8601Compact,
116+
// Accept any of the commonly encountered forms when decoding
117+
ParseLayouts: xmlrpc.CommonParseLayouts(),
118+
// The compact layout carries no offset, so pin both directions to UTC
119+
FormatLocation: time.UTC,
120+
ParseLocation: time.UTC,
121+
}))
122+
```
123+
124+
Fields come in two pairs: `Format*` controls what goes on the wire, `Parse*` what is accepted off it. `FormatLayout` is the single layout used to encode; `ParseLayouts` are tried in order when decoding and default to `FormatLayout` when unset. When you do set `ParseLayouts` it is the complete list - `FormatLayout` is not added for you, so include it if this client should still read back what it writes. A few layout constants are provided:
125+
126+
| Constant | Example |
127+
|------------------------------|-----------------------------|
128+
| `LayoutISO8601Compact` | `19980717T14:08:55` |
129+
| `LayoutISO8601CompactZoned` | `19980717T14:08:55+0200` |
130+
| `LayoutISO8601Basic` | `19980717T140855` |
131+
| `LayoutISO8601BasicZoned` | `19980717T140855+0200` |
132+
| `LayoutISO8601Extended` | `1998-07-17T14:08:55` |
133+
| `LayoutISO8601ExtendedZoned` | `1998-07-17T14:08:55+02:00` |
134+
135+
ISO8601 calls the form without separators *basic* and the form with them *extended*. The layout shown in the XML-RPC specification's example is neither - a basic date with an extended time - and is called *compact* here. The specification does not mandate a layout, and leaves timezone assumptions to server documentation, which is why this is configurable at all. A fractional second is accepted on decode even though no layout declares one.
136+
137+
Notes on timezones:
138+
139+
* `ParseLocation` only applies to decoded values whose layout carries no offset - values that do carry one always keep it. Left unset it follows `time.Parse`: offset-less values are UTC, and an offset matching the process timezone yields that location along with its DST rules. Set it to decode into one location regardless of where the process runs.
140+
* `FormatLocation`, when set, converts values to that location before encoding. By default values are encoded in whichever location they carry, which is fine for zoned layouts but a trap for zone-less ones: a non-UTC `time.Time` would be written as its local wall-clock with no offset, and the receiver has no way to know. **Always set `FormatLocation` when `FormatLayout` carries no offset.**
141+
142+
Servers doing something stranger than a fixed set of layouts can be handled by implementing the `TimeFormatter` interface directly:
143+
144+
```go
145+
type TimeFormatter interface {
146+
FormatTime(t time.Time) string
147+
ParseTime(value string) (time.Time, error)
148+
}
149+
```
150+
106151
### Field renaming
107152

108153
XML-RPC specification does not necessarily specify any rules for struct's member names. Some services allow struct member names to include characters not compatible with standard Go field naming.

doc.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,8 @@
1818
//
1919
// Additional customizations, such as setting custom headers, changing User-Agent or modifying HTTP Client used to make calls,
2020
// pass corresponding Options to NewClient function.
21+
//
22+
// Servers vary in how they represent the dateTime.iso8601 type. Use the TimeFormat Option with a
23+
// LayoutTimeFormatter (or a custom TimeFormatter) to control how time.Time values are encoded and decoded.
2124

2225
package xmlrpc

0 commit comments

Comments
 (0)