Javascript 基于系统时间的持续计时器/时钟?

Javascript 基于系统时间的持续计时器/时钟?,javascript,html,Javascript,Html,我觉得很难理解怎么做。我正在寻找一个时钟,这是一个幻想的时区,是根据系统时间计算,因为它需要进行时,你不在网页上 白天从早上7:00持续到晚上9:59,实时时间为200分钟。13秒的实时时间是1分钟。 夜间从晚上10:00持续到早上6:59,实时时间为45分钟。5秒的实时时间是1分钟 我想使用html显示幻想世界中的时间示例:5:43PM,但同时也显示在幻想世界中直到白天/夜晚的实时分钟数示例:32分钟内的夜晚 我想我需要用日期将系统时间设置为0。设置时间0,0,0,0,然后计算出差值。数学确实

我觉得很难理解怎么做。我正在寻找一个时钟,这是一个幻想的时区,是根据系统时间计算,因为它需要进行时,你不在网页上

白天从早上7:00持续到晚上9:59,实时时间为200分钟。13秒的实时时间是1分钟。 夜间从晚上10:00持续到早上6:59,实时时间为45分钟。5秒的实时时间是1分钟

我想使用html显示幻想世界中的时间示例:5:43PM,但同时也显示在幻想世界中直到白天/夜晚的实时分钟数示例:32分钟内的夜晚


我想我需要用日期将系统时间设置为0。设置时间0,0,0,0,然后计算出差值。数学确实是最让我困惑的东西。

在我们开始解决方案之前,让我们先从一些测试开始——这将给出最终解决方案的定义,并让我们有信心得出正确的解决方案

我们最终追求的是一种功能;我们称之为实时。它接受一个日期对象,并返回一个类似于{day:0,time:'7:00am'}的对象。在你的问题中,你没有提到知道游戏中的哪一天、什么时间是否重要,但这似乎不仅是一个有用的补充,你会发现它对解决方案很重要

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

  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 expect断言;您可以定制任何您喜欢的测试/断言框架…只要确保它有一种方法来测试对象上的值相等

现在开始解决问题

当遇到这样的问题时,很快就会迷惑,尤其是当你考虑所有的实现细节时,最好是看一个更容易推理的简化图片。您选择的数字使您很难理解所需的操作,因为有太多的细节:分钟到小时,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
    }
}
这个函数在我们的最终实现中不会完全相同,因为我们不会使用小时作为时间基础

最后,我们可以减去代表过去比赛日的时间,以了解我们一天实际投入了多少小时:

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')
}
最后,因为我真的更喜欢纯函数-全局函数是不好的!我们可以创建一个工厂来创建一个特定的时间映射器注意:这使用了一些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')
        },
    }
}

您无法在JS中设置系统时间。可能重复?谢谢您的帮助。代码非常有用。我已经玩了至少一个小时了。但我仍然在努力想办法解决这个问题!我没有时间自动更新,感觉速度太快了。也许你可以看看我做错了什么,我做了一些改变。
显然,我最初的数学是错误的,一天持续240分钟,白天200分钟,晚上40分钟。然而,时间仍然与它应该的时间不匹配。下一个午夜应该是协调世界时上午8:20左右。嗨,莎拉……你走对了,我的回答很仓促。我在下班散步的时候考虑过这个问题,我有一个更好的方法来考虑这个问题,但现在已经过了我的就寝时间,所以我的答案的更新将不得不等待。我所概述的基本上是正确的……但有一种更简单的方式来看待它。很快就会更新。没问题。慢慢来,我很感激你的帮助。我整晚都在做这个,所以我想我也该上床睡觉了!我想我解决了!时间也一样。
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,
})