📜 ⬆️ ⬇️

Java and time: part one

Eight years ago, I participated in the design and development of a service that was supposed to serve user requests from all over the globe and coordinate their actions. Working on a project, I realized that very often many important aspects of work over time are simply ignored. Sometimes this is really not very critical: if the service is local and used only in a certain area, or the users are naturally divided into geographically non-interacting geographic clusters. However, if the service unites users all over the world, then one cannot do without a clear understanding of the principles of working with time. Imagine a service in which general events (meetings, for example) start at some strictly specific time, and users expect this. What time to show them, at what point they should be disturbed by notifications, what is a birthday and when you can congratulate a person - in the article I will try to comprehend it.



The article does not claim to the depth and / or academic. This is an attempt to systematize the experience and draw the attention of developers to not very obvious aspects.
')


A new Date Time API has appeared in JDK8, in which many new useful classes have appeared , but I will not mention it, as well as excellent third-party libraries . This is a separate, big topic that deserves a separate article; plus, as I have already mentioned, I try not to talk about any particular technology, but about the principles of operation in general, but in this regard, the new API is fundamentally no different from the old one.

Time axis



Let's start from far away. Right very far away. Draw the axis of time .



Right here begin different questions. Does the time always go in one direction ? Does it go evenly? Is it continuous? What to take for a single vector? Is this axis one for all? Does it depend on position in space? From the speed of movement? What is the minimum measurable segment?

Actually here I am ready only to ask questions, but not to answer them. There is an opinion that there is no time , but I am also not yet ready to discuss this issue.

But there are already resolved moments.

About the unit of measurement, everything is clear - it is clearly and unambiguously specified.

The distance from Moscow to Washington is approximately 7840000 meters and the light travels this distance over the earth's surface in at least 0.026 seconds, which is quite a lot. The request to create an account by the user in Vladivostok will be processed on the Moscow server only after some time. Thus, information about events taking place is not immediately available and depends on the distance between points in space.

In addition, the rate of time itself depends on the speed of movement of the object, and even for quite ordinary near-terrestrial technologies like GPS .

The current standard Java time processing library believes that no relativistic effects exist and no one moves at near-light speeds, and the time axis is the same for all (at least on the scale of one planet) - and this suits us all. Perhaps later, a new Java Date Time API will be implemented in JDK # 6543, which will allow us to write a service for the Millennium Falcon internal office system, taking into account its speed of movement and the presence / absence of wormholes nearby.

Now we note some moment on the time axis. For example, right now I’ll click on the "point" button. (Clicked)



Now you need to think of a way by which I could inform you about the exact moment I pressed this button. The easiest way to do this is to designate a point in time, common to all of us, from which we all constantly count time counts. If this moment in time is indicated ( that very moment ), then I will be able to transfer you the number of my samples from this general moment, and you will be able to understand the relationship between the time you press the button and your current time when you receive my value.

In the primitive physical world, we could meet and simultaneously launch the same hourglass. After that, quietly go about their business, and the times of events to report in the form of the height of the sand column on our watch (probably the clock should be very large and bulky).



The one-moment that we use, in turn, can also be a measurement - but already relative to some more general and important event. In our case, this is the case; the very moment in the Unix-time system (numerical value 0), which is used in Java, is a time point labeled 00:00:00 January 1, 1970 A.D. UTC is already on a different scale - the Gregorian calendar .

What's with java



To set a time point on the time axis in Java, there is a class java.util.Date . In general, java.util.Date is a disgrace to Java, starting from the earliest versions. First, his name does not reflect the essence; and secondly, it is mutable. However, life will be easier if you take it as a simple wrapper over an internal field of type long in which the number of milliseconds from that very moment is stored — nothing more. All other methods are marked as obsolete and should not be used in any way. Just remember that java.utl.Date is identical (in its essence) to the simple numeric long-value of Unix-time in milliseconds.

Date moment = new Date(1451665447567L); //    Unix-time  -- moment.getTime(); //    Unix-time  --. 


If you think a little, it becomes clear that in any language there are limitations to the accuracy of the representation of time. Therefore, java.util.Date (like any other similar types) is not a point on the time axis, but a segment. In our case - a segment of millisecond duration. But from a practical point of view, such accuracy suits us, and therefore we will call it a full stop.

Since the representation in Java from the very beginning is 64-bit, then for our age it will be enough for sure:
We have enough
 Date theEnd = new Date(Long.MAX_VALUE); DateFormat dateFormat = SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.LONG, SimpleDateFormat.LONG); dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); String text = dateFormat.format(theEnd); System.out.println(text); # August 17, 292278994 7:12:55 AM UTC 



For various operations with time such as reading / installation / modification of individual calendar fields (year, month, day, hours, minutes, seconds, etc.) there is a class java.util.Calendar . It is also not without sin - in operations, remember that the months go from 0 (it is better to use constants), and the days go from 1.
java.util.Calendar
  @Test public void testSunday() throws Exception { Calendar calendar = Calendar.getInstance(); calendar.setTimeZone(TimeZone.getTimeZone("UTC")); calendar.set(2016, Calendar.JANUARY, 5, 12, 30, 0); calendar.add(Calendar.DAY_OF_YEAR, -2); Assert.assertEquals(Calendar.SUNDAY, calendar.get(Calendar.DAY_OF_WEEK)); } 



Another muddy moment in java.util.Calendar is that when setting the full date (yyyy, MM, dd, HH, mm, ss), the number of milliseconds is not reset to 0, but remains equal to the number of milliseconds from the previous set point ( current time if the calendar has not changed). Therefore, if under the conditions of the task in milliseconds it must be 0, then this field must be reset by another additional call:
java.util.Calendar
  @Test public void testCalendarMs() throws Exception { TimeZone tz = TimeZone.getTimeZone("Europe/Moscow"); Calendar calendar = Calendar.getInstance(tz); calendar.setLenient(false); calendar.set(2016, Calendar.APRIL, 20, 12, 0, 0); System.out.println(calendar.getTimeInMillis()); calendar.set(Calendar.MILLISECOND, 0); System.out.println(calendar.getTimeInMillis()); } 

 1461142800808
 1461142800000



The first number walks in the first three digits depending on the time of the call. This behavior can be quite critical in the tests.

To translate timestamps to points on the axis and back, there is a class java.text.DateFormat and its heirs.
java.text.DateFormat
  @Test public void testFormat() throws Exception { TimeZone tz = TimeZone.getTimeZone("Europe/Moscow"); DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); dateFormat.setLenient(false); dateFormat.setTimeZone(tz); Date moment = dateFormat.parse("2005-03-27 01:30:00"); Assert.assertEquals("2005-03-27 01:30:00", dateFormat.format(moment)); } 



About java.text.DateFormat and java.util.Calendar it is necessary to say the following:


There are also (in the old API) a few more classes:
java.sql.Timestamp - extension (subclass) java.util.Date with nanosecond precision to work with the TIMESTAMP type in the database
java.sql.Date is an extension (subclass) of java.util.Date for working with the DATE type in the database.
java.sql.Time is an extension (subclass) of java.util.Date for working with the TIME type in the database.


In addition, any time point can be stored as a normal long Unix-time value in milliseconds.

Time zones



Anyone who has worked in a global company with many offices around the world (or even just across Russia) knows that the information contained in the phrase “the meeting will be on January 1, 2016 at 14:00:00” is practically useless. The mark "14:00" does not correspond to any particular point on the time axis, or rather to say - corresponds to several at once. In order for everyone to get together in video conversations at the same time, the meeting organizer needs to specify something else, namely, the time zone in which we will interpret the “14:00” label. Often, the time zone is implied by the domination of the head office (“we work Moscow time”), otherwise, if the default time zone is not implied at all, then you need to specify it explicitly, for example, “January 1, 2016 at 14:00 : 00 MSK ”- in this case, the point on the time axis is set quite unambiguously and everyone will gather for the meeting at the same time.

To disambiguate operations with timestamps in the format HH: MM: CC, the time zone must be specified both when displaying the timestamp and when entering .

You can not specify the time zone explicitly, in cases where it can be implied in some way implicitly:


It is probably worth noting that the time zone of the service (by default) and the time zone of the server (by default) are in general not the same thing. The BIOS, the system, the application and the utilities can, for example, work in the UTC time zone, but with all this, the time zone of the service will be the time zone of Moscow. Although of course it is much easier when they match - in this case, the admins set up time zones on the servers, but the programmers do not think about them at all. If this is just your case, then you can stop reading.

The same timestamp can be displayed differently for users - using the time zone that is most familiar to everyone. For example, the following timestamps point to the same time point on the time axis:
 Fri Jan 1 16:29:00 MSK 2016 Fri Jan 1 13:29:00 UTC 2016 Fri Jan 1 14:29:00 CET 2016 Fri Jan 1 21:29:00 SGT 2016 Fri Jan 1 22:29:00 JST 2016 


Wikipedia about time zones .

In Java, time zone information is represented by the java.util.TimeZone class.

It must be said that the time zone is not an offset. This is not to say that GMT + 3 is Europe / Moscow. But it can be said that during the whole of 2016, the time zone of Europe / Moscow will correspond to the GMT + 3 offset. The time zone is the entire history of displacements completely for the entire historical period, as well as other data that allow us to correctly calculate the displacements at different historical moments, as well as to make the correct time calculations.

Let's explore the time zones - for a start, let's look at Europe / Moscow:
Europe / Moscow
  @Test public void testTzMoscow() throws Exception { TimeZone tz = TimeZone.getTimeZone("Europe/Moscow"); System.out.println(tz.getRawOffset()); System.out.println(tz.getOffset(System.currentTimeMillis())); System.out.println(tz.useDaylightTime()); System.out.println(tz.getDisplayName(false, TimeZone.LONG, Locale.ENGLISH)); System.out.println(tz.getDisplayName(false, TimeZone.SHORT, Locale.ENGLISH)); System.out.println(tz.getDisplayName(true, TimeZone.LONG, Locale.ENGLISH)); System.out.println(tz.getDisplayName(true, TimeZone.SHORT, Locale.ENGLISH)); System.out.println(tz.getDisplayName(false, TimeZone.LONG, Locale.FRENCH)); System.out.println(tz.getDisplayName(false, TimeZone.SHORT, Locale.FRENCH)); System.out.println(tz.getDisplayName(true, TimeZone.LONG, Locale.FRENCH)); System.out.println(tz.getDisplayName(true, TimeZone.SHORT, Locale.FRENCH)); } 

 10,800,000
 10,800,000
 false
 Moscow Standard Time
 MSK
 Moscow Daylight Time
 MSD
 Heure standard de Moscou
 MSK
 Heure avancée de moscou
 MSD



We see that the Europe / Moscow zone has a current base offset of +3 hours relative to UTC, and the total offset at the moment is also +3 hours. The switch of arrows to summer time is absent. The zone has separate names for summer and winter time.

