Skip to content

Conversation

@bhbghghbgb
Copy link
Contributor

@bhbghghbgb bhbghghbgb commented Sep 8, 2025

english

Refactor Server Reset Time Handling

Refactor Tasks to Use Configurable UTC Server Reset Time

This pull request refactors several game-related tasks to use a new, configurable ServerResetTime struct. The goal is to eliminate hard-coded server reset logic, making the application more flexible and robust.

Previously, the application's logic assumed a fixed reset time based on the China/Asia/SAR server (Monday, 4 AM GMT+8). This approach is unreliable because it:

  • Relies on the local machine's clock, which can be affected by Daylight Saving Time (DST). This could cause tasks to run at the wrong time.
  • Is not configurable, forcing users on other servers (e.g., EU, NA) to manually adjust their machine's time zone for the tasks to work correctly.

The new ServerResetTime struct addresses these issues by:

  • Storing the reset time as a specific day of the week and an hour in UTC. This makes the application immune to DST changes and local time zone settings.
  • Providing helper methods like IsWeeklyResetHour and GetDayOfWeek that handle all the necessary date and time calculations. This ensures that the application's logic consistently reflects the correct server time, regardless of the user's location.

Changes

  • BetterGenshinImpact/Model/ServerResetTime.cs: A new struct to encapsulate a server's weekly reset day and hour in UTC.
  • BetterGenshinImpact/Core/Config/OtherConfig.cs: A new setting for a custom server reset time has been added.
  • BetterGenshinImpact/ViewModel/Pages/CommonSettingsPageViewModel.cs: Provides default server options (CN/Asia/SAR, EU, NA) for the user to choose from.
  • BetterGenshinImpact/View/Pages/CommonSettingsPage.xaml: A new configuration component is added to the GUI, allowing users to select a preset or type a custom reset time.
  • BetterGenshinImpact/View/Converters/StringToServerResetTimeConverter.cs & BetterGenshinImpact/App.xaml: A converter is registered to handle string-to-ServerResetTime conversions for user input.
  • Refactored Files: The following files have been refactored to use the new ServerResetTime object's methods instead of hard-coded date logic:
    • BetterGenshinImpact/GameTask/Common/Job/BlessingOfTheWelkinMoonTask.cs
    • BetterGenshinImpact/GameTask/Common/Job/GoToSereniteaPotTask.cs
    • BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs
    • BetterGenshinImpact/Core/Config/OneDragonFlowConfig.cs
  • Tests: A new test file, Test/BetterGenshinImpact.UnitTest/ServerResetTimeTest.cs, has been added to ensure the new date logic is correct. This file can be removed after the review.
image

Notes

  • Three task files and one config file have been refactored. The original lines were commented out and can be deleted after a review.
  • Old code lines are commented and can be removed later
  • Comments and documentation are currently in English (can be translated to Chinese if needed)
  • I am not familiar with this project's structure, so please review the refactoring to ensure it aligns with the codebase's conventions.

Asking for Review

  • File Location: I'm unsure where the ServerResetTime.cs file should be placed. I've temporarily added it to the Model folder. Please advise on the correct location.
  • Scope: I have only refactored the most obvious places where server reset logic is used. There may be other areas that require refactoring (e.g., tasks with time-based scheduling or user-entered times). These can be located by searching for .Hour or .Now in the IDE.
  • Dependency Management: I've accessed the configuration object using TaskContext.Instance().Config.OtherConfig. As I'm not familiar with the project's dependency management strategy, I'm unsure if this is the intended way to retrieve the configuration in different parts of the application.

Chinese AI translation:

重构任务以使用可配置的UTC服务器重置时间

本次拉取请求重构了几个游戏相关任务,以使用一个新的可配置的 ServerResetTime 结构体。目的是消除硬编码的服务器重置逻辑,使应用程序更加灵活和健壮。

以前,应用程序的逻辑依赖于中/亚/SAR服的固定重置时间(GMT+8时间周一凌晨4点)。这种方法不可靠,因为它:

  • 依赖本地机器的时钟,容易受到夏令时(DST)的影响,可能导致任务在错误的时间运行。
  • 不可配置,迫使用户在其他服务器(例如,欧服、美服)上需要手动调整其机器的时区,才能使任务正确运行。

新的 ServerResetTime 结构体通过以下方式解决了这些问题:

  • 将重置时间存储为 UTC时间中的特定星期几和小时。这使得应用程序不受夏令时变化和本地时区设置的影响。
  • 提供 IsWeeklyResetHourGetDayOfWeek 等辅助方法,处理所有必要的日期和时间计算。这确保了无论用户身处何地,应用程序的逻辑都能始终反映正确的服务器时间。

