I'm not sure how can I explain just 'time', so I wanna start with an example.
When I write a movie player, I want to represent the start time(00:00:00) in millis as 0, and if current frame position is on 5 minutes(00:05:00), it would be 300,000(5 * 60 * 1000).
I don't care about timezone, calendar or clock information in real world.
However, JodaTime, for example, Instant is related to Unixtime(based on 1970), Interval, Duration and Period are based on Instant, and LocalTime is based on 24-hour(which enforce hour field to 1-24).
Of course, I can parse "00:05:00" into 300,000 manually, but I wonder how can I achieve this goal with JodaTime or other library.
Duration
Your statement about the Duration class is incorrect. You said it is based on Instant, but, no, it is not.
A Duration represents a span of time not attached to the timeline. A Duration in Joda-Time is merely a count of milliseconds. This fact is stated in its Javadoc.
Ditto for Period, which represents a span-of-time unattached to the timeline on a scale of years-months-days. A Duration works on a scale of hours-minutes-seconds.
To represent a span of five minutes:
Duration d = Duration.standardMinutes( 5 ) ;
If you want to get the moment in the future by that amount of time, add that duration to the current moment.
Instant fiveMinutesInFuture = new Instant().plus( d ) ;
ISO 8601
To represent a duration as text, use the standard ISO 8601 format, with a P at the beginning and a T separating the years-months-days portion from the hours-minutes-seconds portion.
So five minutes is PT5M.
String output = d.toString() ;
Parse such ISO 8601 strings.
Duration d = Duration.parse( "PT5M" ) ; // Five minutes.
Clock format
For serializing a duration object as text, always use the ISO 8601 standard format, never clock format.
For user-interface presentation, I suggest you not represent a span of time using clock notation such as 00:05:00. Doing so is confusing and ambiguous to users. Use the ISO 8601 standard formats instead, as they are designed to be practical and unambiguous.
If you insist on representing the duration in clock time format, call the getStandard… methods to get the hours, minutes, and seconds.
String output =
String.format( "%02d", d.getStandardHours() ) +
":" +
String.format( "%02d", d.getStandardMinutes() ) +
":" +
String.format( "%02d", d.getStandardSeconds() )
;
00:05:00
If the duration might be over 24 hours long, you will also need to call getStandardDays. These days are merely chunks of time of 24-hours long. Such days are not calendar days as the Duration is not attached to the timeline.
java.time
Be aware that Joda-Time is now in maintenance mode. It’s successor, java.time, is built into Java 8 and later as well as Android 26+. For Java 6 & 7 use ThreeTen-Backport. For earlier Android, use ThreeTenABP.
The concepts are the same in java.time as discussed here. The java.time.Duration class is quite similar except for the much finer resolution of nanoseconds and a slightly different syntax.
Instant.now().plus(
Duration.ofMinutes( 5 )
)
Or:
Instant.now().plus(
Duration.parse( "PT5M" )
)
To build a clock-reading, call the to…Part methods.
String output =
String.format( "%02d", d.toHoursPart() ) +
":" +
String.format( "%02d", d.toMinutesPart() ) +
":" +
String.format( "%02d", d.toSecondsPart() )
;
00:05:00
Related
IIUC java.time should end multiple date libraries and related issues, and offer something good and standardized. So it was kinda shock to me to learn, that java.time.Duration violates iso8601 standard. While it should be possible to parse: P2Y1DT1S, as that is valid by standard, java.time.Duration will parse only up to days (so you have to workaround via P731DT1S and java.time.Period can't do that at all, since it does not allow temporal part.
Questions:
Why? Is there a valid reason not to have interval parsing according to specification? What am I overlooking?
Or is there some readily available class, which will parse ISO8601?
Well, parsing such a string means it yields an object which supports both concepts to be present at the same time – a sort of PeriodDuration. But there is none in Java, and its rationale is explained here by JodaStephen:
Note that a Duration contains an amount of seconds, not separate amounts of seconds, minutes and hours. The amount of seconds can exceed 24 hours, thus a Duration can represent a "day". But it is a fixed 24 hours day. By contrast, the representation of a "day in Period is descriptive and takes into account DST. The state of a Period is formed from three separate fields - days, months and years.
Bear in mind that "The customer occupied the hotel room for 2 days and seventeen and a half hours, P2DT17H30M" has the possibility to be complicated by DST cutovers. Using Period and Duration separately things are clear - Period is affected by DST cutovers and Duration is not.
(Emphasis mine.)
So in short, the two concepts handle DST differently, and combining the two complicates things.
Regarding your second question, this answer suggests the org.threeten.extra.PeriodDuration class offered by the ThreeTen-Extra library.
tl;dr
Use the org.threeten.extra.PeriodDuration class.
PeriodDuration
.between(
LocalDate
.of( 2023 , Month.JANUARY , 23 )
.atTime( LocalTime.NOON )
.atZone( ZoneId.of( "America/New_York" ) ) ,
LocalDate
.of( 2023 , Month.JANUARY , 23 )
.atTime( LocalTime.NOON )
.atZone( ZoneId.of( "America/New_York" ) )
.plusDays( 2 )
.plusHours( 17 )
.plusMinutes( 30 )
)
.normalizedStandardDays()
.toString()
P2DT17H30M
Details
The Answer by MC Emperor is correct and wise. Always think twice before making use of a time span combining years-months-days with hours-minutes-seconds.
PeriodDuration
But if you insist on that combination, there is a class for that. You will find it in the ThreeTen-Extra library. This library is led by the same man, Stephen Colebourne, who leads the java.time (JSR 310) framework and its predecessor, Joda-Time.
The org.threeten.extra.PeriodDuration class represents an amount of time in the ISO 8601 calendar system that combines a period and a duration.
Here is some example code.
ZoneId z = ZoneId.of( "America/New_York" );
LocalDate ld = LocalDate.of( 2023 , Month.JANUARY , 23 );
LocalTime lt = LocalTime.NOON;
ZonedDateTime start = ZonedDateTime.of( ld , lt , z );
ZonedDateTime end = start.plusDays( 2 ).plusHours( 17 ).plusMinutes( 30 );
PeriodDuration pd = PeriodDuration.between( start , end );
Dump to console.
System.out.println( start + "/" + end + " = " + pd );
When run:
2023-01-23T12:00-05:00[America/New_York]/2023-01-26T05:30-05:00[America/New_York] = P3DT-6H-30M
Notice in that result we have negative amounts for hours and minutes. This is because the two-phase logic:
First, examine dates, LocalDate objects. Use Period.between to count days where the beginning is inclusive while the ending is exclusive. From the 23rd to the 26th is 3 days.
Second, examine time-of-day values, LocalTime objects. Use Duration.between to calculate elapsed time. We are calculating a duration between noon and 5:30 AM, so we must go back in time six and a half hours.
Therefore our result is 3 days minus 6.5 hours.
You can see this logic in the open source code.
If you want to smooth out this result to flip the negative hours-minutes, add call to PeriodDuration#normalizedStandardDays().
PeriodDuration pd = PeriodDuration.between( start , end ).normalizedStandardDays();
2023-01-23T12:00-05:00[America/New_York]/2023-01-26T05:30-05:00[America/New_York] = P2DT17H30M
P2DT17H30M = P3DT-6H-30M
We now get 2 days and 17.5 hours, P2DT17H30M. This is the same amount of time as 3 days minus 6.5 hours, P3DT-6H-30M.
I am trying to convert 19 digit Unix timestamp such as 1558439504711000000 (one and a half quintillion) into a readable date/time format. My timestamp ends with 6 zeros which suggests the time is in nano seconds.
I have come across some examples where people have used time zones which I don't need. Another example uses ofEpochSecond like so:
Instant instant = Instant.ofEpochSecond(seconds, nanos);
But I am not sure whether I need to use ofEpochSecond.
The code below gives my most recent approach of achieving this:
String timeStamp = "1558439504711000000";
long unixNanoSeconds = Long.parseLong(timeStamp);
Date date = new java.util.Date(timeStamp*1000L);
// My preferred date format
SimpleDateFormat sdf = new java.text.SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
String formattedDate = sdf.format(date);
System.out.println("The timestamp in your preferred format is: " + formattedDate);
But the output I get is something like this:
// The timestamp in your preferred format is: 11-12-49386951 11:43:20
Which does not show the year format in e.g. 2019 format.
tl;dr
Never use legacy class java.util.Date. Instead, use modern java.time.Instant.
Instant // The modern way to represent a moment in UTC with a resolution of nanoseconds. Supplants the terrible `java.util.Date` class.
.ofEpochSecond( // Parse a count since epoch reference of 1970-01-01T00:00:00Z.
0L , // Passing zero for the count of whole seconds, to let the class determine this number from the 2nd argument.
Long.parse( "1558439504711000000" ) // Count of nanoseconds since the epoch reference of 1970-01-01T00:00:00Z.
) // Returns a `Instant` object.
.atZone( // Adjust from UTC to the wall-clock time used by the people of a specific region (a time zone).
ZoneId.of( "Europe/London" )
) // Returns a `ZonedDateTime` object. Same moment as the `Instant`, same point on the timeline, different wall-clock time.
.format( // Generate text to communicate the value of the moment as seen through this time zone.
DateTimeFormatter.ofPattern( // Define how to format our generated text.
"dd-MM-uuuu HH:mm:ss" , // Specify your desired formatting pattern.
Locale.UK // Pass a `Locale` to be used in localizing, to (a) determine human language used in translating name of day-of-week and such, and (b) determine cultural norms to decide issues of capitalization, abbreviation, etc. Not really needed for this particular formatting pattern, but a good habit to specify `Locale`.
) // Returns a `DateTimeFormatter` object.
) // Returns a `String` object containing our text.
21-05-2019 12:51:44
…or…
Instant
.ofEpochSecond (
TimeUnit.NANOSECONDS.toSeconds(
Long.parse( "1558439504711000000" )
) ,
( 1_558_439_504_711_000_000L % 1_000_000_000L )
)
.toString()
2019-05-21T11:51:44.711Z
Note the hour difference because the time zone is one hour ahead of UTC.
Avoid legacy date-time classes
The java.util.Date class is terrible. Along with its littermates such as Calendar & SimpleDateFormat, they amount to a awful mess. Avoid them. Sun, Oracle, and the JCP community gave up on them when they adopted JSR 310.
Instant
A java.util.Date object represents a moment in UTC, with a resolution of milliseconds. Its replacement is java.time.Instant, also a moment in UTC but with a resolution of nanoseconds. Internally, both track a count since the epoch reference of first moment of 1970 in UTC.
To avoid dealing with gigantic numbers, internally a Instant tracks a number of whole seconds since 1970 plus a fractional second kept as a number of nanoseconds. Two separate numbers. Those are what you need to feed Instant.ofEpochSecond.
Parse your input string as a long using the Long class. By the way, notice that your value is pushing towards to the limit of a 64-bit integer.
long totalNanos = Long.parse( "1558439504711000000" ) ;
Use the TimeUnit enum to do the math of splitting out whole seconds.
long secondsPortion = TimeUnit.NANOSECONDS.toSeconds( totalNanos ) ;
Modulo by a billion, the remainder being the nanoseconds of the fractional second.
long nanosPortion = ( totalNanos % 1_000_000_000L ) ;
Instantiate an Instant.
Instant instant = Instant.ofEpochSecond( secondsPortion , nanosPortion ) ;
My timestamp ends with 6 zeros which suggests the time is in nano seconds.
Actually, nanoseconds count up to a billion, so nine (9) digits not six (6). The fractional second in your count from epoch is 711000000, or 711,000,000 nanos. Your number of whole seconds is 1558439504, or 1,558,439,504 (one and a half billion). As a decimal:
1,558,439,504.711000000 seconds since 1970-01-01T00:00Z
Time Zone
I have come across some examples where people have used time zones which I don't need.
To represent a moment, a specific point on the timeline, you always need a time zone (or offset-from-UTC of hours-minutes-seconds).
To see that same moment through the wall-clock time used by the people of a particular region (a time zone), apply a ZoneId to get a ZonedDateTime.
Specify a proper time zone name in the format of Continent/Region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 2-4 letter abbreviation such as BST or EST or IST as they are not true time zones, not standardized, and not even unique(!).
ZoneId z = ZoneId.of( "Europe/London" ) ;
ZonedDateTime zdt = instant.atZone( z ) ; // Same moment, same point on the timeline, different wall-clock time.
2019-05-21T12:51:44.711+01:00[Europe/London]
Notice the adjustment in the time-of-day, going from hour 11 to hour 12. This makes sense as Europe/London zone is an hour ahead of UTC on that date. Same moment, same point on the timeline, different wall-clock time.
Shortcut
As Ole V.V. noted in the comment, you could skip the math discussed above. Feed the entire number of nanoseconds as the second argument to ofEpochSecond. The class internally does the math to separate whole seconds from the fractional second.
Instant instant = Instant.ofEpochSecond( 0L , 1_558_439_504_711_000_000L ) ;
See this code run live at IdeOne.com.
Generate text
Generate text representing the value of that ZonedDateTime in standard ISO 8601 format extended to append the name of the time zone in square brackets.
String output = zdt.toString() ;
2019-05-21T12:51:44.711+01:00[Europe/London]
Or let java.time automatically localize for you.
Locale locale = Locale.UK;
DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime( FormatStyle.SHORT ).withLocale( locale );
String output = zdt.format( f );
21/05/2019, 12:51
Or specify a custom format.
Locale locale = Locale.UK;
DateTimeFormatter f = DateTimeFormatter.ofPattern( "dd-MM-uuuu HH:mm:ss" , locale ) ;
String output = zdt.format( f );
21-05-2019 12:51:44
Tip: Be very careful about providing a date-time without specifying the zone explicitly. This creates ambiguity, where the user may assume a different zone/offset is in play.
I think there is nothing wrong with that, you are dealing with a timestamp that represent a date in the FUTURE (a really far away date in the future).
If you consider this:
String timeStamp = "1558439504";
this should give you: 05/21/2019 # 11:51am (UTC)
Then there is I think an easy way to get the Date. Just create the Instant first based on that timestamp and then do:
Date myDate = Date.from(instant);
Try using this
Date date = new java.util.Date(timeStamp/1000000);
Instead of multiplying by 1000, divide by 1000000
How to convert json date to java.util.Date
{"date":31,"day":4,"hours":0,"minutes":0,"month":11,"seconds":0,"time":2145805200000,"timezoneOffset":-420,"year":137}
Thanks for help
With a quick Google search, I managed to find this webpage: CLICK
The 'parse' method returning a Date shows you how to use the SimpleDateFormat class in Java which is used to parse a String into a Date.Hope this helped!
No JSON Date
No such thing as a JSON date. JSON has very few data types, none of which are date-time related.
Count Since Epoch
That time item with value 2145805200000 is probably a count since epoch. The two questions are: What epoch? and What granularity of count?
Commonly used are milliseconds since the Unix epoch of first moment of 1970 in UTC.
The java.time framework built into Java 8 and later can translate such number. The Instant class represents a moment on the timeline in UTC.
long sinceEpoch = 2145805200000L;
Instant instant = Instant.ofEpochMilli ( sinceEpoch );
Dump to console.
System.out.println ( "instant: " + instant );
instant: 2037-12-30T17:00:00Z
That value may be close to the other fields in your JSON, but is not quite a match.
Your JSON has an field timezoneOffset with value -420. If we interpret that number as an offset-from-UTC in minutes, that would mean 7 hours. We can ask java.time to adjust the Instant into such an offset.
Normally we would want to use a time zone for this adjustment. A time zone is an offset-from-UTC plus a set of historic rules for handling anomalies such as Daylight Saving Time (DST). But in this case we have only the offset-from-UTC. So rather than use a ZoneId we use the subclass ZoneOffset. The result is a ZonedDateTime.
ZoneOffset zoneOffset = ZoneOffset.ofHours ( 7 );
ZonedDateTime zdt = ZonedDateTime.ofInstant ( instant , zoneOffset );
Dump to console.
System.out.println ( "instant: " + instant + " + zoneOffset: " + zoneOffset + " = zdt: " + zdt );
instant: 2037-12-30T17:00:00Z + zoneOffset: +07:00 = zdt: 2037-12-31T00:00+07:00
That gets us to the beginning of the day on the 31st of December in 2037. Still not an exact match to the other JSON fields.
But if that -420 was meant as being 7 hours behind UTC rather than ahead, we need to change that offset from 7 hours to a negative -7 hours. Then the results would be:
instant: 2037-12-30T17:00:00Z + zoneOffset: -07:00 = zdt: 2037-12-30T10:00-07:00
If you knew more about your JSON data, then perhaps a solution could be found.
In my application, it saves a preference which is an integer that is the number of days since the epoch. (not relevant but it is used to create backups every x days)
Given this value, how can I reliably create an instance of joda DateTime?
I'm tempted to convert it to milliseconds by doing the multiplication value * 24 * 60 * 60 * 1000 but this will be wrong due to astronomy / solar time?
Multiplying number of days into the number of milliseconds might be more readable if you use a library function. I highly recommend using Joda. :)
You have a number of days since epoch (GMT), and you want a DateTime (date + time + timezone). At a bare minimum, before you get further, you'll need to specify how you want to treat the time and timezone calculation.
The simplest way (which might not be what you intend) would be to create an instant in the local timezone representing the beginning of epoch, and then use plusDays to add the right number of days:
// in class - note that this will cache the current default timezone
private static final DateTime EPOCH_START_INSTANT = new DateTime(0);
// West of Greenwich, this will actually represent the "day" before.
// Day 0 will be Dec 31, 1969, local time.
DateTime localTime = EPOCH_START_INSTANT.plusDays(yourDayCount);
For the purpose of creating a backup every X days, you might instead want a LocalDate initialized at epoch (January 1, 1970), plus the number of days you want. That could then be changed to a specified local time relatively easily.
// in class
private static final EPOCH_LOCALDATE = new LocalDate(1970, 1, 1);
private static final THREE_AM = new LocalTime(3, 0);
LocalDate localDate = EPOCH_LOCALDATE.plusDays(yourDayCount);
// Midnight (or closest valid time thereto) in the default time zone
DateTime startOfDay = localDate.toDateTimeAtStartOfDay();
// 3 AM in the default time zone
DateTime threeAM = localDate.toDateTime(THREE_AM);
The answer by Jeff Bowman is correct.
I'll show the same idea in the java.time framework, intended to succeed Joda-Time.
java.time
Java 8 and later has the new java.time framework built-in. These new classes supplant the old java.util.Date/.Calendar classes. They are inspired by Joda-Time, defined by JSR 310, and extended by the ThreeTen-Extra project.
I am assuming your count-of-day-from-epoch is in UTC. So we can use the Instant class, basically a count of nanoseconds from the first moment of 1970 in UTC.
long myCountOfDays = 16_721L;
Instant instant = Instant.EPOCH.plus ( myCountOfDays , ChronoUnit.DAYS );
Let's adjust into a time zone. Choosing Montreal arbitrarily. Use a proper time zone name, never the 3-4 letter codes like "EST" or "IST".
ZoneId zoneId = ZoneId.of ( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.ofInstant ( instant , zoneId );
Dump to console.
System.out.println ( "myCountOfDays: " + myCountOfDays + " from epoch: " + Instant.EPOCH + " in UTC is: " + instant + " and in Montréal is: " + zdt + "." );
When run.
myCountOfDays: 16721 from epoch: 1970-01-01T00:00:00Z in UTC is: 2015-10-13T00:00:00Z and in Montréal is: 2015-10-12T20:00-04:00[America/Montreal].
According to the FAQ:
Joda-Time does not support leap seconds. Leap seconds can be supported by writing a new, specialized chronology, or by making a few enhancements to the existing ZonedChronology class. In either case, future versions of Joda-Time will not enable leap seconds by default. Most applications have no need for it, and it might have additional performance costs.
That suggests to me that you need not worry about that aspect.
But rather than doing the math, I would use DateTime#plusDays or MutableDateTime#addDays instead, using The Epoch as your starting point.
I assume, though, that your "days since The Epoch" is allowing for leap days (and that you're using the Gregorian chronology so JodaTime is, too).
Date d = new Date(today.getTimeInMillis());
Date d1 = new Date(dueDate.getTimeInMillis());
int daysUntil = (int) ((d1.getTime() - d.getTime())/ (1000 * 60 * 60 * 24));
Using the above code, where today is a calendar set to 00:00 on the current day, and dueDate is set to 00:00 on the date I am comparing today to, my results from this differ.
There is something in this which varies, making my output either x or x+1 where x is the correct answer.
What is the issue here, and what can I do to make it more stable?
Vague Question
You do not provide actual values, so we cannot determine precisely the problem. We do not know what the today and dueDate variables are.
Outmoded
The question is now outmoded, as the troublesome old date-time classes including java.util.Date/.Calendar have been supplanted by the new java.time framework. See Tutorial. Defined by JSR 310, inspired by Joda-Time, and extended by the ThreeTen-Extra project.
In java.time:
An Instant is a moment on the timeline in UTC.
A ZoneId represents a time zone. Use proper time zone names, never the 3-4 letter codes like "EST" or "IST" as they are neither standardized nor unique.
Conceptually, ZonedDateTime = Instant + ZoneId.
ThreeTen-Extra
Unfortunately, java.time does not include a facility for calculating days elapsed between date-time values. We can use the ThreeTen-Extra project and its Days class with between method to provide that calculation. The ThreeTen-Extra project is a collection of features deemed non-essential for java.time during the JSR process.
ZoneId zoneId = ZoneId.of ( "America/Montreal" );
ZonedDateTime now = ZonedDateTime.now ( zoneId );
ZonedDateTime then = now.minusDays ( 4 );
ZonedDateTime due = now.plusDays ( 3 );
Integer days = org.threeten.extra.Days.between ( then , due ).getAmount ();
Dump to console.
System.out.println ( "From then: " + then + " to due: " + due + " = days: " + days );
From then: 2015-10-31T16:01:13.082-04:00[America/Montreal] to due: 2015-11-07T16:01:13.082-05:00[America/Montreal] = days: 7
Joda-Time
For Android or older versions of Java, use the excellent Joda-Time library.
The Days class is smart and handles anomalies such as Daylight Saving Time (DST).
Note that unlike java.util.Date, a Joda-Time DateTime object knows its own time zone.
// Specify a time zone rather than rely on default.
DateTimeZone timeZone = DateTimeZone.forID( "America/Regina" ); // Or "Europe/London".
DateTime now = new DateTime( timeZone );
DateTime startOfToday = now.withTimeAtStartOfDay();
DateTime fewDaysFromNow = now.plusDays( 3 );
DateTime startOfAnotherDay = fewDaysFromNow.withTimeAtStartOfDay();
Days days = Days.daysBetween( startOfToday, startOfAnotherDay );
Dump to console…
System.out.println( days.getDays() + " days between " + startOfToday + " and " + startOfAnotherDay + "." );
When run…
3 days between 2014-01-21T00:00:00.000-06:00 and 2014-01-24T00:00:00.000-06:00.
There are mainly two reasons why your code is broken:
second parts or millisecond fractions (you might have overlooked)
daylight saving effects
I demonstrate and explain the second reason.
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date d1 = sdf.parse("2016-03-20");
Date d2 = sdf.parse("2016-03-28");
int daysUntil = (int) ((d2.getTime() - d1.getTime()) / (1000 * 60 * 60 * 24));
System.out.println(daysUntil); // 7 (should be 8)
The code was run in timezone "Europe/Berlin". Due to the change from winter time to summer time causing a jump of clocks by one hour forward on 2016-03-27 at 2 am, there is one hour missing. One day has only 23 hours so the division by 24 yields zero resulting in counting one day less.
What can you do else?
Your workaround adding 1000 milliseconds to dueDate sounds as if you have overlooked possible millisecond deltas in your input. This might solve a special case but will usually not be sufficient to solve the daylight saving problem, too. Whatever you choose on base of java.util.Date it is a more or less an evil hack.
The best I have in mind (within the scope of Android-built-in stuff) is to construct an instance of java.util.GregorianCalendar and to add successively one day after one until you have passed the due-date, and then count how many days you have added. Not elegant and errorprone because varying millisecond parts can easily be overlooked here, too.
Otherwise you can try various external libraries for this task. There are four available on Android which can calculate elapsed days in an easy way.
Date4J (main advantage: very small but else limited features)
Threeten-ABP (uses backport of Java-8)
Joda-Time-Android (based on Joda-Time)
Time4A (my own library for Android)