Now look at the Paris time:
Europe / Paris
  @Test public void testTzParis() throws Exception { TimeZone tz = TimeZone.getTimeZone("Europe/Paris"); System.out.println(tz.getRawOffset()); System.out.println(tz.getOffset(System.currentTimeMillis())); System.out.println(tz.useDaylightTime()); System.out.println(tz.getDisplayName(false, TimeZone.LONG, Locale.ENGLISH)); System.out.println(tz.getDisplayName(false, TimeZone.SHORT, Locale.ENGLISH)); System.out.println(tz.getDisplayName(true, TimeZone.LONG, Locale.ENGLISH)); System.out.println(tz.getDisplayName(true, TimeZone.SHORT, Locale.ENGLISH)); System.out.println(tz.getDisplayName(false, TimeZone.LONG, Locale.FRENCH)); System.out.println(tz.getDisplayName(false, TimeZone.SHORT, Locale.FRENCH)); System.out.println(tz.getDisplayName(true, TimeZone.LONG, Locale.FRENCH)); System.out.println(tz.getDisplayName(true, TimeZone.SHORT, Locale.FRENCH)); } 

 3.6 million
 3.6 million
 true
 Central European Time
 CET
 Central European Summer Time
 CEST
 Heure d'europe centrale
 CET
 Heure d'été d'europe centrale
 CEST



The base offset is +1 hour relative to UTC, the total offset at the moment is also +1 hour. Daylight saving time in the zone. Also assigned two names - for winter and for summer time separately.

Now we will look at the “GMT + 5” zone. This is actually not exactly a time zone - it has no history, no summer time, and the shift is constant.
GMT + 5
  @Test public void testGmt5() throws Exception { TimeZone tz = TimeZone.getTimeZone("GMT+5"); System.out.println(tz.getRawOffset()); System.out.println(tz.getOffset(System.currentTimeMillis())); System.out.println(tz.useDaylightTime()); System.out.println(tz.getDisplayName(false, TimeZone.LONG, Locale.ENGLISH)); System.out.println(tz.getDisplayName(false, TimeZone.SHORT, Locale.ENGLISH)); System.out.println(tz.getDisplayName(true, TimeZone.LONG, Locale.ENGLISH)); System.out.println(tz.getDisplayName(true, TimeZone.SHORT, Locale.ENGLISH)); System.out.println(tz.getDisplayName(false, TimeZone.LONG, Locale.FRENCH)); System.out.println(tz.getDisplayName(false, TimeZone.SHORT, Locale.FRENCH)); System.out.println(tz.getDisplayName(true, TimeZone.LONG, Locale.FRENCH)); System.out.println(tz.getDisplayName(true, TimeZone.SHORT, Locale.FRENCH)); } 

 18000000
 18000000
 false
 GMT + 05: 00
 GMT + 05: 00
 GMT + 05: 00
 GMT + 05: 00
 GMT + 05: 00
 GMT + 05: 00
 GMT + 05: 00
 GMT + 05: 00



So it is, the shift is constant, is +5 hours relative to GMT and never changes.

Examples



Let's continue by telling you examples that take too long to explain in words. First, let's look at what happened in 2005 in the time zone “Europe / Moscow”:
Europe / Moscow - 2005
 $ zdump -v /usr/share/zoneinfo/Europe/Moscow | grep 2005 /usr/share/zoneinfo/Europe/Moscow Sat Mar 26 22:59:59 2005 UT = Sun Mar 27 01:59:59 2005 MSK isdst=0 gmtoff=10800 /usr/share/zoneinfo/Europe/Moscow Sat Mar 26 23:00:00 2005 UT = Sun Mar 27 03:00:00 2005 MSD isdst=1 gmtoff=14400 /usr/share/zoneinfo/Europe/Moscow Sat Oct 29 22:59:59 2005 UT = Sun Oct 30 02:59:59 2005 MSD isdst=1 gmtoff=14400 /usr/share/zoneinfo/Europe/Moscow Sat Oct 29 23:00:00 2005 UT = Sun Oct 30 02:00:00 2005 MSK isdst=0 gmtoff=10800 



Great, we see the switch to summer day and back to winter time. Let's look at what happens at these moments with timestamps. To begin with - the transition to winter time:
transition to winter time
  @Test public void testWinterTime() throws Exception { TimeZone tz = TimeZone.getTimeZone("Europe/Moscow"); DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); dateFormat.setLenient(false); dateFormat.setTimeZone(tz); Calendar calendar = Calendar.getInstance(); calendar.setLenient(false); calendar.setTimeZone(TimeZone.getTimeZone("UTC")); calendar.set(2005, Calendar.OCTOBER, 29, 22, 0, 0); for (int i = 0; i < 62; i++) { String mark = dateFormat.format(calendar.getTime()); System.out.printf("%s - %d, %s\n", mark, tz.getOffset(calendar.getTimeInMillis()), tz.inDaylightTime(calendar.getTime())); calendar.add(Calendar.MINUTE, +1); } } 

 2005-10-30 02:00:00 MSD - 14400000, true
 2005-10-30 02:01:00 MSD - 14400000, true
 ...
 2005-10-30 02:58:00 MSD - 14400000, true
 2005-10-30 02:59:00 MSD - 14400000, true
 2005-10-30 02:00:00 MSK - 10,800,000, false
 2005-10-30 02:01:00 MSK - 10,800,000, false



We see that after 02:59:00 MSD, the arrows are moved back an hour and the next mark is already 02:00:00 MSK - winter time. Also, the time zone indicates that summer time is over, and the shift has changed from GMT + 4 to GMT + 3.