更改内容

  • BetterGenshinImpact/Model/ServerResetTime.cs: 一个新的结构体,用于封装服务器的每周重置日期和小时(UTC)。
  • BetterGenshinImpact/Core/Config/OtherConfig.cs: 添加了一个用于配置自定义服务器重置时间的新设置。
  • BetterGenshinImpact/ViewModel/Pages/CommonSettingsPageViewModel.cs: 为用户提供了默认的服务器选项(中/亚/SAR服、欧服、美服)。
  • BetterGenshinImpact/View/Pages/CommonSettingsPage.xaml: 在图形用户界面中添加了一个新的配置组件,允许用户选择预设选项或输入自定义重置时间。
  • BetterGenshinImpact/View/Converters/StringToServerResetTimeConverter.cs & BetterGenshinImpact/App.xaml: 注册了一个转换器,用于处理字符串到 ServerResetTime 类型的转换,以支持用户输入。
  • 已重构文件: 以下文件已重构,以使用新的 ServerResetTime 对象方法,而非硬编码的日期逻辑:
    • BetterGenshinImpact/GameTask/Common/Job/BlessingOfTheWelkinMoonTask.cs
    • BetterGenshinImpact/GameTask/Common/Job/GoToSereniteaPotTask.cs
    • BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs
    • BetterGenshinImpact/Core/Config/OneDragonFlowConfig.cs
  • 测试: 添加了一个新测试文件 Test/BetterGenshinImpact.UnitTest/ServerResetTimeTest.cs,以确保新的日期逻辑正确无误。该文件可以在审查后删除。
image

注意事项

  • 共重构了3个任务文件和1个配置文件。
  • 旧代码行已注释,后续可删除
  • 注释和文档目前为英文(如需可翻译为中文)
  • 我不熟悉此项目的结构,请审查重构部分,以确保其符合代码库的约定。

征求审查意见

  • 文件位置: 我不确定 ServerResetTime.cs 文件应放在哪里。目前我暂时将其放在了 Model 文件夹下。请告知正确的存放位置。
  • 范围: 我只重构了最明显的服务器重置逻辑使用点。可能还有其他不那么明显的地方需要重构(例如,用户输入的时间可能被误解为本地时间,或者任务调度可能基于本地时间而非UTC)。可以通过在IDE中搜索 .Hour.Now 来找到它们。
  • 依赖管理: 我使用了 TaskContext.Instance().Config.OtherConfig 来访问配置对象。由于我不熟悉该项目的依赖管理策略,不确定这是在应用程序中获取配置的预期方式。

@huiyadanli
Copy link
Member

我觉的现在使用本地时区的方式应该不会出现什么问题,你新增的方式还会造成额外的配置成本。

I think using the local time zone now should not cause any problems, and the new method you added will also result in additional configuration costs.

@huiyadanli huiyadanli closed this Sep 8, 2025
@bhbghghbgb
Copy link
Contributor Author

bhbghghbgb commented Sep 8, 2025

Hmm... In that case, is there a way to get the tasks to correctly check for the server reset time if I am in a different time zone than the server?

For example, if I am in GMT+7 but the Asia server is on GMT+8, the server resets at 3 AM my local time (which is 4 AM server time). However, the 'Blessing of the Welkin Moon' task is triggered based on my local time (if (DateTime.Now.Hour == 4)), which might not align correctly.

Similarly, some tasks depend on a specific DayOfWeek (like in AutoDomainTask.cs).

if ((now.DayOfWeek == DayOfWeek.Sunday && now.Hour >= 4 || now.DayOfWeek == DayOfWeek.Monday && now.Hour < 4) || limitedFullyStringRaocrListdone != null)

If there is a simpler way to handle this without adding configuration, please enlighten me. I understand that changing the system timezone to match the server is a workaround, but it would be much more convenient not to have to.

嗯... 既然如此,那有没有办法让我在不同于服务器的时区时,任务也能正确检查服务器的重置时间呢?

举个例子,如果我在 GMT+7 时区,而亚洲服务器在 GMT+8 时区,那么服务器是在我的本地时间凌晨3点重置(也就是服务器时间的早上4点)。然而,‘空月祝福’任务是根据我的本地时间触发的(if (DateTime.Now.Hour == 4)),这可能无法正确对齐。

同样,有些任务依赖于特定的DayOfWeek(比如自动秘境任务)。

