Sabtu, 17 Oktober 2026
23:00 WIB
TERKINI
Universitas Teknokrat Indonesia Mahasiswa Universitas Teknokrat Raih Empat Gelar Bergengsi di Kompetisi Bahasa Inggris Nasional Pesisir Barat Pesisir Barat Jalin Kemitraan Strategis, Dorong Akses Pendidikan Tinggi Terjangkau Universitas Teknokrat Indonesia Teknokrat dan PMD Pesawaran Bersinergi Wujudkan Desa Digital Berkelanjutan Humaniora Memperkuat Hak Konstitusional Perempuan Adat untuk Kedaulatan Bangsa Pesawaran Lampung Dorong Rantai Pasok Hijau Kakao, Kopi, Lada ke Pasar Global Pesawaran Pengalihan Status Guru PPPK ke Penuh Waktu: DPRD Pesawaran Soroti Isu Penempatan Lampung Timur Gotong Royong Fantastis: Warga Bandar Agung Himpun Rp15 Miliar untuk Jalan Desa Bandar Lampung JNE Lampung Sukses Ubah Komplain Milea Cutting Jadi Kemitraan Strategis Bandar Lampung Jadwal Padat Pemprov Lampung 16 Oktober 2026: Pisah Sambut Kapolda dan Rapat Strategis Bandar Lampung Lampung Waspada: BMKG Deteksi 212 Titik Panas di Sepuluh Kabupaten Universitas Teknokrat Indonesia Mahasiswa Universitas Teknokrat Raih Empat Gelar Bergengsi di Kompetisi Bahasa Inggris Nasional Pesisir Barat Pesisir Barat Jalin Kemitraan Strategis, Dorong Akses Pendidikan Tinggi Terjangkau Universitas Teknokrat Indonesia Teknokrat dan PMD Pesawaran Bersinergi Wujudkan Desa Digital Berkelanjutan Humaniora Memperkuat Hak Konstitusional Perempuan Adat untuk Kedaulatan Bangsa Pesawaran Lampung Dorong Rantai Pasok Hijau Kakao, Kopi, Lada ke Pasar Global Pesawaran Pengalihan Status Guru PPPK ke Penuh Waktu: DPRD Pesawaran Soroti Isu Penempatan Lampung Timur Gotong Royong Fantastis: Warga Bandar Agung Himpun Rp15 Miliar untuk Jalan Desa Bandar Lampung JNE Lampung Sukses Ubah Komplain Milea Cutting Jadi Kemitraan Strategis Bandar Lampung Jadwal Padat Pemprov Lampung 16 Oktober 2026: Pisah Sambut Kapolda dan Rapat Strategis Bandar Lampung Lampung Waspada: BMKG Deteksi 212 Titik Panas di Sepuluh Kabupaten

Modul:Date

Wikimedia module

Bagikan:
Dokumentasi modullihatsuntingriwayatsegarkan
WarningModul Lua ini digunakan pada sekitar 93.000 halaman dan perubahannya kemungkinan memicu perubahan massal pada semua halaman yang menggunakannya. Uji cobalah di subhalaman /bak pasir atau /kasus uji Modul:Date, atau bak pasir modul Anda. Pertimbangkan untuk mendiskusikan perubahan di halaman pembicaraan sebelum mengimplementasikannya.

Modul ini menyediakan fungsi penanggalan untuk digunakan pada modul lainnya. Tanggal dalam kalender Gregorian dan kalender Julian didukung, dari 9999 SM hingga 9999 M. Kalender-kalender tersebut bersifat proleptik—diasumsikan berlaku sepanjang waktu tanpa penyimpangan.

Sebuah penanggalan dengan waktu yang opsional, dapat ditentukan dalam berbagai format, dan dapat dikonversi untuk ditampilkan menggunakan berbagai format, misalnya, 1 April 2016 atau 1 April 2016. Properti tanggal meliputi tanggal Julian dan tanggal serial Gregorian, serta hari dalam seminggu dan hari dalam setahun.

Tanggal dapat dibandingkan (misalnya, date1 <= date2), dan dapat digunakan dengan penambahan atau pengurangan (misalnya, date + '3 bulan'). Selisih antara dua tanggal dapat ditentukan dengan date1 - date2. Operasi ini berfungsi dengan tanggal kalender Gregorian dan Julian, tetapi date1 - date2 bernilai nil jika kedua tanggal tersebut menggunakan kalender yang berbeda.

Modul ini menyediakan item-item berikut.

Ekspor Deskrispi
_current Tabel dengan tahun, bulan, tanggal, jam, menit, dan detik saat ini.
_Date Fungsi yang mengembalikan tabel untuk tanggal tertentu.
_days_in_month Fungsi yang mengembalikan jumlah hari dalam sebulan.

Berikut ini adalah contoh penggunaan modul tersebut:

Format keluaranFormat keluaran

A date can be formatted as text.

localDate=require('Module:Date')._Date
localtext=Date(2016,7,1):text()-- result is '1 July 2016'
localtext=Date(2016,7,1):text('%-d %B')-- result is '1 July'
localtext=Date('1 July 2016'):text('mdy')-- result is 'July 1, 2016'

The following simplified formatting codes are available.

Code Result
hm hour:minute, with "am" or "pm" or variant, if specified (14:30 or 2:30 pm or variant)
hms hour:minute:second (14:30:45)
ymd year-month-day (2016-07-01)
mdy month day, year (July 1, 2016)
dmy day month year (1 July 2016)

The following formatting codes (similar to strftime) are available.

Code Result
%a Day abbreviation: Mon, Tue, ...
%A Day name: Monday, Tuesday, ...
%u Day of week: 1 to 7 (Monday to Sunday)
%w Day of week: 0 to 6 (Sunday to Saturday)
%d Day of month zero-padded: 01 to 31
%b Month abbreviation: Jan to Dec
%B Month name: January to December
%m Month zero-padded: 01 to 12
%Y Year zero-padded: 0012, 0120, 1200
%H Hour 24-hour clock zero-padded: 00 to 23
%I Hour 12-hour clock zero-padded: 01 to 12
%p AM or PM or as in options
%M Minute zero-padded: 00 to 59
%S Second zero-padded: 00 to 59
%j Day of year zero-padded: 001 to 366
%-d Day of month: 1 to 31
%-m Month: 1 to 12
%-Y Year: 12, 120, 1200
%-H Hour: 0 to 23
%-M Minute: 0 to 59
%-S Second: 0 to 59
%-j Day of year: 1 to 366
%-I Hour: 1 to 12
%% %

In addition, %{property} (where property is any property of a date) can be used.

For example, Date('1 Feb 2015 14:30:45 A.D.') has the following properties.

Code Result
%{calendar} Gregorian
%{year} 2015
%{month} 2
%{day} 1
%{hour} 14
%{minute} 30
%{second} 45
%{dayabbr} Sun
%{dayname} Sunday
%{dayofweek} 0
%{dow} 0 (same as dayofweek)
%{dayofweekiso} 7
%{dowiso} 7 (same as dayofweekiso)
%{dayofyear} 32
%{era} A.D.
%{gsd} 735630 (numbers of days from 1 January 1 CE; the first is day 1)
%{juliandate} 2457055.1046875 (Julian day)
%{jd} 2457055.1046875 (same as juliandate)
%{isleapyear} false
%{monthdays} 28
%{monthabbr} Feb
%{monthname} February

Some shortcuts are available. Given date = Date('1 Feb 2015 14:30'), the following results would occur.

Code Description Example result Equivalent format
date:text('%c') date and time 2:30 pm 1 February 2015 %-I:%M %p %-d %B %-Y %{era}
date:text('%x') date 1 February 2015 %-d %B %-Y %{era}
date:text('%X') time 2:30 pm %-I:%M %p