In the example there is an interesting nuance: using the Europe / Moscow zone, it is absolutely impossible to set a point in the calendar corresponding to the 02:00:00 MSD mark - the 02:00:00 MSK point is set, which is an hour later than we need. To set this point as a starting point, one has to resort to the services of the UTC time zone, where everything can be installed. Another option would be to set the 01:00:00 MSD point in the Europe / Moscow zone and add an hour.

Now - daylight saving time:
daylight saving time
  @Test public void testSummerTime() { TimeZone tz = TimeZone.getTimeZone("Europe/Moscow"); DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); dateFormat.setLenient(false); dateFormat.setTimeZone(tz); Calendar calendar = Calendar.getInstance(); calendar.setLenient(false); calendar.setTimeZone(TimeZone.getTimeZone("UTC")); calendar.set(2005, Calendar.MARCH, 26, 22, 0, 0); for (int i = 0; i <= 60; i++) { String mark = dateFormat.format(calendar.getTime()); System.out.printf("%s - %d, %s\n", mark, tz.getOffset(calendar.getTimeInMillis()), tz.inDaylightTime(calendar.getTime())); calendar.add(Calendar.MINUTE, +1); } } 

 2005-03-27 01:00:00 MSK - 10,800,000, false
 2005-03-27 01:01:00 MSK - 10,800,000, false
 ...
 2005-03-27 01:58:00 MSK - 10,800,000, false
 2005-03-27 01:59:00 MSK - 10,800,000, false
 2005-03-27 03:00:00 MSD - 14400000, true
 2005-03-27 03:00:01 MSD - 14400000, true



It can be seen that after 01:59:00 MSK immediately follows 03:00:00 MSD - that is, moving the hands an hour forward. The time zone signals that at this moment the offset changes from GMT + 3 to GMT + 4, and the summer time flag appears.

But what will happen if we try to process the label “2005-03-27 02:30:00” in the Europe / Moscow zone - in theory such a label should not exist?
nonexistent tag
  @Test public void testMissing() throws Exception { TimeZone tz = TimeZone.getTimeZone("Europe/Moscow"); DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); dateFormat.setLenient(false); dateFormat.setTimeZone(tz); Date moment = dateFormat.parse("2005-03-27 02:30:00"); System.out.println(moment); } 

 java.text.ParseException: Unparseable date: "2005-03-27 02:30:00"



That's right - in strict mode, we get an exception.

Calculate the length of the day on the day of the transfer of arrows in winter time:
winter time switch
  @Test public void testWinterDay() { TimeZone tz = TimeZone.getTimeZone("Europe/Moscow"); Calendar calendar = Calendar.getInstance(); calendar.setLenient(false); calendar.setTimeZone(tz); calendar.set(2005, Calendar.OCTOBER, 30, 0, 0, 0); Date time1 = calendar.getTime(); calendar.add(Calendar.DAY_OF_YEAR, +1); Date time2 = calendar.getTime(); System.out.println(TimeUnit.MILLISECONDS.toHours(time2.getTime() - time1.getTime())); } 

 25



From 2005-10-30 00:00:00 MSD until 2005-10-31 00:00:00 MSK, 25 hours have passed, and not 24.

Now check the daylight saving time:
daylight saving time
  @Test public void testSummerDay() { TimeZone tz = TimeZone.getTimeZone("Europe/Moscow"); Calendar calendar = Calendar.getInstance(); calendar.setLenient(false); calendar.setTimeZone(tz); calendar.set(2005, Calendar.MARCH, 27, 0, 0, 0); Date time1 = calendar.getTime(); calendar.add(Calendar.DAY_OF_YEAR, +1); Date time2 = calendar.getTime(); System.out.println(TimeUnit.MILLISECONDS.toHours(time2.getTime() - time1.getTime())); } 

 23



Since 2005-03-27 00:00:00 MSK until 2005-03-28 00:00:00 MSD 23 hours have passed, and not 24.

These two last examples are devoted to those who add 24 * 60 * 60 * 1000 milliseconds not as 24 hours, but as a calendar day. You can say that now there is no such problem, since there are no more daylight saving time transfers. To this I can answer the following:


java.sql.Time, java.sql.Date



Types are intended for working with SQL types TIME and DATE, respectively. It is understood that both values ​​do not depend on the time zone, but unfortunately this is not quite the case. Since both types are descendants of java.util.Date - the interpretation of day-hours depends on the time zone:
The problem with the types of java.sql
  @Test public void testSqlTime() throws Exception { //    2015-01-01 01:00:00 MSK Calendar calendar = Calendar.getInstance(); calendar.setTimeZone(TimeZone.getTimeZone("Europe/Moscow")); calendar.setTimeInMillis(0); calendar.set(2015, Calendar.JANUARY, 10, 1, 0, 0); long now = calendar.getTimeInMillis(); //   java.sql.Time java.sql.Time sqlTime = new java.sql.Time(now); java.sql.Date sqlDate = new java.sql.Date(now); //        Europe/London DateFormat timeFormat = new SimpleDateFormat("HH:mm:ss"); timeFormat.setTimeZone(TimeZone.getTimeZone("Europe/London")); Assert.assertEquals("22:00:00", timeFormat.format(sqlTime)); //        Europe/London DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); dateFormat.setTimeZone(TimeZone.getTimeZone("Europe/London")); Assert.assertEquals("2015-01-09", dateFormat.format(sqlDate)); } 



In principle, both types cope with the task of transferring information from business logic to the JDBC driver, since usually the code works in the same time zone both there and there, but in more advanced cases, including serialization, you need to be very careful when using these classes.

In the new API for the respective types of similar problems are solved.

UTC, GMT



Most people know that GMT and UTC are special designations for which offsets are made in other time zones. But not everyone knows that UTC and GMT are not exactly the same thing (formally). I mean that the labels “2015-12-01 00:00:00 GMT” and “2015-12-01 00:00:00 UTC” denote different (albeit close) points on the time axis.

GMT is calculated astronomically based on the position of the earth relative to other objects. GMT is also directly used as a time zone in some countries.

As the rotation of the globe slows down chaotically, the earth is in the same position at increasing intervals of time. Thus, the distance between the time points on the adjacent GMT marks (for example, “10:00:01” and “10:00:02”) may not exactly equal to one second .

UTC is introduced to replace the GMT and is calculated by the atomic clock. Directly as a time zone is not used (only as a support for displacement).

In UTC, the distance between timestamps (for example, “10:00:01” and “10:00:02”) is exactly the same and strictly equal to one second. The slowing of the Earth's rotation and the accumulating difference from GMT is solved by entering an extra second in a year (or even two) - namely, coordination seconds (leap second).

Thus, the difference between points with the same marks in GMT and UTC never exceeds one second.

They write that the time UTC almost everywhere displaced GMT, and that using the notation of displacements in the form of GMT + 3 has long been the moveton - the correct use of the designation UTC + 3.

Neither GMT nor UTC have summer time.

It must be said that Unix-time , which is used in Java, neither UTC nor GMT directly corresponds. On the one hand, in Unix-time, the difference between adjacent labels is always 1 second; on the other hand, the presence of leap second in Unix-time is not assumed.

Default time zone



Do you explicitly display the time zone when outputting, or do not display whether you are requesting a time zone when entering or not asking if you specify a time zone when performing time operations or not specifying that some time zone is still present in these operations implicitly. If you did not specify your own, the time zone will be used by default.

The term default time zone has already been mentioned several times in the text above. All because without this concept, nothing can be explained properly. All operations with time, output and input timestamps require a time zone. The fact that you do not specify it does not mean that it is not there - it is simply taken by default.

But everything is not so simple again - by default for whom and why ?

Let's start with the kernel. The hwclock manual says that the kernel has an internal concept of the time zone, but almost no one uses it, except for some rare modules — like the FAT file system driver. The core hwclock command can inform the core about the time zone change.

Application applications define a time zone by default in several ways.

-, ( ) Ubuntu ( ) /etc/localtime, — /etc/timezone:
 $ cat /etc/timezone Europe/Moscow $ file /etc/localtime /etc/localtime: timezone data, version 2, 15 gmt time flags, 15 std time flags, no leap seconds, 77 transition times, 15 abbreviation chars $ zdump -v /etc/localtime | head -n 10 /etc/localtime -9223372036854775808 = NULL /etc/localtime -9223372036854689408 = NULL /etc/localtime Wed Dec 31 21:29:42 1879 UT = Wed Dec 31 23:59:59 1879 LMT isdst=0 gmtoff=9017 /etc/localtime Wed Dec 31 21:29:43 1879 UT = Thu Jan 1 00:00:00 1880 MMT isdst=0 gmtoff=9017 /etc/localtime Sun Jul 2 21:29:42 1916 UT = Sun Jul 2 23:59:59 1916 MMT isdst=0 gmtoff=9017 /etc/localtime Sun Jul 2 21:29:43 1916 UT = Mon Jul 3 00:01:02 1916 MMT isdst=0 gmtoff=9079 /etc/localtime Sun Jul 1 20:28:40 1917 UT = Sun Jul 1 22:59:59 1917 MMT isdst=0 gmtoff=9079 /etc/localtime Sun Jul 1 20:28:41 1917 UT = Mon Jul 2 00:00:00 1917 MST isdst=1 gmtoff=12679 /etc/localtime Thu Dec 27 20:28:40 1917 UT = Thu Dec 27 23:59:59 1917 MST isdst=1 gmtoff=12679 /etc/localtime Thu Dec 27 20:28:41 1917 UT = Thu Dec 27 23:00:00 1917 MMT isdst=0 gmtoff=9079 


, Ubuntu :
 $ dpkg-reconfigure tzdata 


tzselect:
 $ tzselect Please identify a location so that time zone rules can be set correctly. Please select a continent, ocean, "coord", or "TZ". 1) Africa 2) Americas 3) Antarctica 4) Arctic Ocean 5) Asia 6) Atlantic Ocean 7) Australia 8) Europe 9) Indian Ocean 10) Pacific Ocean 11) coord - I want to use geographical coordinates. 12) TZ - I want to specify the time zone using the Posix TZ format. #? 


TZ, / .

 $ echo $TZ $ date Wed Dec 30 20:18:18 MSK 2015 $ TZ=UTC date Wed Dec 30 17:18:25 UTC 2015 $ TZ=Europe/London date Wed Dec 30 17:18:35 GMT 2015 $ TZ=Europe/Paris date Wed Dec 30 18:18:40 CET 2015 


/ :
 $ date --utc Fri Jan 1 08:34:36 UTC 2016 


, date :
 $ date +%Z MSK 


libc, Java. .

JVM.

 $ cat << EOF | scala -Duser.timezone=Europe/Paris print("%s\n%s\n".format(java.util.TimeZone.getDefault().getID(), new java.util.Date())) EOF ... Europe/Paris Wed Dec 30 19:24:00 CET 2015 


