Refactored Race, MockRace, and VisualiserRace to use RaceClock instead of keeping their own timers.
Moved FPS tracking to Race class, so both VisualiserRace and MockRace can monitor their FPS. LatestMessages is now observable. It notifies observers when an XMLMessage is received. Boat now has StringProperty for name and country/abbreviation. Moved the MockRace timescale value to Constants.RaceTimeScale. This is passed in to MockRace on construction. Tidied up StartController. Copied the visualiser's resources into the resources folder. Refactored RaceClock. Added comments. Tidied code a bit. Moved to shared.model. Started work on RaceController.
This commit is contained in:
@@ -3,7 +3,7 @@ package shared.exceptions;
|
||||
/**
|
||||
* An exception thrown when we cannot generate Boats.xml and send an XML message, or we cannot parse a Boats.xml file.
|
||||
*/
|
||||
public class InvalidBoatDataException extends RuntimeException {
|
||||
public class InvalidBoatDataException extends Exception {
|
||||
|
||||
public InvalidBoatDataException(String message) {
|
||||
super(message);
|
||||
|
||||
@@ -3,7 +3,7 @@ package shared.exceptions;
|
||||
/**
|
||||
* Exception thrown when we cannot generate Race.xml data, and send an XML message, or we cannot parse a Race.xml file.
|
||||
*/
|
||||
public class InvalidRaceDataException extends RuntimeException {
|
||||
public class InvalidRaceDataException extends Exception {
|
||||
|
||||
public InvalidRaceDataException(String message) {
|
||||
super(message);
|
||||
|
||||
@@ -3,7 +3,7 @@ package shared.exceptions;
|
||||
/**
|
||||
* An exception thrown when we cannot generate Regatta.xml and send an XML message, or we cannot parse a Regatta.xml file.
|
||||
*/
|
||||
public class InvalidRegattaDataException extends RuntimeException {
|
||||
public class InvalidRegattaDataException extends Exception {
|
||||
|
||||
public InvalidRegattaDataException(String message) {
|
||||
super(message);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package shared.model;
|
||||
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import network.Messages.Enums.BoatStatusEnum;
|
||||
|
||||
@@ -11,7 +12,7 @@ public class Boat {
|
||||
/**
|
||||
* The name of the boat/team.
|
||||
*/
|
||||
private String name;
|
||||
private StringProperty name = new SimpleStringProperty();
|
||||
|
||||
/**
|
||||
* The current speed of the boat, in knots.
|
||||
@@ -32,7 +33,7 @@ public class Boat {
|
||||
/**
|
||||
* The country or team abbreviation of the boat.
|
||||
*/
|
||||
private String country;
|
||||
private StringProperty country = new SimpleStringProperty();
|
||||
|
||||
/**
|
||||
* The source ID of the boat.
|
||||
@@ -89,9 +90,10 @@ public class Boat {
|
||||
* @param country The abbreviation or country code for the boat.
|
||||
*/
|
||||
public Boat(int sourceID, String name, String country) {
|
||||
this.country = country;
|
||||
this.name = name;
|
||||
|
||||
this.sourceID = sourceID;
|
||||
this.setName(name);
|
||||
this.setCountry(country);
|
||||
|
||||
this.bearing = Bearing.fromDegrees(0d);
|
||||
|
||||
@@ -106,7 +108,7 @@ public class Boat {
|
||||
* @return Name of the boat/team.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
return name.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,9 +116,16 @@ public class Boat {
|
||||
* @param name Name of the boat/team.
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
this.name.setValue(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name property of the boat.
|
||||
* @return The name of the boat, in a StringProperty.
|
||||
*/
|
||||
public StringProperty nameProperty() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current speed of the boat, in knots.
|
||||
@@ -140,7 +149,7 @@ public class Boat {
|
||||
* @return The country/team abbreviation of the boat.
|
||||
*/
|
||||
public String getCountry() {
|
||||
return country;
|
||||
return country.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -148,9 +157,16 @@ public class Boat {
|
||||
* @param country The new country/team abbreviation for the boat.
|
||||
*/
|
||||
public void setCountry(String country) {
|
||||
this.country = country;
|
||||
this.country.setValue(country);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the country/abbreviation property of the boat.
|
||||
* @return The country/abbreviation of the boat, in a StringProperty.
|
||||
*/
|
||||
public StringProperty countryProperty() {
|
||||
return country;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the source ID of the boat.
|
||||
|
||||
@@ -27,6 +27,14 @@ public class Constants {
|
||||
public static final double KnotsToMMPerSecond = 514.444;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The scale factor of the race.
|
||||
* Frame periods are multiplied by this to get the amount of time a single frame represents.
|
||||
* E.g., frame period = 20ms, scale = 5, frame represents 20 * 5 = 100ms, and so boats are simulated for 100ms, even though only 20ms actually occurred.
|
||||
*/
|
||||
public static final int RaceTimeScale = 1;
|
||||
|
||||
/**
|
||||
* The race pre-start time, in milliseconds. 3 minutes.
|
||||
*/
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package shared.model;
|
||||
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.collections.FXCollections;
|
||||
import mock.model.VMG;
|
||||
import network.Messages.Enums.RaceStatusEnum;
|
||||
import network.Messages.Enums.RaceTypeEnum;
|
||||
import network.Messages.LatestMessages;
|
||||
@@ -10,12 +7,7 @@ import shared.dataInput.BoatDataSource;
|
||||
import shared.dataInput.RaceDataSource;
|
||||
import shared.dataInput.RegattaDataSource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import static java.lang.Math.cos;
|
||||
|
||||
|
||||
/**
|
||||
@@ -75,15 +67,11 @@ public abstract class Race implements Runnable {
|
||||
protected List<GPSCoordinate> boundary;
|
||||
|
||||
|
||||
/**
|
||||
* The elapsed time, in milliseconds, of the race.
|
||||
*/
|
||||
protected long totalTimeElapsed;
|
||||
|
||||
/**
|
||||
* The starting timestamp, in milliseconds, of the race.
|
||||
* The clock which tracks the race's start time, current time, and elapsed duration.
|
||||
*/
|
||||
protected long startTime;
|
||||
protected RaceClock raceClock;
|
||||
|
||||
|
||||
/**
|
||||
@@ -91,6 +79,11 @@ public abstract class Race implements Runnable {
|
||||
*/
|
||||
protected int raceId;
|
||||
|
||||
/**
|
||||
* The name of the regatta.
|
||||
*/
|
||||
protected String regattaName;
|
||||
|
||||
/**
|
||||
* The current status of the race.
|
||||
*/
|
||||
@@ -114,6 +107,23 @@ public abstract class Race implements Runnable {
|
||||
protected double windSpeed;
|
||||
|
||||
|
||||
/**
|
||||
* The number of frames per second.
|
||||
* We essentially track the number of frames generated per second, over a one second period. When {@link #lastFpsResetTime} reaches 1 second, {@link #currentFps} is reset.
|
||||
*/
|
||||
private int currentFps = 0;
|
||||
|
||||
/**
|
||||
* The number of frames per second we generated over the last 1 second period.
|
||||
*/
|
||||
private int lastFps = 0;
|
||||
|
||||
/**
|
||||
* The time, in milliseconds, since we last reset our {@link #currentFps} counter.
|
||||
*/
|
||||
private long lastFpsResetTime;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a race object with a given BoatDataSource, RaceDataSource, and RegattaDataSource.
|
||||
@@ -132,28 +142,37 @@ public abstract class Race implements Runnable {
|
||||
this.latestMessages = latestMessages;
|
||||
|
||||
|
||||
//Marks.
|
||||
this.compoundMarks = raceDataSource.getCompoundMarks();
|
||||
|
||||
//Boundaries.
|
||||
this.boundary = raceDataSource.getBoundary();
|
||||
|
||||
|
||||
//Legs.
|
||||
this.useLegsList(raceDataSource.getLegs());
|
||||
|
||||
|
||||
//Race ID.
|
||||
this.raceId = raceDataSource.getRaceId();
|
||||
|
||||
//Regatta name.
|
||||
this.regattaName = regattaDataSource.getRegattaName();
|
||||
|
||||
this.startTime = raceDataSource.getStartDateTime().toInstant().toEpochMilli();
|
||||
|
||||
//Race clock.
|
||||
this.raceClock = new RaceClock(this.raceDataSource.getStartDateTime());
|
||||
//this.raceClock.run();//TODO looks like we may not actually need this.
|
||||
|
||||
//Race status.
|
||||
this.setRaceStatusEnum(RaceStatusEnum.NOT_ACTIVE);
|
||||
//Race type.
|
||||
this.raceType = raceDataSource.getRaceType();
|
||||
|
||||
//Wind speed.
|
||||
this.windSpeed = 0;
|
||||
//Wind direction.
|
||||
this.windDirection = Bearing.fromDegrees(0);
|
||||
|
||||
this.totalTimeElapsed = 0;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -226,6 +245,67 @@ public abstract class Race implements Runnable {
|
||||
return raceType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the regatta.
|
||||
* @return The name of the regatta.
|
||||
*/
|
||||
public String getRegattaName() {
|
||||
return regattaName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the wind bearing.
|
||||
* @return The wind bearing.
|
||||
*/
|
||||
public Bearing getWindDirection() {
|
||||
return windDirection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the wind speed.
|
||||
* Measured in knots.
|
||||
* @return The wind speed.
|
||||
*/
|
||||
public double getWindSpeed() {
|
||||
return windSpeed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the RaceClock for this race.
|
||||
* This is used to track the start time, current time, and elapsed duration of the race.
|
||||
* @return The RaceClock for the race.
|
||||
*/
|
||||
public RaceClock getRaceClock() {
|
||||
return raceClock;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of frames generated per second.
|
||||
* @return Frames per second.
|
||||
*/
|
||||
public int getFps() {
|
||||
return lastFps;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Increments the FPS counter, and adds timePeriod milliseconds to our FPS reset timer.
|
||||
* @param timePeriod Time, in milliseconds, to add to {@link #lastFpsResetTime}.
|
||||
*/
|
||||
protected void incrementFps(long timePeriod) {
|
||||
//Increment.
|
||||
this.currentFps++;
|
||||
|
||||
//Add period to timer.
|
||||
this.lastFpsResetTime += timePeriod;
|
||||
|
||||
//If we have reached 1 second period, snapshot the framerate and reset.
|
||||
if (this.lastFpsResetTime > 1000) {
|
||||
this.lastFps = this.currentFps;
|
||||
|
||||
this.currentFps = 0;
|
||||
this.lastFpsResetTime = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,356 @@
|
||||
package shared.model;
|
||||
|
||||
import com.github.bfsmith.geotimezone.TimeZoneLookup;
|
||||
import com.github.bfsmith.geotimezone.TimeZoneResult;
|
||||
import com.sun.istack.internal.Nullable;
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import shared.model.GPSCoordinate;
|
||||
import visualiser.model.ResizableRaceCanvas;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* This class is used to implement a clock which keeps track of and
|
||||
* displays times relevant to a race. This is displayed on the
|
||||
* {@link ResizableRaceCanvas} via the
|
||||
* {@link visualiser.Controllers.RaceController} and the
|
||||
* {@link visualiser.Controllers.StartController}.
|
||||
*/
|
||||
public class RaceClock implements Runnable {
|
||||
|
||||
/**
|
||||
* The time that we last updated the current race time at.
|
||||
*/
|
||||
private long lastTime;
|
||||
|
||||
/**
|
||||
* The time zone of the race.
|
||||
*/
|
||||
private final ZoneId zoneId;
|
||||
|
||||
/**
|
||||
* The start time of the race.
|
||||
*/
|
||||
private ZonedDateTime startingTime;
|
||||
|
||||
/**
|
||||
* The current time of the race.
|
||||
*/
|
||||
private final StringProperty startingTimeProperty = new SimpleStringProperty();
|
||||
|
||||
|
||||
/**
|
||||
* The current time of the race.
|
||||
*/
|
||||
@Nullable
|
||||
private ZonedDateTime currentTime;
|
||||
|
||||
/**
|
||||
* The current time of the race.
|
||||
*/
|
||||
private final StringProperty currentTimeProperty = new SimpleStringProperty();
|
||||
|
||||
/**
|
||||
* The time until the race starts, or elapsed time in the race after it has started.
|
||||
*/
|
||||
private StringProperty durationProperty = new SimpleStringProperty();
|
||||
|
||||
|
||||
//Format strings.
|
||||
/**
|
||||
* Format string used for starting time.
|
||||
*/
|
||||
private String startingTimeFormat = "'Starting time:' HH:mm dd/MM/YYYY";
|
||||
|
||||
/**
|
||||
* Format string used for current time.
|
||||
*/
|
||||
private String currentTimeFormat = "'Starting time:' HH:mm dd/MM/YYYY";
|
||||
|
||||
/**
|
||||
* Format string used for duration before it has started.
|
||||
*/
|
||||
private String durationBeforeStartFormat = "Starting in: %02d:%02d:%02d";
|
||||
/**
|
||||
* Format string used for duration once the race has started.
|
||||
*/
|
||||
private String durationAfterStartFormat = "Time: %02d:%02d:%02d";
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a RaceClock using a specified starting ZonedDateTime.
|
||||
* @param startingTime The ZonedDateTime that the race starts at.
|
||||
*/
|
||||
public RaceClock(ZonedDateTime startingTime) {
|
||||
this.zoneId = startingTime.getZone();
|
||||
|
||||
//Set start time.
|
||||
setStartingTime(startingTime);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the ZonedDateTime corresponding to a specified GPSCoordinate.
|
||||
* @param gpsCoordinate The GPSCoordinate to lookup.
|
||||
* @return The ZonedDateTime for the coordinate.
|
||||
*/
|
||||
public static ZonedDateTime getCurrentZonedDateTime(GPSCoordinate gpsCoordinate) {
|
||||
TimeZoneLookup timeZoneLookup = new TimeZoneLookup();
|
||||
TimeZoneResult timeZoneResult = timeZoneLookup.getTimeZone(gpsCoordinate.getLatitude(), gpsCoordinate.getLongitude());
|
||||
ZoneId zone = ZoneId.of(timeZoneResult.getResult());
|
||||
return LocalDateTime.now(zone).atZone(zone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the race clock.
|
||||
*/
|
||||
public void run() {
|
||||
new AnimationTimer() {
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
updateTime();
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sets time to given UTC time in seconds from Unix epoch, preserving timezone.
|
||||
* @param time UTC time.
|
||||
*/
|
||||
public void setUTCTime(long time) {
|
||||
Date utcTime = new Date(time);
|
||||
setCurrentTime(utcTime.toInstant().atZone(this.zoneId));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get ZonedDateTime corresponding to local time zone and given UTC time.
|
||||
* @param time time in mills
|
||||
* @return local date time
|
||||
*/
|
||||
public ZonedDateTime getLocalTime(long time) {
|
||||
Date utcTime = new Date(time);
|
||||
return utcTime.toInstant().atZone(this.zoneId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates time by duration elapsed since last update.
|
||||
*/
|
||||
private void updateTime() {
|
||||
|
||||
//Get duration elapsed since last update.
|
||||
Duration duration = Duration.of(System.currentTimeMillis() - this.lastTime, ChronoUnit.MILLIS);
|
||||
|
||||
//Add this duration to the current time.
|
||||
ZonedDateTime newCurrentTime = this.currentTime.plus(duration);
|
||||
setCurrentTime(newCurrentTime);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the starting time of the race.
|
||||
* @return The starting time of the race.
|
||||
*/
|
||||
public ZonedDateTime getStartingTime() {
|
||||
return startingTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the race start time, expressed as the number of milliseconds since the unix epoch.
|
||||
* @return Start time expressed as milliseconds since unix epoch.
|
||||
*/
|
||||
public long getStartingTimeMilli() {
|
||||
return startingTime.toInstant().toEpochMilli();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the starting time of the race.
|
||||
* @param startingTime The starting time of the race.
|
||||
*/
|
||||
public void setStartingTime(ZonedDateTime startingTime) {
|
||||
this.startingTime = startingTime;
|
||||
|
||||
//Convert time into string.
|
||||
String startingTimeString = DateTimeFormatter.ofPattern(this.startingTimeFormat).format(startingTime);
|
||||
|
||||
//Use it.
|
||||
setStartingTimeString(startingTimeString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the starting time of the race, as a string.
|
||||
* @return The starting time of the race, as a string.
|
||||
*/
|
||||
public String getStartingTimeString() {
|
||||
return startingTimeProperty.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the starting time string of the race.
|
||||
* This should only be called by {@link #setStartingTime(ZonedDateTime)}.
|
||||
* @param startingTime The new value for the starting time string.
|
||||
*/
|
||||
private void setStartingTimeString(String startingTime) {
|
||||
this.startingTimeProperty.setValue(startingTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the starting time property.
|
||||
* @return The starting time property.
|
||||
*/
|
||||
public StringProperty startingTimeProperty() {
|
||||
return startingTimeProperty;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the race duration, in milliseconds.
|
||||
* A negative value means that the race has not started.
|
||||
* @return Race duration in milliseconds.
|
||||
*/
|
||||
public long getDurationMilli() {
|
||||
return getCurrentTimeMilli() - getStartingTimeMilli();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the race duration, as a string.
|
||||
* @return Duration as a string.
|
||||
*/
|
||||
public String getDurationString() {
|
||||
return durationProperty.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the duration time string of the race.
|
||||
* @param duration The new value for the duration time string.
|
||||
*/
|
||||
private void setDurationString(String duration) {
|
||||
this.durationProperty.setValue(duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the duration property.
|
||||
* @return The duration property.
|
||||
*/
|
||||
public StringProperty durationProperty() {
|
||||
return durationProperty;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the current time of the race.
|
||||
* @return The current time of the race.
|
||||
*/
|
||||
public ZonedDateTime getCurrentTime() {
|
||||
return currentTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the race current time, expressed as the number of milliseconds since the unix epoch.
|
||||
* @return Current time expressed as milliseconds since unix epoch.
|
||||
*/
|
||||
public long getCurrentTimeMilli() {
|
||||
return currentTime.toInstant().toEpochMilli();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current time of the race.
|
||||
* @param currentTime The current time of the race.
|
||||
*/
|
||||
private void setCurrentTime(ZonedDateTime currentTime) {
|
||||
this.currentTime = currentTime;
|
||||
|
||||
//Convert time into string.
|
||||
String currentTimeString = DateTimeFormatter.ofPattern(this.currentTimeFormat).format(currentTime);
|
||||
|
||||
//Use it.
|
||||
setCurrentTimeString(currentTimeString);
|
||||
|
||||
//Store the last time we updated the current time at.
|
||||
this.lastTime = System.currentTimeMillis();
|
||||
|
||||
//Update the duration string.
|
||||
updateDurationString();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the duration string based on the start time and current time.
|
||||
* This requires {@link #currentTime} to be non-null.
|
||||
*/
|
||||
private void updateDurationString() {
|
||||
//Calculates the duration in seconds.
|
||||
long seconds = Duration.between(startingTime.toLocalDateTime(), currentTime.toLocalDateTime()).getSeconds();
|
||||
|
||||
//Check if the race has already started or not. This determines the format string used.
|
||||
String formatString;
|
||||
if (seconds < 0) {
|
||||
//Race hasn't started.
|
||||
formatString = this.durationBeforeStartFormat;
|
||||
//The seconds value is negative, so we make it positive.
|
||||
seconds = seconds * -1;
|
||||
} else {
|
||||
//Race has started.
|
||||
formatString = this.durationAfterStartFormat;
|
||||
}
|
||||
|
||||
//Format the seconds value.
|
||||
//Hours : minutes : seconds.
|
||||
String formattedDuration = String.format(formatString, seconds / 3600, (seconds % 3600) / 60, seconds % 60);
|
||||
|
||||
//Use it.
|
||||
setDurationString(formattedDuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current time of the race, as a string.
|
||||
* @return The current time of the race, as a string.
|
||||
*/
|
||||
public String getCurrentTimeString() {
|
||||
return currentTimeProperty.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current time string of the race.
|
||||
* @param currentTime The new value for the current time string.
|
||||
*/
|
||||
private void setCurrentTimeString(String currentTime) {
|
||||
this.currentTimeProperty.setValue(currentTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current time property.
|
||||
* @return The current time property.
|
||||
*/
|
||||
public StringProperty currentTimeProperty() {
|
||||
return currentTimeProperty;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the time zone of the race, as a string.
|
||||
* @return The race time zone.
|
||||
*/
|
||||
public String getTimeZone() {
|
||||
return zoneId.toString();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user