{"action":"created","comment":{"url":"https://api.github.com/repos/TryGhost/Ghost/pulls/comments/64546277","id":64546277,"diff_hunk":"@@ -0,0 +1,132 @@\n+var config = require('../../../../config'),\n+    models = require(config.paths.corePath + '/server/models'),\n+    sequence = require(config.paths.corePath + '/server/utils/sequence'),\n+    moment = require('moment'),\n+    Promise = require('bluebird'),\n+    messagePrefix = 'Transform into UTC Dates: ';\n+\n+/**\n+ * there is no chance to calculate UTC dates 100% accurate from DB values because of DST\n+ */\n+module.exports = function transformDatesIntoUTC(options, logger) {\n+    if (ServerTimezoneOffset === 0) {\n+        logger.info('Your Application uses UTC, skip!');\n+        return Promise.resolve();\n+    }\n+\n+    return sequence([\n+        function () {\n+            return models.Posts.forge().fetch().then(function (result) {\n+                if (result.models.length === 0) {\n+                    logger.warn(messagePrefix + 'No Posts found');\n+                    return;\n+                }\n+\n+                return Promise.map(result.models, function mapper(post) {\n+                    post.set('published_at', moment(post.get('published_at')).add(ServerTimezoneOffset, 'minutes').toDate());","path":"core/server/data/migration/fixtures/005/05-transform-dates-into-UTC.js","position":26,"original_position":26,"commit_id":"a1f44598d915c2e84d8c3e30e9968321e85f27c9","original_commit_id":"a1f44598d915c2e84d8c3e30e9968321e85f27c9","user":{"login":"kirrg001","id":1160712,"avatar_url":"https://avatars.githubusercontent.com/u/1160712?v=3","gravatar_id":"","url":"https://api.github.com/users/kirrg001","html_url":"https://github.com/kirrg001","followers_url":"https://api.github.com/users/kirrg001/followers","following_url":"https://api.github.com/users/kirrg001/following{/other_user}","gists_url":"https://api.github.com/users/kirrg001/gists{/gist_id}","starred_url":"https://api.github.com/users/kirrg001/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kirrg001/subscriptions","organizations_url":"https://api.github.com/users/kirrg001/orgs","repos_url":"https://api.github.com/users/kirrg001/repos","events_url":"https://api.github.com/users/kirrg001/events{/privacy}","received_events_url":"https://api.github.com/users/kirrg001/received_events","type":"User","site_admin":false},"body":"A helper function like this can work to detect if a published_at is affected from DST or not. 👍 ","created_at":"2016-05-25T09:59:45Z","updated_at":"2016-05-25T09:59:46Z","html_url":"https://github.com/TryGhost/Ghost/pull/6877#discussion_r64546277","pull_request_url":"https://api.github.com/repos/TryGhost/Ghost/pulls/6877","_links":{"self":{"href":"https://api.github.com/repos/TryGhost/Ghost/pulls/comments/64546277"},"html":{"href":"https://github.com/TryGhost/Ghost/pull/6877#discussion_r64546277"},"pull_request":{"href":"https://api.github.com/repos/TryGhost/Ghost/pulls/6877"}}},"pull_request":{"url":"https://api.github.com/repos/TryGhost/Ghost/pulls/6877","id":71052202,"html_url":"https://github.com/TryGhost/Ghost/pull/6877","diff_url":"https://github.com/TryGhost/Ghost/pull/6877.diff","patch_url":"https://github.com/TryGhost/Ghost/pull/6877.patch","issue_url":"https://api.github.com/repos/TryGhost/Ghost/issues/6877","number":6877,"state":"open","locked":false,"title":"[WIP] Force UTC at process level","user":{"login":"kirrg001","id":1160712,"avatar_url":"https://avatars.githubusercontent.com/u/1160712?v=3","gravatar_id":"","url":"https://api.github.com/users/kirrg001","html_url":"https://github.com/kirrg001","followers_url":"https://api.github.com/users/kirrg001/followers","following_url":"https://api.github.com/users/kirrg001/following{/other_user}","gists_url":"https://api.github.com/users/kirrg001/gists{/gist_id}","starred_url":"https://api.github.com/users/kirrg001/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kirrg001/subscriptions","organizations_url":"https://api.github.com/users/kirrg001/orgs","repos_url":"https://api.github.com/users/kirrg001/repos","events_url":"https://api.github.com/users/kirrg001/events{/privacy}","received_events_url":"https://api.github.com/users/kirrg001/received_events","type":"User","site_admin":false},"body":"#### Force UTC at process level\r\nThis PR will force all non UTC running servers to use UTC. It contains a big migration script to calculate the datetimes into UTC values.\r\n\r\nIf you're interested in why we need this feature, see:\r\nhttps://github.com/TryGhost/Ghost/issues/6406#issuecomment-219505127\r\n\r\n\r\n##### research background\r\n- node.js doesn't have a build-in feature to switch timezones\r\n- some people use the workaround of process.env.TZ='UTC', but this has a couple of downsides: it doesn't work on windows and to read the original system local time, you must read localisation files from the operation system. it turned out it was too complicated and ugly\r\n- some people use https://github.com/TooTallNate/node-time, but it does not support windows and if it would support windows, we would need to compile it with gyp\r\n- not supporting windows at all has a big downside. it means either you disallow starting the server until the blog owner changes its system time to UTC or you allow it with a warning and then you need to ensure the migration script can run again\r\n- moment-timezone in combination with database setting UTC can work, but timezone.setDefault('UTC') will not force that the local JS Date representation is in UTC (new Date() or moment().getDate()). just imagine somebody returns created_at: new Date() back to the frontend, thats wrong\r\n- just storing all our dates as ```toISOString```, will not solve the problem, because when we get the value from the DB it will be transformed back into local time and just imagine you forward this local date to the scheduler it will schedule something wrong (we don't wanne care about UTC at coding level)\r\n- we don't wanne tell all our users: check your system time and if not UTC, please change. if not, you're not able to run ghost anymore (...)\r\n\r\n##### conclusion/result\r\nIt turned out there is no existing solution for this problem. After research and frustration, i had the idea of a different approach. Why not monkey patch the native JS Date to be able to simulate a global UTC force at process level?And i found a really nice module called https://github.com/thorn0/timezoned-date. This module is already in production usage for jsdom. And the owner contributes to angular.js and node.js. It had some minor bugs inside, but with the help of the owner, we fixed all obvious bugs. The npm module is only used when the server time is NOT in UTC.\r\n\r\n##### TODO\r\n- [ ] move migration file to 006\r\n- [ ] wait that #6860 is merged\r\n- [ ] final test on windows\r\n- [ ] final general test\r\n- [ ] prettify the PR summary\r\n- [ ] test again with all node versions","created_at":"2016-05-23T12:26:12Z","updated_at":"2016-05-25T09:59:46Z","closed_at":null,"merged_at":null,"merge_commit_sha":"d7e2f6faa3cc9810c9b990756ff2993af65a4db0","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/TryGhost/Ghost/pulls/6877/commits","review_comments_url":"https://api.github.com/repos/TryGhost/Ghost/pulls/6877/comments","review_comment_url":"https://api.github.com/repos/TryGhost/Ghost/pulls/comments{/number}","comments_url":"https://api.github.com/repos/TryGhost/Ghost/issues/6877/comments","statuses_url":"https://api.github.com/repos/TryGhost/Ghost/statuses/a1f44598d915c2e84d8c3e30e9968321e85f27c9","head":{"label":"kirrg001:feature/force-UTC-usage","ref":"feature/force-UTC-usage","sha":"a1f44598d915c2e84d8c3e30e9968321e85f27c9","user":{"login":"kirrg001","id":1160712,"avatar_url":"https://avatars.githubusercontent.com/u/1160712?v=3","gravatar_id":"","url":"https://api.github.com/users/kirrg001","html_url":"https://github.com/kirrg001","followers_url":"https://api.github.com/users/kirrg001/followers","following_url":"https://api.github.com/users/kirrg001/following{/other_user}","gists_url":"https://api.github.com/users/kirrg001/gists{/gist_id}","starred_url":"https://api.github.com/users/kirrg001/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kirrg001/subscriptions","organizations_url":"https://api.github.com/users/kirrg001/orgs","repos_url":"https://api.github.com/users/kirrg001/repos","events_url":"https://api.github.com/users/kirrg001/events{/privacy}","received_events_url":"https://api.github.com/users/kirrg001/received_events","type":"User","site_admin":false},"repo":{"id":55587930,"name":"Ghost","full_name":"kirrg001/Ghost","owner":{"login":"kirrg001","id":1160712,"avatar_url":"https://avatars.githubusercontent.com/u/1160712?v=3","gravatar_id":"","url":"https://api.github.com/users/kirrg001","html_url":"https://github.com/kirrg001","followers_url":"https://api.github.com/users/kirrg001/followers","following_url":"https://api.github.com/users/kirrg001/following{/other_user}","gists_url":"https://api.github.com/users/kirrg001/gists{/gist_id}","starred_url":"https://api.github.com/users/kirrg001/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kirrg001/subscriptions","organizations_url":"https://api.github.com/users/kirrg001/orgs","repos_url":"https://api.github.com/users/kirrg001/repos","events_url":"https://api.github.com/users/kirrg001/events{/privacy}","received_events_url":"https://api.github.com/users/kirrg001/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/kirrg001/Ghost","description":"Just a blogging platform","fork":true,"url":"https://api.github.com/repos/kirrg001/Ghost","forks_url":"https://api.github.com/repos/kirrg001/Ghost/forks","keys_url":"https://api.github.com/repos/kirrg001/Ghost/keys{/key_id}","collaborators_url":"https://api.github.com/repos/kirrg001/Ghost/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/kirrg001/Ghost/teams","hooks_url":"https://api.github.com/repos/kirrg001/Ghost/hooks","issue_events_url":"https://api.github.com/repos/kirrg001/Ghost/issues/events{/number}","events_url":"https://api.github.com/repos/kirrg001/Ghost/events","assignees_url":"https://api.github.com/repos/kirrg001/Ghost/assignees{/user}","branches_url":"https://api.github.com/repos/kirrg001/Ghost/branches{/branch}","tags_url":"https://api.github.com/repos/kirrg001/Ghost/tags","blobs_url":"https://api.github.com/repos/kirrg001/Ghost/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/kirrg001/Ghost/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/kirrg001/Ghost/git/refs{/sha}","trees_url":"https://api.github.com/repos/kirrg001/Ghost/git/trees{/sha}","statuses_url":"https://api.github.com/repos/kirrg001/Ghost/statuses/{sha}","languages_url":"https://api.github.com/repos/kirrg001/Ghost/languages","stargazers_url":"https://api.github.com/repos/kirrg001/Ghost/stargazers","contributors_url":"https://api.github.com/repos/kirrg001/Ghost/contributors","subscribers_url":"https://api.github.com/repos/kirrg001/Ghost/subscribers","subscription_url":"https://api.github.com/repos/kirrg001/Ghost/subscription","commits_url":"https://api.github.com/repos/kirrg001/Ghost/commits{/sha}","git_commits_url":"https://api.github.com/repos/kirrg001/Ghost/git/commits{/sha}","comments_url":"https://api.github.com/repos/kirrg001/Ghost/comments{/number}","issue_comment_url":"https://api.github.com/repos/kirrg001/Ghost/issues/comments{/number}","contents_url":"https://api.github.com/repos/kirrg001/Ghost/contents/{+path}","compare_url":"https://api.github.com/repos/kirrg001/Ghost/compare/{base}...{head}","merges_url":"https://api.github.com/repos/kirrg001/Ghost/merges","archive_url":"https://api.github.com/repos/kirrg001/Ghost/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/kirrg001/Ghost/downloads","issues_url":"https://api.github.com/repos/kirrg001/Ghost/issues{/number}","pulls_url":"https://api.github.com/repos/kirrg001/Ghost/pulls{/number}","milestones_url":"https://api.github.com/repos/kirrg001/Ghost/milestones{/number}","notifications_url":"https://api.github.com/repos/kirrg001/Ghost/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/kirrg001/Ghost/labels{/name}","releases_url":"https://api.github.com/repos/kirrg001/Ghost/releases{/id}","deployments_url":"https://api.github.com/repos/kirrg001/Ghost/deployments","created_at":"2016-04-06T08:16:39Z","updated_at":"2016-04-06T08:16:43Z","pushed_at":"2016-05-25T09:17:29Z","git_url":"git://github.com/kirrg001/Ghost.git","ssh_url":"git@github.com:kirrg001/Ghost.git","clone_url":"https://github.com/kirrg001/Ghost.git","svn_url":"https://github.com/kirrg001/Ghost","homepage":"https://ghost.org","size":20626,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"TryGhost:master","ref":"master","sha":"d269358d950827e6bdd8b0a52002f8e2130fc867","user":{"login":"TryGhost","id":2178663,"avatar_url":"https://avatars.githubusercontent.com/u/2178663?v=3","gravatar_id":"","url":"https://api.github.com/users/TryGhost","html_url":"https://github.com/TryGhost","followers_url":"https://api.github.com/users/TryGhost/followers","following_url":"https://api.github.com/users/TryGhost/following{/other_user}","gists_url":"https://api.github.com/users/TryGhost/gists{/gist_id}","starred_url":"https://api.github.com/users/TryGhost/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/TryGhost/subscriptions","organizations_url":"https://api.github.com/users/TryGhost/orgs","repos_url":"https://api.github.com/users/TryGhost/repos","events_url":"https://api.github.com/users/TryGhost/events{/privacy}","received_events_url":"https://api.github.com/users/TryGhost/received_events","type":"Organization","site_admin":false},"repo":{"id":9852918,"name":"Ghost","full_name":"TryGhost/Ghost","owner":{"login":"TryGhost","id":2178663,"avatar_url":"https://avatars.githubusercontent.com/u/2178663?v=3","gravatar_id":"","url":"https://api.github.com/users/TryGhost","html_url":"https://github.com/TryGhost","followers_url":"https://api.github.com/users/TryGhost/followers","following_url":"https://api.github.com/users/TryGhost/following{/other_user}","gists_url":"https://api.github.com/users/TryGhost/gists{/gist_id}","starred_url":"https://api.github.com/users/TryGhost/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/TryGhost/subscriptions","organizations_url":"https://api.github.com/users/TryGhost/orgs","repos_url":"https://api.github.com/users/TryGhost/repos","events_url":"https://api.github.com/users/TryGhost/events{/privacy}","received_events_url":"https://api.github.com/users/TryGhost/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/TryGhost/Ghost","description":"Just a blogging platform","fork":false,"url":"https://api.github.com/repos/TryGhost/Ghost","forks_url":"https://api.github.com/repos/TryGhost/Ghost/forks","keys_url":"https://api.github.com/repos/TryGhost/Ghost/keys{/key_id}","collaborators_url":"https://api.github.com/repos/TryGhost/Ghost/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/TryGhost/Ghost/teams","hooks_url":"https://api.github.com/repos/TryGhost/Ghost/hooks","issue_events_url":"https://api.github.com/repos/TryGhost/Ghost/issues/events{/number}","events_url":"https://api.github.com/repos/TryGhost/Ghost/events","assignees_url":"https://api.github.com/repos/TryGhost/Ghost/assignees{/user}","branches_url":"https://api.github.com/repos/TryGhost/Ghost/branches{/branch}","tags_url":"https://api.github.com/repos/TryGhost/Ghost/tags","blobs_url":"https://api.github.com/repos/TryGhost/Ghost/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/TryGhost/Ghost/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/TryGhost/Ghost/git/refs{/sha}","trees_url":"https://api.github.com/repos/TryGhost/Ghost/git/trees{/sha}","statuses_url":"https://api.github.com/repos/TryGhost/Ghost/statuses/{sha}","languages_url":"https://api.github.com/repos/TryGhost/Ghost/languages","stargazers_url":"https://api.github.com/repos/TryGhost/Ghost/stargazers","contributors_url":"https://api.github.com/repos/TryGhost/Ghost/contributors","subscribers_url":"https://api.github.com/repos/TryGhost/Ghost/subscribers","subscription_url":"https://api.github.com/repos/TryGhost/Ghost/subscription","commits_url":"https://api.github.com/repos/TryGhost/Ghost/commits{/sha}","git_commits_url":"https://api.github.com/repos/TryGhost/Ghost/git/commits{/sha}","comments_url":"https://api.github.com/repos/TryGhost/Ghost/comments{/number}","issue_comment_url":"https://api.github.com/repos/TryGhost/Ghost/issues/comments{/number}","contents_url":"https://api.github.com/repos/TryGhost/Ghost/contents/{+path}","compare_url":"https://api.github.com/repos/TryGhost/Ghost/compare/{base}...{head}","merges_url":"https://api.github.com/repos/TryGhost/Ghost/merges","archive_url":"https://api.github.com/repos/TryGhost/Ghost/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/TryGhost/Ghost/downloads","issues_url":"https://api.github.com/repos/TryGhost/Ghost/issues{/number}","pulls_url":"https://api.github.com/repos/TryGhost/Ghost/pulls{/number}","milestones_url":"https://api.github.com/repos/TryGhost/Ghost/milestones{/number}","notifications_url":"https://api.github.com/repos/TryGhost/Ghost/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/TryGhost/Ghost/labels{/name}","releases_url":"https://api.github.com/repos/TryGhost/Ghost/releases{/id}","deployments_url":"https://api.github.com/repos/TryGhost/Ghost/deployments","created_at":"2013-05-04T11:09:13Z","updated_at":"2016-05-25T09:29:26Z","pushed_at":"2016-05-25T09:52:38Z","git_url":"git://github.com/TryGhost/Ghost.git","ssh_url":"git@github.com:TryGhost/Ghost.git","clone_url":"https://github.com/TryGhost/Ghost.git","svn_url":"https://github.com/TryGhost/Ghost","homepage":"https://ghost.org","size":51540,"stargazers_count":19542,"watchers_count":19542,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":4784,"mirror_url":null,"open_issues_count":201,"forks":4784,"open_issues":201,"watchers":19542,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/TryGhost/Ghost/pulls/6877"},"html":{"href":"https://github.com/TryGhost/Ghost/pull/6877"},"issue":{"href":"https://api.github.com/repos/TryGhost/Ghost/issues/6877"},"comments":{"href":"https://api.github.com/repos/TryGhost/Ghost/issues/6877/comments"},"review_comments":{"href":"https://api.github.com/repos/TryGhost/Ghost/pulls/6877/comments"},"review_comment":{"href":"https://api.github.com/repos/TryGhost/Ghost/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/TryGhost/Ghost/pulls/6877/commits"},"statuses":{"href":"https://api.github.com/repos/TryGhost/Ghost/statuses/a1f44598d915c2e84d8c3e30e9968321e85f27c9"}}}}
{"actor":{"display_login":"kirrg001"}}