Skip to content

Issue with ZonedDateTime Handling in Couchbase SDK #2056

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

Open
jessaardithya opened this issue Jun 4, 2025 · 10 comments
Open

Issue with ZonedDateTime Handling in Couchbase SDK #2056

jessaardithya opened this issue Jun 4, 2025 · 10 comments
Labels
status: feedback-provided Feedback has been provided

Comments

@jessaardithya
Copy link

We are encountering challenges with converting ZonedDateTime fields when using the Couchbase SDK. Our application relies on Java’s standard ZonedDateTime type for several temporal fields, as it retains not only the date and time, but also the associated time zone and offset. This level of precision is essential to ensure a consistent user experience, as well as for auditing and business logic purposes.

For further context :

Preserving Java’s standard ZonedDateTime type-2025052409201526 (1).pdf

Then we tried to solve it, After reviewing, the converters should use CouchbaseDocument (instead of JsonObject). That’s what Couchbase uses internally when de/serializing. Adjust the converters as necessary to achieve the desired result. This also shows overriding additionalConverters (instead of overriding customConversions()).

@Override
	protected void additionalConverters(List<Object> converters) {
		converters.add(new ZonedDateTimeToCouchbaseDocumentConverter());
		converters.add(new CouchbaseDocumentToZonedDateTimeConverter());
	}

	@WritingConverter
	public class ZonedDateTimeToCouchbaseDocumentConverter implements Converter<ZonedDateTime, CouchbaseDocument> {
		@Override     public CouchbaseDocument convert(ZonedDateTime source) {
			CouchbaseDocument obj = new CouchbaseDocument();
			// Adjust construction of document as needed
			obj.put("dateTime", source.toOffsetDateTime().toString());
			obj.put("offset", source.getOffset().toString());
			obj.put("zone", source.getZone().toString());
			return obj;

		}
	}

	@ReadingConverter
	public class CouchbaseDocumentToZonedDateTimeConverter implements Converter<CouchbaseDocument, ZonedDateTime> {
		@Override     public ZonedDateTime convert(CouchbaseDocument source) {
		    // Adjust construction of ZonedDateTime as needed. This implementation ignores offset and zone.
			ZonedDateTime obj = ZonedDateTime.parse(source.get("dateTime").toString());
			return obj;
		}
	}

	
	public static class ZDTEntity  extends BaseEntity {
		public ZonedDateTime zdt;

		public ZDTEntity(ZonedDateTime zdt) {
			this.zdt = zdt;
		}

	}

	@Test
	void writesZonedDateTime() {
		CouchbaseDocument converted = new CouchbaseDocument();
		ZDTEntity entity = new ZDTEntity(ZonedDateTime.parse("2025-01-31T10:20:49Z"));
		customConverter.write(entity, converted);
		Map<String, Object> result = converted.export();
		Map<String,Object> attr0 = (Map<String,Object>)result.get("zdt");
		assertThat(attr0.get("dateTime")).isEqualTo(entity.zdt.toOffsetDateTime().toString());
		assertThat(attr0.get("offset")).isEqualTo(entity.zdt.getOffset().toString());
		assertThat(attr0.get("zone")).isEqualTo(entity.zdt.getZone().toString());
		assertThat(converted.getId()).isEqualTo(BaseEntity.ID);
		System.err.println(attr0);
	}

{dateTime=2025-01-31T10:20:49Z, offset=Z, zone=Z}


	@Test
	void readsZonedDateTime() {
		CouchbaseDocument source = new CouchbaseDocument();
		source.put("t", StringEntity.class.getName());
		CouchbaseDocument attr0 = new CouchbaseDocument();
		ZonedDateTime zdt = ZonedDateTime.parse("2025-01-31T10:20:49Z");;
		attr0.put("dateTime", zdt.toOffsetDateTime());
		attr0.put("offset", zdt.getOffset().toString());
		attr0.put("zone", zdt.getZone());
		source.put("zdt", attr0);
		ZDTEntity converted = customConverter.read(ZDTEntity.class, source);
		assertThat(converted.zdt.toOffsetDateTime()).isEqualTo(zdt.toOffsetDateTime());
		System.err.println(converted.zdt);
	}

But still got an error :

Image

Actually, The code above looks almost correct for both the reading and writing converters — what’s left is just how to register them in the Couchbase configuration. it’s the section in the Couchbase config where we register the reading and writing converters that have already been defined.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jun 4, 2025
@mikereiche
Copy link
Collaborator

