ycomp
ycomp

Reputation: 8573

Removing nanoseconds in a datetime string

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

Upvotes: 5

Views: 6117

Answers (3)

Basil Bourque
Basil Bourque

Reputation: 338730

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);

Upvotes: 4

Mariano
Mariano

Reputation: 6511

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

It's probably best to approach with java.time,
but since you insist:

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

Upvotes: 1

hjpotter92
hjpotter92

Reputation: 80639

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

Upvotes: 3

Related Questions