How to get zone name that respects daylight saving (e.g. EDT) - java-time

I have ZonedDateTime instance, trying to get zone as string (e.g. EST/EDT) like this:
merchantLocalReceiptDateTime.getZone().getDisplayName(TextStyle.SHORT, Locale.getDefault())
For my setup, it returns me EST while in fact I was expecting EDT. Pls advise how to get zone string that correctly reflects daylight saving.

static DateTimeFormatter etFormat = DateTimeFormatter.ofPattern("MM/dd/yyyy 'at' hh:mma 'ET'");
static ZoneId istZoneId = ZoneId.of("Asia/Kolkata");
static ZoneId etZoneId = ZoneId.of("America/New_York");
LocalDateTime currentDateTime = LocalDateTime.now();
ZonedDateTime currentISTime = currentDateTime.atZone(istZoneId);
ZonedDateTime currentETime = currentISTime.withZoneSameInstant(etZoneId); //ET Time
System.out.println(etFormat.format(currentETime));

Ok, I do not like this solution but it is the only one I came up with so far, the only one that works:
So we have preinitialized ZonedDateTime merchantLocalReceiptDateTime (remember, this is threetenbp). We can do this then (is it daylight saving for given time?):
boolean isDaylightSaving = merchantLocalReceiptDateTime.getZone()
.getRules().isDaylightSavings(merchantLocalReceiptDateTime.toInstant());
Then, to get short representation of timezone that respects daylight saving, we can do this (TimeZone is not part of threeten, it is java.util):
TimeZone.getTimeZone(merchantLocalReceiptDateTime.getZone().getId())
.getDisplayName(isDaylightSaving, TimeZone.SHORT)
For New York (assuming device language is English/United States), the above produces EST in winter and EDT right now. For timezones without particular daylight saving names, it can give, for example, "GMT+2", "GMT+3" etc. If language is different, you will likely get those "GMT+-".

//SHORT: CEST
DateTimeFormatter.ofPattern("zzz").format(zonedDateTime)
//SHORT: CET
ZoneId.getDisplayName(TextStyle.SHORT,Locale.ENGLISH)
//LONG: Central European Summer Time
DateTimeFormatter.ofPattern("zzzz").format(zonedDateTime)
//LONG: Central European Time
ZoneId.getDisplayName(TextStyle.LONG,Locale.ENGLISH)
//Use this for converting CET to CEST and vice versa
TimeZone tz = TimeZone.getTimeZone(timeZone.getZone());
tz.getDisplayName(true, TimeZone.SHORT, Locale.ENGLISH));

Related

Working with Date() in Swift and having issues adjusting timezones for storing and reading back in Firestore/Firebase