if ((now.DayOfWeek == DayOfWeek.Sunday && now.Hour >= 4 || now.DayOfWeek == DayOfWeek.Monday && now.Hour < 4) || limitedFullyStringRaocrListdone != null)

如果有一个更简单的方法来解决这个问题,而无需增加配置项,请指点我一下。我明白将系统时区改为与服务器一致是一个解决办法,但如果能不这么做,肯定会方便很多。

@FishmanTheMurloc
Copy link
Contributor

FishmanTheMurloc commented Sep 8, 2025

鸭蛋忙得混乱了╮(╯▽╰)╭
老哥的意思是他那边不用东八区时间,代码里用DateTime.Now肯定对不上服务器的4点;以及考虑欧美服的时间设定
ServerResetTime.cs这个里面写得太冗长了,抛弃UTC时间,改用TimeProvider应该能让代码简洁一些并且更符合人类直觉(我盯着截图里的sun;20愣了半天反应过来这是周一凌晨4点周长重置的意思。。。)。。。又想了一下外国人应该更适应UTC,不能说中国人的北京时间直觉就适合全人类。。。但基于服务器时间编程至少能让代码简洁,还便于以后测试。。。

@bhbghghbgb
Copy link
Contributor Author

At first, I thought it would be better to give users more control. My reasoning was that if someone needed to change their system time manually for some reason, allowing a custom server reset time configuration would be helpful.

But if that's not the desired approach, then having a configuration for server region selection and simply inferring the time offset from that would probably be sufficient.

I can try to refactor the code to meet that goal if you would like my help. However, if a core developer plans to implement this, then I will be happy to just use a workaround in the meantime.

起初,我认为给用户更多控制权会更好。我的想法是,如果有人出于某些原因需要手动更改系统时间,那么允许自定义服务器重置时间的配置会很有用。

但如果这不是项目的首选方案,那么提供一个服务器区域选择的配置项,然后据此推断时间偏移量,可能就足够了。

如果你需要我的帮助,我可以尝试朝这个目标重构代码。不过,如果核心开发者打算自己来实现这个功能,那么我很乐意暂时先使用一个变通方案。

@huiyadanli
Copy link
Member

奥,我的问题。那确实只能通过设置来解决。

5a86beaf61d5c8c7d3923ac82f5bb645

看上去同类软件有不错的处理方案,不过针对夏令时还是很麻烦。可能需要引入第三方库来解决,比如 https://github.com/nodatime/nodatime

@bhbghghbgb
Copy link
Contributor Author

Understood. To confirm, the goal is to keep the logic server-time-based inline, like if (GetServerTime().Hour == 4), avoiding helper functions?

If so, NodaTime might be overkill. We can convert UTC to the fixed server timezone (no DST). For example:

// Pseudo-code: Get UTC now, convert to hardcoded server timezone
DateTimeOffset serverTime = DateTimeOffset.UtcNow.ToOffset(TimeSpan.FromHours(8)); // Asia
if (serverTime.Hour == 4 && serverTime.DayOfWeek == DayOfWeek.Monday) {
    // Reset logic
}
// Get the TimeZoneInfo object based on the selected server region
TimeZoneInfo serverTimeZone = TimeZoneInfo.FindSystemTimeZoneById("China Standard Time");
DateTime serverTime = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, serverTimeZone);
// The rest of the logic is the same
if (serverTime.Hour == 4) {
    // Logic for 4 AM reset
}

Both the servers and UTC time are not affected by Daylight Saving Time.

明白了。确认一下,目标是让代码逻辑保持基于服务器时间并内联,比如 if (GetServerTime().Hour == 4),避免辅助函数,对吗?

如果这样,NodaTime 可能太重了。我们可以将 UTC 时间转换为固定的服务器时区(无夏令时)。例如:

// 示例:获取当前UTC时间,转换为硬编码的服务器时区
DateTimeOffset serverTime = DateTimeOffset.UtcNow.ToOffset(TimeSpan.FromHours(8)); // 亚服
if (serverTime.Hour == 4 && serverTime.DayOfWeek == DayOfWeek.Monday) {
    // 重置逻辑
}
// 根据所选服务器区域获取 TimeZoneInfo 对象
TimeZoneInfo serverTimeZone = TimeZoneInfo.FindSystemTimeZoneById("China Standard Time");
DateTime serverTime = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, serverTimeZone);
// 接下来的逻辑与之前相同
if (serverTime.Hour == 4) {
    // 凌晨4点重置的逻辑
}

