SimpleDateFormat [0] issue - java

I've below SimpleDateFormat Code
Date date = new Date();
DateFormat inpuDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SS'Z'");
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
String dateStr = inpuDateFormat.format(cal.getTime());
It works perfectly on my dev servers but it fails on sandbox instances with following error.
org.junit.ComparisonFailure: expected:<...20-08-12T19:06:02.85[0]Z> but was:<...20-08-12T19:06:02.85[]Z>
I've handled it as
dateStr = dateStr.replace("[0]","");
dateStr = dateStr.replace("[]","");
But, I still didn't get the logic why my date is different on different server instances and is there any better way to handle it

java.time
There certainly is a much better way to handle it. Use java.time, the modern Java date and time API, for your date and time work, not Date, DateFormat, SimpleDateFormat nor Calendar.
Instant now = Instant.now();
String dateStr1 = now.toString();
System.out.println(dateStr1);
Output in one run was:
2020-07-24T18:06:07.988093Z
You notice that six decimals on the seconds were output, not two. In other runs you may have three decimals or no fraction at all. Don’t worry, for the majority of purposes you’ll be just fine. The format printed is ISO 8601, and according to ISO 8601 the count of decimals on the seconds, even the presence of seconds at all, is optional. So whatever you need the string for, as long as ISO 8601 format is expected, the string from the above code snippet should be accepted.
I am exploiting the fact that Instant.toString() produces ISO 8601 format, so we don’t need any formatter.
If for some strange reason you do need exactly two decimals on the seconds, use a formatter for specifying so (edit: now outputting Z):
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSX")
.withZone(ZoneOffset.UTC);
String dateStr2 = formatter2.format(now);
System.out.println(dateStr2);
2020-07-24T18:06:07.98Z
To a DateTimeFormatter (opposite a SimpleDateFormat) uppercase S in the format pattern string means fraction of second, and you are free to place from one through nine of them to get from one to nine decimals.
What went wrong in your code?
First, the message that you got from your JUnit test was:
org.junit.ComparisonFailure: expected:<...20-08-12T19:06:02.85[0]Z> but was:<...20-08-12T19:06:02.85[]Z>
The square brackets is JUnit’s way of drawing our attention to the difference between the expected and the actual value. So they are not part of those values. What JUnit tells us is that the value was expected to end in .850Z but instead ended in just .85Z. So a zero was missing. Your test is probably too strict since as I said, it shouldn’t matter whether there are two or three decimals. And 02.85 and 02.850 are just different ways of denoting the exact same value.
This role of the square brackets also explains why replacing [0] and [] in the string didn’t help: the square brackets were never in the strings, so the replacements never made any change to the strings.
Second, to SimpleDateFormat (opposite DateTimeFormatter) format pattern letter uppercase S means millisecond. So putting any other number than three of them makes no sense and gives you incorrect results. In your code you put two. In nine of ten cases the millisecond value is in the interval 100 through 999, and in this case SimpleDateFormat prints all three digits in spite of the only two pattern letters S. This probably explains why your unit test passed in your development environment. On your sandbox incidentally the time ended in 2.085 seconds. The correct ways to render this include 02.08 and 02.085. Your SimpleDateFormat chose neither. To it the millisecond value of 85 was to be rendered in two positions, so it produces 02.85, which is the wrong value, 765 milliseconds later. And your unit test objected while this once there were only two decimals, not three.
Third, not what you asked, but no matter if using the troublesome SimpleDateFormat or the modern DateTimeFormatter you must never hardcode Z as a literal in the format pattern string. The trailing Z means UTC or offset zero from UTC. It needs to be printed (and parsed if that were the case) as an offset, or you get wrong results. The way to make sure you get a Z and not for example an offset of +02:00 is to make sure that an offset of 0 is specified. This was why I put .withZone(ZoneOffset.UTC) on my formatter.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Wikipedia article: ISO 8601

Try to remove the quotes around the 'Z', as 'Z' is a constant whilst without quotes it means 'time zone':
DateFormat inpuDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
(By the way, in most cases you want to use three decimal places for milliseconds: "SSS".)

Related

Create a DateTimeFormater with an Optional Section at Beginning

