datastore adds UTC mark to AWSTime values even when they don't have it - dev preview
Before opening, please confirm:
- [X] I have searched for duplicate or closed issues and discussions.
Language and Async Model
Kotlin
Amplify Categories
DataStore
Gradle script dependencies
// Put output below this line
dependencies {
api amplifyFlutter
implementation "com.amplifyframework:aws-datastore:1.36.5-dev-preview.0"
implementation "com.amplifyframework:aws-api-appsync:1.36.5-dev-preview.0"
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:4.0.0'
testImplementation 'org.mockito:mockito-inline:3.11.2'
testImplementation 'androidx.test:core:1.4.0'
// Tests must be updated if bumped
//noinspection GradleDependency
testImplementation 'org.robolectric:robolectric:4.3.1'
testImplementation 'com.fasterxml.jackson.core:jackson-core:2.12.4'
testImplementation 'com.fasterxml.jackson.core:jackson-annotations:2.12.4'
testImplementation 'com.fasterxml.jackson.core:jackson-databind:2.12.4'
}
Environment information
# Put output below this line
------------------------------------------------------------
Gradle 7.2
------------------------------------------------------------
Build time: 2021-08-17 09:59:03 UTC
Revision: a773786b58bb28710e3dc96c4d1a7063628952ad
Kotlin: 1.5.21
Groovy: 3.0.8
Ant: Apache Ant(TM) version 1.10.9 compiled on September 27 2020
JVM: 11.0.14.1 (Homebrew 11.0.14.1+0)
OS: Mac OS X 12.6 x86_64
Please include any relevant guides or documentation you're referencing
No response
Describe the bug
Found as a bug in flutter dev-preview https://github.com/aws-amplify/amplify-flutter/issues/2214 which uses amplify-android dev preview.
If I have a model with type of AWSTime and I save a time that does not include timezone (which is optional), the version of the model that I query from the datastore will always include "Z" to indicate UTC.
Expected: AWSTime values respect inclusion/omitting timezone as saved
Reproduction steps (if applicable)
Save a model with a value of type AWSTime with no timezone offset set. Then, query that from the datastore and the model will have a timezone offset added "Z" which is optional, but should reflect how user saved it. I'm not sure if saved that way or happens during query.
Code Snippet
// Put your code below this line.
Log output
// Put your logs below this line
amplifyconfiguration.json
No response
GraphQL Schema
// Put your schema below this line
Additional information and screenshots
No response
Hi @ragingsquirrel3 👋 , could you please send us your graphql schema?
Sure, pretty verbose, but there is one model here specifically for testing times which should be pretty obvious.
input AMPLIFY {
globalAuthRule: AuthRule = { allow: public }
}
type Blog @model {
id: ID!
name: String!
posts: [Post] @hasMany(indexName: "byBlog", fields: ["id"])
}
type Post @model {
id: ID!
title: String!
rating: Int!
created: AWSDateTime
blogID: ID @index(name: "byBlog")
blog: Blog @belongsTo(fields: ["blogID"])
comments: [Comment] @hasMany(indexName: "byPost", fields: ["id"])
tags: [Tag] @manyToMany(relationName: "PostTags")
}
type Comment @model {
id: ID!
postID: ID! @index(name: "byPost", sortKeyFields: ["content"])
post: Post @belongsTo(fields: ["postID"])
content: String!
}
type Tag @model {
id: ID!
label: String!
posts: [Post] @manyToMany(relationName: "PostTags")
}
type ModelWithAppsyncScalarTypes @model {
id: ID!
stringValue: String
altStringValue: String
listOfStringValue: [String]
intValue: Int
altIntValue: Int
listOfIntValue: [Int]
floatValue: Float
listOfFloatValue: [Float]
booleanValue: Boolean
listOfBooleanValue: [Boolean]
awsDateValue: AWSDate
listOfAWSDateValue: [AWSDate]
awsTimeValue: AWSTime
listOfAWSTimeValue: [AWSTime]
awsDateTimeValue: AWSDateTime
listOfAWSDateTimeValue: [AWSDateTime]
awsTimestampValue: AWSTimestamp
listOfAWSTimestampValue: [AWSTimestamp]
awsEmailValue: AWSEmail
listOfAWSEmailValue: [AWSEmail]
awsJsonValue: AWSJSON
listOfAWSJsonValue: [AWSJSON]
awsPhoneValue: AWSPhone
listOfAWSPhoneValue: [AWSPhone]
awsURLValue: AWSURL
listOfAWSURLValue: [AWSURL]
awsIPAddressValue: AWSIPAddress
listOfAWSIPAddressValue: [AWSIPAddress]
}
type ModelWithEnum @model {
id: ID!
enumField: EnumField
listOfEnumField: [EnumField]
}
enum EnumField {
yes
no
}
type ModelWithCustomType @model {
id: ID!
customTypeValue: CustomTypeWithAppsyncScalarTypes
listOfCustomTypeValue: [CustomTypeWithAppsyncScalarTypes]
}
type CustomTypeWithAppsyncScalarTypes {
stringValue: String
listOfStringValue: [String]
intValue: Int
listOfIntValue: [Int]
floatValue: Float
listOfFloatValue: [Float]
booleanValue: Boolean
listOfBooleanValue: [Boolean]
awsDateValue: AWSDate
listOfAWSDateValue: [AWSDate]
awsDateTimeValue: AWSDateTime
listOfAWSDateTimeValue: [AWSDateTime]
awsTimeValue: AWSTime
listOfAWSTimeValue: [AWSTime]
awsTimestampValue: AWSTimestamp
listOfAWSTimestampValue: [AWSTimestamp]
awsEmailValue: AWSEmail
listOfAWSEmailValue: [AWSEmail]
awsJsonValue: AWSJSON
listOfAWSJsonValue: [AWSJSON]
awsPhoneValue: AWSPhone
listOfAWSPhoneValue: [AWSPhone]
awsURLValue: AWSURL
listOfAWSURLValue: [AWSURL]
awsIPAddressValue: AWSIPAddress
listOfAWSIPAddressValue: [AWSIPAddress]
enumValue: EnumField
listOfEnumValue: [EnumField]
customTypeValue: SimpleCustomType
listOfCustomTypeValue: [SimpleCustomType]
}
type SimpleCustomType {
foo: String!
}
type HasOneParent @model {
id: ID!
name: String
implicitChild: HasOneChild @hasOne
explicitChildID: ID
explicitChild: HasOneChild @hasOne(fields: ["explicitChildID"])
}
type HasOneChild @model {
id: ID!
name: String
}
type HasManyParent @model {
id: ID!
name: String
implicitChildren: [HasManyChildImplicit] @hasMany
explicitChildren: [HasManyChildExplicit]
@hasMany(indexName: "byHasManyParent", fields: ["id"])
}
type HasManyChildImplicit @model {
id: ID!
name: String
}
type HasManyChildExplicit @model {
id: ID!
name: String
hasManyParentID: ID! @index(name: "byHasManyParent", sortKeyFields: ["name"])
}
type HasManyParentBiDirectionalImplicit @model {
id: ID!
name: String
biDirectionalImplicitChildren: [HasManyChildBiDirectionalImplicit] @hasMany
}
type HasManyChildBiDirectionalImplicit @model {
id: ID!
name: String
hasManyParent: HasManyParentBiDirectionalImplicit @belongsTo
}
type HasManyParentBiDirectionalExplicit @model {
id: ID!
name: String
biDirectionalExplicitChildren: [HasManyChildBiDirectionalExplicit]
@hasMany(indexName: "byHasManyParent", fields: ["id"])
}
type HasManyChildBiDirectionalExplicit @model {
id: ID!
name: String
hasManyParentId: ID! @index(name: "byHasManyParent", sortKeyFields: ["name"])
hasManyParent: HasManyParentBiDirectionalExplicit
@belongsTo(fields: ["hasManyParentId"])
}
type BelongsToParent @model {
id: ID!
name: String
implicitChild: BelongsToChildImplicit @hasOne
explicitChild: BelongsToChildExplicit @hasOne
}
type BelongsToChildImplicit @model {
id: ID!
name: String
belongsToParent: BelongsToParent @belongsTo
}
type BelongsToChildExplicit @model {
id: ID!
name: String
belongsToParentID: ID
belongsToParent: BelongsToParent @belongsTo(fields: ["belongsToParentID"])
}
type MultiRelatedMeeting @model {
id: ID! @primaryKey
title: String!
attendees: [MultiRelatedRegistration]
@hasMany(indexName: "byMeeting", fields: ["id"])
}
type MultiRelatedAttendee @model {
id: ID! @primaryKey
meetings: [MultiRelatedRegistration]
@hasMany(indexName: "byAttendee", fields: ["id"])
}
type MultiRelatedRegistration @model {
id: ID! @primaryKey
meetingId: ID @index(name: "byMeeting", sortKeyFields: ["attendeeId"])
meeting: MultiRelatedMeeting! @belongsTo(fields: ["meetingId"])
attendeeId: ID @index(name: "byAttendee", sortKeyFields: ["meetingId"])
attendee: MultiRelatedAttendee! @belongsTo(fields: ["attendeeId"])
}
Hey @ragingsquirrel3 👋, Thanks for sharing the schema. We can replicate the issue with the details provided and marked this as a bug for the team to evaluate further.
Did a quick check to confirm that this issue is still present and due to the changes here: https://github.com/aws-amplify/amplify-android/pull/1670.
Specifically this block, which will set UTC as default offset if one is not present.
try {
OffsetTime offsetTime = OffsetTime.parse(timeValue, DateTimeFormatter.ISO_OFFSET_TIME);
localTime = LocalTime.from(offsetTime);
zoneOffset = ZoneOffset.from(offsetTime);
} catch (Exception exception) {
localTime = LocalTime.parse(timeValue, DateTimeFormatter.ISO_LOCAL_TIME);
zoneOffset = ZoneOffset.UTC;
}