SimpleDateFormat bug with Large Dates - android

update: I thought the Android version was to blame, but it turns out it is the user-timezone
This code produces incorrect output when my tablets time is in Central European time (+2 in summer time):
SimpleDateFormat dateTimeFormatter = new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss Z", Locale.GERMAN);
Date testDate = dateTimeFormatter
.parse("2999-01-01 00:00:00 +0100");
Log.v(TAG, "test 1 " + testDate);
testDate = dateTimeFormatter.parse("2099-01-01 00:00:00 +0100");
Log.v(TAG, "test 2 " + testDate);
"test 1 Mon Dec 31 23:19:32 CET 2998"
"test 2 Thu Jan 01 00:00:00 CET 2099"
There is a time difference. Why the 40 minutes and some seconds difference on the larger date?
The bug is not present when I put my tablet in (most) other timezones. Something to do with timezones that have dailight saving hours?

Guess I cannot overcome this bug, I've built my own date parser.
Update: my own parser has the same problem
My current solution is just not using these large dates. If date > 100 years in future, I set it to 100 years in the future.

Related

Midnight Time zone conversion

I'm trying to convert midnight of one timezone to midnight of another time zone. Kotlin pretty much made it easy for the conversion of time zones but it does not work the same way when converting date and time to milliseconds.
Problem:
Indian Time: Mon Sep 28 00:00:00 GMT+5:30 2020
Vancouver Time: Sunday Sep 27 11:30:00 GMT-7:00 2020
What I need
Indian Time: Mon Sep 28 00:00:00 GMT+5:30 2020
Vancouver Time: Sunday Sep 27 11:30:00 GMT-7:00 2020
Here is what I tried:
val today = DateTime().withTimeAtStartOfDay().toDate() //current date and time converted to Date format
val dateOutputFormat = SimpleDateFormat("yyyy/MM/dd HH:mm:ss") // formatting the output as SimpleDateFormat
dateOutputFormat.setTimeZone(TimeZone.getTimeZone("America/Vancouver")) //Setting the timezone
Log.d("Datey2", "Before conversion ${today}") // Before conversion Mon Sep 28 00:00:00 GMT+5:30 2020
val Vancouver = Date(dateOutputFormat.format(today)).time //formatting the timezone
Log.d("Datey2", "After conversion $Vancouver") // After conversion Sun Sep 27 11:30:00 GMT+05:30 2020
val VancouverMnight = DateTime(Vancouver).withTimeAtStartOfDay().millis
Log.d("Datey2", "MidNight $VancouverMnight") // MidNight Sun Sep 27 00:00:00 GMT+05:30 2020
Output:
Before conversion Mon Sep 28 00:00:00 GMT+5:30 2020
After conversion Sun Sep 27 11:30:00 GMT+05:30 2020 // Note the GMT+5:30 in vacouver time
MidNight Sun Sep 27 00:00:00 GMT+05:30 2020
Then I convert these into milliseconds as follows
Log.d("Datey2", "After conversion {$VancouverMnight.time}") // using time function gives output in milliseconds
But when I convert those Vancouver outputs to milliseconds, I get the following:
1601186400000 // Sep 26 23:00:00 2020 - Goes 2 days before the given time
1601145000000 // Sep 26 11:30:00 2020
What I need:
Sep 27 11:30:00 2020
Sep 27 00:00:00 2020 (I need these in milliseconds)
Since you are using Joda-Time, I recommend you either stick to that or move on to java.time, the modern Java date and time API. Stay away from Date and SimpleDateFormat. They are poorly designed and long outdated, and there is absolutely no reason why you should want to touch them.
For a Joda-Time solution in Java because this is what I can write and run:
DateTimeZone vancouverTimeZone = DateTimeZone.forID("America/Vancouver");
DateTimeFormatter dateOutputFormat = DateTimeFormat.forPattern("yyyy/MM/dd HH:mm:ss")
.withZone(vancouverTimeZone);
DateTime today = new DateTime().withTimeAtStartOfDay();
System.out.println("Today: " + today);
System.out.println("Today as seen in Vancouver: " + today.toString(dateOutputFormat));
long millisBeforeConversion = today.getMillis();
System.out.println("Millis before conversion: " + millisBeforeConversion);
DateTime vancouverMidnight = today.withZone(vancouverTimeZone)
.withTimeAtStartOfDay();
long millisAfterConversion = vancouverMidnight.getMillis();
System.out.println("Millis after conversion: " + millisAfterConversion);
Output when running today in Asia/Kolkata time zone:
Today: 2020-09-29T00:00:00.000+05:30
Today as seen in Vancouver: 2020/09/28 11:30:00
Millis before conversion: 1601317800000
Millis after conversion: 1601276400000
A day has passed since you asked your questions, so you cannot compare the millisecond values from my output with your own from the question, but you can verify that they agree with what you would want for today (already September 29 in India).
Note that the count of milliseconds since the epoch is independent of time zone. So whether you get the milliseconds before or after conversion to America/Vancouver time zone makes no difference.
If you want to move on to java.time, follow the good answer by Arvind Kumar Avinash.
The problem is that you are trying to write the java.util.Date object directly which outputs the value of java.util.Date#toString. Note that a date-time object is supposed to store the information about date, time, time-zone etc. but not about the formatting. The java.util.Date object is not a real date-time object like the modern date-time classes; rather, it represents the milliseconds from the Epoch of January 1, 1970. When you print an object of java.util.Date, its toString method returns the date-time calculated from this milliseconds value. Since java.util.Date does not have time-zone information, it applies the time-zone of your JVM and displays the same. If you need to print the date-time in a different time-zone, you will need to set the time-zone to SimpleDateFormat and obtain the formatted string from it e.g.
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
public class Main {
public static void main(String[] args) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ssZ z");
Date date = new Date();
// Date and time in India
sdf.setTimeZone(TimeZone.getTimeZone("Asia/Calcutta"));
// Output to be written to the log file
System.out.println(sdf.format(date));
// Date and time in Vancouver
sdf.setTimeZone(TimeZone.getTimeZone("America/Vancouver"));
// Output to be written to the log file
System.out.println(sdf.format(date));
}
}
Output:
2020/09/29 17:57:06+0530 IST
2020/09/29 05:27:06-0700 GMT-07:00
I recommend you switch from the outdated and error-prone java.util date-time API and SimpleDateFormat to the modern java.time date-time API and the corresponding formatting API (package, java.time.format). Learn more about the modern date-time API from Trail: Date Time. If your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.
Using the modern date-time API:
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
public class Main {
public static void main(String[] args) {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ssZ z");
// Date and time in India
ZonedDateTime zdtNowIST = LocalDate.now().atStartOfDay((ZoneId.of("Asia/Calcutta")));
// Output in the default format
System.out.println(zdtNowIST);
// Output in the custom format
System.out.println(zdtNowIST.format(dtf));
// Date and time in Vancouver
ZonedDateTime zdtNowVancouver = zdtNowIST.withZoneSameInstant(ZoneId.of("America/Vancouver"));
// Output in the default format
System.out.println(zdtNowVancouver);
// Output in the custom format
System.out.println(zdtNowVancouver.format(dtf));
}
}
Output:
2020-09-29T00:00+05:30[Asia/Calcutta]
2020/09/29 00:00:00+0530 IST
2020-09-28T11:30-07:00[America/Vancouver]
2020/09/28 11:30:00-0700 GMT-07:00
I do not know Kotlin but I believe you should be able to use Java code directly in Kotlin. If not, at least you should be able to convert it using Kotlin syntax.

