blob: ad8f7360b3dd3b9d2c02590c34189db5aaa4a936 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.location.cts.pseudorange;
import android.util.Pair;
import com.google.common.base.Preconditions;
import com.google.common.primitives.Longs;
import java.util.Calendar;
import java.util.concurrent.TimeUnit;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.Instant;
import java.util.GregorianCalendar;
/**
* A simple class to represent time unit used by GPS.
*/
public class GpsTime implements Comparable<GpsTime> {
public static final int MILLIS_IN_SECOND = 1000;
public static final int SECONDS_IN_MINUTE = 60;
public static final int MINUTES_IN_HOUR = 60;
public static final int HOURS_IN_DAY = 24;
public static final int SECONDS_IN_DAY =
HOURS_IN_DAY * MINUTES_IN_HOUR * SECONDS_IN_MINUTE;
public static final int DAYS_IN_WEEK = 7;
public static final long MILLIS_IN_DAY = TimeUnit.DAYS.toMillis(1);
public static final long MILLIS_IN_WEEK = TimeUnit.DAYS.toMillis(7);
public static final long NANOS_IN_WEEK = TimeUnit.DAYS.toNanos(7);
// GPS epoch is 1980/01/06
public static final long GPS_DAYS_SINCE_JAVA_EPOCH = 3657;
public static final long GPS_UTC_EPOCH_OFFSET_SECONDS =
TimeUnit.DAYS.toSeconds(GPS_DAYS_SINCE_JAVA_EPOCH);
public static final long GPS_UTC_EPOCH_OFFSET_NANOS =
TimeUnit.SECONDS.toNanos(GPS_UTC_EPOCH_OFFSET_SECONDS);
private static final ZonedDateTime LEAP_SECOND_DATE_1981 = getZonedDateTimeUTC(1981, 7, 1);
private static final ZonedDateTime LEAP_SECOND_DATE_2012 = getZonedDateTimeUTC(2012, 7, 1);
private static final ZonedDateTime LEAP_SECOND_DATE_2015 = getZonedDateTimeUTC(2015, 7, 1);
private static final ZonedDateTime LEAP_SECOND_DATE_2017 = getZonedDateTimeUTC(2017, 7, 1);
private static final long nanoSecPerSec = TimeUnit.SECONDS.toNanos(7);
// nanoseconds since GPS epoch (1980/1/6).
private long gpsNanos;
private static ZonedDateTime getZonedDateTimeUTC(int year, int month, int day) {
return getZonedDateTimeUTC(year, month, day, 0, 0, 0, 0);
}
private static ZonedDateTime getZonedDateTimeUTC(int year, int month, int day,
int hour, int minute, int sec, int nanoSec){
ZoneId zone = ZoneId.of("UTC");
ZonedDateTime zdt = ZonedDateTime.of(year, month, day, hour, minute, sec, nanoSec, zone);
return zdt;
}
private static long getMillisFromZonedDateTime(ZonedDateTime zdt) {
return zdt.toInstant().toEpochMilli();
}
/**
* Constructor for GpsTime. Input values are all in GPS time.
* @param year Year
* @param month Month from 1 to 12
* @param day Day from 1 to 31
* @param hour Hour from 0 to 23
* @param minute Minute from 0 to 59
* @param second Second from 0 to 59
*/
public GpsTime(int year, int month, int day, int hour, int minute, double second) {
ZonedDateTime utcDateTime = getZonedDateTimeUTC(year, month, day, hour, minute,
(int) second, (int) ((second * nanoSecPerSec) % nanoSecPerSec));
// Since input time is already specify in GPS time, no need to count leap second here.
initGpsNanos(utcDateTime);
}
/**
* Constructor
* @param zDateTime is created using GPS time values.
*/
public GpsTime(ZonedDateTime zDateTime) {
initGpsNanos(zDateTime);
}
public void initGpsNanos(ZonedDateTime zDateTime){
this.gpsNanos = TimeUnit.MILLISECONDS.toNanos(getMillisFromZonedDateTime(zDateTime))
- GPS_UTC_EPOCH_OFFSET_NANOS;
}
/**
* Constructor
* @param gpsNanos nanoseconds since GPS epoch.
*/
public GpsTime(long gpsNanos) {
this.gpsNanos = gpsNanos;
}
/**
* Creates a GPS time using a UTC based date and time.
* @param zDateTime represents the current time in UTC time, must be after 2009
*/
public static GpsTime fromUtc(ZonedDateTime zDateTime) {
return new GpsTime(TimeUnit.MILLISECONDS.toNanos(getMillisFromZonedDateTime(zDateTime))
+ TimeUnit.SECONDS.toNanos(
GpsTime.getLeapSecond(zDateTime) - GPS_UTC_EPOCH_OFFSET_SECONDS));
}
/**
* Creates a GPS time based upon the current time.
*/
public static GpsTime now() {
ZoneId zone = ZoneId.of("UTC");
ZonedDateTime current = ZonedDateTime.now(zone);
return fromUtc(current);
}
/**
* Creates a GPS time using absolute GPS week number, and the time of week.
* @param gpsWeek
* @param towSec GPS time of week in second
* @return actual time in GpsTime.
*/
public static GpsTime fromWeekTow(int gpsWeek, int towSec) {
long nanos = gpsWeek * NANOS_IN_WEEK + TimeUnit.SECONDS.toNanos(towSec);
return new GpsTime(nanos);
}
/**
* Creates a GPS time using YUMA GPS week number (0..1023), and the time of week.
* @param yumaWeek (0..1023)
* @param towSec GPS time of week in second
* @return actual time in GpsTime.
*/
public static GpsTime fromYumaWeekTow(int yumaWeek, int towSec) {
Preconditions.checkArgument(yumaWeek >= 0);
Preconditions.checkArgument(yumaWeek < 1024);
// Estimate the multiplier of current week.
ZoneId zone = ZoneId.of("UTC");
ZonedDateTime current = ZonedDateTime.now(zone);
GpsTime refTime = new GpsTime(current);
Pair<Integer, Integer> refWeekSec = refTime.getGpsWeekSecond();
int weekMultiplier = refWeekSec.first / 1024;
int gpsWeek = weekMultiplier * 1024 + yumaWeek;
return fromWeekTow(gpsWeek, towSec);
}
public static GpsTime fromTimeSinceGpsEpoch(long gpsSec) {
return new GpsTime(TimeUnit.SECONDS.toNanos(gpsSec));
}
/**
* Computes leap seconds. Only accurate after 2009.
* @param time
* @return number of leap seconds since GPS epoch.
*/
public static int getLeapSecond(ZonedDateTime time) {
if (LEAP_SECOND_DATE_2017.compareTo(time) <= 0) {
return 18;
} else if (LEAP_SECOND_DATE_2015.compareTo(time) <= 0) {
return 17;
} else if (LEAP_SECOND_DATE_2012.compareTo(time) <= 0) {
return 16;
} else if (LEAP_SECOND_DATE_1981.compareTo(time) <= 0) {
// Only correct between 2012/7/1 to 2008/12/31
return 15;
} else {
return 0;
}
}
/**
* Computes GPS weekly epoch of the reference time.
* <p>GPS weekly epoch are defined as of every Sunday 00:00:000 (mor
* @param refTime reference time
* @return nanoseconds since GPS epoch, for the week epoch.
*/
public static Long getGpsWeekEpochNano(GpsTime refTime) {
Pair<Integer, Integer> weekSecond = refTime.getGpsWeekSecond();
return weekSecond.first * NANOS_IN_WEEK;
}
/**
* @return week count since GPS epoch, and second count since the beginning of
* that week.
*/
public Pair<Integer, Integer> getGpsWeekSecond() {
// JAVA/UNIX epoch: January 1, 1970 in msec
// GPS epoch: January 6, 1980 in second
int week = (int) (gpsNanos / NANOS_IN_WEEK);
int second = (int) TimeUnit.NANOSECONDS.toSeconds(gpsNanos % NANOS_IN_WEEK);
return Pair.create(week, second);
}
/**
* @return week count since GPS epoch, and second count in 0.08 sec
* resolution, 23-bit presentation (required by RRLP.)"
*/
public Pair<Integer, Integer> getGpsWeekTow23b() {
// UNIX epoch: January 1, 1970 in msec
// GPS epoch: January 6, 1980 in second
int week = (int) (gpsNanos / NANOS_IN_WEEK);
// 80 millis is 0.08 second.
int tow23b = (int) TimeUnit.NANOSECONDS.toMillis(gpsNanos % NANOS_IN_WEEK) / 80;
return Pair.create(week, tow23b);
}
/**
* @return Day of year in GPS time (GMT time)
*/
public static int getCurrentDayOfYear() {
ZoneId zone = ZoneId.of("UTC");
ZonedDateTime current = ZonedDateTime.now(zone);
// Since current is derived from UTC time, we need to add leap second here.
long gpsTimeMillis = getMillisFromZonedDateTime(current)
+ TimeUnit.SECONDS.toMillis(getLeapSecond(current));
ZonedDateTime gpsCurrent = ZonedDateTime.ofInstant(Instant.ofEpochMilli(gpsTimeMillis), ZoneId.of("UTC"));
return gpsCurrent.getDayOfYear();
}
/**
* @return milliseconds since JAVA/UNIX epoch.
*/
public final long getMillisSinceJavaEpoch() {
return TimeUnit.NANOSECONDS.toMillis(gpsNanos + GPS_UTC_EPOCH_OFFSET_NANOS);
}
/**
* @return milliseconds since GPS epoch.
*/
public final long getMillisSinceGpsEpoch() {
return TimeUnit.NANOSECONDS.toMillis(gpsNanos);
}
/**
* @return microseconds since GPS epoch.
*/
public final long getMicrosSinceGpsEpoch() {
return TimeUnit.NANOSECONDS.toMicros(gpsNanos);
}
/**
* @return nanoseconds since GPS epoch.
*/
public final long getNanosSinceGpsEpoch() {
return gpsNanos;
}
/**
* @return the GPS time in Calendar.
*/
public Calendar getTimeInCalendar() {
return GregorianCalendar.from(getGpsDateTime());
}
/**
* @return a ZonedDateTime with leap seconds considered.
*/
public ZonedDateTime getUtcDateTime() {
ZonedDateTime gpsDateTime = getGpsDateTime();
long gpsMillis = getMillisFromZonedDateTime(gpsDateTime)
- TimeUnit.SECONDS.toMillis(getLeapSecond(gpsDateTime));
return ZonedDateTime.ofInstant(Instant.ofEpochMilli(gpsMillis), ZoneId.of("UTC"));
}
/**
* @return a ZonedDateTime based on the pure GPS time (without considering leap second).
*/
public ZonedDateTime getGpsDateTime() {
long gpsMillis = TimeUnit.NANOSECONDS.toMillis(gpsNanos + GPS_UTC_EPOCH_OFFSET_NANOS);
return ZonedDateTime.ofInstant(Instant.ofEpochMilli(gpsMillis), ZoneId.of("UTC"));
}
/**
* Compares two {@code GpsTime} objects temporally.
*
* @param other the {@code GpsTime} to be compared.
* @return the value {@code 0} if this {@code GpsTime} is simultaneous with
* the argument {@code GpsTime}; a value less than {@code 0} if this
* {@code GpsTime} occurs before the argument {@code GpsTime}; and
* a value greater than {@code 0} if this {@code GpsTime} occurs
* after the argument {@code GpsTime} (signed comparison).
*/
@Override
public int compareTo(GpsTime other) {
return Long.compare(this.getNanosSinceGpsEpoch(), other.getNanosSinceGpsEpoch());
}
@Override
public boolean equals(Object other) {
if (!(other instanceof GpsTime)) {
return false;
}
GpsTime time = (GpsTime) other;
return getNanosSinceGpsEpoch() == time.getNanosSinceGpsEpoch();
}
@Override
public int hashCode() {
return Longs.hashCode(getNanosSinceGpsEpoch());
}
}