1 /* 2 * Copyright 2017 Google LLC 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.cloud; 18 19 import static com.google.common.base.Preconditions.checkArgument; 20 21 import com.google.protobuf.util.Timestamps; 22 import java.io.Serializable; 23 import java.util.Date; 24 import java.util.Objects; 25 import java.util.concurrent.TimeUnit; 26 import org.threeten.bp.Instant; 27 import org.threeten.bp.LocalDateTime; 28 import org.threeten.bp.ZoneOffset; 29 import org.threeten.bp.format.DateTimeFormatter; 30 import org.threeten.bp.format.DateTimeFormatterBuilder; 31 import org.threeten.bp.format.DateTimeParseException; 32 import org.threeten.bp.temporal.TemporalAccessor; 33 34 /** 35 * Represents a timestamp with nanosecond precision. Timestamps cover the range [0001-01-01, 36 * 9999-12-31]. 37 * 38 * <p>{@code Timestamp} instances are immutable. 39 */ 40 public final class Timestamp implements Comparable<Timestamp>, Serializable { 41 42 private static final long serialVersionUID = 5152143600571559844L; 43 44 /** The smallest legal timestamp ("0001-01-01T00:00:00Z"). */ 45 public static final Timestamp MIN_VALUE = new Timestamp(-62135596800L, 0); 46 47 /** The largest legal timestamp ("9999-12-31T23:59:59Z"). */ 48 public static final Timestamp MAX_VALUE = 49 new Timestamp(253402300799L, (int) TimeUnit.SECONDS.toNanos(1) - 1); 50 51 private static final DateTimeFormatter format = DateTimeFormatter.ISO_LOCAL_DATE_TIME; 52 53 private static final DateTimeFormatter timestampParser = 54 new DateTimeFormatterBuilder() 55 .appendOptional(DateTimeFormatter.ISO_LOCAL_DATE_TIME) 56 .optionalStart() 57 .appendOffsetId() 58 .optionalEnd() 59 .toFormatter() 60 .withZone(ZoneOffset.UTC); 61 62 private final long seconds; 63 private final int nanos; 64 Timestamp(long seconds, int nanos)65 private Timestamp(long seconds, int nanos) { 66 this.seconds = seconds; 67 this.nanos = nanos; 68 } 69 70 /** 71 * Creates an instance representing the value of {@code seconds} and {@code nanos} since January 72 * 1, 1970, 00:00:00 UTC. 73 * 74 * @param seconds seconds since January 1, 1970, 00:00:00 UTC. A negative value is the number of 75 * seconds before January 1, 1970, 00:00:00 UTC. 76 * @param nanos the fractional seconds component, in the range 0..999999999. 77 * @throws IllegalArgumentException if the timestamp is outside the representable range 78 */ ofTimeSecondsAndNanos(long seconds, int nanos)79 public static Timestamp ofTimeSecondsAndNanos(long seconds, int nanos) { 80 checkArgument( 81 Timestamps.isValid(seconds, nanos), "timestamp out of range: %s, %s", seconds, nanos); 82 return new Timestamp(seconds, nanos); 83 } 84 85 /** 86 * Creates an instance representing the value of {@code microseconds}. 87 * 88 * @throws IllegalArgumentException if the timestamp is outside the representable range 89 */ ofTimeMicroseconds(long microseconds)90 public static Timestamp ofTimeMicroseconds(long microseconds) { 91 long seconds = microseconds / 1_000_000; 92 int nanos = (int) (microseconds % 1_000_000 * 1000); 93 if (nanos < 0) { 94 seconds--; 95 nanos += 1_000_000_000; 96 } 97 checkArgument( 98 Timestamps.isValid(seconds, nanos), "timestamp out of range: %s, %s", seconds, nanos); 99 return new Timestamp(seconds, nanos); 100 } 101 102 /** 103 * Creates an instance representing the value of {@code Date}. 104 * 105 * @throws IllegalArgumentException if the timestamp is outside the representable range 106 */ of(Date date)107 public static Timestamp of(Date date) { 108 return ofTimeMicroseconds(TimeUnit.MILLISECONDS.toMicros(date.getTime())); 109 } 110 111 /** Creates an instance with current time. */ now()112 public static Timestamp now() { 113 java.sql.Timestamp date = new java.sql.Timestamp(System.currentTimeMillis()); 114 return of(date); 115 } 116 117 /** 118 * Creates an instance representing the value of {@code timestamp}. 119 * 120 * @throws IllegalArgumentException if the timestamp is outside the representable range 121 */ of(java.sql.Timestamp timestamp)122 public static Timestamp of(java.sql.Timestamp timestamp) { 123 int nanos = timestamp.getNanos(); 124 125 // A pre-epoch timestamp can be off by one second because of the way that integer division 126 // works. For example, -1001 / 1000 == -1. In this case, we want this result to be -2. This 127 // causes any pre-epoch timestamp to be off by 1 second - fix this by subtracting 1 from the 128 // seconds value if the seconds value is less than zero and is not divisible by 1000. 129 // TODO: replace with Math.floorDiv when we drop Java 7 support 130 long seconds = timestamp.getTime() / 1000; 131 if (seconds < 0 && timestamp.getTime() % 1000 != 0) { 132 --seconds; 133 } 134 135 return Timestamp.ofTimeSecondsAndNanos(seconds, nanos); 136 } 137 138 /** 139 * Returns the number of seconds since January 1, 1970, 00:00:00 UTC. A negative value is the 140 * number of seconds before January 1, 1970, 00:00:00 UTC. 141 */ getSeconds()142 public long getSeconds() { 143 return seconds; 144 } 145 146 /** Returns the fractional seconds component, in nanoseconds. */ getNanos()147 public int getNanos() { 148 return nanos; 149 } 150 151 /** Returns a JDBC timestamp initialized to the same point in time as {@code this}. */ toSqlTimestamp()152 public java.sql.Timestamp toSqlTimestamp() { 153 java.sql.Timestamp ts = new java.sql.Timestamp(seconds * 1000); 154 ts.setNanos(nanos); 155 return ts; 156 } 157 158 /** 159 * Returns a new {@code java.util.Date} corresponding to this {@code timestamp}. Any 160 * sub-millisecond precision will be stripped. 161 * 162 * @return An approximate {@code java.util.Date} representation of this {@code timestamp}. 163 */ toDate()164 public Date toDate() { 165 long secondsInMilliseconds = TimeUnit.SECONDS.toMillis(this.seconds); 166 long nanosInMilliseconds = TimeUnit.NANOSECONDS.toMillis(this.nanos); 167 return new Date(secondsInMilliseconds + nanosInMilliseconds); 168 } 169 170 @Override compareTo(Timestamp other)171 public int compareTo(Timestamp other) { 172 int r = Long.compare(seconds, other.seconds); 173 if (r == 0) { 174 r = Integer.compare(nanos, other.nanos); 175 } 176 return r; 177 } 178 179 /** Creates an instance of Timestamp from {@code com.google.protobuf.Timestamp}. */ fromProto(com.google.protobuf.Timestamp proto)180 public static Timestamp fromProto(com.google.protobuf.Timestamp proto) { 181 return new Timestamp(proto.getSeconds(), proto.getNanos()); 182 } 183 184 /** 185 * Returns a {@code com.google.protobuf.Timestamp} initialized to the same point in time as {@code 186 * this}. 187 */ toProto()188 public com.google.protobuf.Timestamp toProto() { 189 return com.google.protobuf.Timestamp.newBuilder().setSeconds(seconds).setNanos(nanos).build(); 190 } 191 192 /** 193 * Creates a Timestamp instance from the given string. Input string should be in the RFC 3339 194 * format, like '2020-12-01T10:15:30.000Z' or with the timezone offset, such as 195 * '2020-12-01T10:15:30+01:00'. 196 * 197 * @param timestamp string in the RFC 3339 format 198 * @return created Timestamp 199 * @throws DateTimeParseException if unable to parse 200 */ parseTimestamp(String timestamp)201 public static Timestamp parseTimestamp(String timestamp) { 202 TemporalAccessor temporalAccessor = timestampParser.parse(timestamp); 203 Instant instant = Instant.from(temporalAccessor); 204 return ofTimeSecondsAndNanos(instant.getEpochSecond(), instant.getNano()); 205 } 206 toString(StringBuilder b)207 private StringBuilder toString(StringBuilder b) { 208 format.formatTo(LocalDateTime.ofEpochSecond(seconds, 0, ZoneOffset.UTC), b); 209 if (nanos != 0) { 210 b.append(String.format(".%09d", nanos)); 211 } 212 b.append('Z'); 213 return b; 214 } 215 216 @Override toString()217 public String toString() { 218 return toString(new StringBuilder()).toString(); 219 } 220 221 @Override equals(Object o)222 public boolean equals(Object o) { 223 if (this == o) { 224 return true; 225 } 226 if (o == null || getClass() != o.getClass()) { 227 return false; 228 } 229 Timestamp that = (Timestamp) o; 230 return seconds == that.seconds && nanos == that.nanos; 231 } 232 233 @Override hashCode()234 public int hashCode() { 235 return Objects.hash(seconds, nanos); 236 } 237 238 // TODO(user): Consider adding math operations. 239 } 240