Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support of conversion to java.time classes #593

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.beust.jcommander.converters;

import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.List;

/**
* Converter to {@link Instant}.
*/
public final class InstantConverter extends JavaTimeConverter<Instant> {

public InstantConverter(String optionName) {
super(optionName, Instant.class);
}

@Override
protected List<DateTimeFormatter> supportedFormats() {
return List.of(DateTimeFormatter.ISO_INSTANT);
}

@Override
protected Instant parse(String value, DateTimeFormatter formatter) {
try {
long ms = Long.parseLong(value);
return Instant.ofEpochMilli(ms);
} catch (NumberFormatException e) {
return formatter.parse(value, Instant::from);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.beust.jcommander.converters;

import com.beust.jcommander.ParameterException;

import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.TemporalAccessor;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;

/**
* Base class for all {@link java.time} converters.
*
* @param <T> concrete type to parse into
*/
public abstract class JavaTimeConverter<T extends TemporalAccessor> extends BaseConverter<T> {

private final Class<T> toClass;

/**
* Inheritor constructors should have only 1 parameter - optionName.
*
* @param optionName name of the option
* @param toClass type to parse into
*/
protected JavaTimeConverter(String optionName, Class<T> toClass) {
super(optionName);
this.toClass = toClass;
}

@Override
public final T convert(String value) {
return supportedFormats().stream()
.map(new Mapper(value))
.filter(Objects::nonNull)
.findFirst()
.orElseThrow(() -> new ParameterException(errorMessage(value)));
}

/**
* Supported formats for this type, e.g. {@code HH:mm:ss}
*
* @return list of supported formats
*/
protected abstract List<DateTimeFormatter> supportedFormats();

/**
* Parse the value using the specified formatter.
*
* @param value value to parse
* @param formatter formatter specifying supported format
* @return parsed value
*/
protected abstract T parse(String value, DateTimeFormatter formatter);

private String errorMessage(String value) {
return getErrorString(value, "a " + toClass.getSimpleName());
}

private final class Mapper implements Function<DateTimeFormatter, T> {

private final String value;

private Mapper(String value) {
this.value = value;
}

@Override
public T apply(DateTimeFormatter formatter) {
try {
return parse(value, formatter);
} catch (DateTimeParseException exc) {
return null;
} catch (Exception exc) {
throw new ParameterException(errorMessage(value), exc);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.beust.jcommander.converters;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;

/**
* Converter to {@link LocalDate}.
*/
public class LocalDateConverter extends JavaTimeConverter<LocalDate> {

public LocalDateConverter(String optionName) {
super(optionName, LocalDate.class);
}

@Override
protected List<DateTimeFormatter> supportedFormats() {
return List.of(DateTimeFormatter.ISO_LOCAL_DATE, DateTimeFormatter.ofPattern("dd-MM-yyyy"));
}

@Override
protected LocalDate parse(String value, DateTimeFormatter formatter) {
return LocalDate.parse(value, formatter);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.beust.jcommander.converters;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.List;

/**
* Converter for {@link LocalDateTime}.
*/
public class LocalDateTimeConverter extends JavaTimeConverter<LocalDateTime> {

public LocalDateTimeConverter(String optionName) {
super(optionName, LocalDateTime.class);
}

@Override
protected List<DateTimeFormatter> supportedFormats() {
return List.of(
DateTimeFormatter.ISO_LOCAL_DATE_TIME,
new DateTimeFormatterBuilder().appendPattern("dd-MM-yyyy").appendLiteral('T').append(DateTimeFormatter.ISO_LOCAL_TIME).toFormatter()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really. ISO_DATE_TIME has the same date format as ISO_LOCAL_DATE_TIME - year-month-day, but it can handle offset and zone id, if they are present. The extra pattern I wrote has the date part in day-month-year format, which is also widely used as far as I know.

How would you like to handle that one? I would leave it as is, as it follows more strictly a local date time format (no offset/zone information), but I don't really insist on that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mkarg What are your thoughts on my previous comment?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will share them as soon as I have the time to.

);
}

@Override
protected LocalDateTime parse(String value, DateTimeFormatter formatter) {
return LocalDateTime.parse(value, formatter);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.beust.jcommander.converters;

import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.List;

/**
* Converter for {@link LocalTime}.
*/
public class LocalTimeConverter extends JavaTimeConverter<LocalTime> {

public LocalTimeConverter(String optionName) {
super(optionName, LocalTime.class);
}

@Override
protected List<DateTimeFormatter> supportedFormats() {
return List.of(DateTimeFormatter.ISO_LOCAL_TIME);
}

@Override
protected LocalTime parse(String value, DateTimeFormatter formatter) {
return LocalTime.parse(value, formatter);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.beust.jcommander.converters;

import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;

/**
* Converter for {@link OffsetDateTime}.
*/
public class OffsetDateTimeConverter extends JavaTimeConverter<OffsetDateTime> {

public OffsetDateTimeConverter(String optionName) {
super(optionName, OffsetDateTime.class);
}

@Override
protected List<DateTimeFormatter> supportedFormats() {
return List.of(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}

@Override
protected OffsetDateTime parse(String value, DateTimeFormatter formatter) {
return OffsetDateTime.parse(value, formatter);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.beust.jcommander.converters;

import java.time.OffsetTime;
import java.time.format.DateTimeFormatter;
import java.util.List;

/**
* Converter for {@link OffsetTime}.
*/
public class OffsetTimeConverter extends JavaTimeConverter<OffsetTime> {

public OffsetTimeConverter(String optionName) {
super(optionName, OffsetTime.class);
}

@Override
protected List<DateTimeFormatter> supportedFormats() {
return List.of(DateTimeFormatter.ISO_OFFSET_TIME);
}

@Override
protected OffsetTime parse(String value, DateTimeFormatter formatter) {
return OffsetTime.parse(value, formatter);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.beust.jcommander.converters;

import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;

/**
* Converter for {@link ZonedDateTime}.
*/
public class ZonedDateTimeConverter extends JavaTimeConverter<ZonedDateTime> {

public ZonedDateTimeConverter(String optionName) {
super(optionName, ZonedDateTime.class);
}

@Override
protected List<DateTimeFormatter> supportedFormats() {
return List.of(DateTimeFormatter.ISO_ZONED_DATE_TIME);
}

@Override
protected ZonedDateTime parse(String value, DateTimeFormatter formatter) {
return ZonedDateTime.parse(value, formatter);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,30 @@
import com.beust.jcommander.converters.FileConverter;
import com.beust.jcommander.converters.FloatConverter;
import com.beust.jcommander.converters.ISO8601DateConverter;
import com.beust.jcommander.converters.InstantConverter;
import com.beust.jcommander.converters.IntegerConverter;
import com.beust.jcommander.converters.LocalDateConverter;
import com.beust.jcommander.converters.LocalDateTimeConverter;
import com.beust.jcommander.converters.LocalTimeConverter;
import com.beust.jcommander.converters.LongConverter;
import com.beust.jcommander.converters.OffsetDateTimeConverter;
import com.beust.jcommander.converters.OffsetTimeConverter;
import com.beust.jcommander.converters.StringConverter;
import com.beust.jcommander.converters.PathConverter;
import com.beust.jcommander.converters.URIConverter;
import com.beust.jcommander.converters.URLConverter;
import com.beust.jcommander.converters.ZonedDateTimeConverter;

import java.io.File;
import java.lang.NoClassDefFoundError;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZonedDateTime;
import java.util.Date;
import java.net.URI;
import java.net.URL;
Expand Down Expand Up @@ -67,6 +81,14 @@ public class DefaultConverterFactory implements IStringConverterFactory {
classConverters.put(URI.class, URIConverter.class);
classConverters.put(URL.class, URLConverter.class);

classConverters.put(Instant.class, InstantConverter.class);
classConverters.put(LocalDate.class, LocalDateConverter.class);
classConverters.put(LocalDateTime.class, LocalDateTimeConverter.class);
classConverters.put(LocalTime.class, LocalTimeConverter.class);
classConverters.put(OffsetDateTime.class, OffsetDateTimeConverter.class);
classConverters.put(OffsetTime.class, OffsetTimeConverter.class);
classConverters.put(ZonedDateTime.class, ZonedDateTimeConverter.class);

try {
classConverters.put(Path.class, PathConverter.class);
} catch (NoClassDefFoundError ex) {
Expand Down
43 changes: 43 additions & 0 deletions src/test/java/com/beust/jcommander/JCommanderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@
import java.nio.file.Path;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.ResourceBundle;

Expand Down Expand Up @@ -1519,4 +1527,39 @@ public void validate(String name, Integer i) throws ParameterException {
}
}
}

@Test
public void javaTimeTest() {
String[] argv = {
"-i", "0",
"-ld", "1970-01-12",
"-ldt", "1970-01-12T11:15:09.000000333",
"-lt", "11:15:09.000000333",
"-odt", "1970-01-12T11:15:09.000000333+01:00",
"-ot", "11:15:09.000000333+01:00",
"-zdt", "1970-01-12T11:15:09.000000333Z"
};
JavaTimeParameters params = new JavaTimeParameters();
JCommander.newBuilder().addObject(params).build().parse(argv);

Assert.assertEquals(params.instant, Instant.EPOCH, "Instant parsed incorrectly");

LocalDate localDate = LocalDate.of(1970, 1, 12);
Assert.assertEquals(params.localDate, localDate, "LocalDate parsed incorrectly");

LocalTime localTime = LocalTime.of(11, 15, 9, 333);
Assert.assertEquals(params.localDateTime, LocalDateTime.of(localDate, localTime), "LocalDateTime parsed incorrectly");

Assert.assertEquals(params.localTime, localTime, "LocalTime parsed incorrectly");

ZoneOffset zoneOffset = ZoneOffset.ofHours(1);
OffsetDateTime offsetDateTime = OffsetDateTime.of(localDate, localTime, zoneOffset);
Assert.assertEquals(params.offsetDateTime, offsetDateTime, "OffsetDateTime parsed incorrectly");

OffsetTime offsetTime = OffsetTime.of(localTime, zoneOffset);
Assert.assertEquals(params.offsetTime, offsetTime, "OffsetTime parsed incorrectly");

ZonedDateTime zonedDateTime = ZonedDateTime.of(localDate, localTime, ZoneOffset.UTC);
Assert.assertEquals(params.zonedDateTime, zonedDateTime, "ZonedDateTime parsed incorrectly");
}
}
Loading