--!name Days Until
--!icon bell
--!category Tools
local aq,aA=30,16 local W={"January","February","March","April","May","June","July","August","September","October","November","December"}local ax={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}local S={"b","a","y","o"}local V={b="Birthday",a="Anniversary",y="Every year",o="One-off"}local ay={b="Every year. With the year they were born it says how old they turn.",a="Every year. With the year it began it counts the years.",y="Every year on the same date: a holiday, a reminder, a festival.",o="Just once: a trip, an event, a deadline. Counts the days since afterwards.",}local w={{"New Year's Day","f0101",true,"1 January."},{"Lunar New Year","lunar",false,"New moon in late January or February (dates to 2050)."},{"Valentine's Day","f0214",true,"14 February."},{"St Patrick's Day","f0317",false,"17 March."},{"Mothering Sunday (UK)","e-21",false,"Three weeks before Easter Sunday."},{"Easter","e0",true,"Sunday after the first full moon of spring (Western churches)."},{"Mother's Day","n0502",true,"Second Sunday in May (US, Canada, Australia and more)."},{"Father's Day","n0603",false,"Third Sunday in June (US, UK, Canada and more)."},{"Halloween","f1031",true,"31 October."},{"Thanksgiving (US)","n1144",false,"Fourth Thursday in November."},{"Christmas","f1225",true,"25 December."},{"New Year's Eve","f1231",false,"31 December."},}local az="412948372644342342315039284635244332224130483726453323"local y={}local I=0 local p,s,C,x,L,H="main",1,1,1,1,1 local B,U,h,ag,v local function a_(bk,bd,a9)return math.max(bd,math.min(a9,bk))end local function N(b,m,l)if m<=2 then b=b-1 end local Z=b//400 local P=b-Z*400 local ai=(153*((m+9)%12)+2)//5+l-1 return Z*146097+P*365+P//4-P//100+ai-719468 end local function aw(ap)ap=ap+719468 local Z=ap//146097 local Y=ap-Z*146097 local P=(Y-Y//1460+Y//36524-Y//146096)//365 local ai=Y-(365*P+P//4-P//100)local am=(5*ai+2)//153 local m=am<10 and am+3 or am-9 return P+Z*400+(m<=2 and 1 or 0),m,ai-(153*am+2)//5+1 end local function aN(b)return b%4==0 and(b%100~=0 or b%400==0)end local function al(m,b)if m==2 then return(b==0 or aN(b))and 29 or 28 end return(m==4 or m==6 or m==9 or m==11)and 30 or 31 end local function E()local j=os.time()if not j or j<1700000000 then return nil end local l=os.date("*t",j)return N(l.year,l.month,l.day),l.year end local function a7(b)local q,G,e=b%19,b//100,b%100 local t=(19*q+G-G//4-(G-(G+8)//25+1)//3+15)%30 local aM=(32+2*(G%4)+2*(e//4)-t-e%4)%7 local m=(q+11*t+22*aM)//451 local g=t+aM-7*m+114 return N(b,g//31,g%31+1)end local function ak(c,b)local u=w[c][2]local e,q=u:sub(1,1),u:sub(2)if e=="f"then return N(b,tonumber(q:sub(1,2)),tonumber(q:sub(3,4)))end if e=="e"then return a7(b)+tonumber(q)end if e=="n"then local aK=N(b,tonumber(q:sub(1,2)),1)return aK+(tonumber(q:sub(3,3))-(aK+4)%7)%7+(tonumber(q:sub(4,4))-1)*7 end local d=b-2023 if d>=1 and d<=#az//2 then return N(b,1,1)+tonumber(az:sub(2*d-1,2*d))-1 end end local function aP(m,l,b)if m==2 and l==29 and not aN(b)then l=28 end return N(b,m,l)end local function an(a,i,k)if a.k=="o"then return N(a.y,a.m,a.d),a.y end local b=math.max(k,a.y)local f=aP(a.m,a.d,b)if f<i then b=b+1 f=aP(a.m,a.d,b)end return f,b end local function ac(c)return I&(1<<(c-1))~=0 end local function aL(i,k)local j={}for X,a in ipairs(y)do local f,b=an(a,i,k)j[#j+1]={n=a.n,x=f,y=b,e=a}end for c,t in ipairs(w)do if ac(c)then local f,b=ak(c,k),k if not f or f<i then f,b=ak(c,k+1),k+1 end if f then j[#j+1]={n=t[1],x=f,y=b,h=c}end end end table.sort(j,function(q,G)local aQ,aR=q.x<i,G.x<i if aQ~=aR then return aR end if q.x~=G.x then return(q.x<G.x)~=aQ end return q.n<G.n end)return j end local function bg(g)local j=g%100 return g..((j>=11 and j<=13)and"th"or({"st","nd","rd"})[g%10]or"th")end local function af(a,b)if not a or a.k=="o"or a.y==0 or b<=a.y then return nil end local g=b-a.y if a.k=="b"then return"turns "..g end if a.k=="a"then return bg(g).." anniversary"end return g..(g==1 and" year"or" years")end local function _(f,k)local b,m,l=aw(f)return ax[(f+4)%7+1].." "..l.." "..W[m]..(b~=k and" "..b or"")end local function aI(a)return a.d.." "..W[a.m]..(a.y>0 and" "..a.y or"")end local function aG(g)if g==0 then return"Today"end if g==1 then return"Tomorrow"end if g==-1 then return"Yesterday"end return g>0 and g.." days"or-g.." days ago"end local function av(g)return(g>1 and"In "or"")..aG(g)end local function ab()local as={"D1|"..I}for X,a in ipairs(y)do as[#as+1]=a.k..a.y.."|"..a.m.."|"..a.d.."|"..a.n end snail.save(table.concat(as,"\n"))end local function bj()for c,t in ipairs(w)do if t[3]then I=I|(1<<(c-1))end end local n=snail.load()if type(n)~="string"or n:sub(1,3)~="D1|"then return end I=(tonumber(n:match("^D1|(%d+)"))or I)&4095 for d,b,m,l,g in n:gmatch("\n([bayo])(%d+)|(%d+)|(%d+)|([^\n]+)")do b,m,l=tonumber(b),tonumber(m),tonumber(l)if#y<aq and m>=1 and m<=12 and l>=1 and l<=al(m,b)and(b>0 or d~="o")then y[#y+1]={n=g:sub(1,aA),k=d,y=b,m=m,d=l}end end end local function au(a,c)local i,k=E()if not i then for K,f in ipairs(y)do if f==a then s=K end end return end for K,A in ipairs(aL(i,k))do if(a and A.e==a)or(c and A.h==c)then s=K end end end local aT="!$%&()+,-0123456789:;<=>?ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnpqrstuvwxyz|~"local function ao(c)return aT:sub(c+1,c+1)end local ae={"1234567890<","QWERTYUIOP'","ASDFGHJKL-+","^ZXCVBNM,./","()[_____]?>"}local aW="'-+,./()?"local r local Q,R,M=1,2,1 local function T(f,b)return ae[b]:sub(f,f)end local function aJ(n)r={s=n,at=#n,back=p}M=n==""and 1 or 0 p="kb"end local function aH(a,ah)local X,k=E()local b=a.y>0 and a.y or(k or 2026)h={d=a.d,m=a.m,c=a.y>0 and b//100 or 0,yy=b%100,k=a.k,back=ah}p,H="date",1 end local function aF()local ah=r.back local n=(r.s:gsub("^%s+",""):gsub("%s+$",""))p,r=ah,nil if ah=="event"then if n~=""then B.n=n ab()end elseif n~=""then U.n=n aH(U,"kind")end end local function bi()local e=T(Q,R)local n,J=r.s,r.at if e=="<"then if J>0 then r.s,r.at=n:sub(1,J-1)..n:sub(J+1),J-1 end elseif e=="^"then M=(M+1)%3 elseif e=="["then r.at=math.max(0,J-1)elseif e=="]"then r.at=math.min(#n,J+1)elseif e==">"then aF()elseif#n>=aA then v="That's as long as it can be"else if e=="_"then e=" "elseif M==0 then e=e:lower()end r.s,r.at=n:sub(1,J)..e..n:sub(J+1),J+1 if M==1 and e:match("%a")then M=0 end end end local function bb(d)if d=="top"then aF()elseif d=="ok"then bi()elseif d=="left"or d=="right"then local l,e=d=="left"and-1 or 1,T(Q,R)local g repeat Q=(Q-1+l)%11+1 g=T(Q,R)until e~="_"or g~="_"elseif d=="up"or d=="down"then R=(R-1+(d=="up"and-1 or 1))%#ae+1 end end local function bc()local D,bf={},T(Q,R)=="_"for b=1,#ae do local j={}for f=1,11 do local e=ae[b]:sub(f,f)local aj=b==R and(f==Q or(bf and e=="_"))local t,O,c=aj and 1 or 0,aW:find(e,1,true)if e:match("%u")then c=aj and e:byte()-65 elseif e:match("%d")then c=aj and 26+e:byte()-48 elseif O then c=(aj and 35+O)or(e=="/"and 45)elseif e=="<"then c=46+t elseif e=="^"then c=48+M*2+t elseif e=="["then c=54+t elseif e=="]"then c=56+t elseif e=="_"then c=58+t*3+(T(f-1,b)~="_"and 0 or T(f+1,b)~="_"and 2 or 1)else c=64+t end j[f]=c and ao(c)or("{"..e.."}")end D[b]=table.concat(j)end return table.concat(D,"/")end local function aa()return h.c==0 and 0 or h.c*100+h.yy end local function a0(d)if d=="left"or d=="right"then repeat H=(H-1+(d=="left"and-1 or 1))%5+1 until H<=3 or h.c>0 elseif d=="up"or d=="down"then local l=d=="up"and 1 or-1 if H==1 then h.d=(h.d-1+l)%al(h.m,aa())+1 elseif H==2 then h.m=(h.m-1+l)%12+1 elseif H==3 then local ar=h.k=="o"and{19,20}or{0,19,20}local K=1 for c,e in ipairs(ar)do if e==h.c then K=c end end h.c=ar[(K-1+l)%#ar+1]elseif H==4 then h.yy=((h.yy//10+l)%10)*10+h.yy%10 else h.yy=h.yy-h.yy%10+(h.yy%10+l)%10 end h.d=math.min(h.d,al(h.m,aa()))elseif d=="top"then p=h.back=="kind"and"main"or"event"if h.back=="kind"then v="Not added"end elseif d=="ok"then local a=h.back=="kind"and U or B a.y,a.m,a.d=aa(),h.m,h.d if h.back=="kind"then y[#y+1]=a p="main"local i,k=E()v=a.n..(i and": "..av(an(a,i,k)-i):lower()or" added")else p,v="event","Date saved"end au(a)ab()end end local ad={"name","date","kind","remove"}local function aD()local j={}if#y<aq then j[1]="add"end if E()then j[#j+1]="hols"end return j end local function aO()local i,k=E()if not i then local j={}for c,a in ipairs(y)do j[c]={n=a.n,e=a}end return j end return aL(i,k)end local function be(d)local A,aC=aO(),aD()local g=#A+#aC s=a_(s,1,g)if d=="up"then s=(s-2)%g+1 elseif d=="down"then s=s%g+1 elseif d=="left"then s=math.max(1,s-8)elseif d=="right"then s=math.min(g,s+8)elseif d=="ok"then local o=A[s]if o and o.e then p,B,C="event",o.e,1 elseif o then p,x="hols",o.h elseif aC[s-#A]=="add"then p,L="kind",1 else p,x="hols",1 end end end local function a8(d,q)local a=B local F=ad[C]if d=="up"then C=(C-2)%#ad+1 elseif d=="down"then C=C%#ad+1 elseif d=="top"then p="main"au(a)elseif F=="kind"and(d=="ok"or d=="left"or d=="right")then local K=1 for c,e in ipairs(S)do if e==a.k then K=c end end a.k=S[(K-1+(d=="left"and-1 or 1))%4+1]if a.k=="o"and a.y==0 then local X,k=E()a.y=k or 2026 a.d=math.min(a.d,al(a.m,a.y))v="A one-off needs a year: "..a.y end ab()elseif d=="ok"then if F=="name"then aJ(a.n)elseif F=="date"then aH(a,"event")elseif q~="remove"then ag="remove"else for c,f in ipairs(y)do if f==a then table.remove(y,c);break end end v,p,B=a.n.." removed","main",nil ab()end end end local function ba(d)local g=#w+1 if d=="up"then x=(x-2)%g+1 elseif d=="down"then x=x%g+1 elseif d=="top"then p="main"if w[x]and ac(x)then au(nil,x)end elseif d=="ok"then if w[x]then I=I~(1<<(x-1))else I=I==4095 and 0 or 4095 end ab()end end function key(d)if p=="kb"then v=nil bb(d)return end if p=="main"and d=="top"then return end v=nil local q=ag ag=nil if p=="main"then be(d)elseif p=="kind"then if d=="up"then L=(L-2)%4+1 elseif d=="down"then L=L%4+1 elseif d=="top"then p="main"elseif d=="ok"then local i,k=E()local b,m,l=0,1,1 if i then b,m,l=aw(i)end U={n="",k=S[L],y=S[L]=="o"and(i and b or 2026)or 0,m=m,d=l}aJ("")end elseif p=="date"then a0(d)elseif p=="event"then a8(d,q)else ba(d)end end function tick()end function start()bj()snail.tick(60000)end local function D(j,B,at)local aS=math.max(1,math.min(B-at//2,#j-at+1))for c=aS,math.min(#j,aS+at-1)do snail.row(j[c],c==B)end end local function aE(n)snail.center(true)snail.small(n)snail.center(false)end local aU={["0"]="####.##.##.####",["1"]=".#.##..#..#.###",["2"]="###..#####..###",["3"]="###..####..####",["4"]="#.##.####..#..#",["5"]="####..###..####",["6"]="####..####.####",["7"]="###..#..#..#..#",["8"]="####.#####.####",["9"]="####.####..####",T="###.#..#..#..#.",O="####.##.##.####",D="##.#.##.##.##.",A=".#.#.####.##.#.",Y="#.##.#.#..#..#.",}local aB=19 local function aY(n)local D={}for b=1,5 do local u={}for c=1,#n do u[c]=aU[n:sub(c,c)]:sub(b*3-2,b*3)end D[b]=table.concat(u,".")end local F=#D[1]local O=math.max(2,(aB-F)//2)for b=1,5 do D[b]=("."):rep(O)..D[b]..("."):rep(math.max(2,aB-F-O))end return table.concat(D,"/")end local aX={{1,2},{4,6},{8,9},{10,10},{11,11}}local function a1()local b=aa()local n=string.format("%02d",h.d).."."..W[h.m]:sub(1,3):upper().."."..(b>0 and string.format("%04d",b)or"----")local o,j=aX[H],{}for c=1,11 do local e=n:sub(c,c)if c<o[1]or c>o[2]then j[c]=e=="."and"."or"{"..e.."}"elseif e:match("%u")then j[c]=ao(e:byte()-65)elseif e:match("%d")then j[c]=ao(26+e:byte()-48)else j[c]=ao(37)end end return table.concat(j)end local function a5()snail.title(r.back=="event"and"Rename "..B.n or"New "..V[U.k]:lower())snail.status(v or(M==2 and"CAPS"or M==1 and"Shift"or""))snail.text(r.s:sub(1,r.at).."|"..r.s:sub(r.at+1))snail.small("Up to 16 letters, like Mum, Our wedding or Trip to Rome.")snail.board(bc())snail.hint("arrows move  BACK done  OK press")end local aV={"the day","the month","the century","the year","the year"}local function a2()local a=h.back=="kind"and U or B local i,k=E()local b=aa()snail.title(a.n)snail.status(v or V[h.k]..": UP/DOWN change "..aV[H])snail.board(a1())local bl=b>0 and ax[(N(b,h.m,h.d)+4)%7+1].." "or""snail.text(bl..h.d.." "..W[h.m]..(b>0 and" "..b or", every year"))if i then local O={k=h.k,y=b,m=h.m,d=h.d}local f,bh=an(O,i,k)local z=af(O,bh)snail.small(av(f-i)..(z and", "..z or"")..(f>i and": ".._(f,k)or""))end if h.k~="o"then snail.small("Leave the year as ---- if you don't know it.")end snail.hint("BACK cancel  OK save  L/R field  UP/DN change")end local function a3()local a=B local i,k=E()snail.title(a.n)local F=ad[C]if i then local f,b=an(a,i,k)local z=af(a,b)snail.status(v or av(f-i))snail.small(_(f,k)..(z and", "..z or""))else snail.status(v or V[a.k])end snail.row("Name: "..a.n,C==1)snail.row("Date: "..aI(a),C==2)snail.row("Type: "..V[a.k],C==3)snail.row(ag and"Press OK again to remove "..a.n or"Remove",C==4)snail.small(ay[a.k])snail.hint(F=="kind"and"BACK list  OK change  L/R change  UP/DN move"or F=="remove"and"BACK list  OK twice to remove  UP/DN move"or"BACK list  OK change  UP/DN move")end local function a4()local i,k=E()i,k=i or 0,k or 2026 local e=0 for c=1,#w do if ac(c)then e=e+1 end end snail.title("Holidays")snail.status(v or e.." of "..#w.." shown")local u={}for c,t in ipairs(w)do local f=ak(c,k)if not f or f<i then f=ak(c,k+1)end local X,m,l=aw(f or i)u[c]=(ac(c)and"[x]  "or"[  ]  ")..t[1]..(f and"   "..l.." "..W[m]:sub(1,3)or"")end u[#u+1]=e==#w and"Hide them all"or"Show them all"D(u,x,13)snail.small(w[x]and w[x][4]or"Your own dates stay either way.")snail.hint("BACK list  OK show/hide  UP/DN move")end local function a6()snail.title("Add a date")snail.status(v or#y.." of "..aq.." dates")for c,d in ipairs(S)do snail.row(V[d],c==L)end snail.small(ay[S[L]])snail.hint("BACK list  OK choose  UP/DN move")end local function aZ(o,g)if g==0 then return o.n.." is today"end return(math.abs(g)==1 and"day "or"days ")..(g>0 and"until "or"since ")..o.n end function draw()if p=="kb"then return a5()end if p=="date"then return a2()end if p=="event"then return a3()end if p=="hols"then return a4()end if p=="kind"then return a6()end local i,k=E()local A,u=aO(),{}snail.title("Days Until")if not i then snail.status("The clock isn't set")snail.text("Days Until needs today's date. Connect to Wi-Fi once so the clock gets set, then come back.")for c,o in ipairs(A)do u[c]=o.n.." - "..aI(o.e)end else snail.status(v or _(i,0))local o=A[s]or A[1]if o then local g=o.x-i snail.board(aY(g==0 and"TODAY"or tostring(math.abs(g))))aE(aZ(o,g))local z=af(o.e,o.y)aE(g==0 and(z and z:sub(1,1):upper()..z:sub(2)or _(o.x,k))or _(o.x,k)..(z and", "..z or""))else snail.text("Nothing to count down to yet.")snail.small("Add birthdays, anniversaries, trips and other dates, or show some holidays.")end for c,f in ipairs(A)do local z=f.e and f.e.k=="b"and af(f.e,f.y)u[c]=aG(f.x-i).."   "..f.n..(z and" ("..(f.y-f.e.y)..")"or"")end end local e=0 for c=1,#w do if ac(c)then e=e+1 end end for X,F in ipairs(aD())do u[#u+1]=F=="add"and"+ Add a date"or"Holidays: "..e.." of "..#w.." shown"end D(u,s,i and 9 or 12)local o=A[s]snail.hint(o and o.e and"BACK leave  OK edit  L/R page  UP/DN move"or o and"BACK leave  OK holidays  L/R page  UP/DN move"or"BACK leave  OK choose  L/R page  UP/DN move")end