服务器和 UTC 时间均不受夏令时的影响。

@huiyadanli huiyadanli reopened this Sep 9, 2025
@huiyadanli
Copy link
Member

也就是说原神服务器均不受夏令时影响?

@FishmanTheMurloc
Copy link
Contributor

也就是说原神服务器均不受夏令时影响?

用有限的地理知识想了一下:除非玩家大部分都住在高维度地区,否则服务器没道理用夏令时吧

@bhbghghbgb
Copy link
Contributor Author

Yes, Genshin Impact servers use fixed timezone offsets and do not observe Daylight Saving Time (DST). DST is a feature of specific regional timezones (like US PST/PDT) that alters the local system clock. This inherent variability of local time is the core issue with using DateTime.Now. Using the fixed reference of UTC would resolve this.

没错,原神服务器使用的是固定的时区偏移,不实行夏令时(DST)。DST是某些地区性时区(如美国的PST/PDT)的特性,它会改变本地系统时钟。本地时间的这种固有可变性,正是在代码中使用 DateTime.Now 的核心问题。使用固定的UTC时间基准就可以解决这个问题。

@bhbghghbgb bhbghghbgb marked this pull request as draft September 9, 2025 18:41
@bhbghghbgb
Copy link
Contributor Author

These files internally hard-code server reset logic. This is obvious enough to me, and they have been refactored to use the server region timezone offset setting:

  • BetterGenshinImpact/GameTask/Common/Job/BlessingOfTheWelkinMoonTask.cs
  • BetterGenshinImpact/GameTask/Common/Job/GoToSereniteaPotTask.cs
  • BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs
  • BetterGenshinImpact/Core/Config/OneDragonFlowConfig.cs

These files are candidates for refactoring but it's dubious whether they are supposed to follow server time or local time. They may also require user time input that could be misinterpreted as local time. (Search for type Text and the string .Hour in the IDE to find them):

  • BetterGenshinImpact/GameTask/FarmingPlan/FarmingStatsRecorder.cs
  • BetterGenshinImpact/GameTask/LogParse/ExecutionRecordStorage.cs
  • BetterGenshinImpact/GameTask/LogParse/TravelsDiaryDetailManager.cs
  • BetterGenshinImpact/Service/ScriptService.cs

The refactored code lines are commented for comparison and can be deleted after review.

这些文件在内部硬编码了服务器重置逻辑,这一点对我来说非常明显,并且它们已经被重构以使用服务器区域时区偏移设置:

  • BetterGenshinImpact/GameTask/Common/Job/BlessingOfTheWelkinMoonTask.cs
  • BetterGenshinImpact/GameTask/Common/Job/GoToSereniteaPotTask.cs
  • BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs
  • BetterGenshinImpact/Core/Config/OneDragonFlowConfig.cs

这些文件是待重构的候选,但不明确它们到底应该遵循服务器时间还是本地时间,或者是否需要用户输入可能被误解为本地时间的时间。(可以在 IDE 中搜索类型 Text 和字符串 .Hour 来找到它们):

  • BetterGenshinImpact/GameTask/FarmingPlan/FarmingStatsRecorder.cs
  • BetterGenshinImpact/GameTask/LogParse/ExecutionRecordStorage.cs
  • BetterGenshinImpact/GameTask/LogParse/TravelsDiaryDetailManager.cs
  • BetterGenshinImpact/Service/ScriptService.cs

重构后的代码行已添加注释以供比较,可以在审查后删除。

@bhbghghbgb bhbghghbgb marked this pull request as ready for review September 9, 2025 19:58
@bhbghghbgb
Copy link
Contributor Author

bhbghghbgb commented Sep 10, 2025

I think the server's regional time or its offset needs to be exposed to the JS scripts.

Currently, most scripts weren't made to handle different server regions. They often use Date.now() or new Date().toLocaleString() for their scheduling, which currently works but assumes the CN server region.

A JavaScript Date only stores a UTC timestamp internally; it doesn't store an offset. Therefore, just passing the UTC time from a DateTimeOffset is somewhat bloated and doesn't provide the necessary context for scripts to calculate regional times.

There seem to be two ways to solve this:

  1. Expose the server time offset: The JS script would then need to manually factor this offset in for all its time calculations if they want to handle different regions.
  2. Expose an offset-applied Date object: This would return a Date object that represents the regional server time instead of UTC. However, this is not ideal because any subsequent timezone-related conversion (like toLocaleString()) would double-apply the user's local offset, breaking its intended function. The AutoHoeingOneDragon script is susceptible to this issue.

我认为服务器的区域时间或其偏移量需要暴露给 JS 脚本。

目前,大多数脚本在设计时没有考虑不同的服务器区域。它们经常使用 Date.now()new Date().toLocaleString() 进行任务调度,这目前是可行的,但前提是假设服务器区域为 CN。

JavaScript 的 Date 对象内部只存储 UTC 时间戳;它不存储偏移量。因此,仅仅传递 DateTimeOffset 中的 UTC 时间有些臃肿,并且没有为脚本计算区域时间提供必要的上下文。

似乎有两种方法可以解决这个问题:

  1. 暴露服务器时间偏移量: JS 脚本如果希望处理不同区域,就需要手动在所有时间计算中考虑这个偏移量。
  2. 暴露一个已应用偏移量的 Date 对象: 这将返回一个代表区域服务器时间(而非 UTC)的 Date 对象。然而,这并不理想,因为任何后续的时区相关转换(如 toLocaleString())都会重复应用用户的本地偏移量,从而破坏其预期功能。AutoHoeingOneDragon 脚本就容易遇到这个问题。

return local DateTimeOffset for eaiser testing without Config
@bhbghghbgb
Copy link
Contributor Author

I'm working on abstracting time access to use TimeProvider to make mocking easier for testing, as you suggested.

I've created a simple interface and implementation to replace the TimeZoneHelper:

public interface IServerTimeProvider
{
    DateTimeOffset GetServerTimeNow();
}

public class ServerTimeProvider(TimeProvider timeProvider) : IServerTimeProvider
{
    public DateTimeOffset GetServerTimeNow()
    {
        TimeSpan serverOffset;
        try
        {
            serverOffset = TaskContext.Instance().Config.OtherConfig.ServerTimeZoneOffset;
        }
        catch (Exception)
        {
            serverOffset = TimeSpan.FromHours(8);
        }
        return timeProvider.GetUtcNow().ToOffset(serverOffset);
    }
}

However, I've hit an architectural question. The method GetDomainConfig() in OneDragonFlowConfig has time-dependent logic but is a data model/ObservableObject used for serialization. Injecting IServerTimeProvider into it via constructor is not feasible or appropriate.

https://github.com/babalae/better-genshin-impact/blob/bf02c6786bd2e8badf82e19e475d511eb44c39a7/BetterGenshinImpact/Core/Config/OneDragonFlowConfig.cs#L117C1-L127C49

We have a few options:

  1. Refactor the logic out: Move the time-based logic from GetDomainConfig() into a separate service that can be injected properly, leaving the config as a pure data object.
  2. Use method injection: Change the signature to GetDomainConfig(IServerTimeProvider timeProvider) and pass the dependency in when called.
  3. Keep the static helper for now: Leave this specific method as-is for the moment to avoid a major refactor.

I need your input on the preferred approach for handling these kinds of cases where time logic exists in classes that shouldn't have dependencies injected into them.

我正在按照您的建议,尝试抽象化时间访问以使用 TimeProvider,从而更容易模拟时间进行测试。

我创建了一个简单的接口和实现来替换 TimeZoneHelper

public interface IServerTimeProvider
{
    DateTimeOffset GetServerTimeNow();
}

public class ServerTimeProvider(TimeProvider timeProvider) : IServerTimeProvider
{
    public DateTimeOffset GetServerTimeNow()
    {
        TimeSpan serverOffset;
        try
        {
            serverOffset = TaskContext.Instance().Config.OtherConfig.ServerTimeZoneOffset;
        }
        catch (Exception)
        {
            serverOffset = TimeSpan.FromHours(8);
        }
        return timeProvider.GetUtcNow().ToOffset(serverOffset);
    }
}

但是,我遇到了一个架构上的问题。OneDragonFlowConfig 中的 GetDomainConfig() 方法包含时间依赖逻辑,但它本身是一个用于序列化的数据模型/ObservableObject。通过构造函数向它注入 IServerTimeProvider 是不可行也不合适的。

https://github.com/babalae/better-genshin-impact/blob/bf02c6786bd2e8badf82e19e475d511eb44c39a7/BetterGenshinImpact/Core/Config/OneDragonFlowConfig.cs#L117C1-L127C49

我们有几个选择:

  1. 将逻辑重构出去:GetDomainConfig() 中基于时间的逻辑移到一个可以正确注入依赖的单独服务中,让配置对象保持为纯数据对象。
  2. 使用方法注入: 将方法签名改为 GetDomainConfig(IServerTimeProvider timeProvider),并在调用时传入依赖。
  3. 暂时保持静态辅助方法: 暂时保持这个特定方法不变,以避免大规模的重构。

