Skip to content

fix(local-date-time): fixes #7 add java.time.LocalDateTime support #107

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

Closed
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
14 changes: 14 additions & 0 deletions src/main/java/graphql/scalars/ExtendedScalars.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import graphql.scalars.currency.CurrencyScalar;
import graphql.scalars.datetime.DateScalar;
import graphql.scalars.datetime.DateTimeScalar;
import graphql.scalars.datetime.LocalDateTimeScalar;
import graphql.scalars.datetime.LocalTimeCoercing;
import graphql.scalars.datetime.TimeScalar;
import graphql.scalars.java.JavaPrimitives;
Expand Down Expand Up @@ -45,6 +46,19 @@ public class ExtendedScalars {
*/
public static final GraphQLScalarType DateTime = DateTimeScalar.INSTANCE;

/**
* A date-time without a time-zone in the ISO-8601 calendar system, formatted as 2007-12-03 10:15:30.
* `java.time.LocalDateTime` objects at runtime.
* <p>
* Its {@link graphql.schema.Coercing#serialize(java.lang.Object)} and {@link graphql.schema.Coercing#parseValue(java.lang.Object)} methods
* accept LocalDateTime and formatted Strings as valid objects.
* <p>
* See the <a href="https://www.ietf.org/rfc/rfc3339.txt">rfc3339 spec</a> for more details on the format.
*
* @see java.time.LocalDateTime
*/
public static final GraphQLScalarType LocalDateTime = LocalDateTimeScalar.INSTANCE;

/**
* An RFC-3339 compliant date scalar that accepts string values like `1996-12-19` and produces
* `java.time.LocalDate` objects at runtime.
Expand Down
97 changes: 97 additions & 0 deletions src/main/java/graphql/scalars/datetime/LocalDateTimeScalar.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package graphql.scalars.datetime;

import graphql.Internal;
import graphql.language.StringValue;
import graphql.language.Value;
import graphql.schema.Coercing;
import graphql.schema.CoercingParseLiteralException;
import graphql.schema.CoercingParseValueException;
import graphql.schema.CoercingSerializeException;
import graphql.schema.GraphQLScalarType;

import java.time.DateTimeException;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.util.function.Function;

import static graphql.scalars.util.Kit.typeName;
import static java.lang.String.format;
import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE;
import static java.time.temporal.ChronoField.*;

/**
* Access this via {@link graphql.scalars.ExtendedScalars#LocalDateTime}
*/
@Internal
public final class LocalDateTimeScalar {

public static final GraphQLScalarType INSTANCE;

private LocalDateTimeScalar() {}
public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

static {
Coercing<LocalDateTime, String> coercing = new Coercing<LocalDateTime, String>() {
@Override
public String serialize(Object dataFetcherResult) throws CoercingSerializeException {
try {
LocalDateTime localDateTime = (LocalDateTime) dataFetcherResult;
return localDateTime.format(DATE_TIME_FORMATTER);
} catch (ClassCastException exception) {
throw new CoercingSerializeException("Input is not a LocalDateTime", exception);
}
}

@Override
public LocalDateTime parseValue(Object input) throws CoercingParseValueException {
// Will be String if the value is specified via external variables object, and a StringValue
// if provided direct in the query.
if (input instanceof StringValue) {
return parseString(((StringValue) input).getValue());
}
if (input instanceof String) {
return parseString((String) input);
}
if (input instanceof LocalDateTime) {
return (LocalDateTime) input;
}
throw new CoercingParseValueException(format("Unable to parse %s as LocalDateTime", input));
}

private LocalDateTime parseString(String input) {
try {
return LocalDateTime.parse(input, DATE_TIME_FORMATTER);
} catch (DateTimeParseException parseException) {
throw new CoercingParseValueException(
format("Unable to parse %s as LocalDateTime", input), parseException);
}
}

@Override
public Value<?> valueToLiteral(Object input) {
String s = serialize(input);
return StringValue.newStringValue(s).build();
}

@Override
public LocalDateTime parseLiteral(Object input) throws CoercingParseLiteralException {
try {
return parseValue(input);
} catch (CoercingParseValueException exception) {
throw new CoercingParseLiteralException(exception);
}
}
};

INSTANCE = GraphQLScalarType.newScalar()
.name("LocalDateTime")
.description("A date-time without a time-zone in the ISO-8601 calendar system, formatted as 2007-12-03 10:15:30")
.specifiedByUrl("https://scalars.graphql.org/andimarek/local-date-time") // TODO: Change to .specifiedByURL when builder added to graphql-java
.coercing(coercing)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package graphql.scalars.datetime

import graphql.language.StringValue
import graphql.scalars.ExtendedScalars
import graphql.schema.CoercingParseLiteralException
import graphql.schema.CoercingParseValueException
import graphql.schema.CoercingSerializeException
import spock.lang.Specification
import spock.lang.Unroll

import java.time.LocalDateTime

import static graphql.scalars.util.TestKit.*

class LocalDateTimeScalarTest extends Specification {

def coercing = ExtendedScalars.LocalDateTime.getCoercing()

@Unroll
def "localDatetime parseValue"() {

when:
def result = coercing.parseValue(input)
then:
result == expectedValue
where:
input | expectedValue
"1985-04-12 23:20:50" | mkLocalDT("1985-04-12T23:20:50")
"1985-04-12 23:20:50" | mkLocalDT("1985-04-12T23:20:50.000")
LocalDateTime.of(1985, 04, 12, 23, 20, 50) | mkLocalDT("1985-04-12T23:20:50.000")
LocalDateTime.of(1985, 04, 12, 23, 20) | mkLocalDT("1985-04-12T23:20:00")
}

@Unroll
def "localDatetime parseLiteral"() {

when:
def result = coercing.parseLiteral(input)
then:
result == expectedValue
where:
input | expectedValue
"1985-04-12 23:20:50" | mkLocalDT("1985-04-12T23:20:50.00")
"1996-12-19 16:39:57" | mkLocalDT("1996-12-19T16:39:57")
new StringValue("1996-12-19 16:39:57") | mkLocalDT("1996-12-19T16:39:57")
LocalDateTime.of(1996, 12, 19, 16, 39, 57) | mkLocalDT("1996-12-19T16:39:57")
}

@Unroll
def "localDatetime valueToLiteral"() {

when:
def result = coercing.valueToLiteral(input)
then:
result.isEqualTo(expectedValue)
where:
input | expectedValue
LocalDateTime.of(1996, 12, 19, 16, 39, 57) | new StringValue("1996-12-19 16:39:57")
}

@Unroll
def "localDatetime parseValue bad inputs"() {

when:
coercing.parseValue(input)
then:
thrown(expectedValue)
where:
input | expectedValue
"1985-04-12" | CoercingParseValueException
"2022-11-24T01:00:01.02-00:00" | CoercingParseValueException
mkOffsetDT("1985-04-12T23:20:50.52Z") | CoercingParseValueException
666 || CoercingParseValueException
}

def "localDatetime serialisation"() {

when:
def result = coercing.serialize(input)
then:
result == expectedValue
where:
input | expectedValue
LocalDateTime.of(1996, 12, 19, 16, 39, 57) | "1996-12-19 16:39:57"
}

def "localDatetime serialisation bad inputs"() {

when:
coercing.serialize(input)
then:
thrown(expectedValue)
where:
input | expectedValue
"1985-04-12" | CoercingSerializeException
"2022-11-24T01:00:01.02-00:00" | CoercingSerializeException
mkOffsetDT(year: 1980, hour: 3) | CoercingSerializeException
666 || CoercingSerializeException
}

@Unroll
def "localDatetime parseLiteral bad inputs"() {

when:
coercing.parseLiteral(input)
then:
thrown(expectedValue)
where:
input | expectedValue
"2022-11-24T01:00:01.02-00:00" | CoercingParseLiteralException
}

}
4 changes: 4 additions & 0 deletions src/test/groovy/graphql/scalars/util/TestKit.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ class TestKit {
OffsetDateTime.parse(s)
}

static LocalDateTime mkLocalDT(String s) {
LocalDateTime.parse(s)
}

static OffsetTime mkOffsetT(String s) {
OffsetTime.parse(s)
}
Expand Down