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