Julian dateJulian date

The following has an example of converting a Julian date to a date, then obtaining information about the date.

-- Code                                                -- Result
Date=require('Module:Date')._Date
date=Date('juliandate',320)
number=date.gsd-- -1721105
number=date.jd-- 320
text=date.dayname-- Saturday
text=date:text()-- 9 October 4713&nbsp;BC
text=date:text('%Y-%m-%d')-- 4713-10-09
text=date:text('%{era} %Y-%m-%d')-- BC 4713-10-09
text=date:text('%Y-%m-%d %{era}')-- 4713-10-09&nbsp;BC
text=date:text('%Y-%m-%d %{era}','era=B.C.E.')-- 4713-10-09&nbsp;B.C.E.
text=date:text('%Y-%m-%d','era=BCNEGATIVE')-- -4712-10-09
text=date:text('%Y-%m-%d','era=BCMINUS')-- −4712-10-09 (uses Unicode MINUS SIGN U+2212)
text=Date('juliandate',320):text('%{gsd} %{jd}')-- -1721105 320
text=Date('Oct 9, 4713 B.C.E.'):text('%{gsd} %{jd}')-- -1721105 320
text=Date(-4712,10,9):text('%{gsd} %{jd}')-- -1721105 320

Date differencesDate differences

The difference between two dates can be determined with date1 - date2. The result is valid if both dates use the Gregorian calendar or if both dates use the Julian calendar, otherwise the result is nil. An age and duration can be calculated from a date difference.

For example:

-- Code                                      -- Result
Date=require('Module:Date')._Date
date1=Date('21 Mar 2015')
date2=Date('4 Dec 1999')
diff=date1-date2
d=diff.age_days-- 5586
y,m,d=diff.years,diff.months,diff.days-- 15, 3, 17 (15 years + 3 months + 17 days)
y,m,d=diff:age('ymd')-- 15, 3, 17
y,m,w,d=diff:age('ymwd')-- 15, 3, 2, 3 (15 years + 3 months + 2 weeks + 3 days)
y,m,w,d=diff:duration('ymwd')-- 15, 3, 2, 4
d=diff:duration('d')-- 5587 (a duration includes the final day)

A date difference holds the original dates except they are swapped so diff.date1 >= diff.date2 (diff.date1 is the more recent date). This is shown in the following.

date1=Date('21 Mar 2015')
date2=Date('4 Dec 1999')
diff=date1-date2
neg=diff.isnegative-- false
text=diff.date1:text()-- 21 March 2015
text=diff.date2:text()-- 4 December 1999
diff=date2-date1
neg=diff.isnegative-- true (dates have been swapped)
text=diff.date1:text()-- 21 March 2015
text=diff.date2:text()-- 4 December 1999

A date difference also holds a time difference:

date1=Date('8 Mar 2016 0:30:45')
date2=Date('19 Jan 2014 22:55')
diff=date1-date2
y,m,d=diff.years,diff.months,diff.days-- 2, 1, 17
H,M,S=diff.hours,diff.minutes,diff.seconds-- 1, 35, 45

A date difference can be added to a date, or subtracted from a date.

date1=Date('8 Mar 2016 0:30:45')
date2=Date('19 Jan 2014 22:55')
diff=date1-date2
date3=date2+diff
date4=date1-diff
text=date3:text('ymd hms')-- 2016-03-08 00:30:45
text=date4:text('ymd hms')-- 2014-01-19 22:55:00
equal=(date1==date3)-- true
equal=(date2==date4)-- true

The age and duration methods of a date difference accept a code that identifies the components that should be returned. An extra day is included for the duration method because it includes the final day.

