ZonedDateTime now function mandatorily contains second value - java

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.

Related

SimpleDateFormat [0] issue

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".)

LocalDateTime formatter that always returns at least milliseconds

I need to construct a formatter, that for each LocalDateTime object will print at least up to milliseconds precision, or more if available (in ISO format).
For example I expect the following
LocalDateTime.parse("2018-01-01T00:00:00").format(...) // 2018-01-01T00:00:00.000
LocalDateTime.parse("2018-01-01T00:00:00.123456").format(...) // 2018-01-01T00:00:00.123456
Answer on java.time.DateTimeFormatter : Need ISO_INSTANT that always renders milliseconds is not helpful, as it is for ZonedDateTime. Also pattern yyyy-MM-dd'T'HH:mm:ss.SSS does not work for me, as it is trimming micros and nanos.
You can construct a DateTimeFormatter in this way to achieve the effect of printing at least milliseconds:
DateTimeFormatter dtf =
new DateTimeFormatterBuilder().appendPattern("uuuu-MM-dd'T'HH:mm:ss").appendFraction(
ChronoField.NANO_OF_SECOND,
3,
9,
true
).toFormatter();
String s1 = LocalDateTime.parse("2018-01-01T00:00:00").format(dtf);
String s2 = LocalDateTime.parse("2018-01-01T00:00:00.123456").format(dtf);
System.out.println(s1); // 2018-01-01T00:00:00.000
System.out.println(s2); // 2018-01-01T00:00:00.123456
The parameter is letter 'n' placed 1 to 9 times, which gives nano-of-second. If specified less then 9 times the nanoseconds value will be truncated to the length of number of 'n's. 'SSS' should be working as well. The format is not dependant t which class you are using it, so ZonedDateTime or LocalDateTime should work the same except the fields specific to some class, like in this case time zone only present in ZonedDateTime and not in the LocalDateTime. In any case you can read about it here: Class DateTimeFormatter

How to compare two different times using an if else statement?

I'm trying to compare two different times to see which comes before the other, but I'm having trouble coming up with an else if statement that compares their suffixes (am and pm) and outputs the order of the first time relative to the other time.
EDIT: the date type of suffix1 and suffix2 are strings, the data type of time1 and time2 are int
So far, I have this piece of code that checks if the periods are equal:
if (suffix1.equals(suffix2))
{
if (time1 > time2)
{
System.out.print("After");
}
.....
}
is there a way to see which times' suffix comes before the other?
I would think you’re after something like the following:
String timeString1 = "2:00 PM";
String timeString2 = "9:00 AM";
DateTimeFormatter timeFormatParser
= DateTimeFormatter.ofPattern("h:mm a", Locale.ENGLISH);
LocalTime time1 = LocalTime.parse(timeString1, timeFormatParser);
LocalTime time2 = LocalTime.parse(timeString2, timeFormatParser);
if (time1.isAfter(time2)) {
System.out.println("After");
}
With the strings in my example the code does print After. If your original time strings have a different format, I hope you can modify the format pattern string accordingly, otherwise follow up in a comment. Or maybe better, edit your question to specify your strings precisely. The letters you can use are documented here. Please be aware that the format pattern is case sensitive.
My DateTimeFormatter expects AM or PM in uppercase. If your strings have them in lowercase, there are a couple of options:
The simple: timeString1 = timeString1.toUpperCase(Locale.ENGLISH); and similarly for timeString2.
The more general but also more complex: Use a DateTimeFormatterBuilder to build a non-case-sensitive DateTimeFormatter.
PS Before the modern Java date and time API that I am using came out in 2014, one would often use a class called SimpleDateFormat to parse into a Date object. Nowadays stay away from that. The newer classes have shown to be considerably nicer to work with and more programmer friendly.
Use Date to represent time in application: https://docs.oracle.com/javase/8/docs/api/java/util/Date.html
The string representation, should be used only when displaying the time.
When you use dates you can compare them like this myDate.compareTo(anotherDate).
You can print the date in whichever form you want, here is how: Change date format in a Java string

Java, change date format.

I want to change the format of date from yyyyMM to yyyy-MM.
I have found out that the two ways below works equally well. But which one is the best? I would prefer methodTwo since it is simplier, but is there any advantage using methodOne?
public String date;
public void methodOne()
{
String newDate = date;
DateFormat formatter = new SimpleDateFormat("yyyyMM");
DateFormat wantedFormat = new SimpleDateFormat("yyyy-MM");
Date d = formatter.parse(newDate);
newDate = wantedFormat.format(d);
date = newDate;
}
public void methodTwo()
{
date = date.substring(0, 4) + "-" + date.substring(4, 6);
}
You should prefer method one because it can detect if the input date has an invalid format. Method two could lead to problems, when it's not guaranteed that the input date is always in the same format. Method one is also more easy to adjust, when you later want to change either the input or the output format.
It could make sense to use method two if performance really matters, because it should be slightly faster than method one.
I would say that methodOne is much more generic. If you need to change your wanted format again, you only have to change the argument "yyyy-MM" for whatever new format you want, no matter how exotic it is. I found that when you work with people all across the world, methodOne is much more useful and also easier to read than using substrings. Using substrings means that you have no way to make sure that what is coming has the format you expect. You could be splitting in the wrong order.
Get it in a Easy Way:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM");
String date = sdf.format(yourDate);
Using the SimpleDateFormat is the industry standard. It is easier to read and maintain in the future as it allows the format to be changed with ease.
java.time.YearMonth
There is a third way: Use the YearMonth class built into Java 8 and later as part of the java.time framework.
You don't have a date and a time, so using a date-time class such as java.util.Date or even the java.time types (Instant, OffsetDateTime, ZonedDateTime) is not a good fit. As you have only a year and a month use, well, YearMonth – a perfect fit.
Rather than pass Strings around your code, pass these YearMonth values. Then you get type-safety and valid values.
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuuyy");
YearMonth ym = YearMonth.parse( input );
The default format used by the YearMonth::toString method uses the standard ISO 8601 format you desire.
String output = ym.toString();

How can I make a DateTimeFormatter that accepts trailing junk?

I'm retrofitting old some SimpleDateFormat code to use the new Java 8 DateTimeFormatter. SimpleDateFormat, and thus the old code, accepts strings with stuff in them after the date like "20130311nonsense". The DateTimeFormat I created throws a DateTimeParseException for these strings, which is probably the right thing to do, but I'd like to maintain compatibility. Can I modify my DateTimeFormat to accept these strings?
I'm currently creating it like this:
DateTimeFormatter.ofPattern("yyyyMMdd")
Use the parse() method that takes a ParsePosition, as that one doesn't fail when it doesn't read the entire text:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
TemporalAccessor parse = formatter.parse("20140314 some extra text", new ParsePosition(0));
System.out.println(LocalDate.from(parse));
The ParsePosition instance that you pass will also be updated with the point at which the parsing stopped, so if you need to do something with the leftover text then it will be useful to assign it to a variable prior to calling parse.

Categories

Resources