-
Notifications
You must be signed in to change notification settings - Fork 0
/
calendar.jl
305 lines (228 loc) · 8.32 KB
/
calendar.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
abstract type BusinessCalendar end
abstract type WesternCalendar <: BusinessCalendar end
# const GregorianCalendar = WesternCalendar
abstract type OrthodoxCalendar <: BusinessCalendar end
# const EasternCalendar = OrthodoxCalendar
abstract type MarketCalendar end
include("utils.jl")
include("argentina.jl")
include("brazil.jl")
include("unitedstates.jl")
include("target.jl")
"""
isbusinessday(dt::Date, calendar::BusinessCalendar) -> Bool
Provides methods for determining whether a date `dt` is or not a business day. A `calendar`,
defined for specific exchange holiday schedule or for general country holiday schedule, must
be provided.
"""
function isbusinessday end
"""
isholiday(dt::Date, calendar::BusinessCalendar) -> Bool
Provides methods for determining whether a date `dt` is or not a holiday. A `calendar`,
defined for specific exchange holiday schedule or for general country holiday schedule, must
be provided.
"""
isholiday(dt::Date, calendar::BusinessCalendar) = !isbusinessday(dt, calendar)
"""
BusinessDayConvention
Supertype for elements that provide algorithms used to adjust a date in case it is not a
valid business day.
"""
abstract type BusinessDayConvention end
"""
Unadjusted
A [`BusinessDayConvention`](@ref) subtype that provides no date adjustment.
"""
struct Unadjusted <: BusinessDayConvention end
"""
Following
A [`BusinessDayConvention`](@ref) subtype that chooses the first business day after the
given holiday.
"""
struct Following <: BusinessDayConvention end
"""
ModifiedFollowing
A [`BusinessDayConvention`](@ref) subtype that chooses the first business day after the
given holiday unless it belongs to a different month, in which case choose the first
business day before the holiday.
"""
struct ModifiedFollowing <: BusinessDayConvention end
"""
Preceding
A [`BusinessDayConvention`](@ref) subtype that chooses the first business day before the
given holiday.
"""
struct Preceding <: BusinessDayConvention end
"""
ModifiedPreceding
A [`BusinessDayConvention`](@ref) subtype that chooses the first business day before the
given holiday unless it belongs to a different month, in which case choose the first
business day after the holiday.
"""
struct ModifiedPreceding <: BusinessDayConvention end
"""
HalfMonthModifiedFollowing
A [`BusinessDayConvention`](@ref) subtype that chooses the first business day after the
given holiday unless that day crosses the mid-month (15th) or the end of month, in which
case choose the first business day before the holiday.
"""
struct HalfMonthModifiedFollowing <: BusinessDayConvention end
"""
Nearest
A [`BusinessDayConvention`](@ref) subtype that chooses the nearest business day to the given
holiday. If both the preceding and following business days are equally far away, default to
following business day.
"""
struct Nearest <: BusinessDayConvention end
"""
adjustdate(dt::Date, calendar::BusinessCalendar, [convention::BusinessDayConvention = Following()]) -> Date
Adjusts a non-business day to the appropriate near business day with respect to the given
convention. The convention defaults to [`Following`](@ref) if not provided.
"""
function adjustdate end
# default
adjustdate(dt::Date, calendar::BusinessCalendar, ::BusinessDayConvention) =
adjustdate(dt, calendar, Following())
adjustdate(dt::Date, ::BusinessCalendar, ::Unadjusted) = dt
function adjustdate(dt::Date, calendar::BusinessCalendar, ::Following)
# loop until a "following" business day is reached
while isholiday(dt, calendar)
dt += Day(1)
end
return dt
end
function adjustdate(dt::Date, calendar::BusinessCalendar, ::Preceding)
# loop until a "preceding" business day is reached
while isholiday(dt, calendar)
dt -= Day(1)
end
return dt
end
function adjustdate(dt::Date, calendar::BusinessCalendar, ::ModifiedFollowing)
# get the following business day
dt′ = adjustdate(dt, calendar, Following())
# si cambia el mes, buscamos para atras
if month(dt′) != month(dt)
return adjustdate(dt, calendar, Preceding())
else
return dt′
end
end
function adjustdate(dt::Date, calendar::BusinessCalendar, ::ModifiedPreceding)
# get the following business day
dt′ = adjustdate(dt, calendar, Preceding())
# si cambia el mes, buscamos para adelante
if month(dt′) != month(dt)
return adjustdate(dt, calendar, Following())
else
return dt′
end
end
function adjustdate(dt::Date, calendar::BusinessCalendar, ::HalfMonthModifiedFollowing)
# get the following business day
dt′ = adjustdate(dt, calendar, Following())
# si cambia el mes o pasamos de mitad de mes, buscamos para atras
if month(dt′) != month(dt) || (dayofmonth(dt) <= 15 && dayofmonth(dt′) > 15)
return adjustdate(dt, calendar, Preceding())
else
return dt′
end
end
function adjustdate(dt::Date, calendar::BusinessCalendar, ::Nearest)
dt1 = dt2 = dt
# loop until the first holiday is found in any direction
while isholiday(dt1, calendar) && isholiday(dt2, calendar)
dt1 += Day(1)
dt2 -= Day(1)
end
# defaults to dt1 if both dt1 and dt2 are business days
return isholiday(dt1, calendar) ? dt2 : dt1
end
"""
advance(dt::Date, step::DatePeriod, calendar::BusinessCalendar, [convention::BusinessDayConvention = Following()])
Advances the given date `dt`, `step` number of days and returns the result. `step` can be
either a [`DatePeriod`](@ref) or a [`BusinessDatePeriod`](@ref). In the first case,
the date advances using calendar days and if it falls in a holiday (i.e., a non business
day), it is adjusted using the `convention`. On the second case, the date advances using
business days, so it is expected that `step` can be converted to [`BusinessDay`](@ref). It
is important to note that if the provided step is either `0 days` or `0 businessdays` and
`dt` is a holiday, it will be adjusted using the `convention`.
```jldoctest; setup = :(using DayCounters; using Dates)
julia> dt = Date(2001, 12, 26)
2001-12-26
julia> advance(dt, Day(4), ArgentinaCalendar())
2002-01-02
julia> advance(dt, BusinessDay(4), ArgentinaCalendar())
2002-01-03
julia> dt = Date(2002, 1, 1)
2002-01-01
julia> advance(dt, Day(0), ArgentinaCalendar())
2002-01-02
julia> advance(dt, BusinessDay(0), ArgentinaCalendar())
2002-01-02
```
"""
function advance end
# default
advance(dt::Date, step::Period, calendar::BusinessCalendar, convention::BusinessDayConvention=Following()) =
advance(dt, step, calendar, convention)
function advance(
dt::Date, step::BusinessDatePeriod, calendar::BusinessCalendar, convention::BusinessDayConvention
)
# business days to advance
n = Dates.value(convert(BusinessDay, step))
if iszero(n)
# si me pasan 0 businessday, ajusto
return adjustdate(dt, calendar, convention)
end
# para donde nos movemos?
direction = n > 0 ? 1 : -1
while !iszero(n)
# me muevo un dia
dt += Day(direction)
# pero si es holiday, me sigo moviendo hasta llegar a un business
while isholiday(dt, calendar)
dt += Day(direction)
end
# proximo caso
n -= direction
end
return dt
end
function advance(
date::Date, dt::DatePeriod, calendar::BusinessCalendar, convention::BusinessDayConvention
)
# advance calendar days and return the adjusted date
return adjustdate(date + dt, calendar, convention)
end
# esta no se si exportarla... porque es semejante a daycount (podria tener el tema de cache)
function businessdaysbetween(
from::Date,
to::Date,
calendar::BusinessCalendar,
includefirst::Bool = true,
includelast::Bool = false
)
bd = 0
if from != to
# para donde nos movemos?
direction = from < to ? 1 : -1
# contamos incluyendo extremos
bd = sum(isbusinessday(x, calendar) for x = from:Day(direction):to)
# eliminamos primer extremo si corresponde
if isbusinessday(from, calendar) && !includefirst
bd -= 1
end
# eliminamos segundo extremo si corresponde
if isbusinessday(to, calendar) && !includelast
bd -= 1
end
# cambiamos el signo si corresponde
if (from > to)
bd = -bd
end
elseif includefirst && includelast && isbusinessday(from, calendar)
bd = 1
end
return bd
end