Showing correct time from Milliseconds with desired TimeZone

I'm developing an application which takes data from Google TimeZone API.
Simply I have time in milliseconds of desired place on Earth.
For Example : 1504760156000 it's showing Date time In London which is
Thu Sep 07 2017 09:55:56
As I'm in TimeZone +05:00 from UTC if I manipulate 1504760156000 these milliseconds it will show me whole date time like below:
Thu Sep 07 2017 09:55:56 GMT+0500 (Pakistan Standard Time)
but I want to show:
Thu Sep 07 2017 09:55:56 GMT+0100 (British Summer Time)
The problem is: I have correct date and time for London but enable to show/change TimeZone without changing Time because Time is correct according to London.
UPDATED
After getting some comments. You are not getting me here in my example.
Suppose I am in Pakistan and Time here is 1:55 PM so I asked GOOGLE API via my application to tell me whats the time in London at moment. Google API tell me time in London is 1504760156000 (9:55 AM) in milliseconds if I convert these milliseconds to Date Object it will print out like below:
Date date =new Date(1504760156000)
Thu Sep 07 2017 09:55:56 GMT+0500 (Pakistan Standard Time)
It will manipulate it according to my Local TimeZone but I want results like below
Thu Sep 07 2017 09:55:56 GMT+0100 (British Summer Time)
Updated 2
I prepared timestamp in seconds in UTC as Google Time Zone API needed timestamp UTC in form of seconds
"https://maps.googleapis.com/maps/api/timezone/json?location="+Latitude +","+Longitude+"&timestamp="+currentTimeUTCinSeonds+"&key="API KEY"
Google API respond me with below JSON against London.
{
"dstOffset" : 3600,
"rawOffset" : 0,
"status" : "OK",
"timeZoneId" : "Europe/London",
"timeZoneName" : "British Summer Time"
}
According to Docs:
Calculating the Local Time
The local time of a given location is the sum of the timestamp
parameter, and the dstOffset and rawOffset fields from the result.
I Sum up result timestamp+rawoffset+dstoffset*1000='1504760156000' (at moment when I tried it)
Code from Project
Long ultimateTime=((Long.parseLong(timeObject1.getDstOffset())*1000)+(Long.parseLong(timeObject1.getRawOffset())*1000)+timestamp*1000);
timeObject1.setTimestamp(ultimateTime); //its Sime POJO object to save current time of queried Location
Date date=new Date(ultimateTime);
date1.setText("Date Time : "+date);
As I said I'm manipulating result in Local Time Zone so it gave me below result at that time:
Thu Sep 07 2017 09:55:56 GMT+0500 (Pakistan Standard Time)
But I knew API gave me correct time. The problem is Local offset from UTC . I just want to change GMT+0500 to GMT+0100
Timestamps represent an "absolute" value of a time elapsed since epoch. Your currentTimeUTCinSeconds, for example, represent the number of seconds since unix epoch (which is 1970-01-01T00:00Z, or January 1st 1970 at midnight in UTC). Java API's usually work with the number of milliseconds since epoch.
But the concept is the same - those values are "absolute": they are the same for everyone in the world, no matter where they are. If 2 people in different parts of the world (in different timezones) get the current timestamp at the same time, they'll all get the same number.
What changes is that, in different timezones, this same number represents a different local date and time.
For example, the timestamp you're using, that corresponds to Sep 7th 2017 08:55:56 UTC, which value is 1504774556 (the number of seconds since epoch). This same number corresponds to 09:55 in London, 13:55 in Karachi, 17:55 in Tokyo and so on. Changing this number will change the local times for everyone - there's no need to manipulate it.
If you want to get a java.util.Date that represents this instant, just do:
int currentTimeUTCinSeconds = 1504774556;
// cast to long to not lose precision
Date date = new Date((long) currentTimeUTCinSeconds * 1000);
This date will keep the value 1504774556000 (the number of milliseconds since epoch). This value corresponds to 09:55 in London, 13:55 in Karachi and 17:55 in Tokyo.
But printing this date will convert it to your JVM default timezone (here is a good explanation about the behaviour of Date::toString() method). When you do "Date Time : "+date, it implicity calls toString() method, and the result is the date converted to your default timezone.
If you want the date in a specific format and in a specific timezone, you'll need a SimpleDateFormat. Just printing the date (with System.out.println or by logging it) won't work: you can't change the format of the date object itself, because a Date has no format.
I also use a java.util.Locale to specify that the month and day of week must be in English. If you don't specify a locale, it'll use the system default, and it's not guaranteed to always be English (and this can be changed, even at runtime, so it's better to always specify a locale):
// use the same format, use English for month and day of week
SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM dd yyyy HH:mm:ss 'GMT'Z (zzzz)", Locale.ENGLISH);
// set the timezone I want
sdf.setTimeZone(TimeZone.getTimeZone("Europe/London"));
// format the date
System.out.println(sdf.format(date));
The output will be:
Thu Sep 07 2017 09:55:56 GMT+0100 (British Summer Time)
Note that I don't need to manipulate the timestamp value. I don't use the google API, but I think their explanation is too confusing and the code above achieve the same results with less complication.
In your specific case, you can do:
date1.setText("Date Time : "+sdf.format(date));
Java new Date/Time API
The old classes (Date, Calendar and SimpleDateFormat) have lots of problems and design issues, and they're being replaced by the new APIs.
In Android you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes. To make it work, you'll also need the ThreeTenABP (more on how to use it here).
To get a date from a timestamp, I use a org.threeten.bp.Instant with a org.threeten.bp.ZoneId to convert it to a timezone, creating a org.threeten.bp.ZonedDateTime. Then I use a org.threeten.bp.format.DateTimeFormatter to format it:
int currentTimeUTCinSeconds = 1504774556;
// get the date in London from the timestamp
ZonedDateTime z = Instant.ofEpochSecond(currentTimeUTCinSeconds).atZone(ZoneId.of("Europe/London"));
// format it
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("EEE MMM dd yyyy HH:mm:ss 'GMT'XX (zzzz)", Locale.ENGLISH);
System.out.println(fmt.format(z));
The output is the same:
Thu Sep 07 2017 09:55:56 GMT+0100 (British Summer Time)
In your case, just do:
date1.setText("Date Time : "+fmt.format(z));

