This article is written in continuation of the
first part and is devoted to the
new Date Time API , which was introduced in Java 8. I initially wanted to arrange this topic separately, because it is quite large and serious. I myself have not fully started to use this API in projects, so we will understand together along the way. In principle, there is no urgent need to switch to the new API, moreover, many have not yet started Java 8 projects, which means that there is still time to master.
In the article I will try not to slip into the banal translation of regular documentation, I would like to concentrate more on what seemed to me especially important.
Story
As for working with time, there have been complaints about the standard Java library for a long time. The criticized version of the API was developed a very long time ago and when designing it, serious mistakes were made. Alternatively, many have used the third-party Joda-time library. I myself am not a very big fan of Joda-time for several reasons:
- the standard library classes cannot be avoided anyway, in 99% of cases their functionality copes with the task, but I don’t want to multiply entities beyond necessity;
- The joda-time library does not use the standard database of time zones from the JVM, so during the next maneuver, legislators have to remember that you need to update tzdata not only in JDK, but also in joda-time .
Comparison
It’s probably worth starting with the fact that it didn’t suit many in the old API. And right there, in order not to waste time, I will immediately indicate that the new API has changed for the better.
Dividing classes into packages:
- In the old API, classes for working with time are wrapped in java.util and java.sql packages - among a large number of other classes. In addition, there are also java.util.concurrent.TimeUnit and java.text.DateFormat classes with successors.
- In the new API, a separate java.time package is allocated for working with time.
')
Class Names:
- The names of the classes in the old API do not reflect the essence of what is happening. In the old API there are two classes that can mark a point on the time axis: java.util.Date and java.util.Calendar. The java.util.Date class denotes the time in milliseconds on Unix-time, and not the date at all (it is so named because of the same reasons that the time on the command line is issued by the / bin / date utility). The java.util.Calendar class is also not a calendar at all, it has a state in the form of a time zone, calendar and temporary fields.
- In the new API, class names are given more meaningfully. There are classes similar to those already mentioned: java.time.Instant and java.time.ZonedDateTime. There are also many other classes for more specialized use.
Immutability and thread safety:
Accuracy:
- Representation accuracy in time is one millisecond. For most practical tasks, this is more than enough, but sometimes you want to have higher accuracy.
- The new API submission time is one nanosecond, which is a million times more accurate.
Store time and date stamps:
- Classes for time and date stamps (java.sql.Date and java.sql.Time) are not a pure representation of time and date stamps, because they are inherited from java.util.Date and in one way or another store the full Unix-time value with ignoring part of it. values.
- In the new API, the corresponding java.time.LocalDate and java.time.LocalTime classes store clean tuples (yyyy, MM, dd) and (HH, mm, dd), respectively, and there is no unnecessary information or logic in these classes. Also introduced is the java.time.LocalDateTime class which stores both tuples.
Specify time zone:
- In the old API, many actions where an indication of a time zone is necessary can be performed without specifying it. In this case, the time zone is taken by default, and the programmer may not even guess that he missed something.
- In the new API, all actions where an indication of a time zone is necessary require it explicitly: either as a method argument or the time zone is displayed directly in the method name. In other words, the default time zone is nowhere used by default.
Testing:
- The old API is very difficult to use in tests in which you need to test the behavior of logic over time (this is described in detail in the previous article).
- The new API introduced a special abstract class java.time.Clock, a single instance of which can be injected into the context or simply passed to its logic. By redefining this class for tests, you can control the flow of time for your code during its execution.
Month numbering:
- In the old API, the month numbers come with 0, which is very unintuitive.
- In the new API, the numbers of months come with 1. A new java.time.Month enumeration has appeared .
Tagging:
- In java.util.Calendar set the year-month-day-hour-minute-second, but to reset the milliseconds it was necessary to make a separate call.
- In java.time.ZonedDateTime, all fields are set at once, including nanoseconds.
Duration designation:
- In the old API, there are no classes for defining duration and time intervals. Typically, simple long and storage durations are used in the form of milliseconds.
- The new API defines special classes for duration and periods.
Fears
It’s also probably worth telling that I’m definitely not going to call it “impairment,” but I’ll carefully call it “apprehension”:
- If earlier there were two actively used classes: java.util.Date and java.util.Calendar, now there are more classes, plus a hierarchy of interfaces and abstract classes has been added to them.
- Partly because of the large number of new classes, some nuances of work appeared that I managed to find in just a few hours of research and which we will talk about later.
- The new API does not control the correctness of operations in compile-time. Many problems with the lack of a time zone will only appear in runtime - as will be seen later in the examples. I would prefer a less flexible but more stringent contract.
- during the work, a greater number of objects are generated. For example, retrieving the current time point via Instance.now () besides the java.time.Instance instance itself creates another java.time.Clock for each request, although this is not good at all - in the current implementation it would be enough to call System.currentTimeMillis ( ). Also, intermediate objects are created with many other actions. But I do not think that for a typical backend this will present any problem - neither memory consumption nor execution time. Hardcore players still keep time for a long time or even pack it in an int.
- the problem (is it a problem?) with regard to leap second was not solved at all. In fact, the new library still does not go a step further from Unix-time and relies on the external translation of the second hand back. At the same time, the past still loses a second each time and moves forward. We have not received the library for accurate scientific calculations.
I will tell you more about these and other cases that alerted me in the examples.
Time zones
Let's start as usual with time zones. The new
java.time.ZoneId class denotes a time zone. Its two subclasses java.time.ZoneRegion and
java.time.ZoneOffset implement two types of time zones: a time zone according to geographic principle and a time zone according to a simple offset from UTC, UT or GMT. The translation rules for arrows are placed in a separate
java.time.zone.ZoneRules class, an instance of which is available through the java.time.ZoneId # getRules method.
In general, besides the specified refactoring, I did not find any special fundamental changes here, except that the rules for translating arrows now provide more methods for requesting information. Therefore, everything written in the
old article is also valid for new classes of temporary zones, except that the methods differ somewhat in name.
@Test public void testZoneId() throws Exception {
It is not very clear why case-4, which actually requests the same as case-3, creates java.time.ZoneRegion as a result, and not java.time.ZoneOffset.
@Test public void testZoneUTC() throws Exception { ZoneId zid1 = ZoneOffset.UTC; Assert.assertEquals("ZoneOffset", zid1.getClass().getSimpleName()); ZoneId zid2 = ZoneId.of("Z"); Assert.assertEquals("ZoneOffset", zid2.getClass().getSimpleName()); Assert.assertSame(ZoneOffset.UTC, zid2); ZoneId zid3 = ZoneId.of("UTC"); Assert.assertEquals("ZoneRegion", zid3.getClass().getSimpleName()); }
For the UTC time zone, a special constant java.time.ZoneOffset # UTC has been entered, but nevertheless the request for ZoneId.of (“UTC”) in the new API already produces an object of class java.util.ZoneRegion, and not this constant.
Clock
"
Time is a clock " - as claimed by some physicists. And this phrase is the key to the new API, where the
java.time.Clock class is the cornerstone. And just like some of our watches, time for us can be: constant (non-existent), late, running with varying degrees of accuracy, moving the hands differently in different time zones. In general, in the new API, you can use (or define yourself) practically any course of time, including for testing tests.
A standard java.time.Clock instance can be created only by factory static methods (the class itself is abstract).
A standard java.time.Clock instance always knows about the time zone in which it was created (although this is sometimes unnecessary).
Let's go through the factory methods:
- java.time.Clock # systemDefaultZone - the method creates a system clock in the time zone by default.
- java.time.Clock # systemUTC - the method creates a system clock in the UTC time zone.
- java.time.Clock # system - the method creates a system clock in the specified time zone.
- java.time.Clock # fixed - the method creates a clock of constant time, that is, the clock does not go, but stands still.
- java.time.Clock # offset - method creates a proxy over the specified clock, which shifts time by the specified value.
- java.time.Clock # tickSeconds - the method creates a system clock in the specified time zone, the value of which is rounded to whole seconds.
- java.time.Clock # tickMinutes - the method creates a system clock in the specified time zone, the value of which is rounded to full minutes.
- java.time.Clock # tick - the method creates a proxy over the specified clock, which rounds the time to the specified period.
- java.time.Clock # withZone - the method creates a copy of the current clock in another time zone.
You can override
java.time.Clock and write any logic of time output, for example, a clock that gives random time for each request, why not?
The java.time.Clock object has only three working methods:
- java.time.Clock # getZone - request the time zone in which the clock operates.
- java.time.Clock # millis - request the current time in milliseconds on Unix-time
- java.time.Clock # instant - request the current time in the most general sense (in fact - in nanoseconds on Unix-time)
Now a little criticism:
- I would start a clean java.time.Clock interface and a separate factory java.time.Clocks - but I do not insist.
- For some reason, the time zone is necessarily imposed on the clock. The clock itself is not needed at all: neither java.time.Clock # millis, nor java.time.Clock # instant is used. The time zone of the clock is requested in the factory methods {Zoned, Local, Offset} DateTime, but it was there that could be transferred as a separate parameter in the method, and not stored in ballast in java.time.Clock.
- Unfortunately, there is no MockClock class for manual time management for tests, you have to write it yourself - this is not a problem, but it would be better if it were right away.
- The clock does not have a java.time.Clock # ticks method for measuring timeless nanosecond ticks (similar to java.lang.System # nanoTime). On the one hand, the absence of such a method is understandable, because it does not apply to the calculation of time. But on the other hand, it refers to the measurement of the duration of operations. Therefore, for managing timeless ticks (and measuring duration accordingly) in tests it would be nice if the method for ticks were also in this interface, if only because the manual advance of time in MockClock by default would promote both ticks and time .
Instant
java.time.Instant is the new java.util.Date, only immutable, with nanosecond precision and the correct name. Inside it stores
Unix-time in the form of two fields: long with the number of seconds, and int with the number of nanoseconds within the current second.
The value of both fields can be requested directly, and you can also ask to calculate the Unix-time representation that is more familiar to the old API in the form of milliseconds:
@Test public void testInstantFields() throws Exception { Instant instant = Clock.systemDefaultZone().instant(); System.out.println(instant.getEpochSecond()); System.out.println(instant.getNano()); System.out.println(instant.toEpochMilli()); }
As well as java.util.Date (if used correctly), an object of the java.time.Instant class does not know anything about the time zone.
Separately it is necessary to say about the java.time.Instant.toString () method. If earlier java.util.Date.toString () worked taking into account the current locale and the default time zone, the new java.time.Instant.toString () always forms a textual representation in the UTC time zone and the same ISO-8601 format - this applies and output variables in the IDE when debugging:
@Test public void testInstantString() throws Exception { Instant instant1 = Clock.system(ZoneId.of("Europe/Paris")).instant(); System.out.println(instant1.toString()); Instant instant2 = Clock.systemUTC().instant(); System.out.println(instant2.toString()); Instant instant3 = Clock.systemDefaultZone().instant(); System.out.println(instant3.toString()); }
2016-01-06T15: 22: 53.403Z
2016-01-06T15: 22: 53.417Z
2016-01-06T15: 22: 53.423Z
Basic interfaces
Let's look at the basic
java.time.temporal.TemporalAccessor interface. The TemporalAccessor interface is a directory for requesting individual partial information on the current point or label and all temporary classes of the new API implement it.
Ask for the Unix-time value of java.time.Instant:
@Test(expected = DateTimeException.class) public void testTemporalAccessor2() throws Exception { TemporalAccessor ta = Clock.systemUTC().instant();
We get an exception with a completely inexplicable message:
java.time.DateTimeException: Invalid value for InstantSeconds \
(valid values -9223372036854775808 - 9223372036854775807): 1451983908
After a bit of a jogging, the reason for the exception becomes clear: theoretically, the result may not fit into the int range (although it currently fits). The INSTANT_SECONDS field must be requested as long. We will correct the request, along the way we will request additional meta-information:
@Test public void testTemporalAccessor3() throws Exception { TemporalAccessor ta = Clock.systemUTC().instant(); System.out.println(ta.getLong(ChronoField.INSTANT_SECONDS)); ValueRange vr = ta.range(ChronoField.INSTANT_SECONDS); System.out.println(vr.getMinimum()); System.out.println(vr.getMaximum()); System.out.println(ta.isSupported(ChronoField.INSTANT_SECONDS)); System.out.println(ta.isSupported(ChronoField.CLOCK_HOUR_OF_DAY)); }
1452094053
-9223372036854775808
9223372036854775807
true
false
The CLOCK_HOUR_OF_DAY field is not supported by the Instant type. This is completely expected, since to find out the hour of the day at a time point we need to specify a time zone, which is not in java.time.Instant. Let's try all the same to request this value:
@Test(expected = UnsupportedTemporalTypeException.class) public void testTemporalAccessor1() throws Exception { TemporalAccessor ta = Clock.systemUTC().instant();
That's right - when we request an hour of the day, we get an exception. It's great that the request method did not use the default time zone (which is not in the new API).
In addition to querying individual fields, you can query values using more complex algorithms-strategies that inherit the
java.time.TemporalQuery interface:
@Test public void testTemporalAccessor4() throws Exception { TemporalAccessor ta = Clock.systemUTC().instant(); ZoneId zoneId1 = ta.query(TemporalQueries.zone()); ZoneId zoneId2 = TemporalQueries.zone().queryFrom(ta); Assert.assertEquals(zoneId1, zoneId2); TemporalUnit unit1 = ta.query(TemporalQueries.precision()); TemporalUnit unit2 = TemporalQueries.precision().queryFrom(ta); Assert.assertEquals(unit1, unit2); }
java.time.temporal.Temporal - the interface is a successor of the TemporalAccessor interface. Enters forward and backward point / tag shift operations, the operation of replacing a portion of temporal information, as well as the operation to calculate the distance to another time point / mark. It is implemented by almost all the “full-fledged” temporary classes of the new API.
We try to move the label a day ahead and calculate the difference:
@Test public void testTemporal1() throws Exception { Temporal t1 = Clock.systemUTC().instant(); Temporal t2 = t1.plus(1, ChronoUnit.DAYS); Assert.assertEquals(Duration.ofDays(1).getSeconds(), t2.getLong(ChronoField.INSTANT_SECONDS) - t1.getLong(ChronoField.INSTANT_SECONDS)); Assert.assertEquals(24, t1.until(t2, ChronoUnit.HOURS)); Assert.assertEquals(24, Duration.between(t1, t2).get(ChronoUnit.HOURS)); }
Since all classes have finally become immutable, the results of the operations must not be forgotten to be assigned to another variable, since the original does not change during the operation - everything is the same as java.lang.String or java.math.BigDecimal.
Let's try to change the hour of the day in java.time.Instant:
@Test(expected = UnsupportedTemporalTypeException.class) public void testTemporal2() throws Exception { Temporal t = Clock.systemUTC().instant();
Expectedly we get our hands, because a time zone is necessary for this operation.
java.time.temporal.TemporalAdjuster - interface of the time point / tag correction strategy, for example, moving on the first day of the current code. Previously, you had to write your own auxiliary classes for working with java.util.Calendar fields - now you can put all the code in the form of a strategy, if you don’t have it yet in the standard delivery:
@Test public void testTemporalAdjuster() throws Exception { ZonedDateTime zdt = ZonedDateTime.of(2005, 10, 30, 0, 0, 0, 0, ZoneId.of("Europe/Moscow")); ZonedDateTime zdt1 = zdt.with(TemporalAdjusters.firstDayOfYear()); ZonedDateTime zdt2 = (ZonedDateTime) TemporalAdjusters.firstDayOfYear().adjustInto(zdt); Assert.assertEquals(zdt1, zdt2); Assert.assertEquals(2005, zdt1.get(ChronoField.YEAR)); Assert.assertEquals(1, zdt1.get(ChronoField.MONTH_OF_YEAR)); Assert.assertEquals(1, zdt1.get(ChronoField.DAY_OF_MONTH)); }
Now you can go to the temporary classes.
LocalTime, LocalDate, LocalDateTime
java.time.LocalTime is a tuple (hour, minute, second, nanosecond)
java.time.LocalDate is a tuple (year, month, day of month)
java.time.LocalDateTime - both tuples together
I would also
refer specific classes for storing a part of information to the same classes:
java.time.MonthDay ,
java.time.Year ,
java.time.YearMonthAll these classes are united by the fact that they contain timestamps or parts of them, but they cannot determine the time points on the time axis themselves (even LocalDateTime) - since none of them have a time zone or even an offset.
These classes, like all the others, support the java.lang.Comparable interface, but you need to understand that this is exactly a comparison of timestamps, not time points:
@Test public void testLocalDateTime() throws Exception { ZonedDateTime zdt1 = ZonedDateTime.of(2015, 1, 10, 15, 0, 0, 0, ZoneId.of("Europe/Moscow")); ZonedDateTime zdt2 = ZonedDateTime.of(2015, 1, 10, 14, 0, 0, 0, ZoneId.of("Europe/London")); Assert.assertEquals(-1, zdt1.compareTo(zdt2)); LocalDateTime ldt1 = zdt1.toLocalDateTime(); LocalDateTime ldt2 = zdt2.toLocalDateTime(); Assert.assertEquals(+1, ldt1.compareTo(ldt2)); }
It must be said that despite the inevitable parallels in use between java.time.LocalTime and java.sql.Time, as well as between java.time.LocalDate and java.sql.Date are totally different classes. In the old API, the classes java.sql.Time and java.sql.Date are descendants of java.util.Date, which means that their interpretation (getting the hour value for example) depends on the time zone in which the object of this class was created and on the time zone in which this object will be read. In the new API, the java.time.LocalTime and java.time.LocalDate classes are honest tuples of values, and the time zone does not participate in any way when writing and reading the hour value.
However, the time zone is necessary when creating them from a time point, since the interpretation of the days and hours depends on it:
@Test(expected = DateTimeException.class) public void testLocalDateTimeCreate1() throws Exception { Clock clock = Clock.system(ZoneId.of("Europe/Moscow"));
An exception is thrown, due to the fact that the time zone is simply nowhere to take (in Instant it does not exist, and we do not take the zone by default). But it can be obtained either from the java.time.Clock, or you can transfer it additionally:
@Test public void testLocalDateTimeCreate2() throws Exception { Clock clock = Clock.system(ZoneId.of("Europe/Moscow")); LocalDateTime ldt1 = LocalDateTime.ofInstant(clock.instant(), ZoneId.of("UTC")); System.out.println(ldt1); LocalDateTime ldt2 = LocalDateTime.now(clock); System.out.println(ldt2); }
Now everything works, but the ease with which you can make a mistake is somewhat alarming.
In the comments to the
previous article it was mentioned that the real paranoids should also indicate the calendar during operations with calendar values (which includes the creation of objects of all time classes except Instant). The new API has several calendars, which are called chronologies:
@Test public void testChronology() throws Exception { Clock clock = Clock.system(ZoneId.of("Europe/Moscow")); ZonedDateTime zdt = ZonedDateTime.now(clock); ChronoLocalDateTime dt1 = IsoChronology.INSTANCE.localDateTime(zdt); System.out.println(dt1);
,
ISO-8601 IsoChronology ( ), , , API .
ZonedDateTime
java.time.ZonedDateTime — java.util.Calendar. , , .
ZonedDateTime LocalDateTime:
@Test(expected = DateTimeException.class) public void testZoned1() throws Exception { LocalDateTime ldt = LocalDateTime.of(2015, 1, 10, 0, 0, 0, 0);
, ( LocalDateTime) , - API ( ).
:
@Test public void testZoned2() throws Exception { LocalDateTime ldt = LocalDateTime.of(2015, 1, 10, 0, 0, 0, 0); ZonedDateTime zdt = ZonedDateTime.of(ldt, ZoneId.of("Europe/Moscow")); }
Let's see how strict the ZonedDateTime is with respect to incorrectly specified dates. In java.util.Calendar there is a lenient switch that can be configured for both “strict” and “soft” mode. In the new API, there is no such switch.February 29 is not in a leap year will not pass: @Test(expected = DateTimeException.class) public void testLenient2() throws Exception {
60th second can not be specified: @Test(expected = DateTimeException.class) public void testLenient3() throws Exception {
But the indication of the label at the time of the transfer of the arrows to summer time passes successfully, and the result differs from the expected. In strict mode java.util.Calendar is not missed (see the previous article ). @Test public void testLenient1() throws Exception { ZonedDateTime zdt = ZonedDateTime.of(2005, 3, 27, 2, 30, 0, 0, ZoneId.of("Europe/Moscow")); Assert.assertEquals(3, zdt.getLong(ChronoField.HOUR_OF_DAY)); Assert.assertEquals(30, zdt.getLong(ChronoField.MINUTE_OF_HOUR)); }
I will not write anything about operations in ZonedDateTime - you can see the documentation.OffsetTime, OffsetDateTime
java.time.OffsetTime — LocalTime + ZoneOffset
java.time.OffsetDateTime — LocalDateTime + ZoneOffset
, ( —
, ), OffsetDateTime ZonedDateTime. OffsetDateTime , , .
, (
JavaScript ). ,
ZonedDateTime — . , OffsetDateTime .
API : java.time.Instant, java.time.ZonedDateTime java.time.OffsetTime.
java.time.ZonedDateTime, .
:
@Test public void testWinterDay() throws Exception { ZonedDateTime zdt1 = ZonedDateTime.of(2005, 10, 30, 0, 0, 0, 0, ZoneId.of("Europe/Moscow"));
case#1 case#2 ZonedDateTime , 25 .
case#3 , OffsetDateTime , case#4 , .
case#5 case#6 — , Instant , .
case#7 case#8 — , LocalDateTime , .
, API ( - ). . — Java-. API , java.util.Calendar, , — .
Perhaps it was worth banning most operations with time in all classes except ZonedDateTime, since only he is aware of the arrow translations. It might be worthwhile to prohibit the calculation of Duration using LocalDateTime, since without a time zone it does not define a time point. I am not ready now to somehow seriously discuss the possibility or impossibility of such solutions, but I have a feeling of danger from the new API.Period, Duration
The new API has two classes for determining duration.java.time.Period - description of the calendar duration (period) in the form of a tuple (year, month, day).java.time.Duration - description of the exact duration in the form of an integer number of seconds and fractions of the current second in the form of nanoseconds.The difference between the two can be shown in the example with the day of the transfer of hands to winter time. Due to the switch back, this calendar day consists of 25 hours. @Test public void testDuration() throws Exception { Period period = Period.of(0, 0, 1); Duration duration = Duration.of(1, ChronoUnit.DAYS); ZonedDateTime zdt1 = ZonedDateTime.of(2005, 10, 30, 0, 0, 0, 0, ZoneId.of("Europe/Moscow")); ZonedDateTime ztd2 = zdt1.plus(period); Assert.assertEquals(ZonedDateTime.of(2005, 10, 31, 0, 0, 0, 0, ZoneId.of("Europe/Moscow")), ztd2); ZonedDateTime ztd3 = zdt1.plus(duration); Assert.assertEquals(ZonedDateTime.of(2005, 10, 30, 23, 0, 0, 0, ZoneId.of("Europe/Moscow")), ztd3); }
When adding Period.of (0, 0, 1), we correctly move to the next calendar day. In the case of adding Duration.of (1, ChronoUnit.DAYS), we actually add 24 hours and do not switch to the next calendar day.Formatting and parsing
API , java.text.SimpleDateFormat -. - , SimpleDateFormat - .
API .
java.time.format.DateTimeFormatter — .
@Test public void testFormat() throws Exception { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:dd z", Locale.ENGLISH); ZonedDateTime zdt1 = ZonedDateTime.of(2005, 10, 30, 0, 0, 0, 0, ZoneId.of("Europe/Moscow")); String text = zdt1.format(formatter); System.out.println(text); TemporalAccessor ta = formatter.parse(text);
JavaDoc , API . , , java.time.Temporal (java.time.format.Parsed ), , , , .
I will give the class diagram of the new API. Some minor classes are not shown, as well as the implementation of such interfaces as java.util.Serializable and java.lang.Comparable.Compatibility
For the exchange of information between the old and new API implemented several methods. Moreover, it is implemented quite competently: the old API knows about the new API, but the new API does not know anything about the old API at all. Purely theoretically, this will allow one to throw out all the old classes, but I doubt that this will happen in our life.
@Test public void testTimeZoneCompat() throws Exception { ZoneId zoneId1 = ZoneId.of("Europe/Moscow"); TimeZone timeZone = TimeZone.getTimeZone(zoneId1); ZoneId zoneId2 = timeZone.toZoneId(); Assert.assertEquals(zoneId1, zoneId2); } @Test public void testDateCompat() throws Exception { Instant instant1 = Clock.systemUTC().instant(); Date date = Date.from(instant1); Instant instant2 = date.toInstant(); Assert.assertEquals(instant1, instant2); }
: java.util.Date , API , . , java.lang.System#currentTimeMillis, , .
findings
API. , : ZonedDateTime, runtime . , API . — .