diff --git a/src/main/java/ch/njol/skript/conditions/CondIsTimezoneValid.java b/src/main/java/ch/njol/skript/conditions/CondIsTimezoneValid.java new file mode 100644 index 00000000000..523d23b5a18 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsTimezoneValid.java @@ -0,0 +1,59 @@ +package ch.njol.skript.conditions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.*; +import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; + +import java.time.DateTimeException; +import java.time.ZoneId; + +@Name("Is Timezone Valid") +@Description("Checks if a timezone is valid.") +@Example(""" + set {_timezone} to "America/New_York" + if {_timezone} is a valid timezone: + set {_date} to now in timezone {_timezone} + """) +@Since("INSERT VERSION") +public class CondIsTimezoneValid extends Condition { + + static { + Skript.registerCondition(CondIsTimezoneValid.class, "%strings% (are|is [a[n]]) [negate:in]valid time[ ]zone[s]"); + } + + private Expression timezones; + private boolean isNegated; + + @Override + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + timezones = (Expression) expressions[0]; + isNegated = parseResult.hasTag("negate"); + return true; + } + + @Override + public boolean check(Event event) { + return timezones.check(event, CondIsTimezoneValid::isValidTimezone, isNegated); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return timezones.toString(event, debug) + " are " + (isNegated ? "in" : "") + "valid timezones"; + } + + private static boolean isValidTimezone(String timezone) { + try { + ZoneId.of(timezone); + } catch (DateTimeException e) { + return false; + } + + return true; + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprAllTimezones.java b/src/main/java/ch/njol/skript/expressions/ExprAllTimezones.java new file mode 100644 index 00000000000..273329cdee5 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprAllTimezones.java @@ -0,0 +1,44 @@ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleLiteral; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; + +import java.time.ZoneId; + +@Name("All Timezones") +@Description("Returns a list of all timezones that can be used in the date in timezone expression.") +@Example("set {_timezones::*} to all timezones") +@Since("INSERT VERSION") +public class ExprAllTimezones extends SimpleLiteral { + + static { + Skript.registerExpression(ExprAllTimezones.class, String.class, ExpressionType.SIMPLE, "all [of [the]] time[ ]zones"); + } + + private static String[] timezones = ZoneId.getAvailableZoneIds().toArray(new String[0]); + + public ExprAllTimezones() { + super(timezones, String.class, true); + } + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + return true; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "all timezones"; + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprDateInTimezone.java b/src/main/java/ch/njol/skript/expressions/ExprDateInTimezone.java new file mode 100644 index 00000000000..e62605a353f --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprDateInTimezone.java @@ -0,0 +1,110 @@ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.util.Date; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; + +import java.time.DateTimeException; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +@Name("Date in Timezone") +@Description({ + "Returns a date in the specified timezone. Note that the result date might not be equal to the input date.", + "Use all timezones to get a list of valid timezones." +}) +@Example(""" + set {_date} to now in timezone "Europe/Istanbul" + set {_clock} to {_date} formatted as "kk:mm" + send "It is currently %{_clock}% in Istanbul!" to player + """) +@Since("INSERT VERSION") +public class ExprDateInTimezone extends SimpleExpression { + + static { + Skript.registerExpression(ExprDateInTimezone.class, Date.class, ExpressionType.SIMPLE, + "[the] [date[s]] %dates% in time[ ]zone %string%", + "[the] [date[s]] %dates% in [the] %string% time[ ]zone"); + } + + private Expression dates; + private Expression timezone; + + @Override + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + dates = (Expression) expressions[0]; + timezone = (Expression) expressions[1]; + return true; + } + + @Override + protected Date @Nullable [] get(Event event) { + String timezone = this.timezone.getSingle(event); + + if (timezone == null) { + error("Timezone is not set."); + return new Date[0]; + } + + Date[] dates = this.dates.getArray(event); + + for (int i = 0; i < dates.length; i++) { + dates[i] = getShiftedDate(dates[i], timezone); + } + + return dates; + } + + @Override + public boolean isSingle() { + return dates.isSingle(); + } + + @Override + public Class getReturnType() { + return Date.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "dates " + dates.toString(event, debug) + " in timezone " + timezone.toString(event, debug); + } + + /** + * Shifts a date by the given timezone. Meaning if the system timezone is UTC and you give it a GMT+3 timezone, + * it will add 3 hours to the given date. + * @param date The date to shift + * @param timezone The timezone + * @return A new Date + */ + private Date getShiftedDate(Date date, String timezone) { + ZoneId targetZoneId; + try { + targetZoneId = ZoneId.of(timezone); + } catch (DateTimeException e) { // invalid zone format + error("Invalid timezone."); + return null; + } + + Instant instantDate = date.toInstant(); + ZoneId localZoneId = ZoneId.systemDefault(); + Instant shiftedNow = ZonedDateTime.ofInstant(instantDate, targetZoneId) + .toLocalDateTime() + .atZone(localZoneId) + .toInstant(); + java.util.Date javaDate = java.util.Date.from(shiftedNow); + return Date.fromJavaDate(javaDate); + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprNow.java b/src/main/java/ch/njol/skript/expressions/ExprNow.java index f0b980e7b25..384b1783224 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprNow.java +++ b/src/main/java/ch/njol/skript/expressions/ExprNow.java @@ -20,34 +20,34 @@ @Examples({"broadcast \"Current server time: %now%\""}) @Since("1.4") public class ExprNow extends SimpleExpression { - + static { Skript.registerExpression(ExprNow.class, Date.class, ExpressionType.SIMPLE, "now"); } - + @Override public boolean init(final Expression[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { return true; } - + @Override protected Date[] get(final Event e) { return new Date[] {new Date()}; } - + @Override public boolean isSingle() { return true; } - + @Override public Class getReturnType() { return Date.class; } - + @Override public String toString(final @Nullable Event e, final boolean debug) { return "now"; } - + } diff --git a/src/main/java/ch/njol/skript/test/platform/Environment.java b/src/main/java/ch/njol/skript/test/platform/Environment.java index 7588bbd2122..9960dc5d531 100644 --- a/src/main/java/ch/njol/skript/test/platform/Environment.java +++ b/src/main/java/ch/njol/skript/test/platform/Environment.java @@ -227,6 +227,7 @@ public TestResults runTests(Path runnerRoot, Path testsRoot, boolean devMode, bo args.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000"); args.add("-Duser.language=en"); args.add("-Duser.country=US"); + args.add("-Duser.timezone=UTC"); args.addAll(jvmArgs); args.addAll(Arrays.asList(commandLine)); diff --git a/src/test/skript/tests/misc/timezone.sk b/src/test/skript/tests/misc/timezone.sk new file mode 100644 index 00000000000..5fbc37dd1e1 --- /dev/null +++ b/src/test/skript/tests/misc/timezone.sk @@ -0,0 +1,17 @@ +test "timezone syntaxes": + assert size of all timezones > 0 with "timezones aren't set" + + assert "Europe/Istanbul" is a valid timezone with "single timezone should've been valid" + assert ("Europe/Istanbul" and "Asia/Tokyo") are valid timezones with "multiple timezones with 'and' should've been valid" + assert ("Europe/Istanbul" or "Asia/Tokyo") is a valid timezone with "multiple timezones with 'or' should've been valid" + + assert "hello!" is an invalid timezone with "single timezone should've been invalid" + assert ("hello!" and "hi!") are invalid timezones with "multiple timezones with 'and' should've been invalid" + assert ("hello!" or "Asia/Tokyo") is an invalid timezone with "multiple timezones with 'or' should've been invalid" + + set {_d} to date(2030, 6, 4, 7, 23) + set {_d.in.nyc} to date(2030, 6, 4, 3, 23) + set {_d.in.istanbul} to date(2030, 6, 4, 10, 23) + + assert difference between ({_d} in timezone "America/New_York") and {_d.in.nyc} < 1 second with "returned incorrect date for New York" + assert difference between ({_d} in timezone "Europe/Istanbul") and {_d.in.istanbul} < 1 second with "returned incorrect date for Istanbul"