Skip to content

Commit

Permalink
Temporal truncation
Browse files Browse the repository at this point in the history
  • Loading branch information
sherfert authored and Lojjs committed Mar 5, 2018
1 parent f6bf04f commit a739981
Show file tree
Hide file tree
Showing 20 changed files with 843 additions and 429 deletions.
Expand Up @@ -292,7 +292,7 @@ public T apply( Context ctx, AnyValue[] args ) throws ProcedureException
{ {
AnyValue unit = args[0]; AnyValue unit = args[0];
AnyValue input = args[1]; AnyValue input = args[1];
AnyValue fields = args.length == 3 ? args[2] : EMPTY_MAP; AnyValue fields = args.length == 2 || args[2] == NO_VALUE ? EMPTY_MAP : args[2];
if ( unit instanceof TextValue && input instanceof TemporalValue && fields instanceof MapValue ) if ( unit instanceof TextValue && input instanceof TemporalValue && fields instanceof MapValue )
{ {
return function.truncate( return function.truncate(
Expand Down
Expand Up @@ -35,14 +35,18 @@
import java.time.temporal.IsoFields; import java.time.temporal.IsoFields;
import java.time.temporal.TemporalQueries; import java.time.temporal.TemporalQueries;
import java.time.temporal.TemporalUnit; import java.time.temporal.TemporalUnit;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;


import org.neo4j.helpers.collection.Pair;
import org.neo4j.values.AnyValue; import org.neo4j.values.AnyValue;
import org.neo4j.values.StructureBuilder; import org.neo4j.values.StructureBuilder;
import org.neo4j.values.ValueMapper; import org.neo4j.values.ValueMapper;
import org.neo4j.values.virtual.MapValue; import org.neo4j.values.virtual.MapValue;
import org.neo4j.values.virtual.VirtualValues;


import static java.time.Instant.ofEpochMilli; import static java.time.Instant.ofEpochMilli;
import static java.time.Instant.ofEpochSecond; import static java.time.Instant.ofEpochSecond;
Expand Down Expand Up @@ -152,7 +156,58 @@ public static DateTimeValue truncate(
MapValue fields, MapValue fields,
Supplier<ZoneId> defaultZone ) Supplier<ZoneId> defaultZone )
{ {
throw new UnsupportedOperationException( "not implemented" ); if ( unit.isTimeBased() && !(input instanceof DateTimeValue || input instanceof LocalDateTimeValue) )
{
throw new IllegalArgumentException( "Cannot truncate " + input + " to date time with a time based unit." );
}
LocalDate localDate = input.getDatePart();
LocalTime localTime = input.hasTime() ? input.getLocalTimePart() : LocalTimeValue.DEFAULT_LOCAL_TIME;
ZoneId zoneId = input.hasTimeZone() ? input.getZoneId( defaultZone ) : defaultZone.get();

LocalTime truncatedTime;
LocalDate truncatedDate;
if ( unit.isDateBased() )
{
truncatedDate = DateValue.truncateTo( localDate, unit );
truncatedTime = LocalTimeValue.DEFAULT_LOCAL_TIME;
}
else
{
truncatedDate = localDate;
truncatedTime = localTime.truncatedTo( unit );
}

ZonedDateTime truncatedZDT = ZonedDateTime.of( truncatedDate, truncatedTime, zoneId );

if ( fields.size() == 0 )
{
return datetime( truncatedZDT );
}
else
{
// Timezone needs some special handling, since the builder will shift keeping the instant instead of the local time
Map<String, AnyValue> updatedFields = new HashMap<>( fields.size() + 1 );
for ( Map.Entry<String,AnyValue> entry : fields.entrySet() )
{
if ( "timezone".equals( entry.getKey() ) )
{
truncatedZDT = truncatedZDT.withZoneSameLocal( timezoneOf( entry.getValue() ) );
}
else
{
updatedFields.put( entry.getKey(), entry.getValue() );
}
}

truncatedZDT = updateFieldMapWithConflictingSubseconds( updatedFields, unit, truncatedZDT );

if ( updatedFields.size() == 0 )
{
return datetime( truncatedZDT );
}
updatedFields.put( "datetime", datetime( truncatedZDT ) );
return build( VirtualValues.map( updatedFields ), defaultZone );
}
} }


static DateTimeBuilder<DateTimeValue> builder( Supplier<ZoneId> defaultZone ) static DateTimeBuilder<DateTimeValue> builder( Supplier<ZoneId> defaultZone )
Expand Down Expand Up @@ -344,6 +399,12 @@ public boolean hasTimeZone()
return true; return true;
} }


@Override
boolean hasTime()
{
return true;
}

@Override @Override
public boolean equals( Value other ) public boolean equals( Value other )
{ {
Expand Down
Expand Up @@ -21,21 +21,27 @@


import java.time.Clock; import java.time.Clock;
import java.time.DateTimeException; import java.time.DateTimeException;
import java.time.DayOfWeek;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalTime; import java.time.LocalTime;
import java.time.OffsetTime; import java.time.OffsetTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField; import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.time.temporal.IsoFields; import java.time.temporal.IsoFields;
import java.time.temporal.TemporalAdjusters;
import java.time.temporal.TemporalUnit; import java.time.temporal.TemporalUnit;
import java.time.temporal.UnsupportedTemporalTypeException;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;


import org.neo4j.helpers.collection.Pair;
import org.neo4j.values.StructureBuilder; import org.neo4j.values.StructureBuilder;
import org.neo4j.values.ValueMapper; import org.neo4j.values.ValueMapper;
import org.neo4j.values.virtual.MapValue; import org.neo4j.values.virtual.MapValue;
import org.neo4j.values.virtual.VirtualValues;


import static java.lang.Integer.parseInt; import static java.lang.Integer.parseInt;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
Expand Down Expand Up @@ -111,7 +117,57 @@ public static DateValue truncate(
MapValue fields, MapValue fields,
Supplier<ZoneId> defaultZone ) Supplier<ZoneId> defaultZone )
{ {
throw new UnsupportedOperationException( "not implemented" ); LocalDate localDate = input.getDatePart();
DateValue truncated = date( truncateTo( localDate, unit ) );
if ( fields.size() == 0 )
{
return truncated;
}
else
{
MapValue updatedFields = VirtualValues.copy( fields, Pair.of( "date", truncated ) );
return build( updatedFields, defaultZone );
}
}

static LocalDate truncateTo( LocalDate value, TemporalUnit unit )
{
if ( unit == ChronoUnit.MILLENNIA )
{
return value.with( Neo4JTemporalField.YEAR_OF_MILLENNIUM, 0 );
}
else if ( unit == ChronoUnit.CENTURIES )
{
return value.with( Neo4JTemporalField.YEAR_OF_CENTURY, 0 );
}
else if ( unit == ChronoUnit.DECADES )
{
return value.with( Neo4JTemporalField.YEAR_OF_DECADE, 0 );
}
else if ( unit == ChronoUnit.YEARS )
{
return value.with( TemporalAdjusters.firstDayOfYear() );
}
else if ( unit == IsoFields.QUARTER_YEARS )
{
return value.with( IsoFields.DAY_OF_QUARTER, 1 );
}
else if ( unit == ChronoUnit.MONTHS )
{
return value.with( TemporalAdjusters.firstDayOfMonth() );
}
else if ( unit == ChronoUnit.WEEKS )
{
return value.with( TemporalAdjusters.previousOrSame( DayOfWeek.MONDAY ) );
}
else if ( unit == ChronoUnit.DAYS )
{
return value;
}
else
{
throw new UnsupportedTemporalTypeException( "Unit too small for truncation: " + unit );
}
} }


static DateBuilder builder( Supplier<ZoneId> defaultZone ) static DateBuilder builder( Supplier<ZoneId> defaultZone )
Expand Down Expand Up @@ -169,6 +225,12 @@ public boolean hasTimeZone()
return false; return false;
} }


@Override
boolean hasTime()
{
return false;
}

@Override @Override
public boolean equals( Value other ) public boolean equals( Value other )
{ {
Expand Down
Expand Up @@ -26,7 +26,7 @@ public abstract class IntegralValue extends NumberValue
{ {
public static long safeCastIntegral( String name, AnyValue value, long defaultValue ) public static long safeCastIntegral( String name, AnyValue value, long defaultValue )
{ {
if ( value == null ) if ( value == null || value == Values.NO_VALUE )
{ {
return defaultValue; return defaultValue;
} }
Expand Down
Expand Up @@ -32,14 +32,17 @@
import java.time.temporal.ChronoField; import java.time.temporal.ChronoField;
import java.time.temporal.IsoFields; import java.time.temporal.IsoFields;
import java.time.temporal.TemporalUnit; import java.time.temporal.TemporalUnit;
import java.util.Map;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;


import org.neo4j.helpers.collection.Pair;
import org.neo4j.values.AnyValue; import org.neo4j.values.AnyValue;
import org.neo4j.values.StructureBuilder; import org.neo4j.values.StructureBuilder;
import org.neo4j.values.ValueMapper; import org.neo4j.values.ValueMapper;
import org.neo4j.values.virtual.MapValue; import org.neo4j.values.virtual.MapValue;
import org.neo4j.values.virtual.VirtualValues;


import static java.time.Instant.ofEpochSecond; import static java.time.Instant.ofEpochSecond;
import static java.time.LocalDateTime.ofInstant; import static java.time.LocalDateTime.ofInstant;
Expand Down Expand Up @@ -116,7 +119,42 @@ public static LocalDateTimeValue truncate(
MapValue fields, MapValue fields,
Supplier<ZoneId> defaultZone ) Supplier<ZoneId> defaultZone )
{ {
throw new UnsupportedOperationException( "not implemented" ); if ( unit.isTimeBased() && !(input instanceof DateTimeValue || input instanceof LocalDateTimeValue) )
{
throw new IllegalArgumentException( "Cannot truncate " + input + " to local date time with a time based unit." );
}
LocalTime localTime = input.hasTime() ? input.getLocalTimePart() : LocalTimeValue.DEFAULT_LOCAL_TIME;
LocalDate localDate = input.getDatePart();

LocalTime truncatedTime;
LocalDate truncatedDate;
if ( unit.isDateBased() )
{
truncatedDate = DateValue.truncateTo( localDate, unit );
truncatedTime = LocalTimeValue.DEFAULT_LOCAL_TIME;
}
else
{
truncatedDate = localDate;
truncatedTime = localTime.truncatedTo( unit );
}

LocalDateTime truncatedLDT = LocalDateTime.of( truncatedDate, truncatedTime );
if ( fields.size() == 0 )
{
return localDateTime( truncatedLDT );
}
else
{
Map<String,AnyValue> updatedFields = fields.getMapCopy();
truncatedLDT = updateFieldMapWithConflictingSubseconds( updatedFields, unit, truncatedLDT );
if ( updatedFields.size() == 0 )
{
return localDateTime( truncatedLDT );
}
updatedFields.put( "datetime", localDateTime( truncatedLDT ) );
return build( VirtualValues.map( updatedFields ), defaultZone );
}
} }


static final LocalDateTime DEFAULT_LOCAL_DATE_TIME = static final LocalDateTime DEFAULT_LOCAL_DATE_TIME =
Expand Down Expand Up @@ -285,6 +323,12 @@ public boolean hasTimeZone()
return false; return false;
} }


@Override
boolean hasTime()
{
return true;
}

@Override @Override
public boolean equals( Value other ) public boolean equals( Value other )
{ {
Expand Down
Expand Up @@ -30,14 +30,17 @@
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField; import java.time.temporal.ChronoField;
import java.time.temporal.TemporalUnit; import java.time.temporal.TemporalUnit;
import java.util.Map;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;


import org.neo4j.helpers.collection.Pair;
import org.neo4j.values.AnyValue; import org.neo4j.values.AnyValue;
import org.neo4j.values.StructureBuilder; import org.neo4j.values.StructureBuilder;
import org.neo4j.values.ValueMapper; import org.neo4j.values.ValueMapper;
import org.neo4j.values.virtual.MapValue; import org.neo4j.values.virtual.MapValue;
import org.neo4j.values.virtual.VirtualValues;


import static java.lang.Integer.parseInt; import static java.lang.Integer.parseInt;
import static java.time.ZoneOffset.UTC; import static java.time.ZoneOffset.UTC;
Expand Down Expand Up @@ -102,7 +105,23 @@ public static LocalTimeValue truncate(
MapValue fields, MapValue fields,
Supplier<ZoneId> defaultZone ) Supplier<ZoneId> defaultZone )
{ {
throw new UnsupportedOperationException( "not implemented" ); LocalTime localTime = input.getLocalTimePart();
LocalTime truncatedLT = localTime.truncatedTo( unit );
if ( fields.size() == 0 )
{
return localTime( truncatedLT );
}
else
{
Map<String,AnyValue> updatedFields = fields.getMapCopy();
truncatedLT = updateFieldMapWithConflictingSubseconds( updatedFields, unit, truncatedLT );
if ( updatedFields.size() == 0 )
{
return localTime( truncatedLT );
}
updatedFields.put( "time", localTime( truncatedLT ) );
return build( VirtualValues.map( updatedFields ), defaultZone );
}
} }


static final LocalTime DEFAULT_LOCAL_TIME = LocalTime.of( Field.hour.defaultValue, Field.minute.defaultValue ); static final LocalTime DEFAULT_LOCAL_TIME = LocalTime.of( Field.hour.defaultValue, Field.minute.defaultValue );
Expand Down Expand Up @@ -206,6 +225,12 @@ public boolean hasTimeZone()
return false; return false;
} }


@Override
boolean hasTime()
{
return true;
}

@Override @Override
public boolean equals( Value other ) public boolean equals( Value other )
{ {
Expand Down

0 comments on commit a739981

Please sign in to comment.