From 6b00f5b1ed0d63e962ac1dd12f7088d576558ef7 Mon Sep 17 00:00:00 2001 From: Davy Landman Date: Wed, 9 Jun 2021 10:38:14 +0200 Subject: [PATCH 01/10] Making steps toward java-time api --- src/org/rascalmpl/library/Prelude.java | 361 +++++++++++++------------ 1 file changed, 189 insertions(+), 172 deletions(-) diff --git a/src/org/rascalmpl/library/Prelude.java b/src/org/rascalmpl/library/Prelude.java index 8ba5cc89e8c..30a2fdd3894 100644 --- a/src/org/rascalmpl/library/Prelude.java +++ b/src/org/rascalmpl/library/Prelude.java @@ -45,6 +45,19 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.ParseException; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.OffsetTime; +import java.time.Period; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; +import java.time.temporal.IsoFields; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalUnit; import java.util.Arrays; import java.util.Base64; import java.util.Collections; @@ -63,6 +76,11 @@ import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; +import com.ibm.icu.text.SimpleDateFormat; +import com.ibm.icu.util.Calendar; +import com.ibm.icu.util.TimeZone; +import com.ibm.icu.util.ULocale; + import org.apache.commons.lang.CharSetUtils; import org.rascalmpl.exceptions.RuntimeExceptionFactory; import org.rascalmpl.exceptions.Throw; @@ -72,13 +90,13 @@ import org.rascalmpl.unicode.UnicodeDetector; import org.rascalmpl.unicode.UnicodeOffsetLengthReader; import org.rascalmpl.unicode.UnicodeOutputStreamWriter; +import org.rascalmpl.uri.ISourceLocationWatcher.ISourceLocationChangeType; +import org.rascalmpl.uri.ISourceLocationWatcher.ISourceLocationChanged; +import org.rascalmpl.uri.ISourceLocationWatcher.ISourceLocationType; import org.rascalmpl.uri.LogicalMapResolver; import org.rascalmpl.uri.URIResolverRegistry; import org.rascalmpl.uri.URIUtil; import org.rascalmpl.uri.UnsupportedSchemeException; -import org.rascalmpl.uri.ISourceLocationWatcher.ISourceLocationChangeType; -import org.rascalmpl.uri.ISourceLocationWatcher.ISourceLocationChanged; -import org.rascalmpl.uri.ISourceLocationWatcher.ISourceLocationType; import org.rascalmpl.values.IRascalValueFactory; import org.rascalmpl.values.RascalValueFactory; import org.rascalmpl.values.functions.IFunction; @@ -88,11 +106,6 @@ import org.rascalmpl.values.parsetrees.TreeAdapter; import org.rascalmpl.values.parsetrees.visitors.TreeVisitor; -import com.ibm.icu.text.SimpleDateFormat; -import com.ibm.icu.util.Calendar; -import com.ibm.icu.util.TimeZone; -import com.ibm.icu.util.ULocale; - import io.usethesource.vallang.IBool; import io.usethesource.vallang.IConstructor; import io.usethesource.vallang.IDateTime; @@ -119,21 +132,21 @@ import io.usethesource.vallang.type.Type; import io.usethesource.vallang.type.TypeFactory; import io.usethesource.vallang.type.TypeStore; -import io.usethesource.vallang.visitors.IValueVisitor; +import jnr.ffi.Struct.Offset; public class Prelude { private static final int FILE_BUFFER_SIZE = 8 * 1024; protected final IValueFactory values; protected final IRascalValueFactory rascalValues; private final Random random; - + private final boolean trackIO = System.getenv("TRACKIO") != null; - private final PrintWriter out; + private final PrintWriter out; private final TypeStore store; - + public Prelude(IValueFactory values, IRascalValueFactory rascalValues, PrintWriter out, TypeStore store) { super(); - + this.values = values; this.rascalValues = rascalValues; this.store = store; @@ -142,194 +155,196 @@ public Prelude(IValueFactory values, IRascalValueFactory rascalValues, PrintWrit random = new Random(); } - private IValue createRandomValue(Type t, int depth, int width) { - return t.randomValue(random, values, new TypeStore(), Collections.emptyMap(), depth, width); - } + private IValue createRandomValue(Type t, int depth, int width) { + return t.randomValue(random, values, new TypeStore(), Collections.emptyMap(), depth, width); + } + - /* * Boolean */ - - - public IValue arbBool() // get an arbitrary boolean value.} + + + public IValue arbBool() // get an arbitrary boolean value.} { - return values.bool(random.nextInt(2) == 1); + return values.bool(random.nextInt(2) == 1); } - + /* * DateTime */ public IValue now() - //@doc{Get the current datetime.} + // @doc{Get the current datetime.} { - return values.datetime(Calendar.getInstance().getTimeInMillis()); + return values.datetime(Instant.now().toEpochMilli()); } - public IValue createDate(IInteger year, IInteger month, IInteger day) - //@doc{Create a new date.} + public IValue createDate(IInteger year, IInteger month, IInteger day) + // @doc{Create a new date.} { return values.date(year.intValue(), month.intValue(), day.intValue()); } - - public IValue createTime(IInteger hour, IInteger minute, IInteger second, - IInteger millisecond) - //@doc{Create a new time.} + + public IValue createTime(IInteger hour, IInteger minute, IInteger second, IInteger millisecond) + // @doc{Create a new time.} { return values.time(hour.intValue(), minute.intValue(), second.intValue(), millisecond.intValue()); } - public IValue createTime(IInteger hour, IInteger minute, IInteger second, - IInteger millisecond, IInteger timezoneHourOffset, IInteger timezoneMinuteOffset) - //@doc{Create a new time with the given numeric timezone offset.} + public IValue createTime(IInteger hour, IInteger minute, IInteger second, IInteger millisecond, + IInteger timezoneHourOffset, IInteger timezoneMinuteOffset) + // @doc{Create a new time with the given numeric timezone offset.} { - return values.time(hour.intValue(), minute.intValue(), second.intValue(), - millisecond.intValue(), timezoneHourOffset.intValue(), timezoneMinuteOffset.intValue()); + return values.time(hour.intValue(), minute.intValue(), second.intValue(), millisecond.intValue(), + timezoneHourOffset.intValue(), timezoneMinuteOffset.intValue()); } - - public IValue createDateTime(IInteger year, IInteger month, IInteger day, - IInteger hour, IInteger minute, IInteger second, IInteger millisecond) - //@doc{Create a new datetime.} + + public IValue createDateTime(IInteger year, IInteger month, IInteger day, IInteger hour, IInteger minute, + IInteger second, IInteger millisecond) + // @doc{Create a new datetime.} { - return values.datetime(year.intValue(), month.intValue(), day.intValue(), hour.intValue(), - minute.intValue(), second.intValue(), millisecond.intValue()); + return values.datetime(year.intValue(), month.intValue(), day.intValue(), hour.intValue(), minute.intValue(), + second.intValue(), millisecond.intValue()); } - public IValue createDateTime(IInteger year, IInteger month, IInteger day, - IInteger hour, IInteger minute, IInteger second, IInteger millisecond, - IInteger timezoneHourOffset, IInteger timezoneMinuteOffset) - //@doc{Create a new datetime with the given numeric timezone offset.} + public IValue createDateTime(IInteger year, IInteger month, IInteger day, IInteger hour, IInteger minute, + IInteger second, IInteger millisecond, IInteger timezoneHourOffset, IInteger timezoneMinuteOffset) + // @doc{Create a new datetime with the given numeric timezone offset.} { - return values.datetime(year.intValue(), month.intValue(), day.intValue(), hour.intValue(), - minute.intValue(), second.intValue(), millisecond.intValue(), timezoneHourOffset.intValue(), - timezoneMinuteOffset.intValue()); + return values.datetime(year.intValue(), month.intValue(), day.intValue(), hour.intValue(), minute.intValue(), + second.intValue(), millisecond.intValue(), timezoneHourOffset.intValue(), timezoneMinuteOffset.intValue()); } - - + + public IDateTime arbDateTime() { - return (IDateTime) createRandomValue(TypeFactory.getInstance().dateTimeType(), 5, 5); + return (IDateTime) createRandomValue(TypeFactory.getInstance().dateTimeType(), 5, 5); } + public IValue joinDateAndTime(IDateTime date, IDateTime time) - //@doc{Create a new datetime by combining a date and a time.} + // @doc{Create a new datetime by combining a date and a time.} { - return values.datetime(date.getYear(), date.getMonthOfYear(), date.getDayOfMonth(), - time.getHourOfDay(), time.getMinuteOfHour(), time.getSecondOfMinute(), - time.getMillisecondsOfSecond(), time.getTimezoneOffsetHours(), time.getTimezoneOffsetMinutes()); + return values.datetime(date.getYear(), date.getMonthOfYear(), date.getDayOfMonth(), time.getHourOfDay(), + time.getMinuteOfHour(), time.getSecondOfMinute(), time.getMillisecondsOfSecond(), + time.getTimezoneOffsetHours(), time.getTimezoneOffsetMinutes()); } public IValue splitDateTime(IDateTime dt) - //@doc{Split an existing datetime into a tuple with the date and the time.} + // @doc{Split an existing datetime into a tuple with the date and the time.} { return values.tuple(values.date(dt.getYear(), dt.getMonthOfYear(), dt.getDayOfMonth()), - values.time(dt.getHourOfDay(), dt.getMinuteOfHour(), dt.getSecondOfMinute(), - dt.getMillisecondsOfSecond(), dt.getTimezoneOffsetHours(), dt.getTimezoneOffsetMinutes())); + values.time(dt.getHourOfDay(), dt.getMinuteOfHour(), dt.getSecondOfMinute(), dt.getMillisecondsOfSecond(), + dt.getTimezoneOffsetHours(), dt.getTimezoneOffsetMinutes())); } - - + + public IValue incrementYears(IDateTime dt, IInteger n) - //@doc{Increment the years by a given amount.} + // @doc{Increment the years by a given amount.} { - return incrementDate(dt, Calendar.YEAR, "years", n); + return incrementDate(dt, ChronoUnit.YEARS, "years", n); } - + public IValue incrementMonths(IDateTime dt, IInteger n) - //@doc{Increment the months by a given amount.} + // @doc{Increment the months by a given amount.} { - return incrementDate(dt, Calendar.MONTH, "months", n); + return incrementDate(dt, ChronoUnit.MONTHS, "months", n); } public IValue incrementDays(IDateTime dt, IInteger n) - //@doc{Increment the days by a given amount.} + // @doc{Increment the days by a given amount.} { - return incrementDate(dt, Calendar.DAY_OF_MONTH, "days", n); + return incrementDate(dt, ChronoUnit.DAYS, "days", n); } private String getTZString(int hourOffset, int minuteOffset) { - String tzString = "GMT" + - ((hourOffset < 0 || (0 == hourOffset && minuteOffset < 0)) ? "-" : "+") + - String.format("%02d",hourOffset >= 0 ? hourOffset : hourOffset * -1) + - String.format("%02d",minuteOffset >= 0 ? minuteOffset : minuteOffset * -1); + String tzString = "GMT" + ((hourOffset < 0 || (0 == hourOffset && minuteOffset < 0)) ? "-" : "+") + + String.format("%02d", hourOffset >= 0 ? hourOffset : hourOffset * -1) + + String.format("%02d", minuteOffset >= 0 ? minuteOffset : minuteOffset * -1); return tzString; } private final int millisInAMinute = 1000 * 60; private final int millisInAnHour = millisInAMinute * 60; - private IValue incrementDTField(IDateTime dt, int field, IInteger amount) { - Calendar cal = null; + private IValue incrementDTField(IDateTime dt, ChronoUnit field, IInteger amount) { + Temporal actualDt = dateTimeToJava(dt); + Temporal result = actualDt.plus(amount.longValue(), field); - cal = dateTimeToCalendar(dt); - - // Make sure lenient is true, since this allows wrapping of fields. For - // instance, if you have $2012-05-15, and subtract 15 months, this is - // an error if lenient is false, but gives $2012-02-15 (as expected) - // if lenient is true. - cal.setLenient(true); - - cal.add(field, amount.intValue()); - - // Turn the calendar back into a date, time, or datetime value if (dt.isDate()) { - return calendarToDate(cal); - } else { - if (dt.isTime()) { - return calendarToTime(cal); - } else { - return calendarToDateTime(cal); - } + return temporalToDate(result); + } + else if (dt.isTime()) { + return temporalToTime(result); } + return temporalToDateTime(result); } - private IValue calendarToDateTime(Calendar cal) { - int timezoneHours = cal.get(Calendar.ZONE_OFFSET) / millisInAnHour; - int timezoneMinutes = cal.get(Calendar.ZONE_OFFSET) % millisInAnHour / millisInAMinute; - return createDateTime(values.integer(cal.get(Calendar.YEAR)), - values.integer(cal.get(Calendar.MONTH)+1), - values.integer(cal.get(Calendar.DAY_OF_MONTH)), - values.integer(cal.get(Calendar.HOUR_OF_DAY)), - values.integer(cal.get(Calendar.MINUTE)), - values.integer(cal.get(Calendar.SECOND)), - values.integer(cal.get(Calendar.MILLISECOND)), - values.integer(timezoneHours), - values.integer(timezoneMinutes)); + private IDateTime temporalToDate(Temporal t) { + return values.date( + t.get(ChronoField.YEAR), + t.get(ChronoField.MONTH_OF_YEAR), + t.get(ChronoField.DAY_OF_MONTH) + ); } - private IValue calendarToTime(Calendar cal) { - int timezoneHours = cal.get(Calendar.ZONE_OFFSET) / millisInAnHour; - int timezoneMinutes = cal.get(Calendar.ZONE_OFFSET) % millisInAnHour / millisInAMinute; - return createTime(values.integer(cal.get(Calendar.HOUR_OF_DAY)), - values.integer(cal.get(Calendar.MINUTE)), - values.integer(cal.get(Calendar.SECOND)), - values.integer(cal.get(Calendar.MILLISECOND)), - values.integer(timezoneHours), - values.integer(timezoneMinutes)); + private IDateTime temporalToTime(Temporal t) { + return values.time( + t.get(ChronoField.HOUR_OF_DAY), + t.get(ChronoField.MINUTE_OF_HOUR), + t.get(ChronoField.SECOND_OF_MINUTE), + t.get(ChronoField.MILLI_OF_SECOND), + (int)TimeUnit.HOURS.convert(t.get(ChronoField.OFFSET_SECONDS), TimeUnit.SECONDS), + (int)(TimeUnit.MINUTES.convert(t.get(ChronoField.OFFSET_SECONDS), TimeUnit.SECONDS) % 60) + ); } - private IValue calendarToDate(Calendar cal) { - return createDate(values.integer(cal.get(Calendar.YEAR)), - values.integer(cal.get(Calendar.MONTH)+1), - values.integer(cal.get(Calendar.DAY_OF_MONTH))); + private IDateTime temporalToDateTime(Temporal t) { + return values.datetime( + t.get(ChronoField.YEAR), + t.get(ChronoField.MONTH_OF_YEAR), + t.get(ChronoField.DAY_OF_MONTH), + t.get(ChronoField.HOUR_OF_DAY), + t.get(ChronoField.MINUTE_OF_HOUR), + t.get(ChronoField.SECOND_OF_MINUTE), + t.get(ChronoField.MILLI_OF_SECOND), + (int)TimeUnit.HOURS.convert(t.get(ChronoField.OFFSET_SECONDS), TimeUnit.SECONDS), + (int)(TimeUnit.MINUTES.convert(t.get(ChronoField.OFFSET_SECONDS), TimeUnit.SECONDS) % 60) + ); } - private Calendar dateTimeToCalendar(IDateTime dt) { - TimeZone tz = dt.isDate() ? - TimeZone.getDefault() : - TimeZone.getTimeZone(getTZString(dt.getTimezoneOffsetHours(), dt.getTimezoneOffsetMinutes())); - - Calendar cal = Calendar.getInstance(tz,Locale.getDefault()); - cal.setTimeInMillis(dt.getInstant()); - - return cal; + private Temporal dateTimeToJava(IDateTime dt) { + LocalDate datePart = null; + if (!dt.isTime()) { + datePart = LocalDate.of(dt.getYear(), dt.getMonthOfYear(), dt.getDayOfMonth()); + } + + OffsetTime timePart = null; + if (!dt.isDate()) { + // vallang always has timezone offset in case it's not a date + timePart = OffsetTime.of( + dt.getHourOfDay(), + dt.getMinuteOfHour(), + dt.getSecondOfMinute(), + (int)TimeUnit.MILLISECONDS.toNanos(dt.getMillisecondsOfSecond()), + ZoneOffset.ofHoursMinutes(dt.getTimezoneOffsetHours(), dt.getTimezoneOffsetMinutes())); + } + if (datePart == null && timePart != null) { + return timePart; + } + if (timePart == null && datePart != null) { + return datePart; + } + assert timePart != null && datePart != null; + return datePart.atTime(timePart); } - private IValue incrementTime(IDateTime dt, int field, String fieldName, IInteger amount) { + private IValue incrementTime(IDateTime dt, ChronoUnit field, String fieldName, IInteger amount) { if (dt.isDate()) throw RuntimeExceptionFactory.invalidUseOfDateException("Cannot increment the " + fieldName + " on a date value."); return incrementDTField(dt, field, amount); } - private IValue incrementDate(IDateTime dt, int field, String fieldName, IInteger amount) { + private IValue incrementDate(IDateTime dt, ChronoUnit field, String fieldName, IInteger amount) { if (dt.isTime()) throw RuntimeExceptionFactory.invalidUseOfDateException("Cannot increment the " + fieldName + " on a time value."); @@ -339,99 +354,101 @@ private IValue incrementDate(IDateTime dt, int field, String fieldName, IInteger public IValue incrementHours(IDateTime dt, IInteger n) //@doc{Increment the hours by a given amount.} { - return incrementTime(dt, Calendar.HOUR_OF_DAY, "hours", n); + return incrementTime(dt, ChronoUnit.HOURS, "hours", n); } public IValue incrementMinutes(IDateTime dt, IInteger n) //@doc{Increment the minutes by a given amount.} { - return incrementTime(dt, Calendar.MINUTE, "minutes", n); + return incrementTime(dt, ChronoUnit.MINUTES, "minutes", n); } public IValue incrementSeconds(IDateTime dt, IInteger n) //@doc{Increment the seconds by a given amount.} { - return incrementTime(dt, Calendar.SECOND, "seconds", n); + return incrementTime(dt, ChronoUnit.SECONDS, "seconds", n); } public IValue incrementMilliseconds(IDateTime dt, IInteger n) //@doc{Increment the milliseconds by a given amount.} { - return incrementTime(dt, Calendar.MILLISECOND, "milliseconds", n); + return incrementTime(dt, ChronoUnit.MILLIS, "milliseconds", n); } public IValue decrementYears(IDateTime dt, IInteger n) //@doc{Decrement the years by a given amount.} { - return incrementDate(dt, Calendar.YEAR, "years", n.negate()); + return incrementDate(dt, ChronoUnit.YEARS, "years", n.negate()); } public IValue decrementMonths(IDateTime dt, IInteger n) //@doc{Decrement the months by a given amount.} { - return incrementDate(dt, Calendar.MONTH, "months", n.negate()); } + return incrementDate(dt, ChronoUnit.MONTHS, "months", n.negate()); } public IValue decrementDays(IDateTime dt, IInteger n) //@doc{Decrement the days by a given amount.} { - return incrementDate(dt, Calendar.DAY_OF_MONTH, "days", n.negate()); + return incrementDate(dt, ChronoUnit.DAYS, "days", n.negate()); } public IValue decrementHours(IDateTime dt, IInteger n) //@doc{Decrement the hours by a given amount.} { - return incrementTime(dt, Calendar.HOUR_OF_DAY, "hours", n.negate()); + return incrementTime(dt, ChronoUnit.HOURS, "hours", n.negate()); } public IValue decrementMinutes(IDateTime dt, IInteger n) //@doc{Decrement the minutes by a given amount.} { - return incrementTime(dt, Calendar.MINUTE, "minutes", n.negate()); + return incrementTime(dt, ChronoUnit.MINUTES, "minutes", n.negate()); } public IValue decrementSeconds(IDateTime dt, IInteger n) //@doc{Decrement the seconds by a given amount.} { - return incrementTime(dt, Calendar.SECOND, "seconds", n.negate()); + return incrementTime(dt, ChronoUnit.SECONDS, "seconds", n.negate()); } public IValue decrementMilliseconds(IDateTime dt, IInteger n) //@doc{Decrement the milliseconds by a given amount.} { - return incrementTime(dt, Calendar.MILLISECOND, "milliseconds", n.negate()); + return incrementTime(dt, ChronoUnit.MILLIS, "milliseconds", n.negate()); } public IValue createDurationInternal(IDateTime dStart, IDateTime dEnd) { // dStart and dEnd both have to be dates, times, or datetimes - Calendar startCal = Calendar.getInstance(); - startCal.setTimeInMillis(dStart.getInstant()); - Calendar endCal = Calendar.getInstance(); - endCal.setTimeInMillis(dEnd.getInstant()); - - IValue duration = null; - if (dStart.isDate()) { - if (dEnd.isDate()) { - duration = values.tuple( - values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.YEAR)), - values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.MONTH)), - values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.DAY_OF_MONTH)), - values.integer(0), values.integer(0), values.integer(0), - values.integer(0)); - } else if (dEnd.isTime()) { + Temporal startTemp = dateTimeToJava(dStart); + Temporal endTemp = dateTimeToJava(dEnd); + + if (startTemp instanceof LocalDate) { + if (endTemp instanceof LocalDate) { + Period duration = Period.between((LocalDate)startTemp, (LocalDate)endTemp).normalized(); + + return values.tuple( + values.integer(duration.getYears()), + values.integer(duration.getMonths()), + values.integer(duration.getDays()), + values.integer(0), values.integer(0), values.integer(0), + values.integer(0) + ); + } else if (endTemp instanceof OffsetTime) { throw RuntimeExceptionFactory.invalidUseOfTimeException("Cannot determine the duration between a date with no time and a time with no date."); } else { throw RuntimeExceptionFactory.invalidUseOfDateTimeException("Cannot determine the duration between a date with no time and a datetime."); } - } else if (dStart.isTime()) { - if (dEnd.isTime()) { - duration = values.tuple( - values.integer(0), - values.integer(0), - values.integer(0), - values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.HOUR_OF_DAY)), - values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.MINUTE)), - values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.SECOND)), - values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.MILLISECOND))); + } else if (startTemp instanceof OffsetTime) { + if (endTemp instanceof OffsetTime) { + Duration duration = Duration.between(startTemp, endTemp); + return values.tuple( + values.integer(0), + values.integer(0), + values.integer(0), + values.integer(duration.toHours()), + values.integer(duration.toMinutes() % 60), + values.integer(duration.getSeconds() % 60), + values.integer(duration.toMillis() % 1000) + ); } else if (dEnd.isDate()) { throw RuntimeExceptionFactory.invalidUseOfDateException("Cannot determine the duration between a time with no date and a date with no time."); } else { @@ -439,21 +456,21 @@ public IValue createDurationInternal(IDateTime dStart, IDateTime dEnd) { } } else { if (dEnd.isDateTime()) { - duration = values.tuple( - values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.YEAR)), - values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.MONTH)), - values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.DAY_OF_MONTH)), - values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.HOUR_OF_DAY)), - values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.MINUTE)), - values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.SECOND)), - values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.MILLISECOND))); + return values.tuple( + values.integer(ChronoUnit.YEARS.between(startTemp, endTemp)), + values.integer(ChronoUnit.MONTHS.between(startTemp, endTemp) % 12), + values.integer(ChronoUnit.DAYS.between(startTemp, endTemp) % 31), // TODO: this is broken, day of month in durations ?? + values.integer(ChronoUnit.HOURS.between(startTemp, endTemp) % 24), + values.integer(ChronoUnit.MINUTES.between(startTemp, endTemp) % 60), + values.integer(ChronoUnit.SECONDS.between(startTemp, endTemp) % 60), + values.integer(ChronoUnit.MILLIS.between(startTemp, endTemp) % 1000) + ); } else if (dEnd.isDate()) { throw RuntimeExceptionFactory.invalidUseOfDateException("Cannot determine the duration between a datetime and a date with no time."); } else { throw RuntimeExceptionFactory.invalidUseOfTimeException("Cannot determine the duration between a datetime and a time with no date."); } } - return duration; } public IValue parseDate(IString inputDate, IString formatString) From 0624f7abc23037b4c244b135ede8a02ab6b2a6ef Mon Sep 17 00:00:00 2001 From: Davy Landman Date: Wed, 9 Jun 2021 10:42:33 +0200 Subject: [PATCH 02/10] Cleanup imports --- src/org/rascalmpl/library/Prelude.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/org/rascalmpl/library/Prelude.java b/src/org/rascalmpl/library/Prelude.java index 30a2fdd3894..874e23ba9cf 100644 --- a/src/org/rascalmpl/library/Prelude.java +++ b/src/org/rascalmpl/library/Prelude.java @@ -51,13 +51,9 @@ import java.time.OffsetTime; import java.time.Period; import java.time.ZoneOffset; -import java.time.ZonedDateTime; import java.time.temporal.ChronoField; import java.time.temporal.ChronoUnit; -import java.time.temporal.IsoFields; import java.time.temporal.Temporal; -import java.time.temporal.TemporalField; -import java.time.temporal.TemporalUnit; import java.util.Arrays; import java.util.Base64; import java.util.Collections; @@ -132,7 +128,6 @@ import io.usethesource.vallang.type.Type; import io.usethesource.vallang.type.TypeFactory; import io.usethesource.vallang.type.TypeStore; -import jnr.ffi.Struct.Offset; public class Prelude { private static final int FILE_BUFFER_SIZE = 8 * 1024; From f6a312a59c5fcdb5cfd747de35ea1323d7badb52 Mon Sep 17 00:00:00 2001 From: Davy Landman Date: Wed, 9 Jun 2021 11:50:07 +0200 Subject: [PATCH 03/10] Switching to latest version of vallang with better random datetimes and purely java time based backend --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a7defe740ba..fb6adb2b1c2 100644 --- a/pom.xml +++ b/pom.xml @@ -377,7 +377,7 @@ io.usethesource vallang - 0.13.0 + 0.13.2-RC1 org.ow2.asm From d6db2f56547896d68f3acc6d722d3045af06a4b0 Mon Sep 17 00:00:00 2001 From: Davy Landman Date: Wed, 9 Jun 2021 11:50:28 +0200 Subject: [PATCH 04/10] Fixed date time tests affected by better random datetimes --- .../lang/rascal/tests/library/DateTime.rsc | 63 +++++++++++++------ 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/src/org/rascalmpl/library/lang/rascal/tests/library/DateTime.rsc b/src/org/rascalmpl/library/lang/rascal/tests/library/DateTime.rsc index 3cc59e9ac61..bc2a2cd4437 100644 --- a/src/org/rascalmpl/library/lang/rascal/tests/library/DateTime.rsc +++ b/src/org/rascalmpl/library/lang/rascal/tests/library/DateTime.rsc @@ -9,31 +9,55 @@ module lang::rascal::tests::library::DateTime import DateTime; +import util::Math; +import IO; import String; -test bool createDate_sampled(datetime gen) = - date.year == gen.year && date.month == gen.month && date.day == gen.day - when - date := createDate(gen.year, gen.month, gen.day); +test bool createDate_sampled(datetime gen) { + if (gen.isTime) { + return true; + } + date = createDate(gen.year, gen.month, gen.day); + return date.year == gen.year && date.month == gen.month && date.day == gen.day; +} -test bool createTime_sampled(datetime gen) = - time.hour == gen.hour && time.minute == gen.minute && time.second == gen.second && time.millisecond == gen.millisecond - when - time := createTime(gen.hour, gen.minute, gen.second, gen.millisecond); - -test bool printDate_simpleFormat(datetime gen) = - printDate(gen) == formattedDate(gen); +test bool createTime_sampled(datetime gen) { + if (gen.isDate) { + return true; + } + time = createTime(gen.hour, gen.minute, gen.second, gen.millisecond); + return time.hour == gen.hour && time.minute == gen.minute && time.second == gen.second && time.millisecond == gen.millisecond; +} + +test bool printDate_simpleFormat(datetime gen) { + if (!gen.isDate) { + return true; + } + return printDate(gen) == formattedDate(gen); +} -test bool printTime_simpleFormat(datetime gen) = - printTime(gen) == formattedTime(gen); +test bool printTime_simpleFormat(datetime gen) { + if (gen.isDate) { + return true; + } + return printTime(gen) == formattedTime(gen); +} -test bool printDateTime_simpleFormat(datetime gen) = - printDateTime(gen) == " "; +test bool printDateTime_simpleFormat(datetime gen) { + if (!gen.isDateTime) { + return true; + } + return printDateTime(gen) == " "; +} -test bool incrementDays_withOneDay(datetime gen) = - gen.year > 1751 ? incrementDays(createDate(gen.year, gen.month, gen.day)) == incDateByOneDay(gen) : true; - // TIL; apparently before the year 1752 the US was still on the Julian calendar which calculated a leap year every 4 years. The algorithm used here only works for the Gregorian calendar +test bool incrementDays_withOneDay(datetime gen) { + if (gen.isTime) { + return true; + } + // TIL; apparently before the year 1752 the US was still on the Julian calendar which calculated a leap year every 4 years. The algorithm used here only works for the Gregorian calendar + return gen.year > 1751 ? incrementDays(createDate(gen.year, gen.month, gen.day)) == incDateByOneDay(gen) : true; +} // Increment a date by a day according to the Gregorian calendar algorithm for leap year calculation datetime incDateByOneDay(datetime dt) { @@ -70,8 +94,9 @@ str formattedDate(datetime dt) = "--"; str formattedTime(datetime dt) = - "::.+"; + "::."; +bool negativeOffset(datetime dt) = dt.timezoneOffsetHours < 0 || dt.timezoneOffsetMinutes < 0; private str fill(int val) = fill(val, 2); private str fill(int val, int n) = right("", n, "0"); \ No newline at end of file From 269b2f9fccb28a268ba9844c93aeabc796ae5dea Mon Sep 17 00:00:00 2001 From: Davy Landman Date: Wed, 9 Jun 2021 16:48:16 +0200 Subject: [PATCH 05/10] Fixed bugs in json test and json implementations --- src/org/rascalmpl/library/Prelude.java | 26 +- src/org/rascalmpl/library/lang/json/IO.java | 20 +- src/org/rascalmpl/library/lang/json/IO.rsc | 8 +- .../lang/json/io/JSONReadingTypeVisitor.java | 6 +- .../library/lang/json/io/JsonValueReader.java | 320 +++++++++--------- .../library/lang/json/io/JsonValueWriter.java | 53 +-- .../tests/library/lang/json/JSONIOTests.rsc | 11 +- 7 files changed, 237 insertions(+), 207 deletions(-) diff --git a/src/org/rascalmpl/library/Prelude.java b/src/org/rascalmpl/library/Prelude.java index 9290e561c42..1cae7efea04 100644 --- a/src/org/rascalmpl/library/Prelude.java +++ b/src/org/rascalmpl/library/Prelude.java @@ -48,6 +48,7 @@ import java.time.Duration; import java.time.Instant; import java.time.LocalDate; +import java.time.OffsetDateTime; import java.time.OffsetTime; import java.time.Period; import java.time.ZoneOffset; @@ -270,7 +271,7 @@ private IValue incrementDTField(IDateTime dt, ChronoUnit field, IInteger amount) else if (dt.isTime()) { return temporalToTime(result); } - return temporalToDateTime(result); + return temporalToDateTime((OffsetDateTime)result); } private IDateTime temporalToDate(Temporal t) { @@ -292,21 +293,24 @@ private IDateTime temporalToTime(Temporal t) { ); } - private IDateTime temporalToDateTime(Temporal t) { + public static IDateTime temporalToDateTime(IValueFactory values, OffsetDateTime t) { return values.datetime( - t.get(ChronoField.YEAR), - t.get(ChronoField.MONTH_OF_YEAR), - t.get(ChronoField.DAY_OF_MONTH), - t.get(ChronoField.HOUR_OF_DAY), - t.get(ChronoField.MINUTE_OF_HOUR), - t.get(ChronoField.SECOND_OF_MINUTE), + t.getYear(), + t.getMonthValue(), + t.getDayOfMonth(), + t.getHour(), + t.getMinute(), + t.getSecond(), t.get(ChronoField.MILLI_OF_SECOND), - (int)TimeUnit.HOURS.convert(t.get(ChronoField.OFFSET_SECONDS), TimeUnit.SECONDS), - (int)(TimeUnit.MINUTES.convert(t.get(ChronoField.OFFSET_SECONDS), TimeUnit.SECONDS) % 60) + (int)TimeUnit.HOURS.convert(t.getOffset().getTotalSeconds(), TimeUnit.SECONDS), + (int)(TimeUnit.MINUTES.convert(t.getOffset().getTotalSeconds(), TimeUnit.SECONDS) % 60) ); } + private IDateTime temporalToDateTime(OffsetDateTime t) { + return temporalToDateTime(values, t); + } - private Temporal dateTimeToJava(IDateTime dt) { + public static Temporal dateTimeToJava(IDateTime dt) { LocalDate datePart = null; if (!dt.isTime()) { datePart = LocalDate.of(dt.getYear(), dt.getMonthOfYear(), dt.getDayOfMonth()); diff --git a/src/org/rascalmpl/library/lang/json/IO.java b/src/org/rascalmpl/library/lang/json/IO.java index f3587d58a76..de112f8f8da 100644 --- a/src/org/rascalmpl/library/lang/json/IO.java +++ b/src/org/rascalmpl/library/lang/json/IO.java @@ -1,16 +1,12 @@ /******************************************************************************* - * Copyright (c) 2009-2013 CWI - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html + * Copyright (c) 2009-2013 CWI All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which accompanies this + * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html * * Contributors: - - * * Jurgen J. Vinju - Jurgen.Vinju@cwi.nl - CWI - * * Mark Hills - Mark.Hills@cwi.nl (CWI) - * * Arnold Lankamp - Arnold.Lankamp@cwi.nl - * * Bert Lisser - Bert.Lisser@cwi.nl + * + * * Jurgen J. Vinju - Jurgen.Vinju@cwi.nl - CWI * Mark Hills - Mark.Hills@cwi.nl (CWI) * Arnold + * Lankamp - Arnold.Lankamp@cwi.nl * Bert Lisser - Bert.Lisser@cwi.nl *******************************************************************************/ package org.rascalmpl.library.lang.json; @@ -19,6 +15,7 @@ import java.io.StringReader; import java.io.StringWriter; import java.nio.charset.Charset; +import java.text.DateFormat; import org.rascalmpl.exceptions.RuntimeExceptionFactory; import org.rascalmpl.library.lang.json.io.IValueAdapter; @@ -43,7 +40,6 @@ import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; -import com.ibm.icu.text.DateFormat; public class IO { private final IValueFactory values; @@ -155,7 +151,7 @@ public void writeJSON(ISourceLocation loc, IValue value, IBool implicitConstruct throw RuntimeExceptionFactory.io(values.string(e.getMessage()), null, null); } } - + public IString asJSON(IValue value, IBool implicitConstructors, IBool implicitNodes, IBool unpackedLocations, IString dateTimeFormat, IBool dateTimeAsInt, IInteger indent) { StringWriter string = new StringWriter(); diff --git a/src/org/rascalmpl/library/lang/json/IO.rsc b/src/org/rascalmpl/library/lang/json/IO.rsc index 57abc924710..36e824490b6 100644 --- a/src/org/rascalmpl/library/lang/json/IO.rsc +++ b/src/org/rascalmpl/library/lang/json/IO.rsc @@ -57,7 +57,7 @@ In explicit constructor mode (`implicitConstructor==false`) the following array- A similar distinction is made for values of type `node`, configured using the `implicitNode` parameter. } -java &T readJSON(type[&T] expected, loc src, bool implicitConstructors = true, bool implicitNodes = true, str dateTimeFormat = "yyyy-MM-dd\'T\'HH:mm:ss\'Z\'", bool lenient=false); +java &T readJSON(type[&T] expected, loc src, bool implicitConstructors = true, bool implicitNodes = true, str dateTimeFormat = "yyyy-MM-dd\'T\'HH:mm:ssZ", bool lenient=false); @javaClass{org.rascalmpl.library.lang.json.IO} @doc{parses JSON values from a string @@ -87,10 +87,10 @@ In explicit constructor mode (`implicitConstructor==false`) the following array- A similar distinction is made for values of type `node`, configured using the `implicitNode` parameter. } -java &T parseJSON(type[&T] expected, str src, bool implicitConstructors = true, bool implicitNodes = true, str dateTimeFormat = "yyyy-MM-dd\'T\'HH:mm:ss\'Z\'", bool lenient=false); +java &T parseJSON(type[&T] expected, str src, bool implicitConstructors = true, bool implicitNodes = true, str dateTimeFormat = "yyyy-MM-dd\'T\'HH:mm:ssZ", bool lenient=false); @javaClass{org.rascalmpl.library.lang.json.IO} -java void writeJSON(loc target, value val, bool implicitConstructors=true, bool implicitNodes=true, bool unpackedLocations=false, str dateTimeFormat="yyyy-MM-dd\'T\'HH:mm:ss\'Z\'", bool dateTimeAsInt=false, int indent=0); +java void writeJSON(loc target, value val, bool implicitConstructors=true, bool implicitNodes=true, bool unpackedLocations=false, str dateTimeFormat="yyyy-MM-dd\'T\'HH:mm:ssZ", bool dateTimeAsInt=false, int indent=0); @javaClass{org.rascalmpl.library.lang.json.IO} -java str asJSON(value val, bool implicitConstructors=true, bool implicitNodes=true, bool unpackedLocations=false, str dateTimeFormat="yyyy-MM-dd\'T\'HH:mm:ss\'Z\'", bool dateTimeAsInt=false, int indent = 0); +java str asJSON(value val, bool implicitConstructors=true, bool implicitNodes=true, bool unpackedLocations=false, str dateTimeFormat="yyyy-MM-dd\'T\'HH:mm:ssZ", bool dateTimeAsInt=false, int indent = 0); diff --git a/src/org/rascalmpl/library/lang/json/io/JSONReadingTypeVisitor.java b/src/org/rascalmpl/library/lang/json/io/JSONReadingTypeVisitor.java index b43009f2db7..1fe622cff18 100644 --- a/src/org/rascalmpl/library/lang/json/io/JSONReadingTypeVisitor.java +++ b/src/org/rascalmpl/library/lang/json/io/JSONReadingTypeVisitor.java @@ -651,8 +651,8 @@ public IValue visitDateTime(Type type) throws IOException { int minuteOfHour = -1; int secondOfMinute = -1; int millisecondsOfSecond = -1; - int timezoneOffsetHours = -1; - int timezoneOffsetMinutes = -1; + int timezoneOffsetHours = -99; + int timezoneOffsetMinutes = -99; Map m = (Map)stack.peek(); @@ -675,7 +675,7 @@ public IValue visitDateTime(Type type) throws IOException { if (year != -1 && monthOfYear != -1 && dayOfMonth != -1 && hourOfDay != -1 && minuteOfHour != -1 && secondOfMinute != -1 && millisecondsOfSecond != -1 - && timezoneOffsetHours != -1 && timezoneOffsetMinutes != -1) { + && timezoneOffsetHours != -99 && timezoneOffsetMinutes != -99) { return vf.datetime(year, monthOfYear, dayOfMonth, hourOfDay, minuteOfHour, secondOfMinute, millisecondsOfSecond, timezoneOffsetHours, timezoneOffsetMinutes); } if (year != -1 && monthOfYear != -1 && dayOfMonth != -1 && hourOfDay != -1 diff --git a/src/org/rascalmpl/library/lang/json/io/JsonValueReader.java b/src/org/rascalmpl/library/lang/json/io/JsonValueReader.java index fd6d026c9c9..963fd5a5e2d 100644 --- a/src/org/rascalmpl/library/lang/json/io/JsonValueReader.java +++ b/src/org/rascalmpl/library/lang/json/io/JsonValueReader.java @@ -17,6 +17,11 @@ import java.net.URISyntaxException; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; +import java.time.temporal.TemporalAccessor; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; @@ -24,6 +29,7 @@ import java.util.Map; import java.util.Set; +import org.rascalmpl.library.Prelude; import org.rascalmpl.uri.URIUtil; import io.usethesource.vallang.IInteger; import io.usethesource.vallang.IListWriter; @@ -42,52 +48,50 @@ /** * This class streams a JSON stream directly to an IValue representation and validates the content - * to a given type as declared in a given type store. See the Rascal file lang::json::IO::readJson for documentation. - + * to a given type as declared in a given type store. See the Rascal file lang::json::IO::readJson + * for documentation. + * */ public class JsonValueReader { private static final TypeFactory TF = TypeFactory.getInstance(); private final TypeStore store; private final IValueFactory vf; - private ThreadLocal format; + private DateTimeFormatter format; private boolean constructorsAsObjects = true; private boolean nodesAsObjects = true; - + /** - * @param vf factory which will be used to construct values - * @param store type store to lookup constructors of abstract data-types in and the types of keyword fields + * @param vf factory which will be used to construct values + * @param store type store to lookup constructors of abstract data-types in and the types of keyword + * fields */ public JsonValueReader(IValueFactory vf, TypeStore store) { this.vf = vf; this.store = store; - setCalendarFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + setCalendarFormat("yyyy-MM-dd'T'HH:mm:ssZ"); } - + public JsonValueReader(IValueFactory vf) { this(vf, new TypeStore()); } - + /** * Builder method to set the format to use for all date-time values encoded as strings */ public JsonValueReader setCalendarFormat(String format) { // SimpleDateFormat is not thread safe, so here we make sure // we can use objects of this reader in different threads at the same time - this.format = new ThreadLocal() { - protected SimpleDateFormat initialValue() { - return new SimpleDateFormat(format); - } - }; + this.format = DateTimeFormatter.ofPattern(format); return this; } - + public JsonValueReader setConstructorsAsObjects(boolean setting) { this.constructorsAsObjects = setting; return this; } - - - + + + public JsonValueReader setNodesAsObjects(boolean setting) { this.nodesAsObjects = setting; return this; @@ -95,8 +99,9 @@ public JsonValueReader setNodesAsObjects(boolean setting) { /** * Read and validate a Json stream as an IValue - * @param in json stream - * @param expected type to validate against (recursively) + * + * @param in json stream + * @param expected type to validate against (recursively) * @return an IValue of the expected type * @throws IOException when either a parse error or a validation error occurs */ @@ -120,7 +125,7 @@ public IValue visitInteger(Type type) throws IOException { throw new IOException("Expected integer but got " + e.getMessage()); } } - + public IValue visitReal(Type type) throws IOException { try { switch (in.peek()) { @@ -138,21 +143,21 @@ public IValue visitReal(Type type) throws IOException { throw new IOException("Expected integer but got " + e.getMessage()); } } - + @Override public IValue visitExternal(Type type) throws IOException { throw new IOException("External type " + type + "is not implemented yet by the json reader:" + in.getPath()); } - + @Override public IValue visitString(Type type) throws IOException { if (isNull()) { return null; } - + return vf.string(in.nextString()); } - + @Override public IValue visitTuple(Type type) throws IOException { if (isNull()) { @@ -161,7 +166,7 @@ public IValue visitTuple(Type type) throws IOException { List l = new ArrayList<>(); in.beginArray(); - + if (type.hasFieldNames()) { for (int i = 0; i < type.getArity(); i++) { l.add(read(in, type.getFieldType(i))); @@ -176,7 +181,7 @@ public IValue visitTuple(Type type) throws IOException { in.endArray(); return vf.tuple(l.toArray(new IValue[l.size()])); } - + @Override public IValue visitVoid(Type type) throws IOException { throw new IOException("Can not read json values of type void: " + in.getPath()); @@ -186,7 +191,7 @@ public IValue visitVoid(Type type) throws IOException { public IValue visitFunction(Type type) throws IOException { throw new IOException("Can not read json values of function types: " + in.getPath()); } - + @Override public IValue visitSourceLocation(Type type) throws IOException { switch (in.peek()) { @@ -194,13 +199,13 @@ public IValue visitSourceLocation(Type type) throws IOException { return sourceLocationString(); case BEGIN_OBJECT: return sourceLocationObject(); - default: - throw new IOException("Could not find string or source location object here: " + in.getPath()); + default: + throw new IOException("Could not find string or source location object here: " + in.getPath()); } - } - - - + } + + + private IValue sourceLocationObject() throws IOException { String scheme = null; String authority = null; @@ -213,82 +218,85 @@ private IValue sourceLocationObject() throws IOException { int endLine = -1; int beginColumn = -1; int endColumn = -1; - + in.beginObject(); - + while (in.hasNext()) { - String name = in.nextName(); - switch (name) { - case "scheme": - scheme = in.nextString(); - break; - case "authority": - authority = in.nextString(); - break; - case "path": - path = in.nextString(); - break; - case "fragment": - fragment = in.nextString(); - break; - case "query": - query = in.nextString(); - break; - case "offset": - offset = in.nextInt(); - break; - case "length": - length = in.nextInt(); - break; - case "start": - case "begin": - in.beginArray(); - beginLine = in.nextInt(); - beginColumn = in.nextInt(); - in.endArray(); - break; - case "end": - in.beginArray(); - endLine = in.nextInt(); - endColumn = in.nextInt(); - in.endArray(); - break; - default: - throw new IOException("unexpected property name " + name + " :" + in.getPath()); - } + String name = in.nextName(); + switch (name) { + case "scheme": + scheme = in.nextString(); + break; + case "authority": + authority = in.nextString(); + break; + case "path": + path = in.nextString(); + break; + case "fragment": + fragment = in.nextString(); + break; + case "query": + query = in.nextString(); + break; + case "offset": + offset = in.nextInt(); + break; + case "length": + length = in.nextInt(); + break; + case "start": + case "begin": + in.beginArray(); + beginLine = in.nextInt(); + beginColumn = in.nextInt(); + in.endArray(); + break; + case "end": + in.beginArray(); + endLine = in.nextInt(); + endColumn = in.nextInt(); + in.endArray(); + break; + default: + throw new IOException("unexpected property name " + name + " :" + in.getPath()); + } } - + in.endObject(); - if (path != null && offset != -1 && length != -1 && beginLine != -1 && endLine != -1 && beginColumn != -1 && endColumn != -1) { - return vf.sourceLocation(path, offset, length, beginLine, endLine, beginColumn, endColumn); + if (path != null && offset != -1 && length != -1 && beginLine != -1 && endLine != -1 && beginColumn != -1 + && endColumn != -1) { + return vf.sourceLocation(path, offset, length, beginLine, endLine, beginColumn, endColumn); } try { - if (scheme != null && authority != null && query != null && fragment != null) { - return vf.sourceLocation(scheme, authority, path, query, fragment); - } - if (scheme != null) { - return vf.sourceLocation(scheme, authority == null ? "" : authority, path); - } - } catch (URISyntaxException e) { - throw new IOException(e); + if (scheme != null && authority != null && query != null && fragment != null) { + return vf.sourceLocation(scheme, authority, path, query, fragment); + } + if (scheme != null) { + return vf.sourceLocation(scheme, authority == null ? "" : authority, path); + } + } + catch (URISyntaxException e) { + throw new IOException(e); } if (path != null) { - return vf.sourceLocation(path); + return vf.sourceLocation(path); } - + throw new IOException("Could not parse complete source location: " + in.getPath()); } - + @Override public IValue visitValue(Type type) throws IOException { switch (in.peek()) { case NUMBER: try { return vf.integer(in.nextLong()); - } catch (NumberFormatException e) { - return vf.real(in.nextDouble()); + } + catch (NumberFormatException e) { + return vf.real(in.nextDouble()); } case STRING: return visitString(TF.stringType()); @@ -305,34 +313,36 @@ public IValue visitValue(Type type) throws IOException { in.nextNull(); return null; default: - throw new IOException("Did not expect end of Json value here, while looking for " + type + " + at " + in.getPath()); + throw new IOException( + "Did not expect end of Json value here, while looking for " + type + " + at " + in.getPath()); } } - - private IValue sourceLocationString() throws IOException { - try { - String val = in.nextString().trim(); - - if (val.startsWith("|") && (val.endsWith("|") || val.endsWith(")"))) { - return new StandardTextReader().read(vf, new StringReader(val)); - } - else if (val.contains("://")) { - return vf.sourceLocation(URIUtil.createFromEncoded(val)); - } - else { - // will be simple interpreted as an absolute file name - return vf.sourceLocation(val); - } - } catch (URISyntaxException e) { - throw new IOException("could not parse URI:" + in.getPath() + " due to " + e.getMessage(), e); + + private IValue sourceLocationString() throws IOException { + try { + String val = in.nextString().trim(); + + if (val.startsWith("|") && (val.endsWith("|") || val.endsWith(")"))) { + return new StandardTextReader().read(vf, new StringReader(val)); + } + else if (val.contains("://")) { + return vf.sourceLocation(URIUtil.createFromEncoded(val)); } + else { + // will be simple interpreted as an absolute file name + return vf.sourceLocation(val); + } + } + catch (URISyntaxException e) { + throw new IOException("could not parse URI:" + in.getPath() + " due to " + e.getMessage(), e); + } } - + public IValue visitRational(Type type) throws IOException { if (isNull()) { return null; } - + switch (in.peek()) { case BEGIN_OBJECT: in.beginObject(); @@ -365,21 +375,22 @@ public IValue visitRational(Type type) throws IOException { throw new IOException("Expected integer but got " + in.peek()); } } - + @Override public IValue visitMap(Type type) throws IOException { if (isNull()) { return null; } IMapWriter w = vf.mapWriter(); - + switch (in.peek()) { case BEGIN_OBJECT: in.beginObject(); if (!type.getKeyType().isString()) { - throw new IOException("Can not read JSon object as a map if the key type of the map (" + type + ") is not a string at " + in.getPath()); + throw new IOException("Can not read JSon object as a map if the key type of the map (" + type + + ") is not a string at " + in.getPath()); } - + while (in.hasNext()) { w.put(vf.string(in.nextName()), read(in, type.getValueType())); } @@ -391,25 +402,25 @@ public IValue visitMap(Type type) throws IOException { in.beginArray(); IValue key = read(in, type.getKeyType()); IValue value = read(in, type.getValueType()); - w.put(key,value); + w.put(key, value); in.endArray(); } in.endArray(); return w.done(); default: - throw new IOException("Expected a map encoded as an object or an nested array to match " + type); + throw new IOException("Expected a map encoded as an object or an nested array to match " + type); } } - + @Override public IValue visitAlias(Type type) throws IOException { while (type.isAliased()) { type = type.getAliased(); } - + return type.accept(this); } - + @Override public IValue visitBool(Type type) throws IOException { if (isNull()) { @@ -417,17 +428,17 @@ public IValue visitBool(Type type) throws IOException { } return vf.bool(in.nextBoolean()); } - + @Override public IValue visitAbstractData(Type type) throws IOException { return constructorsAsObjects ? implicitConstructor(type) : explicitConstructor(type); } - + private IValue explicitConstructor(Type type) throws IOException { in.beginArray(); // binary or ternary, first is cons, second is args, third is optional kwargs String label = in.nextString(); Type cons = checkNameCons(type, label); - + // args in.beginArray(); IValue[] args = new IValue[cons.getArity()]; @@ -439,19 +450,19 @@ private IValue explicitConstructor(Type type) throws IOException { } in.endArray(); - Map kwParams = new HashMap<>(); - + Map kwParams = new HashMap<>(); + if (in.peek() == JsonToken.BEGIN_OBJECT) { in.beginObject(); - + while (in.hasNext()) { String kwLabel = in.nextName(); Type kwType = store.getKeywordParameterType(cons, label); - + if (kwType == null) { throw new IOException("Unknown field " + label + ":" + in.getPath()); } - + IValue val = read(in, kwType); if (val != null) { @@ -463,9 +474,9 @@ private IValue explicitConstructor(Type type) throws IOException { } in.endArray(); - + return vf.constructor(cons, args, kwParams); - + } private IValue implicitConstructor(Type type) throws IOException { @@ -473,12 +484,12 @@ private IValue implicitConstructor(Type type) throws IOException { String consName = in.nextName(); Type cons = checkNameCons(type, consName); IValue[] args = new IValue[cons.getArity()]; - Map kwParams = new HashMap<>(); - + Map kwParams = new HashMap<>(); + if (!cons.hasFieldNames() && cons.getArity() != 0) { throw new IOException("For the object encoding constructors must have field names " + in.getPath()); } - + in.beginObject(); while (in.hasNext()) { String label = in.nextName(); @@ -502,33 +513,33 @@ else if (cons.hasKeywordField(label, store)) { throw new IOException("Unknown field " + label + ":" + in.getPath()); } } - + in.endObject(); in.endObject(); - + for (int i = 0; i < args.length; i++) { if (args[i] == null) { throw new IOException("Missing argument " + cons.getFieldName(i) + " to " + cons + ":" + in.getPath()); } } - + return vf.constructor(cons, args, kwParams); } - + @Override public IValue visitConstructor(Type type) throws IOException { return read(in, type.getAbstractDataType()); } - + @Override public IValue visitNode(Type type) throws IOException { return nodesAsObjects ? implicitNode() : explicitNode(); } - + private IValue explicitNode() throws IOException { in.beginArray(); // binary or ternary, first is cons, second is args, third is optional kwargs String label = in.nextString(); - + // args in.beginArray(); List args = new LinkedList<>(); @@ -538,14 +549,14 @@ private IValue explicitNode() throws IOException { in.endArray(); // kwargs - Map kwParams = new HashMap<>(); - + Map kwParams = new HashMap<>(); + if (in.hasNext()) { in.beginObject(); while (in.hasNext()) { String kwLabel = in.nextName(); IValue val = read(in, TF.valueType()); - + if (val != null) { // null values are simply "not" set kwParams.put(kwLabel, val); @@ -555,50 +566,51 @@ private IValue explicitNode() throws IOException { } in.endArray(); - + return vf.node(label, args.toArray(new IValue[args.size()]), kwParams); } private IValue implicitNode() throws IOException { in.beginObject(); - Map kws = new HashMap<>(); - + Map kws = new HashMap<>(); + while (in.hasNext()) { String kwName = in.nextName(); IValue value = read(in, TF.valueType()); - + if (value != null) { kws.put(kwName, value); } } - + in.endObject(); - return vf.node("object", new IValue[] { }, kws); + return vf.node("object", new IValue[] {}, kws); } - + @Override public IValue visitNumber(Type type) throws IOException { return visitInteger(type); } - + @Override public IValue visitParameter(Type type) throws IOException { return type.getBound().accept(this); } - + @Override public IValue visitDateTime(Type type) throws IOException { try { switch (in.peek()) { case STRING: - return vf.datetime(format.get().parse(in.nextString()).toInstant().toEpochMilli()); + OffsetDateTime result = OffsetDateTime.parse(in.nextString(), format); + return Prelude.temporalToDateTime(vf, result); case NUMBER: return vf.datetime(in.nextLong()); default: throw new IOException("Expected a datetime instant " + in.getPath()); } - } catch (ParseException e) { + } catch (DateTimeParseException e) { throw new IOException("Could not parse date: " + in.getPath()); } } diff --git a/src/org/rascalmpl/library/lang/json/io/JsonValueWriter.java b/src/org/rascalmpl/library/lang/json/io/JsonValueWriter.java index 18c963cca2c..63e88abd01d 100644 --- a/src/org/rascalmpl/library/lang/json/io/JsonValueWriter.java +++ b/src/org/rascalmpl/library/lang/json/io/JsonValueWriter.java @@ -1,19 +1,31 @@ -/** - * Copyright (c) 2016, Jurgen J. Vinju, Centrum Wiskunde & Informatica (CWI) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ +/** + * Copyright (c) 2016, Jurgen J. Vinju, Centrum Wiskunde & Informatica (CWI) All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package org.rascalmpl.library.lang.json.io; import java.io.IOException; import java.text.SimpleDateFormat; +import java.time.format.DateTimeFormatter; +import java.time.temporal.Temporal; import java.util.Map.Entry; import io.usethesource.vallang.IBool; @@ -35,31 +47,27 @@ import com.google.gson.stream.JsonWriter; +import org.rascalmpl.library.Prelude; + /** * This class streams am IValue stream directly to an JSon stream. Useful to communicate IValues to browsers. */ public class JsonValueWriter { - private ThreadLocal format; + private DateTimeFormatter format; private boolean constructorsAsObjects = true; private boolean nodesAsObjects = true; private boolean datesAsInts = true; private boolean unpackedLocations = false; public JsonValueWriter() { - setCalendarFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + setCalendarFormat("yyyy-MM-dd'T'HH:mm:ssZ"); } /** * Builder method to set the format to use for all date-time values encoded as strings */ public JsonValueWriter setCalendarFormat(String format) { - // SimpleDateFormat is not thread safe, so here we make sure - // we can use objects of this reader in different threads at the same time - this.format = new ThreadLocal() { - protected SimpleDateFormat initialValue() { - return new SimpleDateFormat(format); - } - }; + this.format = DateTimeFormatter.ofPattern(format); return this; } @@ -354,7 +362,8 @@ public Void visitDateTime(IDateTime o) throws IOException { return null; } else { - throw new IOException("Dates as strings not yet implemented: " + format.get().toPattern()); + out.value(format.format(Prelude.dateTimeToJava(o))); + return null; } } }); diff --git a/src/org/rascalmpl/library/lang/rascal/tests/library/lang/json/JSONIOTests.rsc b/src/org/rascalmpl/library/lang/rascal/tests/library/lang/json/JSONIOTests.rsc index 03da5cca9bd..5d57cc76660 100644 --- a/src/org/rascalmpl/library/lang/rascal/tests/library/lang/json/JSONIOTests.rsc +++ b/src/org/rascalmpl/library/lang/rascal/tests/library/lang/json/JSONIOTests.rsc @@ -4,6 +4,7 @@ import String; import Node; import Type; import lang::json::IO; +import IO; import util::UUID; loc targetFile = |test-temp:///test-<"">.json|; @@ -18,6 +19,10 @@ bool jsonFeaturesSupported(value v) { // json reader/writer can't handle annotations at the moment return false; } + for (/datetime dt := v, !dt.isDateTime) { + // json can only deal with full date timestamp + return false; + } return true; } @@ -66,7 +71,6 @@ data D | kwparams(int x = 2, D d = integer(0)) ; -@ignore{Currently not working with datetimes not as ints} test bool jsonStreaming1(D dt) { if (!jsonFeaturesSupported(dt)) { return true; @@ -79,6 +83,11 @@ test bool jsonStreaming2(D dt) { if (!jsonFeaturesSupported(dt)) { return true; } + dt = visit(dt) { + // workaround when we store date times as int, we lose timezone information + case datetime d => d[timezoneOffsetHours = 0][timezoneOffsetMinutes=0] + when !d.isDate + }; writeJSON(targetFile, dt, dateTimeAsInt=true); return readJSON(#D, targetFile) == dt; } From c5d8af1e1741dda8db51c345af72af2f3d6068bf Mon Sep 17 00:00:00 2001 From: Davy Landman Date: Thu, 10 Jun 2021 11:53:04 +0200 Subject: [PATCH 06/10] Fixed all test around zone offsets --- src/org/rascalmpl/library/Prelude.java | 393 ++++++++++-------- src/org/rascalmpl/library/lang/csv/IO.java | 2 +- src/org/rascalmpl/library/lang/json/IO.rsc | 8 +- .../tests/library/lang/csv/CSVIOTests.rsc | 2 +- 4 files changed, 217 insertions(+), 188 deletions(-) diff --git a/src/org/rascalmpl/library/Prelude.java b/src/org/rascalmpl/library/Prelude.java index 1cae7efea04..6863dd1b5a7 100644 --- a/src/org/rascalmpl/library/Prelude.java +++ b/src/org/rascalmpl/library/Prelude.java @@ -1,18 +1,13 @@ /******************************************************************************* -/******************************************************************************* - * Copyright (c) 2009-2020 CWI, NWO-I CWI - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html + * /******************************************************************************* Copyright (c) + * 2009-2020 CWI, NWO-I CWI All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which accompanies this + * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html * - * Contributors: - * * Paul Klint - Paul.Klint@cwi.nl - CWI - * * Jurgen J. Vinju - Jurgen.Vinju@cwi.nl - CWI - * * Arnold Lankamp - Arnold.Lankamp@cwi.nl - * * Davy Landman - Davy.Landman@cwi.nl - * * Michael Steindorfer - Michael.Steindorfer@cwi.nl - CWI -*******************************************************************************/ + * Contributors: * Paul Klint - Paul.Klint@cwi.nl - CWI * Jurgen J. Vinju - Jurgen.Vinju@cwi.nl - + * CWI * Arnold Lankamp - Arnold.Lankamp@cwi.nl * Davy Landman - Davy.Landman@cwi.nl * Michael + * Steindorfer - Michael.Steindorfer@cwi.nl - CWI + *******************************************************************************/ package org.rascalmpl.library; @@ -48,13 +43,19 @@ import java.time.Duration; 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.Period; +import java.time.ZoneId; import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.time.temporal.ChronoField; import java.time.temporal.ChronoUnit; import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; import java.util.Arrays; import java.util.Base64; import java.util.Collections; @@ -271,41 +272,64 @@ private IValue incrementDTField(IDateTime dt, ChronoUnit field, IInteger amount) else if (dt.isTime()) { return temporalToTime(result); } - return temporalToDateTime((OffsetDateTime)result); + return temporalToDateTime((OffsetDateTime) result); + } + private IDateTime temporalToDate(TemporalAccessor t) { + return temporalToDate(values, (LocalDate)t); } - private IDateTime temporalToDate(Temporal t) { - return values.date( - t.get(ChronoField.YEAR), - t.get(ChronoField.MONTH_OF_YEAR), - t.get(ChronoField.DAY_OF_MONTH) - ); + private static IDateTime temporalToDate(IValueFactory values, LocalDate t) { + return values.date(t.getYear(), t.getMonthValue(), t.getDayOfMonth()); } - private IDateTime temporalToTime(Temporal t) { - return values.time( - t.get(ChronoField.HOUR_OF_DAY), - t.get(ChronoField.MINUTE_OF_HOUR), - t.get(ChronoField.SECOND_OF_MINUTE), - t.get(ChronoField.MILLI_OF_SECOND), - (int)TimeUnit.HOURS.convert(t.get(ChronoField.OFFSET_SECONDS), TimeUnit.SECONDS), - (int)(TimeUnit.MINUTES.convert(t.get(ChronoField.OFFSET_SECONDS), TimeUnit.SECONDS) % 60) - ); + private IDateTime temporalToTime(TemporalAccessor t) { + if (t instanceof OffsetTime) { + return temporalToTime(values, (OffsetTime)t); + } + return temporalToTime(values, (LocalTime)t); + } + private static IDateTime temporalToTime(IValueFactory values, OffsetTime t) { + return values.time(t.getHour(), t.getMinute(), t.getSecond(), t.get(ChronoField.MILLI_OF_SECOND), + (int) TimeUnit.HOURS.convert(t.getOffset().getTotalSeconds(), TimeUnit.SECONDS), + (int) (TimeUnit.MINUTES.convert(t.getOffset().getTotalSeconds(), TimeUnit.SECONDS) % 60)); + } + + private static IDateTime temporalToTime(IValueFactory values, LocalTime t) { + return values.time(t.getHour(), t.getMinute(), t.getSecond(), t.get(ChronoField.MILLI_OF_SECOND)); } public static IDateTime temporalToDateTime(IValueFactory values, OffsetDateTime t) { - return values.datetime( - t.getYear(), - t.getMonthValue(), - t.getDayOfMonth(), - t.getHour(), - t.getMinute(), - t.getSecond(), - t.get(ChronoField.MILLI_OF_SECOND), - (int)TimeUnit.HOURS.convert(t.getOffset().getTotalSeconds(), TimeUnit.SECONDS), - (int)(TimeUnit.MINUTES.convert(t.getOffset().getTotalSeconds(), TimeUnit.SECONDS) % 60) + return values.datetime(t.getYear(), t.getMonthValue(), t.getDayOfMonth(), t.getHour(), t.getMinute(), + t.getSecond(), t.get(ChronoField.MILLI_OF_SECOND), + (int) TimeUnit.HOURS.convert(t.getOffset().getTotalSeconds(), TimeUnit.SECONDS), + (int) (TimeUnit.MINUTES.convert(t.getOffset().getTotalSeconds(), TimeUnit.SECONDS) % 60)); + } + + public static IDateTime temporalToDateTime(IValueFactory values, LocalDateTime t) { + return values.datetime(t.getYear(), t.getMonthValue(), t.getDayOfMonth(), t.getHour(), t.getMinute(), + t.getSecond(), t.get(ChronoField.MILLI_OF_SECOND) ); } + + public static IDateTime temporalToIValue(IValueFactory values, TemporalAccessor t) { + boolean hasDate = t.isSupported(ChronoField.YEAR); + boolean hasTime = t.isSupported(ChronoField.HOUR_OF_DAY); + boolean hasZoneOffset = t.isSupported(ChronoField.OFFSET_SECONDS); + if (hasDate) { + if (hasTime) { + if (hasZoneOffset) { + return temporalToDateTime(values, OffsetDateTime.from(t)); + } + return temporalToDateTime(values, LocalDateTime.from(t)); + } + return temporalToDate(values, LocalDate.from(t)); + } + if (hasZoneOffset) { + return temporalToTime(values, OffsetTime.from(t)); + } + return temporalToTime(values, LocalTime.from(t)); + } + private IDateTime temporalToDateTime(OffsetDateTime t) { return temporalToDateTime(values, t); } @@ -315,15 +339,12 @@ public static Temporal dateTimeToJava(IDateTime dt) { if (!dt.isTime()) { datePart = LocalDate.of(dt.getYear(), dt.getMonthOfYear(), dt.getDayOfMonth()); } - + OffsetTime timePart = null; if (!dt.isDate()) { // vallang always has timezone offset in case it's not a date - timePart = OffsetTime.of( - dt.getHourOfDay(), - dt.getMinuteOfHour(), - dt.getSecondOfMinute(), - (int)TimeUnit.MILLISECONDS.toNanos(dt.getMillisecondsOfSecond()), + timePart = OffsetTime.of(dt.getHourOfDay(), dt.getMinuteOfHour(), dt.getSecondOfMinute(), + (int) TimeUnit.MILLISECONDS.toNanos(dt.getMillisecondsOfSecond()), ZoneOffset.ofHoursMinutes(dt.getTimezoneOffsetHours(), dt.getTimezoneOffsetMinutes())); } if (datePart == null && timePart != null) { @@ -335,85 +356,88 @@ public static Temporal dateTimeToJava(IDateTime dt) { assert timePart != null && datePart != null; return datePart.atTime(timePart); } - + private IValue incrementTime(IDateTime dt, ChronoUnit field, String fieldName, IInteger amount) { if (dt.isDate()) - throw RuntimeExceptionFactory.invalidUseOfDateException("Cannot increment the " + fieldName + " on a date value."); - + throw RuntimeExceptionFactory + .invalidUseOfDateException("Cannot increment the " + fieldName + " on a date value."); + return incrementDTField(dt, field, amount); } private IValue incrementDate(IDateTime dt, ChronoUnit field, String fieldName, IInteger amount) { if (dt.isTime()) - throw RuntimeExceptionFactory.invalidUseOfDateException("Cannot increment the " + fieldName + " on a time value."); - + throw RuntimeExceptionFactory + .invalidUseOfDateException("Cannot increment the " + fieldName + " on a time value."); + return incrementDTField(dt, field, amount); } - + public IValue incrementHours(IDateTime dt, IInteger n) - //@doc{Increment the hours by a given amount.} + // @doc{Increment the hours by a given amount.} { return incrementTime(dt, ChronoUnit.HOURS, "hours", n); - } + } public IValue incrementMinutes(IDateTime dt, IInteger n) - //@doc{Increment the minutes by a given amount.} + // @doc{Increment the minutes by a given amount.} { return incrementTime(dt, ChronoUnit.MINUTES, "minutes", n); - } - + } + public IValue incrementSeconds(IDateTime dt, IInteger n) - //@doc{Increment the seconds by a given amount.} + // @doc{Increment the seconds by a given amount.} { return incrementTime(dt, ChronoUnit.SECONDS, "seconds", n); } - + public IValue incrementMilliseconds(IDateTime dt, IInteger n) - //@doc{Increment the milliseconds by a given amount.} + // @doc{Increment the milliseconds by a given amount.} { return incrementTime(dt, ChronoUnit.MILLIS, "milliseconds", n); } public IValue decrementYears(IDateTime dt, IInteger n) - //@doc{Decrement the years by a given amount.} + // @doc{Decrement the years by a given amount.} { return incrementDate(dt, ChronoUnit.YEARS, "years", n.negate()); - } + } public IValue decrementMonths(IDateTime dt, IInteger n) - //@doc{Decrement the months by a given amount.} + // @doc{Decrement the months by a given amount.} { - return incrementDate(dt, ChronoUnit.MONTHS, "months", n.negate()); } + return incrementDate(dt, ChronoUnit.MONTHS, "months", n.negate()); + } public IValue decrementDays(IDateTime dt, IInteger n) - //@doc{Decrement the days by a given amount.} + // @doc{Decrement the days by a given amount.} { return incrementDate(dt, ChronoUnit.DAYS, "days", n.negate()); } public IValue decrementHours(IDateTime dt, IInteger n) - //@doc{Decrement the hours by a given amount.} + // @doc{Decrement the hours by a given amount.} { return incrementTime(dt, ChronoUnit.HOURS, "hours", n.negate()); - } + } public IValue decrementMinutes(IDateTime dt, IInteger n) - //@doc{Decrement the minutes by a given amount.} + // @doc{Decrement the minutes by a given amount.} { return incrementTime(dt, ChronoUnit.MINUTES, "minutes", n.negate()); - } + } public IValue decrementSeconds(IDateTime dt, IInteger n) - //@doc{Decrement the seconds by a given amount.} + // @doc{Decrement the seconds by a given amount.} { - return incrementTime(dt, ChronoUnit.SECONDS, "seconds", n.negate()); - } + return incrementTime(dt, ChronoUnit.SECONDS, "seconds", n.negate()); + } public IValue decrementMilliseconds(IDateTime dt, IInteger n) - //@doc{Decrement the milliseconds by a given amount.} + // @doc{Decrement the milliseconds by a given amount.} { return incrementTime(dt, ChronoUnit.MILLIS, "milliseconds", n.negate()); - } + } public IValue createDurationInternal(IDateTime dStart, IDateTime dEnd) { // dStart and dEnd both have to be dates, times, or datetimes @@ -422,92 +446,97 @@ public IValue createDurationInternal(IDateTime dStart, IDateTime dEnd) { if (startTemp instanceof LocalDate) { if (endTemp instanceof LocalDate) { - Period duration = Period.between((LocalDate)startTemp, (LocalDate)endTemp).normalized(); - - return values.tuple( - values.integer(duration.getYears()), - values.integer(duration.getMonths()), - values.integer(duration.getDays()), - values.integer(0), values.integer(0), values.integer(0), - values.integer(0) - ); - } else if (endTemp instanceof OffsetTime) { - throw RuntimeExceptionFactory.invalidUseOfTimeException("Cannot determine the duration between a date with no time and a time with no date."); - } else { - throw RuntimeExceptionFactory.invalidUseOfDateTimeException("Cannot determine the duration between a date with no time and a datetime."); + Period duration = Period.between((LocalDate) startTemp, (LocalDate) endTemp).normalized(); + + return values.tuple(values.integer(duration.getYears()), values.integer(duration.getMonths()), + values.integer(duration.getDays()), values.integer(0), values.integer(0), values.integer(0), + values.integer(0)); + } + else if (endTemp instanceof OffsetTime) { + throw RuntimeExceptionFactory.invalidUseOfTimeException( + "Cannot determine the duration between a date with no time and a time with no date."); + } + else { + throw RuntimeExceptionFactory.invalidUseOfDateTimeException( + "Cannot determine the duration between a date with no time and a datetime."); } - } else if (startTemp instanceof OffsetTime) { + } + else if (startTemp instanceof OffsetTime) { if (endTemp instanceof OffsetTime) { Duration duration = Duration.between(startTemp, endTemp); - return values.tuple( - values.integer(0), - values.integer(0), - values.integer(0), - values.integer(duration.toHours()), - values.integer(duration.toMinutes() % 60), - values.integer(duration.getSeconds() % 60), - values.integer(duration.toMillis() % 1000) - ); - } else if (dEnd.isDate()) { - throw RuntimeExceptionFactory.invalidUseOfDateException("Cannot determine the duration between a time with no date and a date with no time."); - } else { - throw RuntimeExceptionFactory.invalidUseOfDateTimeException("Cannot determine the duration between a time with no date and a datetime."); + return values.tuple(values.integer(0), values.integer(0), values.integer(0), + values.integer(duration.toHours()), values.integer(duration.toMinutes() % 60), + values.integer(duration.getSeconds() % 60), values.integer(duration.toMillis() % 1000)); } - } else { + else if (dEnd.isDate()) { + throw RuntimeExceptionFactory.invalidUseOfDateException( + "Cannot determine the duration between a time with no date and a date with no time."); + } + else { + throw RuntimeExceptionFactory.invalidUseOfDateTimeException( + "Cannot determine the duration between a time with no date and a datetime."); + } + } + else { if (dEnd.isDateTime()) { - return values.tuple( - values.integer(ChronoUnit.YEARS.between(startTemp, endTemp)), - values.integer(ChronoUnit.MONTHS.between(startTemp, endTemp) % 12), - values.integer(ChronoUnit.DAYS.between(startTemp, endTemp) % 31), // TODO: this is broken, day of month in durations ?? - values.integer(ChronoUnit.HOURS.between(startTemp, endTemp) % 24), - values.integer(ChronoUnit.MINUTES.between(startTemp, endTemp) % 60), - values.integer(ChronoUnit.SECONDS.between(startTemp, endTemp) % 60), - values.integer(ChronoUnit.MILLIS.between(startTemp, endTemp) % 1000) - ); - } else if (dEnd.isDate()) { - throw RuntimeExceptionFactory.invalidUseOfDateException("Cannot determine the duration between a datetime and a date with no time."); - } else { - throw RuntimeExceptionFactory.invalidUseOfTimeException("Cannot determine the duration between a datetime and a time with no date."); + return values.tuple(values.integer(ChronoUnit.YEARS.between(startTemp, endTemp)), + values.integer(ChronoUnit.MONTHS.between(startTemp, endTemp) % 12), + values.integer(ChronoUnit.DAYS.between(startTemp, endTemp) % 31), // TODO: this is broken, day of + // month in durations ?? + values.integer(ChronoUnit.HOURS.between(startTemp, endTemp) % 24), + values.integer(ChronoUnit.MINUTES.between(startTemp, endTemp) % 60), + values.integer(ChronoUnit.SECONDS.between(startTemp, endTemp) % 60), + values.integer(ChronoUnit.MILLIS.between(startTemp, endTemp) % 1000)); + } + else if (dEnd.isDate()) { + throw RuntimeExceptionFactory.invalidUseOfDateException( + "Cannot determine the duration between a datetime and a date with no time."); + } + else { + throw RuntimeExceptionFactory.invalidUseOfTimeException( + "Cannot determine the duration between a datetime and a time with no date."); } } } - + public IValue parseDate(IString inputDate, IString formatString) - //@doc{Parse an input date given as a string using the given format string} - { + // @doc{Parse an input date given as a string using the given format string} + { try { - java.text.SimpleDateFormat fmt = new java.text.SimpleDateFormat(formatString.getValue()); - fmt.parse(inputDate.getValue()); - java.util.Calendar cal = fmt.getCalendar(); - return values.date(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DATE)); - } catch (IllegalArgumentException iae) { - throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input date: " + inputDate.getValue() + - " using format string: " + formatString.getValue()); - } catch (ParseException e) { - throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input date: " + inputDate.getValue() + - " using format string: " + formatString.getValue()); + return temporalToDate(values, LocalDate.parse(inputDate.getValue(), DateTimeFormatter.ofPattern(formatString.getValue()))); + } + catch (IllegalArgumentException iae) { + throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input date: " + inputDate.getValue() + + " using format string: " + formatString.getValue()); + } + catch (DateTimeParseException e) { + throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input date: " + inputDate.getValue() + + " using format string: " + formatString.getValue()); } } - - public IValue parseDateInLocale(IString inputDate, IString formatString, IString locale) - //@doc{Parse an input date given as a string using a specific locale and format string} + + public IValue parseDateInLocale(IString inputDate, IString formatString, IString locale) + // @doc{Parse an input date given as a string using a specific locale and format string} { try { - java.text.SimpleDateFormat fmt = new java.text.SimpleDateFormat(formatString.getValue(), new Locale(locale.getValue())); + java.text.SimpleDateFormat fmt = + new java.text.SimpleDateFormat(formatString.getValue(), new Locale(locale.getValue())); fmt.parse(inputDate.getValue()); java.util.Calendar cal = fmt.getCalendar(); return values.date(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DATE)); - } catch (IllegalArgumentException iae) { - throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input date: " + inputDate.getValue() + - " using format string: " + formatString.getValue() + " in locale: " + locale.getValue()); - } catch (ParseException e) { - throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input date: " + inputDate.getValue() + - " using format string: " + formatString.getValue() + " in locale: " + locale.getValue()); + } + catch (IllegalArgumentException iae) { + throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input date: " + inputDate.getValue() + + " using format string: " + formatString.getValue() + " in locale: " + locale.getValue()); + } + catch (ParseException e) { + throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input date: " + inputDate.getValue() + + " using format string: " + formatString.getValue() + " in locale: " + locale.getValue()); } } - public IValue parseTime(IString inputTime, IString formatString) - //@doc{Parse an input time given as a string using the given format string} + public IValue parseTime(IString inputTime, IString formatString) + // @doc{Parse an input time given as a string using the given format string} { try { java.text.SimpleDateFormat fmt = new java.text.SimpleDateFormat(formatString.getValue()); @@ -519,22 +548,26 @@ public IValue parseTime(IString inputTime, IString formatString) // but then we use mod 60 since this gives us total # of minutes, including // the hours we have already computed. int zoneHours = cal.get(Calendar.ZONE_OFFSET) / (1000 * 60 * 60); - int zoneMinutes = (cal.get(Calendar.ZONE_OFFSET) / (1000 * 60)) % 60; - return values.time(cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND), cal.get(Calendar.MILLISECOND), zoneHours, zoneMinutes); - } catch (IllegalArgumentException iae) { - throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input date: " + inputTime.getValue() + - " using format string: " + formatString.getValue()); - } catch (ParseException e) { - throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input date: " + inputTime.getValue() + - " using format string: " + formatString.getValue()); + int zoneMinutes = (cal.get(Calendar.ZONE_OFFSET) / (1000 * 60)) % 60; + return values.time(cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND), + cal.get(Calendar.MILLISECOND), zoneHours, zoneMinutes); + } + catch (IllegalArgumentException iae) { + throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input date: " + inputTime.getValue() + + " using format string: " + formatString.getValue()); + } + catch (ParseException e) { + throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input date: " + inputTime.getValue() + + " using format string: " + formatString.getValue()); } } - - public IValue parseTimeInLocale(IString inputTime, IString formatString, IString locale) - //@doc{Parse an input time given as a string using a specific locale and format string} + + public IValue parseTimeInLocale(IString inputTime, IString formatString, IString locale) + // @doc{Parse an input time given as a string using a specific locale and format string} { try { - java.text.SimpleDateFormat fmt = new java.text.SimpleDateFormat(formatString.getValue(), new Locale(locale.getValue())); + java.text.SimpleDateFormat fmt = + new java.text.SimpleDateFormat(formatString.getValue(), new Locale(locale.getValue())); fmt.parse(inputTime.getValue()); java.util.Calendar cal = fmt.getCalendar(); // The value for zone offset comes back in milliseconds. The number of @@ -543,42 +576,41 @@ public IValue parseTimeInLocale(IString inputTime, IString formatString, IString // but then we use mod 60 since this gives us total # of minutes, including // the hours we have already computed. int zoneHours = cal.get(Calendar.ZONE_OFFSET) / (1000 * 60 * 60); - int zoneMinutes = (cal.get(Calendar.ZONE_OFFSET) / (1000 * 60)) % 60; - return values.time(cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND), cal.get(Calendar.MILLISECOND), zoneHours, zoneMinutes); - } catch (IllegalArgumentException iae) { - throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input time: " + inputTime.getValue() + - " using format string: " + formatString.getValue() + " in locale: " + locale.getValue()); - } catch (ParseException e) { - throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input time: " + inputTime.getValue() + - " using format string: " + formatString.getValue() + " in locale: " + locale.getValue()); + int zoneMinutes = (cal.get(Calendar.ZONE_OFFSET) / (1000 * 60)) % 60; + return values.time(cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND), + cal.get(Calendar.MILLISECOND), zoneHours, zoneMinutes); + } + catch (IllegalArgumentException iae) { + throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input time: " + inputTime.getValue() + + " using format string: " + formatString.getValue() + " in locale: " + locale.getValue()); + } + catch (ParseException e) { + throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input time: " + inputTime.getValue() + + " using format string: " + formatString.getValue() + " in locale: " + locale.getValue()); } } public IString printSymbol(IConstructor symbol, IBool withLayout) { - return values.string(SymbolAdapter.toString(symbol, withLayout.getValue())); + return values.string(SymbolAdapter.toString(symbol, withLayout.getValue())); } - + public IValue parseDateTime(IString inputDateTime, IString formatString) { - return parseDateTime(values, inputDateTime, formatString); + return parseDateTime(values, inputDateTime, formatString); } - - static public IValue parseDateTime(IValueFactory values, IString inputDateTime, IString formatString) - //@doc{Parse an input datetime given as a string using the given format string} + + static public IValue parseDateTime(IValueFactory values, IString inputDateTime, IString formatString) + // @doc{Parse an input datetime given as a string using the given format string} { try { - java.text.SimpleDateFormat fmt = new java.text.SimpleDateFormat(formatString.getValue()); - fmt.setLenient(false); - fmt.parse(inputDateTime.getValue()); - java.util.Calendar cal = fmt.getCalendar(); - int zoneHours = cal.get(Calendar.ZONE_OFFSET) / (1000 * 60 * 60); - int zoneMinutes = (cal.get(Calendar.ZONE_OFFSET) / (1000 * 60)) % 60; - return values.datetime(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH), cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND), cal.get(Calendar.MILLISECOND), zoneHours, zoneMinutes); + DateTimeFormatter fmt = DateTimeFormatter.ofPattern(formatString.getValue()); + TemporalAccessor output = fmt.parse(inputDateTime.getValue()); + return temporalToIValue(values, output); } catch (IllegalArgumentException iae) { throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input datetime: " + inputDateTime.getValue() + " using format string: " + formatString.getValue()); - } catch (ParseException e) { + } catch (DateTimeParseException e) { throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input datetime: " + inputDateTime.getValue() + - " using format string: " + formatString.getValue()); + " using format string: " + formatString.getValue() + " with: " + e.getMessage()); } } @@ -586,18 +618,15 @@ public IValue parseDateTimeInLocale(IString inputDateTime, IString formatString, //@doc{Parse an input datetime given as a string using a specific locale and format string} { try { - java.text.SimpleDateFormat fmt = new java.text.SimpleDateFormat(formatString.getValue(), new Locale(locale.getValue())); - fmt.parse(inputDateTime.getValue()); - java.util.Calendar cal = fmt.getCalendar(); - int zoneHours = cal.get(Calendar.ZONE_OFFSET) / (1000 * 60 * 60); - int zoneMinutes = (cal.get(Calendar.ZONE_OFFSET) / (1000 * 60)) % 60; - return values.datetime(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH), cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND), cal.get(Calendar.MILLISECOND), zoneHours, zoneMinutes); + DateTimeFormatter fmt = DateTimeFormatter.ofPattern(formatString.getValue()).withLocale(Locale.forLanguageTag(locale.getValue())); + TemporalAccessor output = fmt.parse(inputDateTime.getValue()); + return temporalToIValue(values, output); } catch (IllegalArgumentException iae) { throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input datetime: " + inputDateTime.getValue() + " using format string: " + formatString.getValue() + " in locale: " + locale.getValue()); - } catch (ParseException e) { + } catch (DateTimeParseException e) { throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input datetime: " + inputDateTime.getValue() + - " using format string: " + formatString.getValue() + " in locale: " + locale.getValue()); + " using format string: " + formatString.getValue() + " in locale: " + locale.getValue() + " with: " + e.getMessage()); } } diff --git a/src/org/rascalmpl/library/lang/csv/IO.java b/src/org/rascalmpl/library/lang/csv/IO.java index 32122a5a04c..7a56347cc93 100644 --- a/src/org/rascalmpl/library/lang/csv/IO.java +++ b/src/org/rascalmpl/library/lang/csv/IO.java @@ -330,7 +330,7 @@ else if (currentType.isDateTime()) { try { // lets be a bit more flexible than rascal's string reader is. // 2012-06-24T00:59:56Z - result[i] = Prelude.parseDateTime(values, values.string(field), values.string("yyyy-MM-dd'T'HH:mm:ss'Z'")); + result[i] = Prelude.parseDateTime(values, values.string(field), values.string("yyyy-MM-dd'T'HH:mm:ssZZZZZ")); } catch (Throwable th) { throw RuntimeExceptionFactory.illegalTypeArgument("Invalid datetime: \"" + field + "\" (" + th.getMessage() + ")", null, null); diff --git a/src/org/rascalmpl/library/lang/json/IO.rsc b/src/org/rascalmpl/library/lang/json/IO.rsc index 36e824490b6..ada4f0f7636 100644 --- a/src/org/rascalmpl/library/lang/json/IO.rsc +++ b/src/org/rascalmpl/library/lang/json/IO.rsc @@ -57,7 +57,7 @@ In explicit constructor mode (`implicitConstructor==false`) the following array- A similar distinction is made for values of type `node`, configured using the `implicitNode` parameter. } -java &T readJSON(type[&T] expected, loc src, bool implicitConstructors = true, bool implicitNodes = true, str dateTimeFormat = "yyyy-MM-dd\'T\'HH:mm:ssZ", bool lenient=false); +java &T readJSON(type[&T] expected, loc src, bool implicitConstructors = true, bool implicitNodes = true, str dateTimeFormat = "yyyy-MM-dd\'T\'HH:mm:ssZZZZZ", bool lenient=false); @javaClass{org.rascalmpl.library.lang.json.IO} @doc{parses JSON values from a string @@ -87,10 +87,10 @@ In explicit constructor mode (`implicitConstructor==false`) the following array- A similar distinction is made for values of type `node`, configured using the `implicitNode` parameter. } -java &T parseJSON(type[&T] expected, str src, bool implicitConstructors = true, bool implicitNodes = true, str dateTimeFormat = "yyyy-MM-dd\'T\'HH:mm:ssZ", bool lenient=false); +java &T parseJSON(type[&T] expected, str src, bool implicitConstructors = true, bool implicitNodes = true, str dateTimeFormat = "yyyy-MM-dd\'T\'HH:mm:ssZZZZZ", bool lenient=false); @javaClass{org.rascalmpl.library.lang.json.IO} -java void writeJSON(loc target, value val, bool implicitConstructors=true, bool implicitNodes=true, bool unpackedLocations=false, str dateTimeFormat="yyyy-MM-dd\'T\'HH:mm:ssZ", bool dateTimeAsInt=false, int indent=0); +java void writeJSON(loc target, value val, bool implicitConstructors=true, bool implicitNodes=true, bool unpackedLocations=false, str dateTimeFormat="yyyy-MM-dd\'T\'HH:mm:ssZZZZZ", bool dateTimeAsInt=false, int indent=0); @javaClass{org.rascalmpl.library.lang.json.IO} -java str asJSON(value val, bool implicitConstructors=true, bool implicitNodes=true, bool unpackedLocations=false, str dateTimeFormat="yyyy-MM-dd\'T\'HH:mm:ssZ", bool dateTimeAsInt=false, int indent = 0); +java str asJSON(value val, bool implicitConstructors=true, bool implicitNodes=true, bool unpackedLocations=false, str dateTimeFormat="yyyy-MM-dd\'T\'HH:mm:ssZZZZZ", bool dateTimeAsInt=false, int indent = 0); diff --git a/src/org/rascalmpl/library/lang/rascal/tests/library/lang/csv/CSVIOTests.rsc b/src/org/rascalmpl/library/lang/rascal/tests/library/lang/csv/CSVIOTests.rsc index e638f031ba1..367fcb0a225 100644 --- a/src/org/rascalmpl/library/lang/rascal/tests/library/lang/csv/CSVIOTests.rsc +++ b/src/org/rascalmpl/library/lang/rascal/tests/library/lang/csv/CSVIOTests.rsc @@ -70,7 +70,7 @@ test bool csvBoolean() { } test bool csvDateTime() { - writeFile(targetFile, "col1,col2\n2012-06-24T00:59:56Z,"); + writeFile(targetFile, "col1,col2\n2012-06-24T00:59:56+02:10,"); r = readCSV(#lrel[datetime a, datetime b], targetFile)[0]; return r.a == r.b; } From 23edb59c112d6e5be26200197f4fec66a9927daf Mon Sep 17 00:00:00 2001 From: Davy Landman Date: Thu, 10 Jun 2021 14:04:03 +0200 Subject: [PATCH 07/10] Removed all calendar code in the prelude --- src/org/rascalmpl/library/Prelude.java | 280 ++++-------------- .../lang/rascal/tests/library/DateTime.rsc | 3 +- 2 files changed, 55 insertions(+), 228 deletions(-) diff --git a/src/org/rascalmpl/library/Prelude.java b/src/org/rascalmpl/library/Prelude.java index 6863dd1b5a7..665c4b85bcf 100644 --- a/src/org/rascalmpl/library/Prelude.java +++ b/src/org/rascalmpl/library/Prelude.java @@ -48,7 +48,6 @@ import java.time.OffsetDateTime; import java.time.OffsetTime; import java.time.Period; -import java.time.ZoneId; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; @@ -74,10 +73,6 @@ import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; -import com.ibm.icu.text.SimpleDateFormat; -import com.ibm.icu.util.Calendar; -import com.ibm.icu.util.TimeZone; -import com.ibm.icu.util.ULocale; import org.apache.commons.lang.CharSetUtils; import org.rascalmpl.exceptions.RuntimeExceptionFactory; @@ -502,92 +497,25 @@ else if (dEnd.isDate()) { public IValue parseDate(IString inputDate, IString formatString) // @doc{Parse an input date given as a string using the given format string} { - try { - return temporalToDate(values, LocalDate.parse(inputDate.getValue(), DateTimeFormatter.ofPattern(formatString.getValue()))); - } - catch (IllegalArgumentException iae) { - throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input date: " + inputDate.getValue() - + " using format string: " + formatString.getValue()); - } - catch (DateTimeParseException e) { - throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input date: " + inputDate.getValue() - + " using format string: " + formatString.getValue()); - } + return parseDateTime(values, inputDate, prepareFormatter(formatString)); } public IValue parseDateInLocale(IString inputDate, IString formatString, IString locale) // @doc{Parse an input date given as a string using a specific locale and format string} { - try { - java.text.SimpleDateFormat fmt = - new java.text.SimpleDateFormat(formatString.getValue(), new Locale(locale.getValue())); - fmt.parse(inputDate.getValue()); - java.util.Calendar cal = fmt.getCalendar(); - return values.date(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DATE)); - } - catch (IllegalArgumentException iae) { - throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input date: " + inputDate.getValue() - + " using format string: " + formatString.getValue() + " in locale: " + locale.getValue()); - } - catch (ParseException e) { - throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input date: " + inputDate.getValue() - + " using format string: " + formatString.getValue() + " in locale: " + locale.getValue()); - } + return parseDateTime(values, inputDate, prepareFormatterLocale(formatString, locale)); } public IValue parseTime(IString inputTime, IString formatString) // @doc{Parse an input time given as a string using the given format string} { - try { - java.text.SimpleDateFormat fmt = new java.text.SimpleDateFormat(formatString.getValue()); - fmt.parse(inputTime.getValue()); - java.util.Calendar cal = fmt.getCalendar(); - // The value for zone offset comes back in milliseconds. The number of - // hours is thus milliseconds / 1000 (to get to seconds) / 60 (to get to minutes) - // / 60 (to get to hours). Minutes is this except for the last division, - // but then we use mod 60 since this gives us total # of minutes, including - // the hours we have already computed. - int zoneHours = cal.get(Calendar.ZONE_OFFSET) / (1000 * 60 * 60); - int zoneMinutes = (cal.get(Calendar.ZONE_OFFSET) / (1000 * 60)) % 60; - return values.time(cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND), - cal.get(Calendar.MILLISECOND), zoneHours, zoneMinutes); - } - catch (IllegalArgumentException iae) { - throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input date: " + inputTime.getValue() - + " using format string: " + formatString.getValue()); - } - catch (ParseException e) { - throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input date: " + inputTime.getValue() - + " using format string: " + formatString.getValue()); - } + return parseDateTime(values, inputTime, prepareFormatter(formatString)); } public IValue parseTimeInLocale(IString inputTime, IString formatString, IString locale) // @doc{Parse an input time given as a string using a specific locale and format string} { - try { - java.text.SimpleDateFormat fmt = - new java.text.SimpleDateFormat(formatString.getValue(), new Locale(locale.getValue())); - fmt.parse(inputTime.getValue()); - java.util.Calendar cal = fmt.getCalendar(); - // The value for zone offset comes back in milliseconds. The number of - // hours is thus milliseconds / 1000 (to get to seconds) / 60 (to get to minutes) - // / 60 (to get to hours). Minutes is this except for the last division, - // but then we use mod 60 since this gives us total # of minutes, including - // the hours we have already computed. - int zoneHours = cal.get(Calendar.ZONE_OFFSET) / (1000 * 60 * 60); - int zoneMinutes = (cal.get(Calendar.ZONE_OFFSET) / (1000 * 60)) % 60; - return values.time(cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND), - cal.get(Calendar.MILLISECOND), zoneHours, zoneMinutes); - } - catch (IllegalArgumentException iae) { - throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input time: " + inputTime.getValue() - + " using format string: " + formatString.getValue() + " in locale: " + locale.getValue()); - } - catch (ParseException e) { - throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input time: " + inputTime.getValue() - + " using format string: " + formatString.getValue() + " in locale: " + locale.getValue()); - } + return parseDateTime(values, inputTime, prepareFormatterLocale(formatString, locale)); } public IString printSymbol(IConstructor symbol, IBool withLayout) { @@ -601,230 +529,128 @@ public IValue parseDateTime(IString inputDateTime, IString formatString) { static public IValue parseDateTime(IValueFactory values, IString inputDateTime, IString formatString) // @doc{Parse an input datetime given as a string using the given format string} { - try { - DateTimeFormatter fmt = DateTimeFormatter.ofPattern(formatString.getValue()); - TemporalAccessor output = fmt.parse(inputDateTime.getValue()); - return temporalToIValue(values, output); - } catch (IllegalArgumentException iae) { - throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input datetime: " + inputDateTime.getValue() + - " using format string: " + formatString.getValue()); - } catch (DateTimeParseException e) { - throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input datetime: " + inputDateTime.getValue() + - " using format string: " + formatString.getValue() + " with: " + e.getMessage()); - } + return parseDateTime(values, inputDateTime, prepareFormatter(formatString)); } public IValue parseDateTimeInLocale(IString inputDateTime, IString formatString, IString locale) //@doc{Parse an input datetime given as a string using a specific locale and format string} { - try { - DateTimeFormatter fmt = DateTimeFormatter.ofPattern(formatString.getValue()).withLocale(Locale.forLanguageTag(locale.getValue())); - TemporalAccessor output = fmt.parse(inputDateTime.getValue()); - return temporalToIValue(values, output); - } catch (IllegalArgumentException iae) { - throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input datetime: " + inputDateTime.getValue() + - " using format string: " + formatString.getValue() + " in locale: " + locale.getValue()); - } catch (DateTimeParseException e) { - throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input datetime: " + inputDateTime.getValue() + - " using format string: " + formatString.getValue() + " in locale: " + locale.getValue() + " with: " + e.getMessage()); - } - } - - private Calendar getCalendarForDate(IDateTime inputDate) { - if (inputDate.isDate() || inputDate.isDateTime()) { - Calendar cal = Calendar.getInstance(TimeZone.getDefault(),Locale.getDefault()); - cal.setLenient(false); - cal.set(inputDate.getYear(), inputDate.getMonthOfYear()-1, inputDate.getDayOfMonth()); - return cal; - } else { - throw new IllegalArgumentException("Cannot get date for a datetime that only represents the time"); - } - } - - private Calendar getCalendarForTime(IDateTime inputTime) { - if (inputTime.isTime() || inputTime.isDateTime()) { - Calendar cal = Calendar.getInstance(TimeZone.getTimeZone(getTZString(inputTime.getTimezoneOffsetHours(),inputTime.getTimezoneOffsetMinutes())),Locale.getDefault()); - cal.setLenient(false); - cal.set(Calendar.HOUR_OF_DAY, inputTime.getHourOfDay()); - cal.set(Calendar.MINUTE, inputTime.getMinuteOfHour()); - cal.set(Calendar.SECOND, inputTime.getSecondOfMinute()); - cal.set(Calendar.MILLISECOND, inputTime.getMillisecondsOfSecond()); - return cal; - } else { - throw new IllegalArgumentException("Cannot get time for a datetime that only represents the date"); - } + return parseDateTime(values, inputDateTime, prepareFormatterLocale(formatString, locale)); } - private Calendar getCalendarForDateTime(IDateTime inputDateTime) { - if (inputDateTime.isDateTime()) { - Calendar cal = Calendar.getInstance(TimeZone.getTimeZone(getTZString(inputDateTime.getTimezoneOffsetHours(),inputDateTime.getTimezoneOffsetMinutes())),Locale.getDefault()); - cal.setLenient(false); - cal.set(inputDateTime.getYear(), inputDateTime.getMonthOfYear()-1, inputDateTime.getDayOfMonth(), inputDateTime.getHourOfDay(), inputDateTime.getMinuteOfHour(), inputDateTime.getSecondOfMinute()); - cal.set(Calendar.MILLISECOND, inputDateTime.getMillisecondsOfSecond()); - return cal; - } else { - throw new IllegalArgumentException("Cannot get date and time for a datetime that only represents the date or the time"); + private static IValue parseDateTime(IValueFactory values, IString input, DateTimeFormatter format) { + try { + return temporalToIValue(values, format.parse(input.getValue())); + } catch (IllegalArgumentException | DateTimeParseException e) { + throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input: " + input.getValue() + " with formatter: " + format + " error: " + e.getMessage()); } + } + public IValue printDate(IDateTime inputDate, IString formatString) //@doc{Print an input date using the given format string} { - try { - SimpleDateFormat sd = new SimpleDateFormat(formatString.getValue()); - Calendar cal = getCalendarForDate(inputDate); - sd.setCalendar(cal); - return values.string(sd.format(cal.getTime())); - } catch (IllegalArgumentException iae) { - throw RuntimeExceptionFactory.dateTimePrintingError("Cannot print date " + inputDate + " with format " + formatString.getValue()); - } + return printDateTime(inputDate, prepareFormatter(formatString)); } public IValue printDate(IDateTime inputDate) //@doc{Print an input date using a default format string} { - SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd"); - Calendar cal = getCalendarForDate(inputDate); - sd.setCalendar(cal); - return values.string(sd.format(cal.getTime())); + return printDate(inputDate, values.string("yyyy-MM-dd")); } public IValue printDateInLocale(IDateTime inputDate, IString formatString, IString locale) //@doc{Print an input date using a specific locale and format string} { - try { - SimpleDateFormat sd = new SimpleDateFormat(formatString.getValue(),new ULocale(locale.getValue())); - Calendar cal = getCalendarForDate(inputDate); - sd.setCalendar(cal); - return values.string(sd.format(cal.getTime())); - } catch (IllegalArgumentException iae) { - throw RuntimeExceptionFactory.dateTimePrintingError("Cannot print date " + inputDate + " with format " + formatString.getValue() + ", in locale: " + locale.getValue()); - } + return printDateTime(inputDate, prepareFormatterLocale(formatString, locale)); } public IValue printDateInLocale(IDateTime inputDate, IString locale) //@doc{Print an input date using a specific locale and a default format string} { - try { - SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd",new ULocale(locale.getValue())); - Calendar cal = getCalendarForDate(inputDate); - sd.setCalendar(cal); - return values.string(sd.format(cal.getTime())); - } catch (IllegalArgumentException iae) { - throw RuntimeExceptionFactory.dateTimePrintingError("Cannot print time " + inputDate + " in locale: " + locale.getValue()); - } + return printDateInLocale(inputDate, values.string("yyyy-MM-dd"), locale); } public IValue printTime(IDateTime inputTime, IString formatString) //@doc{Print an input time using the given format string} { - try { - SimpleDateFormat sd = new SimpleDateFormat(formatString.getValue()); - Calendar cal = getCalendarForTime(inputTime); - sd.setCalendar(cal); - return values.string(sd.format(cal.getTime())); - } catch (IllegalArgumentException iae) { - throw RuntimeExceptionFactory.dateTimePrintingError("Cannot print time " + inputTime + " with format: " + formatString.getValue()); - } + return printDateTime(inputTime, prepareFormatter(formatString)); } public IValue printTime(IDateTime inputTime) //@doc{Print an input time using a default format string} { - SimpleDateFormat sd = new SimpleDateFormat("HH:mm:ss.SSSZ"); - Calendar cal = getCalendarForTime(inputTime); - sd.setCalendar(cal); - return values.string(sd.format(cal.getTime())); + return printTime(inputTime, values.string("HH:mm:ss.SSSZZZZZ")); } public IValue printTimeInLocale(IDateTime inputTime, IString formatString, IString locale) //@doc{Print an input time using a specific locale and format string} { - try { - SimpleDateFormat sd = new SimpleDateFormat(formatString.getValue(),new ULocale(locale.getValue())); - Calendar cal = getCalendarForTime(inputTime); - sd.setCalendar(cal); - return values.string(sd.format(cal.getTime())); - } catch (IllegalArgumentException iae) { - throw RuntimeExceptionFactory.dateTimePrintingError("Cannot print time " + inputTime + " in locale: " + locale.getValue()); - } + return printDateTime(inputTime, prepareFormatterLocale(formatString, locale)); } public IValue printTimeInLocale(IDateTime inputTime, IString locale) //@doc{Print an input time using a specific locale and a default format string} { - try { - SimpleDateFormat sd = new SimpleDateFormat("HH:mm:ss.SSSZ",new ULocale(locale.getValue())); - Calendar cal = getCalendarForTime(inputTime); - sd.setCalendar(cal); - return values.string(sd.format(cal.getTime())); - } catch (IllegalArgumentException iae) { - throw RuntimeExceptionFactory.dateTimePrintingError("Cannot print time " + inputTime + " in locale: " + locale.getValue()); - } + return printDateTimeInLocale(inputTime, values.string("HH:mm:ss.SSSZZZZZ"), locale); } public IValue printDateTime(IDateTime inputDateTime, IString formatString) //@doc{Print an input datetime using the given format string} { + return printDateTime(inputDateTime, prepareFormatter(formatString)); + } + + private IString printDateTime(IDateTime input, DateTimeFormatter fmt) { try { - SimpleDateFormat sd = new SimpleDateFormat(formatString.getValue()); - Calendar cal = getCalendarForDateTime(inputDateTime); - sd.setCalendar(cal); - return values.string(sd.format(cal.getTime())); - } catch (IllegalArgumentException iae) { - throw RuntimeExceptionFactory.dateTimePrintingError("Cannot print datetime " + inputDateTime + " using format string: " + formatString.getValue()); - } + return values.string(fmt.format(dateTimeToJava(input))); + } catch (RuntimeException iae) { + throw RuntimeExceptionFactory.dateTimePrintingError("Cannot print datetime " + input + " using formatter: " + fmt); + } } public IValue printDateTime(IDateTime inputDateTime) //@doc{Print an input datetime using a default format string} { - SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ"); - Calendar cal = getCalendarForDateTime(inputDateTime); - sd.setCalendar(cal); - return values.string(sd.format(cal.getTime())); + return printDateTime(inputDateTime, values.string("yyyy-MM-dd HH:mm:ss.SSSZZZZZ")); } public IValue printDateTimeInLocale(IDateTime inputDateTime, IString formatString, IString locale) //@doc{Print an input datetime using a specific locale and format string} { - try { - SimpleDateFormat sd = new SimpleDateFormat(formatString.getValue(),new ULocale(locale.getValue())); - Calendar cal = getCalendarForDateTime(inputDateTime); - sd.setCalendar(cal); - return values.string(sd.format(cal.getTime())); - } catch (IllegalArgumentException iae) { - throw RuntimeExceptionFactory.dateTimePrintingError("Cannot print datetime " + inputDateTime + " using format string: " + formatString.getValue() + - " in locale: " + locale.getValue()); - } + return printDateTime(inputDateTime, prepareFormatterLocale(formatString, locale)); + } + + private static DateTimeFormatter prepareFormatter(IString formatString) { + return DateTimeFormatter.ofPattern(formatString.getValue()); + } + + private static DateTimeFormatter prepareFormatterLocale(IString formatString, IString locale) { + return prepareFormatterLocale(formatString.getValue(), locale.getValue()); + } + + private static DateTimeFormatter prepareFormatterLocale(String formatString, String locale) { + return DateTimeFormatter.ofPattern(formatString) + .withLocale(Locale.forLanguageTag(locale)); } public IValue printDateTimeInLocale(IDateTime inputDateTime, IString locale) //@doc{Print an input datetime using a specific locale and a default format string} { - try { - SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ",new ULocale(locale.getValue())); - Calendar cal = getCalendarForDateTime(inputDateTime); - sd.setCalendar(cal); - return values.string(sd.format(cal.getTime())); - } catch (IllegalArgumentException iae) { - throw RuntimeExceptionFactory.dateTimePrintingError("Cannot print datetime " + inputDateTime + " in locale: " + locale.getValue()); - } + return printDateTimeInLocale(inputDateTime, values.string("yyyy-MM-dd HH:mm:ss.SSS ZZZZZ"), locale); } public IValue daysDiff(IDateTime dtStart, IDateTime dtEnd) //@doc{Increment the years by a given amount.} - { - if (!(dtStart.isTime() || dtEnd.isTime())) { - Calendar startCal = Calendar.getInstance(); - startCal.setTimeInMillis(dtStart.getInstant()); - Calendar endCal = Calendar.getInstance(); - endCal.setTimeInMillis(dtEnd.getInstant()); - - return values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.DAY_OF_MONTH)); - } - throw RuntimeExceptionFactory.invalidUseOfTimeException("Both inputs must include dates."); - } + { + if (!(dtStart.isTime() || dtEnd.isTime())) { + Temporal start = dateTimeToJava(dtStart); + Temporal end = dateTimeToJava(dtEnd); + return values.integer(ChronoUnit.DAYS.between(start, end)); + } + throw RuntimeExceptionFactory.invalidUseOfTimeException("Both inputs must include dates."); + } /* * Graph diff --git a/src/org/rascalmpl/library/lang/rascal/tests/library/DateTime.rsc b/src/org/rascalmpl/library/lang/rascal/tests/library/DateTime.rsc index bc2a2cd4437..838baf45102 100644 --- a/src/org/rascalmpl/library/lang/rascal/tests/library/DateTime.rsc +++ b/src/org/rascalmpl/library/lang/rascal/tests/library/DateTime.rsc @@ -94,9 +94,10 @@ str formattedDate(datetime dt) = "--"; str formattedTime(datetime dt) = - "::."; + "::.:">"; bool negativeOffset(datetime dt) = dt.timezoneOffsetHours < 0 || dt.timezoneOffsetMinutes < 0; +bool isZulu(datetime dt) = dt.timezoneOffsetHours == 0 && dt.timezoneOffsetMinutes == 0; private str fill(int val) = fill(val, 2); private str fill(int val, int n) = right("", n, "0"); \ No newline at end of file From 858f71fb1d32c128f5adabc65fd729ec4a1fc512 Mon Sep 17 00:00:00 2001 From: Davy Landman Date: Thu, 10 Jun 2021 21:13:16 +0200 Subject: [PATCH 08/10] Removed final dependency on icu --- pom.xml | 5 - .../interpreter/result/DateTimeResult.java | 65 ++++++------ src/org/rascalmpl/library/Prelude.java | 100 +++--------------- .../library/lang/json/io/JsonValueReader.java | 5 +- .../library/lang/json/io/JsonValueWriter.java | 4 +- .../rascalmpl/util/DateTimeConversions.java | 89 ++++++++++++++++ 6 files changed, 138 insertions(+), 130 deletions(-) create mode 100644 src/org/rascalmpl/util/DateTimeConversions.java diff --git a/pom.xml b/pom.xml index fb6adb2b1c2..8dbfc7f15b0 100644 --- a/pom.xml +++ b/pom.xml @@ -439,11 +439,6 @@ nanohttpd 2.3.1 - - com.ibm.icu - icu4j - 58.1 - org.asciidoctor asciidoctorj diff --git a/src/org/rascalmpl/interpreter/result/DateTimeResult.java b/src/org/rascalmpl/interpreter/result/DateTimeResult.java index 47f4a0d4ab7..7f110fdabd7 100644 --- a/src/org/rascalmpl/interpreter/result/DateTimeResult.java +++ b/src/org/rascalmpl/interpreter/result/DateTimeResult.java @@ -1,21 +1,23 @@ /******************************************************************************* - * Copyright (c) 2009-2013 CWI - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html + * Copyright (c) 2009-2013 CWI All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which accompanies this + * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html * * Contributors: - - * * Jurgen J. Vinju - Jurgen.Vinju@cwi.nl - CWI - * * Mark Hills - Mark.Hills@cwi.nl (CWI) - * * Arnold Lankamp - Arnold.Lankamp@cwi.nl -*******************************************************************************/ + * + * * Jurgen J. Vinju - Jurgen.Vinju@cwi.nl - CWI * Mark Hills - Mark.Hills@cwi.nl (CWI) * Arnold + * Lankamp - Arnold.Lankamp@cwi.nl + *******************************************************************************/ package org.rascalmpl.interpreter.result; import static org.rascalmpl.interpreter.result.ResultFactory.bool; import static org.rascalmpl.interpreter.result.ResultFactory.makeResult; +import java.time.Duration; +import java.time.LocalDate; +import java.time.Period; +import java.time.temporal.ChronoUnit; +import java.time.temporal.Temporal; import java.util.Iterator; import org.rascalmpl.exceptions.RuntimeExceptionFactory; @@ -24,6 +26,8 @@ import org.rascalmpl.interpreter.staticErrors.UndeclaredField; import org.rascalmpl.interpreter.staticErrors.UnexpectedType; import org.rascalmpl.interpreter.staticErrors.UnsupportedOperation; +import org.rascalmpl.util.DateTimeConversions; +import org.rascalmpl.values.ValueFactoryFactory; import io.usethesource.vallang.IBool; import io.usethesource.vallang.IDateTime; @@ -34,9 +38,6 @@ import io.usethesource.vallang.type.Type; import io.usethesource.vallang.type.TypeFactory; import io.usethesource.vallang.type.TypeStore; -import org.rascalmpl.values.ValueFactoryFactory; - -import com.ibm.icu.util.Calendar; public class DateTimeResult extends ElementResult { @@ -356,20 +357,18 @@ public Result subtract(Result that) { @Override protected Result subtractDateTime(DateTimeResult that) { IDateTime dStart = this.getValue(); - Calendar startCal = Calendar.getInstance(); - startCal.setTimeInMillis(dStart.getInstant()); - + Temporal tStart = DateTimeConversions.dateTimeToJava(dStart); IDateTime dEnd = that.getValue(); - Calendar endCal = Calendar.getInstance(); - endCal.setTimeInMillis(dEnd.getInstant()); + Temporal tEnd = DateTimeConversions.dateTimeToJava(dEnd); if (dStart.isDate()) { if (dEnd.isDate()) { + Period result = Period.between((LocalDate)tStart, (LocalDate)tEnd); return makeResult(Duration, VF.constructor(DateTimeResult.duration, - VF.integer(startCal.fieldDifference(endCal.getTime(), Calendar.YEAR)), - VF.integer(startCal.fieldDifference(endCal.getTime(), Calendar.MONTH)), - VF.integer(startCal.fieldDifference(endCal.getTime(), Calendar.DAY_OF_MONTH)), + VF.integer(result.getYears()), + VF.integer(result.getMonths()), + VF.integer(result.getDays()), VF.integer(0), VF.integer(0), VF.integer(0), @@ -382,16 +381,17 @@ protected Result subtractDateTime(DateTimeResult that) { } } else if (dStart.isTime()) { if (dEnd.isTime()) { + Duration result = java.time.Duration.between(tStart, tEnd); return makeResult(Duration, VF.constructor(DateTimeResult.duration, VF.integer(0), VF.integer(0), VF.integer(0), - VF.integer(startCal.fieldDifference(endCal.getTime(), Calendar.HOUR_OF_DAY)), - VF.integer(startCal.fieldDifference(endCal.getTime(), Calendar.MINUTE)), - VF.integer(startCal.fieldDifference(endCal.getTime(), Calendar.SECOND)), - VF.integer(startCal.fieldDifference(endCal.getTime(), Calendar.MILLISECOND))), - ctx); + VF.integer(result.toHours()), + VF.integer(result.toMinutes() % 60), + VF.integer(result.getSeconds() % 60), + VF.integer(result.toMillis() % 1000) + ), ctx); } else if (dEnd.isDate()) { throw RuntimeExceptionFactory.invalidUseOfDateException("Cannot determine the duration between a time with no date and a date with no time.", ctx.getCurrentAST(), null); } else { @@ -401,13 +401,12 @@ protected Result subtractDateTime(DateTimeResult that) { if (dEnd.isDateTime()) { return makeResult(Duration, VF.constructor(DateTimeResult.duration, - VF.integer(startCal.fieldDifference(endCal.getTime(), Calendar.YEAR)), - VF.integer(startCal.fieldDifference(endCal.getTime(), Calendar.MONTH)), - VF.integer(startCal.fieldDifference(endCal.getTime(), Calendar.DAY_OF_MONTH)), - VF.integer(startCal.fieldDifference(endCal.getTime(), Calendar.HOUR_OF_DAY)), - VF.integer(startCal.fieldDifference(endCal.getTime(), Calendar.MINUTE)), - VF.integer(startCal.fieldDifference(endCal.getTime(), Calendar.SECOND)), - VF.integer(startCal.fieldDifference(endCal.getTime(), Calendar.MILLISECOND))), + VF.integer(ChronoUnit.MONTHS.between(tStart, tEnd) % 12), + VF.integer(ChronoUnit.DAYS.between(tStart, tEnd) % 31), // TODO is this right? + VF.integer(ChronoUnit.HOURS.between(tStart, tEnd) % 24), + VF.integer(ChronoUnit.MINUTES.between(tStart, tEnd) % 60), + VF.integer(ChronoUnit.SECONDS.between(tStart, tEnd) % 60), + VF.integer(ChronoUnit.MILLIS.between(tStart, tEnd) % 1000)), ctx); } else if (dEnd.isDate()) { throw RuntimeExceptionFactory.invalidUseOfDateException("Cannot determine the duration between a datetime and a date with no time.", ctx.getCurrentAST(), null); diff --git a/src/org/rascalmpl/library/Prelude.java b/src/org/rascalmpl/library/Prelude.java index 665c4b85bcf..56d8c638eb1 100644 --- a/src/org/rascalmpl/library/Prelude.java +++ b/src/org/rascalmpl/library/Prelude.java @@ -43,12 +43,10 @@ import java.time.Duration; 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.Period; -import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.time.temporal.ChronoField; @@ -90,6 +88,7 @@ import org.rascalmpl.uri.URIResolverRegistry; import org.rascalmpl.uri.URIUtil; import org.rascalmpl.uri.UnsupportedSchemeException; +import org.rascalmpl.util.DateTimeConversions; import org.rascalmpl.values.IRascalValueFactory; import org.rascalmpl.values.RascalValueFactory; import org.rascalmpl.values.functions.IFunction; @@ -247,18 +246,9 @@ public IValue incrementDays(IDateTime dt, IInteger n) return incrementDate(dt, ChronoUnit.DAYS, "days", n); } - private String getTZString(int hourOffset, int minuteOffset) { - String tzString = "GMT" + ((hourOffset < 0 || (0 == hourOffset && minuteOffset < 0)) ? "-" : "+") - + String.format("%02d", hourOffset >= 0 ? hourOffset : hourOffset * -1) - + String.format("%02d", minuteOffset >= 0 ? minuteOffset : minuteOffset * -1); - return tzString; - } - - private final int millisInAMinute = 1000 * 60; - private final int millisInAnHour = millisInAMinute * 60; private IValue incrementDTField(IDateTime dt, ChronoUnit field, IInteger amount) { - Temporal actualDt = dateTimeToJava(dt); + Temporal actualDt = DateTimeConversions.dateTimeToJava(dt); Temporal result = actualDt.plus(amount.longValue(), field); if (dt.isDate()) { @@ -270,86 +260,20 @@ else if (dt.isTime()) { return temporalToDateTime((OffsetDateTime) result); } private IDateTime temporalToDate(TemporalAccessor t) { - return temporalToDate(values, (LocalDate)t); + return DateTimeConversions.temporalToDate(values, (LocalDate)t); } - private static IDateTime temporalToDate(IValueFactory values, LocalDate t) { - return values.date(t.getYear(), t.getMonthValue(), t.getDayOfMonth()); - } private IDateTime temporalToTime(TemporalAccessor t) { if (t instanceof OffsetTime) { - return temporalToTime(values, (OffsetTime)t); + return DateTimeConversions.temporalToTime(values, (OffsetTime)t); } - return temporalToTime(values, (LocalTime)t); - } - private static IDateTime temporalToTime(IValueFactory values, OffsetTime t) { - return values.time(t.getHour(), t.getMinute(), t.getSecond(), t.get(ChronoField.MILLI_OF_SECOND), - (int) TimeUnit.HOURS.convert(t.getOffset().getTotalSeconds(), TimeUnit.SECONDS), - (int) (TimeUnit.MINUTES.convert(t.getOffset().getTotalSeconds(), TimeUnit.SECONDS) % 60)); - } - - private static IDateTime temporalToTime(IValueFactory values, LocalTime t) { - return values.time(t.getHour(), t.getMinute(), t.getSecond(), t.get(ChronoField.MILLI_OF_SECOND)); + return DateTimeConversions.temporalToTime(values, (LocalTime)t); } - public static IDateTime temporalToDateTime(IValueFactory values, OffsetDateTime t) { - return values.datetime(t.getYear(), t.getMonthValue(), t.getDayOfMonth(), t.getHour(), t.getMinute(), - t.getSecond(), t.get(ChronoField.MILLI_OF_SECOND), - (int) TimeUnit.HOURS.convert(t.getOffset().getTotalSeconds(), TimeUnit.SECONDS), - (int) (TimeUnit.MINUTES.convert(t.getOffset().getTotalSeconds(), TimeUnit.SECONDS) % 60)); - } - - public static IDateTime temporalToDateTime(IValueFactory values, LocalDateTime t) { - return values.datetime(t.getYear(), t.getMonthValue(), t.getDayOfMonth(), t.getHour(), t.getMinute(), - t.getSecond(), t.get(ChronoField.MILLI_OF_SECOND) - ); - } - - public static IDateTime temporalToIValue(IValueFactory values, TemporalAccessor t) { - boolean hasDate = t.isSupported(ChronoField.YEAR); - boolean hasTime = t.isSupported(ChronoField.HOUR_OF_DAY); - boolean hasZoneOffset = t.isSupported(ChronoField.OFFSET_SECONDS); - if (hasDate) { - if (hasTime) { - if (hasZoneOffset) { - return temporalToDateTime(values, OffsetDateTime.from(t)); - } - return temporalToDateTime(values, LocalDateTime.from(t)); - } - return temporalToDate(values, LocalDate.from(t)); - } - if (hasZoneOffset) { - return temporalToTime(values, OffsetTime.from(t)); - } - return temporalToTime(values, LocalTime.from(t)); - } private IDateTime temporalToDateTime(OffsetDateTime t) { - return temporalToDateTime(values, t); - } - - public static Temporal dateTimeToJava(IDateTime dt) { - LocalDate datePart = null; - if (!dt.isTime()) { - datePart = LocalDate.of(dt.getYear(), dt.getMonthOfYear(), dt.getDayOfMonth()); - } - - OffsetTime timePart = null; - if (!dt.isDate()) { - // vallang always has timezone offset in case it's not a date - timePart = OffsetTime.of(dt.getHourOfDay(), dt.getMinuteOfHour(), dt.getSecondOfMinute(), - (int) TimeUnit.MILLISECONDS.toNanos(dt.getMillisecondsOfSecond()), - ZoneOffset.ofHoursMinutes(dt.getTimezoneOffsetHours(), dt.getTimezoneOffsetMinutes())); - } - if (datePart == null && timePart != null) { - return timePart; - } - if (timePart == null && datePart != null) { - return datePart; - } - assert timePart != null && datePart != null; - return datePart.atTime(timePart); + return DateTimeConversions.temporalToDateTime(values, t); } private IValue incrementTime(IDateTime dt, ChronoUnit field, String fieldName, IInteger amount) { @@ -436,8 +360,8 @@ public IValue decrementMilliseconds(IDateTime dt, IInteger n) public IValue createDurationInternal(IDateTime dStart, IDateTime dEnd) { // dStart and dEnd both have to be dates, times, or datetimes - Temporal startTemp = dateTimeToJava(dStart); - Temporal endTemp = dateTimeToJava(dEnd); + Temporal startTemp = DateTimeConversions.dateTimeToJava(dStart); + Temporal endTemp = DateTimeConversions.dateTimeToJava(dEnd); if (startTemp instanceof LocalDate) { if (endTemp instanceof LocalDate) { @@ -540,7 +464,7 @@ public IValue parseDateTimeInLocale(IString inputDateTime, IString formatString, private static IValue parseDateTime(IValueFactory values, IString input, DateTimeFormatter format) { try { - return temporalToIValue(values, format.parse(input.getValue())); + return DateTimeConversions.temporalToIValue(values, format.parse(input.getValue())); } catch (IllegalArgumentException | DateTimeParseException e) { throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input: " + input.getValue() + " with formatter: " + format + " error: " + e.getMessage()); } @@ -604,7 +528,7 @@ public IValue printDateTime(IDateTime inputDateTime, IString formatString) private IString printDateTime(IDateTime input, DateTimeFormatter fmt) { try { - return values.string(fmt.format(dateTimeToJava(input))); + return values.string(fmt.format(DateTimeConversions.dateTimeToJava(input))); } catch (RuntimeException iae) { throw RuntimeExceptionFactory.dateTimePrintingError("Cannot print datetime " + input + " using formatter: " + fmt); } @@ -645,8 +569,8 @@ public IValue daysDiff(IDateTime dtStart, IDateTime dtEnd) //@doc{Increment the years by a given amount.} { if (!(dtStart.isTime() || dtEnd.isTime())) { - Temporal start = dateTimeToJava(dtStart); - Temporal end = dateTimeToJava(dtEnd); + Temporal start = DateTimeConversions.dateTimeToJava(dtStart); + Temporal end = DateTimeConversions.dateTimeToJava(dtEnd); return values.integer(ChronoUnit.DAYS.between(start, end)); } throw RuntimeExceptionFactory.invalidUseOfTimeException("Both inputs must include dates."); diff --git a/src/org/rascalmpl/library/lang/json/io/JsonValueReader.java b/src/org/rascalmpl/library/lang/json/io/JsonValueReader.java index 963fd5a5e2d..edfa606123d 100644 --- a/src/org/rascalmpl/library/lang/json/io/JsonValueReader.java +++ b/src/org/rascalmpl/library/lang/json/io/JsonValueReader.java @@ -29,8 +29,9 @@ import java.util.Map; import java.util.Set; -import org.rascalmpl.library.Prelude; import org.rascalmpl.uri.URIUtil; +import org.rascalmpl.util.DateTimeConversions; + import io.usethesource.vallang.IInteger; import io.usethesource.vallang.IListWriter; import io.usethesource.vallang.IMapWriter; @@ -604,7 +605,7 @@ public IValue visitDateTime(Type type) throws IOException { switch (in.peek()) { case STRING: OffsetDateTime result = OffsetDateTime.parse(in.nextString(), format); - return Prelude.temporalToDateTime(vf, result); + return DateTimeConversions.temporalToDateTime(vf, result); case NUMBER: return vf.datetime(in.nextLong()); default: diff --git a/src/org/rascalmpl/library/lang/json/io/JsonValueWriter.java b/src/org/rascalmpl/library/lang/json/io/JsonValueWriter.java index 63e88abd01d..e8bab7bffeb 100644 --- a/src/org/rascalmpl/library/lang/json/io/JsonValueWriter.java +++ b/src/org/rascalmpl/library/lang/json/io/JsonValueWriter.java @@ -47,7 +47,7 @@ import com.google.gson.stream.JsonWriter; -import org.rascalmpl.library.Prelude; +import org.rascalmpl.util.DateTimeConversions; /** * This class streams am IValue stream directly to an JSon stream. Useful to communicate IValues to browsers. @@ -362,7 +362,7 @@ public Void visitDateTime(IDateTime o) throws IOException { return null; } else { - out.value(format.format(Prelude.dateTimeToJava(o))); + out.value(format.format(DateTimeConversions.dateTimeToJava(o))); return null; } } diff --git a/src/org/rascalmpl/util/DateTimeConversions.java b/src/org/rascalmpl/util/DateTimeConversions.java new file mode 100644 index 00000000000..df8a1558445 --- /dev/null +++ b/src/org/rascalmpl/util/DateTimeConversions.java @@ -0,0 +1,89 @@ +package org.rascalmpl.util; + +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.temporal.ChronoField; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.util.concurrent.TimeUnit; + + +import io.usethesource.vallang.IDateTime; +import io.usethesource.vallang.IValueFactory; + +public class DateTimeConversions { + + public static Temporal dateTimeToJava(IDateTime dt) { + LocalDate datePart = null; + if (!dt.isTime()) { + datePart = LocalDate.of(dt.getYear(), dt.getMonthOfYear(), dt.getDayOfMonth()); + } + + OffsetTime timePart = null; + if (!dt.isDate()) { + // vallang always has timezone offset in case it's not a date + timePart = OffsetTime.of(dt.getHourOfDay(), dt.getMinuteOfHour(), dt.getSecondOfMinute(), + (int) TimeUnit.MILLISECONDS.toNanos(dt.getMillisecondsOfSecond()), + ZoneOffset.ofHoursMinutes(dt.getTimezoneOffsetHours(), dt.getTimezoneOffsetMinutes())); + } + if (datePart == null && timePart != null) { + return timePart; + } + if (timePart == null && datePart != null) { + return datePart; + } + assert timePart != null && datePart != null; + return datePart.atTime(timePart); + } + + public static IDateTime temporalToIValue(IValueFactory values, TemporalAccessor t) { + boolean hasDate = t.isSupported(ChronoField.YEAR); + boolean hasTime = t.isSupported(ChronoField.HOUR_OF_DAY); + boolean hasZoneOffset = t.isSupported(ChronoField.OFFSET_SECONDS); + if (hasDate) { + if (hasTime) { + if (hasZoneOffset) { + return temporalToDateTime(values, OffsetDateTime.from(t)); + } + return temporalToDateTime(values, LocalDateTime.from(t)); + } + return temporalToDate(values, LocalDate.from(t)); + } + if (hasZoneOffset) { + return temporalToTime(values, OffsetTime.from(t)); + } + return temporalToTime(values, LocalTime.from(t)); + } + + public static IDateTime temporalToDate(IValueFactory values, LocalDate t) { + return values.date(t.getYear(), t.getMonthValue(), t.getDayOfMonth()); + } + + public static IDateTime temporalToTime(IValueFactory values, LocalTime t) { + return values.time(t.getHour(), t.getMinute(), t.getSecond(), t.get(ChronoField.MILLI_OF_SECOND)); + } + + public static IDateTime temporalToDateTime(IValueFactory values, LocalDateTime t) { + return values.datetime(t.getYear(), t.getMonthValue(), t.getDayOfMonth(), t.getHour(), t.getMinute(), + t.getSecond(), t.get(ChronoField.MILLI_OF_SECOND) + ); + } + + public static IDateTime temporalToDateTime(IValueFactory values, OffsetDateTime t) { + return values.datetime(t.getYear(), t.getMonthValue(), t.getDayOfMonth(), t.getHour(), t.getMinute(), + t.getSecond(), t.get(ChronoField.MILLI_OF_SECOND), + (int) TimeUnit.HOURS.convert(t.getOffset().getTotalSeconds(), TimeUnit.SECONDS), + (int) (TimeUnit.MINUTES.convert(t.getOffset().getTotalSeconds(), TimeUnit.SECONDS) % 60)); + } + + public static IDateTime temporalToTime(IValueFactory values, OffsetTime t) { + return values.time(t.getHour(), t.getMinute(), t.getSecond(), t.get(ChronoField.MILLI_OF_SECOND), + (int) TimeUnit.HOURS.convert(t.getOffset().getTotalSeconds(), TimeUnit.SECONDS), + (int) (TimeUnit.MINUTES.convert(t.getOffset().getTotalSeconds(), TimeUnit.SECONDS) % 60)); + } + +} From 68562706bcfcefcef728cc1bde2ba451d883fe91 Mon Sep 17 00:00:00 2001 From: Davy Landman Date: Tue, 15 Jun 2021 15:02:16 +0200 Subject: [PATCH 09/10] Fixed bugs in parsing time values --- .../rascalmpl/semantics/dynamic/JustTime.java | 54 ++++++++++++++++--- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/src/org/rascalmpl/semantics/dynamic/JustTime.java b/src/org/rascalmpl/semantics/dynamic/JustTime.java index 9f696ff0cbd..26a05d76276 100644 --- a/src/org/rascalmpl/semantics/dynamic/JustTime.java +++ b/src/org/rascalmpl/semantics/dynamic/JustTime.java @@ -15,12 +15,16 @@ import java.io.IOException; import java.io.StringReader; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.rascalmpl.exceptions.ImplementationError; import org.rascalmpl.interpreter.IEvaluator; import org.rascalmpl.interpreter.result.Result; import org.rascalmpl.interpreter.staticErrors.DateTimeSyntax; + import io.usethesource.vallang.IConstructor; +import io.usethesource.vallang.IDateTime; import io.usethesource.vallang.ISourceLocation; import io.usethesource.vallang.IValue; import io.usethesource.vallang.exceptions.FactParseError; @@ -39,22 +43,56 @@ public Lexical(ISourceLocation __param1, IConstructor tree, String __param2) { public Result interpret(IEvaluator> __eval) { // Time is of the form $T