Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion ocaml/sdk-gen/c/autogen/src/xen_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -950,7 +950,26 @@ static void parse_into(xen_session *s, xmlNode *value_node,
{
struct tm tm;
memset(&tm, 0, sizeof(tm));
strptime((char *)string, "%Y%m%dT%H:%M:%S", &tm);
// We only support basic ISO8601 since the C SDK only
// connects to the XML-RPC backend
char *formats[] = {
// no dashes, no colons
"%Y%m%dT%H%M%S",
// no dashes, with colons
"%Y%m%dT%H:%M:%S",
// dashes and colons
"%Y-%m-%dT%H:%M:%S",
};
int num_formats = sizeof(formats) / sizeof(formats[0]);

for (int i = 0; i < num_formats; i++)
{
if (strptime((char *)string, formats[i], &tm) != NULL)
{
break;
}
}

((time_t *)value)[slot] = (time_t)mktime(&tm);
free(string);
}
Expand Down
56 changes: 47 additions & 9 deletions ocaml/sdk-gen/csharp/autogen/src/Converters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -385,16 +385,54 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist

internal class XenDateTimeConverter : IsoDateTimeConverter
{
private static readonly string[] DateFormatsUniversal =
{
"yyyyMMddTHH:mm:ssZ", "yyyy-MM-ddThh:mm:ssZ"
string [] DateFormatsUtc = {
// dashes and colons
"yyyy-MM-ddTHH:mm:ssZ",
"yyyy-MM-ddTHH:mm:ss.fffZ",

// no dashes, with colons
"yyyyMMddTHH:mm:ssZ",
"yyyyMMddTHH:mm:ss.fffZ",

// no dashes
"yyyyMMddTHHmmssZ",
"yyyyMMddTHHmmss.fffZ",
};

private static readonly string[] DateFormatsOther =
string[] DateFormatsLocal =
{
"yyyyMMddTHH:mm:ss",
// no dashes
"yyyyMMddTHHmmss.fffzzzz",
"yyyyMMddTHHmmss.fffzzz",
"yyyyMMddTHHmmss.fffzz",
"yyyyMMddTHHmmss.fff",

"yyyyMMddTHHmmsszzzz",
"yyyyMMddTHHmmsszzz",
"yyyyMMddTHHmmsszz"
"yyyyMMddTHHmmsszz",
"yyyyMMddTHHmmss",

// no dashes, with colons
"yyyyMMddTHH:mm:ss.fffzzzz",
"yyyyMMddTHH:mm:ss.fffzzz",
"yyyyMMddTHH:mm:ss.fffzz",
"yyyyMMddTHH:mm:ss.fff",

"yyyyMMddTHH:mm:sszzzz",
"yyyyMMddTHH:mm:sszzz",
"yyyyMMddTHH:mm:sszz",
"yyyyMMddTHH:mm:ss",

// dashes and colons
"yyyy-MM-ddTHH:mm:ss.fffzzzz",
"yyyy-MM-ddTHH:mm:ss.fffzzz",
"yyyy-MM-ddTHH:mm:ss.fffzz",
"yyyy-MM-ddTHH:mm:ss.fff",

"yyyy-MM-ddTHH:mm:sszzzz",
"yyyy-MM-ddTHH:mm:sszzz",
"yyyy-MM-ddTHH:mm:sszz",
"yyyy-MM-ddTHH:mm:ss",
};

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
Expand All @@ -403,11 +441,11 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist

DateTime result;

if (DateTime.TryParseExact(str, DateFormatsUniversal, CultureInfo.InvariantCulture,
if (DateTime.TryParseExact(str, DateFormatsUtc, CultureInfo.InvariantCulture,
DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out result))
return result;

if (DateTime.TryParseExact(str, DateFormatsOther, CultureInfo.InvariantCulture,
if (DateTime.TryParseExact(str, DateFormatsLocal, CultureInfo.InvariantCulture,
DateTimeStyles.None, out result))
return result;

Expand All @@ -420,7 +458,7 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s
{
var dateTime = (DateTime)value;
dateTime = dateTime.ToUniversalTime();
var text = dateTime.ToString(DateFormatsUniversal[0], CultureInfo.InvariantCulture);
var text = dateTime.ToString(DateFormatsUtc[0], CultureInfo.InvariantCulture);
writer.WriteValue(text);
return;
}
Expand Down
29 changes: 28 additions & 1 deletion ocaml/sdk-gen/go/templates/ConvertTime.mustache
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
{{#serialize}}
var timeFormats = []string{time.RFC3339, "20060102T15:04:05Z", "20060102T15:04:05"}
var timeFormats = []string{
time.RFC3339,
"2006-01-02T15:04:05",

// no dashes, no colons
"20060102T15:04:05Z",
"20060102T15:04:05",
"20060102T150405.999999999Z0700",
"20060102T150405",
"20060102T150405Z07",
"20060102T150405Z07:00",

// no dashes, with colons
"20060102T15:04:05Z07",
"20060102T15:04:05Z0700",
"20060102T15:04:05Z07:00",
"20060102T15:04:05.999999999Z07",
"20060102T15:04:05.999999999Z07:00",
"20060102T15:04:05.999999999Z07",

// dashes and colon patterns not covered by `time.RFC3339`
"2006-01-02T15:04:05Z07",
"2006-01-02T15:04:05Z0700",
"2006-01-02T15:04:05Z07:00",
"2006-01-02T15:04:05.999999999Z07",
"2006-01-02T15:04:05.999999999Z07:00",
"2006-01-02T15:04:05.999999999Z07",
}

//nolint:unparam
func serialize{{func_name_suffix}}(context string, value {{type}}) (string, error) {
Expand Down
29 changes: 28 additions & 1 deletion ocaml/sdk-gen/go/test_data/time_convert.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,31 @@
var timeFormats = []string{time.RFC3339, "20060102T15:04:05Z", "20060102T15:04:05"}
var timeFormats = []string{
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not caused by this PR, but why do we need to redefine the time formats in the test code?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, I think it's there to ensure SDK source files are generated as expected.

@minglumlu could you help clarify?

Copy link
Member

@minglumlu minglumlu Aug 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This time_convert.go is an expected string output of rendering the template ConvertTime.mustache. The output will be put into the convert.go in the SDK.
Since the template is changed, as the test data, this file has to be updated as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it's just a test of the mustache rendering, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, as it is under the test_data directory.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok, they're test data in this case, not formats.
Now that I'm thinking about it, it might be nicer to use patterns as date formats in the mustache template instead of hardcoded dates, even if Go works with both. It looks to me like a better coding style - thoughts?

Copy link
Member Author

@danilo-delbusso danilo-delbusso Sep 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually now that I've had a look in Go we use hardcoded dates (see https://pkg.go.dev/time#Parse)

Below is a snippet I used for testing (with which I actually just spotted a typo!), you can run it in on go.dev/play if you want

`example.go`
package main

import (
	"fmt"
	"time"
)

func main() {
	dates := map[string]time.Time{
		// no dashes, no colons
		"20220101T123045":       time.Date(2022, 1, 1, 12, 30, 45, 0, time.Local),
		"20220101T123045Z":      time.Date(2022, 1, 1, 12, 30, 45, 0, time.UTC),
		"20220101T123045+03":    time.Date(2022, 1, 1, 12, 30, 45, 0, time.FixedZone("", 3*60*60)), // +03 timezone
		"20220101T123045+0300":  time.Date(2022, 1, 1, 12, 30, 45, 0, time.FixedZone("", 3*60*60)),
		"20220101T123045+03:00": time.Date(2022, 1, 1, 12, 30, 45, 0, time.FixedZone("", 3*60*60)),

		"20220101T123045.123":       time.Date(2022, 1, 1, 12, 30, 45, 123000000, time.Local),
		"20220101T123045.123Z":      time.Date(2022, 1, 1, 12, 30, 45, 123000000, time.UTC),
		"20220101T123045.123+03":    time.Date(2022, 1, 1, 12, 30, 45, 123000000, time.FixedZone("", 3*60*60)),
		"20220101T123045.123+0300":  time.Date(2022, 1, 1, 12, 30, 45, 123000000, time.FixedZone("", 3*60*60)),
		"20220101T123045.123+03:00": time.Date(2022, 1, 1, 12, 30, 45, 123000000, time.FixedZone("", 3*60*60)),

		// no dashes, with colons
		"20220101T12:30:45":       time.Date(2022, 1, 1, 12, 30, 45, 0, time.Local),
		"20220101T12:30:45Z":      time.Date(2022, 1, 1, 12, 30, 45, 0, time.UTC),
		"20220101T12:30:45+03":    time.Date(2022, 1, 1, 12, 30, 45, 0, time.FixedZone("", 3*60*60)),
		"20220101T12:30:45+0300":  time.Date(2022, 1, 1, 12, 30, 45, 0, time.FixedZone("", 3*60*60)),
		"20220101T12:30:45+03:00": time.Date(2022, 1, 1, 12, 30, 45, 0, time.FixedZone("", 3*60*60)),

		"20220101T12:30:45.123":       time.Date(2022, 1, 1, 12, 30, 45, 123000000, time.Local),
		"20220101T12:30:45.123Z":      time.Date(2022, 1, 1, 12, 30, 45, 123000000, time.UTC),
		"20220101T12:30:45.123+03":    time.Date(2022, 1, 1, 12, 30, 45, 123000000, time.FixedZone("", 3*60*60)),
		"20220101T12:30:45.123+0300":  time.Date(2022, 1, 1, 12, 30, 45, 123000000, time.FixedZone("", 3*60*60)),
		"20220101T12:30:45.123+03:00": time.Date(2022, 1, 1, 12, 30, 45, 123000000, time.FixedZone("", 3*60*60)),

		// dashes and colons
		"2022-01-01T12:30:45":       time.Date(2022, 1, 1, 12, 30, 45, 0, time.Local),
		"2022-01-01T12:30:45Z":      time.Date(2022, 1, 1, 12, 30, 45, 0, time.UTC),
		"2022-01-01T12:30:45+03":    time.Date(2022, 1, 1, 12, 30, 45, 0, time.FixedZone("", 3*60*60)),
		"2022-01-01T12:30:45+0300":  time.Date(2022, 1, 1, 12, 30, 45, 0, time.FixedZone("", 3*60*60)),
		"2022-01-01T12:30:45+03:00": time.Date(2022, 1, 1, 12, 30, 45, 0, time.FixedZone("", 3*60*60)),

		"2022-01-01T12:30:45.123":    time.Date(2022, 1, 1, 12, 30, 45, 123000000, time.Local),
		"2022-01-01T12:30:45.123Z":   time.Date(2022, 1, 1, 12, 30, 45, 123000000, time.UTC),
		"2022-01-01T12:30:45.123+03": time.Date(2022, 1, 1, 12, 30, 45, 123000000, time.FixedZone("", 3*60*60)),
	}
	var timeFormats = []string{
		time.RFC3339,
		"2006-01-02T15:04:05",
		// no dashes, no colons
		"20060102T15:04:05Z",
		"20060102T15:04:05",
		"20060102T150405.999999999Z0700",
		"20060102T150405",
		"20060102T150405Z07",
		"20060102T150405Z07:00",
		// no dashes, with colons
		"20060102T15:04:05Z07",
		"20060102T15:04:05Z0700",
		"20060102T15:04:05Z07:00",
		"20060102T15:04:05.999999999Z07",
		"20060102T15:04:05.999999999Z07:00",
		"20060102T15:04:05.999999999Z07",
		// dashes and colon patterns not covered by `time.RFC3339`
		"2006-01-02T15:04:05Z07",
		"2006-01-02T15:04:05Z0700",
		"2006-01-02T15:04:05Z07:00",
		"2006-01-02T15:04:05.999999999Z07",
		"2006-01-02T15:04:05.999999999Z07:00",
		"2006-01-02T15:04:05.999999999Z07",
	}
	for input, expected := range dates {
		for _, timeFormat := range timeFormats {
			result, err := time.Parse(timeFormat, input)
			if err == nil {

				matching := expected.Equal(result)
				if !matching {
					fmt.Printf("Found for '%s'\n", input)
					fmt.Printf("Corresponding time format is: '%s'\n", timeFormat)
					fmt.Printf("Expected: %s\n", expected)
					fmt.Printf("Parsed: %s\n\n", result)
				}
			}

		}
	}

}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, what I'm saying here is not that it doesn't work, but rather whether we might want to refactor it to use patterns for the time formats (ddmmyy etc.) like we do in the other languages instead of hardcoded dates. It's a matter of style really, not an issue with the functionality.

time.RFC3339,
"2006-01-02T15:04:05",

// no dashes, no colons
"20060102T15:04:05Z",
"20060102T15:04:05",
"20060102T150405.999999999Z0700",
"20060102T150405",
"20060102T150405Z07",
"20060102T150405Z07:00",

// no dashes, with colons
"20060102T15:04:05Z07",
"20060102T15:04:05Z0700",
"20060102T15:04:05Z07:00",
"20060102T15:04:05.999999999Z07",
"20060102T15:04:05.999999999Z07:00",
"20060102T15:04:05.999999999Z07",

// dashes and colon patterns not covered by `time.RFC3339`
"2006-01-02T15:04:05Z07",
"2006-01-02T15:04:05Z0700",
"2006-01-02T15:04:05Z07:00",
"2006-01-02T15:04:05.999999999Z07",
"2006-01-02T15:04:05.999999999Z07:00",
"2006-01-02T15:04:05.999999999Z07",
}

//nolint:unparam
func serializeTime(context string, value time.Time) (string, error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,97 @@
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

/**
* {@link CustomDateDeserializer} is a Jackson JSON deserializer for parsing {@link Date} objects
* {@link CustomDateDeserializer} is a Jackson JSON deserializer for parsing
* {@link Date} objects
* from custom date formats used in Xen-API responses.
*/
public class CustomDateDeserializer extends StdDeserializer<Date> {

/**
* Array of {@link SimpleDateFormat} objects representing the custom date formats
* used in XenServer API responses.
* Array of {@link SimpleDateFormat} objects representing the date formats
* used in xen-api responses.
*
* RFC-3339 date formats can be returned in either Zulu or time zone agnostic.
* This list is not an exhaustive list of formats supported by RFC-3339, rather
* a set of formats that will enable the deserialization of xen-api dates.
* Formats are listed in order of decreasing precision. When adding
* to this list, please ensure the order is kept.
*/
private final SimpleDateFormat[] dateFormatters
= new SimpleDateFormat[]{
private static final SimpleDateFormat[] dateFormatsUtc = {
// Most commonly returned formats
new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"),
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"),
new SimpleDateFormat("ss.SSS"),

// Other
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"),
new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss'Z'"),
new SimpleDateFormat("ss.SSS")
new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss.SSS'Z'"),
new SimpleDateFormat("yyyyMMdd'T'HHmmss.SSS'Z'"),

};

/**
* Array of {@link SimpleDateFormat} objects representing the date formats for
* local time.
* These formats are used to parse dates in local time zones.
* Formats are listed in order of decreasing precision. When adding
* to this list, please ensure the order is kept.
*/
private static final SimpleDateFormat[] dateFormatsLocal = {
// no dashes, no colons
new SimpleDateFormat("yyyyMMdd'T'HHmmss.SSSZZZ"),
new SimpleDateFormat("yyyyMMdd'T'HHmmss.SSSZZ"),
new SimpleDateFormat("yyyyMMdd'T'HHmmss.SSSZ"),
new SimpleDateFormat("yyyyMMdd'T'HHmmss.SSSXXX"),
new SimpleDateFormat("yyyyMMdd'T'HHmmss.SSSXX"),
new SimpleDateFormat("yyyyMMdd'T'HHmmss.SSSX"),
new SimpleDateFormat("yyyyMMdd'T'HHmmss.SSS"),

new SimpleDateFormat("yyyyMMdd'T'HHmmssZZZ"),
new SimpleDateFormat("yyyyMMdd'T'HHmmssZZ"),
new SimpleDateFormat("yyyyMMdd'T'HHmmssZ"),
new SimpleDateFormat("yyyyMMdd'T'HHmmssXXX"),
new SimpleDateFormat("yyyyMMdd'T'HHmmssXX"),
new SimpleDateFormat("yyyyMMdd'T'HHmmssX"),
new SimpleDateFormat("yyyyMMdd'T'HHmmss"),

// no dashes, with colons
new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss.SSSZZZ"),
new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss.SSSZZ"),
new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss.SSSZ"),
new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss.SSSXXX"),
new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss.SSSXX"),
new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss.SSSX"),
new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss.SSS"),

new SimpleDateFormat("yyyyMMdd'T'HH:mm:ssZZZ"),
new SimpleDateFormat("yyyyMMdd'T'HH:mm:ssZZ"),
new SimpleDateFormat("yyyyMMdd'T'HH:mm:ssZ"),
new SimpleDateFormat("yyyyMMdd'T'HH:mm:ssXXX"),
new SimpleDateFormat("yyyyMMdd'T'HH:mm:ssXX"),
new SimpleDateFormat("yyyyMMdd'T'HH:mm:ssX"),
new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss"),

// dashes and colons
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZZZ"),
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZZ"),
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"),
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"),
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXX"),
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX"),
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"),

new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZ"),
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZ"),
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"),
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"),
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXX"),
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX"),
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"),
};

/**
Expand All @@ -62,28 +138,44 @@ public CustomDateDeserializer() {
}

/**
* Constructs a {@link CustomDateDeserializer} instance with the specified value type.
* Constructs a {@link CustomDateDeserializer} instance with the specified value
* type.
*
* @param t The value type to handle (can be null, handled by superclass)
*/
public CustomDateDeserializer(Class t) {
super(t);
var utcTimeZone = TimeZone.getTimeZone("UTC");
for (var utcFormatter : dateFormatsUtc) {
utcFormatter.setTimeZone(utcTimeZone);
}
}

private static

/**
* Deserializes a {@link Date} object from the given JSON parser.
*
* @param jsonParser The JSON parser containing the date value to deserialize
* @param jsonParser The JSON parser containing the date value to
* deserialize
* @param deserializationContext The deserialization context
* @return The deserialized {@link Date} object
* @throws IOException if an I/O error occurs during deserialization
*/
@Override
public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
@Override public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException {
var text = jsonParser.getText();
for (SimpleDateFormat formatter : dateFormatsUtc) {
try {
return formatter.parse(text);
} catch (ParseException e) {
// ignore
}
}

for (SimpleDateFormat formatter : dateFormatters) {
for (SimpleDateFormat formatter : dateFormatsLocal) {
try {
return formatter.parse(jsonParser.getText());
return formatter.parse(text);
} catch (ParseException e) {
// ignore
}
Expand Down
Loading