aws-amplify / amplify-android

The fastest and easiest way to use AWS from your Android app.
https://docs.amplify.aws/lib/q/platform/android/
Apache License 2.0
245 stars 114 forks source link

datastore adds UTC mark to AWSTime values even when they don't have it - dev preview #2012

Open ragingsquirrel3 opened 1 year ago

ragingsquirrel3 commented 1 year ago

Before opening, please confirm:

Language and Async Model

Kotlin

Amplify Categories

DataStore

Gradle script dependencies

```groovy // 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

```graphql // Put your schema below this line ```

Additional information and screenshots

No response

AnilMaktala commented 1 year ago

Hi @ragingsquirrel3 👋 , could you please send us your graphql schema?

ragingsquirrel3 commented 1 year ago

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"])
}
AnilMaktala commented 1 year ago

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.

image image
tylerjroach commented 10 months ago

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;
}