模組:Julianday
外观

local p = {}
local mError = setmetatable({}, {
__index = function (t, k)
local _mError = require('Module:Error')
mError = _mError
return _mError[k]
end
})
-------- //// Lua Api Start //// --------
local function yearIsLeap(year, useJulian)
if useJulian then
return year % 4 == 0
end
return (year % 4 == 0 and year % 100 ~= 0) or (year % 400 == 0)
end
local function getDaysInYear(year, useJulian)
return yearIsLeap(year, useJulian) and 366 or 365
end
local function getDaysInMonth(year, month, useJulian)
if month == 2 then
return yearIsLeap(year, useJulian) and 29 or 28
end
return (month == 4 or month == 6 or month == 9 or month == 11) and 30 or 31
end
local function getDaysSinceYearStart(year, month, day, useJulian)
local result = 0
for m = 1, month - 1, 1 do
result = result + getDaysInMonth(year, m, useJulian)
end
result = result + day - 1
return result
end
function getPassedDaysRaw(startYear, startMonth, startDay, endYear, endMonth, endDay, useJulian)
local result = 0
local flag = 1 -- 結果應該是向前計算還是向後計算
if
startYear > endYear
or (startYear == endYear and startMonth > endMonth)
or (startYear == endYear and startMonth == endMonth and startDay > endDay)
then
-- 交換,確保 end 比 start 還晚,以免還得處理加減法問題
startYear, startMonth, startDay, endYear, endMonth, endDay = endYear, endMonth, endDay, startYear, startMonth, startDay
flag = -1
end
if startYear < endYear then
-- 如果結束日期和開始日期不在同一個年份,快進到兩個同一年
for y = startYear, endYear - 1, 1 do
result = result + getDaysInYear(y, useJulian)
end
end
result = result - getDaysSinceYearStart(startYear, startMonth, startDay, useJulian) + getDaysSinceYearStart(endYear, endMonth, endDay, useJulian)
return result * flag
end
local function getTargetDateRaw(startYear, startMonth, startDay, during, useJulian)
local endYear, endMonth, endDay = startYear, startMonth, startDay
local daysLeft = during
if daysLeft > 0 then
-- 往未來推算
while daysLeft > 0 do
local daysInYear = getDaysInYear(endYear, useJulian) -- 該年總天數
local daysPassed = getDaysSinceYearStart(endYear, endMonth, endDay, useJulian) -- 該年已過去的天數
local daysRemaining = daysInYear - daysPassed -- 當年剩餘天數
if daysLeft >= daysRemaining then
-- 如果還要加的天數超過當年剩餘天數,就直接跨年
daysLeft = daysLeft - daysRemaining
endYear = endYear + 1
endMonth = 1
endDay = 1
else
-- 否則逐月增加
while daysLeft > 0 do
local daysInMonth = getDaysInMonth(endYear, endMonth, useJulian) -- 當月總天數
local remainingInMonth = daysInMonth - endDay + 1 -- 當月剩餘天數
if daysLeft >= remainingInMonth then
-- 如果還要加的天數超過當月剩餘天數,就跨月
daysLeft = daysLeft - remainingInMonth
endMonth = endMonth + 1
endDay = 1
if endMonth > 12 then
endMonth = 1
endYear = endYear + 1
end
else
-- 直接加天數
endDay = endDay + daysLeft
daysLeft = 0
end
end
end
end
elseif daysLeft < 0 then
-- 往過去推算
while daysLeft < 0 do
local daysPassed = getDaysSinceYearStart(endYear, endMonth, endDay, useJulian) -- 當年已過去的天數
if -daysLeft >= daysPassed then
-- 如果還要減的天數超過當年已過去的天數,就直接跨年
daysLeft = daysLeft + daysPassed + 1 -- 把這天也一起減去(daysLeft 是負的)
endYear = endYear - 1
endMonth = 12
endDay = 31
else
-- 否則逐月減少
while daysLeft < 0 do
if -daysLeft >= endDay then
-- 跨月處理
daysLeft = daysLeft + endDay
endMonth = endMonth - 1
if endMonth == 0 then
endMonth = 12
endYear = endYear - 1
end
endDay = getDaysInMonth(endYear, endMonth, useJulian) -- 上個月總天數
else
-- 如果當月可以直接減去天數
endDay = endDay + daysLeft
daysLeft = 0
end
end
end
end
end
return endYear, endMonth, endDay
end
local function getTimeHavePassed(hour, minute, second)
return hour * 3600 + minute * 60 + second
end
local function buildModule(startAt)
local startAtTimeHavePassed = getTimeHavePassed(startAt.hour, startAt.minute, startAt.second)
return {
getDaysSimple = function (year, month, day)
-- 不考慮時間的簡易版本
return getPassedDaysRaw(startAt.year, startAt.month, startAt.day, year, month, day, startAt.useJulian)
end,
getDays = function (year, month, day, hour, minute, second)
local rawDay = getPassedDaysRaw(startAt.year, startAt.month, startAt.day, year, month, day, startAt.useJulian)
local inputTimeHavePassed = getTimeHavePassed(hour, minute, second)
return rawDay + (inputTimeHavePassed - startAtTimeHavePassed) / 86400
end,
fromDay = function (rawDay)
local duringPart = math.floor(rawDay)
local timePart = rawDay - duringPart
local timePartSeconds = math.floor(timePart * 86400 + 0.5) + startAtTimeHavePassed -- 四捨五入總秒數
if timePartSeconds >= 86400 then
-- 換算過來已經換日了,改用無條件進位以避免後續計算換日
duringPart = duringPart + 1
timePartSeconds = timePartSeconds - 86400
end
local year, month, day = getTargetDateRaw(startAt.year, startAt.month, startAt.day, duringPart, startAt.useJulian)
local hour, minute, second = 0, 0, 0
if timePartSeconds ~= 0 then
if math.abs(timePartSeconds) > 86400 then
error('Unknown error.')
end
hour = math.floor(timePartSeconds / 3600)
timePartSeconds = timePartSeconds % 3600
minute = math.floor(timePartSeconds / 60)
second = timePartSeconds % 60
end
return {
year = year,
month = month,
day = day,
hour = hour,
minute = minute,
second = second,
}
end,
}
end
local function mockModule(dayModule, offset)
return {
getDays = function (year, month, day, hour, minute, second)
return dayModule.getDays(year, month, day, hour, minute, second) - offset
end,
fromDay = function (rawDay)
return dayModule.fromDay(rawDay + offset)
end,
}
end
p._mockJuliandayModule = mockModule
p._JuliandayJulian = buildModule({ year = -4712, month = 1, day = 1, hour = 12, minute = 0, second = 0, useJulian = true })
p._JuliandayGregorian = buildModule({ year = -4713, month = 11, day = 24, hour = 12, minute = 0, second = 0, useJulian = false })
--[=[
以下大概率沒啥用,註釋起來,有需要請自己
<syntaxhighlight lang="lua">
local mJulianday = require('Module:Julianday')
local JulianReduced = mJulianday._mockJuliandayModule(mJulianday._JuliandayJulian, -2400000)
</syntaxhighlight>
以此類推
]=]
-- p._JuliandayJulianReduced = mockModule(p._JuliandayJulian, -2400000)
-- p._JuliandayJulianModified = mockModule(p._JuliandayJulian, -2400000.5)
-- p._JuliandayJulianTruncatedNASA = mockModule(p._JuliandayJulian, -2440000.5)
-- p._JuliandayJulianTruncatedNISTLast = mockModule(p._JuliandayJulian, -2450000.5)
-- p._JuliandayJulianTruncatedNISTLastCurrent = mockModule(p._JuliandayJulian, -2460000.5)
-- p._JuliandayReduced = mockModule(p._JuliandayGregorian, -2400000)
-- p._JuliandayModified = mockModule(p._JuliandayGregorian, -2400000.5)
-- p._JuliandayTruncatedNASA = mockModule(p._JuliandayGregorian, -2440000.5)
-- p._JuliandayTruncatedNISTLast = mockModule(p._JuliandayGregorian, -2450000.5)
-- p._JuliandayTruncatedNISTLastCurrent = mockModule(p._JuliandayGregorian, -2460000.5)
p._Julianday = {
getDays = function (year, month, day, hour, minute, second)
if
(year > 1582) or
(year == 1582 and (
month > 10 or
(month == 10 and (
day > 15 or
(day == 15 and hour >= 12)
))
))
then
return p._JuliandayGregorian.getDays(year, month, day, hour, minute, second)
-- elseif
-- (year < 1582) or
-- (year == 1582 and (
-- month < 10 or
-- (month == 10 and (
-- day <= 4 or
-- (day == 5 and hour <= 12)
-- ))
-- ))
-- then
-- return p._JuliandayJulian.getDays(year, month, day, hour, minute, second)
else
-- error(string.format('Fail to execute _Julianday.getDays on %04d-%02d-%02dT%02d:%02d:%02d: time is undefined.', year, month, day, hour, minute, second))
return p._JuliandayJulian.getDays(year, month, day, hour, minute, second)
end
end,
fromDay = function (rawDay)
return (rawDay >= 22991601 and p._JuliandayGregorian or p._JuliandayJulian).fromDay(rawDay)
end,
}
-- getPassedDaysRaw 取得兩個日期間經過的時間
-- @param {number} startYear 開始年分
-- @param {number} startMonth 開始月分
-- @param {number} startDay 開始日期
-- @param {number} endYear 開始年分
-- @param {number} endMonth 開始月分
-- @param {number} endDay 開始日期
-- @param {boolean} [useJulian] 是否應該用格里曆來計算持續日期
-- @return {number} 經過的時間
p.getPassedDaysRaw = getPassedDaysRaw
-- getPassedDays 取得兩個日期間經過的時間,自動切換儒略曆和格里曆
-- 請注意,如果輸入的日期在 1582-10-05 ~ 1582-10-14 之間,可能會出現未定義行為
-- 此函數不檢查是否經過切換曆法的中午 12:00,請自己實作
-- @param {number} startYear 開始年分
-- @param {number} startMonth 開始月分
-- @param {number} startDay 開始日期
-- @param {number} endYear 開始年分
-- @param {number} endMonth 開始月分
-- @param {number} endDay 開始日期
-- @return {number} 經過的時間
function p._getPassedDays(startYear, startMonth, startDay, endYear, endMonth, endDay)
-- 檢查日期是否跨越曆法轉換
local isStartBeforeCutoff = (startYear < julianCutoff.year) or
(startYear == julianCutoff.year and startMonth < julianCutoff.month) or
(startYear == julianCutoff.year and startMonth == julianCutoff.month and startDay <= julianCutoff.day)
local isEndAfterCutoff = (endYear > gregorianCutoff.year) or
(endYear == gregorianCutoff.year and endMonth > gregorianCutoff.month) or
(endYear == gregorianCutoff.year and endMonth == gregorianCutoff.month and endDay >= gregorianCutoff.day)
if isStartBeforeCutoff then
if isEndAfterCutoff then
-- 跨越曆法轉換,分段計算
local daysBefore = getPassedDays(startYear, startMonth, startDay, gregorianCutoff.year, julianCutoff.month, julianCutoff.day, true) -- 使用儒略曆
local daysAfter = getPassedDays(gregorianCutoff.year, gregorianCutoff.month, gregorianCutoff.day, endYear, endMonth, endDay, false) -- 使用格里曆
return daysBefore + daysAfter
end
--都在儒略曆
return getPassedDays(startYear, startMonth, startDay, endYear, endMonth, endDay, true)
else
--都在格里曆
return getPassedDays(startYear, startMonth, startDay, endYear, endMonth, endDay, false)
end
end
-- getTargetDateRaw 取得經過持續日期後的目標時間
-- @param {number} startYear 開始年分
-- @param {number} startMonth 開始月分
-- @param {number} startDay 開始日期
-- @param {number} during 持續時間
-- @param {boolean} [useJulian] 是否應該用格里曆來計算持續日期
-- @return {number, number, number} 目標時間
p._getTargetDateRaw = getTargetDateRaw
-- getTargetDate 取得經過持續日期後的目標時間,自動切換儒略曆和格里曆
-- 請注意,如果輸入的日期在 1582-10-05 ~ 1582-10-14 之間,可能會出現未定義行為
-- @param {number} startYear 開始年分
-- @param {number} startMonth 開始月分
-- @param {number} startDay 開始日期
-- @param {number} during 持續時間
-- @param {boolean} [useJulian] 是否應該用格里曆來計算持續日期
-- @return {number, number, number} 目標時間
function p._getTargetDate(startYear, startMonth, startDay, during)
-- 檢查起始日期是否在1582年10月4日之前
local is_before_gregorian = (startYear < 1582) or (startYear == 1582 and startMonth < 10) or(startYear == 1582 and startMonth == 10 and startDay <= 4)
if isBeforeGregorian then
-- 如果是格里曆實施前,計算起始日期的儒略日
local startJd = JuliandayJulian.getDaysSimple(startYear, startMonth, startDay)
-- 判斷目標日期是否跨越格里曆實施
local targetJd = startJd + during
local gregorianStartJd = 2299161 -- JuliandayGregorian.getDaysSimple(1582, 10, 15)
if targetJd >= gregorianStartJd then
-- 跨越格里曆實施,需要特殊處理
local daysBeforeSwitch = gregorianStartJd - startJd -1 -- 計算起始日期到曆法切換前的天數
-- 計算在儒略曆下的日期
local daysBeforeSwitch = getTargetDateRaw(startYear, startMonth, startDay, daysBeforeSwitch, true)
-- 計算剩餘天數
local remainingDays = during - daysBeforeSwitch -1
-- 從格里曆 1582-10-15 開始計算剩餘天數
return getTargetDateRaw(1582, 10, 15, remainingDays, false)
else
-- 沒有跨越,直接使用儒略曆計算
return getTargetDateRaw(startYear, startMonth, startDay, during, true)
end
else
-- 起始日期已經是格里曆,直接使用格里曆計算
return getTargetDateRaw(startYear, startMonth, startDay, during, false)
end
end
-------- //// Lua Api End //// --------
-------- //// Wikitext Api Start //// --------
local _getArgs
local function getArgs(...)
if not _getArgs then
_getArgs = require('Module:Arguments').getArgs
end
return _getArgs(...)
end
local _yesno
local function yesno(...)
if not _yesno then
_yesno = require('Module:Yesno')
end
return _yesno(...)
end
local _current
local function getCurrentTime()
if not _current then
_current = os.date("*t")
end
return _current
end
local function tonumberWrap(input)
local result = tonumber(input)
if result == nil and input then
error('Fail to parse "' .. input .. '" to number.')
end
return result
end
local function getDaysWrapper(dayModule, startAtNoon, wrapper)
wrappers = wrappers or {}
return function (frame)
local args = getArgs(frame, {
frameOnly = false,
parentFirst = true,
wrappers = wrappers
})
if not args[1] then
local current = getCurrentTime()
return dayModule.getDays(current.year, current.month, current.day, current.hour, current.min, current.sec)
end
local year = tonumberWrap(args[1])
local month = tonumberWrap(args[2]) or 1
local day = tonumberWrap(args[3]) or 1
local hour = tonumberWrap(args[4]) or (startAtNoon and 12 or 0)
local minute = tonumberWrap(args[5]) or 0
local second = tonumberWrap(args[6]) or 0
return dayModule.getDays(year, month, day, hour, minute, second)
end
end
-- implementation [[Template:JD]]
p.Julianday = getDaysWrapper(p._Julianday, true, {
'Template:JD'
})
-- implementation [[Template:JULIANDAY]]
p.JuliandayGregorian = getDaysWrapper(p._JuliandayGregorian, true, {
'Template:JULIANDAY'
})
-- implementation
-- * [[Template:JULIANDAY.YEAR]]
-- * [[Template:JULIANDAY.MONTH]]
-- * [[Template:JULIANDAY.DAY]]
-- * [[Template:JULIANDAY.HOUR]]
-- * [[Template:JULIANDAY.MINUTE]]
-- * [[Template:JULIANDAY.SECOND]]
-- * [[Template:JULIANDAY.TIMESTAMP]]
function p.JuliandayGregorianFromDay(frame)
local args = getArgs(frame, {
frameOnly = false,
parentFirst = true,
wrappers = {
'Template:JULIANDAY.YEAR',
'Template:JULIANDAY.MONTH',
'Template:JULIANDAY.DAY',
'Template:JULIANDAY.HOUR',
'Template:JULIANDAY.MINUTE',
'Template:JULIANDAY.SECOND',
'Template:JULIANDAY.TIMESTAMP',
}
})
local itemToGet = frame.args.type
if
itemToGet ~= 'year' and
itemToGet ~= 'month' and
itemToGet ~= 'day' and
itemToGet ~= 'hour' and
itemToGet ~= 'minute' and
itemToGet ~= 'second' and
itemToGet ~= 'timestamp'
then
error('Type "' .. itemToGet .. '" is invalid.')
end
if not args[1] then
return mError.error({ '[[Module:Julianday]].JuliandayGregorianFromDay: Missing input.' })
end
local ts = tonumberWrap(args[1])
local raw = p._JuliandayGregorian.fromDay(ts)
if itemToGet == 'timestamp' then
return string.format(
yesno(args.useISO) and '%04d-%02d-%02dT%02d:%02d:%02d' or '%04d%02d%02d%02d%02d%02d',
raw.year,
raw.month,
raw.day,
raw.hour,
raw.minute,
raw.second
)
end
return raw[itemToGet]
end
-- implementation [[Template:JULIANDAY.JULIAN]]
p.JuliandayJulian = getDaysWrapper(p._JuliandayJulian, true, {
'Template:JULIANDAY.JULIAN'
})
-------- //// Wikitext Api End //// --------
return p