Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CA-397409: Extend SDK deserialization support for xen-api dates #5802

Merged
merged 4 commits into from
Sep 12, 2024

Conversation

danilo-delbusso
Copy link
Member

non-Zulu dates were not parsed correctly.

This meant that calls to getServerLocaltime calls were failing, as they have no timezone information

@psafont
Copy link
Member

psafont commented Jul 10, 2024

I now think that using human-readable dates in the API and the database was a mistake, they all should be unix timestamps. If there's really a need for server timezones (which I very much doubt), it should be exposed through a specialized API call.

Copy link
Contributor

@kc284 kc284 left a comment

Choose a reason for hiding this comment

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

As above.

@danilo-delbusso danilo-delbusso changed the title Extend Java deserialization support for xen-api dates Extend SDK deserialization support for xen-api dates Jul 17, 2024
@danilo-delbusso
Copy link
Member Author

Last update improves support for C, C#, Go, and Java SDKs.

I have tested them all with the following test dates (this one in C#)

var dates = new Dictionary<string, DateTime>(){
    // no dashes, no colons
    { "20220101T123045", new DateTime(2022, 1, 1, 12, 30, 45, DateTimeKind.Unspecified)},
    { "20220101T123045Z", new DateTime(2022, 1, 1, 12, 30, 45, DateTimeKind.Utc) },
    { "20220101T123045+03",  new DateTime(2022, 1, 1, 9, 30, 45, DateTimeKind.Local)},
    { "20220101T123045+0300", new DateTime(2022, 1, 1, 9, 30, 45, DateTimeKind.Local)},
    { "20220101T123045+03:00", new DateTime(2022, 1, 1, 9, 30, 45, DateTimeKind.Local)},
    
    { "20220101T123045.123", new DateTime(2022, 1, 1, 12, 30, 45,123, DateTimeKind.Unspecified)},
    { "20220101T123045.123Z", new DateTime(2022, 1, 1, 12, 30, 45,123, DateTimeKind.Utc) },
    { "20220101T123045.123+03",  new DateTime(2022, 1, 1, 9, 30, 45,123, DateTimeKind.Local)},
    { "20220101T123045.123+0300", new DateTime(2022, 1, 1, 9, 30, 45,123, DateTimeKind.Local)},
    { "20220101T123045.123+03:00", new DateTime(2022, 1, 1, 9, 30, 45,123, DateTimeKind.Local)},

    // no dashes, with colons
    { "20220101T12:30:45", new DateTime(2022, 1, 1, 12, 30, 45, DateTimeKind.Unspecified)},
    { "20220101T12:30:45Z", new DateTime(2022, 1, 1, 12, 30, 45, DateTimeKind.Utc) },
    { "20220101T12:30:45+03",  new DateTime(2022, 1, 1, 9, 30, 45, DateTimeKind.Local)},
    { "20220101T12:30:45+0300", new DateTime(2022, 1, 1, 9, 30, 45, DateTimeKind.Local)},
    { "20220101T12:30:45+03:00", new DateTime(2022, 1, 1, 9, 30, 45, DateTimeKind.Local)},
    
    { "20220101T12:30:45.123", new DateTime(2022, 1, 1, 12, 30, 45,123, DateTimeKind.Unspecified)},
    { "20220101T12:30:45.123Z", new DateTime(2022, 1, 1, 12, 30, 45,123, DateTimeKind.Utc) },
    { "20220101T12:30:45.123+03",  new DateTime(2022, 1, 1, 9, 30, 45,123, DateTimeKind.Local)},
    { "20220101T12:30:45.123+0300", new DateTime(2022, 1, 1, 9, 30, 45,123, DateTimeKind.Local)},
    { "20220101T12:30:45.123+03:00", new DateTime(2022, 1, 1, 9, 30, 45,123, DateTimeKind.Local)},

    // dashes and colons
    { "2022-01-01T12:30:45", new DateTime(2022, 1, 1, 12, 30, 45, DateTimeKind.Unspecified) },
    { "2022-01-01T12:30:45Z", new DateTime(2022, 1, 1, 12, 30, 45, DateTimeKind.Utc) },
    { "2022-01-01T12:30:45+03", new DateTime(2022, 1, 1, 9, 30, 45, DateTimeKind.Local) },
    { "2022-01-01T12:30:45+0300", new DateTime(2022, 1, 1, 9, 30, 45, DateTimeKind.Local) },
    { "2022-01-01T12:30:45+03:00", new DateTime(2022, 1, 1, 9, 30, 45, DateTimeKind.Local) },

    { "2022-01-01T12:30:45.123", new DateTime(2022, 1, 1, 12, 30, 45, 123, DateTimeKind.Unspecified) },
    { "2022-01-01T12:30:45.123Z", new DateTime(2022, 1, 1, 12, 30, 45, 123, DateTimeKind.Utc) },
    { "2022-01-01T12:30:45.123+03", new DateTime(2022, 1, 1, 9, 30, 45, 123, DateTimeKind.Local) },
    { "2022-01-01T12:30:45.123+0300", new DateTime(2022, 1, 1, 9, 30, 45, 123, DateTimeKind.Local) },
    { "2022-01-01T12:30:45.123+03:00", new DateTime(2022, 1, 1, 9, 30, 45, 123, DateTimeKind.Local) },
};

@@ -1,4 +1,31 @@
var timeFormats = []string{time.RFC3339, "20060102T15:04:05Z", "20060102T15:04:05"}
var timeFormats = []string{
Copy link
Contributor

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
Contributor

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
Contributor

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.

@danilo-delbusso danilo-delbusso changed the title Extend SDK deserialization support for xen-api dates CA-397409: Extend SDK deserialization support for xen-api dates Aug 13, 2024
non-Zulu dates were not parsed correctly

Signed-off-by: Danilo Del Busso <danilo.delbusso@cloud.com>
Signed-off-by: Danilo Del Busso <danilo.delbusso@cloud.com>
Signed-off-by: Danilo Del Busso <danilo.delbusso@cloud.com>
Signed-off-by: Danilo Del Busso <danilo.delbusso@cloud.com>
@kc284 kc284 merged commit eda6239 into xapi-project:master Sep 12, 2024
14 checks passed
github-merge-queue bot pushed a commit that referenced this pull request Sep 16, 2024
…ion (#5950)

The Date module has a very convoluted way to deal with timezones because
of its historic clients. While we can't remove all the issues, we can
remove most of them.
- Dates (timestamps, really) without timezones do not contain a frame of
reference, and hence placing them in the UTC timeline can't be done
accurately in the general case. This means that comparing timestamps
gives incorrect results.
- XMLRPC enforces dates to be encoded in ISO8601format. This means
timestamps can lack a timezone.
- Xapi uses xmlrpc's dates, adding this unfortunate limitation.
- While Date allows these timestamps, it assumes that these are in the
UTC timezone. On top of that, it refuses to process any timestamps that
are in any timezone other than UTC (`Z`)
- The Date module tries really hard to hide the timezone of timestamps
that lack it when printing them. This means that timezoneless timestamps
can persist in the database, for no good reason, as they are treated as
UTC timestamps.
- Host.localtime is the only call that returns timezoneless timestamps,
all the other calls correctly return timestamps in the UTC timezone.
Because the call on purpose does not want to return the current time in
UTC, changing this might break clients not ready to process any
timezone, mainly SDK-built ones
#5802
- Dates are stored as a tuple of date, hour, minutes, seconds. This has
very limited precision, which might be unexpected.

This PR does the following mitigation / fixes:
- Document all calls with Datetimes as parameters that the timestamps
will be interpreted as UTC ones if they miss the timezone.
- Remove the limitation to process any valid timezone
- Timezoneless timstamps are immediately processed as UTC timestamps, as
refusing these timestamps can break clients.

Issues that the PR does not fix
- Host.localtime produces timestamps without timezones, this is needed
as adding non-zero timestamps breaks the SDK clients.
- The server does not reject timezoneless timestamp, this might break
migrations, RPUs, or normal clients, so I've held back on this change.
- Printed timestamp do _not_ retain sub-second precision

Drafting as tests are ongoing
@danilo-delbusso danilo-delbusso deleted the bug/rfc-3339 branch September 20, 2024 08:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants