@@ -290,11 +290,11 @@ export default Controller.extend(SettingsMenuMixin, {
          * Action sent by post settings menu view.
          * (#1351)
          */
-        setPublishedAt(userInput) {
+        setPublishedAtUTC(userInput) {
             if (!userInput) {
-                // Clear out the publishedAt field for a draft
+                // Clear out the publishedAtUTC field for a draft
                 if (this.get('model.isDraft')) {
-                    this.set('model.publishedAt', null);
+                    this.set('model.publishedAtUTC', null);
                 }
                 return;
             }
import Ember from 'ember';
import {parseDateString} from 'ghost-admin/utils/date-formatting';
import SettingsMenuMixin from 'ghost-admin/mixins/settings-menu-controller';
import boundOneWay from 'ghost-admin/utils/bound-one-way';
import isNumber from 'ghost-admin/utils/isNumber';

const {
    $,
    ArrayProxy,
    Controller,
    Handlebars,
    PromiseProxyMixin,
    RSVP,
    computed,
    guidFor,
    inject: {service, controller},
    String: {htmlSafe},
    isArray,
    isBlank,
    observer,
    run
} = Ember;

export default Controller.extend(SettingsMenuMixin, {
    debounceId: null,
    lastPromise: null,
    selectedAuthor: null,

    application: controller(),
    config: service(),
    ghostPaths: service(),
    notifications: service(),
    session: service(),
    slugGenerator: service(),
    timeZone: service(),

    initializeSelectedAuthor: observer('model', function () {
        return this.get('model.author').then((author) => {
            this.set('selectedAuthor', author);
            return author;
        });
    }),

    authors: computed(function () {
        // Loaded asynchronously, so must use promise proxies.
        let deferred = {};

        deferred.promise = this.store.query('user', {limit: 'all'}).then((users) => {
            return users.rejectBy('id', 'me').sortBy('name');
        }).then((users) => {
            return users.filter((user) => {
                return user.get('active');
            });
        });

        return ArrayProxy
            .extend(PromiseProxyMixin)
            .create(deferred);
    }),

    slugValue: boundOneWay('model.slug'),

    // Requests slug from title
    generateAndSetSlug(destination) {
        let title = this.get('model.titleScratch');
        let afterSave = this.get('lastPromise');
        let promise;

        // Only set an "untitled" slug once per post
        if (title === '(Untitled)' && this.get('model.slug')) {
            return;
        }

        promise = RSVP.resolve(afterSave).then(() => {
            return this.get('slugGenerator').generateSlug('post', title).then((slug) => {
                if (!isBlank(slug)) {
                    this.set(destination, slug);
                }
            }).catch(() => {
                // Nothing to do (would be nice to log this somewhere though),
                // but a rejected promise needs to be handled here so that a resolved
                // promise is returned.
            });
        });

        this.set('lastPromise', promise);
    },

    metaTitleScratch: boundOneWay('model.metaTitle'),
    metaDescriptionScratch: boundOneWay('model.metaDescription'),

    seoTitle: computed('model.titleScratch', 'metaTitleScratch', function () {
        let metaTitle = this.get('metaTitleScratch') || '';

        metaTitle = metaTitle.length > 0 ? metaTitle : this.get('model.titleScratch');

        if (metaTitle.length > 70) {
            metaTitle = metaTitle.substring(0, 70).trim();
            metaTitle = Handlebars.Utils.escapeExpression(metaTitle);
            metaTitle = htmlSafe(`${metaTitle}&hellip;`);
        }

        return metaTitle;
    }),

    seoDescription: computed('model.scratch', 'metaDescriptionScratch', function () {
        let metaDescription = this.get('metaDescriptionScratch') || '';
        let html = '';
        let el, placeholder;

        if (metaDescription.length > 0) {
            placeholder = metaDescription;
        } else {
            el = $('.rendered-markdown');

            // Get rendered markdown
            if (el !== undefined && el.length > 0) {
                html = el.clone();
                html.find('.js-drop-zone').remove();
                html = html[0].innerHTML;
            }

            // Strip HTML
            placeholder = $('<div />', {html}).text();
            // Replace new lines and trim
            placeholder = placeholder.replace(/\n+/g, ' ').trim();
        }

        if (placeholder.length > 156) {
            // Limit to 156 characters
            placeholder = placeholder.substring(0, 156).trim();
            placeholder = Handlebars.Utils.escapeExpression(placeholder);
            placeholder = htmlSafe(`${placeholder}&hellip;`);
        }

        return placeholder;
    }),

    seoURL: computed('model.slug', 'config.blogUrl', function () {
        let blogUrl = this.get('config.blogUrl');
        let seoSlug = this.get('model.slug') ? this.get('model.slug') : '';
        let seoURL = `${blogUrl}/${seoSlug}`;

        // only append a slash to the URL if the slug exists
        if (seoSlug) {
            seoURL += '/';
        }

        if (seoURL.length > 70) {
            seoURL = seoURL.substring(0, 70).trim();
            seoURL = htmlSafe(`${seoURL}&hellip;`);
        }

        return seoURL;
    }),

    // observe titleScratch, keeping the post's slug in sync
    // with it until saved for the first time.
    addTitleObserver: observer('model', function () {
        if (this.get('model.isNew') || this.get('model.title') === '(Untitled)') {
            this.addObserver('model.titleScratch', this, 'titleObserver');
        }
    }),

    titleObserver() {
        let title = this.get('model.title');
        let debounceId;

        // generate a slug if a post is new and doesn't have a title yet or
        // if the title is still '(Untitled)' and the slug is unaltered.
        if ((this.get('model.isNew') && !title) || title === '(Untitled)') {
            debounceId = run.debounce(this, 'generateAndSetSlug', 'model.slug', 700);
        }

        this.set('debounceId', debounceId);
    },

    // live-query of all tags for tag input autocomplete
    availableTags: computed(function () {
        return this.get('store').filter('tag', {limit: 'all'}, () => {
            return true;
        });
    }),

    showErrors(errors) {
        errors = isArray(errors) ? errors : [errors];
        this.get('notifications').showErrors(errors);
    },

    actions: {
        discardEnter() {
            return false;
        },

        togglePage() {
            this.toggleProperty('model.page');

            // If this is a new post.  Don't save the model.  Defer the save
            // to the user pressing the save button
            if (this.get('model.isNew')) {
                return;
            }

            this.get('model').save().catch((errors) => {
                this.showErrors(errors);
                this.get('model').rollbackAttributes();
            });
        },

        toggleFeatured() {
            this.toggleProperty('model.featured');

            // If this is a new post.  Don't save the model.  Defer the save
            // to the user pressing the save button
            if (this.get('model.isNew')) {
                return;
            }

            this.get('model').save(this.get('saveOptions')).catch((errors) => {
                this.showErrors(errors);
                this.get('model').rollbackAttributes();
            });
        },

        /**
         * triggered by user manually changing slug
         */
        updateSlug(newSlug) {
            let slug = this.get('model.slug');

            newSlug = newSlug || slug;
            newSlug = newSlug && newSlug.trim();

            // Ignore unchanged slugs or candidate slugs that are empty
            if (!newSlug || slug === newSlug) {
                // reset the input to its previous state
                this.set('slugValue', slug);

                return;
            }

            this.get('slugGenerator').generateSlug('post', newSlug).then((serverSlug) => {
                // If after getting the sanitized and unique slug back from the API
                // we end up with a slug that matches the existing slug, abort the change
                if (serverSlug === slug) {
                    return;
                }

                // Because the server transforms the candidate slug by stripping
                // certain characters and appending a number onto the end of slugs
                // to enforce uniqueness, there are cases where we can get back a
                // candidate slug that is a duplicate of the original except for
                // the trailing incrementor (e.g., this-is-a-slug and this-is-a-slug-2)

                // get the last token out of the slug candidate and see if it's a number
                let slugTokens = serverSlug.split('-');
                let check = Number(slugTokens.pop());

                // if the candidate slug is the same as the existing slug except
                // for the incrementor then the existing slug should be used
                if (isNumber(check) && check > 0) {
                    if (slug === slugTokens.join('-') && serverSlug !== newSlug) {
                        this.set('slugValue', slug);

                        return;
                    }
                }

                this.set('model.slug', serverSlug);

                if (this.hasObserverFor('model.titleScratch')) {
                    this.removeObserver('model.titleScratch', this, 'titleObserver');
                }

                // If this is a new post.  Don't save the model.  Defer the save
                // to the user pressing the save button
                if (this.get('model.isNew')) {
                    return;
                }

                return this.get('model').save();
            }).catch((errors) => {
                this.showErrors(errors);
                this.get('model').rollbackAttributes();
            });
        },

        /**
         * Parse user's set published date.
         * Action sent by post settings menu view.
         * (#1351)
         */
        setPublishedAt(userInput) {
            if (!userInput) {
                // Clear out the publishedAt field for a draft
                if (this.get('model.isDraft')) {
                    this.set('model.publishedAt', null);
                }
                return;
            }

            // The user inputs a date which he expects to be in his timezone. Therefore, from now on
            // we have to work with the timezone offset which we get from the timeZone Service.
            this.get('timeZone.blogTimezone').then((blogTimezone) => {
                let newPublishedAt = parseDateString(userInput, blogTimezone);
                let publishedAt = moment.utc(this.get('model.publishedAt'));
                let errMessage = '';

                // Clear previous errors
                this.get('model.errors').remove('post-setting-date');

                // Validate new Published date
                if (!newPublishedAt.isValid()) {
                    errMessage = 'Published Date must be a valid date with format: ' +
                        'DD MMM YY @ HH:mm (e.g. 6 Dec 14 @ 15:00)';
                }

                // Date is a valid date, so now make it UTC
                newPublishedAt = moment.utc(newPublishedAt);

                if (newPublishedAt.diff(moment.utc(new Date()), 'hours', true) > 0) {

                    // We have to check that the time from now is not shorter than 2 minutes,
                    // otherwise we'll have issues with the serverside scheduling procedure
                    if (newPublishedAt.diff(moment.utc(new Date()), 'minutes', true) < 2) {
                        errMessage = 'Must be at least 2 minutes from now.';
                    } else {
                        // in case the post is already published and the user sets the date
                        // afterwards to a future time, we stop here, and he has to unpublish
                        // his post first
                        if (this.get('model.isPublished')) {
                            errMessage = 'Your post is already published.';
                            // this is the indicator for the different save button layout
                            this.set('timeScheduled', false);
                        }
                        // everything fine, we can start the schedule workflow and change
                        // the save buttons according to it
                        this.set('timeScheduled', true);
                    }
                    // if the post is already scheduled and the user changes the date back into the
                    // past, we'll set the status of the post back to draft, so he can start all over
                    // again
                } else if (this.get('model.isScheduled')) {
                    this.set('model.status', 'draft');
                }

                // If errors, notify and exit.
                if (errMessage) {
                    this.get('model.errors').add('post-setting-date', errMessage);
                    return;
                }

                // Do nothing if the user didn't actually change the date
                if (publishedAt && publishedAt.isSame(newPublishedAt)) {
                    return;
                }

                // Validation complete
                this.set('model.publishedAt', newPublishedAt);

                // If this is a new post.  Don't save the model.  Defer the save
                // to the user pressing the save button
                if (this.get('model.isNew')) {
                    return;
                }

                this.get('model').save().catch((errors) => {
                    this.showErrors(errors);
                    this.get('model').rollbackAttributes();
                });
            });
        },

        setMetaTitle(metaTitle) {
            let property = 'metaTitle';
            let model = this.get('model');
            let currentTitle = model.get(property) || '';

            // Only update if the title has changed
            if (currentTitle === metaTitle) {
                return;
            }

            model.set(property, metaTitle);

            // If this is a new post.  Don't save the model.  Defer the save
            // to the user pressing the save button
            if (model.get('isNew')) {
                return;
            }

            model.save();
        },

        setMetaDescription(metaDescription) {
            let property = 'metaDescription';
            let model = this.get('model');
            let currentDescription = model.get(property) || '';

            // Only update if the description has changed
            if (currentDescription === metaDescription) {
                return;
            }

            model.set(property, metaDescription);

            // If this is a new post.  Don't save the model.  Defer the save
            // to the user pressing the save button
            if (model.get('isNew')) {
                return;
            }

            model.save();
        },

        setCoverImage(image) {
            this.set('model.image', image);

            if (this.get('model.isNew')) {
                return;
            }

            this.get('model').save().catch((errors) => {
                this.showErrors(errors);
                this.get('model').rollbackAttributes();
            });
        },

        clearCoverImage() {
            this.set('model.image', '');

            if (this.get('model.isNew')) {
                return;
            }

            this.get('model').save().catch((errors) => {
                this.showErrors(errors);
                this.get('model').rollbackAttributes();
            });
        },

        resetPubDate() {
            this.set('publishedAtValue', '');
        },

        closeNavMenu() {
            this.get('application').send('closeNavMenu');
        },

        changeAuthor(newAuthor) {
            let author = this.get('model.author');
            let model = this.get('model');

            // return if nothing changed
            if (newAuthor.get('id') === author.get('id')) {
                return;
            }

            model.set('author', newAuthor);

            // if this is a new post (never been saved before), don't try to save it
            if (this.get('model.isNew')) {
                return;
            }

            model.save().catch((errors) => {
                this.showErrors(errors);
                this.set('selectedAuthor', author);
                model.rollbackAttributes();
            });
        },

        addTag(tagName, index) {
            let currentTags = this.get('model.tags');
            let currentTagNames = currentTags.map((tag) => {
                return tag.get('name').toLowerCase();
            });
            let availableTagNames,
                tagToAdd;

            tagName = tagName.trim();

            // abort if tag is already selected
            if (currentTagNames.contains(tagName.toLowerCase())) {
                return;
            }

            this.get('availableTags').then((availableTags) => {
                availableTagNames = availableTags.map((tag) => {
                    return tag.get('name').toLowerCase();
                });

                // find existing tag or create new
                if (availableTagNames.contains(tagName.toLowerCase())) {
                    tagToAdd = availableTags.find((tag) => {
                        return tag.get('name').toLowerCase() === tagName.toLowerCase();
                    });
                } else {
                    tagToAdd = this.get('store').createRecord('tag', {
                        name: tagName
                    });

                    // we need to set a UUID so that selectize has a unique value
                    // it will be ignored when sent to the server
                    tagToAdd.set('uuid', guidFor(tagToAdd));
                }

                // push tag onto post relationship
                if (tagToAdd) {
                    this.get('model.tags').insertAt(index, tagToAdd);
                }
            });
        },

        removeTag(tag) {
            this.get('model.tags').removeObject(tag);

            if (tag.get('isNew')) {
                tag.destroyRecord();
            }
        }
    }
});

This may be a little confusing as we get date from `parseDateString` that is actually in the blog's timezone, not in UTC. We actually convert it into UTC further down on line 319. I would suggest two variables here, one that holds the blog-timezone value and one that holds the UTC value.