JODA is acting crazy? - java

I'm trying to use JODA to simply convert a numeric timestamp (a long representing Unix epoch time), to a Month Day, Year string.
Here's code I just ran a few seconds ago:
long lTimestamp = 1315600867; // Current timestamp is approx 9/9/11 3:41 PM EST
DateTime oTimestamp = new DateTime(lTimestamp);
String strMon, strDay, strYear;
strMon = oTimestamp.monthOfYear().getAsText(Locale.ENGLISH);
strDay = oTimestamp.dayOfMonth().getAsText(Locale.ENGLISH);
strYear = oTimestamp.year().getAsText(Locale.ENGLISH);
String strDate = strMon + " " + strDay + ", " + strYear;
System.out.println("Converted timestamp is : " + strDate);
The output to this is January 16, 1970!!!
Does this make any sense to anyone?!?!

The long you pass into the DateTime constructor is meant to be in milliseconds, not seconds - so use 1315600867000L instead and it's all fine.
Documentation states:
Constructs an instance set to the milliseconds from 1970-01-01T00:00:00Z using ISOChronology in the default time zone.
If you're getting a value which is already in seconds, you just need to multiply by 1000:
long timestampInSeconds = getValueFromDatabase();
long timestampInMillis = timestampInSeconds * 1000L;
DateTime dt = new DateTime(timestampInMillis);
I'd actually advise you to use Instant in this case rather than DateTime - you don't really have a time zone to consider. If you are going to use DateTime, you should specify the time zone explicitly, e.g.
DateTime dt = new DateTime(timestampInMillis, DateTimeZone.UTC);

Related

Get epoch time from yesterday in a range of hours

First off I'm new in this incredible community. This is an amazing site. I'm happy to be part finally.
Every day I have to insert yesterday's data in the DB. For example, today May 22, I have to insert the data of the 21st from 00:00:00 to 23:59:59 in epoch time.
So far I get the epoch time from today with
long now = Instant.now().toEpochMilli();
How could I get yesterday's epoch time? and store the range of hours in two variables? Like
String startDay = 21/05/2020 00:00:00
String endDay = 21/05/2020 23:59:59
You can use java LocalDate like this:
final LocalDate now = LocalDate.now();
final LocalDate yesterday = now.minusDays(1);
final LocalDateTime start = yesterday.atStartOfDay();
final LocalDateTime end = yesterday.atTime(LocalTime.MAX);
And then format date to your desired format.
You can use a ZonedDateTime as an alternative to the answer given by #ArtyomRebrov.
It will take the system time zone implicitly if you don't provide a specific one.
See this example:
public static void main(String[] args) {
// get a datetime plus time zone information using the system time zone
ZonedDateTime startToday = ZonedDateTime.now()
// subtract a day
.minusDays(1)
// and take the minimum time a day can have
.with(LocalTime.MIN);
// use the same datetime to create the end of the day using the maximum time for a day
ZonedDateTime endToday = startToday.with(LocalTime.MAX);
// then print the results in date-time format and as epoch millis
System.out.println("Yesterday's beginning:\t" + startToday + "\t\t\t| "
+ startToday.toInstant().toEpochMilli());
System.out.println("Yesterday's end:\t" + endToday
+ "\t| " + endToday.toInstant().toEpochMilli());
}
Which outputs (on my maching in Germany):
Yesterday's beginning: 2020-05-21T00:00+02:00[Europe/Berlin] | 1590012000000
Yesterday's end: 2020-05-21T23:59:59.999999999+02:00[Europe/Berlin] | 1590098399999

switch between ofEpochMilli and ofEpochSecond

