DateTime parser format in java 8 - java

I am trying to parse a datetimeStamp as below but I am not able to find the correct parser for it. Can someone advise how can I parse such dates:
import java.time.format.DateTimeFormatter;
import java.time.LocalDateTime;
public class HelloWorld{
public static void main(String []args){
int DAYS = 30;
String date ="2021-04-23T12:09:56.123-07:00";
DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter
.ofPattern("YYYY-MM-DD'T'hh:mm:ss.SSS±hh:mm");
LocalDateTime localDateTime = LocalDateTime.parse(date, DATE_TIME_FORMATTER);
System.out.println("Local Date" + localDateTime);
LocalDateTime now = LocalDateTime.now();
if (localDateTime.isBefore(now) && localDateTime.isAfter(now.plusDays(DAYS)))
{
System.out.println("Date is incorrect");
}else{
System.out.println("Success");
}
}
}
Below is also a bit of additional detail:
Must be expressed in ISO 8601 extended format as one of the following - YYYY-MM-DDThh:mm:ss[ .sss ]Z, YYYY-MM-DDThh:mm:ss[ .sss ]±hh:mm . Where [ .sss ] is optional and can be 1 to 3 digits. Must be a value no more than 30 days in the future. Mastercard recommends using a value of (Current Time + 30 minutes). Max length - 29. Type - String.
example: "2015-07-04T12:09:56.123-07:00"
Please help me with the correct formatter for the day specified.

You have 3 separate problems here:
±hh:mm - as per the docs of DateTimeFormatter (remember, reading is fundamental! Always read the javadoc before asking SO questions!), ± isn't a thing. hh and mm are things, but not 'offset hours'. The 'pattern' that represents tz offsets is a single letter; you're most likely looking for Z. But that doesn't ordinarily include the :.
LocalDateTime cannot represent offsets and doesn't know about timezones. If you want that, you're looking for either ZonedDateTime or most likely OffsetDateTime.
Your pattern doesn't include a locale which means the output of it, and what it can parse, depends on the locale of the computer you run this code on, which sounds bad. Always use .ofPattern("someString", Locale.ENGLISH) (or some other locale) unless you are really really sure you want the app to depend on the configuration of the server you run it on, which should be very rare.

