NotesDateTime + NotesDateRange:LotusScript 日期時間處理的雙人組

NotesDateTime + NotesDateRange:LotusScript 日期時間處理的雙人組

2026.05.25 約 1,078 字

重點摘要

  • NotesDateTime + NotesDateRange — 處理 LotusScript 日期時間的兩個 class、從 V3 就有、Domino code 裡最常摸到的 utility class 之一
  • 建立Dim x As New NotesDateTime("...")session.CreateDateTime("...")New 不支援 COM)
  • 三組時區屬性別混用LocalTime(在地時區)/ GMTTime(UTC)/ ZoneTime(依物件設定的 TZ + DST 調整顯示)
  • 時區轉換ConvertToZone(zone, dst) 直接改物件本身、不是回傳新物件 — 跟其他語言慣例不同、要先 clone 一份才能保留原值
  • 日期算術:6 個 Adjust* 方法(AdjustDay / AdjustHour / AdjustMinute / AdjustSecond / AdjustMonth / AdjustYear)— 同樣直接改原物件、自動跨月跨年
  • 差值計算TimeDifference(other) 回 Long(秒)、TimeDifferenceDouble(other) 回 Double(精度高、適合短間隔)
  • NotesDateRange — 「從 A 時間到 B 時間」的區間、4 個 property、沒有任何 method、calendar / schedule 場景常用
  • 常見坑:IsValidDate 檢查解析、SetAnyDate / SetAnyTime 是 wildcard 給搜尋用、跟 NotesItem.DateTimeValue 互動要注意時區

建立 NotesDateTime — 兩種寫法

兩個建構入口:New NotesDateTime("...")session.CreateDateTime("...")。差別在 COM 支援度:

' 方法 1:New 建構式(最簡潔、但不支援 COM agent)
Dim dt1 As New NotesDateTime("2026-05-25 14:30:00")
' 方法 2:session factory(COM agent 必走這條)
Dim session As New NotesSession
Dim dt2 As NotesDateTime
Set dt2 = session.CreateDateTime("2026-05-25 14:30:00")

字串格式接受很寬鬆:

  • ISO 8601:"2026-05-25 14:30:00""2026-05-25T14:30:00"
  • 美式:"05/25/2026 02:30 PM"
  • 純日期:"2026-05-25"(時間部分變 SetAnyTime 狀態)
  • 純時間:"14:30:00"(日期部分變 SetAnyDate 狀態)

解析失敗不會丟 exception — 物件還是建出來、但 IsValidDate = False。Production 程式碼該檢查:

Dim dt As NotesDateTime
Set dt = session.CreateDateTime(userInput)
If Not dt.IsValidDate Then
Print "輸入的日期格式錯誤:" & userInput
Exit Sub
End If

三組時區屬性 — Local / GMT / Zone

最常踩坑的地方:不同 property 代表不同時區、搞混會算錯時間。

Property型別意義何時用
.LocalTimeString (R/W)在地時區的完整 date-time顯示給 user 看、寫進 UI
.GMTTimeString (RO)UTC 表示log、跨系統比對、SLA 計算
.ZoneTimeString (RO)依物件自己的 TZ + DST 調整多時區 calendar 顯示
.LSLocalTimeVariant (R/W)LS 原生 date 型別、本地跟 LS Date 變數互傳
.LSGMTTimeVariant (RO)LS 原生 date 型別、UTC同上、UTC 版

只取日期或時間部分

Property回什麼
.DateOnly日期字串(local TZ)譬如 "05/25/2026"
.TimeOnly時間字串(local TZ)譬如 "14:30:00"

實務 rule of thumb:

  • 存進 NSF / 傳給其他 server → 用 GMTTimeLSGMTTime(UTC 唯一、不會誤解)
  • 顯示給 user → 用 LocalTime(自動依 client 時區)
  • 日期算術 / 比較 → 直接拿物件操作、不要先轉字串

Adjust 系列 — 6 個直接改原物件的算術

6 個對應 6 個時間單位、全部直接改原物件(不是回傳新物件):