In my application one of third party API returning timestamp in epoch.
Sometime it returns epoch time in seconds and sometime in miliseconds not confirmed. My application using below code to
convert it to java date and display to user but when I am receiving time in miliseconds it is failing on year.
String time = "1519377196185"; //Time in miliseconds
//String time = "1521575819"; //Time in seconds.
String timeZone = "US/Pacific";
long epochdate = Long.parseLong(time);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM dd, yyyy hh:mm a");
LocalDateTime date34 =
Instant.ofEpochSecond(epochdate)
.atZone(ZoneId.of(timeZone))
.toLocalDateTime();
String date = date34.format(formatter).toString();
System.out.println("date : " + date);
if I use Instant.ofEpochMilli(epochdate) for miliseconds then it is working fine. So my question is how I can know that coming timestamp is in miliseconds or seconds so on that basis I will switch between ofEpochMilli and ofEpochSecond
Year Offset
I would try to parse first the date as seconds, given that if the date is in millis the year would be something extremely big (in this case 50000), then if the year is not greater than the year defined offset (e.g. 3000), that date is returned, otherwise the date is returned as millis.
public ZonedDateTime getZonedDateTime(String time) {
long longTime = Long.parseLong(time), yearOffset = 3000L;
String timeZone = "US/Pacific";
ZoneId zoneId = ZoneId.of(timeZone);
ZonedDateTime zdt = Instant.ofEpochSecond(longTime).atZone(zoneId);
if (zdt.getLong(ChronoField.YEAR_OF_ERA) >= yearOffset) {
return Instant.ofEpochMilli(longTime).atZone(zoneId);
} else {
return zdt;
}
}
Using the functions prints:
getZonedDateTime("1519377196185"); // 2018-02-23T01:13:16.185-08:00[US/Pacific]
getZonedDateTime("1521575819"); // 2018-03-20T12:56:59-07:00[US/Pacific]
Margin of Error
Any method that you decide to use, would have the possibility of an error and that the date is transformed incorrectly, specially when the date is in milliseconds and is too close of the epoch 1970-01-01T00:00:00Z.
The margin errors using the year offset of 3000 would be:
When the date is originally in milliseconds in the range from 1970-01-01T00:00:00Z until 1971-01-12T04:48:00Z
Would be considered as date in seconds
When the date is originally in seconds and the date is equal or after 3000-01-01T00:00:00Z (improbable date in normal apps)
Would be considered as date in milliseconds
Calculate the range of error in milliseconds dates
You can calculate the range of error for dates originally in milliseconds with:
Instant.ofEpochMilli(LocalDateTime.of(3000, 1, 1, 0, 0) // year = 3000
.toEpochSecond(ZoneOffset.UTC)); // 1971-01-12T04:48:00Z
One easy (temporary, sort of) solution would be to just check the length of your time String. If the length is equal to 13, then it represents milliseconds. This will be true until 1 ms after 2286-11-20T17:46:39.999Z, which is a long time from now. The time in seconds would take even more time to reach a length of 13.
Inspect length of String input
As stated by Jacob G. his Answer, check the length.
String input = "1519377196185"; // Milliseconds since epoch.
//String input = "1519377196" ; // Seconds since epoch.
Instant instant = null;
int length = input.length();
switch ( length )
{
case 13:
instant = Instant.ofEpochMilli( Long.parseLong( input ) );
break;
case 10:
instant = Instant.ofEpochSecond( Long.parseLong( input ) );
break;
default:
throw new IllegalStateException( "Unexpected length of input text for count-from-epoch number: " + input + ". Message # 2188d054-5720-4393-9b18-829913d7ba1c." );
}
System.out.println( input + " ➞ " + instant.toString() ); // Generate a String representing textually the value of the `Instant` object.
1519377196185 ➞ 2018-02-23T09:13:16.185Z
Or:
1519377196 ➞ 2018-02-23T09:13:16Z
ZonedDateTime
To move from UTC in an Instant to some particular time zone, provide the context of a time zone (ZoneId) to get a ZonedDateTime object.
ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;
zdt.toString(): 2018-02-23T10:13:16.185+01:00[Africa/Tunis]
LocalDateTime - not appropriate here
The LocalDateTime class is the wrong class to use, as seen in the Question. This class purposely lacks any concept of time zone or offset-from-UTC. So it does not represent a moment, and is not a point on the timeline. This class represents potential moments along a range of about 26-27 hours.
Use LocalDateTime only when the zone/offset is unknown (a terrible situation) or when you need an indefinite value such as “Christmas starts on first moment of December 25, 2018”.
You can solve this other way by converting epoch in seconds to epoch in milliseconds and apply only Instant.ofEpochMilli() for both:
String time = "1519377196185"; //Time in miliseconds or seconds
if(time.length() == 10) { // length of epoch in seconds is 10
time = time + "000";
}
String timeZone = "US/Pacific";
long epochdate = Long.parseLong(time);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM dd, yyyy hh:mm a");
LocalDateTime date34 = Instant.ofEpochMilli(time)
.atZone(ZoneId.of(timeZone))
.toLocalDateTime();
String date = date34.format(formatter).toString();
System.out.println("date: " + date);