I have timecodes with this structure hh:mm:ss.SSS for which i have a own Class, implementing the Temporal Interface.
It has the custom Field TimecodeHour Field allowing values greater than 23 for hour.
I want to parse with DateTimeFormatter. The hour value is optional (can be omitted, and hours can be greater than 24); as RegEx (\d*\d\d:)?\d\d:\d\d.\d\d\d
For the purpose of this Question my custom Field can be replaced with the normal HOUR_OF_DAY Field.
My current Formatter
DateTimeFormatter UNLIMITED_HOURS = new DateTimeFormatterBuilder()
.appendValue(ChronoField.HOUR_OF_DAY, 2, 2,SignStyle.NEVER)
.appendLiteral(':')
.parseDefaulting(TimecodeHour.HOUR, 0)
.toFormatter(Locale.ENGLISH);
DateTimeFormatter TIMECODE = new DateTimeFormatterBuilder()
.appendOptional(UNLIMITED_HOURS)
.appendValue(MINUTE_OF_HOUR, 2)
.appendLiteral(':')
.appendValue(SECOND_OF_MINUTE, 2)
.appendFraction(MILLI_OF_SECOND, 3, 3, true)
.toFormatter(Locale.ENGLISH);
Timecodes with a hour value parse as expected, but values with hours omittet throw an Exception
java.time.format.DateTimeParseException: Text '20:33.123' could not be parsed at index 5
I assume, as hour and minute have the same pattern, the parser starts at front and captures the minute value for the optional section.
Is this right, and how can solve this?
I started to suspect that 20:33.123 wasn’t meant to indicate a time of day between 20 and 21 minutes past midnight. Maybe rather an amount of time, a little longer than 20 minutes. If this is correct, use a Duration for it.
Unfortunately java.time does not include means for parsing and formatting a Duration in other than ISO 8601 format. This leaves us with at least three options:
Use a third-party library. Time4J offers an elegant solution, see below. Joda-Time has its PeriodFormatter class. Apache may also offer facilities for parsing and formatting of durations.
Convert your string to ISO 8601 format before parsing with Duration.parse().
Write your own parser.
I was thinking that we’re too lazy for 3. and that Joda-Time is getting dated, so I want to pursue options 1. and 2. here, option 1. in the Time4J variant.
A regex for adapting to ISO 8601
ISO 8601 format for a duration feels unusual at first, but is straightforward. PT20M33.123S means 20 minutes 33.123 seconds.
public static Duration parse(String timeCodeString) {
String iso8601 = timeCodeString
.replaceFirst("^(\\d{2,}):(\\d{2}):(\\d{2}\\.\\d{3})$", "PT$1H$2M$3S")
.replaceFirst("^(\\d{2}):(\\d{2}\\.\\d{3})$", "PT$1M$2S");
return Duration.parse(iso8601);
}
Let’s try it out:
System.out.println(parse("20:33.123"));
System.out.println(parse("123:20:33.123"));
Output is:
PT20M33.123S
PT123H20M33.123S
My two calls to replaceFirst first handle the case with hours, then the case without hours. So either will convert a string that matches your regex to ISO 8601 format. Which the Duration class then parses. And as you can see, Duration also prints ISO 8601 format back. Formatting it differently is not bad, though, search for how.
Time4J
The Time4J library offers the really elegant solution very much along the same line of thought as yours. All we really need is this formatter:
private static final Formatter<ClockUnit> TIME_CODE_PARSER
= Duration.formatter(ClockUnit.class, "[###hh:mm:ss.fff][mm:ss.fff]");
Simply use like this:
System.out.println(TIME_CODE_PARSER.parse("20:33.123"));
System.out.println(TIME_CODE_PARSER.parse("123:20:33.123"));
PT20M33,123000000S
PT123H20M33,123000000S
The Time4J Duration class too prints ISO 8601 format. It appears that it uses comma as decimal separator as is preferred in ISO 8601, and that it prints 9 decimals on the seconds also when some of them are 0.
In the format pattern string ###hh means 2 to 5 digit hours, and fff means three digits of decimal fraction of second.
Anything wrong with your approach?
Was there anything wrong with your approach? ChronoField.HOUR_OF_DAY means that: hour of day. 0 is midnight, 12 is noon and 23 is near the end of the day. This is not what you want, so yes, you are using the wrong means. While you can probably get it to work, anyone maintaining your code after you will find it confusing and will probably have a hard time making modification in line with your intentions.
Links
Wikipedia article: ISO 8601
Joda-Time PeriodFormatter
Time4J TimeSpanFormatter
Try with two optional parts (one with hours, other without) like in:
var formatter = new DateTimeFormatterBuilder()
.optionalStart()
.appendValue(HOUR_OF_DAY, 2, 4, SignStyle.NEVER).appendLiteral(":")
.appendValue(MINUTE_OF_HOUR, 2).appendLiteral(":")
.appendValue(SECOND_OF_MINUTE, 2)
.optionalEnd()
.optionalStart()
.parseDefaulting(HOUR_OF_DAY, 0)
.appendValue(MINUTE_OF_HOUR, 2).appendLiteral(":")
.appendValue(SECOND_OF_MINUTE, 2)
.optionalEnd()
.toFormatter(Locale.ENGLISH);
I do not know about TimecodeHour, so I used HOUR_OF_DAY to test(also too lazy to include fractions)
I think fundamentally the problem is that it gets stuck going down the wrong path. It sees a field of length 2, which we know is the minutes but it believes is the hours. Once it believes the optional section is present, when we know it's not, the whole thing is destined to fail.
This is provable by changing the minimum hour length to 3.
.appendValue(TimecodeHour.HOUR, 3, 4, SignStyle.NEVER)
It now knows that the "20" cannot be hours, since hours requires at least 3 digits. With this small change, it now parses correctly, whether the optional section is present or not.
So presuming that the hours field really does need to be between 2 and 4 digits, I think you're stuck with having to implement a workaround. For example, count the number of colons in the string and use a different formatter depending on which one you run into. Using a different delimiter besides a colon for the hours would also work.
The parser logic has undergone quite a few bug fixes over the various Java versions since it was introduced - as you can imagine, there are so many potential edge cases - so I was hopeful using a recent version of Java would make this problem disappear. Unfortunately, it seems even in Java 16, the behaviour is still the same.