TimeZone.setDefault(TimeZone timeZone):
 $ cat << EOF | scala > java.util.TimeZone.setDefault(java.util.TimeZone.getTimeZone("America/Los_Angeles")) > print("%s\n%s\n".format(java.util.TimeZone.getDefault().getID(), new java.util.Date())) > EOF ... America/Los_Angeles Wed Dec 30 10:25:45 PST 2015 


:
 $ TZ=Europe/London cat << EOF | scala -Duser.timezone=Europe/Paris java.util.TimeZone.setDefault(java.util.TimeZone.getTimeZone("America/Los_Angeles")) print("%s\n%s\n".format(java.util.TimeZone.getDefault().getID(), new java.util.Date())) EOF ... America/Los_Angeles Wed Dec 30 10:37:28 PST 2015 




, / . — , - , "+2 " .

Linux libc /usr/share/zoneinfo. tzdata, . . , Linux .

, , — . .

But not everything is so simple.

Java . , , OpenJDK tzdata-java , Oracle JDK JDK , JDK.

, Joda-time tzdata, JVM — , , .

python ( ) .

javascript - , Google Closure .

, , . , Lightning Thunderbird sqlite- , . .

, , ( ), tzdata — JVM.

Android. — Android . tzdata, , ( ). , . , , , tzdata . , - ( Europe/Minsk Europe/Moscow). Europe/Moscow (GMT+4) — . , .

Calendars



. . , , , , , .

, , -. — . , and we all just do not think that our calendar is just one of many, perhaps the most used, but not the only one. You can play with some here .

Leap year



Leap year is a year in which 366 days, not 365 days as in a normal year. In a leap year, one day is added to February - February 29.

The formula for determining that a leap year is simple and described in Wikipedia

Leap second



( ) . , . . , . , — . — .

, , UTC 60- :
 23:59:58 23:59:59 23:59:60 # leap second 00:00:00 00:00:01 


Unix-time 60: «Because it does not handle leap seconds, it is neither a linear representation of time nor a true representation of UTC.»

, - UTC :
 23:59:58 23:59:59 23:59:59 # leap second 00:00:00 00:00:01 


, .

, :


Java, Unix-time, leap-second. API, API, Joda-time. leap-second tzdata , JavaDoc java.util.Date#getSeconds , , , Java- 60 61.

, leap second Java .
API
  @Test public void testLeapSecond1() throws Exception { TimeZone tz = TimeZone.getTimeZone("UTC"); Calendar calendar = Calendar.getInstance(); calendar.setLenient(false); calendar.setTimeZone(tz); calendar.set(2015, Calendar.JUNE, 30, 23, 59, 0); Date d1 = calendar.getTime(); calendar.set(2015, Calendar.JULY, 1, 0, 1, 0); Date d2 = calendar.getTime(); long elapsed = d2.getTime() - d1.getTime(); System.out.println(TimeUnit.MILLISECONDS.toSeconds(elapsed)); } 

 120



— 120 , 121 .

API:
API
  @Test public void testLeapSecond2() throws Exception { ZonedDateTime beforeLeap = ZonedDateTime.of(2015, 6, 30, 23, 30, 0, 0, ZoneOffset.UTC); ZonedDateTime afterLeap = ZonedDateTime.of(2015, 7, 1, 0, 30, 0, 0, ZoneOffset.UTC); long elapsed = afterLeap.toInstant().toEpochMilli() - beforeLeap.toInstant().toEpochMilli(); System.out.println(TimeUnit.MILLISECONDS.toSeconds(elapsed)); } 

 3600



3600 , 3601.

, . — .

. /usr/share/zoneinfo/right.
 $ file /usr/share/zoneinfo/right/UTC /usr/share/zoneinfo/right/UTC: timezone data, version 2, 1 gmt time flag, 1 std time flag, 26 leap seconds, no transition times, 1 abbreviation char $ zdump -v /usr/share/zoneinfo/right/UTC | grep '59:60' | wc -l 26 


/usr/share/zoneinfo .
 $ file /usr/share/zoneinfo/UTC /usr/share/zoneinfo/UTC: timezone data, version 2, 1 gmt time flag, 1 std time flag, no leap seconds, no transition times, 1 abbreviation char $ zdump -v /usr/share/zoneinfo/UTC | grep '59:60' | wc -l 0 


— 26 .

1970-01-01 00:00:00 UTC 2016-01-01 00:00:00 UTC. : Java ( Unix-time) - , .

Java:
1970-01-01 00:00:00 UTC 2016-01-01 00:00:00 UTC - Java
  @Test public void testEpochDiff() throws Exception { ZonedDateTime s = ZonedDateTime.of(1970, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); ZonedDateTime f = ZonedDateTime.of(2016, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); long elapsed = f.toInstant().toEpochMilli() - s.toInstant().toEpochMilli(); System.out.println(TimeUnit.MILLISECONDS.toSeconds(elapsed)); } 

1451606400



1451606400, :
 $ dateutils.ddiff --from-zone "right/UTC" -f '%S' "1970-01-01 00:00:00" "2016-01-01 00:00:00" 1451606400 


— 1451606400 , :
 $ dateutils.ddiff --from-zone "right/UTC" -f '%rS' "1970-01-01 00:00:00" "2016-01-01 00:00:00" 1451606425 


, 1451606425 . 25, 26 , .



, — Java. , — . — .

currentTimeMillis(), nanoTime()