UTC-N to Local Time

Using joda, how do you format a UTC+/-n time, to "wall time" to be displayed to the user:
From (UTC+/-n):
2015-05-15T03:28:49.523-04:00
To (EST) Wall:
2015-05-14 23:22:44
Update (1)
Please consider the following code. We need to use timestamp
for writes to and from the DB in UTC. With that in mind:
DateTimeZone.setDefault(DateTimeZone.UTC);
LocalDateTime utcDate = new LocalDateTime();
DateTimeZone utcTZ = DateTimeZone.forTimeZone(TimeZone.getTimeZone("ETC/UTC"));
DateTimeZone localTZ = DateTimeZone.forTimeZone(TimeZone.getTimeZone("America/Montreal"));
DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
fmt.withZone(localTZ);
DateTime localDateTime = utcDate.toDateTime(localTZ);
DateTime utcDateTime = localDateTime.toDateTime(utcTZ);
Timestamp u = new Timestamp(utcDateTime.getMillis());
System.out.println("UTC Time: " + u);
LocalDateTime date = new LocalDateTime(u);
DateTime srcDateTime = date.toDateTime(utcTZ);
DateTime dstDateTime = srcDateTime.toDateTime(localTZ);
System.out.println("UTC+/- Time: " + dstDateTime.toString());
DateTime dateTimeInTargetTimezone = dstDateTime.withZone(localTZ);
System.out.println("Wall Time: " + dateTimeInTargetTimezone.toString("yyy-MM-dd HH:mm:ss"));
Now, when extracting the UTC time from the DB in a Timestamp object, we need
to display the time to the end user in a "Wall/Funeral Time", whatever you want to call it, in their TZ.
Output
UTC Time: 2015-05-15 20:03:47.561 "Good"
UTC+/- Time: 2015-05-15T20:03:47.561-04:00 "Good"
Wall Time: 2015-05-15 20:03:47 "No! No! No! Danger! We'll be late!"
What in the name! Do I have to do to get dstDateTime to equal the time I see on my wall (ie, 2015-05-15 4:03:47).
Update (2)
Got rid of Timestamp:
DateTimeZone utcTZ = DateTimeZone.forTimeZone(TimeZone.getTimeZone("ETC/UTC"));
DateTimeZone localTZ = DateTimeZone.forTimeZone(TimeZone.getTimeZone("America/Montreal"));
DateTimeFormatter fmt = DateTimeFormat.forPattern("yyy-MM-dd HH:mm:ss");
LocalDateTime utcDate = new LocalDateTime(utcTZ);
DateTime utcDateTime = utcDate.toDateTime(utcTZ);
System.out.println("UTC Time: " + utcDateTime);
DateTime dstDateTime = utcDateTime.toDateTime(localTZ);
System.out.println("Unformated Wall Time: " + dstDateTime);
System.out.println("Wall Time: " + dstDateTime.toString(fmt));
Output
UTC Time: 2015-05-20T14:09:28.469Z
Unformated Wall Time: 2015-05-20T10:09:28.469-04:00
Wall Time: 2015-05-20 10:09:28
Everything looks perfect however, when I try to right the UTZ date to the DB,
I need to convert to Timestamp (ie, new Timestamp(o.getOrderDate().getMillis())), and it obviously rights the local time to the DB, and not the UTC Zulu time that I need.
Thanks in Advance,
Nick.
#Nick
I'm not sure if I understood your question correctly but you can try this:
//sample input
String timestamp = "2015-05-15T03:28:49.523-04:00";
//format to parse
DateTimeFormatter dateTimeF = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
//parse to local date time
LocalDateTime dateTime = dateTimeF.parseLocalDateTime(timestamp).minusHours(4);
//output here minus 4 hours
System.out.println(dateTime);
You can improve your implementation from my sample code above and make the offset hours more dynamic but so far this code provides the result as you mentioned in your example.
Goodluck!
Just use the DateTime.withZone method to change the timezone:
#Test
public void change_timezone() {
String input = "2015-05-15T03:28:49.523-04:00";
DateTimeZone targetTimeZone = DateTimeZone.forID("Europe/London");
DateTime dateTime = DateTime.parse(input);
DateTime dateTimeInTargetTimezone = dateTime.withZone(targetTimeZone);
assertThat(dateTimeInTargetTimezone.toString("yyy-MM-dd HH:mm:ss"),
equalTo("2015-05-15 08:28:49"));
}