我需要您就如何处理这类情况提供意见,即时间逻辑存在于不应直接注入依赖的类中。

@FishmanTheMurloc
Copy link
Contributor

We have a few options:

  1. Refactor the logic out: Move the time-based logic from GetDomainConfig() into a separate service that can be injected properly, leaving the config as a pure data object.
  2. Use method injection: Change the signature to GetDomainConfig(IServerTimeProvider timeProvider) and pass the dependency in when called.
  3. Keep the static helper for now: Leave this specific method as-is for the moment to avoid a major refactor.

你说的对,构造函数注入这里不适用了,即使提供一个无参构造函数或者强行指定构造函数,Json反序列化后还是得为对象手动补上依赖服务,更何况它最终还是ViewModel的成员,要补只能在ViewModel的Command中,调用OneDragonTaskItem.InitAction()时补。。。太难了
方法注入看起来是改动最小的选择,但你重构一波大的我也不介意,因为Review的人是鸭蛋(huiyadanli)
放心大胆地去做,鸭蛋最近很忙,等他有空了会看的(挖坑)

@Takaranoao
Copy link
Member

Takaranoao commented Sep 14, 2025

终于有人做了,太棒了。
我已经被迫让我的电脑过了很久的东八区时间了。期待。

I've been waiting for this for so long, and someone actually submitted a PR. I'm so touched!

@bhbghghbgb
Copy link
Contributor Author

bhbghghbgb commented Sep 14, 2025

The impact of refactoring to allow constructor or method injection of TimeProvider is significant, as it would require changing a substantial part of the project's structure to be fully DI-oriented. The following call stack illustrates the depth of the change needed:

  • DayOfWeek currentDayOfWeek = now.Hour >= 4 ? now.DayOfWeek : now.AddDays(-1).DayOfWeek;
    This logic is inside GoToSereniteaPotTask.GetReward.
  • GoToSereniteaPotTask.GetReward is called from OneDragonTaskItem.InitAction.
  • OneDragonTaskItem.InitAction is called from OneDragonFlowViewModel.OnOneKeyExecute.
  • OneDragonFlowViewModel is instantiated in TaskSettingsPageViewModel's constructor.
  • TaskSettingsPageViewModel is created by the DI container for the view TaskSettingsPage.

Introducing a dependency through all these layers would necessitate registering these components with the DI container. This represents a fundamental architectural shift that is beyond the scope of this current work.

Therefore, I will retain the TimeZoneHelper but refactor it to internally use a TimeProvider that can be supplied during the application's startup. This approach achieves the goal of enabling time mocking for future testing.

为了允许通过构造函数或方法注入 TimeProvider 而进行重构的影响很大,因为这需要将项目的很大一部分结构改为完全依赖注入导向的。下面的调用栈说明了所需更改的深度:

  • DayOfWeek currentDayOfWeek = now.Hour >= 4 ? now.DayOfWeek : now.AddDays(-1).DayOfWeek;
    该逻辑位于 GoToSereniteaPotTask.GetReward 内部。
  • GoToSereniteaPotTask.GetRewardOneDragonTaskItem.InitAction 调用。
  • OneDragonTaskItem.InitActionOneDragonFlowViewModel.OnOneKeyExecute 调用。
  • OneDragonFlowViewModelTaskSettingsPageViewModel 的构造函数中实例化。
  • TaskSettingsPageViewModel 由 DI 容器为视图 TaskSettingsPage 创建。

将依赖项引入所有这些层需要将这些组件注册到 DI 容器中。这代表了一种架构性改变,超出了当前工作的范围。

因此,我将保留 TimeZoneHelper,但会对其重构,使其内部使用一个可在应用程序启动时提供的 TimeProvider。这种方法可以达成为未来测试实现模拟时间的目标。

https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.time.testing.faketimeprovider

@FishmanTheMurloc
Copy link
Contributor

不用注入,我用词不当,现阶段只要通过参数传入就可以了

@bhbghghbgb
Copy link
Contributor Author

Understood, you meant simply passing the parameter down.

My point was that this is also cumbersome as it requires changing many layers. This may have been lost in the AI translation.

For now, should I proceed with passing the parameter?

明白了,您是指简单地将参数传递下去。

我的观点是这也很麻烦,因为需要修改很多层。这一点可能在 AI 翻译中被遗漏了。

