I'm trying to take two strings and make it into a Date object. I'm having trouble trying to work out what formats I need to use.
The first string is a date and is in the format of : 5th Jan
The second string is a time and is in the format of : 8:15
The main issue is what the format would be for the 5th
Since your date string, 5th Jan doesn't have a year, you will have to use some default year e.g. the current year, which you can get from LocalDate.now(). You can put defaults using DateTimeFormatterBuilder#parseDefaulting. Additionally, you can also make the parser
case-insensitive by using DateTimeFormatterBuilder#parseCaseInsensitive.
In order to parse a date string, 5th Jan, you can use the pattern, d'th' MMM. However, in order to deal with other suffixes like in 3rd, 1st etc., you should use the pattern, d['th']['st']['rd']['nd'] MMM where the patterns inside the square bracket are optional.
In order to parse a time string like 8:15, you can use the pattern, H:m.
Demo:
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.util.Locale;
public class Main {
public static void main(String[] args) {
LocalDate date = LocalDate.now();
DateTimeFormatter dtfForDate = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.parseDefaulting(ChronoField.YEAR, date.getYear())
.appendPattern("d['th']['st']['rd']['nd'] MMM")
.toFormatter(Locale.ENGLISH);
DateTimeFormatter dtfForTime = DateTimeFormatter.ofPattern("H:m", Locale.ENGLISH);
String strDate = "5th Jan";
String strTime = "8:15";
LocalDateTime ldt = LocalDate.parse(strDate, dtfForDate)
.atTime(LocalTime.parse(strTime, dtfForTime));
// Print the default string value i.e. the value returned by ldt.toString()
System.out.println(ldt);
// The default format omits seconds and fraction of second if they are 0. In
// order to retain them in the output string, you can use DateTimeFormatter
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss");
String formatted = dtf.format(ldt);
System.out.println(formatted);
}
}
Output:
2021-01-05T08:15
2021-01-05T08:15:00
Learn about the modern date-time API from Trail: Date Time.
Related
I get a timestamp in the format "20210908094049.884Z". This is the last modify timestamp from an LDAP object. I use Spring Boot Ldap. I have no clue how to parse this String in a Datetime like dd.MM.yyyy HH:mm.
Can anyone help me please?
Here is an example:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Main {
public static void main(String[] args) {
// Creating new simple date formatter with the format you've given
SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss.SSS");
// Defining the input date
String inputDate = "20210908094049.884Z";
// Parsing the date, catching the parse exception if date is malformatted
Date date = null;
try {
// Date ends on a Z, we remove this Z (Z is for timezone UTC +0:00)
date = format.parse(inputDate.replace("Z", ""));
System.out.println(date);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
Giving following output:
Wed Sep 08 09:40:49 CEST 2021
Edit:
Here another even better solution from Ole V.V.
import java.time.Instant;
import java.time.format.DateTimeFormatter;
public class Main {
public static void main(String[] args) {
Instant instant = DateTimeFormatter
// Defining pattern to parse
.ofPattern("yyyyMMddHHmmss.SSSXX")
// Defining input to parse with pattern
.parse("20210908094049.884Z", Instant::from);
System.out.println(instant);
}
}
Output is an instant with value:
2021-09-08T09:40:49.884Z
java.time
I recommend that you use java.time, the modern Java date and time API, for your work with timestamps.
The LDAP timestamp format has a number of allowed variations (see the link at the bottom). The following formatter takes many of them into account, not all of them.
private static final DateTimeFormatter LDAP_PARSER = new DateTimeFormatterBuilder()
.appendPattern("uuuuMMddHHmmss")
.optionalStart()
.appendPattern("[.][,]")
.appendFraction(ChronoField.NANO_OF_SECOND, 1, 9, false)
.optionalEnd()
.appendPattern("[XX][X]")
.toFormatter(Locale.ROOT);
With this formatter we may for example parse your string into an OffsetDateTime:
String ldapTimestampString = "20210908094049.884Z";
OffsetDateTime timestamp = OffsetDateTime.parse(ldapTimestampString, LDAP_PARSER);
System.out.println(timestamp);
Output is:
2021-09-08T09:40:49.884Z
Formatting
To convert the timestamp to a string containing date and time you need to decide on a time zone for that since it is never the same date nor the same time in all time zones.
Use this formatter:
private static final DateTimeFormatter FORMATTER
= DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm");
Then do:
ZoneId zone = ZoneId.of("Pacific/Tarawa");
ZonedDateTime dateTime = timestamp.atZoneSameInstant(zone);
String formattedDateTime = dateTime.format(FORMATTER);
System.out.println(formattedDateTime);
08.09.2021 21:40
Links
Oracle tutorial: Date Time explaining how to use java.time.
GeneralizedTime on ldapwiki defining the LDAP timestamp format.
I have following timestamp string "2021010112:12:12.10:00" and I want to convert it to java.time.Instant.
The issue in parsing string through DateTimeFormatter is due to absence of time zone sign before last 4 digits. Logically it is yyyyMMddHH:mm:ss. and 10:00 is time zone offset e.g UTC+10:00, but issue is it does not have sign.
How can I parse this string to Instant object?
Not very elegant, but you could split the input by a dot. That would separate the datetime part from the offset and you can concatenate the desired (and required) sign with the value.
This requires you to know which sign to apply! The code cannot guess it...
Maybe write a method that takes this input String and a sign to be applied as arguments.
Since it seems not possible to parse an unsigned String representation of an offset, you would need something like the following:
public static void main(String[] args) {
String timestamp = "2021010112:12:12.10:00";
// provide a formatter that parses the datetime (the part before the dot)
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuuMMddHH:mm:ss");
// split the timestamp String by the dot to separate datetime from offset
String[] split = timestamp.split("\\.");
// parse the datetime part using the formatter defined above
LocalDateTime ldt = LocalDateTime.parse(split[0], dtf);
// and build up an offset using offset part adding a plus sign
ZoneOffset zoneOffset = ZoneOffset.of("+" + split[1]);
// then create an OffsetDateTime from the LocalDateTime and the ZoneOffset
OffsetDateTime result = OffsetDateTime.of(ldt, zoneOffset);
// finally get an Instant from it
Instant instant = result.toInstant(); // <--- INSTANT HERE
// and print the values
System.out.println(result + " = " + instant.toEpochMilli());
}
This outputs
2021-01-01T12:12:12+10:00 = 1609467132000
ParsePosition
There are two good answers already. Here’s my suggestion.
String timestampString = "2021010112:12:12.10:00";
ParsePosition position = new ParsePosition(0);
TemporalAccessor parsed = PARSER.parse(timestampString, position);
LocalDateTime dateTime = LocalDateTime.from(parsed);
String offsetString = timestampString.substring(position.getIndex());
if (Character.isDigit(offsetString.charAt(0))) { // no sign
offsetString = "+" + offsetString;
}
ZoneOffset offset = ZoneOffset.of(offsetString);
Instant timestamp = dateTime.atOffset(offset).toInstant();
System.out.println(timestamp);
Output:
2021-01-01T02:12:12Z
The downside is the use of the TemporalAccessor interface, a low-level interface that we should not normally use in application code. The upsides include that the code accepts strings with and without sign before the offset and we don’t need any split operation or other application of regular expressions. If the UTC offset is negative, the sign must be there, or we can’t tell. Let’s also try this:
String timestampString = "2021010112:12:12.-10:00";
2021-01-01T22:12:12Z
I believe that the ParsePosition class is the only class from the old java.text package that is reused in java.time, which I personally find curious.
The answer by deHaar is correct. This answer shows an easier way of solving this problem.
You can use the regex, (\.)(\d{1,2}:\d{1,2}) to replace the . (group#1 in the regex) before the timezone offset part (group#2 in the regex) with a +.
Demo:
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class Main {
public static void main(String[] args) {
String strDateTime = "2021010112:12:12.10:00";
strDateTime = strDateTime.replaceFirst("(\\.)(\\d{1,2}:\\d{1,2})", "+$2");
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuuMMddHH:mm:ssXXX", Locale.ENGLISH);
Instant instant = OffsetDateTime.parse(strDateTime, dtf).toInstant();
System.out.println(instant);
}
}
Output:
2021-01-01T02:12:12Z
ONLINE DEMO
Alternatively, you can use the regex, \.(\d{1,2}:\d{1,2}) and prepend group#1 with a + sign. Note that the DateTimeFormatter needs to be adjusted accordingly.
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class Main {
public static void main(String[] args) {
String strDateTime = "2021010112:12:12.10:00";
strDateTime = strDateTime.replaceFirst("\\.(\\d{1,2}:\\d{1,2})", ".+$1");
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuuMMddHH:mm:ss.XXX", Locale.ENGLISH);
Instant instant = OffsetDateTime.parse(strDateTime, dtf).toInstant();
System.out.println(instant);
}
}
ONLINE DEMO
The benefits of using this alternative solution is as described in the following comment by Ole V.V.:
Just speculating, maybe a minus sign would be present in case of a
negative UTC offset. If so, maybe use
strDateTime.replaceFirst("\\.(\\d{1,2}:\\d{1,2})", ".+$1") to obtain
2021010112:12:12.+10:00 in the positive offset case, after which both
positive and negative offset (and zero) could be parsed.
I have a json formatted response Date to the controller that is like this:
#JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = "America/Chicago")
private Date date;
So when I make a post call, it would look like:
"date": "2021-08-20 14:17:43"
So the response string would look something like this {"date":"2021-05-21 14:23:44"}. In JUnit, I am manually creating a response object and setting the Date object so I can then use Gson to turn it into a string and then assert that the two are equal.
I am trying to match this in my SpringMVC JUnit test case by trying to do:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("America/Chicago"));
String formattedDate = sdf.format(new Date());
LocalDate localDate = LocalDate.parse(formattedDate);
Date date = Date.from(localDate.atStartOfDay(ZoneId.of("America/Chicago")).toInstant());
But it is having an error parsing it because of the space between yyyy-MM-dd and HH:mm:ss:
java.time.format.DateTimeParseException: Text '2021-08-20 14:23:44' could not be parsed, unparsed text found at index 10
I think I may be doing this inefficiently, so I was wondering if there was a more simple way to make a Date object that would match the format of #JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = "America/Chicago")
I'm trying to match the response body so it passes via mockito.
Do not mix the modern and the legacy Date-Time API
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class Main {
public static void main(String[] args) {
String strDate = "2021-08-20 14:17:43";
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("u-M-d H:m:s", Locale.ENGLISH);
LocalDateTime ldt = LocalDateTime.parse(strDate, dtf);
System.out.println(ldt);
// Get the required Instant
ZonedDateTime zdtUtc = ldt.atZone(ZoneOffset.UTC);
ZonedDateTime zdtChicago = zdtUtc.withZoneSameInstant(ZoneId.of("America/Chicago"));
Instant instant = zdtChicago.toInstant();
System.out.println(instant);
}
}
Output:
2021-08-20T14:17:43
2021-08-20T14:17:43Z
ONLINE DEMO
java.time
The java.util Date-Time API and their formatting API, SimpleDateFormat are outdated and error-prone. It is recommended to stop using them completely and switch to the modern Date-Time API*. However, for any reason, if you need to convert this object of Instant to an object of java.util.Date, you can do so as follows:
Date date = Date.from(instant);
Learn more about the modern Date-Time API from Trail: Date Time.
* For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.
you could be missing date deserializer
#JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")
#JsonDeserialize(using = LocalDateTimeDeserializer.class)
Posting this to only try and satisfy what you're trying to achieve. But you should follow #Arvind's answer:
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.TimeZone;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("America/Chicago"));
String formattedDate = sdf.format(new Date());
// Updated the lines below
LocalDateTime localDateTime = LocalDateTime.parse(formattedDate, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
Date date = Date.from(localDateTime.atZone(ZoneId.of("America/Chicago")).toInstant());
Best is if you can skip the Date class completely and in your response use Instant or ZonedDateTime from java.time, the modern Java date and time API.
If you cannot avoid using the outdated Date class
… I was wondering if there was a more simple way to make a Date object
that would match the format of #JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = "America/Chicago")
Essential edit: It depends very much on what you mean by match the format. A Date can neither have a format nor a time zone. The string in your JSON has got the format mentioned. The Date has not since this would no be possible. The time zone, America/Chicago, is not present neither in JSON nor in the Date. It is only used for converting between the two. Two Date objects are equal if they denote the same point in time, there is nothing more to it. When you ask about formatting the Date to match the #JsonFormat, this necessarily means formatting into a string.
To convert a string like 2021-08-20 14:23:44 into an old-fashioned Date object I would first define the format and time zone statically:
private static final DateTimeFormatter FORMATTER
= DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss", Locale.ROOT);
private static final ZoneId ZONE = ZoneId.of("America/Chicago");
And then do:
String responseDateString = "2021-08-20 14:23:44";
Instant inst = LocalDateTime.parse(responseDateString, FORMATTER)
.atZone(ZONE)
.toInstant();
Date oldfashionedDate = Date.from(inst);
System.out.println(oldfashionedDate);
Output in my time zone is:
Fri Aug 20 21:23:44 CEST 2021
If I set my time zone to America/Chicago before running, it’s easier to see that the result is correct:
Fri Aug 20 14:23:44 CDT 2021
What went wrong in your code?
First you are correct that formatting a Date into a string only to parse it back is over-complicating things. Second you noticed that your exception came from this line:
LocalDate localDate = LocalDate.parse(formattedDate);
A LocalDate is a date without time of day. So its one-arg parse method expects only 2021-08-20 in the string, nothing more. It was complaining about the space, not because it was a space but just because there were more characters after the expected ones at all.
I am getting an Assertion error " Body Content Expected child but was null when asserting the andExpect XML. If I input as as a String "2020-10-01-5:00" it works fine but if I concatenate the date into a string like:
LocalDate startDate = LocalDate.now().minusDays(90);
String startDateLine = "<start-date>" + startDate + "-5:00</start-date>\n";
It throws the AssertionError. I have verified that the XML is correct before the call so I am unsure what about getting the date and converting to a string causes the test to fail.
Update
Do not add the offset string to the LocalDate string in order to convert it into an OffsetDateTime string. Shown below is the idiomatic way to convert a LocalDate to OffsetDateTime
LocalDate.of(2020, 10, 1)
.atStartOfDay()
.atOffset(ZoneOffset.of("-05:00"));
Demo:
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
public class Main {
public static void main(String[] args) {
LocalDate date = LocalDate.of(2020, 10, 1);
LocalDateTime ldt = date.atStartOfDay();
OffsetDateTime odt = ldt.atOffset(ZoneOffset.of("-05:00"));
System.out.println(odt);
}
}
Output:
2020-10-01T00:00-05:00
ONLINE DEMO
You can get the String representation of an OffsetDateTime using the function OffsetDateTime#toString e.g.
String strOdt = odt.toString();
Original answer
Change your input to have the timezone offset in the format HH:mm e.g. -05:00 so that it conforms to ISO 8601 standards.
Use DateTimeFormatterBuilder with .parseDefaulting(ChronoField.HOUR_OF_DAY, 0) to default the hour-of-day to 0.
Parse the given string to OffsetDateTime as it has timezone offset and OffsetDateTime is the best fit to represent Date-Time with timezone offset.
Demo:
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.util.Locale;
public class Main {
public static void main(String[] args) {
DateTimeFormatter dtf =new DateTimeFormatterBuilder()
.appendPattern("u-M-d[H:m:s]XXX")
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.toFormatter(Locale.ENGLISH);
OffsetDateTime odt = OffsetDateTime.parse("2020-10-01-05:00", dtf);
System.out.println(odt);
}
}
Output:
2020-10-01T00:00-05:00
ONLINE DEMO
Notice the optional pattern inside a square bracket.
Learn more about the modern Date-Time API* from Trail: Date Time.
So am parsing json and sometimes the string I receive which contains the date comes full(dd-mm-yyyy) , and sometimes I only receive yyyy which I dont seem to able to convert to date ,so if anyone can help
As per your business requirement, you can default the month and the day-of-month to the required value using DateTimeFormatterBuilder#parseDefaulting e.g. in the following code, I have defaulted the month and the day-of-month to that of today:
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.util.Locale;
class Main {
public static void main(String[] args) {
// Test
System.out.println(parseToDate("10-10-2020"));
System.out.println(parseToDate("2020"));
}
static LocalDate parseToDate(String str) {
LocalDate today = LocalDate.now();
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendPattern("[dd-MM-uuuu][uuuu]")
.parseDefaulting(ChronoField.MONTH_OF_YEAR, today.getMonthValue())
.parseDefaulting(ChronoField.DAY_OF_MONTH, today.getDayOfMonth())
.toFormatter(Locale.ENGLISH);
return LocalDate.parse(str, formatter);
}
}
Output:
2020-10-10
2020-12-12
Note: The pattern, [dd-MM-uuuu][uuuu] has two optional patterns, dd-MM-uuuu and uuuu.