Strange behaviour when comparing DateTime objects with JODA

Let me depict my scenario:
I have a running app in a server that is located in Dallas, Texas (I think it internally uses EDT timezone). In this server I need to get the time server, convert it to Europe/Madrid timezone and then check if the obtained date is within a Date Interval.
The weird thing is that I'm getting a response that suggests that the current server time once it is converted to Europe/Madrid timezone in BEFORE the lower date interval, which is very weird.
Here is how I'm doing this, getting the server time and convert it to Europe/Madrid timezone:
DateTimeZone timeZoneMadrid = DateTimeZone.forID( "Europe/Madrid" );
DateTimeFormatter formatter = DateTimeFormat.forPattern("YYYY-MM-dd HH:mm");
DateTime nowServer = new DateTime();
log.debug("Current server time is " + nowServer.toString(formatter));
DateTime nowServerSpanishTimeZone = nowServer.withZone(timeZoneMadrid);
log.debug("Current server time converted to Madrid Zone is " + nowServerSpanishTimeZone.toString(formatter));
Output:
Current server is 2014-10-06 06:12
Current server time converted to Madrid Zone is 2014-10-06 12:12
Now, I create the DateTime for the interval, start and end, based on the converted DateTime:
int year = serverTimeConverted.getYear();
int month = serverTimeConverted.getMonthOfYear();
int day = serverTimeConverted.getDayOfMonth();
this.setStartDate(new DateTime(year, month, day, 8, 0, 0, 0));
this.setEndDate(new DateTime(year, month, day, 21, 0, 0, 0));
As you can see, my interval goes from 08:00:00 to 21:00:00
Then I check if the server time converted is within the date range, this is very verbose because I added a lot of checking and output because of the strange behaviour...:
private boolean withinTimeRange(DateTime now, DateTime start, DateTime end){
DateTimeFormatter formatter = DateTimeFormat.forPattern("YYYYMMdd-HH:mm");
String currentDate = now.toString(formatter);
long nowTimeStamp = now.getMillis() / 1000;
long startTimeStamp = start.getMillis() / 1000;
long endTimeStamp = end.getMillis() / 1000;
log.debug("Checking if date " + currentDate + " is in the interval dates " + start.toString(formatter) + " and " + end.toString(formatter));
log.debug("Checking if UNIX timestamp " + nowTimeStamp + " is in the interval dates " + startTimeStamp + " and " + endTimeStamp);
if (!now.isBefore(start)){
log.debug("Current time " + currentDate + " is not before " + start.toString(formatter));
if (!now.isAfter(end)){
log.debug("Current time " + currentDate + " is not after " + end.toString(formatter));
return true;
}
else{
log.debug("Current time " + currentDate + " is after " + end.toString(formatter));
return false;
}
}
else{
log.debug("Current time " + currentDate + " is before " + start.toString(formatter));
return false;
}
}
Just call the method with the time server converted and the start and end dates and, for the previous output, where server time converted is 2014-10-06 12:12, I get this output from previous method:
Checking if date 20141006-12:12 is in the interval dates 20141006-08:00 and 20141006-21:00
Checking if UNIX timestamp 1412590332 is in the interval dates 1412596800 and 1412643600
Current time 20141006-12:12 is before 20141006-08:00
Current timeserver converted to Madrid TimeZone is not within time range, skipping iteration
As you can see the timestamp from the server time converted is before the start datetime.....how is this possible?
I think I'm doing something wrong when creating the DateTime start and end, I've tried creating them with .withTimeZone("Europe/Madrid"), but then I get even strangest behaviour...any clues?
Thanks!
UPDATE: based on previous SO question here, I modified my previous code and now it works:
DateTime now = new DateTime();
LocalDate today = now.toLocalDate();
LocalDate tomorrow = today.plusDays(1);
DateTimeZone timeZoneMadrid = DateTimeZone.forID( "Europe/Madrid" );
DateTime start = today.toDateTimeAtStartOfDay(timeZoneMadrid);
DateTime end = tomorrow.toDateTimeAtStartOfDay(timeZoneMadrid);
start = start.plusHours(8);
end = end.minusHours(4);
Interval interval = new Interval(start, end);
DateTimeFormatter formatter = DateTimeFormat.forPattern("YYYY-MM-dd HH:mm");
String currentDate = now.toString(formatter);
if (interval.contains(now)){
return true;
}
else{
return false;
}
Method DateTime::getMillis returns milliseconds independent of zone. This value is a constant.
Once created DateTime instance will return the same millis in any time zone.
final DateTime now = DateTime.now();
now.withZone(texas).getMillis() == now.withZone(madrid).getMillis(); // true
DateTime::isAfter and DateTime::isBefore methods compares millis returned by DateTime::getMillis method.
So this value is also zone independent.
But printing DateTime with DateTimeFormatter is zone dependent. It will print different hours/minutes in in different time zones.
So, if you want compare dates zone independently, then your result is correct.
Example:
- Precondition:
DateTimeZone usZone = DateTimeZone.forID("US/Eastern");
DateTimeZone spZone = DateTimeZone.forID("Europe/Madrid");
DateTimeZone.setDefault(usZone);
Your code works in next way:
DateTime serverDate = new DateTime(2014, 10, 6, 6, 12); // US zone
DateTime dateInMadrid = serverDate.withZone(spZone); // zone is Madrid, but .getMillis() will return the same value
DateTime startDate = new DateTime(2014, 10, 6, 8, 0); // US zone
// startDate = startDate.withZone(spZone) - this will not change the result
DateTime endDate = new DateTime(2014, 10, 6, 21, 0); // US zone
// endDate = endDate.withZone(spZone) - this will also not change the result
System.out.println(dateInMadrid .isAfter(startDate) && dateInMadrid .isBefore(endDate));
// false - it is correct. because all dates were created in US zone
Correct way: you should create start and end dates in Madrid zone
DateTime serverDate = new DateTime(2014, 10, 6, 6, 12);
DateTime startDateMadrid = new DateTime(2014, 10, 6, 8, 0, spZone); // Madrid zone is used in constructor!
DateTime endDateMadrid = new DateTime(2014, 10, 6, 21, 0, spZone); // Madrid zone is used in constructor!
System.out.println(serverDate.isAfter(startDateMadrid) && serverDate.isBefore(endDateMadrid));
// true