目前,我是否应该先进行参数传递的重构?

@FishmanTheMurloc
Copy link
Contributor

Use method injection: Change the signature to GetDomainConfig(IServerTimeProvider timeProvider) and pass the dependency in when called.

这个从方法传入参数的似乎是最简单的,injection翻译成注入确实没错,但在大杂烩的代码中实施严格的DI太难了,所以往往就降级成简单的参数传递了 ╮(╯▽╰)╭ 具体还是你操刀哈,我也只是来围观的

@bhbghghbgb
Copy link
Contributor Author

  1. Keep the static helper for now: Leave this specific method as-is for the moment to avoid a major refactor.

Ah, I realized I didn't provide context about the static helper function. It's just a simple wrapper with a method to get DateTimeOffset directly to avoid repeating code. The most important part is accessing the Config, which appears to be already available everywhere through the global TaskContext.Instance().Config. I'm only considering whether this wrapper should be considered a 'Helper' and if the code should reside in the Helpers directory.

啊,我意识到我没有说明这个静态辅助函数的上下文。它只是一个简单的包装器,提供了一个直接获取 DateTimeOffset 的方法,以避免代码重复。最关键的部分是访问 Config(配置),这似乎已经可以通过全局的 TaskContext.Instance().Config 在任何地方实现了。我只是在考虑这个包装器是否应该算作一个‘Helper’(辅助类),以及它的代码是否应该放在 Helpers 目录中。

@bhbghghbgb bhbghghbgb marked this pull request as draft September 15, 2025 18:06
…roupProject->TaskCompletionSkipRuleConfig->BoundaryHour add option to use server time or local time, refactor IsSkipTask->IsTodayByBoundary and ScriptGroupProject.Run
@bhbghghbgb bhbghghbgb marked this pull request as ready for review September 15, 2025 20:21
@bhbghghbgb
Copy link
Contributor Author

While refactoring TravelsDiaryDetailManager, I'm uncertain whether it should be adapted to the server timezone setting.

In the method IsFileModifiedThisMonth, File.GetLastWriteTime returns a DateTime with DateTimeKind.Local. This can be easily converted to a regional server timezone DateTimeOffset.

Inside the method UpdateTravelsDiaryDetailManager, data is read from an API. It appears to return records with time in server time on the Asia/CN server, but I'm unsure if it also returns server time for other regions or strictly UTC+8. The current code commit assumes it returns regional server time.

The methods in TravelsDiaryDetailManager seem to be used by LogParse to present information to the user. Changing them to use server time might be incorrect. However, FarmingStatsRecorder.IsDailyFarmingLimitReached -> TryUpdateTravelsData depends on it...

Some assumptions were made in this commit. If it causes unwanted behavior, it can be reverted.

在重构 TravelsDiaryDetailManager 时,我不确定是否应该让它适配服务器时区设置。

IsFileModifiedThisMonth 方法中,File.GetLastWriteTime 返回的是 DateTimeKind.Local 类型的 DateTime。这可以轻松转换为区域服务器时区的 DateTimeOffset

UpdateTravelsDiaryDetailManager 方法内部,从 API 读取了一些数据。它似乎返回的是亚服/国服服务器时间,但我不确定在其他区域它是否也返回服务器时间,或者严格返回 UTC+8 时间。当前的代码提交假设它返回的是区域服务器时间。

TravelsDiaryDetailManager 中的方法似乎被 LogParse 用于向用户呈现信息。将它们改为使用服务器时间可能是不正确的。但是 FarmingStatsRecorder.IsDailyFarmingLimitReached -> TryUpdateTravelsData 依赖于此...

这个提交中做出了一些假设。如果导致不希望的行为,可以将其还原。

@bhbghghbgb
Copy link
Contributor Author

bhbghghbgb commented Sep 17, 2025

The PR is now complete. I found no more candidate operations requiring server time refactoring.

