@@ -897,8 +897,14 @@ defmodule Calendar.ISO do
897897 @ doc false
898898 def date_from_iso_days ( days ) do
899899 { year , day_of_year } = days_to_year ( days )
900- extra_day = if leap_year? ( year ) , do: 1 , else: 0
901- { month , day_in_month } = year_day_to_year_date ( extra_day , day_of_year )
900+
901+ { month , day_in_month } =
902+ if leap_year? ( year ) do
903+ year_day_to_year_date_leap ( day_of_year )
904+ else
905+ year_day_to_year_date ( day_of_year )
906+ end
907+
902908 { year , month , day_in_month + 1 }
903909 end
904910
@@ -913,6 +919,9 @@ defmodule Calendar.ISO do
913919 end
914920 end
915921
922+ defp floor_div_positive_divisor ( int1 , int2 ) when int1 >= 0 , do: div ( int1 , int2 )
923+ defp floor_div_positive_divisor ( int1 , int2 ) , do: - div ( - int1 - 1 , int2 ) - 1
924+
916925 @ doc """
917926 Returns how many days there are in the given year-month.
918927
@@ -1848,7 +1857,7 @@ defmodule Calendar.ISO do
18481857 months_in_year = 12
18491858 total_months = year * months_in_year + month + months - 1
18501859
1851- new_year = Integer . floor_div ( total_months , months_in_year )
1860+ new_year = floor_div_positive_divisor ( total_months , months_in_year )
18521861
18531862 new_month =
18541863 case rem ( total_months , months_in_year ) + 1 do
@@ -1971,7 +1980,7 @@ defmodule Calendar.ISO do
19711980
19721981 if total in @ unix_range_microseconds do
19731982 microseconds = Integer . mod ( total , @ microseconds_per_second )
1974- seconds = @ unix_epoch + Integer . floor_div ( total , @ microseconds_per_second )
1983+ seconds = @ unix_epoch + floor_div_positive_divisor ( total , @ microseconds_per_second )
19751984 precision = precision_for_unit ( unit )
19761985 { date , time } = iso_seconds_to_datetime ( seconds )
19771986 { :ok , date , time , { microseconds , precision } }
@@ -2093,64 +2102,71 @@ defmodule Calendar.ISO do
20932102 end
20942103 end
20952104
2096- # Note that this function does not add the extra leap day for a leap year.
2097- # If you want to add that leap day when appropriate,
2098- # add the result of leap_day_offset/2 to the result of days_before_month/1.
2099- defp days_before_month ( 1 ) , do: 0
2100- defp days_before_month ( 2 ) , do: 31
2101- defp days_before_month ( 3 ) , do: 59
2102- defp days_before_month ( 4 ) , do: 90
2103- defp days_before_month ( 5 ) , do: 120
2104- defp days_before_month ( 6 ) , do: 151
2105- defp days_before_month ( 7 ) , do: 181
2106- defp days_before_month ( 8 ) , do: 212
2107- defp days_before_month ( 9 ) , do: 243
2108- defp days_before_month ( 10 ) , do: 273
2109- defp days_before_month ( 11 ) , do: 304
2110- defp days_before_month ( 12 ) , do: 334
2111-
21122105 defp leap_day_offset ( _year , month ) when month < 3 , do: 0
21132106
21142107 defp leap_day_offset ( year , _month ) do
21152108 if leap_year? ( year ) , do: 1 , else: 0
21162109 end
21172110
21182111 defp days_to_year ( days ) when days < 0 do
2119- year_estimate = - div ( - days , @ days_per_nonleap_year ) - 1
2120-
2121- { year , days_before_year } =
2122- days_to_year ( year_estimate , days , days_to_end_of_epoch ( year_estimate ) )
2112+ y_min = floor_div_positive_divisor ( days , @ days_per_nonleap_year )
2113+ y_max = floor_div_positive_divisor ( days , @ days_per_leap_year )
2114+
2115+ { year , day_start } =
2116+ days_to_year_interpolated (
2117+ y_min ,
2118+ y_max ,
2119+ days ,
2120+ days_in_previous_years ( y_min ) ,
2121+ days_in_previous_years ( y_max )
2122+ )
21232123
2124- leap_year_pad = if leap_year? ( year ) , do: 1 , else: 0
2125- { year , leap_year_pad + @ days_per_nonleap_year + days - days_before_year }
2124+ { year , days - day_start }
21262125 end
21272126
21282127 defp days_to_year ( days ) do
2129- year_estimate = div ( days , @ days_per_nonleap_year )
2130-
2131- { year , days_before_year } =
2132- days_to_year ( year_estimate , days , days_in_previous_years ( year_estimate ) )
2128+ y_min = floor_div_positive_divisor ( days , @ days_per_leap_year )
2129+ y_max = floor_div_positive_divisor ( days , @ days_per_nonleap_year )
2130+
2131+ { year , day_start } =
2132+ days_to_year_interpolated (
2133+ y_min ,
2134+ y_max ,
2135+ days ,
2136+ days_in_previous_years ( y_min ) ,
2137+ days_in_previous_years ( y_max )
2138+ )
21332139
2134- { year , days - days_before_year }
2140+ { year , days - day_start }
21352141 end
21362142
2137- defp days_to_year ( year , days1 , days2 ) when year < 0 and days1 >= days2 do
2138- days_to_year ( year + 1 , days1 , days_to_end_of_epoch ( year + 1 ) )
2143+ defp days_to_year_interpolated ( min , max , _days , d_min , _d_max ) when min >= max do
2144+ { min , d_min }
21392145 end
21402146
2141- defp days_to_year ( year , days1 , days2 ) when year >= 0 and days1 < days2 do
2142- days_to_year ( year - 1 , days1 , days_in_previous_years ( year - 1 ) )
2143- end
2147+ defp days_to_year_interpolated ( min , max , days , d_min , d_max ) do
2148+ diff = max - min
2149+ d_diff = d_max - d_min
21442150
2145- defp days_to_year ( year , _days1 , days2 ) do
2146- { year , days2 }
2147- end
2151+ numerator = diff * ( days - d_min )
2152+ offset = floor_div_positive_divisor ( numerator , d_diff )
21482153
2149- defp days_to_end_of_epoch ( year ) when year < 0 do
2150- previous_year = year + 1
2154+ mid = min + max ( 0 , min ( offset , diff ) )
2155+ d_mid = days_in_previous_years ( mid )
2156+ mid_length = if leap_year? ( mid ) , do: @ days_per_leap_year , else: @ days_per_nonleap_year
21512157
2152- div ( previous_year , 4 ) - div ( previous_year , 100 ) + div ( previous_year , 400 ) +
2153- previous_year * @ days_per_nonleap_year
2158+ cond do
2159+ days < d_mid ->
2160+ new_max = mid - 1
2161+ days_to_year_interpolated ( min , new_max , days , d_min , days_in_previous_years ( new_max ) )
2162+
2163+ days - d_mid >= mid_length ->
2164+ new_min = mid + 1
2165+ days_to_year_interpolated ( new_min , max , days , days_in_previous_years ( new_min ) , d_max )
2166+
2167+ true ->
2168+ { mid , d_mid }
2169+ end
21542170 end
21552171
21562172 defp days_in_previous_years ( 0 ) , do: 0
@@ -2174,54 +2190,48 @@ defmodule Calendar.ISO do
21742190 @ days_per_leap_year
21752191 end
21762192
2177- # Note that 0 is the first day of the month.
2178- defp year_day_to_year_date ( _extra_day , day_of_year ) when day_of_year < 31 do
2179- { 1 , day_of_year }
2180- end
2181-
2182- defp year_day_to_year_date ( extra_day , day_of_year ) when day_of_year < 59 + extra_day do
2183- { 2 , day_of_year - 31 }
2184- end
2185-
2186- defp year_day_to_year_date ( extra_day , day_of_year ) when day_of_year < 90 + extra_day do
2187- { 3 , day_of_year - ( 59 + extra_day ) }
2188- end
2189-
2190- defp year_day_to_year_date ( extra_day , day_of_year ) when day_of_year < 120 + extra_day do
2191- { 4 , day_of_year - ( 90 + extra_day ) }
2192- end
2193-
2194- defp year_day_to_year_date ( extra_day , day_of_year ) when day_of_year < 151 + extra_day do
2195- { 5 , day_of_year - ( 120 + extra_day ) }
2196- end
2197-
2198- defp year_day_to_year_date ( extra_day , day_of_year ) when day_of_year < 181 + extra_day do
2199- { 6 , day_of_year - ( 151 + extra_day ) }
2200- end
2201-
2202- defp year_day_to_year_date ( extra_day , day_of_year ) when day_of_year < 212 + extra_day do
2203- { 7 , day_of_year - ( 181 + extra_day ) }
2204- end
2205-
2206- defp year_day_to_year_date ( extra_day , day_of_year ) when day_of_year < 243 + extra_day do
2207- { 8 , day_of_year - ( 212 + extra_day ) }
2208- end
2209-
2210- defp year_day_to_year_date ( extra_day , day_of_year ) when day_of_year < 273 + extra_day do
2211- { 9 , day_of_year - ( 243 + extra_day ) }
2212- end
2213-
2214- defp year_day_to_year_date ( extra_day , day_of_year ) when day_of_year < 304 + extra_day do
2215- { 10 , day_of_year - ( 273 + extra_day ) }
2216- end
2217-
2218- defp year_day_to_year_date ( extra_day , day_of_year ) when day_of_year < 334 + extra_day do
2219- { 11 , day_of_year - ( 304 + extra_day ) }
2220- end
2193+ # Note that this function does not add the extra leap day for a leap year.
2194+ # If you want to add that leap day when appropriate,
2195+ # add the result of leap_day_offset/2 to the result of days_before_month/1.
2196+ defp days_before_month ( 1 ) , do: 0
2197+ defp days_before_month ( 2 ) , do: 31
2198+ defp days_before_month ( 3 ) , do: 59
2199+ defp days_before_month ( 4 ) , do: 90
2200+ defp days_before_month ( 5 ) , do: 120
2201+ defp days_before_month ( 6 ) , do: 151
2202+ defp days_before_month ( 7 ) , do: 181
2203+ defp days_before_month ( 8 ) , do: 212
2204+ defp days_before_month ( 9 ) , do: 243
2205+ defp days_before_month ( 10 ) , do: 273
2206+ defp days_before_month ( 11 ) , do: 304
2207+ defp days_before_month ( 12 ) , do: 334
22212208
2222- defp year_day_to_year_date ( extra_day , day_of_year ) do
2223- { 12 , day_of_year - ( 334 + extra_day ) }
2224- end
2209+ # Note that 0 is the first day of the month.
2210+ defp year_day_to_year_date ( day_of_year ) when day_of_year < 31 , do: { 1 , day_of_year }
2211+ defp year_day_to_year_date ( day_of_year ) when day_of_year < 59 , do: { 2 , day_of_year - 31 }
2212+ defp year_day_to_year_date ( day_of_year ) when day_of_year < 90 , do: { 3 , day_of_year - 59 }
2213+ defp year_day_to_year_date ( day_of_year ) when day_of_year < 120 , do: { 4 , day_of_year - 90 }
2214+ defp year_day_to_year_date ( day_of_year ) when day_of_year < 151 , do: { 5 , day_of_year - 120 }
2215+ defp year_day_to_year_date ( day_of_year ) when day_of_year < 181 , do: { 6 , day_of_year - 151 }
2216+ defp year_day_to_year_date ( day_of_year ) when day_of_year < 212 , do: { 7 , day_of_year - 181 }
2217+ defp year_day_to_year_date ( day_of_year ) when day_of_year < 243 , do: { 8 , day_of_year - 212 }
2218+ defp year_day_to_year_date ( day_of_year ) when day_of_year < 273 , do: { 9 , day_of_year - 243 }
2219+ defp year_day_to_year_date ( day_of_year ) when day_of_year < 304 , do: { 10 , day_of_year - 273 }
2220+ defp year_day_to_year_date ( day_of_year ) when day_of_year < 334 , do: { 11 , day_of_year - 304 }
2221+ defp year_day_to_year_date ( day_of_year ) , do: { 12 , day_of_year - 334 }
2222+
2223+ defp year_day_to_year_date_leap ( day_of_year ) when day_of_year < 31 , do: { 1 , day_of_year }
2224+ defp year_day_to_year_date_leap ( day_of_year ) when day_of_year < 60 , do: { 2 , day_of_year - 31 }
2225+ defp year_day_to_year_date_leap ( day_of_year ) when day_of_year < 91 , do: { 3 , day_of_year - 60 }
2226+ defp year_day_to_year_date_leap ( day_of_year ) when day_of_year < 121 , do: { 4 , day_of_year - 91 }
2227+ defp year_day_to_year_date_leap ( day_of_year ) when day_of_year < 152 , do: { 5 , day_of_year - 121 }
2228+ defp year_day_to_year_date_leap ( day_of_year ) when day_of_year < 182 , do: { 6 , day_of_year - 152 }
2229+ defp year_day_to_year_date_leap ( day_of_year ) when day_of_year < 213 , do: { 7 , day_of_year - 182 }
2230+ defp year_day_to_year_date_leap ( day_of_year ) when day_of_year < 244 , do: { 8 , day_of_year - 213 }
2231+ defp year_day_to_year_date_leap ( day_of_year ) when day_of_year < 274 , do: { 9 , day_of_year - 244 }
2232+ defp year_day_to_year_date_leap ( day_of_year ) when day_of_year < 305 , do: { 10 , day_of_year - 274 }
2233+ defp year_day_to_year_date_leap ( day_of_year ) when day_of_year < 335 , do: { 11 , day_of_year - 305 }
2234+ defp year_day_to_year_date_leap ( day_of_year ) , do: { 12 , day_of_year - 335 }
22252235
22262236 defp iso_seconds_to_datetime ( seconds ) do
22272237 { days , rest_seconds } = div_rem ( seconds , @ seconds_per_day )
0 commit comments