@oneuptime/common 11.3.25 → 11.3.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/AIConversation.ts +322 -0
- package/Models/DatabaseModels/AIConversationMessage.ts +495 -0
- package/Models/DatabaseModels/AIRun.ts +584 -0
- package/Models/DatabaseModels/AIRunEvent.ts +443 -0
- package/Models/DatabaseModels/IncidentEpisodeRoleMember.ts +1 -1
- package/Models/DatabaseModels/IncidentMember.ts +1 -1
- package/Models/DatabaseModels/IncomingCallPolicy.ts +13 -0
- package/Models/DatabaseModels/Index.ts +11 -0
- package/Models/DatabaseModels/LlmLog.ts +26 -0
- package/Models/DatabaseModels/LlmProvider.ts +2 -2
- package/Models/DatabaseModels/MigrationFailure.ts +170 -0
- package/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.ts +51 -2
- package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +44 -0
- package/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.ts +6 -0
- package/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.ts +6 -0
- package/Models/DatabaseModels/OnCallDutyPolicyTimeLog.ts +1 -1
- package/Models/DatabaseModels/OnCallDutyPolicyUserOverride.ts +2 -2
- package/Server/API/AIChatAPI.ts +693 -0
- package/Server/API/AlertAPI.ts +1 -1
- package/Server/API/IncidentAPI.ts +2 -2
- package/Server/API/IncidentEpisodeAPI.ts +1 -1
- package/Server/API/LlmProviderAPI.ts +170 -0
- package/Server/API/MicrosoftTeamsAPI.ts +5 -0
- package/Server/API/ScheduledMaintenanceAPI.ts +1 -1
- package/Server/API/SlackAPI.ts +663 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783363279075-AddAIChatModels.ts +245 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783443471795-AddLlmProviderToAIConversation.ts +36 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783453297388-AddAIChatWriteActionsAndWidgets.ts +46 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783461767405-MigrationName.ts +119 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.ts +81 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.ts +35 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +16 -0
- package/Server/Infrastructure/PostgresDatabase.ts +11 -0
- package/Server/Services/AIConversationMessageService.ts +39 -0
- package/Server/Services/AIConversationService.ts +63 -0
- package/Server/Services/AIRunEventService.ts +39 -0
- package/Server/Services/AIRunService.ts +39 -0
- package/Server/Services/AIService.ts +127 -33
- package/Server/Services/AnalyticsDatabaseService.ts +10 -1
- package/Server/Services/DatabaseService.ts +29 -1
- package/Server/Services/IncidentService.ts +1 -1
- package/Server/Services/IncomingCallPolicyEscalationRuleService.ts +75 -2
- package/Server/Services/IncomingCallPolicyService.ts +42 -1
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/LlmProviderService.ts +110 -0
- package/Server/Services/MigrationFailureService.ts +9 -0
- package/Server/Services/MonitorProbeService.ts +33 -7
- package/Server/Services/MonitorService.ts +19 -7
- package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +65 -0
- package/Server/Services/OnCallDutyPolicyEscalationRuleService.ts +185 -41
- package/Server/Services/OnCallDutyPolicyEscalationRuleUserService.ts +23 -15
- package/Server/Services/OnCallDutyPolicyExecutionLogService.ts +39 -0
- package/Server/Services/OnCallDutyPolicyScheduleLayerService.ts +148 -0
- package/Server/Services/OnCallDutyPolicyScheduleLayerUserService.ts +11 -2
- package/Server/Services/OnCallDutyPolicyScheduleService.ts +502 -26
- package/Server/Services/OnCallDutyPolicyTimeLogService.ts +49 -7
- package/Server/Services/OnCallDutyPolicyUserOverrideService.ts +218 -4
- package/Server/Services/ProjectCallSMSConfigService.ts +82 -1
- package/Server/Services/ProjectService.ts +24 -0
- package/Server/Services/TeamMemberService.ts +6 -0
- package/Server/Services/UserNotificationRuleService.ts +13 -42
- package/Server/Services/UserOnCallLogService.ts +90 -0
- package/Server/Services/UserOnCallLogTimelineService.ts +43 -1
- package/Server/Types/AnalyticsDatabase/ModelPermission.ts +74 -9
- package/Server/Types/Database/QueryUtil.ts +57 -0
- package/Server/Utils/AI/AIChatPrivacyFilter.ts +28 -0
- package/Server/Utils/AI/AlertAIContextBuilder.ts +6 -1
- package/Server/Utils/AI/Chat/ChatAgentRunner.ts +1066 -0
- package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +239 -0
- package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +51 -0
- package/Server/Utils/AI/IncidentAIContextBuilder.ts +18 -3
- package/Server/Utils/AI/IncidentEpisodeAIContextBuilder.ts +6 -1
- package/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.ts +12 -2
- package/Server/Utils/AI/Toolbox/AlertTools.ts +201 -0
- package/Server/Utils/AI/Toolbox/AlertWriteTools.ts +174 -0
- package/Server/Utils/AI/Toolbox/ContextTools.ts +189 -0
- package/Server/Utils/AI/Toolbox/ExceptionTools.ts +149 -0
- package/Server/Utils/AI/Toolbox/IncidentTools.ts +386 -0
- package/Server/Utils/AI/Toolbox/IncidentWriteTools.ts +350 -0
- package/Server/Utils/AI/Toolbox/Index.ts +231 -0
- package/Server/Utils/AI/Toolbox/LogTools.ts +339 -0
- package/Server/Utils/AI/Toolbox/MetricTools.ts +179 -0
- package/Server/Utils/AI/Toolbox/MonitorTools.ts +193 -0
- package/Server/Utils/AI/Toolbox/RecentChangesTools.ts +208 -0
- package/Server/Utils/AI/Toolbox/Serializer.ts +299 -0
- package/Server/Utils/AI/Toolbox/ToolTypes.ts +249 -0
- package/Server/Utils/AI/Toolbox/TraceTools.ts +402 -0
- package/Server/Utils/AI/Toolbox/WidgetBuilder.ts +189 -0
- package/Server/Utils/Database/MigrationFailureLog.ts +240 -0
- package/Server/Utils/IncomingCallPhoneNumber.ts +41 -0
- package/Server/Utils/LLM/LLMService.ts +605 -96
- package/Server/Utils/Telemetry/IoTSnapshotScan.ts +12 -9
- package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +12 -9
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +512 -19
- package/Server/Utils/Workspace/Slack/Slack.ts +80 -0
- package/Server/Utils/Workspace/Slack/app-manifest.json +16 -1
- package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +0 -8
- package/Tests/Server/Services/OnCallDutyPolicyScheduleLayerUserReorder.test.ts +233 -0
- package/Tests/Server/Services/OnCallDutyPolicyTimeLogServiceScoping.test.ts +242 -0
- package/Tests/Server/Services/OnCallDutyPolicyUserOverrideEdit.test.ts +198 -0
- package/Tests/Server/Services/UserOnCallLogClaimNotificationRule.test.ts +495 -0
- package/Tests/Server/Types/Database/QueryUtil.test.ts +74 -0
- package/Tests/Server/Utils/AI/AIChatModelACL.test.ts +42 -0
- package/Tests/Server/Utils/AI/ChatAgentHelpers.test.ts +89 -0
- package/Tests/Server/Utils/AI/LLMServiceBaseUrl.test.ts +139 -0
- package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +386 -0
- package/Tests/Server/Utils/AI/ToolArgsGetTimeRange.test.ts +62 -0
- package/Tests/Server/Utils/AI/ToolArgsScopeServiceIds.test.ts +79 -0
- package/Tests/Server/Utils/AI/ToolResultSerializer.test.ts +223 -0
- package/Tests/Types/Billing/SubscriptionStatus.test.ts +127 -0
- package/Tests/Types/DateExhaustiveTimezone.test.ts +700 -0
- package/Tests/Types/DateTimezoneWallClock.test.ts +99 -0
- package/Tests/Types/Events/Recurring.test.ts +22 -9
- package/Tests/Types/Metrics/RecordingRuleDefinition.test.ts +213 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilAuditFixes.test.ts +291 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilAuditFixesRound2.test.ts +398 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilDSTWeekendGap.test.ts +114 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilDSTWeeklyDayShift.test.ts +76 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilDailyMutation.test.ts +128 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilDailyProbe.test.ts +203 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilDiffFuzz.test.ts +322 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilEdgeCases.test.ts +280 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveCurrentUser.test.ts +863 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveDaily.test.ts +900 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveMultiLayer.test.ts +1104 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveRotation.test.ts +957 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveWeekly.test.ts +1095 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilInvariants.test.ts +232 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilMergeAudit.test.ts +227 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilMonthYearAudit.test.ts +255 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilMultiLayerAudit.test.ts +388 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilMultiLayerEdge.test.ts +300 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilOvernightDSTAudit.test.ts +158 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilRestrictedGapFix.test.ts +253 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilRestrictedGapRepro.test.ts +157 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilRotationFixes.test.ts +414 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilTimezone.test.ts +243 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilWeekendGapRepro.test.ts +112 -0
- package/Tests/Types/OnCallDutyPolicy/OverrideLensAudit.test.ts +117 -0
- package/Tests/Types/OnCallDutyPolicy/RestrictionTimes.test.ts +8 -6
- package/Tests/Types/OnCallDutyPolicy/RestrictionTimesDefaultDayAudit.test.ts +112 -0
- package/Tests/Types/OnCallDutyPolicy/RestrictionTimesExhaustive.test.ts +821 -0
- package/Tests/Types/OnCallDutyPolicy/RestrictionTimesMutationInvariant.test.ts +886 -0
- package/Tests/Types/OnCallDutyPolicy/UserOverrideUtil.test.ts +255 -0
- package/Tests/Types/OnCallDutyPolicy/UserOverrideUtilExhaustive.test.ts +1126 -0
- package/Types/AI/AIChatMessageRole.ts +6 -0
- package/Types/AI/AIChatMessageStatus.ts +32 -0
- package/Types/AI/AIChatPermissionMode.ts +69 -0
- package/Types/AI/AIChatTypes.ts +231 -0
- package/Types/AI/AIRunEventType.ts +17 -0
- package/Types/AI/AIRunStatus.ts +28 -0
- package/Types/AI/AIRunType.ts +6 -0
- package/Types/Date.ts +200 -15
- package/Types/LLM/LlmType.ts +6 -0
- package/Types/OnCallDutyPolicy/Layer.ts +790 -260
- package/Types/OnCallDutyPolicy/RestrictionTimes.ts +39 -13
- package/Types/OnCallDutyPolicy/UserOverrideUtil.ts +21 -5
- package/UI/Components/Events/RecurringFieldElement.tsx +16 -0
- package/UI/Components/Header/HeaderIconDropdownButton.tsx +22 -12
- package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +35 -3
- package/UI/Components/ModelFormModal/ModelFormModal.tsx +8 -0
- package/UI/Components/Page/Page.tsx +14 -0
- package/UI/Components/RadioButtons/BasicRadioButtons.tsx +1 -1
- package/UI/Utils/LlmTypeDropdownOptions.ts +40 -0
- package/UI/Utils/TestLLMProvider.ts +59 -0
- package/build/dist/Models/DatabaseModels/AIConversation.js +345 -0
- package/build/dist/Models/DatabaseModels/AIConversation.js.map +1 -0
- package/build/dist/Models/DatabaseModels/AIConversationMessage.js +521 -0
- package/build/dist/Models/DatabaseModels/AIConversationMessage.js.map +1 -0
- package/build/dist/Models/DatabaseModels/AIRun.js +619 -0
- package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -0
- package/build/dist/Models/DatabaseModels/AIRunEvent.js +469 -0
- package/build/dist/Models/DatabaseModels/AIRunEvent.js.map +1 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisodeRoleMember.js +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodeRoleMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentMember.js +1 -1
- package/build/dist/Models/DatabaseModels/IncidentMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncomingCallPolicy.js +10 -0
- package/build/dist/Models/DatabaseModels/IncomingCallPolicy.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +10 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/LlmLog.js +28 -0
- package/build/dist/Models/DatabaseModels/LlmLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/LlmProvider.js +2 -2
- package/build/dist/Models/DatabaseModels/LlmProvider.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MigrationFailure.js +196 -0
- package/build/dist/Models/DatabaseModels/MigrationFailure.js.map +1 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +52 -2
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +45 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.js +6 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.js +6 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyUserOverride.js +2 -2
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyUserOverride.js.map +1 -1
- package/build/dist/Server/API/AIChatAPI.js +498 -0
- package/build/dist/Server/API/AIChatAPI.js.map +1 -0
- package/build/dist/Server/API/AlertAPI.js +1 -1
- package/build/dist/Server/API/IncidentAPI.js +2 -2
- package/build/dist/Server/API/IncidentEpisodeAPI.js +1 -1
- package/build/dist/Server/API/LlmProviderAPI.js +108 -1
- package/build/dist/Server/API/LlmProviderAPI.js.map +1 -1
- package/build/dist/Server/API/MicrosoftTeamsAPI.js +4 -0
- package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/ScheduledMaintenanceAPI.js +1 -1
- package/build/dist/Server/API/SlackAPI.js +442 -0
- package/build/dist/Server/API/SlackAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783363279075-AddAIChatModels.js +96 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783363279075-AddAIChatModels.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783443471795-AddLlmProviderToAIConversation.js +25 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783443471795-AddLlmProviderToAIConversation.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783453297388-AddAIChatWriteActionsAndWidgets.js +31 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783453297388-AddAIChatWriteActionsAndWidgets.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783461767405-MigrationName.js +46 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783461767405-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.js +38 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/PostgresDatabase.js +9 -0
- package/build/dist/Server/Infrastructure/PostgresDatabase.js.map +1 -1
- package/build/dist/Server/Services/AIConversationMessageService.js +34 -0
- package/build/dist/Server/Services/AIConversationMessageService.js.map +1 -0
- package/build/dist/Server/Services/AIConversationService.js +42 -0
- package/build/dist/Server/Services/AIConversationService.js.map +1 -0
- package/build/dist/Server/Services/AIRunEventService.js +34 -0
- package/build/dist/Server/Services/AIRunEventService.js.map +1 -0
- package/build/dist/Server/Services/AIRunService.js +34 -0
- package/build/dist/Server/Services/AIRunService.js.map +1 -0
- package/build/dist/Server/Services/AIService.js +84 -25
- package/build/dist/Server/Services/AIService.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +10 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +20 -1
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +1 -1
- package/build/dist/Server/Services/IncomingCallPolicyEscalationRuleService.js +55 -2
- package/build/dist/Server/Services/IncomingCallPolicyEscalationRuleService.js.map +1 -1
- package/build/dist/Server/Services/IncomingCallPolicyService.js +40 -0
- package/build/dist/Server/Services/IncomingCallPolicyService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/LlmProviderService.js +108 -0
- package/build/dist/Server/Services/LlmProviderService.js.map +1 -1
- package/build/dist/Server/Services/MigrationFailureService.js +9 -0
- package/build/dist/Server/Services/MigrationFailureService.js.map +1 -0
- package/build/dist/Server/Services/MonitorProbeService.js +33 -7
- package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +12 -5
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +81 -22
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js +134 -31
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js +21 -13
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js +36 -0
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js +121 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js +11 -2
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +409 -36
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyTimeLogService.js +52 -8
- package/build/dist/Server/Services/OnCallDutyPolicyTimeLogService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js +170 -3
- package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js.map +1 -1
- package/build/dist/Server/Services/ProjectCallSMSConfigService.js +72 -0
- package/build/dist/Server/Services/ProjectCallSMSConfigService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +25 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +6 -0
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +12 -30
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/UserOnCallLogService.js +75 -0
- package/build/dist/Server/Services/UserOnCallLogService.js.map +1 -1
- package/build/dist/Server/Services/UserOnCallLogTimelineService.js +31 -1
- package/build/dist/Server/Services/UserOnCallLogTimelineService.js.map +1 -1
- package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js +50 -7
- package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryUtil.js +45 -0
- package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
- package/build/dist/Server/Utils/AI/AIChatPrivacyFilter.js +18 -0
- package/build/dist/Server/Utils/AI/AIChatPrivacyFilter.js.map +1 -0
- package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js +6 -1
- package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +756 -0
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -0
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +165 -0
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -0
- package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +44 -0
- package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js.map +1 -0
- package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js +18 -3
- package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js.map +1 -1
- package/build/dist/Server/Utils/AI/IncidentEpisodeAIContextBuilder.js +6 -1
- package/build/dist/Server/Utils/AI/IncidentEpisodeAIContextBuilder.js.map +1 -1
- package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js +12 -2
- package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js +167 -0
- package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/AlertWriteTools.js +136 -0
- package/build/dist/Server/Utils/AI/Toolbox/AlertWriteTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/ContextTools.js +141 -0
- package/build/dist/Server/Utils/AI/Toolbox/ContextTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/ExceptionTools.js +117 -0
- package/build/dist/Server/Utils/AI/Toolbox/ExceptionTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js +318 -0
- package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/IncidentWriteTools.js +280 -0
- package/build/dist/Server/Utils/AI/Toolbox/IncidentWriteTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/Index.js +153 -0
- package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/LogTools.js +246 -0
- package/build/dist/Server/Utils/AI/Toolbox/LogTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/MetricTools.js +120 -0
- package/build/dist/Server/Utils/AI/Toolbox/MetricTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/MonitorTools.js +158 -0
- package/build/dist/Server/Utils/AI/Toolbox/MonitorTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/RecentChangesTools.js +165 -0
- package/build/dist/Server/Utils/AI/Toolbox/RecentChangesTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/Serializer.js +228 -0
- package/build/dist/Server/Utils/AI/Toolbox/Serializer.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/ToolTypes.js +142 -0
- package/build/dist/Server/Utils/AI/Toolbox/ToolTypes.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/TraceTools.js +309 -0
- package/build/dist/Server/Utils/AI/Toolbox/TraceTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/WidgetBuilder.js +120 -0
- package/build/dist/Server/Utils/AI/Toolbox/WidgetBuilder.js.map +1 -0
- package/build/dist/Server/Utils/Database/MigrationFailureLog.js +174 -0
- package/build/dist/Server/Utils/Database/MigrationFailureLog.js.map +1 -0
- package/build/dist/Server/Utils/IncomingCallPhoneNumber.js +30 -0
- package/build/dist/Server/Utils/IncomingCallPhoneNumber.js.map +1 -0
- package/build/dist/Server/Utils/LLM/LLMService.js +452 -92
- package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js +11 -8
- package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +11 -8
- package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +378 -15
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js +59 -0
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/app-manifest.json +16 -1
- package/build/dist/Types/AI/AIChatMessageRole.js +7 -0
- package/build/dist/Types/AI/AIChatMessageRole.js.map +1 -0
- package/build/dist/Types/AI/AIChatMessageStatus.js +27 -0
- package/build/dist/Types/AI/AIChatMessageStatus.js.map +1 -0
- package/build/dist/Types/AI/AIChatPermissionMode.js +53 -0
- package/build/dist/Types/AI/AIChatPermissionMode.js.map +1 -0
- package/build/dist/Types/AI/AIChatTypes.js +74 -0
- package/build/dist/Types/AI/AIChatTypes.js.map +1 -0
- package/build/dist/Types/AI/AIRunEventType.js +18 -0
- package/build/dist/Types/AI/AIRunEventType.js.map +1 -0
- package/build/dist/Types/AI/AIRunStatus.js +26 -0
- package/build/dist/Types/AI/AIRunStatus.js.map +1 -0
- package/build/dist/Types/AI/AIRunType.js +7 -0
- package/build/dist/Types/AI/AIRunType.js.map +1 -0
- package/build/dist/Types/Date.js +153 -15
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/LLM/LlmType.js +6 -0
- package/build/dist/Types/LLM/LlmType.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/Layer.js +602 -171
- package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/RestrictionTimes.js +27 -13
- package/build/dist/Types/OnCallDutyPolicy/RestrictionTimes.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js +20 -5
- package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js.map +1 -1
- package/build/dist/UI/Components/Events/RecurringFieldElement.js +13 -0
- package/build/dist/UI/Components/Events/RecurringFieldElement.js.map +1 -1
- package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +10 -5
- package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +18 -4
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
- package/build/dist/UI/Components/Page/Page.js +3 -1
- package/build/dist/UI/Components/Page/Page.js.map +1 -1
- package/build/dist/UI/Components/RadioButtons/BasicRadioButtons.js +1 -1
- package/build/dist/UI/Components/RadioButtons/BasicRadioButtons.js.map +1 -1
- package/build/dist/UI/Utils/LlmTypeDropdownOptions.js +38 -0
- package/build/dist/UI/Utils/LlmTypeDropdownOptions.js.map +1 -0
- package/build/dist/UI/Utils/TestLLMProvider.js +37 -0
- package/build/dist/UI/Utils/TestLLMProvider.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,700 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EXHAUSTIVE timezone / DST coverage for the OneUptimeDate helpers that back the
|
|
3
|
+
* on-call scheduling & restriction-time machinery. These lock in the CORRECT
|
|
4
|
+
* (post-audit-fix) behavior of:
|
|
5
|
+
*
|
|
6
|
+
* - getInstantFromLocalWallClockInTimezone / getLocalDateFromWallClockInTimezone
|
|
7
|
+
* (audit F1: time-picker captured in browser zone, stored in schedule zone)
|
|
8
|
+
* - getHourAndMinuteInTimezoneString (audit F10: show hours in schedule zone)
|
|
9
|
+
* - moveDateToTheDayOfWeek WITH a timezone across spring-forward & fall-back
|
|
10
|
+
* (audit F9: final day-shift must stay timezone-aware)
|
|
11
|
+
* - addRemoveDays / addRemoveWeeks / addRemoveMonths / addRemoveYears WITH a
|
|
12
|
+
* timezone preserving wall-clock across DST
|
|
13
|
+
* - keepTimeButMoveDay across DST
|
|
14
|
+
* - getDayOfWeek / geyDayOfWeekAsNumber in a timezone
|
|
15
|
+
*
|
|
16
|
+
* Assertions are expressed in SCHEDULE-ZONE wall-clock via moment-timezone (or in
|
|
17
|
+
* absolute milliseconds) so they hold regardless of the process TZ the suite runs
|
|
18
|
+
* under. Run under TZ=UTC as well to model a UTC server whose zone differs from
|
|
19
|
+
* the schedule zone.
|
|
20
|
+
*/
|
|
21
|
+
import OneUptimeDate from "../../Types/Date";
|
|
22
|
+
import DayOfWeek from "../../Types/Day/DayOfWeek";
|
|
23
|
+
import moment from "moment-timezone";
|
|
24
|
+
|
|
25
|
+
const NY: string = "America/New_York";
|
|
26
|
+
const LONDON: string = "Europe/London";
|
|
27
|
+
const SYDNEY: string = "Australia/Sydney";
|
|
28
|
+
const KOLKATA: string = "Asia/Kolkata";
|
|
29
|
+
const UTC: string = "UTC";
|
|
30
|
+
|
|
31
|
+
const ALL_ZONES: string[] = [NY, LONDON, SYDNEY, KOLKATA, UTC];
|
|
32
|
+
|
|
33
|
+
// Format an instant's wall-clock in a given zone.
|
|
34
|
+
function hhmm(d: Date, tz: string): string {
|
|
35
|
+
return moment.tz(d, tz).format("HH:mm");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function ymd(d: Date, tz: string): string {
|
|
39
|
+
return moment.tz(d, tz).format("YYYY-MM-DD");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function pad(n: number): string {
|
|
43
|
+
return n.toString().padStart(2, "0");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/*
|
|
47
|
+
* A Date whose PROCESS-local wall-clock is exactly the given components. moment
|
|
48
|
+
* in local mode reads these back identically regardless of the process zone.
|
|
49
|
+
*/
|
|
50
|
+
function localDate(
|
|
51
|
+
y: number,
|
|
52
|
+
monthIndex: number,
|
|
53
|
+
day: number,
|
|
54
|
+
h: number,
|
|
55
|
+
min: number,
|
|
56
|
+
s: number,
|
|
57
|
+
): Date {
|
|
58
|
+
return new Date(y, monthIndex, day, h, min, s);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const ONE_HOUR_MS: number = 60 * 60 * 1000;
|
|
62
|
+
const ONE_DAY_MS: number = 24 * ONE_HOUR_MS;
|
|
63
|
+
|
|
64
|
+
/*
|
|
65
|
+
* The 12/24-hour choice getHourAndMinuteInTimezoneString makes is environment
|
|
66
|
+
* dependent, so derive the same format the code will use and match against it.
|
|
67
|
+
*/
|
|
68
|
+
const TZ_TIME_FORMAT: string = OneUptimeDate.getUserPrefers12HourFormat()
|
|
69
|
+
? "h:mm A"
|
|
70
|
+
: "HH:mm";
|
|
71
|
+
|
|
72
|
+
interface DstDay {
|
|
73
|
+
zone: string;
|
|
74
|
+
// A Sunday on which the transition occurs (all these zones transition on Sun).
|
|
75
|
+
transitionSundayYmd: string;
|
|
76
|
+
springForward: boolean; // true = clocks jump forward (23h day)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Verified against moment-timezone tz data (2026).
|
|
80
|
+
const DST_DAYS: DstDay[] = [
|
|
81
|
+
{ zone: NY, transitionSundayYmd: "2026-03-08", springForward: true },
|
|
82
|
+
{ zone: NY, transitionSundayYmd: "2026-11-01", springForward: false },
|
|
83
|
+
{ zone: LONDON, transitionSundayYmd: "2026-03-29", springForward: true },
|
|
84
|
+
{ zone: LONDON, transitionSundayYmd: "2026-10-25", springForward: false },
|
|
85
|
+
// Southern hemisphere: transitions are reversed relative to the north.
|
|
86
|
+
{ zone: SYDNEY, transitionSundayYmd: "2026-10-04", springForward: true },
|
|
87
|
+
{ zone: SYDNEY, transitionSundayYmd: "2026-04-05", springForward: false },
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
describe("OneUptimeDate exhaustive timezone / DST helpers", () => {
|
|
91
|
+
describe("getInstantFromLocalWallClockInTimezone", () => {
|
|
92
|
+
it("plants the local wall-clock as the same wall-clock in the target zone (winter & summer, all zones)", () => {
|
|
93
|
+
const walls: Date[] = [
|
|
94
|
+
localDate(2026, 0, 6, 9, 0, 0), // winter 09:00
|
|
95
|
+
localDate(2026, 6, 6, 14, 30, 0), // summer 14:30
|
|
96
|
+
localDate(2026, 10, 20, 23, 45, 0), // late evening
|
|
97
|
+
localDate(2026, 3, 1, 0, 1, 0), // just after midnight
|
|
98
|
+
];
|
|
99
|
+
for (const wall of walls) {
|
|
100
|
+
for (const zone of ALL_ZONES) {
|
|
101
|
+
const instant: Date =
|
|
102
|
+
OneUptimeDate.getInstantFromLocalWallClockInTimezone(wall, zone);
|
|
103
|
+
expect(hhmm(instant, zone)).toBe(
|
|
104
|
+
`${pad(wall.getHours())}:${pad(wall.getMinutes())}`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("produces distinct absolute instants for the same wall-clock in different zones", () => {
|
|
111
|
+
const wall: Date = localDate(2026, 5, 15, 9, 0, 0);
|
|
112
|
+
const inNY: Date = OneUptimeDate.getInstantFromLocalWallClockInTimezone(
|
|
113
|
+
wall,
|
|
114
|
+
NY,
|
|
115
|
+
);
|
|
116
|
+
const inKolkata: Date =
|
|
117
|
+
OneUptimeDate.getInstantFromLocalWallClockInTimezone(wall, KOLKATA);
|
|
118
|
+
const inSydney: Date =
|
|
119
|
+
OneUptimeDate.getInstantFromLocalWallClockInTimezone(wall, SYDNEY);
|
|
120
|
+
expect(inNY.getTime()).not.toBe(inKolkata.getTime());
|
|
121
|
+
expect(inNY.getTime()).not.toBe(inSydney.getTime());
|
|
122
|
+
expect(inKolkata.getTime()).not.toBe(inSydney.getTime());
|
|
123
|
+
expect(hhmm(inNY, NY)).toBe("09:00");
|
|
124
|
+
expect(hhmm(inKolkata, KOLKATA)).toBe("09:00");
|
|
125
|
+
expect(hhmm(inSydney, SYDNEY)).toBe("09:00");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("honors the half-hour Kolkata offset (09:00 IST is xx:30 UTC)", () => {
|
|
129
|
+
const wall: Date = localDate(2026, 5, 15, 9, 0, 0);
|
|
130
|
+
const instant: Date =
|
|
131
|
+
OneUptimeDate.getInstantFromLocalWallClockInTimezone(wall, KOLKATA);
|
|
132
|
+
expect(moment.utc(instant).format("mm")).toBe("30");
|
|
133
|
+
expect(moment.utc(instant).format("HH")).toBe("03"); // 09:00 - 5:30
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("preserves wall-clock on each zone's own spring-forward & fall-back Sunday at 09:00", () => {
|
|
137
|
+
for (const d of DST_DAYS) {
|
|
138
|
+
const [y, m, day]: number[] = d.transitionSundayYmd
|
|
139
|
+
.split("-")
|
|
140
|
+
.map((x: string): number => {
|
|
141
|
+
return parseInt(x, 10);
|
|
142
|
+
}) as number[];
|
|
143
|
+
const wall: Date = localDate(y!, m! - 1, day!, 9, 0, 0);
|
|
144
|
+
const instant: Date =
|
|
145
|
+
OneUptimeDate.getInstantFromLocalWallClockInTimezone(wall, d.zone);
|
|
146
|
+
expect(hhmm(instant, d.zone)).toBe("09:00");
|
|
147
|
+
expect(ymd(instant, d.zone)).toBe(d.transitionSundayYmd);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("accepts a string input via fromString (equivalent to the Date input)", () => {
|
|
152
|
+
const wall: Date = localDate(2026, 5, 15, 9, 0, 0);
|
|
153
|
+
const fromDate: Date =
|
|
154
|
+
OneUptimeDate.getInstantFromLocalWallClockInTimezone(wall, NY);
|
|
155
|
+
// Its UTC-marked ISO string parses back to the identical instant.
|
|
156
|
+
const fromString: Date =
|
|
157
|
+
OneUptimeDate.getInstantFromLocalWallClockInTimezone(
|
|
158
|
+
wall.toISOString(),
|
|
159
|
+
NY,
|
|
160
|
+
);
|
|
161
|
+
expect(fromString.getTime()).toBe(fromDate.getTime());
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("NOTE: current behavior - a nonexistent spring-forward gap wall-clock normalizes forward", () => {
|
|
165
|
+
// 02:30 on 2026-03-08 does not exist in NY (02:00 jumps to 03:00).
|
|
166
|
+
const wall: Date = localDate(2026, 2, 8, 2, 30, 0);
|
|
167
|
+
const instant: Date =
|
|
168
|
+
OneUptimeDate.getInstantFromLocalWallClockInTimezone(wall, NY);
|
|
169
|
+
// moment-timezone shifts the gap time forward by the DST offset.
|
|
170
|
+
expect(hhmm(instant, NY)).toBe("03:30");
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe("getLocalDateFromWallClockInTimezone", () => {
|
|
175
|
+
it("returns a local Date whose local wall-clock equals the target-zone wall-clock (all zones)", () => {
|
|
176
|
+
for (const zone of ALL_ZONES) {
|
|
177
|
+
const instant: Date = moment.tz("2026-01-06 09:00", zone).toDate();
|
|
178
|
+
const local: Date = OneUptimeDate.getLocalDateFromWallClockInTimezone(
|
|
179
|
+
instant,
|
|
180
|
+
zone,
|
|
181
|
+
);
|
|
182
|
+
expect(local.getHours()).toBe(9);
|
|
183
|
+
expect(local.getMinutes()).toBe(0);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("honors the half-hour Kolkata offset", () => {
|
|
188
|
+
const instant: Date = moment.tz("2026-06-15 09:15", KOLKATA).toDate();
|
|
189
|
+
const local: Date = OneUptimeDate.getLocalDateFromWallClockInTimezone(
|
|
190
|
+
instant,
|
|
191
|
+
KOLKATA,
|
|
192
|
+
);
|
|
193
|
+
expect(local.getHours()).toBe(9);
|
|
194
|
+
expect(local.getMinutes()).toBe(15);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("reflects the zone's summer vs winter offset for a fixed UTC instant", () => {
|
|
198
|
+
// 2026-07-01 12:00 UTC is 08:00 in NY (EDT, -4).
|
|
199
|
+
const summer: Date = moment.utc("2026-07-01 12:00").toDate();
|
|
200
|
+
const localSummer: Date =
|
|
201
|
+
OneUptimeDate.getLocalDateFromWallClockInTimezone(summer, NY);
|
|
202
|
+
expect(localSummer.getHours()).toBe(8);
|
|
203
|
+
// 2026-01-01 12:00 UTC is 07:00 in NY (EST, -5).
|
|
204
|
+
const winter: Date = moment.utc("2026-01-01 12:00").toDate();
|
|
205
|
+
const localWinter: Date =
|
|
206
|
+
OneUptimeDate.getLocalDateFromWallClockInTimezone(winter, NY);
|
|
207
|
+
expect(localWinter.getHours()).toBe(7);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe("round-trip preservation", () => {
|
|
212
|
+
it("local -> tz-instant -> local preserves wall-clock across winter/summer/DST-day for every zone", () => {
|
|
213
|
+
const walls: Date[] = [
|
|
214
|
+
localDate(2026, 0, 6, 9, 0, 0), // winter
|
|
215
|
+
localDate(2026, 6, 6, 14, 30, 0), // summer
|
|
216
|
+
localDate(2026, 2, 8, 1, 30, 0), // NY spring-forward day (pre-gap, valid everywhere)
|
|
217
|
+
localDate(2026, 10, 1, 23, 15, 0), // NY fall-back day (post-fold, valid everywhere)
|
|
218
|
+
];
|
|
219
|
+
for (const wall of walls) {
|
|
220
|
+
for (const zone of ALL_ZONES) {
|
|
221
|
+
const instant: Date =
|
|
222
|
+
OneUptimeDate.getInstantFromLocalWallClockInTimezone(wall, zone);
|
|
223
|
+
const back: Date = OneUptimeDate.getLocalDateFromWallClockInTimezone(
|
|
224
|
+
instant,
|
|
225
|
+
zone,
|
|
226
|
+
);
|
|
227
|
+
expect(back.getHours()).toBe(wall.getHours());
|
|
228
|
+
expect(back.getMinutes()).toBe(wall.getMinutes());
|
|
229
|
+
expect(back.getSeconds()).toBe(wall.getSeconds());
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("tz-instant -> local -> tz-instant preserves the exact instant (unambiguous wall-clocks)", () => {
|
|
235
|
+
for (const zone of ALL_ZONES) {
|
|
236
|
+
const instants: Date[] = [
|
|
237
|
+
moment.tz("2026-06-15 09:00", zone).toDate(),
|
|
238
|
+
moment.tz("2026-01-15 14:30", zone).toDate(),
|
|
239
|
+
];
|
|
240
|
+
for (const instant0 of instants) {
|
|
241
|
+
const local: Date = OneUptimeDate.getLocalDateFromWallClockInTimezone(
|
|
242
|
+
instant0,
|
|
243
|
+
zone,
|
|
244
|
+
);
|
|
245
|
+
const instant1: Date =
|
|
246
|
+
OneUptimeDate.getInstantFromLocalWallClockInTimezone(local, zone);
|
|
247
|
+
expect(instant1.getTime()).toBe(instant0.getTime());
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe("getHourAndMinuteInTimezoneString", () => {
|
|
254
|
+
it("formats an instant in the requested zone (matches moment format) for every zone", () => {
|
|
255
|
+
const instant: Date = moment.utc("2026-01-06 17:00").toDate();
|
|
256
|
+
for (const zone of ALL_ZONES) {
|
|
257
|
+
const actual: string = OneUptimeDate.getHourAndMinuteInTimezoneString(
|
|
258
|
+
instant,
|
|
259
|
+
zone,
|
|
260
|
+
);
|
|
261
|
+
expect(actual).toBe(moment.tz(instant, zone).format(TZ_TIME_FORMAT));
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("differs across zones for the same instant", () => {
|
|
266
|
+
const nyNoon: Date = moment.tz("2026-01-06 12:00", NY).toDate();
|
|
267
|
+
const inNY: string = OneUptimeDate.getHourAndMinuteInTimezoneString(
|
|
268
|
+
nyNoon,
|
|
269
|
+
NY,
|
|
270
|
+
);
|
|
271
|
+
const inKolkata: string = OneUptimeDate.getHourAndMinuteInTimezoneString(
|
|
272
|
+
nyNoon,
|
|
273
|
+
KOLKATA,
|
|
274
|
+
);
|
|
275
|
+
const inSydney: string = OneUptimeDate.getHourAndMinuteInTimezoneString(
|
|
276
|
+
nyNoon,
|
|
277
|
+
SYDNEY,
|
|
278
|
+
);
|
|
279
|
+
expect(inNY).not.toBe(inKolkata);
|
|
280
|
+
expect(inNY).not.toBe(inSydney);
|
|
281
|
+
expect(inKolkata).not.toBe(inSydney);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("shows the Kolkata half-hour minute component", () => {
|
|
285
|
+
const nyNoon: Date = moment.tz("2026-01-06 12:00", NY).toDate();
|
|
286
|
+
const inKolkata: string = OneUptimeDate.getHourAndMinuteInTimezoneString(
|
|
287
|
+
nyNoon,
|
|
288
|
+
KOLKATA,
|
|
289
|
+
);
|
|
290
|
+
// NY 12:00 EST = 17:00 UTC = 22:30 IST -> minute is 30.
|
|
291
|
+
expect(inKolkata).toContain("30");
|
|
292
|
+
expect(inKolkata).toBe(moment.tz(nyNoon, KOLKATA).format(TZ_TIME_FORMAT));
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it("applies the correct offset in summer vs winter (same wall-clock string, different UTC instant)", () => {
|
|
296
|
+
// Both instants read 12:00 in NY: one in EDT, one in EST.
|
|
297
|
+
const summerInstant: Date = moment.utc("2026-07-01 16:00").toDate(); // NY 12:00 EDT
|
|
298
|
+
const winterInstant: Date = moment.utc("2026-01-01 17:00").toDate(); // NY 12:00 EST
|
|
299
|
+
const expected: string = moment
|
|
300
|
+
.tz(summerInstant, NY)
|
|
301
|
+
.format(TZ_TIME_FORMAT);
|
|
302
|
+
expect(
|
|
303
|
+
OneUptimeDate.getHourAndMinuteInTimezoneString(summerInstant, NY),
|
|
304
|
+
).toBe(expected);
|
|
305
|
+
expect(
|
|
306
|
+
OneUptimeDate.getHourAndMinuteInTimezoneString(winterInstant, NY),
|
|
307
|
+
).toBe(moment.tz(winterInstant, NY).format(TZ_TIME_FORMAT));
|
|
308
|
+
// Both are NY noon.
|
|
309
|
+
expect(hhmm(summerInstant, NY)).toBe("12:00");
|
|
310
|
+
expect(hhmm(winterInstant, NY)).toBe("12:00");
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it("falls back to the local wall-clock when no timezone is given", () => {
|
|
314
|
+
const d: Date = localDate(2026, 0, 6, 8, 15, 0);
|
|
315
|
+
expect(OneUptimeDate.getHourAndMinuteInTimezoneString(d, undefined)).toBe(
|
|
316
|
+
OneUptimeDate.getLocalHourAndMinuteFromDate(d),
|
|
317
|
+
);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("NOTE: current behavior - an empty-string timezone is falsy so it falls back to local", () => {
|
|
321
|
+
const d: Date = localDate(2026, 0, 6, 8, 15, 0);
|
|
322
|
+
expect(OneUptimeDate.getHourAndMinuteInTimezoneString(d, "")).toBe(
|
|
323
|
+
OneUptimeDate.getLocalHourAndMinuteFromDate(d),
|
|
324
|
+
);
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
describe("moveDateToTheDayOfWeek WITH a timezone", () => {
|
|
329
|
+
const ORDERED_DAYS: DayOfWeek[] = [
|
|
330
|
+
DayOfWeek.Sunday,
|
|
331
|
+
DayOfWeek.Monday,
|
|
332
|
+
DayOfWeek.Tuesday,
|
|
333
|
+
DayOfWeek.Wednesday,
|
|
334
|
+
DayOfWeek.Thursday,
|
|
335
|
+
DayOfWeek.Friday,
|
|
336
|
+
DayOfWeek.Saturday,
|
|
337
|
+
];
|
|
338
|
+
|
|
339
|
+
it("lands on the requested weekday AND preserves wall-clock for all 7 targets across every DST week", () => {
|
|
340
|
+
for (const d of DST_DAYS) {
|
|
341
|
+
const y: number = parseInt(d.transitionSundayYmd.split("-")[0]!, 10);
|
|
342
|
+
/*
|
|
343
|
+
* moveToWeek = the Wednesday of the Sunday-anchored week containing the
|
|
344
|
+
* transition Sunday, so all 7 targets fall inside the transition week.
|
|
345
|
+
*/
|
|
346
|
+
const moveToWeek: Date = moment
|
|
347
|
+
.tz(d.transitionSundayYmd, d.zone)
|
|
348
|
+
.add(3, "days")
|
|
349
|
+
.set({ hour: 12 })
|
|
350
|
+
.toDate();
|
|
351
|
+
|
|
352
|
+
for (const targetDay of ORDERED_DAYS) {
|
|
353
|
+
// An authored restriction at 09:00 wall-clock in the schedule zone.
|
|
354
|
+
const restriction: Date = moment
|
|
355
|
+
.tz(`${y}-01-07 09:00`, d.zone)
|
|
356
|
+
.toDate();
|
|
357
|
+
const moved: Date = OneUptimeDate.moveDateToTheDayOfWeek(
|
|
358
|
+
restriction,
|
|
359
|
+
moveToWeek,
|
|
360
|
+
targetDay,
|
|
361
|
+
d.zone,
|
|
362
|
+
);
|
|
363
|
+
expect(OneUptimeDate.getDayOfWeek(moved, d.zone)).toBe(targetDay);
|
|
364
|
+
expect(hhmm(moved, d.zone)).toBe("09:00");
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it("preserves a late-evening wall-clock (23:30) across the NY spring-forward week for all 7 targets", () => {
|
|
370
|
+
const moveToWeek: Date = moment
|
|
371
|
+
.tz("2026-03-08", NY)
|
|
372
|
+
.add(3, "days")
|
|
373
|
+
.set({ hour: 12 })
|
|
374
|
+
.toDate();
|
|
375
|
+
for (const targetDay of ORDERED_DAYS) {
|
|
376
|
+
const restriction: Date = moment.tz("2026-01-07 23:30", NY).toDate();
|
|
377
|
+
const moved: Date = OneUptimeDate.moveDateToTheDayOfWeek(
|
|
378
|
+
restriction,
|
|
379
|
+
moveToWeek,
|
|
380
|
+
targetDay,
|
|
381
|
+
NY,
|
|
382
|
+
);
|
|
383
|
+
expect(OneUptimeDate.getDayOfWeek(moved, NY)).toBe(targetDay);
|
|
384
|
+
expect(hhmm(moved, NY)).toBe("23:30");
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it("keeps a Sunday 01:00 boundary at 01:00 when moved into the NY spring-forward week (gap is at 02:00, F9 boundary)", () => {
|
|
389
|
+
const restriction: Date = moment.tz("2026-01-04 01:00", NY).toDate(); // a Sunday 01:00
|
|
390
|
+
const moveToWeek: Date = moment.tz("2026-03-11 12:00", NY).toDate(); // Wed of NY SF week
|
|
391
|
+
const moved: Date = OneUptimeDate.moveDateToTheDayOfWeek(
|
|
392
|
+
restriction,
|
|
393
|
+
moveToWeek,
|
|
394
|
+
DayOfWeek.Sunday,
|
|
395
|
+
NY,
|
|
396
|
+
);
|
|
397
|
+
expect(OneUptimeDate.getDayOfWeek(moved, NY)).toBe(DayOfWeek.Sunday);
|
|
398
|
+
expect(hhmm(moved, NY)).toBe("01:00");
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it("keeps a Sunday 01:30 boundary at 01:30 when moved into the NY fall-back week (ambiguous folded hour)", () => {
|
|
402
|
+
// 01:30 occurs twice on 2026-11-01 (NY fall-back). Wall-clock must still read 01:30.
|
|
403
|
+
const restriction: Date = moment.tz("2026-01-04 01:30", NY).toDate();
|
|
404
|
+
const moveToWeek: Date = moment.tz("2026-11-04 12:00", NY).toDate(); // Wed of NY FB week
|
|
405
|
+
const moved: Date = OneUptimeDate.moveDateToTheDayOfWeek(
|
|
406
|
+
restriction,
|
|
407
|
+
moveToWeek,
|
|
408
|
+
DayOfWeek.Sunday,
|
|
409
|
+
NY,
|
|
410
|
+
);
|
|
411
|
+
expect(OneUptimeDate.getDayOfWeek(moved, NY)).toBe(DayOfWeek.Sunday);
|
|
412
|
+
expect(ymd(moved, NY)).toBe("2026-11-01");
|
|
413
|
+
expect(hhmm(moved, NY)).toBe("01:30");
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it("NOTE: current behavior - a Sunday 01:00 boundary lands in London's spring-forward gap and normalizes to 02:00", () => {
|
|
417
|
+
/*
|
|
418
|
+
* London jumps 01:00 -> 02:00 on 2026-03-29, so the 01:00 wall-clock does
|
|
419
|
+
* not exist on that Sunday and moment-timezone shifts it forward.
|
|
420
|
+
*/
|
|
421
|
+
const restriction: Date = moment.tz("2026-01-04 01:00", LONDON).toDate(); // a Sunday 01:00
|
|
422
|
+
const moveToWeek: Date = moment.tz("2026-04-01 12:00", LONDON).toDate(); // Wed of London SF week
|
|
423
|
+
const moved: Date = OneUptimeDate.moveDateToTheDayOfWeek(
|
|
424
|
+
restriction,
|
|
425
|
+
moveToWeek,
|
|
426
|
+
DayOfWeek.Sunday,
|
|
427
|
+
LONDON,
|
|
428
|
+
);
|
|
429
|
+
expect(OneUptimeDate.getDayOfWeek(moved, LONDON)).toBe(DayOfWeek.Sunday);
|
|
430
|
+
expect(ymd(moved, LONDON)).toBe("2026-03-29");
|
|
431
|
+
expect(hhmm(moved, LONDON)).toBe("02:00");
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it("returns the same day (no shift) when the target equals moveToWeek's weekday", () => {
|
|
435
|
+
// moveToWeek is a Wednesday; targeting Wednesday must not shift.
|
|
436
|
+
const moveToWeek: Date = moment.tz("2026-03-11 12:00", NY).toDate(); // Wednesday
|
|
437
|
+
const restriction: Date = moment.tz("2026-01-07 09:00", NY).toDate();
|
|
438
|
+
const moved: Date = OneUptimeDate.moveDateToTheDayOfWeek(
|
|
439
|
+
restriction,
|
|
440
|
+
moveToWeek,
|
|
441
|
+
DayOfWeek.Wednesday,
|
|
442
|
+
NY,
|
|
443
|
+
);
|
|
444
|
+
expect(ymd(moved, NY)).toBe("2026-03-11");
|
|
445
|
+
expect(hhmm(moved, NY)).toBe("09:00");
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
describe("addRemoveDays WITH a timezone across DST", () => {
|
|
450
|
+
it("+1 day across spring-forward keeps wall-clock (23h absolute jump)", () => {
|
|
451
|
+
const start: Date = moment.tz("2026-03-07 09:00", NY).toDate();
|
|
452
|
+
const next: Date = OneUptimeDate.addRemoveDays(start, 1, NY);
|
|
453
|
+
expect(hhmm(next, NY)).toBe("09:00");
|
|
454
|
+
expect(ymd(next, NY)).toBe("2026-03-08");
|
|
455
|
+
expect(next.getTime() - start.getTime()).toBe(23 * ONE_HOUR_MS);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it("+1 day across fall-back keeps wall-clock (25h absolute jump)", () => {
|
|
459
|
+
const start: Date = moment.tz("2026-10-31 09:00", NY).toDate();
|
|
460
|
+
const next: Date = OneUptimeDate.addRemoveDays(start, 1, NY);
|
|
461
|
+
expect(hhmm(next, NY)).toBe("09:00");
|
|
462
|
+
expect(ymd(next, NY)).toBe("2026-11-01");
|
|
463
|
+
expect(next.getTime() - start.getTime()).toBe(25 * ONE_HOUR_MS);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it("-1 day back across spring-forward keeps wall-clock", () => {
|
|
467
|
+
const start: Date = moment.tz("2026-03-08 09:00", NY).toDate();
|
|
468
|
+
const prev: Date = OneUptimeDate.addRemoveDays(start, -1, NY);
|
|
469
|
+
expect(hhmm(prev, NY)).toBe("09:00");
|
|
470
|
+
expect(ymd(prev, NY)).toBe("2026-03-07");
|
|
471
|
+
expect(start.getTime() - prev.getTime()).toBe(23 * ONE_HOUR_MS);
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
it("preserves wall-clock across every zone's spring-forward and fall-back", () => {
|
|
475
|
+
for (const d of DST_DAYS) {
|
|
476
|
+
const before: Date = moment
|
|
477
|
+
.tz(d.transitionSundayYmd, d.zone)
|
|
478
|
+
.subtract(1, "days")
|
|
479
|
+
.set({ hour: 9, minute: 0, second: 0, millisecond: 0 })
|
|
480
|
+
.toDate();
|
|
481
|
+
const after: Date = OneUptimeDate.addRemoveDays(before, 1, d.zone);
|
|
482
|
+
expect(hhmm(after, d.zone)).toBe("09:00");
|
|
483
|
+
const absHours: number =
|
|
484
|
+
(after.getTime() - before.getTime()) / ONE_HOUR_MS;
|
|
485
|
+
expect(absHours).toBe(d.springForward ? 23 : 25);
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
it("Kolkata (no DST) advances by exactly 24h", () => {
|
|
490
|
+
const start: Date = moment.tz("2026-03-08 09:00", KOLKATA).toDate();
|
|
491
|
+
const next: Date = OneUptimeDate.addRemoveDays(start, 1, KOLKATA);
|
|
492
|
+
expect(hhmm(next, KOLKATA)).toBe("09:00");
|
|
493
|
+
expect(next.getTime() - start.getTime()).toBe(ONE_DAY_MS);
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it("samples a long horizon: adding k days keeps 09:00 across many DST crossings", () => {
|
|
497
|
+
const start: Date = moment.tz("2026-01-01 09:00", NY).toDate();
|
|
498
|
+
for (let k: number = 0; k <= 730; k += 10) {
|
|
499
|
+
const moved: Date = OneUptimeDate.addRemoveDays(start, k, NY);
|
|
500
|
+
expect(hhmm(moved, NY)).toBe("09:00");
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
describe("addRemoveWeeks WITH a timezone across DST", () => {
|
|
506
|
+
it("+1 week across spring-forward keeps wall-clock (167h absolute span)", () => {
|
|
507
|
+
const start: Date = moment.tz("2026-03-01 09:00", NY).toDate();
|
|
508
|
+
const next: Date = OneUptimeDate.addRemoveWeeks(start, 1, NY);
|
|
509
|
+
expect(hhmm(next, NY)).toBe("09:00");
|
|
510
|
+
expect(ymd(next, NY)).toBe("2026-03-08");
|
|
511
|
+
expect(next.getTime() - start.getTime()).toBe(167 * ONE_HOUR_MS);
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
it("+1 week across fall-back keeps wall-clock (span is not 168h)", () => {
|
|
515
|
+
const start: Date = moment.tz("2026-10-25 09:00", NY).toDate();
|
|
516
|
+
const next: Date = OneUptimeDate.addRemoveWeeks(start, 1, NY);
|
|
517
|
+
expect(hhmm(next, NY)).toBe("09:00");
|
|
518
|
+
expect(ymd(next, NY)).toBe("2026-11-01");
|
|
519
|
+
const absHours: number = (next.getTime() - start.getTime()) / ONE_HOUR_MS;
|
|
520
|
+
expect(absHours).toBe(169);
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
it("samples multiple weeks in every zone keeping wall-clock", () => {
|
|
524
|
+
for (const zone of ALL_ZONES) {
|
|
525
|
+
const start: Date = moment.tz("2026-02-01 09:00", zone).toDate();
|
|
526
|
+
for (let w: number = 0; w <= 40; w += 4) {
|
|
527
|
+
const moved: Date = OneUptimeDate.addRemoveWeeks(start, w, zone);
|
|
528
|
+
expect(hhmm(moved, zone)).toBe("09:00");
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
describe("addRemoveMonths WITH a timezone across DST", () => {
|
|
535
|
+
it("+1 month across spring-forward keeps wall-clock and calendar day", () => {
|
|
536
|
+
const start: Date = moment.tz("2026-02-15 09:00", NY).toDate();
|
|
537
|
+
const next: Date = OneUptimeDate.addRemoveMonths(start, 1, NY);
|
|
538
|
+
expect(ymd(next, NY)).toBe("2026-03-15");
|
|
539
|
+
expect(hhmm(next, NY)).toBe("09:00");
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
it("+1 month across fall-back keeps wall-clock", () => {
|
|
543
|
+
const start: Date = moment.tz("2026-10-15 09:00", NY).toDate();
|
|
544
|
+
const next: Date = OneUptimeDate.addRemoveMonths(start, 1, NY);
|
|
545
|
+
expect(ymd(next, NY)).toBe("2026-11-15");
|
|
546
|
+
expect(hhmm(next, NY)).toBe("09:00");
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
it("clamps a month-end overflow (Jan 31 + 1 month -> last day of Feb) keeping wall-clock", () => {
|
|
550
|
+
const start: Date = moment.tz("2027-01-31 09:00", NY).toDate();
|
|
551
|
+
const next: Date = OneUptimeDate.addRemoveMonths(start, 1, NY);
|
|
552
|
+
expect(ymd(next, NY)).toBe("2027-02-28"); // 2027 is not a leap year
|
|
553
|
+
expect(hhmm(next, NY)).toBe("09:00");
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
it("samples 24 forward months in every zone keeping wall-clock", () => {
|
|
557
|
+
for (const zone of ALL_ZONES) {
|
|
558
|
+
const start: Date = moment.tz("2026-01-10 09:00", zone).toDate();
|
|
559
|
+
for (let mo: number = 0; mo <= 24; mo += 2) {
|
|
560
|
+
const moved: Date = OneUptimeDate.addRemoveMonths(start, mo, zone);
|
|
561
|
+
expect(hhmm(moved, zone)).toBe("09:00");
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
describe("addRemoveYears WITH a timezone across DST", () => {
|
|
568
|
+
it("+1 year keeps wall-clock (offset EST vs EST at the same date)", () => {
|
|
569
|
+
const start: Date = moment.tz("2026-01-15 09:00", NY).toDate();
|
|
570
|
+
const next: Date = OneUptimeDate.addRemoveYears(start, 1, NY);
|
|
571
|
+
expect(ymd(next, NY)).toBe("2027-01-15");
|
|
572
|
+
expect(hhmm(next, NY)).toBe("09:00");
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
it("clamps Feb-29 leap-day overflow (+1 year -> Feb 28) keeping wall-clock", () => {
|
|
576
|
+
const start: Date = moment.tz("2028-02-29 09:00", NY).toDate();
|
|
577
|
+
const next: Date = OneUptimeDate.addRemoveYears(start, 1, NY);
|
|
578
|
+
expect(ymd(next, NY)).toBe("2029-02-28");
|
|
579
|
+
expect(hhmm(next, NY)).toBe("09:00");
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
it("samples several years in every zone keeping wall-clock", () => {
|
|
583
|
+
for (const zone of ALL_ZONES) {
|
|
584
|
+
const start: Date = moment.tz("2026-07-15 09:00", zone).toDate();
|
|
585
|
+
for (let yr: number = 0; yr <= 6; yr += 1) {
|
|
586
|
+
const moved: Date = OneUptimeDate.addRemoveYears(start, yr, zone);
|
|
587
|
+
expect(hhmm(moved, zone)).toBe("09:00");
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
describe("keepTimeButMoveDay across DST", () => {
|
|
594
|
+
it("timezone-aware: keeps the schedule-zone wall-clock on each zone's transition Sunday", () => {
|
|
595
|
+
for (const d of DST_DAYS) {
|
|
596
|
+
// keepTimeFor authored at 14:37:45 wall-clock in the schedule zone.
|
|
597
|
+
const keepTimeFor: Date = moment
|
|
598
|
+
.tz("2026-01-02 14:37:45", d.zone)
|
|
599
|
+
.toDate();
|
|
600
|
+
const moveDayTo: Date = moment
|
|
601
|
+
.tz(`${d.transitionSundayYmd} 00:00`, d.zone)
|
|
602
|
+
.toDate();
|
|
603
|
+
const result: Date = OneUptimeDate.keepTimeButMoveDay(
|
|
604
|
+
keepTimeFor,
|
|
605
|
+
moveDayTo,
|
|
606
|
+
d.zone,
|
|
607
|
+
);
|
|
608
|
+
expect(ymd(result, d.zone)).toBe(d.transitionSundayYmd);
|
|
609
|
+
expect(moment.tz(result, d.zone).format("HH:mm:ss")).toBe("14:37:45");
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
it("timezone-aware: keeps wall-clock when the destination day is the fall-back day and time is the folded hour edge", () => {
|
|
614
|
+
// 09:00 is unambiguous; verify placement onto the fall-back day.
|
|
615
|
+
const keepTimeFor: Date = moment.tz("2026-06-01 09:00", NY).toDate();
|
|
616
|
+
const moveDayTo: Date = moment.tz("2026-11-01 18:00", NY).toDate();
|
|
617
|
+
const result: Date = OneUptimeDate.keepTimeButMoveDay(
|
|
618
|
+
keepTimeFor,
|
|
619
|
+
moveDayTo,
|
|
620
|
+
NY,
|
|
621
|
+
);
|
|
622
|
+
expect(ymd(result, NY)).toBe("2026-11-01");
|
|
623
|
+
expect(hhmm(result, NY)).toBe("09:00");
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
it("no-timezone: keeps the process-local wall-clock and moves to the destination calendar day", () => {
|
|
627
|
+
const keepTimeFor: Date = localDate(2026, 0, 1, 14, 37, 45);
|
|
628
|
+
const moveDayTo: Date = localDate(2026, 5, 20, 3, 0, 0);
|
|
629
|
+
const result: Date = OneUptimeDate.keepTimeButMoveDay(
|
|
630
|
+
keepTimeFor,
|
|
631
|
+
moveDayTo,
|
|
632
|
+
);
|
|
633
|
+
expect(result.getFullYear()).toBe(2026);
|
|
634
|
+
expect(result.getMonth()).toBe(5);
|
|
635
|
+
expect(result.getDate()).toBe(20);
|
|
636
|
+
expect(result.getHours()).toBe(14);
|
|
637
|
+
expect(result.getMinutes()).toBe(37);
|
|
638
|
+
expect(result.getSeconds()).toBe(45);
|
|
639
|
+
});
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
describe("getDayOfWeek / geyDayOfWeekAsNumber in a timezone", () => {
|
|
643
|
+
// 2026-01-05 is a Monday. Noon-UTC anchors avoid any cross-zone day flip.
|
|
644
|
+
const WEEK: Array<{ ymd: string; day: DayOfWeek; iso: number }> = [
|
|
645
|
+
{ ymd: "2026-01-05", day: DayOfWeek.Monday, iso: 1 },
|
|
646
|
+
{ ymd: "2026-01-06", day: DayOfWeek.Tuesday, iso: 2 },
|
|
647
|
+
{ ymd: "2026-01-07", day: DayOfWeek.Wednesday, iso: 3 },
|
|
648
|
+
{ ymd: "2026-01-08", day: DayOfWeek.Thursday, iso: 4 },
|
|
649
|
+
{ ymd: "2026-01-09", day: DayOfWeek.Friday, iso: 5 },
|
|
650
|
+
{ ymd: "2026-01-10", day: DayOfWeek.Saturday, iso: 6 },
|
|
651
|
+
{ ymd: "2026-01-11", day: DayOfWeek.Sunday, iso: 7 },
|
|
652
|
+
];
|
|
653
|
+
|
|
654
|
+
it("maps every isoWeekday to the correct DayOfWeek enum (UTC noon anchors)", () => {
|
|
655
|
+
for (const entry of WEEK) {
|
|
656
|
+
const instant: Date = moment.utc(`${entry.ymd} 12:00`).toDate();
|
|
657
|
+
expect(OneUptimeDate.getDayOfWeek(instant, UTC)).toBe(entry.day);
|
|
658
|
+
expect(OneUptimeDate.geyDayOfWeekAsNumber(instant, UTC)).toBe(
|
|
659
|
+
entry.iso,
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
it("resolves the same UTC-noon instant to the same weekday in NY and Kolkata (no flip)", () => {
|
|
665
|
+
for (const entry of WEEK) {
|
|
666
|
+
const instant: Date = moment.utc(`${entry.ymd} 12:00`).toDate();
|
|
667
|
+
expect(OneUptimeDate.getDayOfWeek(instant, NY)).toBe(entry.day);
|
|
668
|
+
expect(OneUptimeDate.getDayOfWeek(instant, KOLKATA)).toBe(entry.day);
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
it("flips the weekday across zones for a late-evening UTC instant", () => {
|
|
673
|
+
// 2026-01-05 23:00 UTC: NY 18:00 Mon, Kolkata 04:30 Tue.
|
|
674
|
+
const instant: Date = moment.utc("2026-01-05 23:00").toDate();
|
|
675
|
+
expect(OneUptimeDate.getDayOfWeek(instant, NY)).toBe(DayOfWeek.Monday);
|
|
676
|
+
expect(OneUptimeDate.getDayOfWeek(instant, KOLKATA)).toBe(
|
|
677
|
+
DayOfWeek.Tuesday,
|
|
678
|
+
);
|
|
679
|
+
expect(OneUptimeDate.getDayOfWeek(instant, UTC)).toBe(DayOfWeek.Monday);
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
it("flips the weekday the other direction for an early-UTC instant (NY still on the previous day)", () => {
|
|
683
|
+
// 2026-01-06 02:00 UTC: NY 21:00 Mon Jan 5, Kolkata 07:30 Tue Jan 6.
|
|
684
|
+
const instant: Date = moment.utc("2026-01-06 02:00").toDate();
|
|
685
|
+
expect(OneUptimeDate.getDayOfWeek(instant, NY)).toBe(DayOfWeek.Monday);
|
|
686
|
+
expect(OneUptimeDate.getDayOfWeek(instant, KOLKATA)).toBe(
|
|
687
|
+
DayOfWeek.Tuesday,
|
|
688
|
+
);
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
it("reports the correct weekday on each zone's transition Sunday", () => {
|
|
692
|
+
for (const d of DST_DAYS) {
|
|
693
|
+
const noon: Date = moment
|
|
694
|
+
.tz(`${d.transitionSundayYmd} 12:00`, d.zone)
|
|
695
|
+
.toDate();
|
|
696
|
+
expect(OneUptimeDate.getDayOfWeek(noon, d.zone)).toBe(DayOfWeek.Sunday);
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
});
|
|
700
|
+
});
|