Dim dt As New NotesDateTime("2026-05-25 14:30:00")
Call dt.AdjustDay(7) ' → 2026-06-01 14:30:00
Call dt.AdjustHour(-3) ' → 2026-06-01 11:30:00
Call dt.AdjustMonth(1) ' → 2026-07-01 11:30:00
Call dt.AdjustYear(1) ' → 2027-07-01 11:30:00
Call dt.AdjustMinute(45) ' → 2027-07-01 12:15:00
Call dt.AdjustSecond(-15) ' → 2027-07-01 12:14:45

自動跨界AdjustDay(35) 從 5/1 過去就是 6/5、不用自己算閏月。負數倒退、邏輯一致。

兩個 wildcard 方法給搜尋用、不是給日期算術:

Call dt.SetAnyDate() ' 日期變 "*"、用在 view formula 比對任意日期
Call dt.SetAnyTime() ' 時間變 "*"、用在 view formula 比對任意時間

SetNow() 一起記:把整個物件 reset 成「現在」。

Dim now As New NotesDateTime("")
Call now.SetNow() ' 物件現在等於 server 當下時間

比較 — TimeDifference vs TimeDifferenceDouble

兩個都算「自己 - 對方」差幾秒、差別在 return type 跟精度:

Method回傳精度適用
TimeDifference(other)Long(整數秒)1 秒一般場景:超時、跨日、SLA 計時
TimeDifferenceDouble(other)Double小數秒(毫秒級)短間隔測量、效能 benchmark
Dim t1 As New NotesDateTime("")
Call t1.SetNow()
' ...做點事...
Dim t2 As New NotesDateTime("")
Call t2.SetNow()
Dim elapsedSec As Long
elapsedSec = t2.TimeDifference(t1)
Print "經過 " & elapsedSec & " 秒"

順序A.TimeDifference(B) = A - B。負數代表 A 比 B 早。


ConvertToZone — 直接改物件的時區轉換

ConvertToZone(zone, dst) 改物件本身、不是回新物件:

Dim dt As New NotesDateTime("2026-05-25 14:30:00") ' 假設在地 Taipei UTC+8
Call dt.ConvertToZone(0, False) ' 轉成 UTC
Print dt.LocalTime ' "2026-05-25 06:30:00"(注意 LocalTime 已經變了)

兩個參數:

  • zone — 整數時區 offset(Taipei 是 -8、紐約 EST 是 5、注意正負號跟一般習慣相反:HCL 是 「local minus GMT」的反向)
  • dstTrue / False 決定是否套日光節約

要保留原物件就先 clone:

Dim original As New NotesDateTime("2026-05-25 14:30:00")
Dim cloned As New NotesDateTime(original.LocalTime) ' 從 LocalTime 字串重建
Call cloned.ConvertToZone(0, False) ' 只改 cloned、original 不動

LS 沒有 deep clone 的 first-class API、所以走 LocalTime 字串 round-trip 是慣例做法。


NotesDateRange — 區間表達、扁平、無 method

NotesDateRange 物件結構非常扁、4 個 property、完全沒有 method(透過 session.CreateDateRange() 建立):

Property型別用途
.StartDateTimeNotesDateTime (R/W)起始時間
.EndDateTimeNotesDateTime (R/W)結束時間
.TextString (R/W)字串表示(譬如 "05/25/2026 - 06/01/2026"),讀寫雙向
.ParentNotesSession (RO)持有的 session

建立兩種寫法:

' 方法 1:先建兩個 NotesDateTime、再 set 進 range
Dim start As New NotesDateTime("2026-05-25 09:00:00")
Dim ends As New NotesDateTime("2026-05-25 17:00:00")
Dim range As NotesDateRange
Set range = session.CreateDateRange()
Set range.StartDateTime = start
Set range.EndDateTime = ends
' 方法 2:直接 set Text、Domino 自動 parse 出兩端
Dim range2 As NotesDateRange
Set range2 = session.CreateDateRange()
range2.Text = "05/25/2026 09:00 AM - 05/25/2026 05:00 PM"

用途NotesItem 存「時間區間」型欄位(譬如會議的 Required attendance time)就是 NotesDateRange 陣列。Calendar app 場景幾乎都會用到。