Get rid of this wrong pattern (and the DateTimeFormatter) at all and use an OffsetDateTime instead of a LocalDateTime which isn't able to store information about an offset.
This is a conversion of your code (slightly adjusted and commented):
public static void main(String[] args) {
String date = "2021-04-23T12:09:56.123-07:00";
/*
* no DateTimeFormatter needed due to default format of the String
*/
// use an OffsetDateTime to parse a String with an offset
OffsetDateTime odtParsed = OffsetDateTime.parse(date);
// print it
System.out.println("Parsed OffsetDateTime is " + odtParsed);
// take the current instant as an OffsetDateTime at an offset of -7 hours
OffsetDateTime now = OffsetDateTime.now(ZoneOffset.ofHours(-7));
// run your check and get a success
if (odtParsed.isBefore(now) && odtParsed.isAfter(now.plusDays(DAYS))) {
System.out.println("Date is incorrect");
} else {
System.out.println("Success");
}
}
It prints
Parsed OffsetDateTime is 2021-04-23T12:09:56.123-07:00
Success
Hint: This will fail for differently formatted Strings. If the formattings only differ a little, you might be able to create a DateTimeFormatter (via a DateTimeFormatterBuilder) that can handle optional parts and so on.
Edit
If only the millis of day are optional and the format of the offset always consists of a plus or minus followed by two digits for hours, a colon and two digits for minutes, the following example might be showing a sufficient way of making it generic:
public static void main(String[] args) {
String date = "2021-04-23T12:09:56.123-07:00";
String date1 = "2021-04-23T12:09:56.123+02:00";
String date2 = "2021-04-23T12:09:56.123+00:00";
String date3 = "2021-04-23T12:09:56-07:00";
String date4 = "2021-04-23T12:09:56+02:00";
String date5 = "2021-04-23T12:09:56+00:00";
// DateTimeFormatter using optional millis of second
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss[.SSS]xxx");
// use an OffsetDateTime to parse a String with an offset
OffsetDateTime odtParsed = OffsetDateTime.parse(date, dtf);
OffsetDateTime odtParsed1 = OffsetDateTime.parse(date1, dtf);
OffsetDateTime odtParsed2 = OffsetDateTime.parse(date2, dtf);
OffsetDateTime odtParsed3 = OffsetDateTime.parse(date3, dtf);
OffsetDateTime odtParsed4 = OffsetDateTime.parse(date4, dtf);
OffsetDateTime odtParsed5 = OffsetDateTime.parse(date5, dtf);
// print it
System.out.println("Parsed OffsetDateTime is " + odtParsed);
System.out.println("Parsed OffsetDateTime is " + odtParsed1);
System.out.println("Parsed OffsetDateTime is " + odtParsed2);
System.out.println("Parsed OffsetDateTime is " + odtParsed3);
System.out.println("Parsed OffsetDateTime is " + odtParsed4);
System.out.println("Parsed OffsetDateTime is " + odtParsed5);
}
Output:
Parsed OffsetDateTime is 2021-04-23T12:09:56.123-07:00
Parsed OffsetDateTime is 2021-04-23T12:09:56.123+02:00
Parsed OffsetDateTime is 2021-04-23T12:09:56.123Z
Parsed OffsetDateTime is 2021-04-23T12:09:56-07:00
Parsed OffsetDateTime is 2021-04-23T12:09:56+02:00
Parsed OffsetDateTime is 2021-04-23T12:09:56Z
and if you don't want an offset of zero hours and zero minutes to be output as a Z, then use the DateTimeFormatter for output like this:
OffsetDateTime odtParsed5 = OffsetDateTime.parse(date5, dtf);
which makes the output look like this:
Parsed OffsetDateTime is 2021-04-23T12:09:56.000+00:00
Where the millis of day are printed even if they are all zero.
Another Edit:
To check if the date is in the range 30 days in the past to 30 days in the future it might be good to use the date parts only:
public static void main(String[] args) {
// the first part is already known from the examples above...
String date = "2021-04-23T12:09:56.123-07:00";
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss[.SSS]xxx");
OffsetDateTime odtParsed = OffsetDateTime.parse(date, dtf);
// get the current instant as an OffsetDateTime (in UTC == +00:00)
OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC);
/*
* might be smart to keep time of day out of the calculation, so...
*/
// get the date only out of the OffsetDateTime
LocalDate today = now.toLocalDate();
// create one for 30 days before
LocalDate thirtyDaysBeforeToday = today.minusDays(30);
// and one 30 days in the future
LocalDate thirtyDaysInTheFuture = today.plusDays(30);
// finally extract the date part from the parsed date time
LocalDate d = odtParsed.toLocalDate();
// make your checks using the LocalDates, maybe separate the two cases of an invalid date
if (d.isBefore(thirtyDaysBeforeToday)) {
System.err.println("Invalid date: too far in the past");
} else if (d.isAfter(thirtyDaysInTheFuture)) {
System.err.println("Invalid date: too far in the future");
} else {
System.out.println("Valid date");
}
}
Try some different valid and invalid dates...

Your documentation uses ± to mean either + or -, and its placeholders aren't the Java ones. Just use DateTimeFormatter.ISO_OFFSET_DATE_TIME.

Related

Calculate Number of Days between Given time in ISO format and Current time

