|
1 package hirondelle.web4j.model; |
|
2 |
|
3 import hirondelle.web4j.BuildImpl; |
|
4 import hirondelle.web4j.action.ActionImpl; |
|
5 import hirondelle.web4j.model.ModelUtil.NullsGo; |
|
6 import hirondelle.web4j.request.TimeZoneSource; |
|
7 import hirondelle.web4j.util.TimeSource; |
|
8 import hirondelle.web4j.util.Util; |
|
9 |
|
10 import java.io.IOException; |
|
11 import java.io.ObjectInputStream; |
|
12 import java.io.ObjectOutputStream; |
|
13 import java.io.Serializable; |
|
14 import java.util.Calendar; |
|
15 import java.util.GregorianCalendar; |
|
16 import java.util.List; |
|
17 import java.util.Locale; |
|
18 import java.util.TimeZone; |
|
19 |
|
20 /** |
|
21 Building block class for an immutable date-time, with no time zone. |
|
22 |
|
23 <P> |
|
24 This class is provided as an alternative to java.util.{@link java.util.Date}. |
|
25 You're strongly encouraged to use this class in your WEB4J applications, but you can still use java.util.{@link java.util.Date} if you wish. |
|
26 |
|
27 <P>This class can hold : |
|
28 <ul> |
|
29 <li>a date-and-time : <tt>1958-03-31 18:59:56.123456789</tt> |
|
30 <li>a date only : <tt>1958-03-31</tt> |
|
31 <li>a time only : <tt>18:59:56.123456789</tt> |
|
32 </ul> |
|
33 |
|
34 <P> |
|
35 <a href='#Examples'>Examples</a><br> |
|
36 <a href='#JustificationForThisClass'>Justification For This Class</a><br> |
|
37 <a href='#DatesAndTimesInGeneral'>Dates and Times In General</a><br> |
|
38 <a href='#TheApproachUsedByThisClass'>The Approach Used By This Class</a><br> |
|
39 <a href='#TwoSetsOfOperations'>Two Sets Of Operations</a><br> |
|
40 <a href='#ParsingDateTimeAcceptedFormats'>Parsing DateTime - Accepted Formats</a><br> |
|
41 <a href='#FormattingLanguage'>Mini-Language for Formatting</a><br> |
|
42 <a href='#InteractionWithTimeSource'>Interaction with {@link TimeSource}</a><br> |
|
43 <a href='#PassingDateTimeToTheDatabase'>Passing DateTime Objects to the Database</a> |
|
44 |
|
45 <a name='Examples'></a> |
|
46 <h3> Examples</h3> |
|
47 Some quick examples of using this class : |
|
48 <PRE> |
|
49 DateTime dateAndTime = new DateTime("2010-01-19 23:59:59"); |
|
50 //highest precision is nanosecond, not millisecond: |
|
51 DateTime dateAndTime = new DateTime("2010-01-19 23:59:59.123456789"); |
|
52 |
|
53 DateTime dateOnly = new DateTime("2010-01-19"); |
|
54 DateTime timeOnly = new DateTime("23:59:59"); |
|
55 |
|
56 DateTime dateOnly = DateTime.forDateOnly(2010,01,19); |
|
57 DateTime timeOnly = DateTime.forTimeOnly(23,59,59,0); |
|
58 |
|
59 DateTime dt = new DateTime("2010-01-15 13:59:15"); |
|
60 boolean leap = dt.isLeapYear(); //false |
|
61 dt.getNumDaysInMonth(); //31 |
|
62 dt.getStartOfMonth(); //2010-01-01, 00:00:00 |
|
63 dt.getEndOfDay(); //2010-01-15, 23:59:59 |
|
64 dt.format("YYYY-MM-DD"); //formats as '2010-01-15' |
|
65 dt.plusDays(30); //30 days after Jan 15 |
|
66 dt.numDaysFrom(someDate); //returns an int |
|
67 dueDate.lt(someDate); //less-than |
|
68 dueDate.lteq(someDate); //less-than-or-equal-to |
|
69 |
|
70 //{@link ActionImpl#getTimeZone()} is readily available in most Actions |
|
71 DateTime.now(getTimeZone()); |
|
72 DateTime.today(getTimeZone()); |
|
73 DateTime fromMilliseconds = DateTime.forInstant(31313121L, getTimeZone()); |
|
74 birthday.isInFuture(getTimeZone()); |
|
75 </PRE> |
|
76 |
|
77 <a name='JustificationForThisClass'></a> |
|
78 <h3> Justification For This Class</h3> |
|
79 The fundamental reasons why this class exists are : |
|
80 <ul> |
|
81 <li>to avoid the embarrassing number of distasteful inadequacies in the JDK's date classes |
|
82 <li>to oppose the very "mental model" of the JDK's date-time classes with something significantly simpler |
|
83 </ul> |
|
84 |
|
85 <a name='MentalModels'></a> |
|
86 <P><b>There are 2 distinct mental models for date-times, and they don't play well together</b> : |
|
87 <ul> |
|
88 <li><b>timeline</b> - an instant on the timeline, as a physicist would picture it, representing the number of |
|
89 seconds from some epoch. In this picture, such a date-time can have many, many different |
|
90 representations according to calendar and time zone. That is, the date-time, <i> as seen and understood by |
|
91 the end user</i>, can change according to "who's looking at it". It's important to understand that a timeline instant, |
|
92 before being presented to the user, <i>must always have an associated time zone - even in the case of |
|
93 a date only, with no time.</i> |
|
94 <li><b>everyday</b> - a date-time in the Gregorian calendar, such as '2009-05-25 18:25:00', |
|
95 which never changes according to "who's looking at it". Here, <i>the time zone is always both implicit and immutable</i>. |
|
96 </ul> |
|
97 |
|
98 <P>The problem is that java.util.{@link java.util.Date} uses <i>only</i> the timeline style, while <i>most</i> users, <i>most</i> |
|
99 of the time, think in terms of the <i>other</i> mental model - the 'everday' style. |
|
100 |
|
101 In particular, there are a large number of applications which experience |
|
102 <a href='http://martinfowler.com/bliki/TimeZoneUncertainty.html'>problems with time zones</a>, because the timeline model |
|
103 is used instead of the everday model. |
|
104 <i>Such problems are often seen by end users as serious bugs, because telling people the wrong date or time is often a serious issue.</i> |
|
105 <b>These problems make you look stupid.</b> |
|
106 |
|
107 <a name='JDKDatesMediocre'></a> |
|
108 <h4>Date Classes in the JDK are Mediocre</h4> |
|
109 The JDK's classes related to dates are widely regarded as frustrating to work with, for various reasons: |
|
110 <ul> |
|
111 <li>mistakes regarding time zones are very common |
|
112 <li>month indexes are 0-based, leading to off-by-one errors |
|
113 <li>difficulty of calculating simple time intervals |
|
114 <li><tt>java.util.Date</tt> is mutable, but 'building block' classes should be |
|
115 immutable |
|
116 <li>numerous other minor nuisances |
|
117 </ul> |
|
118 |
|
119 <a name='JodaTimeDrawbacks'></a> |
|
120 <h4>Joda Time Has Drawbacks As Well</h4> |
|
121 The <a href='http://joda-time.sourceforge.net/'>Joda Time</a> library is used by some programmers as an alternative |
|
122 to the JDK classes. Joda Time has the following drawbacks : |
|
123 <ul> |
|
124 <li>it limits precision to milliseconds. Database timestamp values almost always have a precision of microseconds |
|
125 or even nanoseconds. This is a serious defect: <b>a library should never truncate your data, for any reason.</b> |
|
126 <li>it's large, with well over 100 items in its <a href='http://joda-time.sourceforge.net/api-release/index.html'>javadoc</a> |
|
127 <li>in order to stay current, it needs to be manually updated occasionally with fresh time zone data |
|
128 <li>it has mutable versions of classes |
|
129 <li>it always coerces March 31 + 1 Month to April 30 (for example), without giving you any choice in the matter |
|
130 <li>some databases allow invalid date values such as '0000-00-00', but Joda Time doesn't seem to be able to handle them |
|
131 </ul> |
|
132 |
|
133 |
|
134 <a name='DatesAndTimesInGeneral'></a> |
|
135 <h3>Dates and Times in General</h3> |
|
136 |
|
137 <h4>Civil Timekeeping Is Complex</h4> |
|
138 Civil timekeeping is a byzantine hodge-podge of arcane and arbitrary rules. Consider the following : |
|
139 <ul> |
|
140 <li>months have varying numbers of days |
|
141 <li>one month (February) has a length which depends on the year |
|
142 <li>not all years have the same number of days |
|
143 <li>time zone rules spring forth arbitrarily from the fecund imaginations of legislators |
|
144 <li>summer hours mean that an hour is 'lost' in the spring, while another hour must |
|
145 repeat itself in the autumn, during the switch back to normal time |
|
146 <li>summer hour logic varies widely across various jurisdictions |
|
147 <li>the cutover from the Julian calendar to the Gregorian calendar happened at different times in |
|
148 different places, which causes a varying number of days to be 'lost' during the cutover |
|
149 <li>occasional insertion of leap seconds are used to ensure synchronization with the |
|
150 rotating Earth (whose speed of rotation is gradually slowing down, in an irregular way) |
|
151 <li>there is no year 0 (1 BC is followed by 1 AD), except in the reckoning used by |
|
152 astronomers |
|
153 </ul> |
|
154 |
|
155 <h4>How Databases Treat Dates</h4> |
|
156 <b>Most databases model dates and times using the Gregorian Calendar in an aggressively simplified form</b>, |
|
157 in which : |
|
158 <ul> |
|
159 <li>the Gregorian calendar is extended back in time as if it was in use previous to its |
|
160 inception (the 'proleptic' Gregorian calendar) |
|
161 <li>the transition between Julian and Gregorian calendars is entirely ignored |
|
162 <li>leap seconds are entirely ignored |
|
163 <li>summer hours are entirely ignored |
|
164 <li>often, even time zones are ignored, in the sense that <i>the underlying database |
|
165 column doesn't usually explicitly store any time zone information</i>. |
|
166 </ul> |
|
167 |
|
168 <P><a name='NoTimeZoneInDb'></a>The final point requires elaboration. |
|
169 Some may doubt its veracity, since they have seen date-time information "change time zone" when |
|
170 retrieved from a database. But this sort of change is usually applied using logic which is <i>external</i> to the data |
|
171 stored in the particular column. |
|
172 |
|
173 <P> For example, the following items might be used in the calculation of a time zone difference : |
|
174 <ul> |
|
175 <li>time zone setting for the client (or JDBC driver) |
|
176 <li>time zone setting for the client's connection to the database server |
|
177 <li>time zone setting of the database server |
|
178 <li>time zone setting of the host where the database server resides |
|
179 </ul> |
|
180 |
|
181 <P>(Note as well what's <i>missing</i> from the above list: your own application's logic, and the user's time zone preference.) |
|
182 |
|
183 <P>When an end user sees such changes to a date-time, all they will say to you is |
|
184 <i>"Why did you change it? That's not what I entered"</i> - and this is a completely valid question. |
|
185 Why <i>did</i> you change it? Because you're using the timeline model instead of the everyday model. |
|
186 Perhaps you're using a inappropriate abstraction for what the user really wants. |
|
187 |
|
188 <a name='TheApproachUsedByThisClass'></a> |
|
189 <h3>The Approach Used By This Class</h3> |
|
190 |
|
191 This class takes the following design approach : |
|
192 <ul> |
|
193 <li>it models time in the "everyday" style, not in the "timeline" style (see <a href='#MentalModels'>above</a>) |
|
194 <li>its precision matches the highest precision used by databases (nanosecond) |
|
195 <li>it uses only the proleptic Gregorian Calendar, over the years <tt>1..9999</tt> |
|
196 <li><i>it ignores all non-linearities</i>: summer-hours, leap seconds, and the cutover |
|
197 from Julian to Gregorian calendars |
|
198 <li><i>it ignores time zones</i>. Most date-times are stored in columns whose type |
|
199 does <i>not</i> include time zone information (see note <a href='#NoTimeZoneInDb'>above</a>). |
|
200 <li>it has (very basic) support for wonky dates, such as the magic value <tt>0000-00-00</tt> used by MySQL |
|
201 <li>it's immutable |
|
202 <li>it lets you choose among 4 policies for 'day overflow' conditions during calculations |
|
203 <li>it talks to your {@link TimeSource} implementation when returning the current moment, allowing you to customise dates during testing |
|
204 </ul> |
|
205 |
|
206 <P>Even though the above list may appear restrictive, it's very likely true that |
|
207 <tt>DateTime</tt> can handle the dates and times you're currently storing in your database. |
|
208 |
|
209 <a name='TwoSetsOfOperations'></a> |
|
210 <h3>Two Sets Of Operations</h3> |
|
211 This class allows for 2 sets of operations: a few "basic" operations, and many "computational" ones. |
|
212 |
|
213 <P><b>Basic operations</b> model the date-time as a simple, dumb String, with absolutely no parsing or substructure. |
|
214 This will always allow your application to reflect exactly what is in a <tt>ResultSet</tt>, with |
|
215 absolutely no modification for time zone, locale, or for anything else. |
|
216 |
|
217 <P>This is meant as a back-up, to ensure that <i>your application will always be able |
|
218 to, at the very least, display a date-time exactly as it appears in your |
|
219 <tt>ResultSet</tt> from the database</i>. This style is particularly useful for handling invalid |
|
220 dates such as <tt>2009-00-00</tt>, which can in fact be stored by some databases (MySQL, for |
|
221 example). It can also be used to handle unusual items, such as MySQL's |
|
222 <a href='http://dev.mysql.com/doc/refman/5.1/en/time.html'>TIME</a> datatype. |
|
223 |
|
224 <P>The basic operations are represented by {@link #DateTime(String)}, {@link #toString()}, and {@link #getRawDateString()}. |
|
225 |
|
226 <P><b>Computational operations</b> allow for calculations and formatting. |
|
227 If a computational operation is performed by this class (for example, if the caller asks for the month), |
|
228 then any underlying date-time String must be parseable by this class into its components - year, month, day, and so on. |
|
229 Computational operations require such parsing, while the basic operations do not. Almost all methods in this class |
|
230 are categorized as computational operations. |
|
231 |
|
232 <a name="ParsingDateTimeAcceptedFormats"></a> |
|
233 <h3>Parsing DateTime - Accepted Formats</h3> |
|
234 The {@link #DateTime(String)} constructor accepts a <tt>String</tt> representation of a date-time. |
|
235 The format of the String can take a number of forms. When retrieving date-times from a database, the |
|
236 majority of cases will have little problem in conforming to these formats. If necessary, your SQL statements |
|
237 can almost always use database formatting functions to generate a String whose format conforms to one of the |
|
238 many formats accepted by the {@link #DateTime(String)} constructor. |
|
239 |
|
240 <a name="FormattingLanguage"></a> |
|
241 <h3>Mini-Language for Formatting</h3> |
|
242 This class defines a simple mini-language for formatting a <tt>DateTime</tt>, used by the various <tt>format</tt> methods. |
|
243 |
|
244 <P>The following table defines the symbols used by this mini-language, and the corresponding text they |
|
245 would generate given the date: |
|
246 <PRE>1958-04-09 Wednesday, 03:05:06.123456789 AM</PRE> |
|
247 in an English Locale. (Items related to date are in upper case, and items related to time are in lower case.) |
|
248 |
|
249 <P><table border='1' cellpadding='3' cellspacing='0'> |
|
250 <tr><th>Format</th><th>Output</th> <th>Description</th><th>Needs Locale?</th></tr> |
|
251 <tr><td>YYYY</td> <td>1958</td> <td>Year</td><td>...</td></tr> |
|
252 <tr><td>YY</td> <td>58</td> <td>Year without century</td><td>...</td></tr> |
|
253 <tr><td>M</td> <td>4</td> <td>Month 1..12</td><td>...</td></tr> |
|
254 <tr><td>MM</td> <td>04</td> <td>Month 01..12</td><td>...</td></tr> |
|
255 <tr><td>MMM</td> <td>Apr</td> <td>Month Jan..Dec</td><td>Yes</td></tr> |
|
256 <tr><td>MMMM</td> <td>April</td> <td>Month January..December</td><td>Yes</td></tr> |
|
257 <tr><td>DD</td> <td>09</td> <td>Day 01..31</td><td>...</td></tr> |
|
258 <tr><td>D</td> <td>9</td> <td>Day 1..31</td><td>...</td></tr> |
|
259 <tr><td>WWWW</td> <td>Wednesday</td> <td>Weekday Sunday..Saturday</td><td>Yes</td></tr> |
|
260 <tr><td>WWW</td> <td>Wed</td> <td>Weekday Sun..Sat</td><td>Yes</td></tr> |
|
261 <tr><td>hh</td> <td>03</td> <td>Hour 01..23</td><td>...</td></tr> |
|
262 <tr><td>h</td> <td>3</td> <td>Hour 1..23</td><td>...</td></tr> |
|
263 <tr><td>hh12</td> <td>03</td> <td>Hour 01..12</td><td>...</td></tr> |
|
264 <tr><td>h12</td> <td>3</td> <td>Hour 1..12</td><td>...</td></tr> |
|
265 <tr><td>a</td> <td>AM</td> <td>AM/PM Indicator</td><td>Yes</td></tr> |
|
266 <tr><td>mm</td> <td>05</td> <td>Minutes 01..59</td><td>...</td></tr> |
|
267 <tr><td>m</td> <td>5</td> <td>Minutes 1..59</td><td>...</td></tr> |
|
268 <tr><td>ss</td> <td>06</td> <td>Seconds 01..59</td><td>...</td></tr> |
|
269 <tr><td>s</td> <td>6</td> <td>Seconds 1..59</td><td>...</td></tr> |
|
270 <tr><td>f</td> <td>1</td> <td>Fractional Seconds, 1 decimal</td><td>...</td></tr> |
|
271 <tr><td>ff</td> <td>12</td> <td>Fractional Seconds, 2 decimals</td><td>...</td></tr> |
|
272 <tr><td>fff</td> <td>123</td> <td>Fractional Seconds, 3 decimals</td><td>...</td></tr> |
|
273 <tr><td>ffff</td> <td>1234</td> <td>Fractional Seconds, 4 decimals</td><td>...</td></tr> |
|
274 <tr><td>fffff</td> <td>12345</td> <td>Fractional Seconds, 5 decimals</td><td>...</td></tr> |
|
275 <tr><td>ffffff</td> <td>123456</td> <td>Fractional Seconds, 6 decimals</td><td>...</td></tr> |
|
276 <tr><td>fffffff</td> <td>1234567</td> <td>Fractional Seconds, 7 decimals</td><td>...</td></tr> |
|
277 <tr><td>ffffffff</td> <td>12345678</td> <td>Fractional Seconds, 8 decimals</td><td>...</td></tr> |
|
278 <tr><td>fffffffff</td> <td>123456789</td> <td>Fractional Seconds, 9 decimals</td><td>...</td></tr> |
|
279 <tr><td>|</td> <td>(no example)</td> <td>Escape character</td><td>...</td></tr> |
|
280 </table> |
|
281 |
|
282 <P>As indicated above, some of these symbols can only be used with an accompanying <tt>Locale</tt>. |
|
283 In general, if the output is text, not a number, then a <tt>Locale</tt> will be needed. |
|
284 For example, 'September' is localizable text, while '09' is a numeric representation, which doesn't require a <tt>Locale</tt>. |
|
285 Thus, the symbol 'MM' can be used without a <tt>Locale</tt>, while 'MMMM' and 'MMM' both require a <tt>Locale</tt>, since they |
|
286 generate text, not a number. |
|
287 |
|
288 <P>The fractional seconds 'f' does not perform any rounding. |
|
289 |
|
290 <P> The escape character '|' allows you to insert arbitrary text. |
|
291 The escape character always appears in pairs; these pairs define a range of characters over which |
|
292 the text will not be interpreted using the special format symbols defined above. |
|
293 |
|
294 <P>Examples : |
|
295 <table border='1' cellpadding='3' cellspacing='0'> |
|
296 <tr><th>Format</th><th>Output</th></tr> |
|
297 <tr><td>YYYY-MM-DD hh:mm:ss.fffffffff a</td> <td>1958-04-09 03:05:06.123456789 AM</td></tr> |
|
298 <tr><td>YYYY-MM-DD hh:mm:ss.fff a</td> <td>1958-04-09 03:05:06.123 AM</td></tr> |
|
299 <tr><td>YYYY-MM-DD</td> <td>1958-04-09</td></tr> |
|
300 <tr><td>hh:mm:ss.fffffffff</td> <td>03:05:06.123456789</td></tr> |
|
301 <tr><td>hh:mm:ss</td> <td>03:05:06</td></tr> |
|
302 <tr><td>YYYY-M-D h:m:s</td> <td>1958-4-9 3:5:6</td></tr> |
|
303 <tr><td>WWWW, MMMM D, YYYY</td> <td>Wednesday, April 9, 1958</td></tr> |
|
304 <tr><td>WWWW, MMMM D, YYYY |at| D a</td> <td>Wednesday, April 9, 1958 at 3 AM</td></tr> |
|
305 </table> |
|
306 |
|
307 <P>In the last example, the escape characters are needed only because 'a', the formating symbol for am/pm, appears in the text. |
|
308 |
|
309 <a name='InteractionWithTimeSource'></a> |
|
310 <h3>Interaction with {@link TimeSource}</h3> |
|
311 The methods of this class related to the current date interact with your configured implementation |
|
312 of {@link hirondelle.web4j.util.TimeSource}. That is, {@link #now(TimeZone)} and {@link #today(TimeZone)} |
|
313 will return values derived from {@link TimeSource}. Thus, callers of this class automatically use any |
|
314 'fake' system clock that you may want to define. |
|
315 |
|
316 <a name='PassingDateTimeToTheDatabase'></a> |
|
317 <h3>Passing DateTime Objects to the Database</h3> |
|
318 When a <tt>DateTime</tt> is passed as a parameter to an SQL statement, the <tt>DateTime</tt> is |
|
319 formatted into a <tt>String</tt> of a form accepted by the database. There are two mechanisms to |
|
320 accomplish this |
|
321 <ul> |
|
322 <li>in your DAO code, format the <tt>DateTime</tt> explicitly as a String, using one of the <tt>format</tt> methods, |
|
323 and pass the <tt>String</tt> as the parameter to the SQL statement, and not the actual <tt>DateTime</tt> |
|
324 <li>pass the <tt>DateTime</tt> itself. In this case, WEB4J will use the setting in <tt>web.xml</tt> named |
|
325 <tt>DateTimeFormatForPassingParamsToDb</tt> to perform the formatting for you. If the formats defined by this |
|
326 setting are not appropriate for a given case, you will need to format the <tt>DateTime</tt> as a String explicitly instead. |
|
327 </ul> |
|
328 */ |
|
329 public final class DateTime implements Comparable<DateTime>, Serializable { |
|
330 |
|
331 /** The seven parts of a <tt>DateTime</tt> object. The <tt>DAY</tt> represents the day of the month (1..31), not the weekday. */ |
|
332 public enum Unit { |
|
333 YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, NANOSECONDS; |
|
334 } |
|
335 |
|
336 /** |
|
337 Policy for treating 'day-of-the-month overflow' conditions encountered during some date calculations. |
|
338 |
|
339 <P>Months are different from other units of time, since the length of a month is not fixed, but rather varies with |
|
340 both month and year. This leads to problems. Take the following simple calculation, for example : |
|
341 |
|
342 <PRE>May 31 + 1 month = ?</PRE> |
|
343 |
|
344 <P>What's the answer? Since there is no such thing as June 31, the result of this operation is inherently ambiguous. |
|
345 This <tt>DayOverflow</tt> enumeration lists the various policies for treating such situations, as supported by |
|
346 <tt>DateTime</tt>. |
|
347 |
|
348 <P>This table illustrates how the policies behave : |
|
349 <P><table BORDER="1" CELLPADDING="3" CELLSPACING="0"> |
|
350 <tr> |
|
351 <th>Date</th> |
|
352 <th>DayOverflow</th> |
|
353 <th>Result</th> |
|
354 </tr> |
|
355 <tr> |
|
356 <td>May 31 + 1 Month</td> |
|
357 <td>LastDay</td> |
|
358 <td>June 30</td> |
|
359 </tr> |
|
360 <tr> |
|
361 <td>May 31 + 1 Month</td> |
|
362 <td>FirstDay</td> |
|
363 <td>July 1</td> |
|
364 </tr> |
|
365 <tr> |
|
366 <td>December 31, 2001 + 2 Months</td> |
|
367 <td>Spillover</td> |
|
368 <td>March 3</td> |
|
369 </tr> |
|
370 <tr> |
|
371 <td>May 31 + 1 Month</td> |
|
372 <td>Abort</td> |
|
373 <td>RuntimeException</td> |
|
374 </tr> |
|
375 </table> |
|
376 */ |
|
377 public enum DayOverflow { |
|
378 /** Coerce the day to the last day of the month. */ |
|
379 LastDay, |
|
380 /** Coerce the day to the first day of the next month. */ |
|
381 FirstDay, |
|
382 /** Spillover the day into the next month. */ |
|
383 Spillover, |
|
384 /** Throw a RuntimeException. */ |
|
385 Abort; |
|
386 } |
|
387 |
|
388 /** |
|
389 Constructor taking a date-time as a String. |
|
390 |
|
391 <P>This constructor is called when WEB4J's data layer needs to translate a column in a <tt>ResultSet</tt> |
|
392 into a <tt>DateTime</tt>. |
|
393 |
|
394 <P> When this constructor is called, the underlying text can be in an absolutely arbitrary |
|
395 form, since it will not, initially, be parsed in any way. This policy of extreme |
|
396 leniency allows you to use dates in an arbitrary format, without concern over possible |
|
397 transformations of the date (time zone in particular), and without concerns over possibly bizarre content, such |
|
398 as '2005-00-00', as seen in some databases, such as MySQL. |
|
399 |
|
400 <P><i>However</i>, the moment you attempt to call <a href='#TwoSetsOfOperations'>almost any method</a> |
|
401 in this class, an attempt will be made to parse |
|
402 the given date-time string into its constituent parts. Then, if the date-time string does not match one of the |
|
403 example formats listed below, a <tt>RuntimeException</tt> will be thrown. |
|
404 |
|
405 <P>Before calling this constructor, you may wish to call {@link #isParseable(String)} to explicitly test whether a |
|
406 given String is parseable by this class. |
|
407 |
|
408 <P>The full date format expected by this class is <tt>'YYYY-MM-YY hh:mm:ss.fffffffff'</tt>. |
|
409 All fields except for the fraction of a second have a fixed width. |
|
410 In addition, various portions of this format are also accepted by this class. |
|
411 |
|
412 <P>All of the following dates can be parsed by this class to make a <tt>DateTime</tt> : |
|
413 <ul> |
|
414 <li><tt>2009-12-31 00:00:00.123456789</tt> |
|
415 <li><tt>2009-12-31T00:00:00.123456789</tt> |
|
416 <li><tt>2009-12-31 00:00:00.12345678</tt> |
|
417 <li><tt>2009-12-31 00:00:00.1234567</tt> |
|
418 <li><tt>2009-12-31 00:00:00.123456</tt> |
|
419 <li><tt>2009-12-31 23:59:59.12345</tt> |
|
420 <li><tt>2009-01-31 16:01:01.1234</tt> |
|
421 <li><tt>2009-01-01 16:59:00.123</tt> |
|
422 <li><tt>2009-01-01 16:00:01.12</tt> |
|
423 <li><tt>2009-02-28 16:25:17.1</tt> |
|
424 <li><tt>2009-01-01 00:01:01</tt> |
|
425 <li><tt>2009-01-01 16:01</tt> |
|
426 <li><tt>2009-01-01 16</tt> |
|
427 <li><tt>2009-01-01</tt> |
|
428 <li><tt>2009-01</tt> |
|
429 <li><tt>2009</tt> |
|
430 <li><tt>0009</tt> |
|
431 <li><tt>9</tt> |
|
432 <li><tt>00:00:00.123456789</tt> |
|
433 <li><tt>00:00:00.12345678</tt> |
|
434 <li><tt>00:00:00.1234567</tt> |
|
435 <li><tt>00:00:00.123456</tt> |
|
436 <li><tt>23:59:59.12345</tt> |
|
437 <li><tt>01:59:59.1234</tt> |
|
438 <li><tt>23:01:59.123</tt> |
|
439 <li><tt>00:00:00.12</tt> |
|
440 <li><tt>00:59:59.1</tt> |
|
441 <li><tt>23:59:00</tt> |
|
442 <li><tt>23:00:10</tt> |
|
443 <li><tt>00:59</tt> |
|
444 </ul> |
|
445 |
|
446 <P>The range of each field is : |
|
447 <ul> |
|
448 <li>year: 1..9999 (leading zeroes are optional) |
|
449 <li>month: 01..12 |
|
450 <li>day: 01..31 |
|
451 <li>hour: 00..23 |
|
452 <li>minute: 00..59 |
|
453 <li>second: 00..59 |
|
454 <li>nanosecond: 0..999999999 |
|
455 </ul> |
|
456 |
|
457 <P>Note that <b>database format functions</b> are an option when dealing with date formats. |
|
458 Since your application is always in control of the SQL used to talk to the database, you can, if needed, usually |
|
459 use database format functions to alter the format of dates returned in a <tt>ResultSet</tt>. |
|
460 */ |
|
461 public DateTime(String aDateTime) { |
|
462 fIsAlreadyParsed = false; |
|
463 if (aDateTime == null) { |
|
464 throw new IllegalArgumentException("String passed to DateTime constructor is null. You can use an empty string, but not a null reference."); |
|
465 } |
|
466 fDateTime = aDateTime; |
|
467 } |
|
468 |
|
469 /** |
|
470 Return <tt>true</tt> only if the given String follows one of the formats documented by {@link #DateTime(String)}. |
|
471 <P>If the text is not from a trusted source, then the caller may use this method to validate whether the text |
|
472 is in a form that's parseable by this class. |
|
473 */ |
|
474 public static boolean isParseable(String aCandidateDateTime){ |
|
475 boolean result = true; |
|
476 try { |
|
477 DateTime dt = new DateTime(aCandidateDateTime); |
|
478 dt.ensureParsed(); |
|
479 } |
|
480 catch (RuntimeException ex){ |
|
481 result = false; |
|
482 } |
|
483 return result; |
|
484 } |
|
485 |
|
486 |
|
487 /** |
|
488 Constructor taking each time unit explicitly. |
|
489 |
|
490 <P>Although all parameters are optional, many operations on this class require year-month-day to be |
|
491 present. |
|
492 |
|
493 @param aYear 1..9999, optional |
|
494 @param aMonth 1..12 , optional |
|
495 @param aDay 1..31, cannot exceed the number of days in the given month/year, optional |
|
496 @param aHour 0..23, optional |
|
497 @param aMinute 0..59, optional |
|
498 @param aSecond 0..59, optional |
|
499 @param aNanoseconds 0..999,999,999, optional (allows for databases that store timestamps up to |
|
500 nanosecond precision). |
|
501 */ |
|
502 public DateTime(Integer aYear, Integer aMonth, Integer aDay, Integer aHour, Integer aMinute, Integer aSecond, Integer aNanoseconds) { |
|
503 fIsAlreadyParsed = true; |
|
504 fYear = aYear; |
|
505 fMonth = aMonth; |
|
506 fDay = aDay; |
|
507 fHour = aHour; |
|
508 fMinute = aMinute; |
|
509 fSecond = aSecond; |
|
510 fNanosecond = aNanoseconds; |
|
511 validateState(); |
|
512 } |
|
513 |
|
514 /** |
|
515 Factory method returns a <tt>DateTime</tt> having year-month-day only, with no time portion. |
|
516 <P>See {@link #DateTime(Integer, Integer, Integer, Integer, Integer, Integer, Integer)} for constraints on the parameters. |
|
517 */ |
|
518 public static DateTime forDateOnly(Integer aYear, Integer aMonth, Integer aDay) { |
|
519 return new DateTime(aYear, aMonth, aDay, null, null, null, null); |
|
520 } |
|
521 |
|
522 /** |
|
523 Factory method returns a <tt>DateTime</tt> having hour-minute-second-nanosecond only, with no date portion. |
|
524 <P>See {@link #DateTime(Integer, Integer, Integer, Integer, Integer, Integer, Integer)} for constraints on the parameters. |
|
525 */ |
|
526 public static DateTime forTimeOnly(Integer aHour, Integer aMinute, Integer aSecond, Integer aNanoseconds) { |
|
527 return new DateTime(null, null, null, aHour, aMinute, aSecond, aNanoseconds); |
|
528 } |
|
529 |
|
530 /** |
|
531 Constructor taking a millisecond value and a {@link TimeZone}. |
|
532 This constructor may be use to convert a <tt>java.util.Date</tt> into a <tt>DateTime</tt>. |
|
533 |
|
534 <P>To use nanosecond precision, please use {@link #forInstantNanos(long, TimeZone)} instead. |
|
535 |
|
536 @param aMilliseconds must be in the range corresponding to the range of dates supported by this class (year 1..9999); corresponds |
|
537 to a millisecond instant on the timeline, measured from the epoch used by {@link java.util.Date}. |
|
538 */ |
|
539 public static DateTime forInstant(long aMilliseconds, TimeZone aTimeZone) { |
|
540 Calendar calendar = new GregorianCalendar(aTimeZone); |
|
541 calendar.setTimeInMillis(aMilliseconds); |
|
542 int year = calendar.get(Calendar.YEAR); |
|
543 int month = calendar.get(Calendar.MONTH) + 1; // 0-based |
|
544 int day = calendar.get(Calendar.DAY_OF_MONTH); |
|
545 int hour = calendar.get(Calendar.HOUR_OF_DAY); // 0..23 |
|
546 int minute = calendar.get(Calendar.MINUTE); |
|
547 int second = calendar.get(Calendar.SECOND); |
|
548 int milliseconds = calendar.get(Calendar.MILLISECOND); |
|
549 int nanoseconds = milliseconds * 1000 * 1000; |
|
550 return new DateTime(year, month, day, hour, minute, second, nanoseconds); |
|
551 } |
|
552 |
|
553 /** |
|
554 For the given time zone, return the corresponding time in milliseconds-since-epoch for this <tt>DateTime</tt>. |
|
555 |
|
556 <P>This method is meant to help you convert between a <tt>DateTime</tt> and the |
|
557 JDK's date-time classes, which are based on the combination of a time zone and a |
|
558 millisecond value from the Java epoch. |
|
559 <P>Since <tt>DateTime</tt> can go to nanosecond accuracy, the return value can |
|
560 lose precision. The nanosecond value is truncated to milliseconds, not rounded. |
|
561 To retain nanosecond accuracy, please use {@link #getNanosecondsInstant(TimeZone)} instead. |
|
562 <P>Requires year-month-day to be present; if not, a runtime exception is thrown. |
|
563 */ |
|
564 public long getMilliseconds(TimeZone aTimeZone){ |
|
565 Integer year = getYear(); |
|
566 Integer month = getMonth(); |
|
567 Integer day = getDay(); |
|
568 //coerce missing times to 0: |
|
569 Integer hour = getHour() == null ? 0 : getHour(); |
|
570 Integer minute = getMinute() == null ? 0 : getMinute(); |
|
571 Integer second = getSecond() == null ? 0 : getSecond(); |
|
572 Integer nanos = getNanoseconds() == null ? 0 : getNanoseconds(); |
|
573 |
|
574 Calendar calendar = new GregorianCalendar(aTimeZone); |
|
575 calendar.set(Calendar.YEAR, year); |
|
576 calendar.set(Calendar.MONTH, month-1); // 0-based |
|
577 calendar.set(Calendar.DAY_OF_MONTH, day); |
|
578 calendar.set(Calendar.HOUR_OF_DAY, hour); // 0..23 |
|
579 calendar.set(Calendar.MINUTE, minute); |
|
580 calendar.set(Calendar.SECOND, second); |
|
581 calendar.set(Calendar.MILLISECOND, nanos/1000000); |
|
582 |
|
583 return calendar.getTimeInMillis(); |
|
584 } |
|
585 |
|
586 /** |
|
587 Constructor taking a nanosecond value and a {@link TimeZone}. |
|
588 |
|
589 <P>To use milliseconds instead of nanoseconds, please use {@link #forInstant(long, TimeZone)}. |
|
590 |
|
591 @param aNanoseconds must be in the range corresponding to the range of dates supported by this class (year 1..9999); corresponds |
|
592 to a nanosecond instant on the time-line, measured from the epoch used by {@link java.util.Date}. |
|
593 */ |
|
594 public static DateTime forInstantNanos(long aNanoseconds, TimeZone aTimeZone) { |
|
595 //these items can be of either sign |
|
596 long millis = aNanoseconds / MILLION; //integer division truncates towards 0, doesn't round |
|
597 long nanosRemaining = aNanoseconds % MILLION; //size 0..999,999 |
|
598 //when negative: go to the previous millis, and take the complement of nanosRemaining |
|
599 if(aNanoseconds < 0){ |
|
600 millis = millis - 1; |
|
601 nanosRemaining = MILLION + nanosRemaining; //-1 remaining coerced to 999,999 |
|
602 } |
|
603 |
|
604 //base calculation in millis |
|
605 Calendar calendar = new GregorianCalendar(aTimeZone); |
|
606 calendar.setTimeInMillis(millis); |
|
607 int year = calendar.get(Calendar.YEAR); |
|
608 int month = calendar.get(Calendar.MONTH) + 1; // 0-based |
|
609 int day = calendar.get(Calendar.DAY_OF_MONTH); |
|
610 int hour = calendar.get(Calendar.HOUR_OF_DAY); // 0..23 |
|
611 int minute = calendar.get(Calendar.MINUTE); |
|
612 int second = calendar.get(Calendar.SECOND); |
|
613 int milliseconds = calendar.get(Calendar.MILLISECOND); |
|
614 |
|
615 DateTime withoutNanos = new DateTime(year, month, day, hour, minute, second, milliseconds * MILLION); |
|
616 //adjust for nanos - this cast is acceptable, because the value's range is 0..999,999: |
|
617 DateTime withNanos = withoutNanos.plus(0, 0, 0, 0, 0, 0, (int)nanosRemaining, DayOverflow.Spillover); |
|
618 return withNanos; |
|
619 } |
|
620 |
|
621 /** |
|
622 For the given time zone, return the corresponding time in nanoseconds-since-epoch for this <tt>DateTime</tt>. |
|
623 |
|
624 <P>For conversion between a <tt>DateTime</tt> and the JDK's date-time classes, |
|
625 you should likely use {@link #getMilliseconds(TimeZone)} instead. |
|
626 <P>Requires year-month-day to be present; if not, a runtime exception is thrown. |
|
627 */ |
|
628 public long getNanosecondsInstant(TimeZone aTimeZone){ |
|
629 // these are always positive: |
|
630 Integer year = getYear(); |
|
631 Integer month = getMonth(); |
|
632 Integer day = getDay(); |
|
633 //coerce missing times to 0: |
|
634 Integer hour = getHour() == null ? 0 : getHour(); |
|
635 Integer minute = getMinute() == null ? 0 : getMinute(); |
|
636 Integer second = getSecond() == null ? 0 : getSecond(); |
|
637 Integer nanos = getNanoseconds() == null ? 0 : getNanoseconds(); |
|
638 |
|
639 int millis = nanos / MILLION; //integer division truncates, doesn't round |
|
640 int nanosRemaining = nanos % MILLION; //0..999,999 - always positive |
|
641 |
|
642 //base calculation in millis |
|
643 Calendar calendar = new GregorianCalendar(aTimeZone); |
|
644 calendar.set(Calendar.YEAR, year); |
|
645 calendar.set(Calendar.MONTH, month-1); // 0-based |
|
646 calendar.set(Calendar.DAY_OF_MONTH, day); |
|
647 calendar.set(Calendar.HOUR_OF_DAY, hour); // 0..23 |
|
648 calendar.set(Calendar.MINUTE, minute); |
|
649 calendar.set(Calendar.SECOND, second); |
|
650 calendar.set(Calendar.MILLISECOND, millis); |
|
651 |
|
652 long baseResult = calendar.getTimeInMillis() * MILLION; // either sign |
|
653 //the adjustment for nanos is always positive, toward the future: |
|
654 return baseResult + nanosRemaining; |
|
655 } |
|
656 |
|
657 /** |
|
658 Return the raw date-time String passed to the {@link #DateTime(String)} constructor. |
|
659 Returns <tt>null</tt> if that constructor was not called. See {@link #toString()} as well. |
|
660 */ |
|
661 public String getRawDateString() { |
|
662 return fDateTime; |
|
663 } |
|
664 |
|
665 /** Return the year, 1..9999. */ |
|
666 public Integer getYear() { |
|
667 ensureParsed(); |
|
668 return fYear; |
|
669 } |
|
670 |
|
671 /** Return the Month, 1..12. */ |
|
672 public Integer getMonth() { |
|
673 ensureParsed(); |
|
674 return fMonth; |
|
675 } |
|
676 |
|
677 /** Return the Day of the Month, 1..31. */ |
|
678 public Integer getDay() { |
|
679 ensureParsed(); |
|
680 return fDay; |
|
681 } |
|
682 |
|
683 /** Return the Hour, 0..23. */ |
|
684 public Integer getHour() { |
|
685 ensureParsed(); |
|
686 return fHour; |
|
687 } |
|
688 |
|
689 /** Return the Minute, 0..59. */ |
|
690 public Integer getMinute() { |
|
691 ensureParsed(); |
|
692 return fMinute; |
|
693 } |
|
694 |
|
695 /** Return the Second, 0..59. */ |
|
696 public Integer getSecond() { |
|
697 ensureParsed(); |
|
698 return fSecond; |
|
699 } |
|
700 |
|
701 /** Return the Nanosecond, 0..999999999. */ |
|
702 public Integer getNanoseconds() { |
|
703 ensureParsed(); |
|
704 return fNanosecond; |
|
705 } |
|
706 |
|
707 /** |
|
708 Return the Modified Julian Day Number. |
|
709 <P>The Modified Julian Day Number is defined by astronomers for simplifying the calculation of the number of days between 2 dates. |
|
710 Returns a monotonically increasing sequence number. |
|
711 Day 0 is November 17, 1858 00:00:00 (whose Julian Date was 2400000.5). |
|
712 |
|
713 <P>Using the Modified Julian Day Number instead of the Julian Date has 2 advantages: |
|
714 <ul> |
|
715 <li>it's a smaller number |
|
716 <li>it starts at midnight, not noon (Julian Date starts at noon) |
|
717 </ul> |
|
718 |
|
719 <P>Does not reflect any time portion, if present. |
|
720 |
|
721 <P>(In spite of its name, this method, like all other methods in this class, uses the |
|
722 proleptic Gregorian calendar - not the Julian calendar.) |
|
723 |
|
724 <P>Requires year-month-day to be present; if not, a runtime exception is thrown. |
|
725 */ |
|
726 public Integer getModifiedJulianDayNumber() { |
|
727 ensureHasYearMonthDay(); |
|
728 int result = calculateJulianDayNumberAtNoon() - 1 - EPOCH_MODIFIED_JD; |
|
729 return result; |
|
730 } |
|
731 |
|
732 /** |
|
733 Return an index for the weekday for this <tt>DateTime</tt>. |
|
734 Returns 1..7 for Sunday..Saturday. |
|
735 <P>Requires year-month-day to be present; if not, a runtime exception is thrown. |
|
736 */ |
|
737 public Integer getWeekDay() { |
|
738 ensureHasYearMonthDay(); |
|
739 int dayNumber = calculateJulianDayNumberAtNoon() + 1; |
|
740 int index = dayNumber % 7; |
|
741 return index + 1; |
|
742 } |
|
743 |
|
744 /** |
|
745 Return an integer in the range 1..366, representing a count of the number of days from the start of the year. |
|
746 January 1 is counted as day 1. |
|
747 <P>Requires year-month-day to be present; if not, a runtime exception is thrown. |
|
748 */ |
|
749 public Integer getDayOfYear() { |
|
750 ensureHasYearMonthDay(); |
|
751 int k = isLeapYear() ? 1 : 2; |
|
752 Integer result = ((275 * fMonth) / 9) - k * ((fMonth + 9) / 12) + fDay - 30; // integer division |
|
753 return result; |
|
754 } |
|
755 |
|
756 /** |
|
757 Returns true only if the year is a leap year. |
|
758 <P>Requires year to be present; if not, a runtime exception is thrown. |
|
759 */ |
|
760 public Boolean isLeapYear() { |
|
761 ensureParsed(); |
|
762 Boolean result = null; |
|
763 if (isPresent(fYear)) { |
|
764 result = isLeapYear(fYear); |
|
765 } |
|
766 else { |
|
767 throw new MissingItem("Year is absent. Cannot determine if leap year."); |
|
768 } |
|
769 return result; |
|
770 } |
|
771 |
|
772 /** |
|
773 Return the number of days in the month which holds this <tt>DateTime</tt>. |
|
774 <P>Requires year-month-day to be present; if not, a runtime exception is thrown. |
|
775 */ |
|
776 public int getNumDaysInMonth() { |
|
777 ensureHasYearMonthDay(); |
|
778 return getNumDaysInMonth(fYear, fMonth); |
|
779 } |
|
780 |
|
781 /** |
|
782 Return The week index of this <tt>DateTime</tt> with respect to a given starting <tt>DateTime</tt>. |
|
783 <P>The single parameter to this method defines first day of week number 1. |
|
784 See {@link #getWeekIndex()} as well. |
|
785 <P>Requires year-month-day to be present; if not, a runtime exception is thrown. |
|
786 */ |
|
787 public Integer getWeekIndex(DateTime aStartingFromDate) { |
|
788 ensureHasYearMonthDay(); |
|
789 aStartingFromDate.ensureHasYearMonthDay(); |
|
790 int diff = getModifiedJulianDayNumber() - aStartingFromDate.getModifiedJulianDayNumber(); |
|
791 return (diff / 7) + 1; // integer division |
|
792 } |
|
793 |
|
794 /** |
|
795 Return The week index of this <tt>DateTime</tt>, taking day 1 of week 1 as Sunday, January 2, 2000. |
|
796 <P>See {@link #getWeekIndex(DateTime)} as well, which takes an arbitrary date to define |
|
797 day 1 of week 1. |
|
798 <P>Requires year-month-day to be present; if not, a runtime exception is thrown. |
|
799 */ |
|
800 public Integer getWeekIndex() { |
|
801 DateTime start = DateTime.forDateOnly(2000, 1, 2); |
|
802 return getWeekIndex(start); |
|
803 } |
|
804 |
|
805 /** |
|
806 Return <tt>true</tt> only if this <tt>DateTime</tt> has the same year-month-day as the given parameter. |
|
807 Time is ignored by this method. |
|
808 <P> Requires year-month-day to be present, both for this <tt>DateTime</tt> and for |
|
809 <tt>aThat</tt>; if not, a runtime exception is thrown. |
|
810 */ |
|
811 public boolean isSameDayAs(DateTime aThat) { |
|
812 boolean result = false; |
|
813 ensureHasYearMonthDay(); |
|
814 aThat.ensureHasYearMonthDay(); |
|
815 result = (fYear.equals(aThat.fYear) && fMonth.equals(aThat.fMonth) && fDay.equals(aThat.fDay)); |
|
816 return result; |
|
817 } |
|
818 |
|
819 /** |
|
820 'Less than' comparison. |
|
821 Return <tt>true</tt> only if this <tt>DateTime</tt> comes before the given parameter, according to {@link #compareTo(DateTime)}. |
|
822 */ |
|
823 public boolean lt(DateTime aThat) { |
|
824 return compareTo(aThat) < EQUAL; |
|
825 } |
|
826 |
|
827 /** |
|
828 'Less than or equal to' comparison. |
|
829 Return <tt>true</tt> only if this <tt>DateTime</tt> comes before the given parameter, according to {@link #compareTo(DateTime)}, |
|
830 or this <tt>DateTime</tt> equals the given parameter. |
|
831 */ |
|
832 public boolean lteq(DateTime aThat) { |
|
833 return compareTo(aThat) < EQUAL || equals(aThat); |
|
834 } |
|
835 |
|
836 /** |
|
837 'Greater than' comparison. |
|
838 Return <tt>true</tt> only if this <tt>DateTime</tt> comes after the given parameter, according to {@link #compareTo(DateTime)}. |
|
839 */ |
|
840 public boolean gt(DateTime aThat) { |
|
841 return compareTo(aThat) > EQUAL; |
|
842 } |
|
843 |
|
844 /** |
|
845 'Greater than or equal to' comparison. |
|
846 Return <tt>true</tt> only if this <tt>DateTime</tt> comes after the given parameter, according to {@link #compareTo(DateTime)}, |
|
847 or this <tt>DateTime</tt> equals the given parameter. |
|
848 */ |
|
849 public boolean gteq(DateTime aThat) { |
|
850 return compareTo(aThat) > EQUAL || equals(aThat); |
|
851 } |
|
852 |
|
853 /** Return the smallest non-null time unit encapsulated by this <tt>DateTime</tt>. */ |
|
854 public Unit getPrecision() { |
|
855 ensureParsed(); |
|
856 Unit result = null; |
|
857 if (isPresent(fNanosecond)) { |
|
858 result = Unit.NANOSECONDS; |
|
859 } |
|
860 else if (isPresent(fSecond)) { |
|
861 result = Unit.SECOND; |
|
862 } |
|
863 else if (isPresent(fMinute)) { |
|
864 result = Unit.MINUTE; |
|
865 } |
|
866 else if (isPresent(fHour)) { |
|
867 result = Unit.HOUR; |
|
868 } |
|
869 else if (isPresent(fDay)) { |
|
870 result = Unit.DAY; |
|
871 } |
|
872 else if (isPresent(fMonth)) { |
|
873 result = Unit.MONTH; |
|
874 } |
|
875 else if (isPresent(fYear)) { |
|
876 result = Unit.YEAR; |
|
877 } |
|
878 return result; |
|
879 } |
|
880 |
|
881 /** |
|
882 Truncate this <tt>DateTime</tt> to the given precision. |
|
883 <P>The return value will have all items lower than the given precision simply set to |
|
884 <tt>null</tt>. In addition, the return value will not include any date-time String passed to the |
|
885 {@link #DateTime(String)} constructor. |
|
886 |
|
887 @param aPrecision takes any value <i>except</i> {@link Unit#NANOSECONDS} (since it makes no sense to truncate to the highest |
|
888 available precision). |
|
889 */ |
|
890 public DateTime truncate(Unit aPrecision) { |
|
891 ensureParsed(); |
|
892 DateTime result = null; |
|
893 if (Unit.NANOSECONDS == aPrecision) { |
|
894 throw new IllegalArgumentException("It makes no sense to truncate to nanosecond precision, since that's the highest precision available."); |
|
895 } |
|
896 else if (Unit.SECOND == aPrecision) { |
|
897 result = new DateTime(fYear, fMonth, fDay, fHour, fMinute, fSecond, null); |
|
898 } |
|
899 else if (Unit.MINUTE == aPrecision) { |
|
900 result = new DateTime(fYear, fMonth, fDay, fHour, fMinute, null, null); |
|
901 } |
|
902 else if (Unit.HOUR == aPrecision) { |
|
903 result = new DateTime(fYear, fMonth, fDay, fHour, null, null, null); |
|
904 } |
|
905 else if (Unit.DAY == aPrecision) { |
|
906 result = new DateTime(fYear, fMonth, fDay, null, null, null, null); |
|
907 } |
|
908 else if (Unit.MONTH == aPrecision) { |
|
909 result = new DateTime(fYear, fMonth, null, null, null, null, null); |
|
910 } |
|
911 else if (Unit.YEAR == aPrecision) { |
|
912 result = new DateTime(fYear, null, null, null, null, null, null); |
|
913 } |
|
914 return result; |
|
915 } |
|
916 |
|
917 /** |
|
918 Return <tt>true</tt> only if all of the given units are present in this <tt>DateTime</tt>. |
|
919 If a unit is <i>not</i> included in the argument list, then no test is made for its presence or absence |
|
920 in this <tt>DateTime</tt> by this method. |
|
921 */ |
|
922 public boolean unitsAllPresent(Unit... aUnits) { |
|
923 boolean result = true; |
|
924 ensureParsed(); |
|
925 for (Unit unit : aUnits) { |
|
926 if (Unit.NANOSECONDS == unit) { |
|
927 result = result && fNanosecond != null; |
|
928 } |
|
929 else if (Unit.SECOND == unit) { |
|
930 result = result && fSecond != null; |
|
931 } |
|
932 else if (Unit.MINUTE == unit) { |
|
933 result = result && fMinute != null; |
|
934 } |
|
935 else if (Unit.HOUR == unit) { |
|
936 result = result && fHour != null; |
|
937 } |
|
938 else if (Unit.DAY == unit) { |
|
939 result = result && fDay != null; |
|
940 } |
|
941 else if (Unit.MONTH == unit) { |
|
942 result = result && fMonth != null; |
|
943 } |
|
944 else if (Unit.YEAR == unit) { |
|
945 result = result && fYear != null; |
|
946 } |
|
947 } |
|
948 return result; |
|
949 } |
|
950 |
|
951 /** |
|
952 Return <tt>true</tt> only if this <tt>DateTime</tt> has a non-null values for year, month, and day. |
|
953 */ |
|
954 public boolean hasYearMonthDay() { |
|
955 return unitsAllPresent(Unit.YEAR, Unit.MONTH, Unit.DAY); |
|
956 } |
|
957 |
|
958 /** |
|
959 Return <tt>true</tt> only if this <tt>DateTime</tt> has a non-null values for hour, minute, and second. |
|
960 */ |
|
961 public boolean hasHourMinuteSecond() { |
|
962 return unitsAllPresent(Unit.HOUR, Unit.MINUTE, Unit.SECOND); |
|
963 } |
|
964 |
|
965 /** |
|
966 Return <tt>true</tt> only if all of the given units are absent from this <tt>DateTime</tt>. |
|
967 If a unit is <i>not</i> included in the argument list, then no test is made for its presence or absence |
|
968 in this <tt>DateTime</tt> by this method. |
|
969 */ |
|
970 public boolean unitsAllAbsent(Unit... aUnits) { |
|
971 boolean result = true; |
|
972 ensureParsed(); |
|
973 for (Unit unit : aUnits) { |
|
974 if (Unit.NANOSECONDS == unit) { |
|
975 result = result && fNanosecond == null; |
|
976 } |
|
977 else if (Unit.SECOND == unit) { |
|
978 result = result && fSecond == null; |
|
979 } |
|
980 else if (Unit.MINUTE == unit) { |
|
981 result = result && fMinute == null; |
|
982 } |
|
983 else if (Unit.HOUR == unit) { |
|
984 result = result && fHour == null; |
|
985 } |
|
986 else if (Unit.DAY == unit) { |
|
987 result = result && fDay == null; |
|
988 } |
|
989 else if (Unit.MONTH == unit) { |
|
990 result = result && fMonth == null; |
|
991 } |
|
992 else if (Unit.YEAR == unit) { |
|
993 result = result && fYear == null; |
|
994 } |
|
995 } |
|
996 return result; |
|
997 } |
|
998 |
|
999 /** |
|
1000 Return this <tt>DateTime</tt> with the time portion coerced to '00:00:00.000000000'. |
|
1001 <P>Requires year-month-day to be present; if not, a runtime exception is thrown. |
|
1002 */ |
|
1003 public DateTime getStartOfDay() { |
|
1004 ensureHasYearMonthDay(); |
|
1005 return getStartEndDateTime(fDay, 0, 0, 0, 0); |
|
1006 } |
|
1007 |
|
1008 /** |
|
1009 Return this <tt>DateTime</tt> with the time portion coerced to '23:59:59.999999999'. |
|
1010 <P>Requires year-month-day to be present; if not, a runtime exception is thrown. |
|
1011 */ |
|
1012 public DateTime getEndOfDay() { |
|
1013 ensureHasYearMonthDay(); |
|
1014 return getStartEndDateTime(fDay, 23, 59, 59, 999999999); |
|
1015 } |
|
1016 |
|
1017 /** |
|
1018 Return this <tt>DateTime</tt> with the time portion coerced to '00:00:00.000000000', |
|
1019 and the day coerced to 1. |
|
1020 <P>Requires year-month-day to be present; if not, a runtime exception is thrown. |
|
1021 */ |
|
1022 public DateTime getStartOfMonth() { |
|
1023 ensureHasYearMonthDay(); |
|
1024 return getStartEndDateTime(1, 0, 0, 0, 0); |
|
1025 } |
|
1026 |
|
1027 /** |
|
1028 Return this <tt>DateTime</tt> with the time portion coerced to '23:59:59.999999999', |
|
1029 and the day coerced to the end of the month. |
|
1030 <P>Requires year-month-day to be present; if not, a runtime exception is thrown. |
|
1031 */ |
|
1032 public DateTime getEndOfMonth() { |
|
1033 ensureHasYearMonthDay(); |
|
1034 return getStartEndDateTime(getNumDaysInMonth(), 23, 59, 59, 999999999); |
|
1035 } |
|
1036 |
|
1037 /** |
|
1038 Create a new <tt>DateTime</tt> by adding an interval to this one. |
|
1039 |
|
1040 <P>See {@link #plusDays(Integer)} as well. |
|
1041 |
|
1042 <P>Changes are always applied by this class <i>in order of decreasing units of time</i>: |
|
1043 years first, then months, and so on. After changing both the year and month, a check on the month-day combination is made before |
|
1044 any change is made to the day. If the day exceeds the number of days in the given month/year, then |
|
1045 (and only then) the given {@link DayOverflow} policy applied, and the day-of-the-month is adusted accordingly. |
|
1046 |
|
1047 <P>Afterwards, the day is then changed in the usual way, followed by the remaining items (hour, minute, second, and nanosecond). |
|
1048 |
|
1049 <P><em>The mental model for this method is very similar to that of a car's odometer.</em> When a limit is reach for one unit of time, |
|
1050 then a rollover occurs for a neighbouring unit of time. |
|
1051 |
|
1052 <P>The returned value cannot come after <tt>9999-12-13 23:59:59</tt>. |
|
1053 |
|
1054 <P>This class works with <tt>DateTime</tt>'s having the following items present : |
|
1055 <ul> |
|
1056 <li>year-month-day and hour-minute-second (and optional nanoseconds) |
|
1057 <li>year-month-day only. In this case, if a calculation with a time part is performed, that time part |
|
1058 will be initialized by this class to 00:00:00.0, and the <tt>DateTime</tt> returned by this class will include a time part. |
|
1059 <li>hour-minute-second (and optional nanoseconds) only. In this case, the calculation is done starting with the |
|
1060 the arbitrary date <tt>0001-01-01</tt> (in order to remain within a valid state space of <tt>DateTime</tt>). |
|
1061 </ul> |
|
1062 |
|
1063 @param aNumYears positive, required, in range 0...9999 |
|
1064 @param aNumMonths positive, required, in range 0...9999 |
|
1065 @param aNumDays positive, required, in range 0...9999 |
|
1066 @param aNumHours positive, required, in range 0...9999 |
|
1067 @param aNumMinutes positive, required, in range 0...9999 |
|
1068 @param aNumSeconds positive, required, in range 0...9999 |
|
1069 @param aNumNanoseconds positive, required, in range 0...999999999 |
|
1070 */ |
|
1071 public DateTime plus(Integer aNumYears, Integer aNumMonths, Integer aNumDays, Integer aNumHours, Integer aNumMinutes, Integer aNumSeconds, Integer aNumNanoseconds, DayOverflow aDayOverflow) { |
|
1072 DateTimeInterval interval = new DateTimeInterval(this, aDayOverflow); |
|
1073 return interval.plus(aNumYears, aNumMonths, aNumDays, aNumHours, aNumMinutes, aNumSeconds, aNumNanoseconds); |
|
1074 } |
|
1075 |
|
1076 /** |
|
1077 Create a new <tt>DateTime</tt> by subtracting an interval to this one. |
|
1078 |
|
1079 <P>See {@link #minusDays(Integer)} as well. |
|
1080 <P>This method has nearly the same behavior as {@link #plus(Integer, Integer, Integer, Integer, Integer, Integer, Integer, DayOverflow)}, |
|
1081 except that the return value cannot come before <tt>0001-01-01 00:00:00</tt>. |
|
1082 */ |
|
1083 public DateTime minus(Integer aNumYears, Integer aNumMonths, Integer aNumDays, Integer aNumHours, Integer aNumMinutes, Integer aNumSeconds, Integer aNumNanoseconds, DayOverflow aDayOverflow) { |
|
1084 DateTimeInterval interval = new DateTimeInterval(this, aDayOverflow); |
|
1085 return interval.minus(aNumYears, aNumMonths, aNumDays, aNumHours, aNumMinutes, aNumSeconds, aNumNanoseconds); |
|
1086 } |
|
1087 |
|
1088 /** |
|
1089 Return a new <tt>DateTime</tt> by adding an integral number of days to this one. |
|
1090 |
|
1091 <P>Requires year-month-day to be present; if not, a runtime exception is thrown. |
|
1092 @param aNumDays can be either sign; if negative, then the days are subtracted. |
|
1093 */ |
|
1094 public DateTime plusDays(Integer aNumDays) { |
|
1095 ensureHasYearMonthDay(); |
|
1096 int thisJDAtNoon = getModifiedJulianDayNumber() + 1 + EPOCH_MODIFIED_JD; |
|
1097 int resultJD = thisJDAtNoon + aNumDays; |
|
1098 DateTime datePortion = fromJulianDayNumberAtNoon(resultJD); |
|
1099 return new DateTime(datePortion.getYear(), datePortion.getMonth(), datePortion.getDay(), fHour, fMinute, fSecond, fNanosecond); |
|
1100 } |
|
1101 |
|
1102 /** |
|
1103 Return a new <tt>DateTime</tt> by subtracting an integral number of days from this one. |
|
1104 |
|
1105 <P>Requires year-month-day to be present; if not, a runtime exception is thrown. |
|
1106 @param aNumDays can be either sign; if negative, then the days are added. |
|
1107 */ |
|
1108 public DateTime minusDays(Integer aNumDays) { |
|
1109 return plusDays(-1 * aNumDays); |
|
1110 } |
|
1111 |
|
1112 /** |
|
1113 The whole number of days between this <tt>DateTime</tt> and the given parameter. |
|
1114 <P>Requires year-month-day to be present, both for this <tt>DateTime</tt> and for the <tt>aThat</tt> |
|
1115 parameter; if not, a runtime exception is thrown. |
|
1116 */ |
|
1117 public int numDaysFrom(DateTime aThat) { |
|
1118 return aThat.getModifiedJulianDayNumber() - this.getModifiedJulianDayNumber(); |
|
1119 } |
|
1120 |
|
1121 /** |
|
1122 The number of seconds between this <tt>DateTime</tt> and the given argument. |
|
1123 <P>If only time information is present in both this <tt>DateTime</tt> and <tt>aThat</tt>, then there are |
|
1124 no restrictions on the values of the time units. |
|
1125 <P>If any date information is present, in either this <tt>DateTime</tt> or <tt>aThat</tt>, |
|
1126 then full year-month-day must be present in both; if not, then the date portion will be ignored, and only the |
|
1127 time portion will contribute to the calculation. |
|
1128 */ |
|
1129 public long numSecondsFrom(DateTime aThat) { |
|
1130 long result = 0; |
|
1131 if(hasYearMonthDay() && aThat.hasYearMonthDay()){ |
|
1132 result = numDaysFrom(aThat) * 86400; //just the day portion |
|
1133 } |
|
1134 result = result - this.numSecondsInTimePortion() + aThat.numSecondsInTimePortion(); |
|
1135 return result; |
|
1136 } |
|
1137 |
|
1138 /** |
|
1139 Output this <tt>DateTime</tt> as a formatted String using numbers, with no localizable text. |
|
1140 |
|
1141 <P>Example: |
|
1142 <PRE>dt.format("YYYY-MM-DD hh:mm:ss");</PRE> |
|
1143 would generate text of the form |
|
1144 <PRE>2009-09-09 18:23:59</PRE> |
|
1145 |
|
1146 <P>If months, weekdays, or AM/PM indicators are output as localizable text, you must use {@link #format(String, Locale)}. |
|
1147 @param aFormat uses the <a href="#FormattingLanguage">formatting mini-language</a> defined in the class comment. |
|
1148 */ |
|
1149 public String format(String aFormat) { |
|
1150 DateTimeFormatter format = new DateTimeFormatter(aFormat); |
|
1151 return format.format(this); |
|
1152 } |
|
1153 |
|
1154 /** |
|
1155 Output this <tt>DateTime</tt> as a formatted String using numbers and/or localizable text. |
|
1156 |
|
1157 <P>This method is intended for alphanumeric output, such as '<tt>Sunday, November 14, 1858 10:00 AM</tt>'. |
|
1158 <P>If months and weekdays are output as numbers, you are encouraged to use {@link #format(String)} instead. |
|
1159 |
|
1160 @param aFormat uses the <a href="#FormattingLanguage">formatting mini-language</a> defined in the class comment. |
|
1161 @param aLocale used to generate text for Month, Weekday and AM/PM indicator; required only by patterns which return localized |
|
1162 text, instead of numeric forms. |
|
1163 */ |
|
1164 public String format(String aFormat, Locale aLocale) { |
|
1165 DateTimeFormatter format = new DateTimeFormatter(aFormat, aLocale); |
|
1166 return format.format(this); |
|
1167 } |
|
1168 |
|
1169 /** |
|
1170 Output this <tt>DateTime</tt> as a formatted String using numbers and explicit text for months, weekdays, and AM/PM indicator. |
|
1171 |
|
1172 <P>Use of this method is likely relatively rare; it should be used only if the output of {@link #format(String, Locale)} is |
|
1173 inadequate. |
|
1174 |
|
1175 @param aFormat uses the <a href="#FormattingLanguage">formatting mini-language</a> defined in the class comment. |
|
1176 @param aMonths contains text for all 12 months, starting with January; size must be 12. |
|
1177 @param aWeekdays contains text for all 7 weekdays, starting with Sunday; size must be 7. |
|
1178 @param aAmPmIndicators contains text for A.M and P.M. indicators (in that order); size must be 2. |
|
1179 */ |
|
1180 public String format(String aFormat, List<String> aMonths, List<String> aWeekdays, List<String> aAmPmIndicators) { |
|
1181 DateTimeFormatter format = new DateTimeFormatter(aFormat, aMonths, aWeekdays, aAmPmIndicators); |
|
1182 return format.format(this); |
|
1183 } |
|
1184 |
|
1185 /** |
|
1186 Return the current date-time. |
|
1187 <P>Combines the configured implementation of {@link TimeSource} with the given {@link TimeZone}. |
|
1188 The <tt>TimeZone</tt> will typically come from your implementation of {@link TimeZoneSource}. |
|
1189 |
|
1190 <P>In an Action, the current date-time date can be referenced using |
|
1191 <PRE>DateTime.now(getTimeZone())</PRE> |
|
1192 See {@link ActionImpl#getTimeZone()}. |
|
1193 |
|
1194 <P>Only millisecond precision is possible for this method. |
|
1195 */ |
|
1196 public static DateTime now(TimeZone aTimeZone) { |
|
1197 TimeSource timesource = BuildImpl.forTimeSource(); |
|
1198 return forInstant(timesource.currentTimeMillis(), aTimeZone); |
|
1199 } |
|
1200 |
|
1201 /** |
|
1202 Return the current date. |
|
1203 <P>As in {@link #now(TimeZone)}, but truncates the time portion, leaving only year-month-day. |
|
1204 <P>In an Action, today's date can be referenced using |
|
1205 <PRE>DateTime.today(getTimeZone())</PRE> |
|
1206 See {@link ActionImpl#getTimeZone()}. |
|
1207 */ |
|
1208 public static DateTime today(TimeZone aTimeZone) { |
|
1209 DateTime result = now(aTimeZone); |
|
1210 return result.truncate(Unit.DAY); |
|
1211 } |
|
1212 |
|
1213 /** Return <tt>true</tt> only if this date is in the future, with respect to {@link #now(TimeZone)}. */ |
|
1214 public boolean isInTheFuture(TimeZone aTimeZone) { |
|
1215 return now(aTimeZone).lt(this); |
|
1216 } |
|
1217 |
|
1218 /** Return <tt>true</tt> only if this date is in the past, with respect to {@link #now(TimeZone)}. */ |
|
1219 public boolean isInThePast(TimeZone aTimeZone) { |
|
1220 return now(aTimeZone).gt(this); |
|
1221 } |
|
1222 |
|
1223 /** |
|
1224 Return a <tt>DateTime</tt> corresponding to a change from one {@link TimeZone} to another. |
|
1225 |
|
1226 <P>A <tt>DateTime</tt> object has an implicit and immutable time zone. |
|
1227 If you need to change the implicit time zone, you can use this method to do so. |
|
1228 |
|
1229 <P>Example : |
|
1230 <PRE> |
|
1231 TimeZone fromUK = TimeZone.getTimeZone("Europe/London"); |
|
1232 TimeZone toIndonesia = TimeZone.getTimeZone("Asia/Jakarta"); |
|
1233 DateTime newDt = oldDt.changeTimeZone(fromUK, toIndonesia); |
|
1234 </PRE> |
|
1235 |
|
1236 <P>Requires year-month-day-hour to be present; if not, a runtime exception is thrown. |
|
1237 @param aFromTimeZone the implicit time zone of this object. |
|
1238 @param aToTimeZone the implicit time zone of the <tt>DateTime</tt> returned by this method. |
|
1239 @return aDateTime corresponding to the change of time zone implied by the 2 parameters. |
|
1240 */ |
|
1241 public DateTime changeTimeZone(TimeZone aFromTimeZone, TimeZone aToTimeZone){ |
|
1242 DateTime result = null; |
|
1243 ensureHasYearMonthDay(); |
|
1244 if (unitsAllAbsent(Unit.HOUR)){ |
|
1245 throw new IllegalArgumentException("DateTime does not include the hour. Cannot change the time zone if no hour is present."); |
|
1246 } |
|
1247 Calendar fromDate = new GregorianCalendar(aFromTimeZone); |
|
1248 fromDate.set(Calendar.YEAR, getYear()); |
|
1249 fromDate.set(Calendar.MONTH, getMonth()-1); |
|
1250 fromDate.set(Calendar.DAY_OF_MONTH, getDay()); |
|
1251 fromDate.set(Calendar.HOUR_OF_DAY, getHour()); |
|
1252 if(getMinute() != null) { |
|
1253 fromDate.set(Calendar.MINUTE, getMinute()); |
|
1254 } |
|
1255 else { |
|
1256 fromDate.set(Calendar.MINUTE, 0); |
|
1257 } |
|
1258 //other items zeroed out here, since they don't matter for time zone calculations |
|
1259 fromDate.set(Calendar.SECOND, 0); |
|
1260 fromDate.set(Calendar.MILLISECOND, 0); |
|
1261 |
|
1262 //millisecond precision is OK here, since the seconds/nanoseconds are not part of the calc |
|
1263 Calendar toDate = new GregorianCalendar(aToTimeZone); |
|
1264 toDate.setTimeInMillis(fromDate.getTimeInMillis()); |
|
1265 //needed if this date has hour, but no minute (bit of an oddball case) : |
|
1266 Integer minute = getMinute() != null ? toDate.get(Calendar.MINUTE) : null; |
|
1267 result = new DateTime( |
|
1268 toDate.get(Calendar.YEAR), toDate.get(Calendar.MONTH) + 1, toDate.get(Calendar.DAY_OF_MONTH), |
|
1269 toDate.get(Calendar.HOUR_OF_DAY), minute, getSecond(), getNanoseconds() |
|
1270 ); |
|
1271 return result; |
|
1272 } |
|
1273 |
|
1274 /** |
|
1275 Compare this object to another, for ordering purposes. |
|
1276 <P> Uses the 7 date-time elements (year..nanosecond). The Year is considered the most |
|
1277 significant item, and the Nanosecond the least significant item. Null items are placed first in this comparison. |
|
1278 */ |
|
1279 public int compareTo(DateTime aThat) { |
|
1280 if (this == aThat) return EQUAL; |
|
1281 ensureParsed(); |
|
1282 aThat.ensureParsed(); |
|
1283 |
|
1284 NullsGo nullsGo = NullsGo.FIRST; |
|
1285 int comparison = ModelUtil.comparePossiblyNull(this.fYear, aThat.fYear, nullsGo); |
|
1286 if (comparison != EQUAL) return comparison; |
|
1287 |
|
1288 comparison = ModelUtil.comparePossiblyNull(this.fMonth, aThat.fMonth, nullsGo); |
|
1289 if (comparison != EQUAL) return comparison; |
|
1290 |
|
1291 comparison = ModelUtil.comparePossiblyNull(this.fDay, aThat.fDay, nullsGo); |
|
1292 if (comparison != EQUAL) return comparison; |
|
1293 |
|
1294 comparison = ModelUtil.comparePossiblyNull(this.fHour, aThat.fHour, nullsGo); |
|
1295 if (comparison != EQUAL) return comparison; |
|
1296 |
|
1297 comparison = ModelUtil.comparePossiblyNull(this.fMinute, aThat.fMinute, nullsGo); |
|
1298 if (comparison != EQUAL) return comparison; |
|
1299 |
|
1300 comparison = ModelUtil.comparePossiblyNull(this.fSecond, aThat.fSecond, nullsGo); |
|
1301 if (comparison != EQUAL) return comparison; |
|
1302 |
|
1303 comparison = ModelUtil.comparePossiblyNull(this.fNanosecond, aThat.fNanosecond, nullsGo); |
|
1304 if (comparison != EQUAL) return comparison; |
|
1305 |
|
1306 return EQUAL; |
|
1307 } |
|
1308 |
|
1309 /** |
|
1310 Equals method for this object. |
|
1311 |
|
1312 <P>Equality is determined by the 7 date-time elements (year..nanosecond). |
|
1313 */ |
|
1314 @Override public boolean equals(Object aThat) { |
|
1315 /* |
|
1316 * Implementation note: it was considered branching this method, according to whether |
|
1317 * the objects are already parsed. That was rejected, since maintaining 'synchronicity' |
|
1318 * with hashCode would not then be possible, since hashCode is based only on one object, |
|
1319 * not two. |
|
1320 */ |
|
1321 ensureParsed(); |
|
1322 Boolean result = ModelUtil.quickEquals(this, aThat); |
|
1323 if (result == null) { |
|
1324 DateTime that = (DateTime)aThat; |
|
1325 that.ensureParsed(); |
|
1326 result = ModelUtil.equalsFor(this.getSignificantFields(), that.getSignificantFields()); |
|
1327 } |
|
1328 return result; |
|
1329 } |
|
1330 |
|
1331 /** |
|
1332 Hash code for this object. |
|
1333 |
|
1334 <P> Uses the same 7 date-time elements (year..nanosecond) as used by |
|
1335 {@link #equals(Object)}. |
|
1336 */ |
|
1337 @Override public int hashCode() { |
|
1338 if (fHashCode == 0) { |
|
1339 ensureParsed(); |
|
1340 fHashCode = ModelUtil.hashCodeFor(getSignificantFields()); |
|
1341 } |
|
1342 return fHashCode; |
|
1343 } |
|
1344 |
|
1345 /** |
|
1346 Intended for <i>debugging and logging</i> only. |
|
1347 |
|
1348 <P><b>To format this <tt>DateTime</tt> for presentation to the user, see the various <tt>format</tt> methods.</b> |
|
1349 |
|
1350 <P>If the {@link #DateTime(String)} constructor was called, then return that String. |
|
1351 |
|
1352 <P>Otherwise, the return value is constructed from each date-time element, in a fixed format, depending |
|
1353 on which time units are present. Example values : |
|
1354 <ul> |
|
1355 <li>2011-04-30 13:59:59.123456789 |
|
1356 <li>2011-04-30 13:59:59 |
|
1357 <li>2011-04-30 |
|
1358 <li>2011-04-30 13:59 |
|
1359 <li>13:59:59.123456789 |
|
1360 <li>13:59:59 |
|
1361 <li>and so on... |
|
1362 </ul> |
|
1363 |
|
1364 <P>In the great majority of cases, this will give reasonable output for debugging and logging statements. |
|
1365 |
|
1366 <P>In cases where a bizarre combinations of time units is present, the return value is presented in a verbose form. |
|
1367 For example, if all time units are present <i>except</i> for minutes, the return value has this form: |
|
1368 <PRE>Y:2001 M:1 D:31 h:13 m:null s:59 f:123456789</PRE> |
|
1369 */ |
|
1370 @Override public String toString() { |
|
1371 String result = ""; |
|
1372 if (Util.textHasContent(fDateTime)) { |
|
1373 result = fDateTime; |
|
1374 } |
|
1375 else { |
|
1376 String format = calcToStringFormat(); |
|
1377 if(format != null){ |
|
1378 result = format(calcToStringFormat()); |
|
1379 } |
|
1380 else { |
|
1381 StringBuilder builder = new StringBuilder(); |
|
1382 addToString("Y", fYear, builder); |
|
1383 addToString("M", fMonth, builder); |
|
1384 addToString("D", fDay, builder); |
|
1385 addToString("h", fHour, builder); |
|
1386 addToString("m", fMinute, builder); |
|
1387 addToString("s", fSecond, builder); |
|
1388 addToString("f", fNanosecond, builder); |
|
1389 result = builder.toString().trim(); |
|
1390 } |
|
1391 } |
|
1392 return result; |
|
1393 } |
|
1394 |
|
1395 // PACKAGE-PRIVATE (for unit testing, mostly) |
|
1396 |
|
1397 static final class ItemOutOfRange extends RuntimeException { |
|
1398 ItemOutOfRange(String aMessage) { |
|
1399 super(aMessage); |
|
1400 } |
|
1401 } |
|
1402 |
|
1403 static final class MissingItem extends RuntimeException { |
|
1404 MissingItem(String aMessage) { |
|
1405 super(aMessage); |
|
1406 } |
|
1407 } |
|
1408 |
|
1409 /** Intended as internal tool, for testing only. Not scope is not public! */ |
|
1410 void ensureParsed() { |
|
1411 if (!fIsAlreadyParsed) { |
|
1412 parseDateTimeText(); |
|
1413 } |
|
1414 } |
|
1415 |
|
1416 /** |
|
1417 Return the number of days in the given month. The returned value depends on the year as |
|
1418 well, because of leap years. Returns <tt>null</tt> if either year or month are |
|
1419 absent. WRONG - should be public?? |
|
1420 Package-private, needed for interval calcs. |
|
1421 */ |
|
1422 static Integer getNumDaysInMonth(Integer aYear, Integer aMonth) { |
|
1423 Integer result = null; |
|
1424 if (aYear != null && aMonth != null) { |
|
1425 if (aMonth == 1) { |
|
1426 result = 31; |
|
1427 } |
|
1428 else if (aMonth == 2) { |
|
1429 result = isLeapYear(aYear) ? 29 : 28; |
|
1430 } |
|
1431 else if (aMonth == 3) { |
|
1432 result = 31; |
|
1433 } |
|
1434 else if (aMonth == 4) { |
|
1435 result = 30; |
|
1436 } |
|
1437 else if (aMonth == 5) { |
|
1438 result = 31; |
|
1439 } |
|
1440 else if (aMonth == 6) { |
|
1441 result = 30; |
|
1442 } |
|
1443 else if (aMonth == 7) { |
|
1444 result = 31; |
|
1445 } |
|
1446 else if (aMonth == 8) { |
|
1447 result = 31; |
|
1448 } |
|
1449 else if (aMonth == 9) { |
|
1450 result = 30; |
|
1451 } |
|
1452 else if (aMonth == 10) { |
|
1453 result = 31; |
|
1454 } |
|
1455 else if (aMonth == 11) { |
|
1456 result = 30; |
|
1457 } |
|
1458 else if (aMonth == 12) { |
|
1459 result = 31; |
|
1460 } |
|
1461 else { |
|
1462 throw new AssertionError("Month is out of range 1..12:" + aMonth); |
|
1463 } |
|
1464 } |
|
1465 return result; |
|
1466 } |
|
1467 |
|
1468 static DateTime fromJulianDayNumberAtNoon(int aJDAtNoon) { |
|
1469 //http://www.hermetic.ch/cal_stud/jdn.htm |
|
1470 int l = aJDAtNoon + 68569; |
|
1471 int n = (4 * l) / 146097; |
|
1472 l = l - (146097 * n + 3) / 4; |
|
1473 int i = (4000 * (l + 1)) / 1461001; |
|
1474 l = l - (1461 * i) / 4 + 31; |
|
1475 int j = (80 * l) / 2447; |
|
1476 int d = l - (2447 * j) / 80; |
|
1477 l = j / 11; |
|
1478 int m = j + 2 - (12 * l); |
|
1479 int y = 100 * (n - 49) + i + l; |
|
1480 return DateTime.forDateOnly(y, m, d); |
|
1481 } |
|
1482 |
|
1483 // PRIVATE |
|
1484 |
|
1485 /* |
|
1486 There are 2 representations of a date - a text form, and a 'parsed' form, in which all |
|
1487 of the elements of the date are separated out. A DateTime starts out with one of these |
|
1488 forms, and may need to generate the other. |
|
1489 */ |
|
1490 |
|
1491 /** The text form of a date. @serial */ |
|
1492 private String fDateTime; |
|
1493 |
|
1494 /* The following 7 items represent the parsed form of a DateTime. */ |
|
1495 /** @serial */ |
|
1496 private Integer fYear; |
|
1497 /** @serial */ |
|
1498 private Integer fMonth; |
|
1499 /** @serial */ |
|
1500 private Integer fDay; |
|
1501 /** @serial */ |
|
1502 private Integer fHour; |
|
1503 /** @serial */ |
|
1504 private Integer fMinute; |
|
1505 /** @serial */ |
|
1506 private Integer fSecond; |
|
1507 /** @serial */ |
|
1508 private Integer fNanosecond; |
|
1509 |
|
1510 /** Indicates if this DateTime has been parsed into its 7 constituents. @serial */ |
|
1511 private boolean fIsAlreadyParsed; |
|
1512 |
|
1513 /** @serial */ |
|
1514 private int fHashCode; |
|
1515 |
|
1516 private static final int EQUAL = 0; |
|
1517 |
|
1518 private static int EPOCH_MODIFIED_JD = 2400000; |
|
1519 |
|
1520 private static final int MILLION = 1000000; |
|
1521 |
|
1522 private static final long serialVersionUID = -1300068157085493891L; |
|
1523 |
|
1524 /** |
|
1525 Return a the whole number, with no fraction. |
|
1526 The JD at noon is 1 more than the JD at midnight. |
|
1527 */ |
|
1528 private int calculateJulianDayNumberAtNoon() { |
|
1529 //http://www.hermetic.ch/cal_stud/jdn.htm |
|
1530 int y = fYear; |
|
1531 int m = fMonth; |
|
1532 int d = fDay; |
|
1533 int result = (1461 * (y + 4800 + (m - 14) / 12)) / 4 + (367 * (m - 2 - 12 * ((m - 14) / 12))) / 12 - (3 * ((y + 4900 + (m - 14) / 12) / 100)) / 4 + d - 32075; |
|
1534 return result; |
|
1535 } |
|
1536 |
|
1537 private void ensureHasYearMonthDay() { |
|
1538 ensureParsed(); |
|
1539 if (!hasYearMonthDay()) { |
|
1540 throw new MissingItem("DateTime does not include year/month/day."); |
|
1541 } |
|
1542 } |
|
1543 |
|
1544 /** Return the number of seconds in any existing time portion of the date. */ |
|
1545 private int numSecondsInTimePortion() { |
|
1546 int result = 0; |
|
1547 if (fSecond != null) { |
|
1548 result = result + fSecond; |
|
1549 } |
|
1550 if (fMinute != null) { |
|
1551 result = result + 60 * fMinute; |
|
1552 } |
|
1553 if (fHour != null) { |
|
1554 result = result + 3600 * fHour; |
|
1555 } |
|
1556 return result; |
|
1557 } |
|
1558 |
|
1559 private void validateState() { |
|
1560 checkRange(fYear, 1, 9999, "Year"); |
|
1561 checkRange(fMonth, 1, 12, "Month"); |
|
1562 checkRange(fDay, 1, 31, "Day"); |
|
1563 checkRange(fHour, 0, 23, "Hour"); |
|
1564 checkRange(fMinute, 0, 59, "Minute"); |
|
1565 checkRange(fSecond, 0, 59, "Second"); |
|
1566 checkRange(fNanosecond, 0, 999999999, "Nanosecond"); |
|
1567 checkNumDaysInMonth(fYear, fMonth, fDay); |
|
1568 } |
|
1569 |
|
1570 private void checkRange(Integer aValue, int aMin, int aMax, String aName) { |
|
1571 if (!Check.optional(aValue, Check.range(aMin, aMax))) { |
|
1572 throw new ItemOutOfRange(aName + " is not in the range " + aMin + ".." + aMax + ". Value is:" + aValue); |
|
1573 } |
|
1574 } |
|
1575 |
|
1576 private void checkNumDaysInMonth(Integer aYear, Integer aMonth, Integer aDay) { |
|
1577 if (hasYearMonthDay(aYear, aMonth, aDay) && aDay > getNumDaysInMonth(aYear, aMonth)) { |
|
1578 throw new ItemOutOfRange("The day-of-the-month value '" + aDay + "' exceeds the number of days in the month: " + getNumDaysInMonth(aYear, aMonth)); |
|
1579 } |
|
1580 } |
|
1581 |
|
1582 private void parseDateTimeText() { |
|
1583 DateTimeParser parser = new DateTimeParser(); |
|
1584 DateTime dateTime = parser.parse(fDateTime); |
|
1585 /* |
|
1586 * This is unusual - we essentially copy from one object to another. This could be |
|
1587 * avoided by building another interface, But defining a top-level interface for this |
|
1588 * simple task is too high a price. |
|
1589 */ |
|
1590 fYear = dateTime.fYear; |
|
1591 fMonth = dateTime.fMonth; |
|
1592 fDay = dateTime.fDay; |
|
1593 fHour = dateTime.fHour; |
|
1594 fMinute = dateTime.fMinute; |
|
1595 fSecond = dateTime.fSecond; |
|
1596 fNanosecond = dateTime.fNanosecond; |
|
1597 validateState(); |
|
1598 } |
|
1599 |
|
1600 private boolean hasYearMonthDay(Integer aYear, Integer aMonth, Integer aDay) { |
|
1601 return isPresent(aYear, aMonth, aDay); |
|
1602 } |
|
1603 |
|
1604 private static boolean isLeapYear(Integer aYear) { |
|
1605 boolean result = false; |
|
1606 if (aYear % 100 == 0) { |
|
1607 // this is a century year |
|
1608 if (aYear % 400 == 0) { |
|
1609 result = true; |
|
1610 } |
|
1611 } |
|
1612 else if (aYear % 4 == 0) { |
|
1613 result = true; |
|
1614 } |
|
1615 return result; |
|
1616 } |
|
1617 |
|
1618 private Object[] getSignificantFields() { |
|
1619 return new Object[]{fYear, fMonth, fDay, fHour, fMinute, fSecond, fNanosecond}; |
|
1620 } |
|
1621 |
|
1622 private void addToString(String aName, Object aValue, StringBuilder aBuilder) { |
|
1623 aBuilder.append(aName + ":" + String.valueOf(aValue) + " "); |
|
1624 } |
|
1625 |
|
1626 /** Return true only if all the given arguments are non-null. */ |
|
1627 private boolean isPresent(Object... aItems) { |
|
1628 boolean result = true; |
|
1629 for (Object item : aItems) { |
|
1630 if (item == null) { |
|
1631 result = false; |
|
1632 break; |
|
1633 } |
|
1634 } |
|
1635 return result; |
|
1636 } |
|
1637 |
|
1638 private DateTime getStartEndDateTime(Integer aDay, Integer aHour, Integer aMinute, Integer aSecond, Integer aNanosecond) { |
|
1639 ensureHasYearMonthDay(); |
|
1640 return new DateTime(fYear, fMonth, aDay, aHour, aMinute, aSecond, aNanosecond); |
|
1641 } |
|
1642 |
|
1643 private String calcToStringFormat(){ |
|
1644 String result = null; //caller will check for this; null means the set of units is bizarre |
|
1645 if(unitsAllPresent(Unit.YEAR) && unitsAllAbsent(Unit.MONTH, Unit.DAY, Unit.HOUR, Unit.MINUTE, Unit.SECOND, Unit.NANOSECONDS)){ |
|
1646 result = "YYYY"; |
|
1647 } |
|
1648 else if (unitsAllPresent(Unit.YEAR, Unit.MONTH) && unitsAllAbsent(Unit.DAY, Unit.HOUR, Unit.MINUTE, Unit.SECOND, Unit.NANOSECONDS)){ |
|
1649 result = "YYYY-MM"; |
|
1650 } |
|
1651 else if (unitsAllPresent(Unit.YEAR, Unit.MONTH, Unit.DAY) && unitsAllAbsent(Unit.HOUR, Unit.MINUTE, Unit.SECOND, Unit.NANOSECONDS)){ |
|
1652 result = "YYYY-MM-DD"; |
|
1653 } |
|
1654 else if (unitsAllPresent(Unit.YEAR, Unit.MONTH, Unit.DAY, Unit.HOUR) && unitsAllAbsent(Unit.MINUTE, Unit.SECOND, Unit.NANOSECONDS)){ |
|
1655 result = "YYYY-MM-DD hh"; |
|
1656 } |
|
1657 else if (unitsAllPresent(Unit.YEAR, Unit.MONTH, Unit.DAY, Unit.HOUR, Unit.MINUTE) && unitsAllAbsent(Unit.SECOND, Unit.NANOSECONDS)){ |
|
1658 result = "YYYY-MM-DD hh:mm"; |
|
1659 } |
|
1660 else if (unitsAllPresent(Unit.YEAR, Unit.MONTH, Unit.DAY, Unit.HOUR, Unit.MINUTE, Unit.SECOND) && unitsAllAbsent(Unit.NANOSECONDS)){ |
|
1661 result = "YYYY-MM-DD hh:mm:ss"; |
|
1662 } |
|
1663 else if (unitsAllPresent(Unit.YEAR, Unit.MONTH, Unit.DAY, Unit.HOUR, Unit.MINUTE, Unit.SECOND, Unit.NANOSECONDS)){ |
|
1664 result = "YYYY-MM-DD hh:mm:ss.fffffffff"; |
|
1665 } |
|
1666 else if (unitsAllAbsent(Unit.YEAR, Unit.MONTH, Unit.DAY) && unitsAllPresent(Unit.HOUR, Unit.MINUTE, Unit.SECOND, Unit.NANOSECONDS)){ |
|
1667 result = "hh:mm:ss.fffffffff"; |
|
1668 } |
|
1669 else if (unitsAllAbsent(Unit.YEAR, Unit.MONTH, Unit.DAY, Unit.NANOSECONDS) && unitsAllPresent(Unit.HOUR, Unit.MINUTE, Unit.SECOND)){ |
|
1670 result = "hh:mm:ss"; |
|
1671 } |
|
1672 else if (unitsAllAbsent(Unit.YEAR, Unit.MONTH, Unit.DAY, Unit.SECOND, Unit.NANOSECONDS) && unitsAllPresent(Unit.HOUR, Unit.MINUTE)){ |
|
1673 result = "hh:mm"; |
|
1674 } |
|
1675 return result; |
|
1676 } |
|
1677 |
|
1678 /** |
|
1679 Always treat de-serialization as a full-blown constructor, by |
|
1680 validating the final state of the de-serialized object. |
|
1681 */ |
|
1682 private void readObject(ObjectInputStream aInputStream) throws ClassNotFoundException, IOException { |
|
1683 //always perform the default de-serialization first |
|
1684 aInputStream.defaultReadObject(); |
|
1685 //no mutable fields in this case |
|
1686 validateState(); |
|
1687 } |
|
1688 |
|
1689 /** |
|
1690 This is the default implementation of writeObject. |
|
1691 Customise if necessary. |
|
1692 */ |
|
1693 private void writeObject(ObjectOutputStream aOutputStream) throws IOException { |
|
1694 //perform the default serialization for all non-transient, non-static fields |
|
1695 aOutputStream.defaultWriteObject(); |
|
1696 } |
|
1697 |
|
1698 } |