Java : , — .



:




, - — java.lang.System#nanoTime. java.lang.System#currentTimeMillis , . JavaDoc .

, java.lang.Thread :
java.lang.Thread#join(long)
  public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } } 



, - , , java.util.concurrent.ExecutorService, System.nanoTime.

, System.currentTimeMillis() — .



— .

— , , -, , .

: , . IoC, , low-coupling high-cohesion. , . , . — . , , . , .

, :
org.myproject.Chronometer
 public interface Chronometer { Date getCurrentMoment(); long getCurrentMs(); long getCurrentTicks(); } 



, . (org.myproject.Timer) .

— , , :
 #   user.setCreated(new Date()); user.setModified(new Date()); #   Date now = chronometer.getCurrentMoment(); user.setCreated(now); user.setModified(now); #   Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.DAY_OF_YEAR, -3); Date expiration = calendar.getTime(); #   Calendar calendar = Calendar.getInstance(); calendar.setTimeZone(this.operationTimeZone) calendar.setTime(chronometer.getCurrentMoment()); calendar.add(Calendar.DAY_OF_YEAR, -3); Date expiration = calendar.getTime(); 



, -.

.
 #   -   DELETE FROM records WHERE created <= DATE_SUB(NOW(), INTERVAL 30 DAY) #    -       DELETE FROM records WHERE created <= DATE_SUB(:now, INTERVAL 30 DAY) #    -       /  DELETE FROM records WHERE created <= :expiration 



, , — 1953 2312:
Sample test
 //  ""  mockChronometer.setCurrentTime("2120.06.10 15:33:11"); //      ""  Period period = new Period(); period.setStart(TimeUtils.parse("2120.06.01 00:00:00")); period.setEnd(TimeUtils.parse("2120.07.01 00:00:00")); period.setIndex("wdwwddwwdw"); period.setType(PeriodType.MONTH); period.setDescription("efefef"); periodsDao.save(period); // -  -         "2120.06.10 15:33:11". //   ,      . //         -     (  ). checkSomething1(); // ...  2     mockChronometer.setCurrentTime("2120.06.12 15:38:14"); //            checkSomething2(); 



/ , MockChronometer TimeUtils — . — . - UTC , .

Java 8 Date Time API java.time.Clock , — , .

— JVM , System.nanoTime() System.currentTimeMillis(). , . — System.nanoTime(), System.currentTimeMillis(), new Date(), Calendar.getInstance() .

Spring Framework



8 Spring LocaleResolver, TimezoneResolver (, , ). , DispatcherServlet.

, ( ), 4- .

MVC



— , , .

FreeMarker :
 <#setting time_zone="Europe/Moscow"> 


JSP :
 <fmt:formatDate type="both" value="${now}" timeZone="Europe/Moscow"/> <fmt:timeZone value="Europe/Moscow"> <fmt:formatDate type="both" value="${now}"/> </fmt:timeZone> 


Velocity - , .



— java.util.Date#getTime() long- Unix-time. long java.util.Date . Hibernate RowMapper'. , . , , MySQL, FROM_UNIXTIME.

, / . (, , ), , . , /:


, . long - ( ) .

, , .

, , . MySQL : TIMESTAMP and DATETIME.

, TIMESTAMP , «2015-01-01 12:00:00 MSK» , «2015-01-01 09:00:00 UTC» UTC, . DATETIME, MSK- «2015-01-01 12:00:00 MSK», MySQL UTC- «2015-01-01 12:00:00 UTC», .

MySQL. :
 $ sudo docker run --name mysql-time -e MYSQL_ROOT_PASSWORD=root -d mysql/mysql-server:5.7 $ sudo docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 82bb3eebc8bc mysql/mysql-server:5.7 /entrypoint.sh mysq 5 minutes ago Up 5 minutes 3306/tcp mysql-time $ sudo docker exec -it 82bb3eebc8bc bash [root@82bb3eebc8bc /]# TZ=Europe/Moscow mysql -u root -p Enter password: mysql> CREATE DATABASE test; Query OK, 1 row affected (0.00 sec) mysql> use test; Database changed mysql> CREATE TABLE dates (id INTEGER, t1 TIMESTAMP, t2 DATETIME); Query OK, 0 rows affected (0.02 sec) 



«Europe/Moscow» :
Europe/Moscow
 mysql> SET time_zone = 'Europe/Moscow'; Query OK, 0 rows affected (0.00 sec) mysql> SELECT @@session.time_zone; +---------------------+ | @@session.time_zone | +---------------------+ | Europe/Moscow | +---------------------+ 1 row in set (0.00 sec) mysql> INSERT INTO dates VALUES (1, '2015-01-01 12:00:00', '2015-01-01 12:00:00'); Query OK, 1 row affected (0.00 sec) mysql> SELECT * FROM dates WHERE id = 1; +------+---------------------+---------------------+ | id | t1 | t2 | +------+---------------------+---------------------+ | 1 | 2015-01-01 12:00:00 | 2015-01-01 12:00:00 | +------+---------------------+---------------------+ 1 row in set (0.00 sec) 



«UTC» :
UTC
 mysql> SET time_zone = 'UTC'; Query OK, 0 rows affected (0.00 sec) mysql> SELECT @@session.time_zone; +---------------------+ | @@session.time_zone | +---------------------+ | UTC | +---------------------+ 1 row in set (0.00 sec) mysql> SELECT * FROM dates WHERE id = 1; +------+---------------------+---------------------+ | id | t1 | t2 | +------+---------------------+---------------------+ | 1 | 2015-01-01 09:00:00 | 2015-01-01 12:00:00 | +------+---------------------+---------------------+ 1 row in set (0.00 sec) 