I have to find out number of days between a given Time and current time. Given time is in ISO format and one example is "2021-01-14 16:23:46.217-06:00".
I have tried it using "java.text.SimpleDateFormat" but it's not giving me accurate results.
In Below Given date, for today's time I am getting output as "633" Days which isn't correct. somehow after parsing it is taking date as "21 december 2020" which isn't correct
String TIMESTAMP_FORMAT = "YYYY-MM-DD hh:mm:ss.s-hh:mm" ;
int noOfDays = Utility.getTimeDifferenceInDays("2021-01-14 16:23:46.217-06:00", TIMESTAMP_FORMAT);
public static int getTimeDifferenceInDays(String timestamp, String TIMESTAMP_FORMAT) {
DateFormat df = new SimpleDateFormat(TIMESTAMP_FORMAT);
try {
Date date = df.parse(timestamp);
long timeDifference = (System.currentTimeMillis() - date.getTime());
return (int) (timeDifference / (1000*60*60*24));
} catch (ParseException e) {
e.printStackTrace();
}
return 0;
}
Looking for a better solution which gives me correct number of days. Thanks
Use java.time API
Classes Date and SimpleDateFormat are legacy.
Since Java 8 (which was released 10 years ago) we have a new Time API, represented by classes from the java.time package.
To parse and format the data, you can use DateTimeFormatter. An instance of DateTimeFormatter can be obtained via static method ofPattern(), or using DateTimeFormatterBuilder.
ofPattern():
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSXXX");
DateTimeFormatterBuilder:
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendPattern("yyyy-MM-dd HH:mm:ss.") // main date-time part
.appendValue(ChronoField.MILLI_OF_SECOND, 3) // fraction part of second
.appendOffset("+HH:MM", "+00:00") // can be substituted with appendPattern("zzz") or appendPattern("XXX")
.toFormatter();
The string "2021-01-14 16:23:46.217-06:00", which you've provided as an example, contains date-time information and UTC offset. Such data can be represented by OffsetDateTime.
To get the number of days between two temporal objects, you can use ChronoUnit.between() as #MC Emperor has mentioned in the comments.
That's how the whole code might look like:
String toParse = "2021-01-14 16:23:46.217-06:00";
OffsetDateTime dateTime = OffsetDateTime.parse(toParse, formatter);
System.out.println("parsed date-time: " + dateTime);
Instant now = Instant.now();
long days = ChronoUnit.DAYS.between(dateTime.toInstant(), now);
System.out.println("days: " + days);
Output:
parsed date-time: 2021-01-14T16:23:46.217-06:00
days: 615
Note that since in this case you need only difference in days between the current date instead of OffsetDateTime you can use LocalDateTime, UTC offset would be ignored while parsing a string. If you decide to do so, then the second argument passed to ChronoUnit.between() should be also of type LocalDateTime.

How to Convert a 2021-09-29T17:04:31.0000 +05:30 to 2021-09-29 17:04:31.0000000 Asia/Calcutta In Java?