Using Joda-Time to get UTC offset for a given date and timezone

I have dates in the format 20Jan2013, 08Aug2012 etc, with their own specific timezones. So for example, 20Jan2013 might have a timezone ID of Australia/Melbourne, and 08Aug2012 might have an ID of Europe/London. What I want to do is, based on these timezones and the dates, calculate the UTC offset for that timezone on the given date. I've come up with this so far:
DateTimeFormatter dtf = DateTimeFormat.forPattern("ZZ");
DateTimeFormatter dtf1 = DateTimeFormat.forPattern("ddMMMYYYY");
DateTimeZone zone = DateTimeZone.forID("Australia/Melbourne");
DateTime thisDate = dtf1.parseDateTime("30Jul2013");
System.out.println("\nZone: " + thisDate.withZone(zone));
This gives me the output:
Zone: 2013-07-30T00:00:00.000+10:00
This is correct, but I would like to extract just the UTC offset from this, which in this case is +10:00. I've looked for ways to do this but can't find anything. Is there any way I can do this? The only option I see is to convert the output to a String and use the substring method to get the UTC offset.
The above code does take DST (Daylight Saving Time) into account. So for example if I had:
DateTime thisDate = dtf1.parseDateTime("30Jan2013");
The output would be: 2013-01-30T00:00:00.000+11:00
(+11:00 at the end instead of +10:00)
So basically all I need to do is find a way to extract +11:00 from 2013-07-30T00:00:00.000+11:00. Please help!
Simple Method for Obtaining Timezone Name and Offset in Hours
public static String getCurrentTimeZoneOffset() {
DateTimeZone tz = DateTimeZone.getDefault();
Long instant = DateTime.now().getMillis();
String name = tz.getName(instant);
long offsetInMilliseconds = tz.getOffset(instant);
long hours = TimeUnit.MILLISECONDS.toHours( offsetInMilliseconds );
String offset = Long.toString( hours );
return name + " (" + offset + " Hours)";
// Example: "Mountain Standard Time (-7 Hours)"
}
Couple caveats:
This gets the default DateTimeZone from JodaTime. You can modify it to accept a specific DateTimeZone that is passed into the method.
This returns it in a format like "Mountain Standard Time (-7 Hours)" but you can format it as you see fit quite easily.
Hope that helps.
JP
In order for Joda to give the correct offset, you must provide a datetime instant.Without a datetime instant, it is impossible to calculate the offset since we have different offsets(daylight savings). This is how I would use Joda to get offset in + HH:mm format :
int offsetInMillis = DateTimeZone.forID(zoneId).getOffset(new DateTime().getMillis());
String offset = String.format("%02d:%02d", Math.abs(offsetInMillis / 3600000),
Math.abs((offsetInMillis / 60000) % 60));
offset = (offsetInMillis >= 0 ? "+" : "-") + offset;
If you just need the timezone offset, use DateTimeZone.forID() to get the time zone and then tz.getOffset(instant) to get the offset to UTC in milliseconds.
It may look odd that you need an instant to calculate the offset to UTC but this is necessary to take Daylight Savings into account as well as changes in the timezone. Yes, countries change their timezones once in a while:
Why does timezone data change?
Timezone settings are adopted locally, and there is no world timezone authority.
EDIT This gives you the correct result:
DateTimeFormatter dtf1 = DateTimeFormat.forPattern("ddMMMYYYY");
DateTimeZone zone = DateTimeZone.forID("Australia/Melbourne");
DateTime thisDate = dtf1.parseDateTime("30Jul2013").withZone(zone);
assertEquals( 10 * CommonConstants.MILLISECONDS_PER_HOUR,
zone.getOffset( thisDate ) );
thisDate.get
Java 8 introduced better Date and Time handling to address some of the language's previous limitations in that area. A few of my projects have started to use it rather than Joda.
Using the java.time package:
ZonedDateTime dateTime = LocalDate.of(2013 , 1 , 20).atStartOfDay( ZoneId.of("Australia/Melbourne"));
ZoneOffset zo = dateTime.getOffset();
int offset = zo.getTotalSeconds();
long hours = TimeUnit.SECONDS.toHours(offset);
long minutes = TimeUnit.SECONDS.toMinutes(offset % 3600);
The hours variable is set to 11 and the minutes to 0.
It also calculates the minutes-offset, for time zones that are partial hours, such as Newfoundland and Labrador in eastern Canada:
ZonedDateTime dateTime = LocalDate.of(2013, 1, 20).atStartOfDay( ZoneId.of("Canada/Newfoundland"));
In this case, the offset is -03:30 (three and a half hours behind UTC), hours is -3 and minutes is -30.
For the String representation, rather than the integer number of hours and minutes, use the ZoneOffset's toString() method. So for the example above, use:
String offsetString = zo.toString();
When you know offset and timestamp so in order to get current time you can use
public static String formatMonthDayMinuteByGivenUtcOffset(long timestamp, int offset) {
return JODA_FORMATTER.print(createDateTime(timestamp, offset));
}

Categories

Resources