Data integrity violated in function "set" of class GregorianCalendar

I'am using GregorianCalendar class to manipulate with date and time.
I need to get only a current date without time.
My code:
Calendar today = new GregorianCalendar();
today.set(GregorianCalendar.HOUR,0);
today.set(GregorianCalendar.MINUTE,0);
today.set(GregorianCalendar.SECOND,0);
today.set(GregorianCalendar.MILLISECOND,0);
Date todayDate = new Date();
todayDate.setTime(today.getTime().getTime());
I expect todayDate will be like this "Wed Dec 07 00:00:00 EET 2016"
But actually todayDate is "Wed Dec 07 12:00:00 EET 2016".
Which is the correct way to do it?
Ia understend difference between fields HOUR and HOUR_OF_DAY when I get value, but why when I set value of HOUR to "0" the HOUR_OF_DAY is not seting to "0" automatically. Zero is always zero...
Question is about Data integrity...
It is a mistake to thought zero is always zero... It is not true for hours after midday. Zero hours after midday in short form (am/pm) is 12 hours in 24 form.
Thanks to #selvin

How can i convert ISO time into IST and GMT in android?

Here, is my code to convert ISO to IST and ISO to GMT.
Log.e("ISO TIME",""+time);//The time which i got from JSON file.
//IST TIME ZONE
Date date=new Date();
DateFormat format=DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.FULL);
SimpleDateFormat simpleDateFormat=new SimpleDateFormat();
simpleDateFormat.applyPattern("yyyy-MM-dd'T'HH:mm'+00':ss");
date = simpleDateFormat.parse(time.toString());
format.setTimeZone(simpleDateFormat.getTimeZone());
Log.e("IST TIME", "" + format.format(date));
//GMT TIME ZONE
Date date1=new Date();
SimpleDateFormat sdf=(SimpleDateFormat)DateFormat.getDateTimeInstance(DateFormat.FULL,DateFormat.FULL);
sdf.applyPattern("yyyy-MM-dd'T'HH:mm'+00':ss");
date1=sdf.parse(time);
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
Log.e("GMT TIME", "" + sdf.format(date1));
Here, is my output
E/ISO TIME: 2016-01-18T08:40+00:00
E/GMT TIME: 2016-01-18T03:10+00:00
E/IST TIME: Jan 18, 2016 8:40:00 AM India Standard Time
Problem is the actual difference between IST and GMT is 5:30
But in my output i was got the difference exactly 5:30
Please, help me to solve out this problem.
#Logic was definitely right.
But i have some suggestion for your.
You need to add 5:30 hours into your GMT time then you will get IST.
Never add you 5:30 hours into your IST time.
Look at this example
your IST time is 8:40 and GMT time is 3:10
1 hour added each operation
In round brackets one hour add for every iteration:
3:10->4:10(1)->5:10(2)->6:10(3)->7:10(4)->8:10(5)
Remaining IST time minutes to complete an hour is 00:20
add 00:20 into 8:10 it will become a 8:30.
take GMT time for addition
3:20
Your code is absolutely right