Glad Benjamin passed on the information that I gave him. The code is intended to go in your implementation of AbstractCouchbaseConfiguration where there is indeed an additionalConverters(converters) method. If yours does not have that method, then it seems you would have an old version of spring-data-couchbase. Either upgrade or let me know what version you have so we can figure it out.

@jessaardithya
Copy link
Author

Hi @mikereiche thanks for responses.
Currently we use sprinboot version 2.7.2 and using spring-data-couchbase-4.4.2.jar

@mikereiche mikereiche added status: feedback-provided Feedback has been provided and removed status: waiting-for-triage An issue we've not yet triaged labels Jun 4, 2025
@mikereiche
Copy link
Collaborator

Ok - your original override was correct for 4.4.2 (with the appropriate names). Sorry about that. The only change is you needed was to use CouchbaseDocument instead of JsonObject.

@Override     public CustomConversions customConversions() {
    List<Converter<?, ?>> converters = new ArrayList<>();
    converters.add(new ZonedDateTimeToCouchbaseDocumentConverter());
    converters.add(new CouchbaseDocumentToZonedDateTimeConverter());
    return new CouchbaseCustomConversions(converters);
}

note to self: original CBSE ticket - https://jira.issues.couchbase.com/browse/CBSE-20261

@mikereiche
Copy link
Collaborator

copied from the CBSE:

The converters should use CouchbaseDocument (instead of JsonObject). That’s what Couchbase uses internally when de/serializing. Adjust the converters as necessary to achieve the desired result. This also shows overriding additionalConverters (instead of overriding customConversions()).

	@Override
	protected void additionalConverters(List<Object> converters) { // this is for 5.x
		converters.add(new ZonedDateTimeToCouchbaseDocumentConverter());
		converters.add(new CouchbaseDocumentToZonedDateTimeConverter());
	}

	@WritingConverter
	public class ZonedDateTimeToCouchbaseDocumentConverter implements Converter<ZonedDateTime, CouchbaseDocument> {
		@Override     public CouchbaseDocument convert(ZonedDateTime source) {
			CouchbaseDocument obj = new CouchbaseDocument();
			// Adjust construction of document as needed
			obj.put("dateTime", source.toOffsetDateTime().toString());
			obj.put("offset", source.getOffset().toString());
			obj.put("zone", source.getZone().toString());
			return obj;

		}
	}

	@ReadingConverter
	public class CouchbaseDocumentToZonedDateTimeConverter implements Converter<CouchbaseDocument, ZonedDateTime> {
		@Override     public ZonedDateTime convert(CouchbaseDocument source) {
		    // Adjust construction of ZonedDateTime as needed. This implementation ignores offset and zone.
			ZonedDateTime obj = ZonedDateTime.parse(source.get("dateTime").toString());
			return obj;
		}
	}


Tests

	
	public static class ZDTEntity  extends BaseEntity {
		public ZonedDateTime zdt;

		public ZDTEntity(ZonedDateTime zdt) {
			this.zdt = zdt;
		}

	}

	@Test
	void writesZonedDateTime() {
		CouchbaseDocument converted = new CouchbaseDocument();
		ZDTEntity entity = new ZDTEntity(ZonedDateTime.parse("2025-01-31T10:20:49Z"));
		customConverter.write(entity, converted);
		Map<String, Object> result = converted.export();
		Map<String,Object> attr0 = (Map<String,Object>)result.get("zdt");
		assertThat(attr0.get("dateTime")).isEqualTo(entity.zdt.toOffsetDateTime().toString());
		assertThat(attr0.get("offset")).isEqualTo(entity.zdt.getOffset().toString());
		assertThat(attr0.get("zone")).isEqualTo(entity.zdt.getZone().toString());
		assertThat(converted.getId()).isEqualTo(BaseEntity.ID);
		System.err.println(attr0);
	}

{dateTime=2025-01-31T10:20:49Z, offset=Z, zone=Z}


	@Test
	void readsZonedDateTime() {
		CouchbaseDocument source = new CouchbaseDocument();
		source.put("t", StringEntity.class.getName());
		CouchbaseDocument attr0 = new CouchbaseDocument();
		ZonedDateTime zdt = ZonedDateTime.parse("2025-01-31T10:20:49Z");
		attr0.put("dateTime", zdt.toOffsetDateTime());
		attr0.put("offset", zdt.getOffset().toString());
		attr0.put("zone", zdt.getZone());
		source.put("zdt", attr0);
		ZDTEntity converted = customConverter.read(ZDTEntity.class, source);
		assertThat(converted.zdt.toOffsetDateTime()).isEqualTo(zdt.toOffsetDateTime());
		System.err.println(converted.zdt);
	}
2025-01-31T10:20:49Z

@jessaardithya
Copy link
Author

Ok - your original override was correct for 4.4.2 (with the appropriate names). Sorry about that. The only change is you needed was to use CouchbaseDocument instead of JsonObject.

@Override     public CustomConversions customConversions() {
    List<Converter<?, ?>> converters = new ArrayList<>();
    converters.add(new ZonedDateTimeToCouchbaseDocumentConverter());
    converters.add(new CouchbaseDocumentToZonedDateTimeConverter());
    return new CouchbaseCustomConversions(converters);
}

note to self: original CBSE ticket - https://jira.issues.couchbase.com/browse/CBSE-20261

Hi @mikereiche,

Do you mean for this code is to replace the error code? (additionalConverters) ?

Because I'm bit confuse with your second response, it looks similar to the code that we have tried before.

@jessaardithya
Copy link
Author

Ok - your original override was correct for 4.4.2 (with the appropriate names). Sorry about that. The only change is you needed was to use CouchbaseDocument instead of JsonObject.

@Override     public CustomConversions customConversions() {
    List<Converter<?, ?>> converters = new ArrayList<>();
    converters.add(new ZonedDateTimeToCouchbaseDocumentConverter());
    converters.add(new CouchbaseDocumentToZonedDateTimeConverter());
    return new CouchbaseCustomConversions(converters);
}

note to self: original CBSE ticket - https://jira.issues.couchbase.com/browse/CBSE-20261

Hi @mikereiche,

We try this code and still got an error.

@jessaardithya
Copy link
Author

This is our final code :

@Override
//    //public CustomConversions customConversions() {
    public CouchbaseCustomConversions customConversions() {
        return new CouchbaseCustomConversions(Arrays.asList(
                //            new ZonedDateTimeToJsonConverter(),
                //            new JsonToZonedDateTimeConverter()
                //            new ZonedDateTimeToMapConverter(),
                //            new MapToZonedDateTimeConverter()
                //                new ZonedDateTimeToWrapperConverter(),
                //                new WrapperToZonedDateTimeConverter()
                new ZonedDateTimeToCouchbaseDocumentConverter(),
                new CouchbaseDocumentToZonedDateTimeConverter()
        ));
    }
@ReadingConverter
public class CouchbaseDocumentToZonedDateTimeConverter implements Converter<CouchbaseDocument, ZonedDateTime> {

    @Override
    public ZonedDateTime convert(CouchbaseDocument source) {
        // Adjust construction of ZonedDateTime as needed. This implementation ignores offset and zone.
        ZonedDateTime obj = ZonedDateTime.parse(source.get("dateTime").toString());
        return obj;
    }
}
@WritingConverter
public class ZonedDateTimeToCouchbaseDocumentConverter implements Converter<ZonedDateTime, CouchbaseDocument> {

    @Override
    public CouchbaseDocument convert(ZonedDateTime source) {
        CouchbaseDocument obj = new CouchbaseDocument();
        // Adjust construction of document as needed
        obj.put("dateTime", source.toOffsetDateTime().toString());
        obj.put("offset", source.getOffset().toString());
        obj.put("zone", source.getZone().toString());
        return obj;
    }
}

clean version:

@Override
  public CouchbaseCustomConversions customConversions() {
      return new CouchbaseCustomConversions(Arrays.asList(
              new ZonedDateTimeToCouchbaseDocumentConverter(),
              new CouchbaseDocumentToZonedDateTimeConverter()
      ));
  }

@mikereiche
Copy link
Collaborator

We try this code and still got an error.

What error?

@jessaardithya
Copy link
Author

We try this code and still got an error.

What error?

Hi, sorry—just to clarify, I meant to say it didn’t work, not that it threw an error.

Right now, we only need to fix the registration part, since the writer and reader logic is almost correct. The issue seems to happen when trying to register the reader and converter in the CouchbaseConfiguration.

Image

@mikereiche
Copy link
Collaborator

mikereiche commented Jun 6, 2025

Right. And I said don't use additionalConverters() as it was introduced in a later version. Use what you had earlier - with the new names, of course.

Curiously, in the post where you say "We get this error" the code shown is NOT using additionalConverters() - but then when I ask about the error, you show code that DOES use additionalConverters().

btw - you'll only be able to tell if the converters are correct after they are successfully registered.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: feedback-provided Feedback has been provided
Projects
None yet
Development

No branches or pull requests

3 participants