Moment-timezone: POSIX-style TZ string support?

Created on 15 Mar 2016  路  12Comments  路  Source: moment/moment-timezone

Hello,

I am looking for a way to generate Posix TZ Strings with given timezone. Is it possible to generate with moment-timezone?

New Feature pending

Most helpful comment

Ok, so here is a whole bunch of code to throw at you. :smile:

A few caveats first:

  1. I really only recommend POSIX time zones for scenarios like the one you described. If you are working with smarter devices (like raspberry pi, arduino, etc.) then please just use tzdb zones.
  2. With the POSIX string, the device will be able to keep local time correctly. Don't expect the device to be accurate with regard to _past_ timestamps.
  3. If you're just collecting data with these devices, then consider that you might not need time zones on the device at all. You might just work with UTC, and do all local time conversions with the data afterwards.
  4. One place this makes a lot of sense is if the device itself has a physical display showing a clock.
  5. Make sure your device regularly checks for an updated POSIX definition. Time zones can change, and when they do the POSIX string will be different from one year to the next.
  6. There are some superficial assumptions being made, such as that all time zone transitions are for DST. Indeed we know that not to be the case, as some time zones have changed their base offsets. That would be picked up as DST here, but would probably have the same effect on the device.
  7. Some cases simply can't be handled correctly, such as when there are more than two transitions in a single year (Morocco presently, Egypt in past years).

Ok, here's the code already.

function getPosixStringForCurrentYear(tz) {
    var jan = moment.tz({month: 0, day: 1}, tz);
    var jun = moment.tz({month: 5, day: 1}, tz);
    var janOffset = jan.utcOffset();
    var junOffset = jun.utcOffset();
    var stdOffset = Math.min(janOffset, junOffset);
    var dltOffset = Math.max(janOffset, junOffset);
    var std = stdOffset === janOffset ? jan : jun;
    var dlt = dltOffset === janOffset ? jan : jun;

    var s = formatAbbreviationForPosix(std).concat(formatOffsetForPosix(stdOffset));

    if (stdOffset !== dltOffset) {
        s = s.concat(formatAbbreviationForPosix(dlt));
        if (dltOffset !== stdOffset + 60) {
            s = s.concat(formatOffsetForPosix(dltOffset));
        }

        s = s.concat(',').concat(formatTransitionForPosix(tz, std));
        s = s.concat(',').concat(formatTransitionForPosix(tz, dlt));
    }

    return s;
}

function formatAbbreviationForPosix(m) {
    var a = m.format('z');
    return /^[\+\-\d]+$/.test(a) ? '<'.concat(a).concat('>') : a;
}

function formatOffsetForPosix(offset) {
    var h = -offset / 60 | 0;
    var m = Math.abs(offset % 60);
    return h + (m === 0 ? '' : ':'.concat(m < 10 ? '0' : '').concat(m));
}

function formatTransitionForPosix(tz, m) {
    var zone = moment.tz.zone(tz);
    var ts = zone.untils[zone._index(m)];
    if (!isFinite(ts)) {
        return "J365/25";
    }
    var transition = moment(ts).utcOffset(-zone.utcOffset(ts - 1));
    var n = ((transition.date() - 1) / 7 | 0) + 1;
    var s = transition.format('[M]M.[n].d').replace('n', n);
    var time = transition.format('[/]H:mm:ss').replace(/\:00$/, '').replace(/\:00$/, '');
    if (time !== '/2') {
        s = s.concat(time);
    }
    return s;
}

Some examples:

getPosixStringForCurrentYear('America/New_York')    // "EST5EDT,M3.2.0,M11.1.0"
getPosixStringForCurrentYear('Australia/Sydney')    // "AEST-10AEDT,M10.1.0,M4.1.0/3"
getPosixStringForCurrentYear('America/Havana')      // ""CST5CDT,M3.2.0/0,M11.1.0/1"
getPosixStringForCurrentYear('Europe/London')       // "GMT0BST,M3.4.0/1,M10.4.0"
getPosixStringForCurrentYear('Australia/Lord_Howe') // "<+1030>-10:30<+11>-11,M10.1.0,M4.1.0"
getPosixStringForCurrentYear('Pacific/Chatham')     // "<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45"
getPosixStringForCurrentYear('Europe/Astrakhan')    // "<+04>-4"

All 12 comments

Nope, and not recommended, for the reasons described in the timezone tag wiki on StackOverflow. (Scroll down to the second-to-last section titled "POSIX style time zones")

I'm curious though - what is the use case? Why do you want them?

Also, the advice on the link you gave is highly applicable:

Caution: edit this file at your own risk. We have provided some TZ values that we believe are correct but we offer no guarantees on their validity.

It's up to you to derive the correct POSIX.1 TZ string for any new time zones you want to add. If the politicians in the zone in question mess around with the timing of daylight saving every year, you will have to edit the file to suit every year, too.

And guess what - politicians do indeed do this each and every year!

Hello,

Thanks. We are working on IoT devices which have no TZ database built inside and we have make them timezone-aware, especially for the DST. I am planing to build a service with _moment-timezone_ on _AWS Lambda_ and _API Gateway_ for IoT devices, when devices query with relevant timezone, the service return POSIX-style timezone for them and the firmware in the device will configure itself properly. Is it a good solution or is there any other recommended approaches?

Thanks again.

Ok, I can understand that use case. I'll post some code shortly.

Ok, so here is a whole bunch of code to throw at you. :smile:

A few caveats first:

  1. I really only recommend POSIX time zones for scenarios like the one you described. If you are working with smarter devices (like raspberry pi, arduino, etc.) then please just use tzdb zones.
  2. With the POSIX string, the device will be able to keep local time correctly. Don't expect the device to be accurate with regard to _past_ timestamps.
  3. If you're just collecting data with these devices, then consider that you might not need time zones on the device at all. You might just work with UTC, and do all local time conversions with the data afterwards.
  4. One place this makes a lot of sense is if the device itself has a physical display showing a clock.
  5. Make sure your device regularly checks for an updated POSIX definition. Time zones can change, and when they do the POSIX string will be different from one year to the next.
  6. There are some superficial assumptions being made, such as that all time zone transitions are for DST. Indeed we know that not to be the case, as some time zones have changed their base offsets. That would be picked up as DST here, but would probably have the same effect on the device.
  7. Some cases simply can't be handled correctly, such as when there are more than two transitions in a single year (Morocco presently, Egypt in past years).

Ok, here's the code already.

function getPosixStringForCurrentYear(tz) {
    var jan = moment.tz({month: 0, day: 1}, tz);
    var jun = moment.tz({month: 5, day: 1}, tz);
    var janOffset = jan.utcOffset();
    var junOffset = jun.utcOffset();
    var stdOffset = Math.min(janOffset, junOffset);
    var dltOffset = Math.max(janOffset, junOffset);
    var std = stdOffset === janOffset ? jan : jun;
    var dlt = dltOffset === janOffset ? jan : jun;

    var s = formatAbbreviationForPosix(std).concat(formatOffsetForPosix(stdOffset));

    if (stdOffset !== dltOffset) {
        s = s.concat(formatAbbreviationForPosix(dlt));
        if (dltOffset !== stdOffset + 60) {
            s = s.concat(formatOffsetForPosix(dltOffset));
        }

        s = s.concat(',').concat(formatTransitionForPosix(tz, std));
        s = s.concat(',').concat(formatTransitionForPosix(tz, dlt));
    }

    return s;
}

function formatAbbreviationForPosix(m) {
    var a = m.format('z');
    return /^[\+\-\d]+$/.test(a) ? '<'.concat(a).concat('>') : a;
}

function formatOffsetForPosix(offset) {
    var h = -offset / 60 | 0;
    var m = Math.abs(offset % 60);
    return h + (m === 0 ? '' : ':'.concat(m < 10 ? '0' : '').concat(m));
}

function formatTransitionForPosix(tz, m) {
    var zone = moment.tz.zone(tz);
    var ts = zone.untils[zone._index(m)];
    if (!isFinite(ts)) {
        return "J365/25";
    }
    var transition = moment(ts).utcOffset(-zone.utcOffset(ts - 1));
    var n = ((transition.date() - 1) / 7 | 0) + 1;
    var s = transition.format('[M]M.[n].d').replace('n', n);
    var time = transition.format('[/]H:mm:ss').replace(/\:00$/, '').replace(/\:00$/, '');
    if (time !== '/2') {
        s = s.concat(time);
    }
    return s;
}

Some examples:

getPosixStringForCurrentYear('America/New_York')    // "EST5EDT,M3.2.0,M11.1.0"
getPosixStringForCurrentYear('Australia/Sydney')    // "AEST-10AEDT,M10.1.0,M4.1.0/3"
getPosixStringForCurrentYear('America/Havana')      // ""CST5CDT,M3.2.0/0,M11.1.0/1"
getPosixStringForCurrentYear('Europe/London')       // "GMT0BST,M3.4.0/1,M10.4.0"
getPosixStringForCurrentYear('Australia/Lord_Howe') // "<+1030>-10:30<+11>-11,M10.1.0,M4.1.0"
getPosixStringForCurrentYear('Pacific/Chatham')     // "<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45"
getPosixStringForCurrentYear('Europe/Astrakhan')    // "<+04>-4"

Note, I updated the code above to handle some edge cases. Really this should be unit tested. :wink:

THANK YOU SO MUCH!!!!

After some discussion with the moment core team, we've decided not to include this in the moment-timezone API at this time. However, we will move it to a separate utility script and add some unit tests. That way we can be sure we don't break it in future releases. You'd still need to download the script separately to use it.

I understand. Thank you!

Note - there was a bug in the code above in the formatTransitionForPosix function. I have updated it, and the examples.

Awesome! Thank you!

Given Moment's project status update and the age of this issue, I am closing this. You can still use the code sample, but we won't be adding it to the repository in the way I suggested above.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mj1856 picture mj1856  路  5Comments

ghost picture ghost  路  4Comments

rama41222 picture rama41222  路  3Comments

edu2004eu picture edu2004eu  路  6Comments

dharapvj picture dharapvj  路  4Comments