, t1 — , t2 .

, . , ( ), ( ). , , — . , ( ), .

Many databases have types of types that, in addition to the time point itself, store additional and information about the time zone in which it was entered. For example, this can be useful if you need to know which particular time zone was primary for this value.

NTP



, , , — NTP .

— , , NTP .

, UDP , .

, 10.

Notifications



( ) : (, , ) (, , , ). , , . - 10 20 . , .

, - , . , - ( ) , , .

— , . : Android TV, ; Android-, . , . , , .

Example
 Date now = chronometer.getCurrentMoment(); TimeZone timeZone = userDeviceRecord.getTimeZone(); Calendar calendarFrom = Calendar.getInstance(timeZone); calendarFrom.setTime(now); calendarFrom.set(Calendar.HOUR_OF_DAY, comfortHourFrom); Calendar calendarTill = Calendar.getInstance(timeZone); calendarTill.setTime(now); calendarTill.set(Calendar.HOUR_OF_DAY, comfortourHourTill); if (now.after(calendarTill.getTime())) { calendarFrom.add(Calendar.DAY_OF_YEAR, +1); } long delayMs = Math.max(calendarFrom.getTime().getTime() - now.getTime(), 0); notificationService.sendNotificationWithDelay(msg, delayMs); 



AM / PM



, , , 12- .

, : am (. ante meridiem — « ») pm (. post meridiem — « »).

, — 12pm 0am, 12am. , — 12am 0pm, 12pm. , .



— Linux «The Hardware Clock».



: , , - . , .

Hardware Clock ( ) Linux hwclock. ( /dev/rtc), root.

 $ sudo hwclock Wed 30 Dec 2015 17:59:12 MSK -0.328637 seconds 


Hardware Clock . ?

, Linux , (The System Time). Linux , , . — Hardware Clock , , . Linux Hardware Clock, — .

— . , hwclock

( - ).

, . Hardware Clock — . hwclock . Hardware Clock - . hwclock Hardware Clock , /etc/adjtime. Hardware Clock System Time , .

, - System Time . , - , System Time . — ( 11 ) System Time Hardware Time.

hwclock .

, Linux Hardware Clock . , Hardware Clock yyyy, MM, dd, HH, mm, ss . , .

: UTC, (, -).

BIOS . BIOS , , BIOS ( — Windows ); , BIOS UTC ( — ).

Windows , Hardware Clock . Linux Hardware Clock, UTC ( ). , , BIOS Windows, Linux .

Debian/Ubuntu :
 $ cat /etc/default/rcS | grep UTC # assume that the BIOS clock is set to UTC time (recommended) UTC=yes 




- , , Ubuntu/Debian faketime , .

 $ date Wed Dec 30 22:53:11 MSK 2015 $ faketime -f '+2y' date Fri Dec 29 22:53:39 MSK 2017 


Birthday



- , «15 », . , «15 2001 », . , . -, . -, . , , , .

, ? , :


Japan-France
  @Test public void testBirthday() throws Exception { TimeZone japanTz = TimeZone.getTimeZone("Japan"); Calendar japanCalendar = Calendar.getInstance(japanTz); japanCalendar.setLenient(false); japanCalendar.setTimeInMillis(0); japanCalendar.set(2016, Calendar.APRIL, 15, 9, 0, 0); System.out.println("Japan 2016-04-15 09:00:00: " + japanCalendar.getTimeInMillis()); japanCalendar.set(2016, Calendar.APRIL, 15, 21, 0, 0); System.out.println("Japan 2016-04-16 21:00:00: " + japanCalendar.getTimeInMillis()); TimeZone franceTz = TimeZone.getTimeZone("Europe/France"); Calendar franceCalendar = Calendar.getInstance(franceTz); franceCalendar.setLenient(false); franceCalendar.setTimeInMillis(0); franceCalendar.set(2016, Calendar.APRIL, 14, 9, 0, 0); System.out.println("France 2016-04-14 09:00:00: " + franceCalendar.getTimeInMillis()); franceCalendar.set(2016, Calendar.APRIL, 14, 21, 0, 0); System.out.println("France 2016-04-14 21:00:00: " + franceCalendar.getTimeInMillis()); franceCalendar.set(2016, Calendar.APRIL, 15, 9, 0, 0); System.out.println("France 2016-04-15 09:00:00: " + franceCalendar.getTimeInMillis()); franceCalendar.set(2016, Calendar.APRIL, 15, 21, 0, 0); System.out.println("France 2016-04-15 21:00:00: " + franceCalendar.getTimeInMillis()); } 

Japan 2016-04-15 09:00:00: 1460678400000
Japan 2016-04-16 21:00:00: 1460721600000
France 2016-04-14 09:00:00: 1460624400000
France 2016-04-14 21:00:00: 1460667600000
France 2016-04-15 09:00:00: 1460710800000
France 2016-04-15 21:00:00: 1460754000000





t(f) t(u) — , , — . , , , . , «».



, , . : , , .

, 2000.01.10 00:00:01 . 2016.01.09 23:59:59 . (2000.01.10) (2016.01.09) 16 . 2016.01.09 23:59:59 — 2016.01.10 ( ) 16 . , 16, — .

, / 00:00:00 , . 2016.01.11 00:00:00 — 16 , .

findings





— Date Time API Java 8.

Source: https://habr.com/ru/post/274811/


All Articles