How to Convert a 2021-09-29T17:04:31.0000 +05:30 to 2021-09-29 17:04:31.0000000 Asia/Calcutta In Java?
and additional question is here-
I tried many ways to get the timezone(Asia/Calcutta) from offset(+05:30) but unable to get.
Can someone please help?
Since you cannot derive a zone from an offset (reliably), you may want to use a workaround / different approach.
Here's an example that parses the input String to an OffsetDateTime using a suitable DateTimeFormatter and then creates the desired ZoneId which it applies to the OffsetDateTime in order to create a ZonedDateTime.
public static void main(String[] args) {
// your example input
String offsetTime = "2021-09-29T17:04:31.0000 +05:30";
// define a formatter that parses a String of this format
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSS xxx");
// then parse it to an object that knows date, time and offset
OffsetDateTime odt = OffsetDateTime.parse(offsetTime, dtf);
// print the result
System.out.println(odt);
// now define the desired zone
ZoneId asiaCalcutta = ZoneId.of("Asia/Calcutta");
// and use the offset-aware object to apply the zone
ZonedDateTime zdt = odt.atZoneSameInstant(asiaCalcutta);
// print it
System.out.println(zdt);
}
This outputs
2021-09-29T17:04:31+05:30
2021-09-29T17:04:31+05:30[Asia/Calcutta]
Please note: This will only keep the same offset if the offset and the zone actually match. I don't know your concrete requirements, so only you can know if this approach is appropriate in your situation or not.
If you want to find out time zones that currently use a specific offset and then make your code decide which is the correct one, you may want to have a look at this question and its answers.
I tried to write this code to cover three possibilities String parameter , or LocalDateTime parameter and ZonedDateTime Parameter :
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
public class SpecialFormater {
// parameter : ZonedDateTime
public static String specialFormaterZonedDateTime(ZonedDateTime zdt) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSS");
ZoneId zid = zdt.getZone();
System.out.println("Your initial format =" + zdt);
String result = "Your expected format =" + zdt.format(formatter) + " " + zid;
return result;
}
// parameter : LocalDateTime
public static String specialFormaterLocalDateTime(LocalDateTime dt) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSS");
ZoneId zid = ZoneId.of("Asia/Calcutta");
System.out.println("Your initial format =" + dt);
ZonedDateTime zdt = ZonedDateTime.of(dt, zid);
System.out.println("Your zoned dateTime default format =" + zdt);
String result = "Your expected format =" + dt.format(formatter) + " " + zid;
return result;
}
// Parameter : String
public static String specialFormaterString(String localDateTime) {
System.out.println("String before formatting =" + localDateTime);
String pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSS xxx";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
LocalDateTime dt = LocalDateTime.parse(localDateTime, formatter);
ZoneId zid = ZoneId.of("Asia/Calcutta");
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSS");
String result = "String after formatting =" + dt.format(formatter2) + " " + zid;
return result;
}
public static void main(String[] args) {
System.out.println("specialFormaterLocalDateTime");
System.out.println(specialFormaterLocalDateTime(LocalDateTime.now()));
System.out.println("***********");
System.out.println();
System.out.println("specialFormaterString");
System.out.println(specialFormaterString("2021-09-29T17:04:31.0000 +05:30"));
System.out.println("**********");
System.out.println();
System.out.println("specialFormaterZonedDateTime");
System.out.println(specialFormaterZonedDateTime(ZonedDateTime.now()));
}
}
Output :
specialFormaterLocalDateTime
Your initial format =2021-10-06T12:35:45.029
Your zoned dateTime default format =2021-10-06T12:35:45.029+05:30[Asia/Calcutta]
Your expected format =2021-10-06 12:35:45.0290000 Asia/Calcutta
***********
specialFormaterString
String before formatting =2021-09-29T17:04:31.0000 +05:30
String after formatting =2021-09-29 17:04:31.0000000 Asia/Calcutta
**********
specialFormaterZonedDateTime
Your initial format =2021-10-06T12:35:45.064+02:00[Europe/Paris]
Your expected format =2021-10-06 12:35:45.0640000 Europe/Paris
How to get just some time zone that matches the offset using java.time
I tend to understand from your comment under Arvind Kumar Avinash’ answer that you have solved it by simply taking the first time zone that has the correct offset (or what you thought was the correct offset). Of course java.time, the modern Java date and time API, can do that. I wanted to show you a way.
String input = "2021-09-29T17:04:31.0000 +05:30";
// Remove space and parse
OffsetDateTime dateTime = OffsetDateTime.parse(input.replace(" ", ""));
ZoneOffset offset = dateTime.getOffset();
// Find and convert to a zone that matches the offset (there will usually be many)
ZonedDateTime zonedDateTime = ZoneId.getAvailableZoneIds()
.stream()
.map(ZoneId::of)
.map(dateTime::atZoneSameInstant)
.filter(zdt -> zdt.getOffset().equals(offset))
.findFirst()
// If all else fails, use the offset as time zone
.orElse(dateTime.atZoneSameInstant(offset));
// Convert to 2021-09-29 17:04:31.0000000 Asia/Calcutta format
String result = zonedDateTime.format(FORMATTER);
System.out.println(result);
I have used this formatter for formatting the output:
private static final DateTimeFormatter FORMATTER
= DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss.SSSSSS VV", Locale.ROOT);
And the output is (on my Java 11):
2021-09-29 17:04:31.000000 Asia/Kolkata
Asia/Kolkata is the present-day name for the time zone formerly known as Asia/Calcutta. Is that close enough?
In my code I have taken the case into account where no suitable time zone exists. Let’s see this with an offset that I don’t think is used in any real time zone:
String input = "2021-10-06T18:54:31.0000 +00:30";
2021-10-06 18:54:31.000000 +00:30
If you didn’t want this, you will at least need to change the line with the call to orElse().

How to format LocalDate to ISO 8601 with T and Z?

I'm trying to generate a random date and time, and convert it to the "yyyy-MM-dd'T'HH:mm:ss'Z'" format.
Here is what I have tried:
public static String generateRandomDateAndTimeInString() {
LocalDate date = LocalDate.now().minus(Period.ofDays((new Random().nextInt(365 * 70))));
System.out.println("date and time :: " + date.toString());
return formatDate(date) ;
}
public static String formatDate(LocalDate date){
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
return dateFormat.format(date);
}
But in the line dateFormat.format(date), it complains with:
java.lang.IllegalArgumentException: Cannot format given Object as a Date
The second problem is that, the output of print does not contain the time:
date :: 1998-12-24
I don't know how to get it to work.
Never format the java.time types using SimpleDateFormat
Using the SimpleDateFormat, you are supposed to format only legacy date-time types e.g. java.util.Date. In order to format the java.time date-time types, you need to use DateTimeFormatter.
Never enclose Z within single quotes
It's a blunder to enclose Z within single quotes in a format. The symbol Z stands for zulu and specifies UTC+00:00. If you enclose it within single quotes, it will simply mean character literal, Z and won't function as UTC+00:00 on parsing.
You do not need to use a formatter explicitly
For this requirement, you do not need to use a formatter explicitly because the OffsetDateTime#toString already returns the string in the format that you need. However, if the number of seconds in an OffsetDateTime object is zero, the same and the subsequent smaller units are truncated by OffsetDateTime#toString. If you need the full format irrespective of the value of seconds, then, of course, you will have to use DateTimeFormatter.
import java.time.LocalDate;
import java.time.Period;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Random;
public class Main {
public static void main(String[] args) {
System.out.println(generateRandomDateAndTimeInString());
}
public static String generateRandomDateAndTimeInString() {
LocalDate date = LocalDate.now().minus(Period.ofDays((new Random().nextInt(365 * 70))));
System.out.println("date and time :: " + date.toString());
return formatDate(date);
}
public static String formatDate(LocalDate date) {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ssX");
// return date.atStartOfDay().atOffset(ZoneOffset.UTC).toString();
return date.atStartOfDay().atOffset(ZoneOffset.UTC).format(dtf);
}
}
A sample run:
date and time :: 1996-09-05
1996-09-05T00:00:00Z
Note that the date-time API of java.util 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.
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.
Learn more about the modern date-time API from Trail: Date Time.
If you still need to use SimpleDateFormat for whatsoever reason:
Convert LocalDate to ZonedDateTime with ZoneOffset.UTC and at the start of the day ➡️ Convert ZonedDateTime to Instant ➡️ Obtain java.util.Date object from Instant.
public static String formatDate(LocalDate date) {
Date utilDate = Date.from(date.atStartOfDay(ZoneOffset.UTC).toInstant());
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
return dateFormat.format(utilDate);
}
If you want to ignore the time part then you can use ZonedDateTime like this:
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
return ZonedDateTime.of(
date,
LocalTime.MIN,
ZoneId.of("Europe/Paris")
).format(dateFormat);
Output example
2013-10-19T00:00:00+0200
Or much better, you can use just toString to get a formatted date as a String with the default format of ZonedDateTime:
return ZonedDateTime.of(
date,
LocalTime.MIN,
ZoneId.of("Europe/Paris")
).toString();
Output
2013-10-19T00:00+02:00[Europe/Paris]
Note
This date are always with 00:00:00 for time part, because we are using LocalTime.MIN
Also, you can change the ZoneId to the expected Zone, this was just an example.
Important
DateFormat and SimpleDateFormat are legacy library, so please don't mix them with the java.time library, in the top you are using LocalDate which mean you are using this java.time library so keep going with it in all your code.
ZoneOffset utc = ZoneOffset.UTC;
LocalDate today = LocalDate.now(utc);
LocalDate seventyYearsAgo = today.minusYears(70);
int totalDays = Math.toIntExact(ChronoUnit.DAYS.between(seventyYearsAgo, today));
LocalDate date = today.minusDays(new Random().nextInt(totalDays));
String dateString = date.atStartOfDay(utc).toString();
System.out.println("date and time :: " + dateString);
Example output:
date and time :: 1983-08-24T00:00Z
Points to note:
Let java.time convert from years to days. It gives more readable and more correct code (a year is not always 365 days).
To have time of day and UTC offset in the string, convert a ZonedDateTime or an OffsetDateTime since such objects hold time of day and offset. A LocalDate does not. It’s a date without time of day and without offset from UTC. The Z you asked for denotes an offset of 0 from UTC.
If you want hours, minutes and seconds in the output too, you can have that by counting seconds rather than days. In this case use OffsetDateTime for the entire operation (or ZonedDateTime if in a time zone different from UTC).
ZoneOffset utc = ZoneOffset.UTC;
OffsetDateTime today = OffsetDateTime.now(utc).truncatedTo(ChronoUnit.SECONDS);
OffsetDateTime seventyYearsAgo = today.minusYears(70);
long totalSeconds = ChronoUnit.SECONDS.between(seventyYearsAgo, today);
OffsetDateTime date = today.minusSeconds(ThreadLocalRandom.current().nextLong(0, totalSeconds));
String dateString = date.toString();
System.out.println("date and time :: " + dateString);
date and time :: 1996-09-21T06:49:56Z
I am using ThreadLocalRandom because it can generate a random long value in a specified interval. Funnily ThreadLocalRandom has a lot of convenient methods that Random hasn’t got.

