diff --git a/common/locales/en/settings.json b/common/locales/en/settings.json
index 587055ae78..06ec86887b 100644
--- a/common/locales/en/settings.json
+++ b/common/locales/en/settings.json
@@ -39,6 +39,9 @@
"xml": "(XML)",
"json": "(JSON)",
"customDayStart": "Custom Day Start",
+ "changeCustomDayStart": "Change Custom Day Start?",
+ "sureChangeCustomDayStart": "Are you sure you want to change your custom day start?",
+ "nextCron": "WARNING: Your dailies will reset next on <%= time %>.",
"24HrClock": "24Hr Clock",
"customDayStartInfo1": "Habitica defaults to check and reset your Dailies at midnight in your own time zone each day. It is recommended that you read the following information before changing it: ",
"customDayStartInfo4": "Complete all your Dailies before changing the Custom Day Start or Rest in the Inn that day. Changing your Custom Day Start may cause Cron to run immediately, but after the first day it works as expected.
Allow a window of two hours for the change to take effect. For example, if it is currently set to 0 (midnight), change it before 10pm; if you want to set it to 9pm, change it before 7pm.
Enter an hour from 0 to 23 (it uses a 24 hour clock). Typing is more effective than arrow keys. Once set, reload the page to confirm that the new value is being displayed.",
diff --git a/common/script/index.coffee b/common/script/index.coffee
index 2985dbfe43..f0dd2536fa 100644
--- a/common/script/index.coffee
+++ b/common/script/index.coffee
@@ -66,13 +66,6 @@ api.startOfDay = (options={}) ->
dayStart.subtract({days:1})
dayStart
-
-api.startOfDayAllowsFuture = (options={}) ->
- # Use this version to use if you need the result even if the offset would cause today's day start to be in the future.
- o = sanitizeOptions(options)
- moment(o.now).startOf('day').add({hours:o.dayStart})
-
-
api.dayMapping = {0:'su',1:'m',2:'t',3:'w',4:'th',5:'f',6:'s'}
###
@@ -360,18 +353,6 @@ Friendly timestamp
###
api.friendlyTimestamp = (timestamp) -> moment(timestamp).format('MM/DD h:mm:ss a')
-###
-ISO timestamp
-###
-api.isoTimestamp = (timestamp) -> moment(timestamp).toISOString()
-
-###
-plain timestamp
-###
-api.momentTimestamp = (timestamp) -> moment(timestamp)
-
-
-
###
Does user have new chat messages?
###
diff --git a/test/spec/controllers/settingsCtrlSpec.js b/test/spec/controllers/settingsCtrlSpec.js
new file mode 100644
index 0000000000..33d3ffe601
--- /dev/null
+++ b/test/spec/controllers/settingsCtrlSpec.js
@@ -0,0 +1,109 @@
+'use strict';
+
+describe('Settings Controller', function() {
+ var rootScope, scope, user, User, ctrl;
+
+ beforeEach(function() {
+ module(function($provide) {
+ user = specHelper.newUser();
+ User = {
+ set: sandbox.stub(),
+ user: user
+ };
+ $provide.value('User', User);
+ $provide.value('Guide', sandbox.stub());
+ });
+
+ inject(function(_$rootScope_, _$controller_) {
+ scope = _$rootScope_.$new();
+ rootScope = _$rootScope_;
+
+ // Load RootCtrl to ensure shared behaviors are loaded
+ _$controller_('RootCtrl', {$scope: scope, User: User});
+
+ ctrl = _$controller_('SettingsCtrl', {$scope: scope, User: User});
+ });
+ });
+
+ describe('#openDayStartModal', function() {
+ beforeEach(function() {
+ sandbox.stub(rootScope, 'openModal');
+ sandbox.stub(window, 'alert');
+ });
+
+ context('failures', function() {
+ var tests = {
+ 'blank': '',
+ 'not a whole number': 5.3,
+ 'not a number': 'foo',
+ 'less than 0': -5,
+ 'more than 24': 25
+ };
+
+ for (var test in tests) {
+ it('returns with an alert if number is ' + test, function() {
+ scope.openDayStartModal(tests[test]);
+
+ expect(rootScope.openModal).to.not.be.called;
+ expect(window.alert).to.be.calledOnce;
+ expect(window.alert).to.be.calledWith(env.t('enterNumber'));
+ });
+ }
+ });
+
+ context('success', function() {
+ it('opens the day start modal', function() {
+ scope.openDayStartModal(5);
+
+ expect(rootScope.openModal).to.be.calledOnce;
+ expect(rootScope.openModal).to.be.calledWith('change-day-start', {scope: scope});
+ });
+
+ it('sets nextCron variable', function() {
+ expect(scope.nextCron).to.not.exist;
+
+ scope.openDayStartModal(5);
+
+ expect(scope.nextCron).to.exist;
+ });
+
+ it('calculates the next time cron will run', function() {
+ var fakeCurrentTime = new Date(2013, 3, 1, 3, 12).getTime();
+ var expectedTime = new Date(2013, 3, 1, 5, 0, 0).getTime();
+ sandbox.useFakeTimers(fakeCurrentTime);
+
+ scope.openDayStartModal(5);
+
+ expect(scope.nextCron).to.eq(expectedTime);
+ });
+
+ it('calculates the next time cron will run and adds a day if cron would have already passed', function() {
+ var fakeCurrentTime = new Date(2013, 3, 1, 8, 12).getTime();
+ var expectedTime = new Date(2013, 3, 2, 5, 0, 0).getTime();
+ sandbox.useFakeTimers(fakeCurrentTime);
+
+ scope.openDayStartModal(5);
+
+ expect(scope.nextCron).to.eq(expectedTime);
+ });
+ });
+ });
+
+ describe('#saveDayStart', function() {
+
+ it('updates user\'s custom day start and last cron', function() {
+
+ var fakeCurrentTime = new Date(2013, 3, 1, 8, 12).getTime();
+ var expectedTime = fakeCurrentTime;
+ sandbox.useFakeTimers(fakeCurrentTime);
+ scope.dayStart = 5;
+ scope.saveDayStart();
+
+ expect(User.set).to.be.calledOnce;
+ expect(User.set).to.be.calledWith({
+ 'preferences.dayStart': 5,
+ 'lastCron': expectedTime
+ });
+ });
+ });
+});
diff --git a/website/public/js/controllers/settingsCtrl.js b/website/public/js/controllers/settingsCtrl.js
index fcb41a57ab..2d07e7cb45 100644
--- a/website/public/js/controllers/settingsCtrl.js
+++ b/website/public/js/controllers/settingsCtrl.js
@@ -64,30 +64,25 @@ habitrpg.controller('SettingsCtrl',
$scope.dayStart = User.user.preferences.dayStart;
- function updateLastCron(oldDayStart, newDayStart){
- var getOldStart = Shared.startOfDayAllowsFuture({ dayStart: oldDayStart});
- var getNewStart = Shared.startOfDayAllowsFuture({ dayStart: newDayStart});
- var lastCron = User.user.lastCron;
- var momentLastCron = Shared.momentTimestamp(lastCron);
- var isoNewStart = Shared.isoTimestamp(getNewStart);
- alert('Times are oldstart'+Shared.friendlyTimestamp(getOldStart)+' lastcron '+Shared.friendlyTimestamp(lastCron)+' and newstart '+Shared.friendlyTimestamp(getNewStart));
- if (getOldStart < momentLastCron && momentLastCron < getNewStart) {
- alert('Setting lastcron to '+Shared.friendlyTimestamp(getNewStart));
- User.set({ 'lastCron' : isoNewStart});
- }
- };
+ $scope.openDayStartModal = function(dayStart) {
+ var flooredDayStart = Math.floor(dayStart);
- $scope.saveDayStart = function(varDayStart) {
- var oldDayStart = User.user.preferences.dayStart;
- var newDayStart = varDayStart;
-
- if ( newDayStart != Math.floor(newDayStart) || newDayStart < 0 || newDayStart > 24 ) {
- newDayStart = 0;
+ if (dayStart !== flooredDayStart || dayStart < 0 || dayStart > 24 ) {
return alert(window.env.t('enterNumber'));
}
- updateLastCron( oldDayStart, newDayStart);
- User.set({'preferences.dayStart': Math.floor(newDayStart)});
- }
+
+ $scope.dayStart = dayStart;
+ $scope.nextCron = _calculateNextCron();
+
+ $rootScope.openModal('change-day-start', { scope: $scope });
+ };
+
+ $scope.saveDayStart = function() {
+ User.set({
+ 'preferences.dayStart': Math.floor($scope.dayStart),
+ 'lastCron': +new Date
+ });
+ };
$scope.language = window.env.language;
$scope.avalaibleLanguages = window.env.avalaibleLanguages;
@@ -210,5 +205,18 @@ habitrpg.controller('SettingsCtrl',
subs["google_6mo"].discount = false;
});
}
+
+ function _calculateNextCron() {
+ $scope.dayStart;
+
+ var nextCron = moment().hours($scope.dayStart).minutes(0).seconds(0).milliseconds(0);
+
+ var currentHour = moment().format('H');
+ if (currentHour >= $scope.dayStart) {
+ nextCron = nextCron.add(1, 'day');;
+ }
+
+ return +nextCron.format('x');
+ }
}
]);
diff --git a/website/views/options/settings.jade b/website/views/options/settings.jade
index ad5bdf7c7e..039fe9f7a8 100644
--- a/website/views/options/settings.jade
+++ b/website/views/options/settings.jade
@@ -90,10 +90,16 @@ script(type='text/ng-template', id='partials/options.settings.settings.html')
h5=env.t('customDayStartInfo1')
a(ng-click='showCustomDayStartInfo = !showCustomDayStartInfo') {{!showCustomDayStartInfo ? env.t('showMoreMore') : env.t('showMoreLess')}}
h5(ng-if='showCustomDayStartInfo')!=env.t('customDayStartInfo4')
- .form-group
- .input-group
- input.form-control(type='number', min='0', max='23', ng-model='dayStart', ng-blur='saveDayStart(dayStart)')
- span.input-group-addon= ':00 (' + env.t('24HrClock') + ')'
+ .form-horizontal
+ .form-group
+ .col-sm-7
+ .input-group
+ input.form-control(type='number', min='0', max='23', ng-model='dayStart')
+ .input-group-addon= ':00 (' + env.t('24HrClock') + ')'
+
+ .col-sm-5
+ br.visible-xs
+ button.btn.btn-block.btn-primary(ng-click='openDayStartModal(dayStart)') Save Custom Day Start
.personal-options.col-md-6
.panel.panel-default
diff --git a/website/views/shared/modals/index.jade b/website/views/shared/modals/index.jade
index 8146882046..a70803cf49 100644
--- a/website/views/shared/modals/index.jade
+++ b/website/views/shared/modals/index.jade
@@ -14,3 +14,7 @@ include ./limited
include ./invite-friends
include ./welcome.jade
include ./low-health.jade
+
+//- Settings
+script(type='text/ng-template', id='modals/change-day-start.html')
+ include ./settings/change-day-start.jade
diff --git a/website/views/shared/modals/settings/change-day-start.jade b/website/views/shared/modals/settings/change-day-start.jade
new file mode 100644
index 0000000000..603b6b76c5
--- /dev/null
+++ b/website/views/shared/modals/settings/change-day-start.jade
@@ -0,0 +1,15 @@
+.text-center
+ .modal-header
+ h3=env.t('changeCustomDayStart')
+
+ .modal-body
+ .alert.alert-danger
+ p=env.t('nextCron', {time: '{{nextCron | date: "M/d/yy \'@\' h:mm a"}}'})
+ p=env.t('sureChangeCustomDayStart')
+
+ .alert.alert-info
+ div last cron {{user.lastCron}}
+
+ .modal-footer
+ a.btn.btn-default(ng-click='$close()')=env.t('close')
+ a.btn.btn-danger(ng-click='saveDayStart(); $close()')=env.t('confirm')