正在进行的基于系统时间的计时器/时钟?

时间:2017-03-07 02:57:05

标签: javascript html

发现很难绕过如何做到这一点。我希望制作一个幻想时区的时钟,并根据系统时间计算(因为当你不在网页上时需要继续)。

白天从早上7点到晚上9点59分是实时200分钟。 13秒(实时)是1分钟。 夜间从晚上10点到早上6点59分持续45分钟实时。 5秒(实时)是1分钟。

我想在幻想世界中使用html显示时间(例如:下午5:43),但随后还会显示实时分钟的时间,直到幻想世界中的白天/夜晚(例如:32分钟内的夜晚) )。

我认为我需要使用Date.setHours(0,0,0,0)将系统时间设置为0然后计算出差异。数学真的让我感到困惑。

1 个答案:

答案 0 :(得分:1)

在我们开始使用解决方案之前,让我们先从一些测试开始 - 这将为最终解决方案提供定义,让我们相信我们能够找到正确的解决方案。

我们最终追求的是一个功能;我们称之为realTimeToGameTime。它需要一个Date对象,并返回一个看起来像{ day: 0, time: '7:00 am' }的对象。在你的问题中,你没有提到重要的是要知道它在游戏中的哪一天(仅在什么时间),但这不仅是一个有用的补充,你会发现它对解决方案很重要。 / p>

现在我们知道最终结果是什么样的,我们可以考虑一些测试。实时和游戏时间都有一点代表游戏时间和实时之间的对应关系。实时,它可以是我们想要的任何时间点;我称之为c。在游戏时间,我们会在第0天任意调用它。它允许我们构建一个时间轴,为我们提供测试用例的输入和输出(以分钟和小时/分钟为单位):

  c+0m   c+100m    c+200m   c+215m   c+245m   c+345m   c+445m   c+490m
  c+0h   c+1:40h   c+3:20h  c+3:35h  c+4:05h  c+5:45h  c+7:25h  c+8:10h
---+--------+--------+--------+--------+--------+--------+---------+---
  7:00a   2:30p    10:00p    1:00a   7:00a     2:30p   10:00p    7:00a
   day 0                              day 1                       day 2

我们将选择一个任意的日期/时间 - 2017年1月1日午夜 - 并编写我们的测试:

const y = 2017
const m = 0        // 0=Jan in JS
const d = 1
expect(realTimeToGameTime(new Date(y, m, d, 0, 0))
  .toEqual({ day: 0, time: '7:00 am' })
expect(realTimeToGameTime(new Date(y, m, d, 1, 40))
  .toEqual({ day: 0, time: '2:30 pm' })
expect(realTimeToGameTime(new Date(y, m, d, 3, 20))
  .toEqual({ day: 0, time: '10:00 pm' })   
expect(realTimeToGameTime(new Date(y, m, d, 3, 35))
  .toEqual({ day: 0, time: '1:00 am' })   
expect(realTimeToGameTime(new Date(y, m, d, 4, 50))
  .toEqual({ day: 1, time: '7:00 am' })   
expect(realTimeToGameTime(new Date(y, m, d, 5, 45))
  .toEqual({ day: 1, time: '2:30 pm' })   
expect(realTimeToGameTime(new Date(y, m, d, 7, 25))
  .toEqual({ day: 1, time: '10:00 pm' })
expect(realTimeToGameTime(new Date(y, m, d, 8, 50))
  .toEqual({ day: 2, time: '7:00 am' })

(在这个例子中,我正在使用Jest期望断言;你可以定制你喜欢的任何测试/断言框架......只要确保它有办法测试对象上的值相等。)

现在解决方案。

当遇到这样的问题时 - 可能会很快混淆,特别是当你考虑所有的实现细节时 - 最好看一个更容易推理的简化图片。您选择的数字使得很难理解所需的操作,因为有太多的细节:几分钟到几小时,JavaScript喜欢毫秒等等。所以让我们从一个简化的图片开始,这个图片接近您的示例,但更容易思考(不要担心:它会根据您的具体需求进行推广)。

让我们说我们只担心几个小时,游戏中的白天(7:00a-9:59p)实时持续3小时,夜间(10:00p-6:59a)持续1小时即时的。在这个简化的模型中,我们只关心时间,我们有一个更整洁的时间表:

  c+0h    c+1h   c+2h    c+3h   c+4h    c+5h    c+6h    c+7h   c+8h
----+-------+------+-------+------+------+-------+-------+------+-----
  7:00a  12:00p  5:00p  10:00p  7:00a  12:00p  5:00p  10:00p  7:00a
   day 0                         day 1                         day 2

现在问题更容易保持在我们的头脑中,但是到达最终的解决方案似乎仍然令人生畏。但是在更难的问题中嵌入了一个简单的问题:从游戏世界新一天的黎明开始,在特定的真实世界持续时间之后,现实游戏世界中几点了?更简单的是,让我们将其限制在游戏世界的某一天。解决 问题非常简单。然而,在我们这样做之前,我们需要一些输入。以下是相关输入(我在这里使用“day”表示24小时,区别于“白天”):

const gameDaybreak = 7
const gameDayLength = 24
const gameDaytimeLength = 15
const gameNighttimeLength = gameDayLength - gameDaytimeLength

const gameDayLengthInRealTime = 4
const gameDaytimeLengthInRealTime = 3
const gameNighttimeLengthInRealTime = 
  gameDayLengthInRealTime - gameDaytimeLengthInRealTime

现在我们可以编写我们的函数:

gameTimeSinceDaybreakFromRealDuration(realDuration) {
    if(realDuration > gameDayLengthInRealTime)
        throw new Error("this function only works within a single game day")
    if(realDuration <= gameDaytimeLengthInRealTime) {
        return (gameDaybreak + realDuration * gameDaytimeLength /
            gameDaytimeLengthInRealTime) % gameDayLength
    } else {
        return (gameDaybreak + gameDaytimeLength +
            (realDuration - gameDaytimeLengthInRealTime) *
                gameNighttimeLength / gameNighttimeLengthInRealTime) %
                    gameDayLength
    }
}

只剩下一个拼图:如何确定游戏日,以及游戏日何时开始实时。

幸运的是,我们知道每个游戏日都会消耗特定数量的实际时间(在我们的简化示例中,为4小时)。因此,如果我们有一个任意的通信日期,我们可以简单地将该日期的小时数除以4,并到达(小数)游戏日。我们只是为了获得整数游戏时间而采取行动:

const c = new Date(2017, 0, 1)    // this can be anything

const realHoursSinceC = (realDate.valueOf() - c.valueOf())/1000/60/60
const gameDayFromRealDate = Math.floor(realHoursSinceC / gameDayLengthInRealTime)

(此功能在我们的最终实施中不完全相同,因为我们不会使用小时作为我们的时间基础。)

最后,为了了解我们当天有多少(实际)小时,我们可以减去代表过去游戏日的时间:

const realHoursIntoGameDay = (realDate.valueOf() - c.valueOf()) -
    gameDayFromRealDate * gameDayLengthInRealTime

(在这最后两次计算中,我们实际上可以更有效率,但这有点清晰。)

这就是我们刚才所做的真正的魔力:我们一直在把所有事情看作几个小时,所以它更容易理解。 但我们所做的一切只是在相同单位的任意时间点和持续时间。 JavaScript喜欢毫秒...所以我们所要做的就是使用毫秒而不是几小时,我们有一个通用的解决方案!

最后一个细节是我们需要一个格式化程序来将游戏时间变成一个很好的格式:

function formatGameTime(gameTime) {
    const totalMinutes = gameTime/1000/60    // convert from milliseconds to minutes
    const hours = Math.floor(totalMinutes/60)
    let minutes = (totalMinutes % 60).toFixed(0)
    if(minutes.length < 2) minutes = "0" + minutes   // get that leading zero!
    return hours < 13
        ? (hours + ':' + minutes + ' am')
        : ((hours - 12) + ':' + minutes + ' pm')
}

现在我们所要做的就是把它们放在一起:

realTimeToGameTime(realTime) {
    const gameDayFromRealDate = Math.floor((realTime.valueOf() - c.valueOf() /  gameDayLengthInRealTime)
    const realTimeIntoGameDay = (realTime.valueOf() - c.valueOf()) - gameDayFromRealDate *
        gameDayLengthInRealTime
    const gameTime = this.gameTimeSinceDaybreakFromRealDuration(realTimeIntoGameDay)
    return {
        day: gameDayFromRealDate,
        time: this.formatGameTime(gameTime),
    }
},

最后,因为我真的更喜欢纯函数 - 全局变量并不好!我们可以创建一个工厂来创建一个特定的“时间映射器”(注意这是使用一些ES2015功能):

function createTimeMapper(config) {
    const {
        c,
        gameDaybreak,
        gameDayLength,
        gameDaytimeLength,
        gameNighttimeLength,
        gameDayLengthInRealTime,
        gameDaytimeLengthInRealTime,
        gameNighttimeLengthInRealTime,
    } = config
    return {
        realTimeToGameTime(realTime) {
            const gameDayFromRealDate = Math.floor((realTime.valueOf() - c.valueOf()) / gameDayLengthInRealTime)
            const realTimeIntoGameDay = (realTime.valueOf() - c.valueOf()) - gameDayFromRealDate * gameDayLengthInRealTime
            const gameTime = this.gameTimeSinceDaybreakFromRealDuration(realTimeIntoGameDay)
            return {
                day: gameDayFromRealDate,
                time: this.formatGameTime(gameTime),
            }
        },

        gameTimeSinceDaybreakFromRealDuration(realDuration) {
          if(realDuration > gameDayLengthInRealTime)
            throw new Error("this function only works within a single game day")
          if(realDuration <= gameDaytimeLengthInRealTime) {
            return (gameDaybreak + realDuration * gameDaytimeLength / gameDaytimeLengthInRealTime) %
              gameDayLength
          } else {
            return (gameDaybreak + gameDaytimeLength +
              (realDuration - gameDaytimeLengthInRealTime) * gameNighttimeLength / gameNighttimeLengthInRealTime) %
                gameDayLength
          }
        },

        formatGameTime(gameTime) {
            const totalMinutes = gameTime/1000/60    // convert from milliseconds to minutes
            const hours = Math.floor(totalMinutes/60)
            let minutes = (totalMinutes % 60).toFixed(0)
            if(minutes.length < 2) minutes = "0" + minutes   // get that leading zero!
            return hours < 13
              ? (hours + ':' + minutes + ' am')
              : ((hours - 12) + ':' + minutes + ' pm')
        },
    }
}

使用它:

const timeMapper = createTimeMapper({
    new Date(2017, 0, 1),
    gameDaybreak: 7*1000*60*60,
    gameDayLength: 24*1000*60*60,
    gameDaytimeLength: 15*1000*60*60,
    gameNighttimeLength: 9*1000*60*60,
    gameDayLengthInRealTime: (245/60)*1000*60*60,
    gameDaytimeLengthInRealTime: (200/60)*1000*60*60,
    gameNighttimeLengthInRealTime: (45/60)*1000*60*60,
})