Parsing and formatting LocalDate with unnecessary time and timezone

Edit :
I opened a bug and it has been confirmed by Oracle. You can follow the resolution here : https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8216414
I'm interfacing with a LDAP repository which store the birthdate of a person with the time and timezone like this :
If the birthdate is "27-12-2018", then the LDAP string is "20181227000000+0000".
I cannot find a way to parse AND format the birthdate using the same pattern.
The following code works well for formatting but not for parsing :
LocalDate date = LocalDate.of(2018, 12, 27);
String pattern = "yyyyMMdd'000000+0000'";
DateTimeFormatter birthdateFormat = DateTimeFormatter.ofPattern(pattern);
// Outputs correctly 20181227000000+0000
date.format(birthdateFormat);
// Throw a DatetimeParseException at index 0
date = LocalDate.parse("20181227000000+0000", birthdateFormat);
And the following code works well for parsing but not for formatting
LocalDate date = LocalDate.of(2018, 12, 27);
String pattern = "yyyyMMddkkmmssxx";
DateTimeFormatter birthdateFormat = DateTimeFormatter.ofPattern(pattern);
// Throws a UnsupportedTemporalTypeException for ClockHourOfDay not supported
// Anyway I would have an unwanted string with non zero hour, minute, second, timezone
date.format(birthdateFormat);
// Parse correctly the date to 27-12-2018
date = LocalDate.parse("20181227000000+0000", birthdateFormat);
Which pattern could satisfy both parsing and formating ?
Am I forced to use 2 different patterns ?
I am asking because the pattern is configured in a property file. I want to configure 1 pattern only in this property file. I would like to externalize the pattern because the LDAP is not part of my project, it is a shared resource and I have no guarantee that the format cannot change.
Since your LDAP string has zoned format ...+0000, I would suggest using ZonedDateTime or OffsetDateTime.
This pattern yyyyMMddHHmmssZZZ would do the trick for both parsing and formatting.
LocalDate date = LocalDate.of(2018, 12, 27);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmssZZZ");
Formatting
First convert your LocalDate to ZonedDateTime/OffsetDateTime:
ZonedDateTime zonedDateTime = date.atStartOfDay(ZoneOffset.UTC);
// or
OffsetDateTime offsetDateTime = date.atStartOfDay().atOffset(ZoneOffset.UTC);
Then format it:
// Both output correctly 20181227000000+0000
System.out.println(zonedDateTime.format(formatter));
// or
System.out.println(offsetDateTime.format(formatter));
Parsing
First parse the ZonedDateTime/OffsetDateTime:
// Both parse correctly
ZonedDateTime zonedDateTime = ZonedDateTime.parse("20181227000000+0000", formatter);
// or
OffsetDateTime offsetDateTime = OffsetDateTime.parse("20181227000000+0000", formatter);
Once you have ZonedDateTime/OffsetDateTime, you can simply retrieve the LocalDate like this:
LocalDate date = LocalDate.from(zonedDateTime);
// or
LocalDate date = LocalDate.from(offsetDateTime);
Update
Both parsing and formatting can be simplified to one-liners:
LocalDate date = LocalDate.from(formatter.parse(ldapString));
String ldapString = OffsetDateTime.of(date, LocalTime.MIN, ZoneOffset.UTC).format(formatter);
In case you're still unsatisfied with the code above then you can extract the logic to utility methods:
public LocalDate parseLocalDate(String ldapString) {
return LocalDate.from(formatter.parse(ldapString));
}
public String formatLocalDate(LocalDate date) {
return OffsetDateTime.of(date, LocalTime.MIN, ZoneOffset.UTC)
.format(formatter);
}
I suggest:
LocalDate date = LocalDate.of(2018, Month.DECEMBER, 27);
String pattern = "yyyyMMddHHmmssxx";
DateTimeFormatter birthdateFormat = DateTimeFormatter.ofPattern(pattern);
// Outputs 20181227000000+0000
String formatted = date.atStartOfDay(ZoneOffset.UTC).format(birthdateFormat);
System.out.println(formatted);
// Parses to 2018-12-27T00:00Z
OffsetDateTime odt = OffsetDateTime.parse("20181227000000+0000", birthdateFormat);
System.out.println(odt);
// Validate
if (! odt.toLocalTime().equals(LocalTime.MIN)) {
System.out.println("Unexpected time of day: " + odt);
}
if (! odt.getOffset().equals(ZoneOffset.UTC)) {
System.out.println("Unexpected time zone offset: " + odt);
}
// Converts to 2018-12-27
date = odt.toLocalDate();
System.out.println(date);
The LDAP string represents both date, time and UTC offset. The good solution is to respect that and generate all of those when formatting (setting time of day to 00:00 and offset to 0) and parsing all of them back (at best also validating them to catch if any surprises should arise). Conversion between LocalDate and OffsetDateTime is straightforward when you know how.
Edit 3: Allowing the pattern to be configured
… the pattern is configured in a property file… I want to configure 1
pattern only in this property file.
… I have no guarantee that the format cannot change.
To take the possibility into account that the pattern may some day not contain time of day and/or no UTC offset use this formatter in the above code:
DateTimeFormatter birthdateFormat = new DateTimeFormatterBuilder()
.appendPattern(pattern)
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.toFormatter()
.withZone(ZoneOffset.UTC);
This defines a default time of day (midnight) and a default offset (0). As long as time and offset are defined in the string from LDAP, the defaults are not used.
If you think it is getting too complicated, using two configured formats, one for formatting and one for parsing, may be the best solution (the least annoying solution) for you.
Edit: Avoiding type conversions
I consider the above the nice solution. However, if you insist an avoiding the conversion from LocalDate to ZonedDateTime using atStartOfDay and from OffsetDateTime using toLocalDate, that is possible through the following hack:
DateTimeFormatter birthdateFormat = new DateTimeFormatterBuilder()
.appendValue(ChronoField.YEAR, 4, 4, SignStyle.NEVER)
.appendValue(ChronoField.MONTH_OF_YEAR, 2, 2, SignStyle.NEVER)
.appendValue(ChronoField.DAY_OF_MONTH, 2, 2, SignStyle.NEVER)
.appendLiteral("000000+0000")
.toFormatter();
// Outputs 20181227000000+0000
String formatted = date.format(birthdateFormat);
System.out.println(formatted);
// Parses into 2018-12-27
date = LocalDate.parse("20181227000000+0000", birthdateFormat);
System.out.println(date);
I am specifying the exact width of each field so that the formatter can know where to separate them in the string when parsing.
Edit 2: Is this a bug in parsing?
I would immediately have expected yyyyMMdd'000000+0000' to work for both formatting and parsing. You may try filing a bug with Oracle and seeing what they say, though I wouldn’t bee too optimistic.
Stupid simple solution:
String s1 = "20181227000000+0000";
DateTimeFormatter yyyyMMdd = DateTimeFormatter.ofPattern("yyyyMMdd");
LocalDate date = LocalDate.parse(s1.substring(0, 8), yyyyMMdd);
System.out.println("date = " + date);
String s2 = date.format(yyyyMMdd) + "000000+0000";
System.out.println("s2 = " + s2);
System.out.println(s1.equals(s2));

