001 package hirondelle.web4j.model;
002
003 import hirondelle.web4j.BuildImpl;
004 import hirondelle.web4j.action.ActionImpl;
005 import hirondelle.web4j.model.ModelUtil.NullsGo;
006 import hirondelle.web4j.request.TimeZoneSource;
007 import hirondelle.web4j.util.TimeSource;
008 import hirondelle.web4j.util.Util;
009
010 import java.io.IOException;
011 import java.io.ObjectInputStream;
012 import java.io.ObjectOutputStream;
013 import java.io.Serializable;
014 import java.util.Calendar;
015 import java.util.GregorianCalendar;
016 import java.util.List;
017 import java.util.Locale;
018 import java.util.TimeZone;
019
020 /**
021 Building block class for an immutable date-time, with no time zone.
022
023 <P>
024 This class is provided as an alternative to java.util.{@link java.util.Date}.
025 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.
026
027 <P>This class can hold :
028 <ul>
029 <li>a date-and-time : <tt>1958-03-31 18:59:56.123456789</tt>
030 <li>a date only : <tt>1958-03-31</tt>
031 <li>a time only : <tt>18:59:56.123456789</tt>
032 </ul>
033
034 <P>
035 <a href='#Examples'>Examples</a><br>
036 <a href='#JustificationForThisClass'>Justification For This Class</a><br>
037 <a href='#DatesAndTimesInGeneral'>Dates and Times In General</a><br>
038 <a href='#TheApproachUsedByThisClass'>The Approach Used By This Class</a><br>
039 <a href='#TwoSetsOfOperations'>Two Sets Of Operations</a><br>
040 <a href='#ParsingDateTimeAcceptedFormats'>Parsing DateTime - Accepted Formats</a><br>
041 <a href='#FormattingLanguage'>Mini-Language for Formatting</a><br>
042 <a href='#InteractionWithTimeSource'>Interaction with {@link TimeSource}</a><br>
043 <a href='#PassingDateTimeToTheDatabase'>Passing DateTime Objects to the Database</a>
044
045 <a name='Examples'></a>
046 <h3> Examples</h3>
047 Some quick examples of using this class :
048 <PRE>
049 DateTime dateAndTime = new DateTime("2010-01-19 23:59:59");
050 //highest precision is nanosecond, not millisecond:
051 DateTime dateAndTime = new DateTime("2010-01-19 23:59:59.123456789");
052
053 DateTime dateOnly = new DateTime("2010-01-19");
054 DateTime timeOnly = new DateTime("23:59:59");
055
056 DateTime dateOnly = DateTime.forDateOnly(2010,01,19);
057 DateTime timeOnly = DateTime.forTimeOnly(23,59,59,0);
058
059 DateTime dt = new DateTime("2010-01-15 13:59:15");
060 boolean leap = dt.isLeapYear(); //false
061 dt.getNumDaysInMonth(); //31
062 dt.getStartOfMonth(); //2010-01-01, 00:00:00
063 dt.getEndOfDay(); //2010-01-15, 23:59:59
064 dt.format("YYYY-MM-DD"); //formats as '2010-01-15'
065 dt.plusDays(30); //30 days after Jan 15
066 dt.numDaysFrom(someDate); //returns an int
067 dueDate.lt(someDate); //less-than
068 dueDate.lteq(someDate); //less-than-or-equal-to
069
070 //{@link ActionImpl#getTimeZone()} is readily available in most Actions
071 DateTime.now(getTimeZone());
072 DateTime.today(getTimeZone());
073 DateTime fromMilliseconds = DateTime.forInstant(31313121L, getTimeZone());
074 birthday.isInFuture(getTimeZone());
075 </PRE>
076
077 <a name='JustificationForThisClass'></a>
078 <h3> Justification For This Class</h3>
079 The fundamental reasons why this class exists are :
080 <ul>
081 <li>to avoid the embarrassing number of distasteful inadequacies in the JDK's date classes
082 <li>to oppose the very "mental model" of the JDK's date-time classes with something significantly simpler
083 </ul>
084
085 <a name='MentalModels'></a>
086 <P><b>There are 2 distinct mental models for date-times, and they don't play well together</b> :
087 <ul>
088 <li><b>timeline</b> - an instant on the timeline, as a physicist would picture it, representing the number of
089 seconds from some epoch. In this picture, such a date-time can have many, many different
090 representations according to calendar and time zone. That is, the date-time, <i> as seen and understood by
091 the end user</i>, can change according to "who's looking at it". It's important to understand that a timeline instant,
092 before being presented to the user, <i>must always have an associated time zone - even in the case of
093 a date only, with no time.</i>
094 <li><b>everyday</b> - a date-time in the Gregorian calendar, such as '2009-05-25 18:25:00',
095 which never changes according to "who's looking at it". Here, <i>the time zone is always both implicit and immutable</i>.
096 </ul>
097
098 <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>
099 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>The full date format expected by this class is <tt>'YYYY-MM-YY hh:mm:ss.fffffffff'</tt>.
406 All fields except for the fraction of a second have a fixed width.
407 In addition, various portions of this format are also accepted by this class.
408
409 <P>All of the following dates can be parsed by this class to make a <tt>DateTime</tt> :
410 <ul>
411 <li><tt>2009-12-31 00:00:00.123456789</tt>
412 <li><tt>2009-12-31T00:00:00.123456789</tt>
413 <li><tt>2009-12-31 00:00:00.12345678</tt>
414 <li><tt>2009-12-31 00:00:00.1234567</tt>
415 <li><tt>2009-12-31 00:00:00.123456</tt>
416 <li><tt>2009-12-31 23:59:59.12345</tt>
417 <li><tt>2009-01-31 16:01:01.1234</tt>
418 <li><tt>2009-01-01 16:59:00.123</tt>
419 <li><tt>2009-01-01 16:00:01.12</tt>
420 <li><tt>2009-02-28 16:25:17.1</tt>
421 <li><tt>2009-01-01 00:01:01</tt>
422 <li><tt>2009-01-01 16:01</tt>
423 <li><tt>2009-01-01 16</tt>
424 <li><tt>2009-01-01</tt>
425 <li><tt>2009-01</tt>
426 <li><tt>2009</tt>
427 <li><tt>0009</tt>
428 <li><tt>9</tt>
429 <li><tt>00:00:00.123456789</tt>
430 <li><tt>00:00:00.12345678</tt>
431 <li><tt>00:00:00.1234567</tt>
432 <li><tt>00:00:00.123456</tt>
433 <li><tt>23:59:59.12345</tt>
434 <li><tt>01:59:59.1234</tt>
435 <li><tt>23:01:59.123</tt>
436 <li><tt>00:00:00.12</tt>
437 <li><tt>00:59:59.1</tt>
438 <li><tt>23:59:00</tt>
439 <li><tt>23:00:10</tt>
440 <li><tt>00:59</tt>
441 </ul>
442
443 <P>The range of each field is :
444 <ul>
445 <li>year: 1..9999 (leading zeroes are optional)
446 <li>month: 01..12
447 <li>day: 01..31
448 <li>hour: 00..23
449 <li>minute: 00..59
450 <li>second: 00..59
451 <li>nanosecond: 0..999999999
452 </ul>
453
454 <P>Note that <b>database format functions</b> are an option when dealing with date formats.
455 Since your application is always in control of the SQL used to talk to the database, you can, if needed, usually
456 use database format functions to alter the format of dates returned in a <tt>ResultSet</tt>.
457 */
458 public DateTime(String aDateTime) {
459 fIsAlreadyParsed = false;
460 if (aDateTime == null) {
461 throw new IllegalArgumentException("String passed to DateTime constructor is null. You can use an empty string, but not a null reference.");
462 }
463 fDateTime = aDateTime;
464 }
465
466 /**
467 Constructor taking each time unit explicitly.
468
469 <P>Although all parameters are optional, many operations on this class require year-month-day to be
470 present.
471
472 @param aYear 1..9999, optional
473 @param aMonth 1..12 , optional
474 @param aDay 1..31, cannot exceed the number of days in the given month/year, optional
475 @param aHour 0..23, optional
476 @param aMinute 0..59, optional
477 @param aSecond 0..59, optional
478 @param aNanoseconds 0..999,999,999, optional (allows for databases that store timestamps up to
479 nanosecond precision).
480 */
481 public DateTime(Integer aYear, Integer aMonth, Integer aDay, Integer aHour, Integer aMinute, Integer aSecond, Integer aNanoseconds) {
482 fIsAlreadyParsed = true;
483 fYear = aYear;
484 fMonth = aMonth;
485 fDay = aDay;
486 fHour = aHour;
487 fMinute = aMinute;
488 fSecond = aSecond;
489 fNanosecond = aNanoseconds;
490 validateState();
491 }
492
493 /**
494 Factory method returns a <tt>DateTime</tt> having year-month-day only, with no time portion.
495 <P>See {@link #DateTime(Integer, Integer, Integer, Integer, Integer, Integer, Integer)} for constraints on the parameters.
496 */
497 public static DateTime forDateOnly(Integer aYear, Integer aMonth, Integer aDay) {
498 return new DateTime(aYear, aMonth, aDay, null, null, null, null);
499 }
500
501 /**
502 Factory method returns a <tt>DateTime</tt> having hour-minute-second-nanosecond only, with no date portion.
503 <P>See {@link #DateTime(Integer, Integer, Integer, Integer, Integer, Integer, Integer)} for constraints on the parameters.
504 */
505 public static DateTime forTimeOnly(Integer aHour, Integer aMinute, Integer aSecond, Integer aNanoseconds) {
506 return new DateTime(null, null, null, aHour, aMinute, aSecond, aNanoseconds);
507 }
508
509 /**
510 Constructor taking a millisecond value and a {@link TimeZone}.
511 This constructor may be use to convert a <tt>java.util.Date</tt> into a <tt>DateTime</tt>.
512
513 <P>Unfortunately, only millisecond precision is possible for this method.
514
515 @param aMilliseconds must be in the range corresponding to the range of dates supported by this class (year 1..9999); corresponds
516 to a millisecond instant on the timeline, measured from the epoch used by {@link java.util.Date}.
517 */
518 public static DateTime forInstant(long aMilliseconds, TimeZone aTimeZone) {
519 Calendar calendar = new GregorianCalendar(aTimeZone);
520 calendar.setTimeInMillis(aMilliseconds);
521 int year = calendar.get(Calendar.YEAR);
522 int month = calendar.get(Calendar.MONTH) + 1; // 0-based
523 int day = calendar.get(Calendar.DAY_OF_MONTH);
524 int hour = calendar.get(Calendar.HOUR_OF_DAY); // 0..23
525 int minute = calendar.get(Calendar.MINUTE);
526 int second = calendar.get(Calendar.SECOND);
527 int milliseconds = calendar.get(Calendar.MILLISECOND);
528 int nanoseconds = milliseconds * 1000 * 1000;
529 return new DateTime(year, month, day, hour, minute, second, nanoseconds);
530 }
531
532 /**
533 For the given time zone, return the corresponding time in milliseconds for this <tt>DateTime</tt>.
534
535 <P>This method is meant to help you convert between a <tt>DateTime</tt> and the
536 JDK's date-time classes, which are based on the combination of a time zone and a
537 millisecond value from the Java epoch.
538 <P>Since <tt>DateTime</tt> can go to nanosecond accuracy, the return value can
539 lose precision. The nanosecond value is truncated to milliseconds, not rounded.
540 <P>Requires year-month-day to be present; if not, a runtime exception is thrown.
541 */
542 public long getMilliseconds(TimeZone aTimeZone){
543 Integer year = getYear();
544 Integer month = getMonth();
545 Integer day = getDay();
546 //coerce missing times to 0:
547 Integer hour = getHour() == null ? 0 : getHour();
548 Integer minute = getMinute() == null ? 0 : getMinute();
549 Integer second = getSecond() == null ? 0 : getSecond();
550 Integer nanos = getNanoseconds() == null ? 0 : getNanoseconds();
551
552 Calendar calendar = new GregorianCalendar(aTimeZone);
553 calendar.set(Calendar.YEAR, year);
554 calendar.set(Calendar.MONTH, month-1); // 0-based
555 calendar.set(Calendar.DAY_OF_MONTH, day);
556 calendar.set(Calendar.HOUR_OF_DAY, hour); // 0..23
557 calendar.set(Calendar.MINUTE, minute);
558 calendar.set(Calendar.SECOND, second);
559 calendar.set(Calendar.MILLISECOND, nanos/1000000);
560
561 return calendar.getTimeInMillis();
562 }
563
564 /**
565 Return the raw date-time String passed to the {@link #DateTime(String)} constructor.
566 Returns <tt>null</tt> if that constructor was not called. See {@link #toString()} as well.
567 */
568 public String getRawDateString() {
569 return fDateTime;
570 }
571
572 /** Return the year, 1..9999. */
573 public Integer getYear() {
574 ensureParsed();
575 return fYear;
576 }
577
578 /** Return the Month, 1..12. */
579 public Integer getMonth() {
580 ensureParsed();
581 return fMonth;
582 }
583
584 /** Return the Day of the Month, 1..31. */
585 public Integer getDay() {
586 ensureParsed();
587 return fDay;
588 }
589
590 /** Return the Hour, 0..23. */
591 public Integer getHour() {
592 ensureParsed();
593 return fHour;
594 }
595
596 /** Return the Minute, 0..59. */
597 public Integer getMinute() {
598 ensureParsed();
599 return fMinute;
600 }
601
602 /** Return the Second, 0..59. */
603 public Integer getSecond() {
604 ensureParsed();
605 return fSecond;
606 }
607
608 /** Return the Nanosecond, 0..999999999. */
609 public Integer getNanoseconds() {
610 ensureParsed();
611 return fNanosecond;
612 }
613
614 /**
615 Return the Modified Julian Day Number.
616 <P>The Modified Julian Day Number is defined by astronomers for simplifying the calculation of the number of days between 2 dates.
617 Returns a monotonically increasing sequence number.
618 Day 0 is November 17, 1858 00:00:00 (whose Julian Date was 2400000.5).
619
620 <P>Using the Modified Julian Day Number instead of the Julian Date has 2 advantages:
621 <ul>
622 <li>it's a smaller number
623 <li>it starts at midnight, not noon (Julian Date starts at noon)
624 </ul>
625
626 <P>Does not reflect any time portion, if present.
627
628 <P>(In spite of its name, this method, like all other methods in this class, uses the
629 proleptic Gregorian calendar - not the Julian calendar.)
630
631 <P>Requires year-month-day to be present; if not, a runtime exception is thrown.
632 */
633 public Integer getModifiedJulianDayNumber() {
634 ensureHasYearMonthDay();
635 int result = calculateJulianDayNumberAtNoon() - 1 - EPOCH_MODIFIED_JD;
636 return result;
637 }
638
639 /**
640 Return an index for the weekday for this <tt>DateTime</tt>.
641 Returns 1..7 for Sunday..Saturday.
642 <P>Requires year-month-day to be present; if not, a runtime exception is thrown.
643 */
644 public Integer getWeekDay() {
645 ensureHasYearMonthDay();
646 int dayNumber = calculateJulianDayNumberAtNoon() + 1;
647 int index = dayNumber % 7;
648 return index + 1;
649 }
650
651 /**
652 Return an integer in the range 1..366, representing a count of the number of days from the start of the year.
653 January 1 is counted as day 1.
654 <P>Requires year-month-day to be present; if not, a runtime exception is thrown.
655 */
656 public Integer getDayOfYear() {
657 ensureHasYearMonthDay();
658 int k = isLeapYear() ? 1 : 2;
659 Integer result = ((275 * fMonth) / 9) - k * ((fMonth + 9) / 12) + fDay - 30; // integer division
660 return result;
661 }
662
663 /**
664 Returns true only if the year is a leap year.
665 <P>Requires year to be present; if not, a runtime exception is thrown.
666 */
667 public Boolean isLeapYear() {
668 ensureParsed();
669 Boolean result = null;
670 if (isPresent(fYear)) {
671 result = isLeapYear(fYear);
672 }
673 else {
674 throw new MissingItem("Year is absent. Cannot determine if leap year.");
675 }
676 return result;
677 }
678
679 /**
680 Return the number of days in the month which holds this <tt>DateTime</tt>.
681 <P>Requires year-month-day to be present; if not, a runtime exception is thrown.
682 */
683 public int getNumDaysInMonth() {
684 ensureHasYearMonthDay();
685 return getNumDaysInMonth(fYear, fMonth);
686 }
687
688 /**
689 Return The week index of this <tt>DateTime</tt> with respect to a given starting <tt>DateTime</tt>.
690 <P>The single parameter to this method defines first day of week number 1.
691 See {@link #getWeekIndex()} as well.
692 <P>Requires year-month-day to be present; if not, a runtime exception is thrown.
693 */
694 public Integer getWeekIndex(DateTime aStartingFromDate) {
695 ensureHasYearMonthDay();
696 aStartingFromDate.ensureHasYearMonthDay();
697 int diff = getModifiedJulianDayNumber() - aStartingFromDate.getModifiedJulianDayNumber();
698 return (diff / 7) + 1; // integer division
699 }
700
701 /**
702 Return The week index of this <tt>DateTime</tt>, taking day 1 of week 1 as Sunday, January 2, 2000.
703 <P>See {@link #getWeekIndex(DateTime)} as well, which takes an arbitrary date to define
704 day 1 of week 1.
705 <P>Requires year-month-day to be present; if not, a runtime exception is thrown.
706 */
707 public Integer getWeekIndex() {
708 DateTime start = DateTime.forDateOnly(2000, 1, 2);
709 return getWeekIndex(start);
710 }
711
712 /**
713 Return <tt>true</tt> only if this <tt>DateTime</tt> has the same year-month-day as the given parameter.
714 Time is ignored by this method.
715 <P> Requires year-month-day to be present, both for this <tt>DateTime</tt> and for
716 <tt>aThat</tt>; if not, a runtime exception is thrown.
717 */
718 public boolean isSameDayAs(DateTime aThat) {
719 boolean result = false;
720 ensureHasYearMonthDay();
721 aThat.ensureHasYearMonthDay();
722 result = (fYear.equals(aThat.fYear) && fMonth.equals(aThat.fMonth) && fDay.equals(aThat.fDay));
723 return result;
724 }
725
726 /**
727 'Less than' comparison.
728 Return <tt>true</tt> only if this <tt>DateTime</tt> comes before the given parameter, according to {@link #compareTo(DateTime)}.
729 */
730 public boolean lt(DateTime aThat) {
731 return compareTo(aThat) < EQUAL;
732 }
733
734 /**
735 'Less than or equal to' comparison.
736 Return <tt>true</tt> only if this <tt>DateTime</tt> comes before the given parameter, according to {@link #compareTo(DateTime)},
737 or this <tt>DateTime</tt> equals the given parameter.
738 */
739 public boolean lteq(DateTime aThat) {
740 return compareTo(aThat) < EQUAL || equals(aThat);
741 }
742
743 /**
744 'Greater than' comparison.
745 Return <tt>true</tt> only if this <tt>DateTime</tt> comes after the given parameter, according to {@link #compareTo(DateTime)}.
746 */
747 public boolean gt(DateTime aThat) {
748 return compareTo(aThat) > EQUAL;
749 }
750
751 /**
752 'Greater than or equal to' comparison.
753 Return <tt>true</tt> only if this <tt>DateTime</tt> comes after the given parameter, according to {@link #compareTo(DateTime)},
754 or this <tt>DateTime</tt> equals the given parameter.
755 */
756 public boolean gteq(DateTime aThat) {
757 return compareTo(aThat) > EQUAL || equals(aThat);
758 }
759
760 /** Return the smallest non-null time unit encapsulated by this <tt>DateTime</tt>. */
761 public Unit getPrecision() {
762 ensureParsed();
763 Unit result = null;
764 if (isPresent(fNanosecond)) {
765 result = Unit.NANOSECONDS;
766 }
767 else if (isPresent(fSecond)) {
768 result = Unit.SECOND;
769 }
770 else if (isPresent(fMinute)) {
771 result = Unit.MINUTE;
772 }
773 else if (isPresent(fHour)) {
774 result = Unit.HOUR;
775 }
776 else if (isPresent(fDay)) {
777 result = Unit.DAY;
778 }
779 else if (isPresent(fMonth)) {
780 result = Unit.MONTH;
781 }
782 else if (isPresent(fYear)) {
783 result = Unit.YEAR;
784 }
785 return result;
786 }
787
788 /**
789 Truncate this <tt>DateTime</tt> to the given precision.
790 <P>The return value will have all items lower than the given precision simply set to
791 <tt>null</tt>. In addition, the return value will not include any date-time String passed to the
792 {@link #DateTime(String)} constructor.
793
794 @param aPrecision takes any value <i>except</i> {@link Unit#NANOSECONDS} (since it makes no sense to truncate to the highest
795 available precision).
796 */
797 public DateTime truncate(Unit aPrecision) {
798 ensureParsed();
799 DateTime result = null;
800 if (Unit.NANOSECONDS == aPrecision) {
801 throw new IllegalArgumentException("It makes no sense to truncate to nanosecond precision, since that's the highest precision available.");
802 }
803 else if (Unit.SECOND == aPrecision) {
804 result = new DateTime(fYear, fMonth, fDay, fHour, fMinute, fSecond, null);
805 }
806 else if (Unit.MINUTE == aPrecision) {
807 result = new DateTime(fYear, fMonth, fDay, fHour, fMinute, null, null);
808 }
809 else if (Unit.HOUR == aPrecision) {
810 result = new DateTime(fYear, fMonth, fDay, fHour, null, null, null);
811 }
812 else if (Unit.DAY == aPrecision) {
813 result = new DateTime(fYear, fMonth, fDay, null, null, null, null);
814 }
815 else if (Unit.MONTH == aPrecision) {
816 result = new DateTime(fYear, fMonth, null, null, null, null, null);
817 }
818 else if (Unit.YEAR == aPrecision) {
819 result = new DateTime(fYear, null, null, null, null, null, null);
820 }
821 return result;
822 }
823
824 /**
825 Return <tt>true</tt> only if all of the given units are present in this <tt>DateTime</tt>.
826 If a unit is <i>not</i> included in the argument list, then no test is made for its presence or absence
827 in this <tt>DateTime</tt> by this method.
828 */
829 public boolean unitsAllPresent(Unit... aUnits) {
830 boolean result = true;
831 ensureParsed();
832 for (Unit unit : aUnits) {
833 if (Unit.NANOSECONDS == unit) {
834 result = result && fNanosecond != null;
835 }
836 else if (Unit.SECOND == unit) {
837 result = result && fSecond != null;
838 }
839 else if (Unit.MINUTE == unit) {
840 result = result && fMinute != null;
841 }
842 else if (Unit.HOUR == unit) {
843 result = result && fHour != null;
844 }
845 else if (Unit.DAY == unit) {
846 result = result && fDay != null;
847 }
848 else if (Unit.MONTH == unit) {
849 result = result && fMonth != null;
850 }
851 else if (Unit.YEAR == unit) {
852 result = result && fYear != null;
853 }
854 }
855 return result;
856 }
857
858 /**
859 Return <tt>true</tt> only if this <tt>DateTime</tt> has a non-null values for year, month, and day.
860 */
861 public boolean hasYearMonthDay() {
862 return unitsAllPresent(Unit.YEAR, Unit.MONTH, Unit.DAY);
863 }
864
865 /**
866 Return <tt>true</tt> only if this <tt>DateTime</tt> has a non-null values for hour, minute, and second.
867 */
868 public boolean hasHourMinuteSecond() {
869 return unitsAllPresent(Unit.HOUR, Unit.MINUTE, Unit.SECOND);
870 }
871
872 /**
873 Return <tt>true</tt> only if all of the given units are absent from this <tt>DateTime</tt>.
874 If a unit is <i>not</i> included in the argument list, then no test is made for its presence or absence
875 in this <tt>DateTime</tt> by this method.
876 */
877 public boolean unitsAllAbsent(Unit... aUnits) {
878 boolean result = true;
879 ensureParsed();
880 for (Unit unit : aUnits) {
881 if (Unit.NANOSECONDS == unit) {
882 result = result && fNanosecond == null;
883 }
884 else if (Unit.SECOND == unit) {
885 result = result && fSecond == null;
886 }
887 else if (Unit.MINUTE == unit) {
888 result = result && fMinute == null;
889 }
890 else if (Unit.HOUR == unit) {
891 result = result && fHour == null;
892 }
893 else if (Unit.DAY == unit) {
894 result = result && fDay == null;
895 }
896 else if (Unit.MONTH == unit) {
897 result = result && fMonth == null;
898 }
899 else if (Unit.YEAR == unit) {
900 result = result && fYear == null;
901 }
902 }
903 return result;
904 }
905
906 /**
907 Return this <tt>DateTime</tt> with the time portion coerced to '00:00:00.000000000'.
908 <P>Requires year-month-day to be present; if not, a runtime exception is thrown.
909 */
910 public DateTime getStartOfDay() {
911 ensureHasYearMonthDay();
912 return getStartEndDateTime(fDay, 0, 0, 0, 0);
913 }
914
915 /**
916 Return this <tt>DateTime</tt> with the time portion coerced to '23:59:59.999999999'.
917 <P>Requires year-month-day to be present; if not, a runtime exception is thrown.
918 */
919 public DateTime getEndOfDay() {
920 ensureHasYearMonthDay();
921 return getStartEndDateTime(fDay, 23, 59, 59, 999999999);
922 }
923
924 /**
925 Return this <tt>DateTime</tt> with the time portion coerced to '00:00:00.000000000',
926 and the day coerced to 1.
927 <P>Requires year-month-day to be present; if not, a runtime exception is thrown.
928 */
929 public DateTime getStartOfMonth() {
930 ensureHasYearMonthDay();
931 return getStartEndDateTime(1, 0, 0, 0, 0);
932 }
933
934 /**
935 Return this <tt>DateTime</tt> with the time portion coerced to '23:59:59.999999999',
936 and the day coerced to the end of the month.
937 <P>Requires year-month-day to be present; if not, a runtime exception is thrown.
938 */
939 public DateTime getEndOfMonth() {
940 ensureHasYearMonthDay();
941 return getStartEndDateTime(getNumDaysInMonth(), 23, 59, 59, 999999999);
942 }
943
944 /**
945 Create a new <tt>DateTime</tt> by adding an interval to this one.
946
947 <P>See {@link #plusDays(Integer)} as well.
948
949 <P>Changes are always applied by this class <i>in order of decreasing units of time</i>:
950 years first, then months, and so on. After changing both the year and month, a check on the month-day combination is made before
951 any change is made to the day. If the day exceeds the number of days in the given month/year, then
952 (and only then) the given {@link DayOverflow} policy applied, and the day-of-the-month is adusted accordingly.
953
954 <P>Afterwards, the day is then changed in the usual way, followed by the remaining items (hour, minute, and second).
955 Changes to the fractional seconds are not included in this method, since there doesn't seem to be much practical use for it.
956
957 <P>The returned value cannot come after <tt>9999-12-13 23:59:59</tt>.
958
959 <P>This class works with <tt>DateTime</tt>'s having the following items present :
960 <ul>
961 <li>year-month-day and hour-minute-second (and optional nanoseconds)
962 <li>year-month-day only. In this case, if a calculation with a time part is performed, that time part
963 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.
964 <li>hour-minute-second (and optional nanoseconds) only. In this case, the calculation is done starting with the
965 the arbitrary date <tt>0001-01-01</tt> (in order to remain within a valid state space of <tt>DateTime</tt>).
966 </ul>
967
968 @param aNumYears positive, required, in range 0...9999
969 @param aNumMonths positive, required, in range 0...9999
970 @param aNumDays positive, required, in range 0...9999
971 @param aNumHours positive, required, in range 0...9999
972 @param aNumMinutes positive, required, in range 0...9999
973 @param aNumSeconds positive, required, in range 0...9999
974 */
975 public DateTime plus(Integer aNumYears, Integer aNumMonths, Integer aNumDays, Integer aNumHours, Integer aNumMinutes, Integer aNumSeconds, DayOverflow aDayOverflow) {
976 DateTimeInterval interval = new DateTimeInterval(this, aDayOverflow);
977 return interval.plus(aNumYears, aNumMonths, aNumDays, aNumHours, aNumMinutes, aNumSeconds);
978 }
979
980 /**
981 Create a new <tt>DateTime</tt> by subtracting an interval to this one.
982
983 <P>See {@link #minusDays(Integer)} as well.
984 <P>This method has nearly the same behavior as {@link #plus(Integer, Integer, Integer, Integer, Integer, Integer, DayOverflow)},
985 except that the return value cannot come before <tt>0001-01-01 00:00:00</tt>.
986 */
987 public DateTime minus(Integer aNumYears, Integer aNumMonths, Integer aNumDays, Integer aNumHours, Integer aNumMinutes, Integer aNumSeconds, DayOverflow aDayOverflow) {
988 DateTimeInterval interval = new DateTimeInterval(this, aDayOverflow);
989 return interval.minus(aNumYears, aNumMonths, aNumDays, aNumHours, aNumMinutes, aNumSeconds);
990 }
991
992 /**
993 Return a new <tt>DateTime</tt> by adding an integral number of days to this one.
994
995 <P>Requires year-month-day to be present; if not, a runtime exception is thrown.
996 @param aNumDays can be either sign; if negative, then the days are subtracted.
997 */
998 public DateTime plusDays(Integer aNumDays) {
999 ensureHasYearMonthDay();
1000 int thisJDAtNoon = getModifiedJulianDayNumber() + 1 + EPOCH_MODIFIED_JD;
1001 int resultJD = thisJDAtNoon + aNumDays;
1002 DateTime datePortion = fromJulianDayNumberAtNoon(resultJD);
1003 return new DateTime(datePortion.getYear(), datePortion.getMonth(), datePortion.getDay(), fHour, fMinute, fSecond, fNanosecond);
1004 }
1005
1006 /**
1007 Return a new <tt>DateTime</tt> by subtracting an integral number of days from this one.
1008
1009 <P>Requires year-month-day to be present; if not, a runtime exception is thrown.
1010 @param aNumDays can be either sign; if negative, then the days are added.
1011 */
1012 public DateTime minusDays(Integer aNumDays) {
1013 return plusDays(-1 * aNumDays);
1014 }
1015
1016 /**
1017 The whole number of days between this <tt>DateTime</tt> and the given parameter.
1018 <P>Requires year-month-day to be present, both for this <tt>DateTime</tt> and for the <tt>aThat</tt>
1019 parameter; if not, a runtime exception is thrown.
1020 */
1021 public int numDaysFrom(DateTime aThat) {
1022 return aThat.getModifiedJulianDayNumber() - this.getModifiedJulianDayNumber();
1023 }
1024
1025 /**
1026 The number of seconds between this <tt>DateTime</tt> and the given argument.
1027 <P>If only time information is present in both this <tt>DateTime</tt> and <tt>aThat</tt>, then there are
1028 no restrictions on the values of the time units.
1029 <P>If any date information is present, in either this <tt>DateTime</tt> or <tt>aThat</tt>,
1030 then full year-month-day must be present in both; if not, then the date portion will be ignored, and only the
1031 time portion will contribute to the calculation.
1032 */
1033 public long numSecondsFrom(DateTime aThat) {
1034 long result = 0;
1035 if(hasYearMonthDay() && aThat.hasYearMonthDay()){
1036 result = numDaysFrom(aThat) * 86400; //just the day portion
1037 }
1038 result = result - this.numSecondsInTimePortion() + aThat.numSecondsInTimePortion();
1039 return result;
1040 }
1041
1042 /**
1043 Output this <tt>DateTime</tt> as a formatted String using numbers, with no localizable text.
1044
1045 <P>Example:
1046 <PRE>dt.format("YYYY-MM-DD hh:mm:ss");</PRE>
1047 would generate text of the form
1048 <PRE>2009-09-09 18:23:59</PRE>
1049
1050 <P>If months, weekdays, or AM/PM indicators are output as localizable text, you must use {@link #format(String, Locale)}.
1051 @param aFormat uses the <a href="#FormattingLanguage">formatting mini-language</a> defined in the class comment.
1052 */
1053 public String format(String aFormat) {
1054 DateTimeFormatter format = new DateTimeFormatter(aFormat);
1055 return format.format(this);
1056 }
1057
1058 /**
1059 Output this <tt>DateTime</tt> as a formatted String using numbers and/or localizable text.
1060
1061 <P>This method is intended for alphanumeric output, such as '<tt>Sunday, November 14, 1858 10:00 AM</tt>'.
1062 <P>If months and weekdays are output as numbers, you are encouraged to use {@link #format(String)} instead.
1063
1064 @param aFormat uses the <a href="#FormattingLanguage">formatting mini-language</a> defined in the class comment.
1065 @param aLocale used to generate text for Month, Weekday and AM/PM indicator; required only by patterns which return localized
1066 text, instead of numeric forms.
1067 */
1068 public String format(String aFormat, Locale aLocale) {
1069 DateTimeFormatter format = new DateTimeFormatter(aFormat, aLocale);
1070 return format.format(this);
1071 }
1072
1073 /**
1074 Output this <tt>DateTime</tt> as a formatted String using numbers and explicit text for months, weekdays, and AM/PM indicator.
1075
1076 <P>Use of this method is likely relatively rare; it should be used only if the output of {@link #format(String, Locale)} is
1077 inadequate.
1078
1079 @param aFormat uses the <a href="#FormattingLanguage">formatting mini-language</a> defined in the class comment.
1080 @param aMonths contains text for all 12 months, starting with January; size must be 12.
1081 @param aWeekdays contains text for all 7 weekdays, starting with Sunday; size must be 7.
1082 @param aAmPmIndicators contains text for A.M and P.M. indicators (in that order); size must be 2.
1083 */
1084 public String format(String aFormat, List<String> aMonths, List<String> aWeekdays, List<String> aAmPmIndicators) {
1085 DateTimeFormatter format = new DateTimeFormatter(aFormat, aMonths, aWeekdays, aAmPmIndicators);
1086 return format.format(this);
1087 }
1088
1089 /**
1090 Return the current date-time.
1091 <P>Combines the configured implementation of {@link TimeSource} with the given {@link TimeZone}.
1092 The <tt>TimeZone</tt> will typically come from your implementation of {@link TimeZoneSource}.
1093
1094 <P>In an Action, the current date-time date can be referenced using
1095 <PRE>DateTime.now(getTimeZone())</PRE>
1096 See {@link ActionImpl#getTimeZone()}.
1097
1098 <P>Only millisecond precision is possible for this method.
1099 */
1100 public static DateTime now(TimeZone aTimeZone) {
1101 TimeSource timesource = BuildImpl.forTimeSource();
1102 return forInstant(timesource.currentTimeMillis(), aTimeZone);
1103 }
1104
1105 /**
1106 Return the current date.
1107 <P>As in {@link #now(TimeZone)}, but truncates the time portion, leaving only year-month-day.
1108 <P>In an Action, today's date can be referenced using
1109 <PRE>DateTime.today(getTimeZone())</PRE>
1110 See {@link ActionImpl#getTimeZone()}.
1111 */
1112 public static DateTime today(TimeZone aTimeZone) {
1113 DateTime result = now(aTimeZone);
1114 return result.truncate(Unit.DAY);
1115 }
1116
1117 /** Return <tt>true</tt> only if this date is in the future, with respect to {@link #now(TimeZone)}. */
1118 public boolean isInTheFuture(TimeZone aTimeZone) {
1119 return now(aTimeZone).lt(this);
1120 }
1121
1122 /** Return <tt>true</tt> only if this date is in the past, with respect to {@link #now(TimeZone)}. */
1123 public boolean isInThePast(TimeZone aTimeZone) {
1124 return now(aTimeZone).gt(this);
1125 }
1126
1127 /**
1128 Return a <tt>DateTime</tt> corresponding to a change from one {@link TimeZone} to another.
1129
1130 <P>A <tt>DateTime</tt> object has an implicit and immutable time zone.
1131 If you need to change the implicit time zone, you can use this method to do so.
1132
1133 <P>Example :
1134 <PRE>
1135 TimeZone fromUK = TimeZone.getTimeZone("Europe/London");
1136 TimeZone toIndonesia = TimeZone.getTimeZone("Asia/Jakarta");
1137 DateTime newDt = oldDt.changeTimeZone(fromUK, toIndonesia);
1138 </PRE>
1139
1140 <P>Requires year-month-day-hour to be present; if not, a runtime exception is thrown.
1141 @param aFromTimeZone the implicit time zone of this object.
1142 @param aToTimeZone the implicit time zone of the <tt>DateTime</tt> returned by this method.
1143 @return aDateTime corresponding to the change of time zone implied by the 2 parameters.
1144 */
1145 public DateTime changeTimeZone(TimeZone aFromTimeZone, TimeZone aToTimeZone){
1146 DateTime result = null;
1147 ensureHasYearMonthDay();
1148 if (unitsAllAbsent(Unit.HOUR)){
1149 throw new IllegalArgumentException("DateTime does not include the hour. Cannot change the time zone if no hour is present.");
1150 }
1151 Calendar fromDate = new GregorianCalendar(aFromTimeZone);
1152 fromDate.set(Calendar.YEAR, getYear());
1153 fromDate.set(Calendar.MONTH, getMonth()-1);
1154 fromDate.set(Calendar.DAY_OF_MONTH, getDay());
1155 fromDate.set(Calendar.HOUR_OF_DAY, getHour());
1156 if(getMinute() != null) {
1157 fromDate.set(Calendar.MINUTE, getMinute());
1158 }
1159 else {
1160 fromDate.set(Calendar.MINUTE, 0);
1161 }
1162 //other items zeroed out here, since they don't matter for time zone calculations
1163 fromDate.set(Calendar.SECOND, 0);
1164 fromDate.set(Calendar.MILLISECOND, 0);
1165
1166 //millisecond precision is OK here, since the seconds/nanoseconds are not part of the calc
1167 Calendar toDate = new GregorianCalendar(aToTimeZone);
1168 toDate.setTimeInMillis(fromDate.getTimeInMillis());
1169 //needed if this date has hour, but no minute (bit of an oddball case) :
1170 Integer minute = getMinute() != null ? toDate.get(Calendar.MINUTE) : null;
1171 result = new DateTime(
1172 toDate.get(Calendar.YEAR), toDate.get(Calendar.MONTH) + 1, toDate.get(Calendar.DAY_OF_MONTH),
1173 toDate.get(Calendar.HOUR_OF_DAY), minute, getSecond(), getNanoseconds()
1174 );
1175 return result;
1176 }
1177
1178 /**
1179 Compare this object to another, for ordering purposes.
1180 <P> Uses the 7 date-time elements (year..nanosecond). The Year is considered the most
1181 significant item, and the Nanosecond the least significant item. Null items are placed first in this comparison.
1182 */
1183 public int compareTo(DateTime aThat) {
1184 if (this == aThat) return EQUAL;
1185 ensureParsed();
1186 aThat.ensureParsed();
1187
1188 NullsGo nullsGo = NullsGo.FIRST;
1189 int comparison = ModelUtil.comparePossiblyNull(this.fYear, aThat.fYear, nullsGo);
1190 if (comparison != EQUAL) return comparison;
1191
1192 comparison = ModelUtil.comparePossiblyNull(this.fMonth, aThat.fMonth, nullsGo);
1193 if (comparison != EQUAL) return comparison;
1194
1195 comparison = ModelUtil.comparePossiblyNull(this.fDay, aThat.fDay, nullsGo);
1196 if (comparison != EQUAL) return comparison;
1197
1198 comparison = ModelUtil.comparePossiblyNull(this.fHour, aThat.fHour, nullsGo);
1199 if (comparison != EQUAL) return comparison;
1200
1201 comparison = ModelUtil.comparePossiblyNull(this.fMinute, aThat.fMinute, nullsGo);
1202 if (comparison != EQUAL) return comparison;
1203
1204 comparison = ModelUtil.comparePossiblyNull(this.fSecond, aThat.fSecond, nullsGo);
1205 if (comparison != EQUAL) return comparison;
1206
1207 comparison = ModelUtil.comparePossiblyNull(this.fNanosecond, aThat.fNanosecond, nullsGo);
1208 if (comparison != EQUAL) return comparison;
1209
1210 return EQUAL;
1211 }
1212
1213 /**
1214 Equals method for this object.
1215
1216 <P>Equality is determined by the 7 date-time elements (year..nanosecond).
1217 */
1218 @Override public boolean equals(Object aThat) {
1219 /*
1220 * Implementation note: it was considered branching this method, according to whether
1221 * the objects are already parsed. That was rejected, since maintaining 'synchronicity'
1222 * with hashCode would not then be possible, since hashCode is based only on one object,
1223 * not two.
1224 */
1225 ensureParsed();
1226 Boolean result = ModelUtil.quickEquals(this, aThat);
1227 if (result == null) {
1228 DateTime that = (DateTime)aThat;
1229 that.ensureParsed();
1230 result = ModelUtil.equalsFor(this.getSignificantFields(), that.getSignificantFields());
1231 }
1232 return result;
1233 }
1234
1235 /**
1236 Hash code for this object.
1237
1238 <P> Uses the same 7 date-time elements (year..nanosecond) as used by
1239 {@link #equals(Object)}.
1240 */
1241 @Override public int hashCode() {
1242 if (fHashCode == 0) {
1243 ensureParsed();
1244 fHashCode = ModelUtil.hashCodeFor(getSignificantFields());
1245 }
1246 return fHashCode;
1247 }
1248
1249 /**
1250 Intended for <i>debugging and logging</i> only.
1251
1252 <P><b>To format this <tt>DateTime</tt> for presentation to the user, see the various <tt>format</tt> methods.</b>
1253
1254 <P>If the {@link #DateTime(String)} constructor was called, then return that String.
1255
1256 <P>Otherwise, the return value is constructed from each date-time element, in a fixed format, depending
1257 on which time units are present. Example values :
1258 <ul>
1259 <li>2011-04-30 13:59:59.123456789
1260 <li>2011-04-30 13:59:59
1261 <li>2011-04-30
1262 <li>2011-04-30 13:59
1263 <li>13:59:59.123456789
1264 <li>13:59:59
1265 <li>and so on...
1266 </ul>
1267
1268 <P>In the great majority of cases, this will give reasonable output for debugging and logging statements.
1269
1270 <P>In cases where a bizarre combinations of time units is present, the return value is presented in a verbose form.
1271 For example, if all time units are present <i>except</i> for minutes, the return value has this form:
1272 <PRE>Y:2001 M:1 D:31 h:13 m:null s:59 f:123456789</PRE>
1273 */
1274 @Override public String toString() {
1275 String result = "";
1276 if (Util.textHasContent(fDateTime)) {
1277 result = fDateTime;
1278 }
1279 else {
1280 String format = calcToStringFormat();
1281 if(format != null){
1282 result = format(calcToStringFormat());
1283 }
1284 else {
1285 StringBuilder builder = new StringBuilder();
1286 addToString("Y", fYear, builder);
1287 addToString("M", fMonth, builder);
1288 addToString("D", fDay, builder);
1289 addToString("h", fHour, builder);
1290 addToString("m", fMinute, builder);
1291 addToString("s", fSecond, builder);
1292 addToString("f", fNanosecond, builder);
1293 result = builder.toString().trim();
1294 }
1295 }
1296 return result;
1297 }
1298
1299 // PACKAGE-PRIVATE (for unit testing, mostly)
1300
1301 static final class ItemOutOfRange extends RuntimeException {
1302 ItemOutOfRange(String aMessage) {
1303 super(aMessage);
1304 }
1305 }
1306
1307 static final class MissingItem extends RuntimeException {
1308 MissingItem(String aMessage) {
1309 super(aMessage);
1310 }
1311 }
1312
1313 /** Intended as internal tool, for testing only. Not scope is not public! */
1314 void ensureParsed() {
1315 if (!fIsAlreadyParsed) {
1316 parseDateTimeText();
1317 }
1318 }
1319
1320 /**
1321 Return the number of days in the given month. The returned value depends on the year as
1322 well, because of leap years. Returns <tt>null</tt> if either year or month are
1323 absent. WRONG - should be public??
1324 Package-private, needed for interval calcs.
1325 */
1326 static Integer getNumDaysInMonth(Integer aYear, Integer aMonth) {
1327 Integer result = null;
1328 if (aYear != null && aMonth != null) {
1329 if (aMonth == 1) {
1330 result = 31;
1331 }
1332 else if (aMonth == 2) {
1333 result = isLeapYear(aYear) ? 29 : 28;
1334 }
1335 else if (aMonth == 3) {
1336 result = 31;
1337 }
1338 else if (aMonth == 4) {
1339 result = 30;
1340 }
1341 else if (aMonth == 5) {
1342 result = 31;
1343 }
1344 else if (aMonth == 6) {
1345 result = 30;
1346 }
1347 else if (aMonth == 7) {
1348 result = 31;
1349 }
1350 else if (aMonth == 8) {
1351 result = 31;
1352 }
1353 else if (aMonth == 9) {
1354 result = 30;
1355 }
1356 else if (aMonth == 10) {
1357 result = 31;
1358 }
1359 else if (aMonth == 11) {
1360 result = 30;
1361 }
1362 else if (aMonth == 12) {
1363 result = 31;
1364 }
1365 else {
1366 throw new AssertionError("Month is out of range 1..12:" + aMonth);
1367 }
1368 }
1369 return result;
1370 }
1371
1372 static DateTime fromJulianDayNumberAtNoon(int aJDAtNoon) {
1373 //http://www.hermetic.ch/cal_stud/jdn.htm
1374 int l = aJDAtNoon + 68569;
1375 int n = (4 * l) / 146097;
1376 l = l - (146097 * n + 3) / 4;
1377 int i = (4000 * (l + 1)) / 1461001;
1378 l = l - (1461 * i) / 4 + 31;
1379 int j = (80 * l) / 2447;
1380 int d = l - (2447 * j) / 80;
1381 l = j / 11;
1382 int m = j + 2 - (12 * l);
1383 int y = 100 * (n - 49) + i + l;
1384 return DateTime.forDateOnly(y, m, d);
1385 }
1386
1387 // PRIVATE
1388
1389 /*
1390 There are 2 representations of a date - a text form, and a 'parsed' form, in which all
1391 of the elements of the date are separated out. A DateTime starts out with one of these
1392 forms, and may need to generate the other.
1393 */
1394
1395 /** The text form of a date. @serial */
1396 private String fDateTime;
1397
1398 /* The following 7 items represent the parsed form of a DateTime. */
1399 /** @serial */
1400 private Integer fYear;
1401 /** @serial */
1402 private Integer fMonth;
1403 /** @serial */
1404 private Integer fDay;
1405 /** @serial */
1406 private Integer fHour;
1407 /** @serial */
1408 private Integer fMinute;
1409 /** @serial */
1410 private Integer fSecond;
1411 /** @serial */
1412 private Integer fNanosecond;
1413
1414 /** Indicates if this DateTime has been parsed into its 7 constituents. @serial */
1415 private boolean fIsAlreadyParsed;
1416
1417 /** @serial */
1418 private int fHashCode;
1419
1420 private static final int EQUAL = 0;
1421
1422 private static int EPOCH_MODIFIED_JD = 2400000;
1423
1424 private static final long serialVersionUID = -1300068157085493891L;
1425
1426 /**
1427 Return a the whole number, with no fraction.
1428 The JD at noon is 1 more than the JD at midnight.
1429 */
1430 private int calculateJulianDayNumberAtNoon() {
1431 //http://www.hermetic.ch/cal_stud/jdn.htm
1432 int y = fYear;
1433 int m = fMonth;
1434 int d = fDay;
1435 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;
1436 return result;
1437 }
1438
1439 private void ensureHasYearMonthDay() {
1440 ensureParsed();
1441 if (!hasYearMonthDay()) {
1442 throw new MissingItem("DateTime does not include year/month/day.");
1443 }
1444 }
1445
1446 /** Return the number of seconds in any existing time portion of the date. */
1447 private int numSecondsInTimePortion() {
1448 int result = 0;
1449 if (fSecond != null) {
1450 result = result + fSecond;
1451 }
1452 if (fMinute != null) {
1453 result = result + 60 * fMinute;
1454 }
1455 if (fHour != null) {
1456 result = result + 3600 * fHour;
1457 }
1458 return result;
1459 }
1460
1461 private void validateState() {
1462 checkRange(fYear, 1, 9999, "Year");
1463 checkRange(fMonth, 1, 12, "Month");
1464 checkRange(fDay, 1, 31, "Day");
1465 checkRange(fHour, 0, 23, "Hour");
1466 checkRange(fMinute, 0, 59, "Minute");
1467 checkRange(fSecond, 0, 59, "Second");
1468 checkRange(fNanosecond, 0, 999999999, "Nanosecond");
1469 checkNumDaysInMonth(fYear, fMonth, fDay);
1470 }
1471
1472 private void checkRange(Integer aValue, int aMin, int aMax, String aName) {
1473 if (!Check.optional(aValue, Check.range(aMin, aMax))) {
1474 throw new ItemOutOfRange(aName + " is not in the range " + aMin + ".." + aMax + ". Value is:" + aValue);
1475 }
1476 }
1477
1478 private void checkNumDaysInMonth(Integer aYear, Integer aMonth, Integer aDay) {
1479 if (hasYearMonthDay(aYear, aMonth, aDay) && aDay > getNumDaysInMonth(aYear, aMonth)) {
1480 throw new ItemOutOfRange("The day-of-the-month value '" + aDay + "' exceeds the number of days in the month: " + getNumDaysInMonth(aYear, aMonth));
1481 }
1482 }
1483
1484 private void parseDateTimeText() {
1485 DateTimeParser parser = new DateTimeParser();
1486 DateTime dateTime = parser.parse(fDateTime);
1487 /*
1488 * This is unusual - we essentially copy from one object to another. This could be
1489 * avoided by building another interface, But defining a top-level interface for this
1490 * simple task is too high a price.
1491 */
1492 fYear = dateTime.fYear;
1493 fMonth = dateTime.fMonth;
1494 fDay = dateTime.fDay;
1495 fHour = dateTime.fHour;
1496 fMinute = dateTime.fMinute;
1497 fSecond = dateTime.fSecond;
1498 fNanosecond = dateTime.fNanosecond;
1499 validateState();
1500 }
1501
1502 private boolean hasYearMonthDay(Integer aYear, Integer aMonth, Integer aDay) {
1503 return isPresent(aYear, aMonth, aDay);
1504 }
1505
1506 private static boolean isLeapYear(Integer aYear) {
1507 boolean result = false;
1508 if (aYear % 100 == 0) {
1509 // this is a century year
1510 if (aYear % 400 == 0) {
1511 result = true;
1512 }
1513 }
1514 else if (aYear % 4 == 0) {
1515 result = true;
1516 }
1517 return result;
1518 }
1519
1520 private Object[] getSignificantFields() {
1521 return new Object[]{fYear, fMonth, fDay, fHour, fMinute, fSecond, fNanosecond};
1522 }
1523
1524 private void addToString(String aName, Object aValue, StringBuilder aBuilder) {
1525 aBuilder.append(aName + ":" + String.valueOf(aValue) + " ");
1526 }
1527
1528 /** Return true only if all the given arguments are non-null. */
1529 private boolean isPresent(Object... aItems) {
1530 boolean result = true;
1531 for (Object item : aItems) {
1532 if (item == null) {
1533 result = false;
1534 break;
1535 }
1536 }
1537 return result;
1538 }
1539
1540 private DateTime getStartEndDateTime(Integer aDay, Integer aHour, Integer aMinute, Integer aSecond, Integer aNanosecond) {
1541 ensureHasYearMonthDay();
1542 return new DateTime(fYear, fMonth, aDay, aHour, aMinute, aSecond, aNanosecond);
1543 }
1544
1545 private String calcToStringFormat(){
1546 String result = null; //caller will check for this; null means the set of units is bizarre
1547 if(unitsAllPresent(Unit.YEAR) && unitsAllAbsent(Unit.MONTH, Unit.DAY, Unit.HOUR, Unit.MINUTE, Unit.SECOND, Unit.NANOSECONDS)){
1548 result = "YYYY";
1549 }
1550 else if (unitsAllPresent(Unit.YEAR, Unit.MONTH) && unitsAllAbsent(Unit.DAY, Unit.HOUR, Unit.MINUTE, Unit.SECOND, Unit.NANOSECONDS)){
1551 result = "YYYY-MM";
1552 }
1553 else if (unitsAllPresent(Unit.YEAR, Unit.MONTH, Unit.DAY) && unitsAllAbsent(Unit.HOUR, Unit.MINUTE, Unit.SECOND, Unit.NANOSECONDS)){
1554 result = "YYYY-MM-DD";
1555 }
1556 else if (unitsAllPresent(Unit.YEAR, Unit.MONTH, Unit.DAY, Unit.HOUR) && unitsAllAbsent(Unit.MINUTE, Unit.SECOND, Unit.NANOSECONDS)){
1557 result = "YYYY-MM-DD hh";
1558 }
1559 else if (unitsAllPresent(Unit.YEAR, Unit.MONTH, Unit.DAY, Unit.HOUR, Unit.MINUTE) && unitsAllAbsent(Unit.SECOND, Unit.NANOSECONDS)){
1560 result = "YYYY-MM-DD hh:mm";
1561 }
1562 else if (unitsAllPresent(Unit.YEAR, Unit.MONTH, Unit.DAY, Unit.HOUR, Unit.MINUTE, Unit.SECOND) && unitsAllAbsent(Unit.NANOSECONDS)){
1563 result = "YYYY-MM-DD hh:mm:ss";
1564 }
1565 else if (unitsAllPresent(Unit.YEAR, Unit.MONTH, Unit.DAY, Unit.HOUR, Unit.MINUTE, Unit.SECOND, Unit.NANOSECONDS)){
1566 result = "YYYY-MM-DD hh:mm:ss.fffffffff";
1567 }
1568 else if (unitsAllAbsent(Unit.YEAR, Unit.MONTH, Unit.DAY) && unitsAllPresent(Unit.HOUR, Unit.MINUTE, Unit.SECOND, Unit.NANOSECONDS)){
1569 result = "hh:mm:ss.fffffffff";
1570 }
1571 else if (unitsAllAbsent(Unit.YEAR, Unit.MONTH, Unit.DAY, Unit.NANOSECONDS) && unitsAllPresent(Unit.HOUR, Unit.MINUTE, Unit.SECOND)){
1572 result = "hh:mm:ss";
1573 }
1574 else if (unitsAllAbsent(Unit.YEAR, Unit.MONTH, Unit.DAY, Unit.SECOND, Unit.NANOSECONDS) && unitsAllPresent(Unit.HOUR, Unit.MINUTE)){
1575 result = "hh:mm";
1576 }
1577 return result;
1578 }
1579
1580 /**
1581 Always treat de-serialization as a full-blown constructor, by
1582 validating the final state of the de-serialized object.
1583 */
1584 private void readObject(ObjectInputStream aInputStream) throws ClassNotFoundException, IOException {
1585 //always perform the default de-serialization first
1586 aInputStream.defaultReadObject();
1587 //no mutable fields in this case
1588 validateState();
1589 }
1590
1591 /**
1592 This is the default implementation of writeObject.
1593 Customise if necessary.
1594 */
1595 private void writeObject(ObjectOutputStream aOutputStream) throws IOException {
1596 //perform the default serialization for all non-transient, non-static fields
1597 aOutputStream.defaultWriteObject();
1598 }
1599
1600 }