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