Code Returned values
'ymwd' years, months, weeks, days
'ymd' years, months, days
'ym' years, months
'y' years
'm' months
'wd' weeks, days
'w' weeks
'd' days
Dokumentasi di atas ditransklusikan dari Modul:Date/doc. (sunting | riwayat)
Penyunting dapat melakukan uji coba pada halaman bak pasir (buat | cermin) dan kasus uji (buat) modul ini.
Subhalaman modul ini.
-- Date functions for use by other modules.
-- I18N and time zones are not supported.
localMINUS='−'-- Unicode U+2212 MINUS SIGN
localfloor=math.floor
localDate,DateDiff,diffmt-- forward declarations
localuniq={'unique identifier'}
localfunctionis_date(t)
-- The system used to make a date read-only means there is no unique
-- metatable that is conveniently accessible to check.
returntype(t)=='table'andt._id==uniq
end
localfunctionis_diff(t)
returntype(t)=='table'andgetmetatable(t)==diffmt
end
localfunction_list_join(list,sep)
returntable.concat(list,sep)
end
localfunctioncollection()
-- Return a table to hold items.
return{
n=0,
add=function(self,item)
self.n=self.n+1
self[self.n]=item
end,
join=_list_join,
}
end
localfunctionstrip_to_nil(text)
-- If text is a string, return its trimmed content, or nil if empty.
-- Otherwise return text (convenient when Date fields are provided from
-- another module which may pass a string, a number, or another type).
iftype(text)=='string'then
text=text:match('(%S.-)%s*$')
end
returntext
end
localfunctionis_leap_year(year,calname)
-- Return true if year is a leap year.
ifcalname=='Julian'then
returnyear%4==0
end
return(year%4==0andyear%100~=0)oryear%400==0
end
localfunctiondays_in_month(year,month,calname)
-- Return number of days (1..31) in given month (1..12).
ifmonth==2andis_leap_year(year,calname)then
return29
end
return({31,28,31,30,31,30,31,31,30,31,30,31})[month]
end
localfunctionh_m_s(time)
-- Return hour, minute, second extracted from fraction of a day.
time=floor(time*24*3600+0.5)-- number of seconds
localsecond=time%60
time=floor(time/60)
returnfloor(time/60),time%60,second
end
localfunctionhms(date)
-- Return fraction of a day from date's time, where (0 <= fraction < 1)
-- if the values are valid, but could be anything if outside range.
return(date.hour+(date.minute+date.second/60)/60)/24
end
localfunctionjulian_date(date)
-- Return jd, jdz from a Julian or Gregorian calendar date where
--   jd = Julian date and its fractional part is zero at noon
--   jdz = same, but assume time is 00:00:00 if no time given
-- http://www.tondering.dk/claus/cal/julperiod.php#formula
-- Testing shows this works for all dates from year -9999 to 9999!
-- JDN 0 is the 24-hour period starting at noon UTC on Monday
--    1 January 4713 BC  = (-4712, 1, 1)   Julian calendar
--   24 November 4714 BC = (-4713, 11, 24) Gregorian calendar
localoffset
locala=floor((14-date.month)/12)
localy=date.year+4800-a
ifdate.calendar=='Julian'then
offset=floor(y/4)-32083
else
offset=floor(y/4)-floor(y/100)+floor(y/400)-32045
end
localm=date.month+12*a-3
localjd=date.day+floor((153*m+2)/5)+365*y+offset
ifdate.hastimethen
jd=jd+hms(date)-0.5
returnjd,jd
end
returnjd,jd-0.5
end
localfunctionset_date_from_jd(date)
-- Set the fields of table date from its Julian date field.
-- Return true if date is valid.
-- http://www.tondering.dk/claus/cal/julperiod.php#formula
-- This handles the proleptic Julian and Gregorian calendars.
-- Negative Julian dates are not defined but they work.
localcalname=date.calendar
locallow,high-- min/max limits for date ranges −9999-01-01 to 9999-12-31
ifcalname=='Gregorian'then
low,high=-1930999.5,5373484.49999
elseifcalname=='Julian'then
low,high=-1931076.5,5373557.49999
else
return
end
localjd=date.jd
ifnot(type(jd)=='number'andlow<=jdandjd<=high)then
return
end
localjdn=floor(jd)
ifdate.hastimethen
localtime=jd-jdn-- 0 <= time < 1
iftime>=0.5then-- if at or after midnight of next day
jdn=jdn+1
time=time-0.5
else
time=time+0.5
end
date.hour,date.minute,date.second=h_m_s(time)
else
date.second=0
date.minute=0
date.hour=0
end
localb,c
ifcalname=='Julian'then
b=0
c=jdn+32082
else-- Gregorian
locala=jdn+32044
b=floor((4*a+3)/146097)
c=a-floor(146097*b/4)
end
locald=floor((4*c+3)/1461)
locale=c-floor(1461*d/4)
localm=floor((5*e+2)/153)
date.day=e-floor((153*m+2)/5)+1
date.month=m+3-12*floor(m/10)
date.year=100*b+d-4800+floor(m/10)
returntrue
end
localfunctionfix_numbers(numbers,y,m,d,H,M,S,partial,hastime,calendar)
-- Put the result of normalizing the given values in table numbers.
-- The result will have valid m, d values if y is valid; caller checks y.
-- The logic of PHP mktime is followed where m or d can be zero to mean
-- the previous unit, and -1 is the one before that, etc.
-- Positive values carry forward.
localdate
ifnot(1<=mandm<=12)then
date=Date(y,1,1)
ifnotdatethenreturnend
date=date+((m-1)..'m')
y,m=date.year,date.month
end
localdays_hms
ifnotpartialthen
ifhastimeandHandMandSthen
ifnot(0<=HandH<=23and
0<=MandM<=59and
0<=SandS<=59)then
days_hms=hms({hour=H,minute=M,second=S})
end
end
ifdays_hmsornot(1<=dandd<=days_in_month(y,m,calendar))then
date=dateorDate(y,m,1)
ifnotdatethenreturnend
date=date+(d-1+(days_hmsor0))
y,m,d=date.year,date.month,date.day
ifdays_hmsthen
H,M,S=date.hour,date.minute,date.second
end
end
end
numbers.year=y
numbers.month=m
numbers.day=d
ifdays_hmsthen
-- Don't set H unless it was valid because a valid H will set hastime.
numbers.hour=H
numbers.minute=M
numbers.second=S
end
end
localfunctionset_date_from_numbers(date,numbers,options)
-- Set the fields of table date from numeric values.
-- Return true if date is valid.
iftype(numbers)~='table'then
return
end
localy=numbers.yearordate.year
localm=numbers.monthordate.month
locald=numbers.dayordate.day
localH=numbers.hour
localM=numbers.minuteordate.minuteor0
localS=numbers.secondordate.secondor0
localneed_fix
ifyandmanddthen
date.partial=nil
ifnot(-9999<=yandy<=9999and
1<=mandm<=12and
1<=dandd<=days_in_month(y,m,date.calendar))then
ifnotdate.want_fixthen
return
end
need_fix=true
end
elseifyanddate.partialthen
ifdornot(-9999<=yandy<=9999)then
return
end
ifmandnot(1<=mandm<=12)then
ifnotdate.want_fixthen
return
end
need_fix=true
end
else
return
end
ifdate.partialthen
H=nil-- ignore any time
M=nil
S=nil
else
ifHthen
-- It is not possible to set M or S without also setting H.
date.hastime=true
else
H=0
end
ifnot(0<=HandH<=23and
0<=MandM<=59and
0<=SandS<=59)then
ifdate.want_fixthen
need_fix=true
else
return
end
end
end
date.want_fix=nil
ifneed_fixthen
fix_numbers(numbers,y,m,d,H,M,S,date.partial,date.hastime,date.calendar)
returnset_date_from_numbers(date,numbers,options)
end
date.year=y-- -9999 to 9999 ('n BC' → year = 1 - n)
date.month=m-- 1 to 12 (may be nil if partial)
date.day=d-- 1 to 31 (* = nil if partial)
date.hour=H-- 0 to 59 (*)
date.minute=M-- 0 to 59 (*)
date.second=S-- 0 to 59 (*)
iftype(options)=='table'then
for_,kinipairs({'am','era','format'})do
ifoptions[k]then
date.options[k]=options[k]
end
end
end
returntrue
end
localfunctionmake_option_table(options1,options2)
-- If options1 is a string, return a table with its settings, or
-- if it is a table, use its settings.
-- Missing options are set from table options2 or defaults.
-- If a default is used, a flag is set so caller knows the value was not intentionally set.
-- Valid option settings are:
-- am: 'am', 'a.m.', 'AM', 'A.M.'
--     'pm', 'p.m.', 'PM', 'P.M.' (each has same meaning as corresponding item above)
-- era: 'BCMINUS', 'BCNEGATIVE', 'BC', 'B.C.', 'BCE', 'B.C.E.', 'AD', 'A.D.', 'CE', 'C.E.'
-- Option am = 'am' does not mean the hour is AM; it means 'am' or 'pm' is used, depending on the hour,
--    and am = 'pm' has the same meaning.
-- Similarly, era = 'BC' means 'BC' is used if year <= 0.
-- BCMINUS displays a MINUS if year < 0 and the display format does not include %{era}.
-- BCNEGATIVE is similar but displays a hyphen.
localresult={bydefault={}}
iftype(options1)=='table'then
result.am=options1.am
result.era=options1.era
elseiftype(options1)=='string'then
-- Example: 'am:AM era:BC' or 'am=AM era=BC'.
foriteminoptions1:gmatch('%S+')do
locallhs,rhs=item:match('^(%w+)[:=](.+)$')
iflhsthen
result[lhs]=rhs
end
end
end
options2=type(options2)=='table'andoptions2or{}
localdefaults={am='am',era='BC'}
fork,vinpairs(defaults)do
ifnotresult[k]then
ifoptions2[k]then
result[k]=options2[k]
else
result[k]=v
result.bydefault[k]=true
end
end
end
returnresult
end
localampm_options={
-- lhs = input text accepted as an am/pm option
-- rhs = code used internally
['am']='am',
['AM']='AM',
['a.m.']='a.m.',
['A.M.']='A.M.',
['pm']='am',-- same as am
['PM']='AM',
['p.m.']='a.m.',
['P.M.']='A.M.',
}
localera_text={
-- Text for displaying an era with a positive year (after adjusting
-- by replacing year with 1 - year if date.year <= 0).
-- options.era = { year<=0 , year>0 }
['BCMINUS']={'SM','',isbc=true,sign=MINUS},
['BCNEGATIVE']={'SM','',isbc=true,sign='-'},
['BC']={'SM','',isbc=true},
['B.C.']={'S.M.','',isbc=true},
['BCE']={'BCE','',isbc=true},
['SM']={'SM','',isbc=true},
['B.C.E.']={'B.C.E.','',isbc=true},
['AD']={'SM','M'},
['A.D.']={'S.M.','M.'},
['CE']={'SM','CE'},
['C.E.']={'S.M.','C.E.'},
['M']={'SM','M'},
}
localfunctionget_era_for_year(era,year)
return(era_text[era]orera_text['BC'])[year>0and2or1]or''
end
localfunctionstrftime(date,format,options)
-- Return date formatted as a string using codes similar to those
-- in the C strftime library function.
localsformat=string.format
localshortcuts={
['%c']='%-I:%M %p %-d %B %-Y %{era}',-- date and time: 2:30 pm 1 April 2016
['%x']='%-d %B %-Y %{era}',-- date:          1 April 2016
['%X']='%-I:%M %p',-- time:          2:30 pm
}
ifshortcuts[format]then
format=shortcuts[format]
end
localcodes={
a={field='dayabbr'},
A={field='dayname'},
b={field='monthabbr'},
B={field='monthname'},
u={fmt='%d',field='dowiso'},
w={fmt='%d',field='dow'},
d={fmt='%02d',fmt2='%d',field='day'},
m={fmt='%02d',fmt2='%d',field='month'},
Y={fmt='%04d',fmt2='%d',field='year'},
H={fmt='%02d',fmt2='%d',field='hour'},
M={fmt='%02d',fmt2='%d',field='minute'},
S={fmt='%02d',fmt2='%d',field='second'},
j={fmt='%03d',fmt2='%d',field='dayofyear'},
I={fmt='%02d',fmt2='%d',field='hour',special='hour12'},
p={field='hour',special='am'},
}
options=make_option_table(options,date.options)
localamopt=options.am
localeraopt=options.era
localfunctionreplace_code(spaces,modifier,id)
localcode=codes[id]
ifcodethen
localfmt=code.fmt
ifmodifier=='-'andcode.fmt2then
fmt=code.fmt2
end
localvalue=date[code.field]
ifnotvaluethen
returnnil-- an undefined field in a partial date
end
localspecial=code.special
ifspecialthen
ifspecial=='hour12'then
value=value%12
value=value==0and12orvalue
elseifspecial=='am'then
localap=({
['a.m.']={'a.m.','p.m.'},
['AM']={'AM','PM'},
['A.M.']={'A.M.','P.M.'},
})[ampm_options[amopt]]or{'am','pm'}
return(spaces==''and''or'&nbsp;')..(value<12andap[1]orap[2])
end
end
ifcode.field=='year'then
localsign=(era_text[eraopt]or{}).sign
ifnotsignorformat:find('%{era}',1,true)then
sign=''
ifvalue<=0then
value=1-value
end
else
ifvalue>=0then
sign=''
else
value=-value
end
end
returnspaces..sign..sformat(fmt,value)
end
returnspaces..(fmtandsformat(fmt,value)orvalue)
end
end
localfunctionreplace_property(spaces,id)
ifid=='era'then
-- Special case so can use local era option.
localresult=get_era_for_year(eraopt,date.year)
ifresult==''then
return''
end
return(spaces==''and''or'&nbsp;')..result
end
localresult=date[id]
iftype(result)=='string'then
returnspaces..result
end
iftype(result)=='number'then
returnspaces..tostring(result)
end
iftype(result)=='boolean'then
returnspaces..(resultand'1'or'0')
end
-- This occurs if id is an undefined field in a partial date, or is the name of a function.
returnnil
end
localPERCENT='\127PERCENT\127'
return(format
:gsub('%%%%',PERCENT)
:gsub('(%s*)%%{(%w+)}',replace_property)
:gsub('(%s*)%%(%-?)(%a)',replace_code)
:gsub(PERCENT,'%%')
)
end
localfunction_date_text(date,fmt,options)
-- Return a formatted string representing the given date.
ifnotis_date(date)then
error('date:text: need a date (use "date:text()" with a colon)',2)
end
iftype(fmt)=='string'andfmt:match('%S')then
iffmt:find('%',1,true)then
returnstrftime(date,fmt,options)
end
elseifdate.partialthen
fmt=date.monthand'my'or'y'
else
fmt='dmy'
ifdate.hastimethen
fmt=(date.second>0and'hms 'or'hm ')..fmt
end
end
localfunctionbad_format()
-- For consistency with other format processing, return given format
-- (or cleaned format if original was not a string) if invalid.
returnmw.text.nowiki(fmt)
end
ifdate.partialthen
-- Ignore days in standard formats like 'ymd'.
iffmt=='ym'orfmt=='ymd'then
fmt=date.monthand'%Y-%m %{era}'or'%Y %{era}'
elseiffmt=='my'orfmt=='dmy'orfmt=='mdy'then
fmt=date.monthand'%B %-Y %{era}'or'%-Y %{era}'
elseiffmt=='y'then
fmt=date.monthand'%-Y %{era}'or'%-Y %{era}'
else
returnbad_format()
end
returnstrftime(date,fmt,options)
end
localfunctionhm_fmt()
localplain=make_option_table(options,date.options).bydefault.am
returnplainand'%H:%M'or'%-I:%M %p'
end
localneed_time=date.hastime
localt=collection()
foriteminfmt:gmatch('%S+')do
localf
ifitem=='hm'then
f=hm_fmt()
need_time=false
elseifitem=='hms'then
f='%H:%M:%S'
need_time=false
elseifitem=='ymd'then
f='%Y-%m-%d %{era}'
elseifitem=='mdy'then
f='%B %-d, %-Y %{era}'
elseifitem=='dmy'then
f='%-d %B %-Y %{era}'
else
returnbad_format()
end
t:add(f)
end
fmt=t:join(' ')
ifneed_timethen
fmt=hm_fmt()..' '..fmt
end
returnstrftime(date,fmt,options)
end
localday_info={
-- 0=Sun to 6=Sat
[0]={'Min','Minggu'},
{'Sen','Senin'},
{'Sel','Selasa'},
{'Rab','Rabu'},
{'Kam','Kamis'},
{'Jum','Jumat'},
{'Sab','Sabtu'},
}
localmonth_info={
-- 1=Jan to 12=Dec
{'Jan','Januari'},
{'Feb','Februari'},
{'Mar','Maret'},
{'Apr','April'},
{'Mei','Mei'},
{'Jun','Juni'},
{'Jul','Juli'},
{'Agu','Agustus'},
{'Sep','September'},
{'Okt','Oktober'},
{'Nov','November'},
{'Des','Desember'},
}
localfunctionname_to_number(text,translate)
iftype(text)=='string'then
returntranslate[text:lower()]
end
end
localfunctionday_number(text)
returnname_to_number(text,{
sun=0,sunday=0,min=0,minggu=0,
mon=1,monday=1,sen=1,senin=1,
tue=2,tuesday=2,sel=2,selasa=2,
wed=3,wednesday=3,rab=3,rabu=3,
thu=4,thursday=4,kam=4,kamis=4,
fri=5,friday=5,jum=5,jumat=5,
sat=6,saturday=6,sab=6,sabtu=6,
})
end
localfunctionmonth_number(text)
returnname_to_number((text:gsub('%.','')),{
jan=1,january=1,januari=1,
feb=2,february=2,februari=2,
mar=3,march=3,maret=3,
apr=4,april=4,
may=5,mei=5,
jun=6,june=6,juni=6,
jul=7,july=7,juli=7,
aug=8,august=8,agu=8,agt=8,agustus=8,
sep=9,september=9,sept=9,
oct=10,october=10,okt=10,oktober=10,
nov=11,november=11,
dec=12,december=12,des=12,desember=12,
})
end
localfunction_list_text(list,fmt)
-- Return a list of formatted strings from a list of dates.
ifnottype(list)=='table'then
error('date:list:text: need "list:text()" with a colon',2)
end
localresult={join=_list_join}
fori,dateinipairs(list)do
result[i]=date:text(fmt)
end
returnresult
end
localfunction_date_list(date,spec)
-- Return a possibly empty numbered table of dates meeting the specification.
-- Dates in the list are in ascending order (oldest date first).
-- The spec should be a string of form "<count> <day> <op>"
-- where each item is optional and
--   count = number of items wanted in list
--   day = abbreviation or name such as Mon or Monday
--   op = >, >=, <, <= (default is > meaning after date)
-- If no count is given, the list is for the specified days in date's month.
-- The default day is date's day.
-- The spec can also be a positive or negative number:
--   -5 is equivalent to '5 <'
--   5  is equivalent to '5' which is '5 >'
ifnotis_date(date)then
error('date:list: need a date (use "date:list()" with a colon)',2)
end
locallist={text=_list_text}
ifdate.partialthen
returnlist
end
localcount,offset,operation
localops={
['>=']={before=false,include=true},
['>']={before=false,include=false},
['<=']={before=true,include=true},
['<']={before=true,include=false},
}
ifspecthen
iftype(spec)=='number'then
count=floor(spec+0.5)
ifcount<0then
count=-count
operation=ops['<']
end
elseiftype(spec)=='string'then
localnum,day,op=spec:match('^%s*(%d*)%s*(%a*)%s*([<>=]*)%s*$')
ifnotnumthen
returnlist
end
ifnum~=''then
count=tonumber(num)
end
ifday~=''then
localdow=day_number(day:gsub('[sS]$',''))-- accept plural days
ifnotdowthen
returnlist
end
offset=dow-date.dow
end
operation=ops[op]
else
returnlist
end
end
offset=offsetor0
operation=operationorops['>']
localdatefrom,dayfirst,daylast
ifoperation.beforethen
ifoffset>0or(offset==0andnotoperation.include)then
offset=offset-7
end
ifcountthen
ifcount>1then
offset=offset-7*(count-1)
end
datefrom=date+offset
else
daylast=date.day+offset
dayfirst=daylast%7
ifdayfirst==0then
dayfirst=7
end
end
else
ifoffset<0or(offset==0andnotoperation.include)then
offset=offset+7
end
ifcountthen
datefrom=date+offset
else
dayfirst=date.day+offset
daylast=date.monthdays
end
end
ifnotcountthen
ifdaylast<dayfirstthen
returnlist
end
count=floor((daylast-dayfirst)/7)+1
datefrom=Date(date,{day=dayfirst})
end
fori=1,countdo
ifnotdatefromthenbreakend-- exceeds date limits
list[i]=datefrom
datefrom=datefrom+7
end
returnlist
end
-- Cache the current date/time (UTC). Avoid `current.min` or `current.sec` in articles!
-- This returns an empty object with a per-field lazy getter optimization. https://phabricator.wikimedia.org/T416616
localcurrent=os.date('!*t')
localfunctionextract_date(newdate,text)
-- Parse the date/time in text and return n, o where
--   n = table of numbers with date/time fields
--   o = table of options for AM/PM or AD/BC or format, if any
-- or return nothing if date is known to be invalid.
-- Caller determines if the values in n are valid.
-- A year must be positive ('1' to '9999'); use 'BC' for BC.
-- In a y-m-d string, the year must be four digits to avoid ambiguity
-- ('0001' to '9999'). The only way to enter year <= 0 is by specifying
-- the date as three numeric parameters like ymd Date(-1, 1, 1).
-- Dates of form d/m/y, m/d/y, y/m/d are rejected as potentially ambiguous.
localdate,options={},{}
iftext:sub(-1)=='Z'then
-- Extract date/time from a Wikidata timestamp.
-- The year can be 1 to 16 digits but this module handles 1 to 4 digits only.
-- Examples: '+2016-06-21T14:30:00Z', '-0000000180-00-00T00:00:00Z'.
localsign,y,m,d,H,M,S=text:match('^([+%-])(%d+)%-(%d%d)%-(%d%d)T(%d%d):(%d%d):(%d%d)Z$')
ifsignthen
y=tonumber(y)
ifsign=='-'andy>0then
y=-y
end
ify<=0then
options.era='BCE'
end
date.year=y
m=tonumber(m)
d=tonumber(d)
H=tonumber(H)
M=tonumber(M)
S=tonumber(S)
ifm==0then
newdate.partial=true
returndate,options
end
date.month=m
ifd==0then
newdate.partial=true
returndate,options
end
date.day=d
ifH>0orM>0orS>0then
date.hour=H
date.minute=M
date.second=S
end
returndate,options
end
return
end
localfunctionextract_ymd(item)
-- Called when no day or month has been set.
localy,m,d=item:match('^(%d%d%d%d)%-(%w+)%-(%d%d?)$')
ifythen
ifdate.yearthen
return
end
ifm:match('^%d%d?$')then
m=tonumber(m)
else
m=month_number(m)
end
ifmthen
date.year=tonumber(y)
date.month=m
date.day=tonumber(d)
returntrue
end
end
end
localfunctionextract_day_or_year(item)
-- Called when a day would be valid, or
-- when a year would be valid if no year has been set and partial is set.
localnumber,suffix=item:match('^(%d%d?%d?%d?)(.*)$')
ifnumberthen
localn=tonumber(number)
if#number<=2andn<=31then
suffix=suffix:lower()
ifsuffix==''orsuffix=='st'orsuffix=='nd'orsuffix=='rd'orsuffix=='th'then
date.day=n
returntrue
end
elseifsuffix==''andnewdate.partialandnotdate.yearthen
date.year=n
returntrue
end
end
end
localfunctionextract_month(item)
-- A month must be given as a name or abbreviation; a number could be ambiguous.
localm=month_number(item)
ifmthen
date.month=m
returntrue
end
end
localfunctionextract_time(item)
localh,m,s=item:match('^(%d%d?):(%d%d)(:?%d*)$')
ifdate.hourornoththen
return
end
ifs~=''then
s=s:match('^:(%d%d)$')
ifnotsthen
return
end
end
date.hour=tonumber(h)
date.minute=tonumber(m)
date.second=tonumber(s)-- nil if empty string
returntrue
end
localitem_count=0
localindex_time
localfunctionset_ampm(item)
localH=date.hour
ifHandnotoptions.amandindex_time+1==item_countthen
options.am=ampm_options[item]-- caller checked this is not nil
ifitem:match('^[Aa]')then
ifnot(1<=HandH<=12)then
return
end
ifH==12then
date.hour=0
end
else
ifnot(1<=HandH<=23)then
return
end
ifH<=11then
date.hour=H+12
end
end
returntrue
end
end
foritemintext:gsub(',',' '):gsub('&nbsp;',' '):gmatch('%S+')do
item_count=item_count+1
ifera_text[item]then
-- Era is accepted in peculiar places.
ifoptions.erathen
return
end
options.era=item
elseifampm_options[item]then
ifnotset_ampm(item)then
return
end
elseifitem:find(':',1,true)then
ifnotextract_time(item)then
return
end
index_time=item_count
elseifdate.dayanddate.monththen
ifdate.yearthen
return-- should be nothing more so item is invalid
end
ifnotitem:match('^(%d%d?%d?%d?)$')then
return
end
date.year=tonumber(item)
elseifdate.daythen
ifnotextract_month(item)then
return
end
elseifdate.monththen
ifnotextract_day_or_year(item)then
return
end
elseifextract_month(item)then
options.format='mdy'
elseifextract_ymd(item)then
options.format='ymd'
elseifextract_day_or_year(item)then
ifdate.daythen
options.format='dmy'
end
else
return
end
end
ifnotdate.yearordate.year==0then
return
end
localera=era_text[options.era]
iferaandera.isbcthen
date.year=1-date.year
end
returndate,options
end
localfunctionautofill(date1,date2)
-- Fill any missing month or day in each date using the
-- corresponding component from the other date, if present,
-- or with 1 if both dates are missing the month or day.
-- This gives a good result for calculating the difference
-- between two partial dates when no range is wanted.
-- Return filled date1, date2 (two full dates).
localfunctionfilled(a,b)
-- Return date a filled, if necessary, with month and/or day from date b.
-- The filled day is truncated to fit the number of days in the month.
localfillmonth,fillday
ifnota.monththen
fillmonth=b.monthor1
end
ifnota.daythen
fillday=b.dayor1
end
iffillmonthorfilldaythen-- need to create a new date
a=Date(a,{
month=fillmonth,
day=math.min(filldayora.day,days_in_month(a.year,fillmonthora.month,a.calendar))
})
end
returna
end
returnfilled(date1,date2),filled(date2,date1)
end
localfunctiondate_add_sub(lhs,rhs,is_sub)
-- Return a new date from calculating (lhs + rhs) or (lhs - rhs),
-- or return nothing if invalid.
-- The result is nil if the calculated date exceeds allowable limits.
-- Caller ensures that lhs is a date; its properties are copied for the new date.
iflhs.partialthen
-- Adding to a partial is not supported.
-- Can subtract a date or partial from a partial, but this is not called for that.
return
end
localfunctionis_prefix(text,word,minlen)
localn=#text
return(minlenor1)<=nandn<=#wordandtext==word:sub(1,n)
end
localfunctiondo_days(n)
localforcetime,jd
iffloor(n)==nthen
jd=lhs.jd
else
forcetime=notlhs.hastime
jd=lhs.jdz
end
jd=jd+(is_suband-norn)
ifforcetimethen
jd=tostring(jd)
ifnotjd:find('.',1,true)then
jd=jd..'.0'
end
end
returnDate(lhs,'juliandate',jd)
end
iftype(rhs)=='number'then
-- Add/subtract days, including fractional days.
returndo_days(rhs)
end
iftype(rhs)=='string'then
-- rhs is a single component like '26m' or '26 months' (with optional sign).
-- Fractions like '3.25d' are accepted for the units which are handled as days.
localsign,numstr,id=rhs:match('^%s*([+-]?)([%d%.]+)%s*(%a+)$')
ifsignthen
ifsign=='-'then
is_sub=not(is_subandtrueorfalse)
end
localy,m,days
localnum=tonumber(numstr)
ifnotnumthen
return
end
id=id:lower()
ifis_prefix(id,'years')oris_prefix(id,'tahun')then
y=num
m=0
elseifis_prefix(id,'months')oris_prefix(id,'bulan')then
y=floor(num/12)
m=num%12
elseifis_prefix(id,'weeks')oris_prefix(id,'minggu')then
days=num*7
elseifis_prefix(id,'days')oris_prefix(id,'hari')then
days=num
elseifis_prefix(id,'hours')oris_prefix(id,'jam')then
days=num/24
elseifis_prefix(id,'minutes',3)oris_prefix(id,'menit',3)then
days=num/(24*60)
elseifis_prefix(id,'seconds')oris_prefix(id,'detik')then
days=num/(24*3600)
else
return
end
ifdaysthen
returndo_days(days)
end
ifnumstr:find('.',1,true)then
return
end
ifis_subthen
y=-y
m=-m
end
assert(-11<=mandm<=11)
y=lhs.year+y
m=lhs.month+m
ifm>12then
y=y+1
m=m-12
elseifm<1then
y=y-1
m=m+12
end
locald=math.min(lhs.day,days_in_month(y,m,lhs.calendar))
returnDate(lhs,y,m,d)
end
end
ifis_diff(rhs)then
localdays=rhs.age_days
if(is_suborfalse)~=(rhs.isnegativeorfalse)then
days=-days
end
returnlhs+days
end
end
localfull_date_only={
dayabbr=true,
dayname=true,
dow=true,
dayofweek=true,
dowiso=true,
dayofweekiso=true,
dayofyear=true,
gsd=true,
juliandate=true,
jd=true,
jdz=true,
jdnoon=true,
}
-- Metatable for a date's calculated fields.
localdatemt={
__index=function(self,key)
ifrawget(self,'partial')then
iffull_date_only[key]thenreturnend
ifkey=='monthabbr'orkey=='monthdays'orkey=='monthname'then
ifnotself.monththenreturnend
end
end
localvalue
ifkey=='dayabbr'then
value=day_info[self.dow][1]
elseifkey=='dayname'then
value=day_info[self.dow][2]
elseifkey=='dow'then
value=(self.jdnoon+1)%7-- day-of-week 0=Sun to 6=Sat
elseifkey=='dayofweek'then
value=self.dow
elseifkey=='dowiso'then
value=(self.jdnoon%7)+1-- ISO day-of-week 1=Mon to 7=Sun
elseifkey=='dayofweekiso'then
value=self.dowiso
elseifkey=='dayofyear'then
localfirst=Date(self.year,1,1,self.calendar).jdnoon
value=self.jdnoon-first+1-- day-of-year 1 to 366
elseifkey=='era'then
-- Era text (never a negative sign) from year and options.
value=get_era_for_year(self.options.era,self.year)
elseifkey=='format'then
value=self.options.formator'dmy'
elseifkey=='gsd'then
-- GSD = 1 from 00:00:00 to 23:59:59 on 1 January 1 AD Gregorian calendar,
-- which is from jd 1721425.5 to 1721426.49999.
value=floor(self.jd-1721424.5)
elseifkey=='juliandate'orkey=='jd'orkey=='jdz'then
localjd,jdz=julian_date(self)
rawset(self,'juliandate',jd)
rawset(self,'jd',jd)
rawset(self,'jdz',jdz)
returnkey=='jdz'andjdzorjd
elseifkey=='jdnoon'then
-- Julian date at noon (an integer) on the calendar day when jd occurs.
value=floor(self.jd+0.5)
elseifkey=='isleapyear'then
value=is_leap_year(self.year,self.calendar)
elseifkey=='monthabbr'then
value=month_info[self.month][1]
elseifkey=='monthdays'then
value=days_in_month(self.year,self.month,self.calendar)
elseifkey=='monthname'then
value=month_info[self.month][2]
end
ifvalue~=nilthen
rawset(self,key,value)
returnvalue
end
end,
}
-- Date operators.
localfunctionmt_date_add(lhs,rhs)
ifnotis_date(lhs)then
lhs,rhs=rhs,lhs-- put date on left (it must be a date for this to have been called)
end
returndate_add_sub(lhs,rhs)
end
localfunctionmt_date_sub(lhs,rhs)
ifis_date(lhs)then
ifis_date(rhs)then
returnDateDiff(lhs,rhs)
end
returndate_add_sub(lhs,rhs,true)
end
end
localfunctionmt_date_concat(lhs,rhs)
returntostring(lhs)..tostring(rhs)
end
localfunctionmt_date_tostring(self)
returnself:text()
end
localfunctionmt_date_eq(lhs,rhs)
-- Return true if dates identify same date/time where, for example,
-- Date(-4712, 1, 1, 'Julian') == Date(-4713, 11, 24, 'Gregorian') is true.
-- This is called only if lhs and rhs have the same type and the same metamethod.
iflhs.partialorrhs.partialthen
-- One date is partial; the other is a partial or a full date.
-- The months may both be nil, but must be the same.
returnlhs.year==rhs.yearandlhs.month==rhs.monthandlhs.calendar==rhs.calendar
end
returnlhs.jdz==rhs.jdz
end
localfunctionmt_date_lt(lhs,rhs)
-- Return true if lhs < rhs, for example,
-- Date('1 Jan 2016') < Date('06:00 1 Jan 2016') is true.
-- This is called only if lhs and rhs have the same type and the same metamethod.
iflhs.partialorrhs.partialthen
-- One date is partial; the other is a partial or a full date.
iflhs.calendar~=rhs.calendarthen
returnlhs.calendar=='Julian'
end
iflhs.partialthen
lhs=lhs.partial.first
end
ifrhs.partialthen
rhs=rhs.partial.first
end
end
returnlhs.jdz<rhs.jdz
end
--[[ Examples of syntax to construct a date:
Date(y, m, d, 'julian')             default calendar is 'gregorian'
Date(y, m, d, H, M, S, 'julian')
Date('juliandate', jd, 'julian')    if jd contains "." text output includes H:M:S
Date('currentdate')
Date('currentdatetime')
Date('1 April 1995', 'julian')      parse date from text
Date('1 April 1995 AD', 'julian')   using an era sets a flag to do the same for output
Date('04:30:59 1 April 1995', 'julian')
Date(date)                          copy of an existing date
Date(date, t)                       same, updated with y,m,d,H,M,S fields from table t
Date(t)                       		date with y,m,d,H,M,S fields from table t
]]
functionDate(...)-- for forward declaration above
-- Return a table holding a date assuming a uniform calendar always applies
-- (proleptic Gregorian calendar or proleptic Julian calendar), or
-- return nothing if date is invalid.
-- A partial date has a valid year, however its month may be nil, and
-- its day and time fields are nil.
-- Field partial is set to false (if a full date) or a table (if a partial date).
localcalendars={julian='Julian',gregorian='Gregorian'}
localnewdate={
_id=uniq,
calendar='Gregorian',-- default is Gregorian calendar
hastime=false,-- true if input sets a time
hour=0,-- always set hour/minute/second so don't have to handle nil
minute=0,
second=0,
options={},
list=_date_list,
subtract=function(self,rhs,options)
returnDateDiff(self,rhs,options)
end,
text=_date_text,
}
localargtype,datetext,is_copy,jd_number,tnums
localnumindex=0
localnumfields={'year','month','day','hour','minute','second'}
localnumbers={}
for_,vinipairs({...})do
v=strip_to_nil(v)
localvlower=type(v)=='string'andv:lower()ornil
ifv==nilthen
-- Ignore empty arguments after stripping so modules can directly pass template parameters.
elseifcalendars[vlower]then
newdate.calendar=calendars[vlower]
elseifvlower=='partial'then
newdate.partial=true
elseifvlower=='fix'then
newdate.want_fix=true
elseifis_date(v)then
-- Copy existing date (items can be overridden by other arguments).
ifis_copyortnumsthen
return
end
is_copy=true
newdate.calendar=v.calendar
newdate.partial=v.partial
newdate.hastime=v.hastime
newdate.options=v.options
newdate.year=v.year
newdate.month=v.month
newdate.day=v.day
newdate.hour=v.hour
newdate.minute=v.minute
newdate.second=v.second
elseiftype(v)=='table'then
iftnumsthen
return
end
tnums={}
localtfields={year=1,month=1,day=1,hour=2,minute=2,second=2}
fortk,tvinpairs(v)do
iftfields[tk]then
tnums[tk]=tonumber(tv)
end
iftfields[tk]==2then
newdate.hastime=true
end
end
else
localnum=tonumber(v)
ifnotnumandargtype=='setdate'andnumindex==1then
num=month_number(v)
end
ifnumthen
ifnotargtypethen
argtype='setdate'
end
ifargtype=='setdate'andnumindex<6then
numindex=numindex+1
numbers[numfields[numindex]]=num
elseifargtype=='juliandate'andnotjd_numberthen
jd_number=num
iftype(v)=='string'then
ifv:find('.',1,true)then
newdate.hastime=true
end
elseifnum~=floor(num)then
-- The given value was a number. The time will be used
-- if the fractional part is nonzero.
newdate.hastime=true
end
else
return
end
elseifargtypethen
return
elseiftype(v)=='string'then
ifv=='currentdate'orv=='currentdatetime'orv=='juliandate'then
argtype=v
else
argtype='datetext'
datetext=v
end
else
return
end
end
end
ifargtype=='datetext'then
iftnumsornotset_date_from_numbers(newdate,extract_date(newdate,datetext))then
return
end
elseifargtype=='juliandate'then
newdate.partial=nil
newdate.jd=jd_number
ifnotset_date_from_jd(newdate)then
return
end
elseifargtype=='currentdate'orargtype=='currentdatetime'then
newdate.partial=nil
newdate.year=current.year
newdate.month=current.month
newdate.day=current.day
ifargtype=='currentdatetime'then
newdate.hour=current.hour
newdate.minute=current.min
newdate.second=current.sec
newdate.hastime=true
end
newdate.calendar='Gregorian'-- ignore any given calendar name
elseifargtype=='setdate'then
iftnumsornotset_date_from_numbers(newdate,numbers)then
return
end
elseifnot(is_copyortnums)then
return
end
iftnumsthen
newdate.jd=nil-- force recalculation in case jd was set before changes from tnums
ifnotset_date_from_numbers(newdate,tnums)then
return
end
end
ifnewdate.partialthen
localyear=newdate.year
localmonth=newdate.month
localfirst=Date(year,monthor1,1,newdate.calendar)
month=monthor12
locallast=Date(year,month,days_in_month(year,month),newdate.calendar)
newdate.partial={first=first,last=last}
else
newdate.partial=false-- avoid index lookup
end
setmetatable(newdate,datemt)
localreadonly={}
localmt={
__index=newdate,
__newindex=function(t,k,v)error('date.'..tostring(k)..' is read-only',2)end,
__add=mt_date_add,
__sub=mt_date_sub,
__concat=mt_date_concat,
__tostring=mt_date_tostring,
__eq=mt_date_eq,
__lt=mt_date_lt,
}
returnsetmetatable(readonly,mt)
end
localfunction_diff_age(diff,code,options)
-- Return a tuple of integer values from diff as specified by code, except that
-- each integer may be a list of two integers for a diff with a partial date, or
-- return nil if the code is not supported.
-- If want round, the least significant unit is rounded to nearest whole unit.
-- For a duration, an extra day is added.
localwantround,wantduration,wantrange
iftype(options)=='table'then
wantround=options.round
wantduration=options.duration
wantrange=options.range
else
wantround=options
end
ifnotis_diff(diff)then
localf=wantdurationand'duration'or'age'
error(f..': need a date difference (use "diff:'..f..'()" with a colon)',2)
end
ifdiff.partialthen
-- Ignore wantround, wantduration.
localfunctionchoose(v)
iftype(v)=='table'then
ifnotwantrangeorv[1]==v[2]then
-- Example: Date('partial', 2005) - Date('partial', 2001) gives
-- diff.years = { 3, 4 } to show the range of possible results.
-- If do not want a range, choose the second value as more expected.
returnv[2]
end
end
returnv
end
ifcode=='ym'orcode=='ymd'then
ifnotwantrangeanddiff.iszerothen
-- This avoids an unexpected result such as
-- Date('partial', 2001) - Date('partial', 2001)
-- giving diff = { years = 0, months = { 0, 11 } }
-- which would be reported as 0 years and 11 months.
return0,0
end
returnchoose(diff.partial.years),choose(diff.partial.months)
end
ifcode=='y'then
returnchoose(diff.partial.years)
end
ifcode=='m'orcode=='w'orcode=='d'then
returnchoose({diff.partial.mindiff:age(code),diff.partial.maxdiff:age(code)})
end
returnnil
end
localextra_days=wantdurationand1or0
ifcode=='wd'orcode=='w'orcode=='d'then
localoffset=wantroundand0.5or0
localdays=diff.age_days+extra_days
ifcode=='wd'orcode=='d'then
days=floor(days+offset)
ifcode=='d'then
returndays
end
returnfloor(days/7),days%7
end
returnfloor(days/7+offset)
end
localH,M,S=diff.hours,diff.minutes,diff.seconds
ifcode=='dh'orcode=='dhm'orcode=='dhms'orcode=='h'orcode=='hm'orcode=='hms'orcode=='M'orcode=='s'then
localdays=floor(diff.age_days+extra_days)
localinc_hour
ifwantroundthen
ifcode=='dh'orcode=='h'then
ifM>=30then
inc_hour=true
end
elseifcode=='dhm'orcode=='hm'then
ifS>=30then
M=M+1
ifM>=60then
M=0
inc_hour=true
end
end
elseifcode=='M'then
ifS>=30then
M=M+1
end
else
-- Nothing needed because S is an integer.
end
ifinc_hourthen
H=H+1
ifH>=24then
H=0
days=days+1
end
end
end
ifcode=='dh'orcode=='dhm'orcode=='dhms'then
ifcode=='dh'then
returndays,H
elseifcode=='dhm'then
returndays,H,M
else
returndays,H,M,S
end
end
localhours=days*24+H
ifcode=='h'then
returnhours
elseifcode=='hm'then
returnhours,M
elseifcode=='M'orcode=='s'then
M=hours*60+M
ifcode=='M'then
returnM
end
returnM*60+S
end
returnhours,M,S
end
ifwantroundthen
localinc_hour
ifcode=='ymdh'orcode=='ymwdh'then
ifM>=30then
inc_hour=true
end
elseifcode=='ymdhm'orcode=='ymwdhm'then
ifS>=30then
M=M+1
ifM>=60then
M=0
inc_hour=true
end
end
elseifcode=='ymd'orcode=='ymwd'orcode=='yd'orcode=='md'then
ifH>=12then
extra_days=extra_days+1
end
end
ifinc_hourthen
H=H+1
ifH>=24then
H=0
extra_days=extra_days+1
end
end
end
localy,m,d=diff.years,diff.months,diff.days
ifextra_days>0then
d=d+extra_days
ifd>28orcode=='yd'then
-- Recalculate in case have passed a month.
diff=diff.date1+extra_days-diff.date2
y,m,d=diff.years,diff.months,diff.days
end
end
ifcode=='ymd'then
returny,m,d
elseifcode=='yd'then
ify>0then
-- It is known that diff.date1 > diff.date2.
diff=diff.date1-(diff.date2+(y..'y'))
end
returny,floor(diff.age_days)
elseifcode=='md'then
returny*12+m,d
elseifcode=='ym'orcode=='m'then
ifwantroundthen
ifd>=16then
m=m+1
ifm>=12then
m=0
y=y+1
end
end
end
ifcode=='ym'then
returny,m
end
returny*12+m
elseifcode=='ymw'then
localweeks=floor(d/7)
ifwantroundthen
localdays=d%7
ifdays>3or(days==3andH>=12)then
weeks=weeks+1
end
end
returny,m,weeks
elseifcode=='ymwd'then
returny,m,floor(d/7),d%7
elseifcode=='ymdh'then
returny,m,d,H
elseifcode=='ymwdh'then
returny,m,floor(d/7),d%7,H
elseifcode=='ymdhm'then
returny,m,d,H,M
elseifcode=='ymwdhm'then
returny,m,floor(d/7),d%7,H,M
end
ifcode=='y'then
ifwantroundandm>=6then
y=y+1
end
returny
end
returnnil
end
localfunction_diff_duration(diff,code,options)
iftype(options)~='table'then
options={round=options}
end
options.duration=true
return_diff_age(diff,code,options)
end
-- Metatable for some operations on date differences.
diffmt={-- for forward declaration above
__concat=function(lhs,rhs)
returntostring(lhs)..tostring(rhs)
end,
__tostring=function(self)
returntostring(self.age_days)
end,
__index=function(self,key)
localvalue
ifkey=='age_days'then
ifrawget(self,'partial')then
localfunctionjdz(date)
return(date.partialanddate.partial.firstordate).jdz
end
value=jdz(self.date1)-jdz(self.date2)
else
value=self.date1.jdz-self.date2.jdz
end
end
ifvalue~=nilthen
rawset(self,key,value)
returnvalue
end
end,
}
functionDateDiff(date1,date2,options)-- for forward declaration above
-- Return a table with the difference between two dates (date1 - date2).
-- The difference is negative if date1 is older than date2.
-- Return nothing if invalid.
-- If d = date1 - date2 then
--     date1 = date2 + d
-- If date1 >= date2 and the dates have no H:M:S time specified then
--     date1 = date2 + (d.years..'y') + (d.months..'m') + d.days
-- where the larger time units are added first.
-- The result of Date(2015,1,x) + '1m' is Date(2015,2,28) for
-- x = 28, 29, 30, 31. That means, for example,
--     d = Date(2015,3,3) - Date(2015,1,31)
-- gives d.years, d.months, d.days = 0, 1, 3 (excluding date1).
ifnot(is_date(date1)andis_date(date2)anddate1.calendar==date2.calendar)then
return
end
localwantfill
iftype(options)=='table'then
wantfill=options.fill
end
localisnegative=false
localiszero=false
ifdate1<date2then
isnegative=true
date1,date2=date2,date1
elseifdate1==date2then
iszero=true
end
-- It is known that date1 >= date2 (period is from date2 to date1).
ifdate1.partialordate2.partialthen
-- Two partial dates might have timelines:
---------------------A=================B--- date1 is from A to B inclusive
--------C=======D-------------------------- date2 is from C to D inclusive
-- date1 > date2 iff A > C (date1.partial.first > date2.partial.first)
-- The periods can overlap ('April 2001' - '2001'):
-------------A===B------------------------- A=2001-04-01  B=2001-04-30
--------C=====================D------------ C=2001-01-01  D=2001-12-31
ifwantfillthen
date1,date2=autofill(date1,date2)
else
localfunctionzdiff(date1,date2)
localdiff=date1-date2
ifdiff.isnegativethen
returndate1-date1-- a valid diff in case we call its methods
end
returndiff
end
localfunctiongetdate(date,which)
returndate.partialanddate.partial[which]ordate
end
localmaxdiff=zdiff(getdate(date1,'last'),getdate(date2,'first'))
localmindiff=zdiff(getdate(date1,'first'),getdate(date2,'last'))
localyears,months
ifmaxdiff.years==mindiff.yearsthen
years=maxdiff.years
ifmaxdiff.months==mindiff.monthsthen
months=maxdiff.months
else
months={mindiff.months,maxdiff.months}
end
else
years={mindiff.years,maxdiff.years}
end
returnsetmetatable({
date1=date1,
date2=date2,
partial={
years=years,
months=months,
maxdiff=maxdiff,
mindiff=mindiff,
},
isnegative=isnegative,
iszero=iszero,
age=_diff_age,
duration=_diff_duration,
},diffmt)
end
end
localy1,m1=date1.year,date1.month
localy2,m2=date2.year,date2.month
localyears=y1-y2
localmonths=m1-m2
locald1=date1.day+hms(date1)
locald2=date2.day+hms(date2)
localdays,time
ifd1>=d2then
days=d1-d2
else
months=months-1
-- Get days in previous month (before the "to" date) given December has 31 days.
localdpm=m1>1anddays_in_month(y1,m1-1,date1.calendar)or31
ifd2>=dpmthen
days=d1-hms(date2)
else
days=dpm-d2+d1
end
end
ifmonths<0then
years=years-1
months=months+12
end
days,time=math.modf(days)
localH,M,S=h_m_s(time)
returnsetmetatable({
date1=date1,
date2=date2,
partial=false,-- avoid index lookup
years=years,
months=months,
days=days,
hours=H,
minutes=M,
seconds=S,
isnegative=isnegative,
iszero=iszero,
age=_diff_age,
duration=_diff_duration,
},diffmt)
end
return{
_current=current,
_Date=Date,
_days_in_month=days_in_month,
}
Konten disalin dari Wikipedia Bahasa Indonesia (lisensi CC BY-SA) Lihat versi asli di Wikipedia

Rekomendasi Pilihan