org.neo4j.values.storable.TemporalValue Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of neo4j-values Show documentation
Show all versions of neo4j-values Show documentation
Neo4j property value system.
The newest version!
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [https://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package org.neo4j.values.storable;
import static org.neo4j.values.storable.DateTimeValue.datetime;
import static org.neo4j.values.storable.DateTimeValue.parseZoneName;
import static org.neo4j.values.storable.IntegralValue.safeCastIntegral;
import static org.neo4j.values.storable.LocalDateTimeValue.localDateTime;
import static org.neo4j.values.storable.NumberType.NO_NUMBER;
import static org.neo4j.values.storable.TimeValue.time;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.chrono.ChronoZonedDateTime;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.time.temporal.IsoFields;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAdjuster;
import java.time.temporal.TemporalAmount;
import java.time.temporal.TemporalField;
import java.time.temporal.TemporalQuery;
import java.time.temporal.TemporalUnit;
import java.time.temporal.UnsupportedTemporalTypeException;
import java.time.temporal.ValueRange;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.neo4j.exceptions.ArithmeticException;
import org.neo4j.exceptions.InvalidArgumentException;
import org.neo4j.exceptions.TemporalParseException;
import org.neo4j.exceptions.UnsupportedTemporalUnitException;
import org.neo4j.hashing.HashFunction;
import org.neo4j.internal.helpers.collection.Pair;
import org.neo4j.values.AnyValue;
import org.neo4j.values.StructureBuilder;
import org.neo4j.values.virtual.MapValue;
public abstract class TemporalValue> extends HashMemoizingScalarValue
implements Temporal {
TemporalValue() {
// subclasses are confined to this package,
// but type-checking is valuable to be able to do outside
// (therefore the type itself is public)
}
public abstract TemporalValue add(DurationValue duration);
public abstract TemporalValue sub(DurationValue duration);
abstract T temporal();
/**
* @return the date part of this temporal, if date is supported.
*/
abstract LocalDate getDatePart();
/**
* @return the local time part of this temporal, if time is supported.
*/
abstract LocalTime getLocalTimePart();
/**
* @return the time part of this temporal, if time is supported.
*/
abstract OffsetTime getTimePart(Supplier defaultZone);
/**
* @return the zone id, if time is supported. If time is supported, but no timezone, the defaultZone will be used.
* @throws UnsupportedTemporalUnitException if time is not supported
*/
abstract ZoneId getZoneId(Supplier defaultZone);
/**
* @return the zone offset, if this temporal has a zone offset.
* @throws UnsupportedTemporalUnitException if this does not have a offset
*/
abstract ZoneOffset getZoneOffset();
abstract boolean supportsTimeZone();
abstract boolean hasTime();
abstract V replacement(T temporal);
@Override
public final T asObjectCopy() {
return temporal();
}
@Override
public long updateHash(HashFunction hashFunction, long hash) {
// todo Good enough? Or do subclasses need to implement each their own?
return hashFunction.update(hash, hashCode());
}
@Override
@SuppressWarnings("unchecked")
public final V with(TemporalAdjuster adjuster) {
return replacement((T) temporal().with(adjuster));
}
@Override
@SuppressWarnings("unchecked")
public final V plus(TemporalAmount amount) {
return replacement((T) temporal().plus(amount));
}
@Override
@SuppressWarnings("unchecked")
public final V minus(TemporalAmount amount) {
return replacement((T) temporal().minus(amount));
}
@Override
@SuppressWarnings("unchecked")
public final V minus(long amountToSubtract, TemporalUnit unit) {
return replacement((T) temporal().minus(amountToSubtract, unit));
}
@Override
public final boolean isSupported(TemporalUnit unit) {
return temporal().isSupported(unit);
}
@Override
@SuppressWarnings("unchecked")
public final V with(TemporalField field, long newValue) {
return replacement((T) temporal().with(field, newValue));
}
@Override
@SuppressWarnings("unchecked")
public final V plus(long amountToAdd, TemporalUnit unit) {
return replacement((T) temporal().plus(amountToAdd, unit));
}
@Override
public final long until(Temporal endExclusive, TemporalUnit unit) {
if (!(endExclusive instanceof TemporalValue to)) {
throw new InvalidArgumentException("Can only compute durations between TemporalValues.");
}
TemporalValue from = this;
if (unit.isTimeBased()) {
from = attachTime(from);
to = attachTime(to);
} else if (from.isSupported(ChronoField.SECOND_OF_DAY) && !to.isSupported(ChronoField.SECOND_OF_DAY)) {
to = attachTime(to);
} else if (to.isSupported(ChronoField.SECOND_OF_DAY) && !from.isSupported(ChronoField.SECOND_OF_DAY)) {
from = attachTime(from);
}
if (from.isSupported(ChronoField.MONTH_OF_YEAR) && !to.isSupported(ChronoField.MONTH_OF_YEAR)) {
to = attachDate(to, from.getDatePart());
} else if (to.isSupported(ChronoField.MONTH_OF_YEAR) && !from.isSupported(ChronoField.MONTH_OF_YEAR)) {
from = attachDate(from, to.getDatePart());
}
if (from.supportsTimeZone() && !to.supportsTimeZone()) {
to = attachTimeZone(to, from.getZoneId(from::getZoneOffset));
} else if (to.supportsTimeZone() && !from.supportsTimeZone()) {
from = attachTimeZone(from, to.getZoneId(to::getZoneOffset));
}
long until;
try {
until = from.temporal().until(to, unit);
} catch (UnsupportedTemporalTypeException e) {
String prettyVal = unit instanceof Value v ? v.prettyPrint() : String.valueOf(unit);
throw UnsupportedTemporalUnitException.cannotProcess(prettyVal, e);
}
return until;
}
private static TemporalValue attachTime(TemporalValue temporal) {
boolean supportsTime = temporal.isSupported(ChronoField.SECOND_OF_DAY);
if (supportsTime) {
return temporal;
} else {
LocalDate datePart = temporal.getDatePart();
LocalTime timePart = LocalTimeValue.DEFAULT_LOCAL_TIME;
return localDateTime(LocalDateTime.of(datePart, timePart));
}
}
private static TemporalValue attachDate(TemporalValue temporal, LocalDate dateToAttach) {
LocalTime timePart = temporal.getLocalTimePart();
if (temporal.supportsTimeZone()) {
// turn time into date time
return datetime(ZonedDateTime.of(dateToAttach, timePart, temporal.getZoneOffset()));
} else {
// turn local time into local date time
return localDateTime(LocalDateTime.of(dateToAttach, timePart));
}
}
private static TemporalValue attachTimeZone(TemporalValue temporal, ZoneId zoneIdToAttach) {
if (temporal.isSupported(ChronoField.MONTH_OF_YEAR)) {
// turn local date time into date time
return datetime(ZonedDateTime.of(temporal.getDatePart(), temporal.getLocalTimePart(), zoneIdToAttach));
} else {
// turn local time into time
if (zoneIdToAttach instanceof ZoneOffset) {
return time(OffsetTime.of(temporal.getLocalTimePart(), (ZoneOffset) zoneIdToAttach));
} else {
throw new IllegalStateException("Should only attach offsets to local times, not zone ids.");
}
}
}
@Override
public final ValueRange range(TemporalField field) {
return temporal().range(field);
}
@Override
public final int get(TemporalField field) {
int accessor;
try {
accessor = temporal().get(field);
} catch (UnsupportedTemporalTypeException e) {
String prettyVal = field instanceof Value v ? v.prettyPrint() : String.valueOf(field);
throw UnsupportedTemporalUnitException.cannotProcess(prettyVal, e);
}
return accessor;
}
public final AnyValue get(String fieldName) {
TemporalFields field = TemporalFields.fields.get(fieldName.toLowerCase(Locale.ROOT));
if (field == TemporalFields.epochSeconds || field == TemporalFields.epochMillis) {
T temp = temporal();
if (temp instanceof ChronoZonedDateTime zdt) {
if (field == TemporalFields.epochSeconds) {
return Values.longValue(zdt.toInstant().toEpochMilli() / 1000);
} else {
return Values.longValue(zdt.toInstant().toEpochMilli());
}
} else {
throw new UnsupportedTemporalUnitException("Epoch not supported.");
}
}
if (field == TemporalFields.timezone) {
return Values.stringValue(getZoneId(this::getZoneOffset).toString());
}
if (field == TemporalFields.offset) {
return Values.stringValue(getZoneOffset().toString());
}
if (field == TemporalFields.offsetMinutes) {
return Values.intValue(getZoneOffset().getTotalSeconds() / 60);
}
if (field == TemporalFields.offsetSeconds) {
return Values.intValue(getZoneOffset().getTotalSeconds());
}
if (field == null || field.field == null) {
throw new UnsupportedTemporalUnitException("No such field: " + fieldName);
}
return Values.intValue(get(field.field));
}
@Override
public R query(TemporalQuery query) {
return temporal().query(query);
}
@Override
public final boolean isSupported(TemporalField field) {
return temporal().isSupported(field);
}
@Override
public final long getLong(TemporalField field) {
return temporal().getLong(field);
}
@Override
public final NumberType numberType() {
return NO_NUMBER;
}
@Override
public final boolean equals(boolean x) {
return false;
}
@Override
public final boolean equals(long x) {
return false;
}
@Override
public final boolean equals(double x) {
return false;
}
@Override
public final boolean equals(char x) {
return false;
}
@Override
public final boolean equals(String x) {
return false;
}
@Override
public String toString() {
return prettyPrint();
}
static VALUE parse(Class type, Pattern pattern, Function parser, CharSequence text) {
Matcher matcher = pattern.matcher(text);
VALUE result = matcher.matches() ? parser.apply(matcher) : null;
if (result == null) {
throw TemporalParseException.cannotParseText(valueName(type), text.toString());
}
return result;
}
static VALUE parse(Class type, Pattern pattern, Function parser, TextValue text) {
Matcher matcher = text.matcher(pattern);
VALUE result = matcher != null && matcher.matches() ? parser.apply(matcher) : null;
if (result == null) {
throw TemporalParseException.cannotParseText(valueName(type), text.stringValue());
}
return result;
}
static VALUE parse(
Class type,
Pattern pattern,
BiFunction, VALUE> parser,
CharSequence text,
Supplier defaultZone) {
Matcher matcher = pattern.matcher(text);
VALUE result = matcher.matches() ? parser.apply(matcher, defaultZone) : null;
if (result == null) {
throw TemporalParseException.cannotParseText(valueName(type), text.toString());
}
return result;
}
static VALUE parse(
Class type,
Pattern pattern,
BiFunction, VALUE> parser,
TextValue text,
Supplier defaultZone) {
Matcher matcher = text.matcher(pattern);
VALUE result = matcher != null && matcher.matches() ? parser.apply(matcher, defaultZone) : null;
if (result == null) {
throw new TemporalParseException("Text cannot be parsed to a " + valueName(type), text.stringValue(), 0);
}
return result;
}
private static String valueName(Class type) {
String name = type.getSimpleName();
return name.substring(0, name.length() - /*"Value" is*/ 5 /*characters*/);
}
public static TimeCSVHeaderInformation parseHeaderInformation(String text) {
return parseHeaderInformation(Value.parseStringMap(text));
}
public static TimeCSVHeaderInformation parseHeaderInformation(Map options) {
TimeCSVHeaderInformation fields = new TimeCSVHeaderInformation();
options.forEach(fields::assign);
return fields;
}
abstract static class Builder implements StructureBuilder {
private final Supplier defaultZone;
private DateTimeBuilder state;
protected AnyValue timezone;
protected Map fields = new EnumMap<>(TemporalFields.class);
Builder(Supplier defaultZone) {
this.defaultZone = defaultZone;
}
@Override
public final Result build() {
if (state == null) {
throw new InvalidArgumentException("Builder state empty");
}
state.checkAssignments(this.supportsDate());
try {
return buildInternal();
} catch (DateTimeException e) {
throw new InvalidArgumentException(e.getMessage(), e);
}
}
Temp assignAllFields(Temp temp) {
Temp result = temp;
for (Map.Entry entry : fields.entrySet()) {
TemporalFields f = entry.getKey();
if (f == TemporalFields.year && fields.containsKey(TemporalFields.week)) {
// Year can mean week-based year, if a week is specified.
result = (Temp) result.with(
IsoFields.WEEK_BASED_YEAR, safeCastIntegral(f.name(), entry.getValue(), f.defaultValue));
} else if (!f.isGroupSelector()
&& f != TemporalFields.timezone
&& f != TemporalFields.millisecond
&& f != TemporalFields.microsecond
&& f != TemporalFields.nanosecond) {
TemporalField temporalField = f.field;
result = (Temp)
result.with(temporalField, safeCastIntegral(f.name(), entry.getValue(), f.defaultValue));
}
}
// Assign all sub-second parts in one step
if (supportsTime()
&& (fields.containsKey(TemporalFields.millisecond)
|| fields.containsKey(TemporalFields.microsecond)
|| fields.containsKey(TemporalFields.nanosecond))) {
result = (Temp) result.with(
TemporalFields.nanosecond.field,
validNano(
fields.get(TemporalFields.millisecond),
fields.get(TemporalFields.microsecond),
fields.get(TemporalFields.nanosecond)));
}
return result;
}
@Override
public final StructureBuilder add(String fieldName, AnyValue value) {
TemporalFields field = TemporalFields.fields.get(fieldName.toLowerCase(Locale.ROOT));
if (field == null) {
throw InvalidArgumentException.noSuchTemporalField(fieldName);
}
// Change state
field.assign(this, value);
// Set field for this builder
fields.put(field, value);
return this;
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean supports(TemporalField field) {
if (field.isDateBased()) {
return supportsDate();
}
if (field.isTimeBased()) {
return supportsTime();
}
throw new IllegalStateException("Fields should be either date based or time based");
}
protected abstract boolean supportsDate();
protected abstract boolean supportsTime();
protected abstract boolean supportsTimeZone();
protected abstract boolean supportsEpoch();
protected ZoneId timezone(AnyValue timezone) {
return timezone == null ? defaultZone.get() : timezoneOf(timezone);
}
// Construction
protected abstract Result buildInternal();
// Timezone utilities
protected final ZoneId optionalTimezone() {
return timezone == null ? null : timezone();
}
protected final ZoneId timezone() {
return timezone(timezone);
}
}
/**
* All fields that can be a asigned to or read from temporals.
* Make sure that writable fields defined in "decreasing" order between year and nanosecond.
*/
public enum TemporalFields {
year(ChronoField.YEAR, 0),
quarter(IsoFields.QUARTER_OF_YEAR, 1),
month(ChronoField.MONTH_OF_YEAR, 1),
week(IsoFields.WEEK_OF_WEEK_BASED_YEAR, 1),
ordinalDay(ChronoField.DAY_OF_YEAR, 1),
dayOfQuarter(IsoFields.DAY_OF_QUARTER, 1),
dayOfWeek(ChronoField.DAY_OF_WEEK, 1),
day(ChronoField.DAY_OF_MONTH, 1),
hour(ChronoField.HOUR_OF_DAY, 0),
minute(ChronoField.MINUTE_OF_HOUR, 0),
second(ChronoField.SECOND_OF_MINUTE, 0),
millisecond(ChronoField.MILLI_OF_SECOND, 0),
microsecond(ChronoField.MICRO_OF_SECOND, 0),
nanosecond(ChronoField.NANO_OF_SECOND, 0),
// Read only accessors (not assignable)
weekYear(IsoFields.WEEK_BASED_YEAR, 0) //
{ //
@Override
void assign(Builder> builder, AnyValue value) {
throw new UnsupportedTemporalUnitException("Not supported: " + name());
}
},
offset //
{ //
@Override
void assign(Builder> builder, AnyValue value) {
throw new UnsupportedTemporalUnitException("Not supported: " + name());
}
},
offsetMinutes //
{ //
@Override
void assign(Builder> builder, AnyValue value) {
throw new UnsupportedTemporalUnitException("Not supported: " + name());
}
},
offsetSeconds //
{ //
@Override
void assign(Builder> builder, AnyValue value) {
throw new UnsupportedTemporalUnitException("Not supported: " + name());
}
},
// time zone
timezone //
{ //
@Override
void assign(Builder> builder, AnyValue value) {
if (!builder.supportsTimeZone()) {
throw new UnsupportedTemporalUnitException(
"Cannot assign time zone if also assigning other fields.");
}
if (builder.timezone != null) {
String timeZ;
if (builder.timezone instanceof Value v) timeZ = v.prettyPrint();
else timeZ = String.valueOf(builder.timezone);
throw InvalidArgumentException.assignTimezoneTwice(timeZ);
}
builder.timezone = value;
}
},
// group selectors
date //
{ //
@Override
void assign(Builder> builder, AnyValue value) {
if (!builder.supportsDate()) {
throw new UnsupportedTemporalUnitException("Not supported: " + name());
}
if (builder.state == null) {
builder.state = new DateTimeBuilder();
}
builder.state = builder.state.assign(this, value);
}
@Override
boolean isGroupSelector() {
return true;
}
},
time //
{ //
@Override
void assign(Builder> builder, AnyValue value) {
if (!builder.supportsTime()) {
throw new UnsupportedTemporalUnitException("Not supported: " + name());
}
if (builder.state == null) {
builder.state = new DateTimeBuilder();
}
builder.state = builder.state.assign(this, value);
}
@Override
boolean isGroupSelector() {
return true;
}
},
datetime //
{ //
@Override
void assign(Builder> builder, AnyValue value) {
if (!builder.supportsDate() || !builder.supportsTime()) {
throw new UnsupportedTemporalUnitException("Not supported: " + name());
}
if (builder.state == null) {
builder.state = new DateTimeBuilder();
}
builder.state = builder.state.assign(this, value);
}
@Override
boolean isGroupSelector() {
return true;
}
},
epochSeconds //
{ //
@Override
void assign(Builder> builder, AnyValue value) {
if (!builder.supportsEpoch()) {
throw new UnsupportedTemporalUnitException("Not supported: " + name());
}
if (builder.state == null) {
builder.state = new DateTimeBuilder();
}
builder.state = builder.state.assign(this, value);
}
@Override
boolean isGroupSelector() {
return true;
}
},
epochMillis //
{ //
@Override
void assign(Builder> builder, AnyValue value) {
if (!builder.supportsEpoch()) {
throw new UnsupportedTemporalUnitException("Not supported: " + name());
}
if (builder.state == null) {
builder.state = new DateTimeBuilder();
}
builder.state = builder.state.assign(this, value);
}
@Override
boolean isGroupSelector() {
return true;
}
};
private static final Map fields = new HashMap<>();
static {
for (TemporalFields field : values()) {
fields.put(field.name().toLowerCase(Locale.ROOT), field);
}
// aliases
fields.put("weekday", dayOfWeek);
fields.put("quarterday", dayOfQuarter);
}
final TemporalField field;
final int defaultValue;
TemporalFields(TemporalField field, int defaultValue) {
this.field = field;
this.defaultValue = defaultValue;
}
TemporalFields() {
this.field = null;
this.defaultValue = -1;
}
boolean isGroupSelector() {
return false;
}
void assign(Builder> builder, AnyValue value) {
assert field != null : "method should have been overridden";
if (!builder.supports(field)) {
throw new UnsupportedTemporalUnitException("Not supported: " + name());
}
if (builder.state == null) {
builder.state = new DateTimeBuilder();
}
builder.state = builder.state.assign(this, value);
}
public static Set allFields() {
return fields.keySet();
}
}
private static class DateTimeBuilder {
protected DateBuilder date;
protected ConstructTime time;
DateTimeBuilder() {}
DateTimeBuilder(DateBuilder date, ConstructTime time) {
this.date = date;
this.time = time;
}
void checkAssignments(boolean requiresDate) {
if (date != null) {
date.checkAssignments();
}
if (time != null) {
if (requiresDate) {
if (date != null) {
date.assertFullyAssigned();
} else {
throw InvalidArgumentException.mustSpecifyField(TemporalFields.year.name());
}
}
time.checkAssignments();
}
}
DateTimeBuilder assign(TemporalFields field, AnyValue value) {
if (field == TemporalFields.datetime
|| field == TemporalFields.epochSeconds
|| field == TemporalFields.epochMillis) {
return new SelectDateTimeDTBuilder(date, time).assign(field, value);
} else if (field == TemporalFields.time || field == TemporalFields.date) {
return new SelectDateOrTimeDTBuilder(date, time).assign(field, value);
} else {
return assignToSubBuilders(field, value);
}
}
DateTimeBuilder assignToSubBuilders(TemporalFields field, AnyValue value) {
if (field == TemporalFields.date || field.field != null && field.field.isDateBased()) {
if (date == null) {
date = new ConstructDate();
}
date = date.assign(field, value);
} else if (field == TemporalFields.time || field.field != null && field.field.isTimeBased()) {
if (time == null) {
time = new ConstructTime();
}
time.assign(field, value);
} else {
throw new IllegalStateException(
"This method should not be used for any fields the DateBuilder or TimeBuilder can't handle");
}
return this;
}
}
private static class SelectDateTimeDTBuilder extends DateTimeBuilder {
private AnyValue datetime;
private AnyValue epochSeconds;
private AnyValue epochMillis;
SelectDateTimeDTBuilder(DateBuilder date, ConstructTime time) {
super(date, time);
}
@Override
void checkAssignments(boolean requiresDate) {
// Nothing to do
}
@Override
DateTimeBuilder assign(TemporalFields field, AnyValue value) {
if (field == TemporalFields.date || field == TemporalFields.time) {
throw InvalidArgumentException.temporalSelectionConflict(
field.name(), "datetime or epochSeconds or epochMillis");
} else if (field == TemporalFields.datetime) {
if (epochSeconds != null) {
throw InvalidArgumentException.temporalSelectionConflict(field.name(), "epochSeconds");
} else if (epochMillis != null) {
throw InvalidArgumentException.temporalSelectionConflict(field.name(), "epochMillis");
}
datetime = assignment(TemporalFields.datetime, datetime, value);
} else if (field == TemporalFields.epochSeconds) {
if (epochMillis != null) {
throw InvalidArgumentException.temporalSelectionConflict(field.name(), "epochMillis");
} else if (datetime != null) {
throw InvalidArgumentException.temporalSelectionConflict(field.name(), "datetime");
}
epochSeconds = assignment(TemporalFields.epochSeconds, epochSeconds, value);
} else if (field == TemporalFields.epochMillis) {
if (epochSeconds != null) {
throw InvalidArgumentException.temporalSelectionConflict(field.name(), "epochSeconds");
} else if (datetime != null) {
throw InvalidArgumentException.temporalSelectionConflict(field.name(), "datetime");
}
epochMillis = assignment(TemporalFields.epochMillis, epochMillis, value);
} else {
return assignToSubBuilders(field, value);
}
return this;
}
}
private static class SelectDateOrTimeDTBuilder extends DateTimeBuilder {
SelectDateOrTimeDTBuilder(DateBuilder date, ConstructTime time) {
super(date, time);
}
@Override
DateTimeBuilder assign(TemporalFields field, AnyValue value) {
if (field == TemporalFields.datetime
|| field == TemporalFields.epochSeconds
|| field == TemporalFields.epochMillis) {
throw InvalidArgumentException.temporalSelectionConflict(field.name(), "date or time");
} else {
return assignToSubBuilders(field, value);
}
}
}
private abstract static class DateBuilder {
abstract DateBuilder assign(TemporalFields field, AnyValue value);
abstract void checkAssignments();
abstract void assertFullyAssigned();
}
private static final class ConstructTime {
private AnyValue hour;
private AnyValue minute;
private AnyValue second;
private AnyValue millisecond;
private AnyValue microsecond;
private AnyValue nanosecond;
private AnyValue time;
ConstructTime() {}
void assign(TemporalFields field, AnyValue value) {
switch (field) {
case hour:
hour = assignment(field, hour, value);
break;
case minute:
minute = assignment(field, minute, value);
break;
case second:
second = assignment(field, second, value);
break;
case millisecond:
millisecond = assignment(field, millisecond, value);
break;
case microsecond:
microsecond = assignment(field, microsecond, value);
break;
case nanosecond:
nanosecond = assignment(field, nanosecond, value);
break;
case time:
case datetime:
time = assignment(field, time, value);
break;
default:
throw new IllegalStateException("Not a time field: " + field);
}
}
void checkAssignments() {
if (time == null) {
assertDefinedInOrder(
Pair.of(hour, "hour"),
Pair.of(minute, "minute"),
Pair.of(second, "second"),
Pair.of(oneOf(millisecond, microsecond, nanosecond), "subsecond"));
}
}
}
private static class ConstructDate extends DateBuilder {
AnyValue year;
AnyValue date;
@Override
ConstructDate assign(TemporalFields field, AnyValue value) {
switch (field) {
case year:
year = assignment(field, year, value);
return this;
case quarter:
case dayOfQuarter:
return new QuarterDate(year, date).assign(field, value);
case month:
case day:
return new CalendarDate(year, date).assign(field, value);
case week:
case dayOfWeek:
return new WeekDate(year, date).assign(field, value);
case ordinalDay:
return new OrdinalDate(year, date).assign(field, value);
case date:
case datetime:
date = assignment(field, date, value);
return this;
default:
throw new IllegalStateException("Not a date field: " + field);
}
}
@Override
void checkAssignments() {
// Nothing to do
}
@Override
void assertFullyAssigned() {
if (date == null) {
throw InvalidArgumentException.mustSpecifyField(TemporalFields.month.name());
}
}
}
private static final class CalendarDate extends ConstructDate {
private AnyValue month;
private AnyValue day;
CalendarDate(AnyValue year, AnyValue date) {
this.year = year;
this.date = date;
}
@Override
ConstructDate assign(TemporalFields field, AnyValue value) {
switch (field) {
case year:
year = assignment(field, year, value);
return this;
case month:
month = assignment(field, month, value);
return this;
case day:
day = assignment(field, day, value);
return this;
case date:
case datetime:
date = assignment(field, date, value);
return this;
default:
throw new UnsupportedTemporalUnitException("Cannot assign " + field + " to calendar date.");
}
}
@Override
void checkAssignments() {
if (date == null) {
assertDefinedInOrder(Pair.of(year, "year"), Pair.of(month, "month"), Pair.of(day, "day"));
}
}
@Override
void assertFullyAssigned() {
if (date == null) {
assertAllDefined(Pair.of(year, "year"), Pair.of(month, "month"), Pair.of(day, "day"));
}
}
}
private static final class WeekDate extends ConstructDate {
private AnyValue week;
private AnyValue dayOfWeek;
WeekDate(AnyValue year, AnyValue date) {
this.year = year;
this.date = date;
}
@Override
ConstructDate assign(TemporalFields field, AnyValue value) {
switch (field) {
case year:
year = assignment(field, year, value);
return this;
case week:
week = assignment(field, week, value);
return this;
case dayOfWeek:
dayOfWeek = assignment(field, dayOfWeek, value);
return this;
case date:
case datetime:
date = assignment(field, date, value);
return this;
default:
throw new UnsupportedTemporalUnitException("Cannot assign " + field + " to week date.");
}
}
@Override
void checkAssignments() {
if (date == null) {
assertDefinedInOrder(Pair.of(year, "year"), Pair.of(week, "week"), Pair.of(dayOfWeek, "dayOfWeek"));
}
}
@Override
void assertFullyAssigned() {
if (date == null) {
assertAllDefined(Pair.of(year, "year"), Pair.of(week, "week"), Pair.of(dayOfWeek, "dayOfWeek"));
}
}
}
private static final class QuarterDate extends ConstructDate {
private AnyValue quarter;
private AnyValue dayOfQuarter;
QuarterDate(AnyValue year, AnyValue date) {
this.year = year;
this.date = date;
}
@Override
ConstructDate assign(TemporalFields field, AnyValue value) {
switch (field) {
case year:
year = assignment(field, year, value);
return this;
case quarter:
quarter = assignment(field, quarter, value);
return this;
case dayOfQuarter:
dayOfQuarter = assignment(field, dayOfQuarter, value);
return this;
case date:
case datetime:
date = assignment(field, date, value);
return this;
default:
throw new UnsupportedTemporalUnitException("Cannot assign " + field + " to quarter date.");
}
}
@Override
void checkAssignments() {
if (date == null) {
assertDefinedInOrder(
Pair.of(year, "year"), Pair.of(quarter, "quarter"), Pair.of(dayOfQuarter, "dayOfQuarter"));
}
}
@Override
void assertFullyAssigned() {
if (date == null) {
assertAllDefined(
Pair.of(year, "year"), Pair.of(quarter, "quarter"), Pair.of(dayOfQuarter, "dayOfQuarter"));
}
}
}
private static final class OrdinalDate extends ConstructDate {
private AnyValue ordinalDay;
OrdinalDate(AnyValue year, AnyValue date) {
this.year = year;
this.date = date;
}
@Override
ConstructDate assign(TemporalFields field, AnyValue value) {
switch (field) {
case year:
year = assignment(field, year, value);
return this;
case ordinalDay:
ordinalDay = assignment(field, ordinalDay, value);
return this;
case date:
case datetime:
date = assignment(field, date, value);
return this;
default:
throw new UnsupportedTemporalUnitException("Cannot assign " + field + " to ordinal date.");
}
}
@Override
void assertFullyAssigned() {
if (date == null) {
assertAllDefined(Pair.of(year, "year"), Pair.of(ordinalDay, "ordinalDay"));
}
}
}
private static AnyValue assignment(TemporalFields field, AnyValue oldValue, AnyValue newValue) {
if (oldValue != null) {
String prettyVal = newValue instanceof Value v ? v.prettyPrint() : String.valueOf(newValue);
throw InvalidArgumentException.cannotReassign(prettyVal, String.valueOf(field));
}
return newValue;
}
@SafeVarargs
static void assertDefinedInOrder(Pair... values) {
if (values[0].first() == null) {
throw InvalidArgumentException.mustSpecifyField(values[0].other());
}
String firstNotAssigned = null;
for (Pair value : values) {
if (value.first() == null) {
if (firstNotAssigned == null) {
firstNotAssigned = value.other();
}
} else if (firstNotAssigned != null) {
throw InvalidArgumentException.cannotSpecifyWithout(value.other(), firstNotAssigned);
}
}
}
@SafeVarargs
static void assertAllDefined(Pair... values) {
for (Pair value : values) {
if (value.first() == null) {
throw InvalidArgumentException.mustSpecifyField(value.other());
}
}
}
static org.neo4j.values.AnyValue oneOf(
org.neo4j.values.AnyValue a, org.neo4j.values.AnyValue b, org.neo4j.values.AnyValue c) {
return a != null ? a : b != null ? b : c;
}
static ZoneId timezoneOf(AnyValue timezone) {
if (timezone instanceof TextValue) {
return parseZoneName(((TextValue) timezone).stringValue());
}
throw new UnsupportedOperationException("Cannot convert to ZoneId: " + timezone);
}
private static int validNano(AnyValue millisecond, AnyValue microsecond, AnyValue nanosecond) {
long ms = safeCastIntegral("millisecond", millisecond, TemporalFields.millisecond.defaultValue);
long us = safeCastIntegral("microsecond", microsecond, TemporalFields.microsecond.defaultValue);
long ns = safeCastIntegral("nanosecond", nanosecond, TemporalFields.nanosecond.defaultValue);
if (ms < 0 || ms >= 1000) {
final long milliLimit = 1000L;
throw InvalidArgumentException.invalidMillisecondValue(milliLimit, ms);
}
final long microLimit = (millisecond != null ? 1000L : 1000_000L);
if (us < 0 || us >= microLimit) {
throw InvalidArgumentException.invalidMicrosecondValue(microLimit, us);
}
final long nanoLimit = (microsecond != null ? 1000L : millisecond != null ? 1000_000L : 1000_000_000L);
if (ns < 0 || ns >= nanoLimit) {
throw InvalidArgumentException.invalidNanosecondValue(nanoLimit, ns);
}
return (int) (ms * 1000_000 + us * 1000 + ns);
}
static VALUE updateFieldMapWithConflictingSubseconds(
MapValue fields, TemporalUnit unit, TEMP temporal, BiFunction mapFunction) {
boolean conflictingMilliSeconds =
unit == ChronoUnit.MILLIS && (fields.containsKey("microsecond") || fields.containsKey("nanosecond"));
boolean conflictingMicroSeconds = unit == ChronoUnit.MICROS && fields.containsKey("nanosecond");
if (conflictingMilliSeconds) {
AnyValue millis = Values.intValue(temporal.get(ChronoField.MILLI_OF_SECOND));
AnyValue micros = fields.get("microsecond");
AnyValue nanos = fields.get("nanosecond");
int newNanos = validNano(millis, micros, nanos);
TEMP newTemporal = (TEMP) temporal.with(ChronoField.NANO_OF_SECOND, newNanos);
MapValue filtered = fields.filter((k, ignore) -> !k.equals("microsecond") && !k.equals("nanosecond"));
return mapFunction.apply(filtered, newTemporal);
} else if (conflictingMicroSeconds) {
AnyValue micros = Values.intValue(temporal.get(ChronoField.MICRO_OF_SECOND));
AnyValue nanos = fields.get("nanosecond");
int newNanos = validNano(null, micros, nanos);
TEMP newTemporal = (TEMP) temporal.with(ChronoField.NANO_OF_SECOND, newNanos);
MapValue filtered = fields.filter((k, ignore) -> !k.equals("nanosecond"));
return mapFunction.apply(filtered, newTemporal);
} else {
return mapFunction.apply(fields, temporal);
}
}
static TEMP assertValidArgument(Supplier func) {
try {
return func.get();
} catch (DateTimeException e) {
throw new InvalidArgumentException(e.getMessage(), e);
}
}
static TEMP assertValidUnit(TemporalUnit unit, Supplier func) {
try {
return func.get();
} catch (DateTimeException e) {
String prettyVal = unit instanceof Value v ? v.prettyPrint() : String.valueOf(unit);
throw UnsupportedTemporalUnitException.cannotProcess(prettyVal, e);
}
}
static OFFSET assertValidZone(Supplier func) {
try {
return func.get();
} catch (DateTimeException e) {
throw new InvalidArgumentException(e.getMessage(), e);
}
}
static TEMP assertParsable(String input, Supplier func) {
try {
return func.get();
} catch (DateTimeException e) {
throw TemporalParseException.cannotProcessDateTime(input, e);
}
}
static String assertPrintable(String input, Supplier func) {
try {
return func.get();
} catch (DateTimeException e) {
throw TemporalParseException.cannotProcessDateTime(input, e);
}
}
static TEMP assertValidArithmetic(Supplier func) {
try {
return func.get();
} catch (DateTimeException | ArithmeticException e) {
throw new ArithmeticException(e.getMessage(), e);
}
}
static Pair getTruncatedDateAndTime(TemporalUnit unit, TemporalValue input, String type) {
if (unit.isTimeBased() && !(input instanceof DateTimeValue || input instanceof LocalDateTimeValue)) {
throw new UnsupportedTemporalUnitException(
String.format("Cannot truncate %s to %s with a time based unit.", input, type));
}
LocalDate localDate = input.getDatePart();
LocalTime localTime = input.hasTime() ? input.getLocalTimePart() : LocalTimeValue.DEFAULT_LOCAL_TIME;
LocalTime truncatedTime;
LocalDate truncatedDate;
if (unit.isDateBased()) {
truncatedDate = DateValue.truncateTo(localDate, unit);
truncatedTime = LocalTimeValue.DEFAULT_LOCAL_TIME;
} else {
truncatedDate = localDate;
truncatedTime = localTime.truncatedTo(unit);
}
return Pair.of(truncatedDate, truncatedTime);
}
static class TimeCSVHeaderInformation implements CSVHeaderInformation {
String timezone;
@Override
public void assign(String key, Object valueObj) {
if (!(valueObj instanceof String value)) {
String prettyVal = valueObj instanceof Value v ? v.prettyPrint() : String.valueOf(valueObj);
throw InvalidArgumentException.cannotAssignNonStringTimezone(String.valueOf(valueObj), prettyVal, key);
}
if ("timezone".equalsIgnoreCase(key)) {
if (timezone == null) {
timezone = value;
} else {
throw InvalidArgumentException.setTimeZoneTwice(value);
}
} else {
throw InvalidArgumentException.unsupportedHeader(key, value);
}
}
Supplier zoneSupplier(Supplier defaultSupplier) {
if (timezone != null) {
ZoneId tz = DateTimeValue.parseZoneName(timezone);
// Override defaultZone
return () -> tz;
}
return defaultSupplier;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TimeCSVHeaderInformation that = (TimeCSVHeaderInformation) o;
return Objects.equals(timezone, that.timezone);
}
@Override
public int hashCode() {
return Objects.hash(timezone);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy