"CEST" timezone changes offset to GMT during transition to/from Daylight Saving Time (it shouldn't) - swift

UIKit provides TimeZone.secondsFromGMT() method which simply returns the number of seconds from the GMT time.
The function returns incorrect value as of 27th of Oct, when Europe moved off of the summer time (CEST-> CET). It currently returns 3600, and prior to the 27th of Oct, it returned correctly 7200.
This function should always return constant value of 7200 irrelevant of current timezone (Daylight savings change timezones, not timezone itself). Even if Europe went to the CET time during the winter, CEST time didn’t change (Europe just doesn’t “use” it now).
This behavior breaks my unit tests and therefore correctness of the app.
Effectively this should always pass:
let timeZone = TimeZone(abbreviation: "CEST")!
XCTAssertTrue(timeZone.secondsFromGMT() == 7200)
Am I thinking about this incorrectly?

Your misconception is here:
(Daylight savings change timezones, not timezone itself). Even if Europe went to the CET time during the winter, CEST time didn’t change (Europe just doesn’t “use” it now).
What you are talking about, are offsets. TimeZone objects are capable of representing offsets, namely by creating one using the init(secondsFromGMT:) initialiser. In most cases though, thinking in terms of timezones, such as Europe/Paris is more useful.
init(abbreviation:) simply looks up the string you passed in the TimeZone.abbreviationDictionary, and uses the looked-up value as the timezone identifier. With a simple inspection, you will see that in the dictionary, CEST corresponds to the value Europe/Paris.
The identifier Europe/Paris represents the timezone adopted by France. The TimeZone object created with this identifier encapsulates, among other things, all the historical offset transitions, and the transition rules for future transitions (e.g. "DST ends on the first Sunday on or after 25 Oct").
In other words, a TimeZone object is not just a number of seconds from GMT. It is more like a mathematical function, mapping points in time to local date times. This function would mostly be continuous, except in those points in time where an offset transition happens.
If you just want a constant offset of 7200 seconds, do:
TimeZone(secondsFromGMT: 7200)

Related

PostgreSQL: Can't get timezones with DST to work properly

I'm trying to figure out how Postgres handles DST in combination with intervals. Specifically I want to allow my users to create events with recurring dates - e.g. everyday at 16:00 local time.
For what I'm doing I need to store the first date in the user's local time, and then add a number of days to it, without changing the time of day in the user's local time. I was hoping that timestamptz with a full timezone name (so it knows when to apply DST?) combined with simple 1 day intervals would do the job - but it fails at my simple example:
Germany uses CET (+1:00) and switches to CEST (+2:00) at 2:00 in the morning on March 28.
Tunesia uses CET all year.
Thus I expected, that using a timestamptz on March 27 and adding 1 day to it, I'd see a different utc-offset in Berlin, and no change in Tunis - but they both changed the offset equally, as if Tunis was using DST:
select
'2021-03-27 16:00:00 Africa/Tunis'::timestamptz as "tunis_before_dst",
'2021-03-27 16:00:00 Africa/Tunis'::timestamptz + INTERVAL '1 day' as "tunis_after_dst",
'2021-03-27 16:00:00 Europe/Berlin'::timestamptz as "berlin_before_dst",
'2021-03-27 16:00:00 Europe/Berlin'::timestamptz + INTERVAL '1 day' as "berlin_after_dst"
results in:
tunis_before_dst: '2021-03-27 16:00:00+01'
tunis_after_dst: '2021-03-28 16:00:00+02'
berlin_before_dst: '2021-03-27 16:00:00+01'
berlin_after_dst: '2021-03-28 16:00:00+02'
Looking through pg_timezone_names, I can see that my Postgres instance is aware of Africa/Tunis not having DST - so I'm wondering why it's changing the UTC offset for it then.
I guess it's obvious that timezones and DST are very confusing to me, but am I doing something wrong handling them or is timezonetz not what I think it is?
Rant first. The concept of DST is breathtaking nonsense. Even the name is obvious BS. "Daylight Saving Time". No daylight has been saved. I can't believe the EU still did not manage to get rid of it, even though the overwhelming majority wants it gone, and it's been consensus to scrap it for a while now.
With that out of my system, the primary misunderstanding is this: you assume that the data type timestamp with time zone would store time zone information. It does not. Becomes obvious here:
Thus I expected, [...] I'd see a different utc-offset in Berlin, and no change in
Tunis - but they both changed the offset equally
The time zone offset you see in the output is the offset determined by the current timezone setting of your session. Time zone serves as input / output modifier / decorator. Postgres always stores UTC time internally. And no time zone information whatsoever.
The type name is a bit deceiving there. It has been known to fool the best:
Time zone storage in data type "timestamp with time zone"
Once you've grasped that concept, the rest should become obvious.
To preserve the local time (wall clock time of day), use the data type timestamp without time zone (timestamp), or even just time (never use the broken timetz), and store time zone information additionally - ideally the time zone name ('Europe/Berlin' like you have it), not a time zone abbreviation or a numeric offset.
timestamp with time zone (timestamptz) is the right choice to store unique points in time, independent of any time zones. The time zone offset is just an input modifier. Both of the following literals result in the same timestamptz value exactly, because both time zones happen to apply the same offset at this time of the year:
'2021-03-27 16:00:00 Africa/Tunis'::timestamptz
'2021-03-27 16:00:00 Europe/Berlin'::timestamptz
But these differ by one hour, because the German offset has changed according to the local DST regime:
'2021-03-28 16:00:00 Africa/Tunis'::timestamptz
'2021-03-28 16:00:00 Europe/Berlin'::timestamptz
Related:
Ignoring time zones altogether in Rails and PostgreSQL
Preserve timezone in PostgreSQL timestamptz type

What is the standard for encoding a date as a timestamp?

Is there a standard for encoding a date as a timestamp? My thoughts:
This should be 12:00pm UTC in local time, eg 9:00am at T-3, therefore anyone consuming the timestamp, regardless of their -12/+12 offset, will recognize the same date, regardless of whether they parse at the UTC timezone
It could be 12:00pm at UTC
It could be the start of the day (12:00am) at UTC
It could be start of the day (12:00am UTC) in local time eg 9:00pm at T-3
Is there an official spec or standard to adhere to?
It would be easy to point to this document and say 'this is the standard' as opposed to being unaware and having to change our logic down the line.
There isn't a standard for this, because a date and a timestamp are logically two very different concepts.
A date covers the entire range of time on that day, not a specific point in time.
It may be a different date for a person in another time zone at any given point in time, but dates themselves do not have any association with time zones. Visualize a date as just a square on a calendar, not a point on a timeline.
Many APIs will use midnight (00:00) as the default time when a date-only value is assigned to a date+time value. However:
Whether it is UTC based or local-time based is very dependent on that particular API. There is no standard for this, nor is one answer necessarily better than the other.
Assigning a local-time midnight can be problematic for time zones with transitions near midnight. For example, in Santiago, Chile on 2019-09-08, the day started at 01:00 due to the start of DST. There was no 00:00 on that day.
Also, you tagged your question with momentjs. Since a Moment object is basically a timestamp (not a date), then Moment.js will generally assign the start of the day if provided a date-only value. The time zone involved is key to deciding which moment that actually is, which illustrates my prior points.
For example:
// Parsing as UTC
moment.utc('2019-09-08').format() //=> "2019-09-08T00:00:00Z"
// Parsing as Local Time (my local time zone is US Pacific Time)
moment('2019-09-08').format() //=> "2019-09-08T00:00:00-07:00"
// Parsing in a specific time zone (on a day without midnight)
moment.tz('2019-09-08', 'America/Santiago').format() //=> "2019-09-08T01:00:00-03:00"
Also keep in mind that sometimes APIs can be misnamed. The JavaScript Date object is not a date-only value, but actually a timestamp, just like a moment.

Understanding this date format - Redtail

What date format is this: -147114000000-0700. It is supposed to be 05/04/1965.
The first term looks like a unix timestamp. But then why would we need the second term?
I am using Redtail's api, but they provide negligible documentation on this. They are sending over a date looking like "/Date(-147114000000-0700)/". I have never seen this format before. Ignoring all the useless text, we get -147114000000-0700, still leaving me puzzled.
The -147114000000 value is a unix timestamp in milliseconds: it represents the number of milliseconds since unix epoch (which is 1970-01-01T00:00Z or January 1st 1970 at midnight in UTC).
As the number -147114000000 is negative, it represents a date before epoch. In this case, 1965-05-04T07:00:00Z (or May 4th 1965 at 7 AM in UTC).
-0700 is an UTC offset: it represents the difference from UTC. In this case, 7 hours behind UTC, which results in 1965-05-04T00:00-07:00 (or May 4th 1965 at midnight in -07:00 offset). Note that an offset can be written as -07:00, -0700 or -07.
But keep in mind that this same value can represent a different date and time in each timezone. For example, in Pacific/Honolulu timezone (that uses the -10:00 offset since 1947), the same timestamp corresponds to 1965-05-03T21:00-10:00 (May 3rd 1965 at 9 PM, in offset -10:00). So the corresponding date and time will depend on what timezone you convert this to.
That being said, probably the purpose of having the offset is just to tell you what's the offset that the date/time refers to, so it prevents you from converting to a different offset (where you can get different values for local date and time).
Just reminding that -0700 is not a timezone, it's just an offset. Actually, a timezone is the set of all offsets that a region had, has and will have during its history, while the offset is just the difference from UTC (check the section TimeZone != Offset in the timezone tag description). There can be more than one timezone that uses the same offset, so you can't really say in what timezone this is in.

Strange time zone in PostgreSQL timestamp conversion

This SQL:
select to_timestamp(extract(epoch from '0001-01-01 00:00:00'::timestamp))
produces this output:
0001-01-01 08:06:00+08:06
I realize that to_timestamp() always adds a time zone, hence the additional 8 hours and +8 in the time zone segment. But what is the :06? And where did the extra 6 minutes come from?
EDIT
If I initially execute set local timezone to 'UTC'; then I get expected results.
Before UTC was invented, each city had its own local time, mostly with a difference of just some minutes among each other.
Just after standardization of timezones (and the respective adoption by everybody), the local times were set to the values we know today.
That's why you get these strange results for ancient dates, specially before the year 1900.
Actually, Taipei changed from UTC+08:06 to UTC+08:00 only in Jan 1st of 1896, so dates before it will have the +08:06 offset.
If you set your timezone to UTC this doesn't happen, basically because UTC's offset is zero and never changes.

FormCalc Date function doesn't returns current system date

How do I get date function to return date according to current system date?
Right now, with the code snippet below, it always returns UK time, not the current system date.
<calculate>
<script>$ = concat( num2date(date(), DateFmt()), " ", num2Time(Time(), TimeFmt()) )</script>
Any help is appreciated!
It's probably not UK time exactly, but rather GMT (or UTC, to use a more precise term). The UK happens to be aligned to GMT in the winter, but in the summer it advances one hour to BST for daylight saving time.
Now, I've never used LiveCycle myself, but nonetheless, I've read through the somewhat minimal docs for LiveCycle FormCalc Date and Time Functions, and the spec, and it appears to me that a few critical mistakes were made.
The date and time functions return UTC-based values, but only the time-related functions have been made aware of the local time zone. That is, there are separate Num2Time and Num2GMTime functions, but there is only one Num2Date function.
The Num2Date function works in terms of whole integer days, and thus they are simply days since 1900-01-01. Therefore, the number being passed in to the function must already be representative of the desired time zone. However, the Date function only gets the current GMT date. There does not appear to be a function to get the current local date.
It's different on the time side, because of the millisecond precision involved. However, there's yet another flaw here. Despite the docs saying that the Time function returning "the number of milliseconds since the epoch", its actually returning only the number of milliseconds since midnight GMT. There is no day-over-day accumulation of milliseconds from the date part. The docs here are even lying in their sample code which says:
Returns the current system time as the number of milliseconds since the epoch.
Time() => 71533235 at precisely 3:52:15 P.M. on September 15th, 2003 to a user in the Eastern Standard Time (EST) zone.
If that was indeed the case (and ensuring to use their 1900-01-01 epoch), the value would actually include an additional 3272572800000 milliseconds representing the days between 1900-01-01 and 2003-09-15, bringing the total timestamp to 3272644333235. Additionally, there's a typo there, because the timestamp they give is 3:52:13, not 3:52:15. Clearly nobody paid close attention to these docs!
The real problem is that one cannot be certain that the number of milliseconds since midnight of the current day in the local time zone is the same on every day. If instead of getting the current time, you were working with past stored time values, you might be an hour off (+ or -) if the current offset is different due to daylight saving time. Example: Eastern time might be UTC-5 or UTC-4, but only the offset currently in effect will be used by the Num2Time function, even if the date you're working with is using the other offset.
So, in summary, the Date function is insufficient, leading to your observations, and the date/time functionality in general is poorly designed. Given the constraints of this API, I can't even recommend a workaround. There would have to be a LocalDate function of some kind to be used instead of the Date function, but it doesn't exist.
The only advice I can offer is that it appears (from my research, not experience) that LiveCycle can use either FormCalc or JavaScript. So - use JavaScript instead.