Formatting date in Java using SimpleDateFormat

I am trying to parse a date into an appropriate format, but I keep getting the error
Unparseable date
Can anyone tell me what the mistake is?
try {
System.out.println(new SimpleDateFormat("d-MMM-Y").parse("05-03-2018").toString());
} catch (ParseException e) {
e.printStackTrace();
}
I want the date to have this format:
05-Mar-18
Since you want to change the format, first read and parse the date (from String) of your own format in a Date type object. Then use that date object by formatting it into a new (desired) format using a SimpleDateFormat.
The error in your code is with the MMM and Y. MMM is the month in string while your input is a numeric value. Plus the Y in your SimpleDateFormat is an invalid year. yy is what needs to be added.
So here is a code that would fix your problem.
SimpleDateFormat dateFormat = new SimpleDateFormat("d-MM-yyyy");
Date date = dateFormat.parse("05-03-2018");
dateFormat = new SimpleDateFormat("dd-MMM-yy");
System.out.println(dateFormat.format(date));
I hope this is what you're looking for.
There are some concepts about dates you should be aware of.
There's a difference between a date and a text that represents a date.
Example: today's date is March 9th 2018. That date is just a concept, an idea of "a specific point in our calendar system".
The same date, though, can be represented in many formats. It can be "graphical", in the form of a circle around a number in a piece of paper with lots of other numbers in some specific order, or it can be in plain text, such as:
09/03/2018 (day/month/year)
03/09/2018 (monty/day/year)
2018-03-09 (ISO8601 format)
March, 9th 2018
9 de março de 2018 (in Portuguese)
2018年3月5日 (in Japanese)
and so on...
Note that the text representations are different, but all of them represent the same date (the same value).
With that in mind, let's see how Java works with these concepts.
a text is represented by a String. This class contains a sequence of characters, nothing more. These characters can represent anything; in this case, it's a date
a date was initially represented by java.util.Date, and then by java.util.Calendar, but those classes are full of problems and you should avoid them if possible. Today we have a better API for that.
With the java.time API (or the respective backport for versions lower than 8), you have easier and more reliable tools to deal with dates.
In your case, you have a String (a text representing a date) and you want to convert it to another format. You must do it in 2 steps:
convert the String to some date-type (transform the text to numerical day/month/year values) - that's called parsing
convert this date-type value to some format (transform the numerical values to text in a specific format) - that's called formatting
For step 1, you can use a LocalDate, a type that represents a date (day, month and year, without hours and without timezone), because that's what your input is:
String input = "05-03-2018";
DateTimeFormatter inputParser = DateTimeFormatter.ofPattern("dd-MM-yyyy");
// parse the input
LocalDate date = LocalDate.parse(input, inputParser);
That's more reliable than SimpleDateFormat because it solves lots of strange bugs and problems of the old API.
Now that we have our LocalDate object, we can do step 2:
// convert to another format
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MMM-yy", Locale.ENGLISH);
String output = date.format(formatter);
Note that I used a java.util.Locale. That's because the output you want has a month name in English, and if you don't specify a locale, it'll use the JVM's default (and who guarantees it'll always be English? it's better to tell the API which language you're using instead of relying on the default configs, because those can be changed anytime, even by other applications running in the same JVM).
And how do I know which letters must be used in DateTimeFormatter? Well, I've just read the javadoc. Many developers ignore the documentation, but we must create the habit to check it, specially the javadoc, that tells you things like the difference between uppercase Y and lowercase y in SimpleDateFormat.

ZonedDateTime now function mandatorily contains second value

My code requires the current UTC time in String format.
String date = ZonedDateTime.now(ZoneOffset.UTC).withNano(0).toString();
This works fine in most cases except for when the time has zero seconds value.
Normal output: 2018-03-07T11:33:09Z
Problem output: 2018-03-06T11:33Z
It skips printing the second time. How can I force the seconds to be printed even if they are zero?
You can use a formatter to always print the seconds-part:
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ssX");
String s = ZonedDateTime.now(ZoneOffset.UTC).withNano(0).format(dtf);
System.out.println(s); // 2018-03-20T16:15:07Z
If you use a DateTimeFormatter like the one suggested by Meno, you don't even need to set the nanos value to zero:
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ssX");
String formatted = ZonedDateTime.now(ZoneOffset.UTC).format(fmt);
System.out.println(formatted); // 2018-03-08T13:33:52Z
That's because the formatter prints only the fields specified in the format. In this case, it's using HH:mm:ss, which means "hours:minutes:seconds". As the fraction of seconds is not in the pattern, they're not printed, so there's no need to call withNano.
This may sound like a minor detail, but as all those classes are immutable, methods like withNano always create a new object, and in this case you don't need to, so using the formatter not only gives you the desired output, but it also avoids the creation of unnecessary objects (with only one ZonedDateTime, this might sound irrelevant, but in a scenario with lots of dates being formatted this can make some difference, IMO).
I'd call withNano(0) only if I really want to set this field to zero. If I just don't want to print it when formatting, I wouldn't call it.

What is the date format for "2017-03-29 18:17:12+00"

I want to construct date "2017-03-29 18:17:12+00" but I am getting the following format while using the format,
public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ssZ";
2017-02-28 11:12:31+0000
I Need the format of "2017-03-29 18:17:12+00".
How to get this? what is the format?
Kindly provide your inputs
Try X which is ISO 8601 time zone
public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ssX";
Try X intead of Z in your format string.
I would make sure to always use the ISO8601.
As shazin stated you can replace your Z with X to perform the accepting of a Date.
As addition I would suggest to use the OffsetDateTime API like this:
OffsetDateTime time = OffsetDateTime.parse( YOUR_DATE_STRING );
this will give you an OffsetDateTime object you can work with to the correct time-zone. Also if you want to convert the input to your local time zone or the UTC timezone you can use
time.atZoneSameInstant( ZoneId.systemDefault() );
Note that not all time zones are shifted from UTC by an integral number of
hours. So when standards where created, nobody thought that there would
be a demand to print the time shift as only 2 digits.
And probably this is why "Z" as a time shift placeholder is converted to
"+" or "-" char, and just four digits.
I think, the solution is:
generate the data/time string with 4 digit time shift part,
cut off the last 2 digits.
But be aware that in some cases, e.g. Afghanistan (4h 30m)
or Central Australia (10h 30m) this 2-digit time shift will be inaccurate.
If you use JDK 8 java.time API, you will get the desired format (ISO 8601) by default.
System.out.println(OffsetDateTime.now(ZoneId.systemDefault())); // 2017-05-26T16:20:42.821+05:30

Java SimpleDateFormat Timezone offset with minute separated by colon

How can I get my date formatted as 2012-11-25T23:50:56.193+01:00 using SimpleDateFormat?
If I use Z in the format like
yyyy-MM-dd'T'hh:mm:ss.SSSZ
then it shows
2013-03-06T11:49:05.490+0100
You can get the timezone offset formatted like +01:00 with the SimpleDateFormat in Java 7 (yyyy-MM-dd'T'HH:mm:ss.SSSXXX), or with the Joda's DateTimeFormat (yyyy-MM-dd'T'HH:mm:ss.SSSZZ).
Here’s the 2017 answer. If there is any way you can (which there is), throw the outdated classes like SimpleDateFormat overboard and use the modern and more convenient classes in java.time. In particular, the desired format, 2012-11-25T23:50:56.193+01:00 complies with ISO-8601 and therefore comes out of the box with the newer classes, just use OffsetDateTime.toString():
OffsetDateTime time = OffsetDateTime.now();
System.out.println(time.toString());
This prints something like
2017-05-10T16:14:20.407+02:00
One thing you may or may not want to be aware of, though, it prints as many groups of 3 decimals on the seconds as it takes to print the precision in the OffsetDateTime object. Apparently on my computer “now” comes with a precision of milliseconds (seconds with three decimals).
If you have an oldfashioned Date object, for example, you got it from a call to some legacy method, I recommend the first thing you do is convert it to Instant, which is one of the modern classes. From there you can easily other conversions depending on your requirements:
Date now = new Date();
OffsetDateTime time = now.toInstant().atZone(ZoneId.systemDefault()).toOffsetDateTime();
System.out.println(time.toString());
I am really doing more conversions than necessary. atZone(ZoneId.systemDefault()) produced a ZonedDateTime, and its toString() will not always give you the format you said you wanted; but it can easily be formatted into it:
ZonedDateTime time = now.toInstant().atZone(ZoneId.systemDefault());
System.out.println(time.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));

Categories

Resources