I am storing dates from a Swift Project in Firestore without any problem. Dates are converted into UTC format and stored in Firestore as a Timestamp. All good.
Then, back on clientside, I can read them back and apply the TimeZone.current and the date/time are adjusted accordingly based on the timezone the user is currently in.
So, as en example, a time originally of:
9:00 pm Melbourne time (which is GMT+10),
shows as 7:00 am if the user is in New York.
Great.
But I have some items that I want to adjust for timezones (as per above) and others I don't.
So say I have two items the same as the above example, but one is an alarm and I want to keep at the time it was originally set for regardless of the new timezone... So still keep it at 9:00 pm.
I have a Bool flag saved in my database to say ignoreTimezone but I'm lost as to how to do this in Swift when reading back the timestamp from Firestore in UTC format and get it back to the original 9:00 pm.
All the Q&A's I've found are all about converting timezones etc. but not really on this example of ignoring one and keeping the date and time set to the timezone they were originally set for.
Thanks in advance for any help and/or suggestions.
Question updated as recommended
I have now incorporated the suggested code. So have a calendar extension:
extension Calendar {
func offsetFromMidnight(for date: Date) -> TimeInterval {
return date.timeIntervalSince(startOfDay(for: date))
}
}
Then I carry out the recommended steps.
Take an offset from midnight, in this case, the current Date():
let offsetSinceMidnight = UInt64(Calendar.current.offsetFromMidnight(for: Date()))
This value is then stored on the server.
I'm currently in Melbourne (Australia), so the date and time item used for testing is July 9 # 2:00pm.
When it is retrieved on the client end in a different timezone, I'm using the recommended code:
//Create a calendar for the target timezone
guard let chicagoTimeZone = TimeZone(identifier: "America/Chicago") else { fatalError() }
var chicagoCalendar = Calendar(identifier: .gregorian)
chicagoCalendar.timeZone = chicagoTimeZone
//Calculate midngiht in the target calendar
let chicagoMidnight = chicagoCalendar.startOfDay(for: Date())
//calculate the same time-of-day in the new timezone
let adjustedChicagoTime = Date(timeInterval: TimeInterval(offsetSinceMidnight), since: chicagoMidnight)
The output is set to the correct time, 2:00pm in Chicago, but because of the differnent dates (Chicago is still July 8th), then the midnight timeinterval is being applied on the wrong date. So I get July 8 # 2:00pm.
I'm assuming I will also need to capture the original date components to apply the offsetSinceMidnight to a date in the newTimeZone that has matching date components??? Or is there a better approach to this?
Date objects store an instant in time, anywhere in the world. They don't capture the idea of a time-of-day regardless of time zone.
To do that I would suggest calculating an offsetFromMidnight value.
Edited to fix return value.
extension Calendar {
func offsetFromMidnight(for date: Date) -> TimeInterval {
return date.timeIntervalSince(startOfDay(for: date))
}
}
You'd call that function in the user's current calendar to get the seconds since midnight in the user's current time zone. Save that to your database. (You could round to a long integer with very little loss of precision.)
I happen to BE in the NYT time zone (EDT) so using that as the destination time zone won't work for me since it won't change anything. Instead, I'll show code to convert from my timezone to GMT:
//Run on user's local machine (in EDT in my case):
let offsetSinceMidnight = UInt64(Calendar.current.offsetFromMidnight(for: Date()))
//Save offset to FireStore
Then if you want that same time of day in a new timezone, you'd use code like this:
//Create a calendar for the target time zone (or the user's local time zone on the destination machine)
guard let gmt = TimeZone(abbreviation: "GMT") else { fatalError() }
var gmtCalendar = Calendar(identifier: .gregorian)
gmtCalendar.timeZone = gmt
//Read time offset from FireStore
let offsetFromNYC = Calendar.current.offsetFromMidnight(for: Date())
//Calculate midnight in target calendar
let gmtMidnight = gmtCalendar.startOfDay(for: Date())
//Calculate the same time-of-day in the GMT time zone
let gmtTimeToday = Date(timeInterval: TimeInterval(offsetSinceMidnight), since: gmtMidnight)
print(gmtTimeToday)
Note that the above will give you the same hours/minutes/seconds as the offsetFromMidnight time.
Edit:
If your goal is to set an alarm to the next future time-of-day in the local time zone, you'd need to add logic to check if the computed date/time is in the past and adjust:
//Change adjustedChicagoTime to a var
var adjustedChicagoTime = Date(timeInterval: TimeInterval(offsetSinceMidnight), since: chicagoMidnight)
//If the alarm time is in the past, add a day to the date.
if adjustedChicagoTime < Date() {
adjustedChicagoTime = Calendar.current.date(byAdding: .day,
value: 1, to: adjustedChicagoTime, wrappingComponents: false)
}
Edit #2:
After a back-and-forth, it sounds like you sometimes want to save a date and time that's independent of time zone, like 9:30 AM on 10 July. If I create that date in EDT, and you view it in Melborne, it's ALWAYS 9:30 AM on 10 July.
Other times, you want to upload and download dates & times that honor time zones.
In order to easily do both, I would suggest saving 2 different string date/time fields to FireStore, one with a time zone, and one without. The one with timezone (or rather offset from GMT) would capture a moment in time around the world, and could be converted to a local time.
The one without time zone would describe a day/month/year/hours/minutes in local time.
You could generate/parse those strings in Swift using date formatters like this:
let baseFormatString = "YYYY-MM-dd'T'HH:mm"
let timeZoneFormatString = baseFormatString + "ZZZ"
let noTimeZoneFormatter = DateFormatter()
noTimeZoneFormatter.dateFormat = baseFormatString
let timeZoneFormatter = DateFormatter()
timeZoneFormatter.dateFormat = timeZoneFormatString
Note that by default a date formatter uses the system's time zone, so the "no time zone formatter" would assume the local time zone. If you use it to convert a date string to a date, it will assume the date is in the local time zone.

