0 | module IotaTime.Calendar.Julian
2 | import IotaTime.Internal.ApplyPeriod
3 | import IotaTime.Calendar
4 | import IotaTime.Period
6 | import Derive.Prelude
8 | %language ElabReflection
14 | data Julian = JulianCalendar
16 | namespace JulianMonths
19 | = January | February | March | April | May | June
20 | | July | August | September | October | November | December
23 | monthNumber : JulianMonth -> Integer
24 | monthNumber January = 1
25 | monthNumber February = 2
26 | monthNumber March = 3
27 | monthNumber April = 4
29 | monthNumber June = 6
30 | monthNumber July = 7
31 | monthNumber August = 8
32 | monthNumber September = 9
33 | monthNumber October = 10
34 | monthNumber November = 11
35 | monthNumber December = 12
38 | Eq JulianMonth where
39 | left == right = monthNumber left == monthNumber right
42 | Ord JulianMonth where
43 | compare left right = compare (monthNumber left) (monthNumber right)
45 | %runElab derive `{JulianMonth
} [Show]
47 | monthFromNumber : Integer -> JulianMonth
48 | monthFromNumber 1 = JulianMonths.January
49 | monthFromNumber 2 = JulianMonths.February
50 | monthFromNumber 3 = JulianMonths.March
51 | monthFromNumber 4 = JulianMonths.April
52 | monthFromNumber 5 = JulianMonths.May
53 | monthFromNumber 6 = JulianMonths.June
54 | monthFromNumber 7 = JulianMonths.July
55 | monthFromNumber 8 = JulianMonths.August
56 | monthFromNumber 9 = JulianMonths.September
57 | monthFromNumber 10 = JulianMonths.October
58 | monthFromNumber 11 = JulianMonths.November
59 | monthFromNumber _ = JulianMonths.December
62 | record JulianDate where
63 | constructor MkJulianDate
64 | daysSinceEpoch : Integer
65 | 0 validDays : So (daysSinceEpoch >= -746631)
69 | left == right = left.daysSinceEpoch == right.daysSinceEpoch
72 | Ord JulianDate where
73 | compare left right = compare left.daysSinceEpoch right.daysSinceEpoch
77 | isLeapYear : Year -> Bool
78 | isLeapYear value = yearValue value `mod` 4 == 0
81 | maxDaysInMonth : JulianMonth -> Year -> DayOfMonth
82 | maxDaysInMonth JulianMonths.February value = if isLeapYear value then 29 else 28
83 | maxDaysInMonth JulianMonths.April _ = 30
84 | maxDaysInMonth JulianMonths.June _ = 30
85 | maxDaysInMonth JulianMonths.September _ = 30
86 | maxDaysInMonth JulianMonths.November _ = 30
87 | maxDaysInMonth _ _ = 31
89 | daysFromJulianCivil : Year -> JulianMonth -> DayOfMonth -> Integer
90 | daysFromJulianCivil valueYear valueMonth valueDay =
91 | let number = JulianMonths.monthNumber valueMonth
92 | shiftedYear = yearValue valueYear - if number <= 2 then 1 else 0
93 | relativeYear = shiftedYear - 2000
94 | shiftedMonth = number + if number > 2 then -
3 else 9
95 | dayOfYear = (153 * shiftedMonth + 2) `div` 5 + dayOfMonthValue valueDay - 1
96 | in relativeYear * 365 + relativeYear `div` 4 + dayOfYear
98 | julianCivilFromDays : Integer -> (Year, JulianMonth, DayOfMonth)
99 | julianCivilFromDays value =
101 | era = value `div` 1461
102 | dayOfEra = value - era * 1461
103 | yearOfEra : Integer
104 | yearOfEra = min 3 (dayOfEra `div` 365)
105 | dayOfYear = dayOfEra - yearOfEra * 365
106 | shiftedMonth = (5 * dayOfYear + 2) `div` 153
107 | dayNumber = dayOfYear - (153 * shiftedMonth + 2) `div` 5 + 1
108 | monthNumber = shiftedMonth + if shiftedMonth < 10 then 3 else -
9
109 | yearNumber = 2000 + era * 4 + yearOfEra + if monthNumber <= 2 then 1 else 0
110 | in (yearFromInteger yearNumber, monthFromNumber monthNumber,
111 | dayOfMonthFromInteger dayNumber)
118 | checkedJulianDate : (days : Integer) ->
119 | (0 valid : So (days >= -
746631)) -> JulianDate
120 | checkedJulianDate days valid = MkJulianDate days valid
123 | HasCalendarBridge JulianDate where
124 | toBridgeDays date = date.daysSinceEpoch + 13
125 | acceptsBridgeDays days = days - 13 >= epochDay
126 | fromBridgeDays days @{valid} = checkedJulianDate (days - 13) valid
127 | bridgeCalendarName = "Julian"
130 | isValidDate : DayOfMonth -> JulianMonth -> Year -> Bool
131 | isValidDate valueDay valueMonth valueYear =
132 | dayOfMonthValue valueDay <= dayOfMonthValue (maxDaysInMonth valueMonth valueYear) &&
133 | yearValue valueYear >= -
44
135 | clampToJulian : Integer -> Integer
136 | clampToJulian = max epochDay
138 | makeJulianDate : Integer -> JulianDate
139 | makeJulianDate days =
140 | let clamped = clampToJulian days
141 | in case choose (clamped >= -
746631) of
142 | Left valid => checkedJulianDate clamped valid
143 | Right _ => checkedJulianDate epochDay Oh
145 | shiftJulianDays : Integer -> JulianDate -> JulianDate
146 | shiftJulianDays amount date = makeJulianDate (date.daysSinceEpoch + amount)
148 | shiftJulianMonths : Integer -> JulianDate -> JulianDate
149 | shiftJulianMonths amount date =
150 | let (valueYear, valueMonth, valueDay) = julianCivilFromDays date.daysSinceEpoch
151 | monthOrdinal = JulianMonths.monthNumber valueMonth - 1 + amount
152 | targetYear = yearFromInteger (yearValue valueYear + monthOrdinal `div` 12)
153 | targetMonth = monthFromNumber (monthOrdinal `mod` 12 + 1)
154 | targetDay = min valueDay (maxDaysInMonth targetMonth targetYear)
155 | in makeJulianDate (daysFromJulianCivil targetYear targetMonth targetDay)
157 | shiftJulianYears : Integer -> JulianDate -> JulianDate
158 | shiftJulianYears amount date =
159 | let (valueYear, valueMonth, valueDay) = julianCivilFromDays date.daysSinceEpoch
160 | targetYear = yearFromInteger (yearValue valueYear + amount)
161 | targetDay = min valueDay (maxDaysInMonth valueMonth targetYear)
162 | in makeJulianDate (daysFromJulianCivil targetYear valueMonth targetDay)
164 | applyJulianPeriod : Period target -> JulianDate -> JulianDate
165 | applyJulianPeriod = applyDatePeriodWith
166 | shiftJulianYears shiftJulianMonths shiftJulianDays
168 | julianDayOfWeek : JulianDate -> DayOfWeek
169 | julianDayOfWeek date = weekdayFromNumber (date.daysSinceEpoch + 2)
171 | nextJulian : Integer -> DayOfWeek -> JulianDate -> JulianDate
172 | nextJulian count target date =
173 | makeJulianDate (date.daysSinceEpoch +
174 | nextWeekdayOffset count (julianDayOfWeek date) target)
176 | previousJulian : Integer -> DayOfWeek -> JulianDate -> JulianDate
177 | previousJulian count target date =
178 | makeJulianDate (date.daysSinceEpoch +
179 | previousWeekdayOffset count (julianDayOfWeek date) target)
182 | Calendar Julian where
183 | DateRep = JulianDate
184 | MonthRep _ = JulianMonth
186 | isValidDays = (>= epochDay)
187 | fromDays days @{valid} = checkedJulianDate days valid
188 | toDaysFor date = date.daysSinceEpoch
189 | toDaysValid (MkJulianDate _ valid) = valid
190 | toFromDays _ _ = Refl
191 | fromToDays (MkJulianDate _ _) = Refl
192 | calendarName = "Julian"
194 | year' date = let (value, _, _) = julianCivilFromDays date.daysSinceEpoch in value
195 | toYmd date = let (_, valueMonth, valueDay) = julianCivilFromDays date.daysSinceEpoch
196 | in (valueMonth, valueDay)
197 | day' date = let (_, _, value) = julianCivilFromDays date.daysSinceEpoch in value
198 | month' date = let (_, value, _) = julianCivilFromDays date.daysSinceEpoch in value
200 | applyCalendarPeriod' = applyJulianPeriod
201 | shiftCalendarDays' = shiftJulianDays
203 | dayOfWeekFor = julianDayOfWeek
204 | nextFor = nextJulian
205 | previousFor = previousJulian
208 | Show JulianDate where
209 | show date = case julianCivilFromDays date.daysSinceEpoch of
210 | (valueYear, valueMonth, valueDay) =>
211 | "calendarDate " ++ show valueDay ++ " " ++
212 | show valueMonth ++ " " ++ show valueYear
215 | HasCalendar JulianDate where
216 | calendarCapability = ()
219 | PeriodTarget JulianDate where
223 | ApplyPeriod JulianDate where
224 | applyPeriod = applyJulianPeriod
227 | CalendarValue JulianDate where
228 | CalendarMonth _ = JulianMonth
229 | calendarValueToDays = toDaysFor {calendar = Julian}
230 | calendarValueYear = yearFor {calendar = Julian}
231 | calendarValueMonthDay = toYmd {calendar = Julian}
232 | calendarValueDayOfWeek = dayOfWeekFor {calendar = Julian}
233 | calendarValueBetweenWith = betweenWithFor {calendar = Julian}
236 | CalendarNavigation JulianDate where
237 | calendarValueNext = nextFor {calendar = Julian}
238 | calendarValuePrevious = previousFor {calendar = Julian}
242 | calendarDate : (valueDay : DayOfMonth) -> (valueMonth : JulianMonth) ->
243 | (valueYear : Year) ->
244 | {auto 0 valid : So (isValidDate valueDay valueMonth valueYear)} ->
245 | CalendarDate Julian
246 | calendarDate valueDay valueMonth valueYear =
247 | makeJulianDate (daysFromJulianCivil valueYear valueMonth valueDay)
251 | data JulianDateError
252 | = InvalidJulianDate DayOfMonth JulianMonth Year
253 | | InvalidJulianDayCount Integer
254 | | InvalidJulianNthDay DayNth DayOfWeek JulianMonth Year
255 | | InvalidJulianWeekDate WeekNumber DayOfWeek Year
259 | refineDate : DayOfMonth -> JulianMonth -> Year ->
260 | Either JulianDateError (CalendarDate Julian)
261 | refineDate valueDay valueMonth valueYear =
262 | case choose (isValidDate valueDay valueMonth valueYear) of
263 | Left valid => Right (calendarDate valueDay valueMonth valueYear @{valid})
264 | Right _ => Left (InvalidJulianDate valueDay valueMonth valueYear)
268 | fromDays : (days : Integer) -> {auto 0 valid : So
269 | (IotaTime.Calendar.isValidDays {calendar = Julian} days)} ->
270 | CalendarDate Julian
271 | fromDays days @{valid} = checkedJulianDate days valid
275 | refineDays : Integer -> Either JulianDateError (CalendarDate Julian)
276 | refineDays days = case choose
277 | (IotaTime.Calendar.isValidDays {calendar = Julian} days) of
278 | Left valid => Right (fromDays days @{valid})
279 | Right _ => Left (InvalidJulianDayCount days)
281 | nthJulianDayOfMonth : DayNth -> DayOfWeek -> JulianMonth -> Year -> DayOfMonth
282 | nthJulianDayOfMonth nth target valueMonth valueYear =
283 | let monthLength = maxDaysInMonth valueMonth valueYear
284 | firstDate = makeJulianDate (daysFromJulianCivil valueYear valueMonth 1)
286 | (weekdayNumber target - weekdayNumber (julianDayOfWeek firstDate))
288 | lastDate = makeJulianDate (daysFromJulianCivil valueYear valueMonth monthLength)
290 | (weekdayNumber (julianDayOfWeek lastDate) - weekdayNumber target)
292 | dayNumber = nthWeekdayDayNumber nth (dayOfMonthValue monthLength)
293 | firstOffset lastOffset
294 | in dayOfMonthFromInteger dayNumber
297 | isValidNthDay : DayNth -> DayOfWeek -> JulianMonth -> Year -> Bool
298 | isValidNthDay nth target valueMonth valueYear =
299 | if yearValue valueYear > -
44
301 | Fifth => nthJulianDayOfMonth nth target valueMonth valueYear <=
302 | maxDaysInMonth valueMonth valueYear
305 | (nthJulianDayOfMonth nth target valueMonth valueYear) valueMonth valueYear
309 | fromNthDay : (nth : DayNth) -> (target : DayOfWeek) ->
310 | (valueMonth : JulianMonth) -> (valueYear : Year) ->
312 | (isValidNthDay nth target valueMonth valueYear)} ->
313 | CalendarDate Julian
314 | fromNthDay nth target valueMonth valueYear =
316 | (daysFromJulianCivil valueYear valueMonth
317 | (nthJulianDayOfMonth nth target valueMonth valueYear))
321 | refineNthDay : DayNth -> DayOfWeek -> JulianMonth -> Year ->
322 | Either JulianDateError (CalendarDate Julian)
323 | refineNthDay nth target valueMonth valueYear =
324 | case choose (isValidNthDay nth target valueMonth valueYear) of
325 | Left valid => Right (fromNthDay nth target valueMonth valueYear @{valid})
326 | Right _ => Left (InvalidJulianNthDay nth target valueMonth valueYear)
328 | weekDateDays : WeekNumber -> DayOfWeek -> Year -> Integer
329 | weekDateDays week target valueYear =
330 | let firstDay = daysFromJulianCivil valueYear JulianMonths.January 1
331 | firstWeekStart = firstDay -
332 | weekdayNumber (julianDayOfWeek (makeJulianDate firstDay))
333 | in firstWeekStart + 7 * (weekNumberValue week - 1) +
334 | weekdayNumber target
337 | isValidWeekDate : WeekNumber -> DayOfWeek -> Year -> Bool
338 | isValidWeekDate week target valueYear =
339 | (yearValue valueYear > -
44 && weekNumberValue week >= 0) ||
340 | IotaTime.Calendar.isValidDays {calendar = Julian}
341 | (weekDateDays week target valueYear)
345 | fromWeekDate : (week : WeekNumber) -> (target : DayOfWeek) ->
346 | (valueYear : Year) ->
347 | {auto 0 valid : So (isValidWeekDate week target valueYear)} ->
348 | CalendarDate Julian
349 | fromWeekDate week target valueYear =
350 | makeJulianDate (weekDateDays week target valueYear)
354 | refineWeekDate : WeekNumber -> DayOfWeek -> Year ->
355 | Either JulianDateError (CalendarDate Julian)
356 | refineWeekDate week target valueYear =
357 | case choose (isValidWeekDate week target valueYear) of
358 | Left valid => Right (fromWeekDate week target valueYear @{valid})
359 | Right _ => Left (InvalidJulianWeekDate week target valueYear)