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