TimeZone name / abbreviation from offset - ios

I'm starting with a GMT offset in seconds, and I want the name of the corresponding time zone. I'm saying:
let offset = -28800
let tz = TimeZone(secondsFromGMT: offset)
let tzname = tz?.localizedName(for: .standard, locale: Locale.current)
I've tried many ways of getting tzname but all I ever get is GMT-05:00. That's not what I want. I'm hoping for something like "Eastern Standard Time" or "EST". Is there a way to get it?
I do not want to pass through some sort of reverse geocoding. I expect the system simply to hand me the answer. Other responses to this sort of question on Stack Overflow seem to indicate that that's what it ought to do, but it isn't doing it.

You cannot use the seconds offset from GMT, alone, to uniquely determine the timezone abbreviation. There is not a one-to-one correspondence between offsets and timezones.
This probably isn't going to be satisfying, but you can get a list of matching timezones by iterating through the knownTimeZoneIdentifiers and find a list of matching time zones:
let abbreviationsAndIdentifiers = TimeZone.knownTimeZoneIdentifiers
.compactMap { TimeZone(identifier: $0) }
.filter { $0.secondsFromGMT() == -28800 }
From there, you can use map to grab either abbreviation(for:), identifier or localizedName(for:locale:) to get PST, America/Los_Angeles or Pacific Standard Time for -28800, respectively. Obviously, the above may return a variety of different matches depending on the offset and time of year.
A few caveats for future readers:
Timezone abbreviations are not unique. See https://en.wikipedia.org/wiki/List_of_time_zone_abbreviations and you’ll see the same three letter abbreviation mapping to completely different time zones. E.g. CST can mean “Central Standard Time” (in North America), “Cuba Standard Time” or “China Standard Time”.
Be wary about the fact that an offset from GMT of -28800 will result in different timezones depending upon the time of the year. E.g. -28800 will return one set of timezone matches in January and another set in July because of daylight savings. Consider using secondsFromGMT(for:), specifying the date, instead, to remove any ambiguity.

Related

How to get list of timezones in NodaTime

How do I get a list of "time zones" from NodaTime such that I can make a UI like the below for my users to choose from?
I want to show the UTC offset and then the appropriate cities/countries/locations. It doesn't need to be exactly like the below, but you know, something close.
DateTimeZone doesn't have a name property, and ToString()ing produces duplicates (from the list of Ids from IDateTimeZoneProvider).
I see you can go from ~countries to zones, with TzdbDateTimeZoneSource.Default.ZoneLocations, but thats also not exactly what I'm looking for. I can see how I can cobble these two data sources together, but this feels like a solved problem I shouldn't be reinventing.
Noda Time doesn't currently provide user-oriented strings for time zones, no.
The best source of data for that is CLDR. We have a long-standing issue for this, but unfortunately it's fundamentally tricky. At some point I'd like to get back to it, but I haven't found time yet :(
You can use the Onism.Cldr project to access CLDR data. You'll need to understand how the CLDR data works in two respects though:
The time zone data structures such as metazones
The text data structures that allow you to get a particular string resource in the user's chosen language
Apologies that the answer at the moment is really just "No, there's nothing out of the box" - but that's the reality :(
You can get a list of display names and their corresponding IANA time zone ids, suitable for building a dropdown in the way you described, using my TimeZoneNames library. The resulting IDs are compatible with NodaTime's TZDB provider.
// You can either hardcode the language (ex: "en-US"), or get it from .NET globalization:
var languageCode = CultureInfo.CurrentUICulture.Name;
// Then get the names, as a list of key/value pairs
var list = TZNames.GetDisplayNames(languageCode, useIanaZoneIds: true);
// Use them as you wish. For example:
foreach (var name in list)
{
Console.WriteLine($"{name.Key} = \"{name.Value}\"");
}
Output (truncated):
Etc/GMT+12 = "(UTC-12:00) International Date Line West"
Etc/GMT+11 = "(UTC-11:00) Coordinated Universal Time-11"
America/Adak = "(UTC-10:00) Aleutian Islands"
Pacific/Honolulu = "(UTC-10:00) Hawaii"
Pacific/Marquesas = "(UTC-09:30) Marquesas Islands"
America/Anchorage = "(UTC-09:00) Alaska"
Etc/GMT+9 = "(UTC-09:00) Coordinated Universal Time-09"
America/Tijuana = "(UTC-08:00) Baja California"
Etc/GMT+8 = "(UTC-08:00) Coordinated Universal Time-08"
America/Los_Angeles = "(UTC-08:00) Pacific Time (US & Canada)"
America/Phoenix = "(UTC-07:00) Arizona"
America/Chihuahua = "(UTC-07:00) Chihuahua, La Paz, Mazatlan"
America/Denver = "(UTC-07:00) Mountain Time (US & Canada)"
America/Guatemala = "(UTC-06:00) Central America"
America/Chicago = "(UTC-06:00) Central Time (US & Canada)"
Pacific/Easter = "(UTC-06:00) Easter Island"
...
The display names are sourced from Windows language packs. The IDs are translated from Windows to IANA through CLDR. If you want Windows IDs instead, you can set useIanaZoneIds to false (or omit it).
See also Methods for listing time zones and the Acknowledgements in the TimeZoneNames docs.
You can consider using GeoTimeZone Nuget Package to get the IANA timezone id by location i.e latitude and longitude for example
// using coordinates for a place in London use GeoTimeZone Library
string tz = GeoTimeZone.TimeZoneLookup.GetTimeZone(50.4372, -3.5559).Result; // Europe/London
DateTimeZone dateTimeZone = DateTimeZoneProviders.Tzdb.GetZoneOrNull(tz);
//You can get the UTC timeoffset at any instant possibly like this
Offset offset = dateTimeZone
.GetUtcOffset(SystemClock.Instance.GetCurrentInstant());
Console.WriteLine(offset); //+01

Postgres at time zone '+07' syntax [duplicate]

I am running PostgreSQL 9.6.6 on x86_64-pc-linux-gnu and my time zone is set to 'UTC'.
Does anyone know why the results of the following SELECT statements are different?
A)
SELECT timezone('EST', '2017-12-21');
timezone
---------------------
2017-12-20 19:00:00
B)
SELECT timezone('-05', '2017-12-21');
timezone
---------------------
2017-12-21 05:00:00
According to the pg_timezone_names table -05 should have the same offset as EST... Any thoughts? Thanks.
https://www.postgresql.org/docs/current/static/view-pg-timezone-names.html
The view pg_timezone_names provides a list of time zone names that are
recognized by SET TIMEZONE
and further:
utc_offset interval Offset from UTC (positive means east of Greenwich)
when you set timezone to 'EST' - you declare that your client is in EST time zone, thus returned time will be adjusted for your tz:
t=# select '2017-12-21'::timestamptz;
timestamptz
------------------------
2017-12-21 00:00:00-05
(1 row)
the interval match utc_offset from pg_timezone_names and isequal -05, so it works as expected. (indeed in EST will be 5 hours less then UTC) same result if you set timezone to '-05'.
Both -05 and EST give same result for SET TIMEZONE as described in docs.
Now you answer reconciles with docs on using interval: https://www.postgresql.org/docs/current/static/functions-datetime.html#FUNCTIONS-DATETIME-ZONECONVERT
In these expressions, the desired time zone zone can be specified
either as a text string (e.g., 'PST') or as an interval (e.g.,
INTERVAL '-08:00').
following these rules it works as well:
t=# select '2017-12-21'::timestamptz at time zone 'EST';
timezone
---------------------
2017-12-20 19:00:00
(1 row)
t=# select '2017-12-21'::timestamptz at time zone interval '-05:00';
timezone
---------------------
2017-12-20 19:00:00
(1 row)
but further, docs say:
In the text case, a time zone name can be specified in any of the ways
described in Section 8.5.3.
which is https://www.postgresql.org/docs/current/static/datatype-datetime.html#DATATYPE-TIMEZONES
PostgreSQL allows you to specify time zones in three different forms:
recognized time zone names are listed in the pg_timezone_names
recognized abbreviations are listed in the pg_timezone_abbrevs
POSIX-style time zone specifications of the form STDoffset or STDoffsetDST
(formatting mine)
and lastly:
One should be wary that the POSIX-style time zone feature can lead to
silently accepting bogus input...Another issue to keep in mind is that
in POSIX time zone names, positive offsets are used for locations west
of Greenwich. Everywhere else, PostgreSQL follows the ISO-8601
convention that positive timezone offsets are east of Greenwich.
TL;DR
So in short - when you define '-05' as text (not interval) input for timezone() function or AT TIME ZONE directive (effectively same) Postgres thinks this is an attempt to use POSIX style time zone and thus inverts sign, thus you get "opposite" result...
a simple demonstration of this documented inversion:
t=# select '2017-12-21'::timestamptz at time zone '05';
timezone
---------------------
2017-12-20 19:00:00
(1 row)
Okay I think I found an answer to my own question:
According to the PostgreSQL docs, section 9.9.3 at the following link https://www.postgresql.org/docs/9.6/static/functions-datetime.html
In these expressions, the desired time zone zone can be specified either as a text string (e.g., 'PST') or as an interval (e.g., INTERVAL '-08:00'). In the text case, a time zone name can be specified in any of the ways described in Section 8.5.3.
So using the INTERVAL syntax, the following appears to work:
SELECT timezone(INTERVAL '-05:00', '2017-12-21');
timezone
---------------------
2017-12-20 19:00:00
I think it is still curious, what exactly SELECT timezone('-05', '2017-12-21'); means, as the following also provides the expected result (with the addition of a TZ offset):
SELECT timezone('-05', '2017-12-21'::timestamp);
timezone
------------------------
2017-12-20 19:00:00+00