String-Date conversion with nanoseconds

I've been struggling for a while with this piece of code for an Android app and I can't get the hang of it. I've read and tried every solution I found on stackoverflow and other places, but still no luck.
What I want to do is have a function to convert a string like "17.08.2012 05:35:19:7600000" to a UTC date and a function that takes an UTC date and converts it to a string like that.
String value = "17.08.2012 05:35:19:7600000";
DateFormat df = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss:SSSSSSS");
try
{
Date today = df.parse(value);
System.out.println("Today = " + df.format(today) + " " + today.toGMTString());
}
catch (ParseException e)
{
e.printStackTrace();
}
This results in : Today = 17.08.2012 07:41:59:0000000 17 Aug 2012 04:41:59 GMT which are both wrong.
I tried setting SDF's timezone to UTC, no luck.
Another thing that I noticed: if I do df.setLenient(false);
It gives me : java.text.ParseException: Unparseable date: "17.08.2012 05:35:19:7600000" .
If anyone can provide me with some explanations / sample code, I would be very grateful. Thanks in advance
The result you are getting is absolutely right.
Let's analyze this:
17.08.2012 05:35:19:7600000
17: Day of month (17th)
08: Month of year (August)
2012: Year (2012)
05: Hour of day (5am)
35: Minute of hour (:35)
19: Second of minute (:19)
7600000: Milliseconds of second (7,600,000)
Now, the way the VM sees this is that you are declaring the time of day as 5:35:19am, then adding 7,600,000 milliseconds to it. 7,600,000 milliseconds = 7,600 seconds = 2 hours, 6 minutes, 40 seconds. 5:35:19am + 02:06:40 = 7:41:59am (and 0 milliseconds). This is the result you are getting. (It also appears that you are not setting the timezone properly, so the GMT string is 3 hours behind your result.)
If you want to retain the :7600000, to my knowledge this is not possible. As this can be simplified into seconds, the VM will automatically reduce it into the other time increments. The milliseconds (the SSSS) should be for storing values <1000.
I'd suggest you create a new SimpleDateFormat for your output; but remember that the milliseconds will be absorbed into the other times (since they are all stored as a single long in the Date object).
private String convertDate(String cdate)
{
SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss:SSSSSSS");
SimpleDateFormat postFormater = new SimpleDateFormat("yyyy-MM-dd");
Date convertedDate;
try
{
convertedDate = dateFormat.parse(cdate);
cdate = postFormater.format(convertedDate);
}
catch (ParseException e)
{
Toast.makeText(getApplicationContext(),e.toString(),Toast.LENGTH_SHORT).show();
}
return cdate;
}
Try this.
This is what you need (but it will loose millisecond information):
"dd.MM.yyyy HH:mm:ss.'000000'"
If you used "dd.MM.yyyy HH:mm:ss.SSSSSS", then would get three leading zeros for your milliseconds.
If you used "dd.MM.yyyy HH:mm:ss.SSS'000'", then you could format a date, but not parse any date.
Try it out:
public static void main(String[] args) throws ParseException {
printDate("dd.MM.yyyy HH:mm:ss.SSS");//02.05.2010 21:45:58.073
printDate("dd.MM.yyyy HH:mm:ss.SSSSSS");//02.05.2010 21:45:58.000073
printDate("dd.MM.yyyy HH:mm:ss.SSS'000'");//02.05.2010 21:45:58.073000
printDate("dd.MM.yyyy HH:mm:ss.'000000'");//02.05.2010 21:45:58.000000
tryToParseDate("dd.MM.yyyy HH:mm:ss.SSS");//good
tryToParseDate("dd.MM.yyyy HH:mm:ss.SSSSSS");//good
tryToParseDate("dd.MM.yyyy HH:mm:ss.SSS'000'");//bad
tryToParseDate("dd.MM.yyyy HH:mm:ss.'000000'");//good
}
private static void printDate(String formatString) {
Date now = new Date();
SimpleDateFormat format = new SimpleDateFormat(formatString);
String formattedDate = format.format(now);
// print that date
System.out.println(formattedDate);
}
private static void tryToParseDate(String formatString) {
Date now = new Date();
SimpleDateFormat format = new SimpleDateFormat(formatString);
String formattedDate = format.format(now);
// try to parse it again
try {
format.parse(formattedDate);
System.out.println("good");
} catch (ParseException e) {
System.out.println("bad");
}
}
To drop the nanoseconds, use:
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.")
Update: java.time
The Question and other Answers use terrible date-time classes that are now legacy. These flawed classes were years ago supplanted by the modern java.time classes defined in JSR 310. Avoid Calendar, DateFormat, Date, etc.
Define a formatting pattern with DateTimeFormatter.
String input = "17.08.2012 05:35:19:7600000";
DateTimeFormatter f = DateTimeFormatter.ofPattern( "dd.MM.uuuu HH:mm:ss:SSSSSSS" );
Parse.
LocalDateTime ldt = LocalDateTime.parse( input , f ) ;
A LocalDateTime object represents a date with a time of day, but lacks the context of a time zone or offset from UTC.
If you are certain the input text is intended to represent a moment as seen in UTC, having an offset of zero hours-minutes-seconds, then assign a ZoneOffset to produce an OffsetDateTime object.
OffsetDateTime odt = ldt.atOffset( ZoneOffset.UTC ) ;
See this code run live at Ideone.com.
ISO 8601
I suggest you educate the publisher of your data about the use of ISO 8601 standard formats when serializing date-time values to text.
The java.time classes use ISO 8601 formats by default when parsing/generating text.

Categories