Java 8 LocalDateTime has different results

I am trying to update some code to use Java 8's feature for parsing multiple date formats. my local time on my box is set to UTC-11.
the below code works when using the SimpleDateformat.
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
Date correctDate = dateFormat.parse("2018-09-6T03:28:59.039-04:00");
//Gives me correct date
System.println( correctDate);//Wed Sep 5th 20:28:59 GMT-11:00 2018
I am trying to update this code to give the same date as above with the DateTimeFormatter in Java 8 , so i can handle another date format..
DateTimeFormattter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss[.SSS]XXX");
LocalDateTime updateDate = LocalDateTime.parse( "2018-09-6T03:28:59.039-04:00", dtf);
//shows the wrong date of 2018-09-06 03:28:59.039.
System.out.println( updateDate.toString() );// 2018-09-06 03:28:59.039
[solved]
I was able to fix this by using ZonedDateTime.
ZonedDateTime zdt = ZonedDateTime.parse("2018-09-6T03:28:59.039-04:00");
zonedDateTime = zdt.withZoneSameInstance(ZoneId.of("GMT"));
Date correctDate = Date.from( zonedDateTime.toInstance());
//correctDate is what i wanted Wed Sep 5th 20:28:59 GMT-11:00 2018
As soon as you parse your date string into a LocalDateTime the zone offset is lost because LocalDateTime does not hold any time zone or offset information.
When you format the LocalDateTime to a string again, you'll only have the time as it was parsed without offset.
The Documentation of LocalDateTime clearly explains this:
This class does not store or represent a time-zone. Instead, it is a description of the date, as used for birthdays, combined with the local time as seen on a wall clock. It cannot represent an instant on the time-line without additional information such as an offset or time-zone.
You should consider using OffsetDateTime or ZonedDateTime.
Solved, using OffsetDateTime as suggested in the accepted 'Answer':
OffsetDateTime odt = OffsetDateTime.parse("2018-09-6T03:28:59.039-04:00");
Date correctDate = Date.from( odt.toInstant());

How to check time is in which timezone in swift?

I am displaying the current time using this code:
let UTCDate = Date()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd hh:mm a"
formatter.timeZone = TimeZone(identifier:"GMT")
let defaultTimeZoneStr = formatter.string(from: UTCDate)
Can someone help me to check if this time is in which timezone(eg:central timezone, eastern timezone)....
Unless the time is specified as a string with some kind of time zone indicator, such as "2017-04-14 10:00:00 EDT" or "2017-04-14 10:00:00 -0400", there's no way to tell what time zone for a given time value.
The Swift way to store times as Date values, which simply specify a number of seconds before or after January 1, 2001, UTC, and to display any time value using the calendar and time zone that makes the most sense for the user. Usually, this means using the time zone settings, because in most cases -- but not all cases -- that time zone setting will match the time zone where the user is.
If you want the abbreviated name of the user's current time zone setting, use this:
Calendar.current.timeZone.abbreviation()! // returns "EDT" for me;
// I’m in the eastern time zone
// and on daylight saving time
Or if you prefer getting the time zone by geographic identifier, use this:
Calendar.current.timeZone.identifier // returns "America/New_York" for me
Or if you want the full name of the time zone, try this (and play with the parameters):
// Returns "Eastern Standard Time" for me
Calendar.current.timeZone.localizedName(for: .standard, locale: Locale.current)

Using Groovy - Add text to a returned date based on the time

I am running this groovy script:
def sdf = new java.text.SimpleDateFormat("yyyy-MM-dd kk:mm:ss")
return sdf.format( new Date())
and it returns = 2016-03-23 16:54:39
Which is fine however at the end of the returned value I want to add a space and either AM or PM depending on the what time it is (AM or PM time) as per the below:
AM time returns = 2016-03-23 11:54:39 AM
PM time returns = 2016-03-23 16:54:39 PM
Any ideas?
If you want to append AM / PM you can append an 'a' (Am/pm marker) at the end of the date formatter pattern:
new java.text.SimpleDateFormat("yyyy-MM-dd kk:mm:ss a").format(new Date())
This will yield:
2016-03-23 18:05:20 PM
See the Java documentation for the exact details of SimpleDateFormat.
It's even easier with groovy, as Date had a format method;
println new Date().format('yyyy-MM-dd HH:mm:ss a')
java.time
The Date and SimpleTextFormat classes are now outmoded by the java.time framework built into Java 8 and later.
An Instant is a moment on the timeline in UTC with up to nanosecond resolution.
The code in this Answer is Java, as I do not know Groovy syntax.
Instant instant = Instant.now();
Apply a time zone to get a ZonedDateTime.
ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId );
Define your formatting pattern. These codes in DateTimeFormatter are similar to SimpleDateFormat but not exactly the same. Study the class doc.
DateTimeFormatter formatter = DateTimeFormatter.ofPattern ( "yyyy-MM-dd kk:mm:ss" );
Be aware that the kk used in that particular pattern means 1-24 used for hours of the day instead of 0-23 (hh).
Generate a String as a textual representation of your date-time object.
String output = zdt.format( formatter );

Swift: Get correct time zone from Date Picker?

I am trying to get the correct time zone from the date picker in swift using time formatter, it's not working. I'm getting UTC, not EST.
1) If I print dateFormatter.stringFromDate(datePicker) I get EST, but
2) I don't need a string, I need an NSDate in EST so
3) I can use it to get the timeIntervalSinceDate(NSDate) in EST.
My trick of trying to take it from string back to NSDate as seen below didn't work. It's still in UTC and the time interval since date is not right.
dateFormatter.locale = NSLocale.currentLocale()
dateFormatter.timeZone = NSTimeZone.localTimeZone()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let date: NSDate = dateFormatter.dateFromString(dateFormatter.stringFromDate(datePicker))!
print(date)
print(date.timeIntervalSinceDate(datePicker))
The above answer is totally wrong. Date picker report the date in system locale anytime, so, if the datePicker shows an 08:00 Time and you are GMT+2, the property date of the picker will be 06:00.
So for have the absolute value of the datePicker you have to pass to him the UTC time zone in view did load with:
datePicker.timeZone = TimeZone.init(identifier: "UTC")
Now, the date property of the picker will be the expected and choosen one.
You cannot "get a time zone" from a date picker. You can just get a date. The date will be independent on the current time zone of the device.
Perhaps you think you have a different date, but actually, there is no such thing as a "UTC date" or "EST date". Instead, there is only one date, and you use date formatters to display them for various time zones.
Note that there is quite a bit of redundancy in your code. The default locale and time zone of a date formatter are already the same values that you set. Also, when you have a method that returns a NSDate you do not have annotate the constant with : NSDate, making your code more verbose and cluttered.
Note that if you print a date the console will always show UTC. e.g.
let date = NSDate() // Nov 10, 9:44 PM
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "YYYY-MM-dd hh:mm a"
let dateString = dateFormatter.stringFromDate(date) // "2015-11-10 09:44 PM"
print(date) // "2015-11-10 20:44:54 +0000\n"
To set the TimeZone of a DatePicker to UTC use:
datePicker.timeZone = TimeZone.init(identifier: "UTC")
Notice the camelcase notation of "timeZone".
Unfortunately I don't have enough credit to comment on the last post, which has it almost right, so I had to create a new answer.
A little trivia: TimeZone has been around since iOS 2.0 as is stated here:
https://developer.apple.com/documentation/uikit/uidatepicker/1615976-timezone
Those who are trying to find a solution and are not able to wrap their head around the exact issue. Here is what something I tried:
Use time.addTimeInterval(-14400) function, where -14400 is the 4 hours difference. So if you want UTC to EST do this.
Something like this:-
var time = Date() // assuming you have this in UTC
time.addTimeInterval(-14400)
You can also use addingTimeInterval function which returns you the new date.
I know this is a little wonky cause we're manually doing this, but hope it helps someone.