Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

java.time.ZonedDateTime missing seconds in output

+5
−0

When printing a java.time.ZonedDateTime, it's missing the seconds in output, is it a bug or a feature?

See my code:

package tryNcry;

import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class TestOfTime {

	public static void main(String[] args) {
		Instant naked = Instant.parse("2000-01-01T12:00:01Z");
		System.out.println(naked);
		ZonedDateTime withTimeZone = naked.atZone(ZoneId.of("Australia/Melbourne"));
		System.out.println(withTimeZone);

		long toSeconds = withTimeZone.toEpochSecond();
		ZonedDateTime recreated = Instant.ofEpochSecond(toSeconds).atZone(ZoneId.of("Australia/Melbourne"));
		System.out.println(recreated);
		recreated = Instant.ofEpochSecond(toSeconds - 1).atZone(ZoneId.of("Australia/Melbourne"));
		System.out.println(recreated);
// output:
//		2000-01-01T12:00:01Z
//		2000-01-01T23:00:01+11:00[Australia/Melbourne]
//		2000-01-01T23:00:01+11:00[Australia/Melbourne]
//		2000-01-01T23:00+11:00[Australia/Melbourne]		 
	}
}

The last output skips the seconds. I would call it a (strange) feature if the minutes also would be skipped, but they are displayed.

What is it?


Thanks for the answers, in short it is:

  • it is documented, so it is a feature, not a bug
  • you can easily circumvent that feature by specifying explicitly what format you want to have
History

0 comment threads

2 answers

+4
−0

Although it might look strange, this behaviour is - kinda - documented. But it's not so straighforward to figure out.

In ZonedDateTime::toString docs it says:

The format consists of the LocalDateTime followed by the ZoneOffset.

Therefore, we must check the docs for LocalDateTime::toString, which says:

The output will be one of the following ISO-8601 formats:

  • uuuu-MM-dd'T'HH:mm
  • uuuu-MM-dd'T'HH:mm:ss
  • uuuu-MM-dd'T'HH:mm:ss.SSS
  • uuuu-MM-dd'T'HH:mm:ss.SSSSSS
  • uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSS

The format used will be the shortest that outputs the full value of the time where the omitted parts are implied to be zero.

The last sentence (which I highlighted above) is the key to understand it. If the seconds are omitted, it means that all the following fields (in this case, seconds and fractions of second) are zero.

You didn't mention the Java version you're using, so I'll use mine (JDK 21) as an example. Look at the source code for ZonedDateTime::toString method:

public String toString() {
    String str = dateTime.toString() + offset.toString();
    if (offset != zone) {
        str += '[' + zone.toString() + ']';
    }
    return str;
}

It delegates to dateTime.toString() to build the date/time part. And the dateTime field is an instance of LocalDateTime, so let's see its toString method:

public String toString() {
    return date.toString() + 'T' + time.toString();
}

It delegates the time part to its time field, which is a LocalTime, so let's see its toString method:

public String toString() {
    StringBuilder buf = new StringBuilder(18);
    int hourValue = hour;
    int minuteValue = minute;
    int secondValue = second;
    int nanoValue = nano;
    buf.append(hourValue < 10 ? "0" : "").append(hourValue)
        .append(minuteValue < 10 ? ":0" : ":").append(minuteValue);
    if (secondValue > 0 || nanoValue > 0) {
        buf.append(secondValue < 10 ? ":0" : ":").append(secondValue);
        if (nanoValue > 0) {
            buf.append('.');
            if (nanoValue % 1000_000 == 0) {
                buf.append(Integer.toString((nanoValue / 1000_000) + 1000).substring(1));
            } else if (nanoValue % 1000 == 0) {
                buf.append(Integer.toString((nanoValue / 1000) + 1000_000).substring(1));
            } else {
                buf.append(Integer.toString((nanoValue) + 1000_000_000).substring(1));
            }
        }
    }
    return buf.toString();
}

Finally we can see the reason: if (secondValue > 0 || nanoValue > 0) means that the output will stop at the minutes if both seconds and fractions of second are zero.


If you want to always print the seconds

Then use a java.time.format.DateTimeFormatter:

DateTimeFormatter fmt = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ssXXX'['VV']'");
System.out.println(fmt.format(recreated)); // 2000-01-01T23:00:00+11:00[Australia/Melbourne]

But in this case, it'll always omit the fractions of second. To omit them only if they are zero, use a java.time.format.DateTimeFormatterBuilder. See the difference:

// fractions of second is zero
ZonedDateTime zdt = Instant.parse("2000-01-01T12:00:01Z").atZone(ZoneId.of("Australia/Melbourne"));

// formatter without fractions of second
DateTimeFormatter noFractionFormat = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ssXXX'['VV']'");
// formatter with fractions of second, if not zero
DateTimeFormatter fractionFormat = new DateTimeFormatterBuilder()
    .appendPattern("uuuu-MM-dd'T'HH:mm:ss")
    .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
    .appendPattern("XXX'['VV']'")
    .toFormatter();

// none print fractions of second (as the value is zero)
System.out.println(noFractionFormat.format(zdt));
System.out.println(fractionFormat.format(zdt));

// fractions of second isn't zero
zdt = zdt.plusNanos(1);
// don't print fractions of second
System.out.println(noFractionFormat.format(zdt));
// print fractions of second
System.out.println(fractionFormat.format(zdt));

The second formatter will print the fractions of second if they're not zero. The first one will never print it. The output is:

2000-01-01T23:00:01+11:00[Australia/Melbourne]
2000-01-01T23:00:01+11:00[Australia/Melbourne]
2000-01-01T23:00:01+11:00[Australia/Melbourne]
2000-01-01T23:00:01.000000001+11:00[Australia/Melbourne]
History

0 comment threads

+4
−0

The toString() method of ZonedDateTime uses that of LocalDateTime, which in turn uses that of LocalTime.

If we look at the source of OpenJDK, at least, we see the following method comment on toString:

The format used will be the shortest that outputs the full value of the time where the omitted parts are implied to be zero.

"12:00" is understood (implied) to be a time; "12" on it's own is not.

We see in the code that when the seconds and nanoseconds are 0, they will not be added to the String:

buf.append(hourValue < 10 ? "0" : "").append(hourValue)
        .append(minuteValue < 10 ? ":0" : ":").append(minuteValue);
    if (secondValue > 0 || nanoValue > 0) {
    ....
}

So, in short, it's on purpose. It's a feature, or at least intended to be one.

History

0 comment threads

Sign up to answer this question »