GregorianCalendar / Calendar and setting the HOUR field oddity

I'm trying to create a 'fixed' time (midnight in 24 hour clock format, i.e., 00:00:00) to set as a string for a SQL SELECT query using the the following...
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
GregorianCalendar todayDate = new GregorianCalendar();
Log.d(TAG, "todayDate: " + todayDate.getTime().toString());
Log.d(TAG, "formatted todayDate: " + sdf.format(todayDate.getTime()));
todayDate.clear(Calendar.HOUR);
todayDate.clear(Calendar.MINUTE);
todayDate.clear(Calendar.SECOND);
todayDate.set(Calendar.HOUR, 0);
todayDate.set(Calendar.MINUTE, 0);
todayDate.set(Calendar.SECOND, 0);
Log.d(TAG, "formatted modified todayDate: " + sdf.format(todayDate.getTime()));
This is fine UNLESS the current time is PM. For example,
todayDate: Fri Jan 28 23:34:34 GMT 2011
formatted todayDate: 2011-01-28 23:34:34
formatted modified todayDate: 2011-01-28 12:00:00 <- THE hour is 12 not 00
If I do this when the current time is between midnight and midday, (i.e., 00:00:00 -> 11:59:59 AM) then my hour in the formatted string is correctly set to 00. If I do it at any time after midday and before midnight then I get 12 for my hour and not 00.
Can anybody explain this and help me find a fix (or alternative way of doing things) please?
You need to set HOUR_OF_DAY to 0 instead of HOUR
todayDate.set(Calendar.HOUR_OF_DAY, 0);
From the API docs:
HOUR Field number for get and set indicating the hour of the morning or afternoon. HOUR is used for the 12-hour clock (0 - 11). Noon and midnight are represented by 0, not by 12. E.g., at 10:04:15.250 PM the HOUR is 10.
HOUR_OF_DAY Field number for get and set indicating the hour of the day. HOUR_OF_DAY is used for the 24-hour clock. E.g., at 10:04:15.250 PM the HOUR_OF_DAY is 22.

Categories

Resources