實戰範例 1:期限檢查 agent

scheduled agent 找超過 30 天未更新的文件、發提醒:

Sub Initialize
Dim session As New NotesSession
Dim db As NotesDatabase
Set db = session.CurrentDatabase
' 算出「30 天前」的時間點
Dim cutoff As New NotesDateTime("")
Call cutoff.SetNow()
Call cutoff.AdjustDay(-30)
' 找所有 LastModified < cutoff 的文件
Dim docs As NotesDocumentCollection
Set docs = db.Search("@Modified < [" & cutoff.LocalTime & "]", Nothing, 0)
Dim doc As NotesDocument
Set doc = docs.GetFirstDocument()
Do Until doc Is Nothing
' 算這份文件多久沒動了
Dim lastMod As NotesDateTime
Set lastMod = doc.LastModified
Dim staleDays As Long
staleDays = -lastMod.TimeDifference(cutoff) / 86400 ' 秒 → 天
Print doc.GetItemValue("Subject")(0) & " 已 " & (staleDays + 30) & " 天未更新"
Set doc = docs.GetNextDocument(doc)
Loop
End Sub

實戰範例 2:跨時區會議排程

把 Taipei 09:00 的會議翻譯成參加者所在時區:

Function MeetingTimeForZone(taipeiTime As String, attendeeZone As Integer) As String
Dim dt As NotesDateTime
Set dt = New NotesDateTime(taipeiTime)
' 從 Taipei (UTC+8) 轉到 attendee 時區
Call dt.ConvertToZone(attendeeZone, False)
MeetingTimeForZone = dt.LocalTime
End Function
' 用法
Print MeetingTimeForZone("2026-05-25 09:00:00", 5) ' New York EST → 21:00(前一天)
Print MeetingTimeForZone("2026-05-25 09:00:00", 0) ' UTC → 01:00
Print MeetingTimeForZone("2026-05-25 09:00:00", -8) ' Taipei → 09:00 維持

常見坑

NotesItem.DateTimeValue 互動

從文件(NotesDocument 那篇有 item 模型細節)讀出來的 DateTime item value 帶 server timezone 資訊、寫回去要小心:

Dim doc As NotesDocument
Set doc = view.GetFirstDocument()
Dim item As NotesItem
Set item = doc.GetFirstItem("EventDate")
Dim dt As NotesDateTime
Set dt = item.DateTimeValue
' dt 帶 server TZ、不是你 client TZ
Print dt.GMTTime ' ← 用 GMT 比較安全、不會有 TZ 誤解

SetAnyDate / SetAnyTime 不是「清空」

很多人以為這兩個 method 是「把日期清空」、其實是給搜尋用的 wildcard

Dim dt As New NotesDateTime("2026-05-25 14:30:00")
Call dt.SetAnyDate() ' 日期變 wildcard "*"、但時間 14:30:00 還在
' 用途:放進 view selection formula、比對「任何日期、時間是 14:30」的文件

要表達「現在不知道時間」、不要用 SetAnyTime;該用空字串建構或乾脆別建。

Adjust* mutate 而不是 return 新物件

' ❌ 錯
Dim tomorrow As NotesDateTime
Set tomorrow = today.AdjustDay(1) ' AdjustDay 不回 value、tomorrow 會是 Nothing
' ✅ 對
Set tomorrow = New NotesDateTime(today.LocalTime)
Call tomorrow.AdjustDay(1)

JavaScript / Python / Java 8 的 immutable date 慣例不適用 — LS 是「直接改原物件」風格、要保留原值要先 clone。


同類別在其他語言

語言對應
LotusScriptNotesDateTime / NotesDateRange
Javalotus.domino.DateTime / lotus.domino.DateRange(API surface 一致、命名去掉 Notes 前綴)
SSJS用 JS 原生 Date(XPages 內可以再用 @Today() / @Now() 等 formula function 補強)

Java side 的差別主要在記憶體管理:lotus.domino.DateTime 物件用完要 .recycle()、不像 LS 自動 GC。SSJS 直接用 new Date() / Date.now()、Domino 不另外提供包裝。

參考來源

← 回到文章列表