Understanding this date format - Redtail - date

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.

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

Is it possible to specify an ISO Date with a timezone (i.e. GMT offset) but without a time?

I'm trying to see if you can specify a date with a timezone in ISO but without also specifying a time.
This may seem odd to ask about having a timezone without actually having a time, but technically a date represents a range between two times... the 24-hour period spanning from midnight to midnight, and that 'midnight' has to be in a timezone.
In our case, we have an API that wants to say 'Filter things on-or-before date X and on-or-after date Y' and we want the user to specify 'April 9th' (in their time zone) for both to get all things that happen on that day.
Of course we solve this by adding a day to the first date, then changing it to a pure 'before', but the front-end is required to do that math. We can't do it on the backend because having to send a date with a time means we would be sending April 9th at midnight, then on the backend adding a day to that, but what if someone passed in 4pm?
We could fail the date if it has a non-midnight time, but then we're back to why pass it in the first place.
So again, can you have a date with a timezone but not have a time component?
To decode ISO8601 dates only with year-month-day and time zone set the appropriate formatOptions of the ISO8601DateFormatter
let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withFullDate, .withTimeZone]
If by time zone you mean a UTC offset (as used with ISO 8601 dates with times), this is no problem. If by time zone you mean a true time zone with historic, present and known future offsets from UTC, including for example summer time/DST, like America/New_York or North American Eastern Time, then ISO 8601 does not support that, neither for dates with nor without time of day.
2020-04-25-04:00
This is perfectly valid ISO 8601 for April 25 this year at offset -04:00. So you may use it for representing the interval from 2020-04-25T00:00-04:00 (inclusive) to 2020-04-26T00:00-04:00 (exclusive). Which would then be equivalent to 2020-04-25T04:00Z to 2020-04-26T04:00Z (Z meaning UTC).
Java example code
I don’t know any Swift, so cannot tell you how to format or parse such a string in Swift. In Java formatting it is not bad. Example:
LocalDate date = LocalDate.of(2020, Month.APRIL, 25);
String isoOffsetDateString = date
.atStartOfDay(ZoneId.of("America/New_York"))
.format(DateTimeFormatter.ISO_OFFSET_DATE);
System.out.println(isoOffsetDateString);
Output:
2020-04-25-04:00
I am using Java’s built-in ISO_OFFSET_DATE formatter. The documentation informs us that this formnatter is:
The ISO date formatter that formats or parses a date with an offset,
such as '2011-12-03+01:00'.
Parsing the string and producing the start and end of the day takes a little more:
TemporalAccessor parsed
= DateTimeFormatter.ISO_OFFSET_DATE.parse(isoOffsetDateString);
Instant start = LocalDate.from(parsed)
.atStartOfDay(ZoneOffset.from(parsed))
.toInstant();
Instant end = start.plus(1, ChronoUnit.DAYS);
System.out.println("From " + start + " inclusive to " + end + " exclusive");
From 2020-04-25T04:00:00Z inclusive to 2020-04-26T04:00:00Z exclusive
I have opted to convert to Instant, the class for a moment in time independent of offset or time zone. Instants print in UTC, as the trailing Z on each says. In your Java code you may prefer not to do this conversion or to do a different conversion, all depending on circumstances.
Link
Documentation of DateTimeFormatter.ISO_OFFSET_DATE

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.

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

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)

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.