Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Removing nanoseconds in a datetime string

Tags:

java

regex

What I'm trying to achieve...

I have dates in potentially 2 formats:

2013-07-03T14:30:38Z
2013-07-03T14:30:38.000000Z

I want to axe the .00000 if it exists

I got this far, to remove everything after the '.' time.replaceFirst("^(.*?)\\b\\..*$", "$1");

result: 2013-07-03T14:30:38

Problem is, I still need the 'Z' .. how can I keep the Z ?

I'm no Date Time expert...

so I'm not entirely sure if there will always be 6 0s (if the string contains the nanoseconds) or if there can be a variable number of 0s...

Java 8

I'm using java but didn't want to tag it as java since it's basically just pure regex

like image 618
ycomp Avatar asked Feb 09 '23 22:02

ycomp


2 Answers

Strings != Date-Time

I should correct the terminology in the Question: You (apparently) have strings in a certain format, not "dates". Do not confuse date-time values with their String representations.

Just as "$12.34" is not a number, it is a string representation of a number with a certain format applied. That string can be parsed into a number.

This concept is the essence of the following code. We parse a string to instantiate a date-time object, then use that date-time object to generate a new string representation.

java.time

Java 8 and later has a new java.time framework built-in (Tutorial). You could use that for parsing a string as a date-time value, and then generating a new string representation in any format you desire. Here's some code if you want to explore this as an alternative to regex.

Both of your possible formats are nearly in ISO 8601 format. Strictly speaking the standard expects only millisecond resolution (3 decimal places) as far as I know, but the java.time framework extends that to nanosecond (up to 9 decimal places).

The java.time framework uses ISO 8601 as its default when parsing and generating strings. So you need not specify a formatter pattern, and instead can use one of the several predefined formatters aimed at ISO 8601 standard formats.

An Instant object represents a moment on the timeline in UTC. You could assign a particular time zone to get a ZonedDateTime, but not needed for this particular Question. This class can parse your input strings by default.

String input = "2013-07-03T14:30:38.123456789Z";
Instant instant = Instant.parse( input );

After parsing we have an instant object in hand. From there we can effectively truncate the fractions of a second by calling the with method to change the fractional second to zero. Technically speaking we are not changing the fractional seconds, as java.time uses immutable objects. A new object is created based on the values of the original.

Instant instantTruncated = instant.with( ChronoField.NANO_OF_SECOND , 0 );

Lastly we use the predefined formatter, DateTimeFormatter.ISO_INSTANT. This formatter automatically suppresses the fractional zero from the generated string in groups of three digits where the value is zero. So …7.12 prints as …7.120, …7.1234 as …7.123400, and …7.0 as …7. That last value is fits our needs.

DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT;
String output = formatter.format( instantTruncated );

The toString method on Instant uses the formatter DateTimeFormatter.ISO_INSTANT by default. So technically we could shorten the code to just call toString. But I want to show explicitly how a formatter is involved.

Dump to console.

System.out.println( "instant: " + instant );
System.out.println( "instantTruncated: " + instantTruncated );
System.out.println( "output: " + output );

When run.

instant: 2013-07-03T14:30:38.123456789Z
instantTruncated: 2013-07-03T14:30:38Z
output: 2013-07-03T14:30:38Z

We can assign a time zone to that Instant to get a 'ZonedDateTime`.

ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.ofInstant( instantTuncated , zoneId);
like image 72
Basil Bourque Avatar answered Feb 11 '23 15:02

Basil Bourque


Simply add the Z back inside:

time.replaceFirst( "^(.*?)\\b\\..*$", "$1Z");

And alternative would be to capture everything until you encounter the period character (.):

time.replaceFirst( "^([^.]+).*(Z)$", "$1$2");
like image 34
hjpotter92 Avatar answered Feb 11 '23 15:02

hjpotter92