I have a POST API like so:
#JsonDeserialize(using = MyCustomDeserializer.class)
#JsonSerialize(using = MyCustomSerializer.class)
#NotNull(message = "fromDate should not be blank!")
#FutureOrPresent(message = "fromDate must be of Future or Present")
private Instant fromDate;
I have custom serializer and deserializer like so in my utility package:
public class MyCustomSerializer extends JsonSerializer<Instant> {
private DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(ZoneOffset.UTC);
#Override
public void serialize(Instant value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
String str = fmt.format(value);
gen.writeString(str);
}
}
public class MyCustomDeserializer extends JsonDeserializer<Instant> {
private DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(ZoneOffset.UTC);
#Override
public Instant deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
LocalDate date = LocalDate.parse(p.getText());
return Instant.from(fmt.parse(date.toString()));
}
}
I want to save internally with time component as start of day. The API should be taking in and outputting by only date
I tried converting this to Instant. Can someone assist how do I do this(store in db as date+start_of_day) and expose/consume as yyyy-MM-dd?
TL;DR
return date.atStartOfDay(ZoneOffset.UTC).toInstant();
(Not tested, please forgive any typos.)
Details
You are really trying to fit a square peg into a round hole. An Instant is a point in time, and at that point in time, it is two or three different dates in different time zones. Settling for UTC helps, then we can get it to work. It still isn’t ideal. The standard solution (hack) to the discrepancy is to represent the date as the start of the day in some time zone, and UTC is an excellent choice.
Why your code didn’t work
Parsing the date (after formatting it) in your deserializer gives the date and the UTC offset, but no time of day. We need all three to establish an unambiguous point in time. Instead in my code I explicitly specify the start of the day.
Related
I want to parse LocalDateTime 2021-11-24T15:11:38.395 to LocalDateTime 2021-11-24T15:11:38.39. But LocalDateTime.parse() always adds zero in the end, ignoring my pattern.
public class DateTimeFormatUtils {
private static final String ISO_DATE_TIME = "yyyy-MM-dd'T'HH:mm:ss.SS";
public static LocalDateTime formatToISO(final LocalDateTime localDateTime) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(ISO_DATE_TIME);
return LocalDateTime.parse(formatter.format(localDateTime), formatter);
}
public static void main(String[] args) {
LocalDateTime now = LocalDateTime.now();
System.out.println(now);
System.out.println(formatToISO(now));
}
}
Output:
2021-11-30T11:48:28.449195200
2021-11-30T11:48:28.440
Is there way to deal with this problem?
Note that the strings "2021-11-24T15:11:38.39" and "2021-11-24T15:11:38.390" represent the same LocalDateTime. Technically, you've already got your expected output!
Since you say that the output is not what you expect, you are actually expecting a String as the output, since "2021-11-24T15:11:38.39" and "2021-11-24T15:11:38.390" are different strings. formatToISO should return a string - you should not parse the formatted date back to a LocalDateTime:
public static String formatToISO(final LocalDateTime localDateTime) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(ISO_DATE_TIME);
return formatter.format(localDateTime);
}
This is similar to the common mistake of beginners printing out doubles and expecting the specific format they have used to assign to the variable to come out.
double d = 5;
System.out.println(d); // expected 5, actual 5.0
LocalDateTime, just like double, doesn't store anything about how it should formatted. It just stores a value, and the same value will be formatted the same way.
Java fraction-of-seconds is always return 3 digits.
Work around is, first convert LocalDateTime to String, then remove last character of the string.
Off course, please validate null checks.
private static String removeLastDigit(String localDateTime) {
return localDateTime.substring(0, localDateTime.length()-1);
}
I've searched the Jackson docs, but can't find any good documentation for the pattern of #JsonFormat for floating point numbers.
Given a field
#JsonProperty("Cost")
private Double cost;
How can I get Jackson to format it as fixed point number with four digits precision in decimal format with #JsonFormat?
PS: I know one should not use floats for money. Spare us the discussion, please.
You would need to create a custom Serializer for that. Something like
#JsonProperty("amountOfMoney")
#JsonSerialize(using = MySerializer.class)
private Double cost;
public class MySerializerextends JsonSerializer<Double> {
#Override
public void serialize(Double value, JsonGenerator generator, SerializerProvider provider) throws IOException,
JsonProcessingException {
double roundedValue = value*10000;
roundedValue = Math.round(roundedValue );
roundedValue = roundedValue /10000;
generator.writeNumber(roundedValue );
}
}
You can see about the class here
https://fasterxml.github.io/jackson-databind/javadoc/2.3.0/com/fasterxml/jackson/databind/JsonSerializer.html
The rounding part might not be the best. You can do it as you prefer ;) Using decimal format can work too. If you use writeNumber it will print the value as a number in the result Json. That's why I changed my answer from writeString and using decimal format.
You should be able to use pattern of #JsonFormat for that if the implementation allows it.
Datatype-specific additional piece of configuration that may be used
to further refine formatting aspects. This may, for example, determine
low-level format String used for Date serialization; however, exact
use is determined by specific JsonSerializer
But with jackson I believe it works only for dates.
You can specify your own formatter in custom serializer class.
formatter = new DecimalFormat();
formatter.setMaximumFractionDigits(2);
formatter.setMinimumFractionDigits(2);
formatter.setGroupingUsed(false);
DecimalFormatSymbols sym = DecimalFormatSymbols.getInstance();
sym.setDecimalSeparator('.');
formatter.setDecimalFormatSymbols(sym);
Then, in actual serialize method:
final String output = formatter.format(value);
jsonGenerator.writeNumber(output);
Building on #Veselin's answers I'm using
public class DoubleDecimalSerializerWithSixDigitPrecisionAndDotSeparator
extends JsonSerializer<Double> {
#Override
public void serialize(Double value, JsonGenerator generator, SerializerProvider serializers)
throws IOException {
generator.writeNumber(String.format(Locale.US, "%.6f", value));
}
}
The use case is the generation of CSVs in Germany, so I don't care for JSON formatting and want a "." as a decimal separator.
I am developing a multilingual Struts2 application, and I have quite a few actions which are dealing with Calendar properties. The default type conversion works most of the time, however in some locales I would like to change the default format used.
Specifically I would like to have the dates in English locale to follow the yyyy-MM-dd format. However, this does not work (strangely yyyy-MM-dd HH:mm works fine, but in this case I do not want to have a time part), as Struts2 expect dates in English locale to look different.
So, I would like to change the expected format of the conversion. I am looking for a sane solution for this. The options I have already tried:
A) Own StrutsTypeConverter. This should work, but I could not inject the format specified in the package.properties file into it.
B) Changing the getter/setter pair, to use String instead - works, but this is not a sane solution.
How to fix the solution A? Or is there an alternative approach? Of course, if this can be done entirely in configuration, that would be the best.
Okay, I found a solution for my problem at hand, still, I think this could done in a saner way. Anyway, I am posting my own type converter:
public class DateConverter extends StrutsTypeConverter {
private DateFormat dateFormat;
{
ActionContext ctx = ActionContext.getContext();
ActionSupport action = (ActionSupport) ctx.getActionInvocation().getAction();
String formatString = action.getText("dateformat.ui");
dateFormat = new SimpleDateFormat(formatString);
}
public Object convertFromString(Map context, String[] values, Class toClass) {
String input = values[0];
try {
Date date = dateFormat.parse(input);
Calendar cal = Calendar.getInstance();
cal.setTime(date);
return cal;
} catch (ParseException e) {
return null;
}
}
public String convertToString(Map context, Object object) {
Calendar cal = (Calendar) object;
return dateFormat.format(cal.getTime());
}
}
I removed the non-essential parts of the code, but this is a working solution.
Why does this code return 0001-02-05?
public static String getNowDate() throws ParseException
{
return Myformat(toFormattedDateString(Calendar.getInstance()));
}
I changed the code to:
public static String getNowDate() throws ParseException
{
Calendar temp=Calendar.getInstance();
return temp.YEAR+"-"+temp.MONTH+"-"+temp.DAY_OF_MONTH;
}
And now it returns 1-2-5.
Please, help me get the actual date. all i need is the Sdk date.
Calendar.YEAR, Calendar.MONTH, Calendar.DAY_OF_MONTH are int constants (just look up in the API doc)...
So, as #Alex posted, to create a formatted String out of a Calendar instance, you should use SimpleDateFormat.
If however you need the numeric representations of specific fields, use the get(int) function:
int year = temp.get(Calendar.YEAR);
int month = temp.get(Calendar.MONTH);
int dayOfMonth = temp.get(Calendar.DAY_OF_MONTH);
WARNING! Month starts from 0!!! I've made some mistakes because of this!
Use SimpleDateFormat
new SimpleDateFormat("yyyy-MM-dd").format(Calendar.getInstance().getTime());
You are using constants to be used with the Calendar.get() method.
Why not use SimpleDateFormat?
public static String getNowDate() {
return new SimpleDateFormat("yyyy-MM-dd").format(new Date());
}
You are doing it wrong. Change to:
return temp.get(Calendar.YEAR)+"-"+ (temp.get(Calendar.MONTH)+1) +"-"+temp.get(Calendar.DAY_OF_MONTH);
Also, you may want to look into Date:
Date dt = new Date();
//this will get current date and time, guaranteed to nearest millisecond
System.out.println(dt.toString());
//you can format it as follows in your required format
System.out.println(new SimpleDateFormat("yyyy-MM-dd").format(dt));
i want to use standard class called DateFormat which has subclass SimpleDateFormat TO write a method called convert which returns a String in the form dd.mm.yy: when passed a GregorianCalendar with a specific date
public String convert (Calendar gc) { ... }
For example, when myGC is a GregorianCalendar variable representing the 25th of December 2006, String s = convert(myGC); should set s to the string "25.12.06".
and i'm having trouble to write a convert method on this
public String convert(Calendar c) {
return new SimpleDateFormat("dd.MM.yy").format(c.getTime());
}
Eventually you'll want to store that SimpleDateFormat as a member (for example) if performance becomes a concern.
Why not just use a pattern like this one "dd.MM.yy" in your SimpleDateFormat ?
DateFormat dateFormatter = new SimpleDateFormat("dd.MM.yy");
String myDate = dateFormatter.format(cal.getTime());