How can I add timezone to Esper queries?

I am using Esper & I need to filter events by their timestamp. The events come from an external source.
The challenge is that the cutoff instant is at a different timezone than the events` timestamp, e.g. the cutoff instant is at 3:30 CET (e.g. Prague time) while the timestamp field of the event is at UTC.
This poses a problem when the timezone shifts to Daylight Savings Time, because the cutoff instant needs to be modified in the query. E.g. in this case, if the cutoff instant is 3:30 CET, during winter time it would be on 2:30 UTC and during DST it would be on 1:30 UTC. It means that I have to change the query when the time shifts into and out of DST.
This is the current query:
SELECT *
FROM my_table
WHERE timestamp_field.after( timestamp.withtime(2,30,0,0) )
I would like to have a robust solution that will save me the hassle of changing the cutoff timestamp queries every few months. Can I add the timezone to the query statement itself? Is there any other solution?
It may help to add an event property to the event that represents UTC time i.e. normalize the event timestamp to UTC and use the normalized property instead.
The query could also use a variable instead of the hardcoded numbers. Another option would perhaps be changing Esper source to take in a timezone for some func.s
After struggling unsuccessfully with trying ot do it in the WHERE caluse or using a Pattern, I managed to solve the issue using a [Single-Row Function plugin][1].
I pass the plugin function the cutoff hour, timezone & event timezone and compute the cutoff hour in the event's timezone.
My query changed to:
SELECT *
FROM my_table
WHERE timestamp_field.after( timestamp.withtime(
eventTZHour(2, 'UTC', 'Europe/Prague'), 30, 0, 0) )
I added the Java implementation in a class:
public class EsperPlugins {
public int eventTZHour(int hour, String eventTZ, String cutoffTZ) {
// return tz calculations
}
}
and finally registered the plugin in esper.cfg.xml:
<esper-configuration>
<plugin-singlerow-function name="eventTZHour"
function-class="EsperPlugins"
function-method="eventTZHour"/>
</esper-configuration>
[1]: http://www.espertech.com/esper/release-5.2.0/esper-reference/html/extension.html#custom-singlerow-function from esper's docs

Why compareDate from NSCalendar seems to need to set an UTC timeZone to work properly?

I create two dates like the following:
let date1 = stringToDate("2015-02-12 12:29:29")!
let date2 = stringToDate("2015-02-11 19:18:49")!
func stringToDate(var dateString: String) -> NSDate? {
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
dateFormatter.timeZone = NSTimeZone(name: "UTC")
return dateFormatter.dateFromString(dateString)
}
As you can see, the two date are different and are not on the same day.
To test if two dates are on the same day, I use the following method:
func isSameDayThan(date1: NSDate, date2: NSDate) -> Bool {
let calendar = NSCalendar.currentCalendar()
calendar.timeZone = NSTimeZone(abbreviation: "GMT+10")!
return calendar.compareDate(date1, toDate: date2, toUnitGranularity: .DayCalendarUnit) == .OrderedSame
}
There I don't precise any timeZone in the calendar. The local TimeZone of my device is set to GMT+10.
In that case, isSameDayThan(date1, date2)-> true
If I change the timeZone to something inferior or equal to GMT+04, then I get isSameDayThan(date1, date2)-> false.
What I don't understand is that the result is different depending on the timeZone, but I am comparing two NSDate() and NSDate() has nothing to do with time zone if I'm not wrong.
The timezone comes into play because you compare the dates with a granularity that is timezone dependent. So you are actually comparing against the local representation of the date. The point in time model that is often used to describe NSDate doesn't know about days and weeks. From a abstract standpoint (i.e. the point in time that is the same everywhere in the universe) it actually doesn't even know about seconds.
Anyway, if you would compare with == you would obviously not need a timezone. That's the only comparison that is truly independent from the local representation. If two points in time are exactly the same they are equal. Easy.
Everything beyond a straight == comparison has to be converted into local units. Not only you have to use the correct calendar, but you have to use the correct timezone as well.
Luckily there are no calendars that have days that are shorter or longer than 24 hours. And there are no timezones that differ in seconds either. Because we know that, you can actually see if dates are within the same minute with an easy calculation. e.g.:
Int(date1.timeIntervalSince1970 / 60) == Int(date2.timeIntervalSince1970 / 60)
No calendar needed because we (currently) don't have calendars that have minutes that are not 60 seconds long. No timezone needed, because we don't have timezones with offsets that differ in the number of seconds.
But we have a few timezones that have offsets that are only fractions of an hour. For example India Time Zone which has an offset of +05:30. So starting with hours the boundaries of the granularity units are timezone dependent.
If you have two NSDates which are set to 9:25 and 9:35 UTC, they are in the same hour if you compare in any timezone that has an offset that does not differ in the number of minutes (e.g. 00 in +x:00). They are in the same hour in UTC, they are in the same hour in UTC+5:00 and UTC-5:00.
But if you compare in India Time Zone these two dates are actually in different hours. Because 9:25 UTC in IST is 2:55, and 9:35 UTC is 3:05 in IST.
In your example you are comparing to the granularity of the day. Which needs to take all timezones into account. But we can still ignore the calendar, because all calendars use days that are 24 hours long.
But if you would compare to the granularity of a week, month or year you would have to take the calendar into account as well. There are calendars that have totally different months. Just because two dates are in the same month in gregorian calendars doesn't mean that they are in the same month in hebrew calendars.
Yes, it's complicated. And that's the reason all date calculation appear so verbose. People often try to hide the complexity behind a fancy helper function. Which often leads to problems. So be aware of creation functions like isSameDay().
Each time you compare a date you have to make the decision what timezone and calendar to use. If you rely on helper functions you will miss the one instance where you should actually compare against UTC instead of the local timezone.
TL;DR: If you compare with granularity you should always set the correct calendar and the correct timezone.
Th two dates are different days in the UTC time zone. But in the GMT+10 time zone they are both the same day - February 12.
2015-02-12 12:29:29 UTC = 2015-02-12 22:29:29 UTC+10
2015-02-11 19:18:49 UTC = 2015-02-12 05:18:49 UTC+10
By Default, the comparison is done in the local time zone but your date objects were specifically created in the UTC time zone.
If you create the NSDate objects from the strings using the default time zone and compare them using the default time zone, then the dates would have two different days.

Problem in getting DST time?

I am trying to get the time of other GMT value by using
Calendar c1 = Calendar.getInstance(TimeZone.getTimeZone(gmt));
but how can i get the DST time at that time zone.
The TimeZone class provides a getDSTSavings() method for a specific TimeZone Object. (JavaDoc: "Returns the amount of time to be added to local standard time to get local wall clock time.")
The Calendar interface provides two getOffset() methods, which let you find out the offset from UTC. (JavaDoc: "Returns the offset of this time zone from UTC at the specified date. If Daylight Saving Time is in effect at the specified date, the offset value is adjusted with the amount of daylight saving. ")
please see this piece of code to grok the complicated ways of java time:
#Test
public void testDST() {
final TimeZone met = TimeZone.getTimeZone("MET");
Calendar gc = new GregorianCalendar(met);
final long timeInMillis = gc.getTimeInMillis();
final long gmtTime= timeInMillis-(gc.getTimeZone().getOffset(timeInMillis));
final Date gmtDate = new Date(gmtTime);
System.out.printf("%-40s: %tc\n%-40s: %tc\n%-40s: %tc\n%-40s: %d\n%-40s: %d",
"new Date() (local timezone)",new Date(),
"UTC", gmtDate ,
"now from Calendar with TC GMT+02:00",gc,
"zoneoffset",gc.get(Calendar.ZONE_OFFSET),
"dst savings",met.getDSTSavings());
}
You can also define your own SimpleTimeZone and provide custom DST rules, however, i have not found out how to get this information from the predefined TimeZones.
You should also be aware, that if TimeZone.getTimeZone(TZName) does not find the specified timezone, it does not throw an exception, but it just uses GMT, which can cause major misunderstandings.
You can find all this information (and a lot more) in javadoc for Calendar, TimeZone, Date, etc.
There are few methods available in java.util.TimeZone to get Daylight Saving Time. Please check out the BlackBerry Java Docs page.

Resources