A wrapper class around valid or partial date in yyyy-MM-dd
format such as
2022-01-01 [LocalDate]
2022-01-00 [YearMonth]
2022-00-01 [YearDay]
0000-01-01 [MonthDay]
2022-00-00 [Year]
0000-01-00 [Month]
0000-00-01 [Day]
0000-00-00 [Dateless]
There are a few ways to construct PartialLocalDate
instant
// via constructor
PartialLocalDate pld = new PartialLocalDate("0000-10-30");
// via static method
PartialLocalDate pld = PartialLocalDate.of(0000);
PartialLocalDate pld = PartialLocalDate.of(0000, 10);
PartialLocalDate pld = PartialLocalDate.of(0000, 10, 30);
Calling toString
will always return in yyyy-MM-dd
format, no matter the type of partial date
0000-10-30
0000-00-00
2022-00-30
0000-00-30
It also provides a Classifier
enum such that one would know what is the construct of the PartialLocalDate
enum Classifier {
LOCAL_DATE,
YEAR_MONTH,
MONTH_DAY,
YEAR_DAY,
YEAR,
MONTH,
DAY,
DATELESS
}
It also provides methods to query for
Year, Month, or Day
value- Whether if it's a valid Date (i.e
2022-08-04
)
The core idea is to make use of TemporalAccessor
which is a base interface type for date, time and offset objects
to represent the PartialLocalDate
internally.
However, one challenge is that there is no default support for Day
(i.e 0000-00-10
) or Dateless
(i.e 0000-00-00
). As such, I implemented a simple Day
class which implements TemporalAccessor
and for Dateless
, we can classify it using a Year
class.
This can be further integrates at two layer
- HTTP Layer
- Database Layer
This will abstract away the need to manually handle the conversion, and also for developer to only work with PartialLocalDate
rather than String
We can use @JsonComponent to register the custom serializer and deserializer from String
to PartialLocalDate
and vice versa
There is no need to explicitly create a custom deserializer from request JSON
to PartialLocalDate
because it is supported by default.
So imagine the request like such
{
"companyName": "SG",
"localDate": "2022-08-16",
"localDateTime": "2022-08-16T16:28:24.441",
"offsetDateTime": "2022-08-16T16:28:24.441+07:00",
"partialLocalDate": "2022-08-00"
}
Making a API call, and the serialized object will look like
{
"id": "62fbbbbbf036556e8d15bf1e",
"companyName": "SG",
"localDate": "2022-08-16",
"localDateTime": "2022-08-16T16:28:24.441",
"offsetDateTime": "2022-08-16T09:28:24.441Z",
"partialLocalDate": {
"temporalAccessor": "2022-08",
"classifier": "YEAR_MONTH",
"monthValue": 8,
"yearValue": 2022,
"dayValue": 0,
"validLocalDate": false
}
}
By default, jackson
will use the public field and getters
as part of the serialization process which is why we see it returns with an object and the various fields. However, what we actually need to return back to client is just a String
like 2022-08-00
.
In this case, we can use @JsonComponent
provided by Spring
to write the custom serializer - PartialLocalDateSerializer to return only the value we want. See PartialLocalDateSerializerJsonTests
Once implemented, the returned value from the API will become this
{
"id": "62fbbdddf036556e8d15bf21",
"companyName": "SG",
"localDate": "2022-08-16",
"localDateTime": "2022-08-16T16:28:24.441",
"offsetDateTime": "2022-08-16T09:28:24.441Z",
"partialLocalDate": "2022-08-00"
}
Also see EmploymentControllerTests
Similarly, for MongoDB
, we can use MappingMongoConverter to map specific classes to and from the database
When we read and write from MongoDB
, we can also specify how to "serialize" and "deserialize" PartialLocalDate
. To do so, we can make use of Spring Converter interface, and register those converters to MongoCustomConversions and it will handle the mapping automatically.
See MongoPartialLocalDateReader, MongoPartialLocalDateWriter and MongoConfig for the implementation details.
The object that is saved in database would result in
{
_id: ObjectId('62fbbbbbf036556e8d15bf1e'),
companyName: 'SG',
localDate: ISODate('2022-08-15T16:00:00.000Z'),
localDateTime: ISODate('2022-08-16T08:28:24.441Z'),
offsetDateTime: {
dateTime: ISODate('2022-08-16T09:28:24.441Z'),
offset: 'Z'
},
partialLocalDate: {
date: '2022-08-00',
classifier: 'YEAR_MONTH',
year: 2022,
month: 8,
day: 0
},
_class: 'com.bwgjoseph.partiallocaldate.EmploymentDO'
}
And when retrieved, it will be mapped back to PartialLocalDate
object
See PartialLocalDateTests, PartialLocalDateSerializerJsonTests, EmploymentControllerTests, EmploymentCrudRepositoryTests