Summary:

  • Added a config item in BetterGenshinImpact/View/Pages/CommonSettingsPage.xaml for server region selection. BetterGenshinImpact/Core/Config/OtherConfig.cs stores the selected server's time offset.
  • All operations dependent on server time now use DateTimeOffset, which stores timezone offset information and allows easy conversion.
  • These files/tasks were cleanly updated to use the server time offset config:
    • BetterGenshinImpact/Core/Config/OneDragonFlowConfig.cs
    • BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs
    • BetterGenshinImpact/GameTask/Common/Job/BlessingOfTheWelkinMoonTask.cs
    • BetterGenshinImpact/GameTask/Common/Job/GoToSereniteaPotTask.cs
    • BetterGenshinImpact/GameTask/FarmingPlan/FarmingStatsRecorder.cs
  • The server time offset is exposed to JS in BetterGenshinImpact/Core/Script/EngineExtend.cs for multi-region support.
  • For backward compatibility, boundary time configs in ScriptGroupConfig are retained but deprecated. An option was added to specify whether to use local time or server time (default: local) as the reference:
    • BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml
    • BetterGenshinImpact/Core/Config/TaskCompletionSkipRuleConfig.cs
    • BetterGenshinImpact/Core/Config/PathingPartyTaskCycleConfig.cs
    • BetterGenshinImpact/ViewModel/Pages/View/ScriptGroupConfigViewModel.cs
    • BetterGenshinImpact/Service/ScriptService.cs
  • Modified BetterGenshinImpact/GameTask/LogParse/ExecutionRecord.cs to include new server-time fields for IsSkipTask->IsTodayByBoundary logic. Maintains backward compatibility by falling back to existing local time fields if new fields are missing.
  • Refactored only externally-called methods in BetterGenshinImpact/GameTask/LogParse/TravelsDiaryDetailManager.cs that expect server time (e.g., UpdateTravelsDiaryDetailManager, loadNowDayActionItems). LogParse functions were untouched.
  • Note: Updating the app to the current commit/build should not cause a config reset. Additionally, if your system's local timezone is already set to UTC+8, you should not notice any behavioral changes. Changing these settings may cause existing records to parse incorrectly if not deleted, as there's no current mechanism to invalidate them.

PR 现已完成。未发现更多需要重构的服务器时间相关操作。

总结:

  • BetterGenshinImpact/View/Pages/CommonSettingsPage.xaml 中添加了服务器区域选择的配置项。BetterGenshinImpact/Core/Config/OtherConfig.cs 存储所选服务器的时区偏移量。
  • 所有依赖于服务器时间的操作现在都使用 DateTimeOffset,它存储时区偏移信息并便于转换。
  • 以下文件/任务已更新为使用服务器时区偏移配置:
    • BetterGenshinImpact/Core/Config/OneDragonFlowConfig.cs
    • BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs
    • BetterGenshinImpact/GameTask/Common/Job/BlessingOfTheWelkinMoonTask.cs
    • BetterGenshinImpact/GameTask/Common/Job/GoToSereniteaPotTask.cs
    • BetterGenshinImpact/GameTask/FarmingPlan/FarmingStatsRecorder.cs
  • 服务器时区偏移量通过 BetterGenshinImpact/Core/Script/EngineExtend.cs 暴露给 JS,以支持多区域。
  • 为了向后兼容,ScriptGroupConfig 中的边界时间配置被保留但已弃用。添加了一个选项来指定是使用本地时间还是服务器时间(默认:本地)作为参考时间:
    • BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml
    • BetterGenshinImpact/Core/Config/TaskCompletionSkipRuleConfig.cs
    • BetterGenshinImpact/Core/Config/PathingPartyTaskCycleConfig.cs
    • BetterGenshinImpact/ViewModel/Pages/View/ScriptGroupConfigViewModel.cs
    • BetterGenshinImpact/Service/ScriptService.cs
  • 修改了 BetterGenshinImpact/GameTask/LogParse/ExecutionRecord.cs,为 IsSkipTask->IsTodayByBoundary 逻辑添加了新的服务器时间字段。如果新字段缺失,则回退到现有的本地时间字段以保持兼容性。
  • 仅重构了 BetterGenshinImpact/GameTask/LogParse/TravelsDiaryDetailManager.cs 中期望服务器时间的外部调用方法(例如 UpdateTravelsDiaryDetailManager, loadNowDayActionItems)。未触动 LogParse 功能。
  • 注意:将应用程序更新到当前版本不会导致配置重置。此外,如果您的系统本地时区已设置为UTC+8,您将不会察觉到任何行为上的变化。更改这些设置可能会导致现有记录解析错误(如果未删除),因为目前没有使其失效的机制。
config screenshots image image image

@huiyadanli huiyadanli merged commit 94e5030 into babalae:main Sep 22, 2025
1 check passed
@huiyadanli
Copy link
Member

Thanks for this!

@bhbghghbgb bhbghghbgb deleted the server-reset-time branch September 23, 2025 07:24
ShiroRikka pushed a commit to ShiroRikka/ShiroRikka-better-genshin-impact that referenced this pull request Sep 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants