Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POSIX-style TZ string support? #314

Closed
pahud opened this issue Mar 15, 2016 · 12 comments
Closed

POSIX-style TZ string support? #314

pahud opened this issue Mar 15, 2016 · 12 comments

Comments

@pahud
Copy link

pahud commented Mar 15, 2016

Hello,

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

@mattjohnsonpint
Copy link
Contributor

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?

@mattjohnsonpint
Copy link
Contributor

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!

@pahud
Copy link
Author

pahud commented Mar 18, 2016

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.

@mattjohnsonpint
Copy link
Contributor

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

@mattjohnsonpint
Copy link
Contributor

mattjohnsonpint commented Mar 18, 2016

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

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"

@mattjohnsonpint
Copy link
Contributor

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

@pahud
Copy link
Author

pahud commented Mar 18, 2016

THANK YOU SO MUCH!!!!

@mattjohnsonpint
Copy link
Contributor

mattjohnsonpint commented Mar 24, 2016

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.

@pahud
Copy link
Author

pahud commented Mar 24, 2016 via email

@mattjohnsonpint
Copy link
Contributor

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

@raldone01
Copy link

Awesome! Thank you!

@mattjohnsonpint
Copy link
Contributor

mattjohnsonpint commented Sep 23, 2020

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants