Would be nice to have this in the standard Elixir library, but we don't.
Date.add(date, n, :month) # where n could be +/-
How would you implement this?
This looks like a good starting point: https://stackoverflow.com/a/53407676/44080
Date.utc_today() |> Timex.shift(months: -1)
You could use the Timex implementation:
defp shift_by(%NaiveDateTime{:year => year, :month => month} = datetime, value, :months) do
m = month + value
shifted =
cond do
m > 0 ->
years = div(m - 1, 12)
month = rem(m - 1, 12) + 1
%{datetime | :year => year + years, :month => month}
m <= 0 ->
years = div(m, 12) - 1
month = 12 + rem(m, 12)
%{datetime | :year => year + years, :month => month}
end
# If the shift fails, it's because it's a high day number, and the month
# shifted to does not have that many days. This will be handled by always
# shifting to the last day of the month shifted to.
case :calendar.valid_date({shifted.year,shifted.month,shifted.day}) do
false ->
last_day = :calendar.last_day_of_the_month(shifted.year, shifted.month)
cond do
shifted.day <= last_day ->
shifted
:else ->
%{shifted | :day => last_day}
end
true ->
shifted
end
end
Timex uses the MIT license, so you should be able to incorporate this in pretty much any project.
ex_cldr_calendars can also do basic date math for adding and subtracting years, quarters, months, weeks and days for any calendar that implements the Calendar behaviour.
iex> Cldr.Calendar.plus ~D[2019-03-31], :months, -1
~D[2019-02-28]
# The :coerce option determines whether to force an end
# of month date when the result of the operation is an invalid date
iex> Cldr.Calendar.plus ~D[2019-03-31], :months, -1, coerce: false
{:error, :invalid_date}
Without adding a dependency like Timex, the following works for adding/subtracting Gregorian months without too much trouble - assuming you only need the first of each month. Shifting to a day of the month directly may be best served through a library, given how many calendrical fallacies there are.
defmodule DateUtils
#doc """
Shift a given date forward or back n months
"""
def shift_n_months(date, n) when n < 0, do: subtract_n_months(date, -1 * n)
def shift_n_months(date, n), do: add_n_months(date, n)
def add_n_months(date, 0), do: Date.beginning_of_month(date)
def add_n_months(date, n) do
date
|> Date.end_of_month()
|> Date.add(1)
|> add_n_months(n - 1)
end
def subtract_n_months(date, 0), do: Date.beginning_of_month(date)
def subtract_n_months(date, n) do
date
|> Date.beginning_of_month()
|> Date.add(-1)
|> subtract_n_months(n - 1)
end
end
There is an elixir function Date.add/2. Give it any date and it will add the dates for you.
iex>Date.add(~D[2000-01-03], -2)
~D[2000-01-01]
If you want to create the date to add to then i suggest you use the Date.new/4
iex>{:ok, date} = Date.new(year, month, day)
iex>date |> Date.add(n)
Related
I have dates that look like "17-JAN-19", "18-FEB-20". When I attempt to use the Dates package Date("17-JAN-19", "d-u-yy") I get reasonably 0019-01-17. I could do Date("17-JAN-19", "d-u-yy") + Year(2000) but that introduces the possibility of new errors (I was going to give the example of leap year but that generally works though there is the very rare error Date("29-FEB-00", "d-u-yy")+Year(1900)).
Is there a date format that embeds known information about century?
As mentioned in https://github.com/JuliaLang/julia/issues/30002 there are multiple heuristics for assigning the century to a date. I would recommend being explicit and handling it through a helper function.
const NOCENTURYDF = DateFormat("d-u-y")
"""
parse_date(obj::AbstractString,
breakpoint::Integer = year(now()) - 2000,
century::Integer = 20)
Parses date in according to DateFormat("d-u-y") after attaching century information.
If the year portion is greater that the current year,
it assumes it corresponds to the previous century.
"""
function parse_date(obj::AbstractString,
breakpoint::Integer = year(now()) - 2000,
century::Integer = 20)
# breakpoint = year(now()) - 2000
# century = year(now()) ÷ 100
#assert 0 ≤ breakpoint ≤ 99
yy = rpad(parse(Int, match(r"\d{2}$", obj).match), 2, '0')
Date(string(obj[1:7],
century - (parse(Int, yy) > breakpoint),
yy),
NOCENTURYDF)
end
parse_date("17-JAN-19")
parse_date("29-FEB-00")
I want to aggregate a monthly series at the quarterly frequency, for which R has ts and aggregate() (see the first answer on this thread) and pandas has df.resample("Q").sum() (see this question). Does Julia offer something similar?
Appendix: my current solution uses a function to convert a data to the first quarter and split-apply-combine:
"""
month_to_quarter(date)
Returns the date corresponding to the first day of the quarter enclosing date
# Examples
```jldoctest
julia> Date(1990, 1, 1) == RED.month_to_quarter(Date(1990, 2, 1))
true
julia> Date(1990, 1, 1) == RED.month_to_quarter(Date(1990, 1, 1))
true
julia> Date(1990, 1, 1) == RED.month_to_quarter(Date(1990, 2, 25))
true
```
"""
function month_to_quarter(date::Date)
new_month = 1 + 3 * floor((Dates.month(date) - 1) / 3)
return Date(Dates.year(date), new_month, 1)
end
"""
monthly_to_quarterly(monthly_df)
Aggregates a monthly data frame to the quarterly frequency. The data frame should have a :DATE column.
# Examples
```jldoctest
julia> monthly = convert(DataFrame, hcat(collect([Dates.Date(1990, m, 1) for m in 1:3]), [1; 2; 3]));
julia> rename!(monthly, :x1 => :DATE);
julia> rename!(monthly, :x2 => :value);
julia> quarterly = RED.monthly_to_quarterly(monthly);
julia> quarterly[:value][1]
2.0
julia> length(quarterly[:value])
1
```
"""
function monthly_to_quarterly(monthly::DataFrame)
# quarter months: 1, 4, 7, 10
quarter_months = collect(1:3:10)
# Deep copy the data frame
monthly_copy = deepcopy(monthly)
# Drop initial rows until it starts on a quarter
while !in(Dates.month(monthly_copy[:DATE][1]), quarter_months)
# Verify that something is left to pop
#assert 1 <= length(monthly_copy[:DATE])
monthly_copy = monthly_copy[2:end, :]
end
# Drop end rows until it finishes before a quarter
while !in(Dates.month(monthly_copy[:DATE][end]), 2 + quarter_months)
monthly_copy = monthly_copy[1:end-1, :]
end
# Change month of each date to the nearest quarter
monthly_copy[:DATE] = month_to_quarter.(monthly_copy[:DATE])
# Split-apply-combine
quarterly = by(monthly_copy, :DATE, df -> mean(df[:value]))
# Rename
rename!(quarterly, :x1 => :value)
return quarterly
end
I couldn't find such a function in the docs. Here's a more DataFrames.jl-ish and more succint version of your own answer
using DataFrames
# copy-pasted your own function
function month_to_quarter(date::Date)
new_month = 1 + 3 * floor((Dates.month(date) - 1) / 3)
return Date(Dates.year(date), new_month, 1)
end
# the data
r=collect(1:6)
monthly = DataFrame(date=[Dates.Date(1990, m, 1) for m in r],
val=r);
# the functionality
monthly[:quarters] = month_to_quarter.(monthly[:date])
_aggregated = by(monthly, :quarters, df -> DataFrame(S = sum(df[:val])))
#show monthly
#show _aggregated
I want to display the weeks dates based on a week number that I get from my db.
So if I get week=43 then it would display all 7 dates for that week.
Like this.
M=23
T=24
W=25
T=26
F=27
S=28
S=29
Have tested with a lot of date formating but I can't get it working.
So any input really appreciated, thanks!
I ended up with this, works perfect!
currentDate = Date
weekNumber=DatePart("ww", currentDate, vbMonday, vbFirstFourDays)
y = Year(Date)
Public Function FirstDayOfWeek(Year, Week)
Dim TempDate
TempDate = DateSerial(Year, 1, 1)
Do Until DatePart("ww", TempDate, vbMonday, vbFirstFourDays) = 1
TempDate = TempDate + 7
Loop
TempDate = TempDate + (7 * (Week - 1))
FirstDayOfWeek = TempDate - Weekday(TempDate, vbMonday) + 1
End Function
Dim startDatum
Dim slutDatum
startDatum = FirstDayOfWeek(y, weekNumber)
slutDatum = startDatum + 6
mon=DatePart("d", startDatum)
tus=DatePart("d", startDatum+1)
wen=DatePart("d", startDatum+2)
tur=DatePart("d", startDatum+3)
fri=DatePart("d", startDatum+4)
sat=DatePart("d", startDatum+5)
sun=DatePart("d", startDatum+6)
Well, there isn't really an inverse DatePart() function, so you have to make your own calculations.
dim w, wd, y, m, i
y = Year(Date) '- year of the week in question; I'm using today's date
w = 43
wd = DateAdd("d",w*7,CDate("1/1/" & y)) '- adjust as needed for 1st week of year
m = DateAdd("d",2-Weekday(wd),wd) '- find Monday of week
Hopefully, you can go from there.
I have a variable with a date table that looks like this
* table:
[day]
* number: 15
[year]
* number: 2015
[month]
* number: 2
How do I get the days between the current date and the date above? Many thanks!
You can use os.time() to convert your table to seconds and get the current time and then use os.difftime() to compute the difference. see Lua Wiki for more details.
reference = os.time{day=15, year=2015, month=2}
daysfrom = os.difftime(os.time(), reference) / (24 * 60 * 60) -- seconds in a day
wholedays = math.floor(daysfrom)
print(wholedays) -- today it prints "1"
as #barnes53 pointed out could be off by one day for a few seconds so it's not ideal, but it may be good enough for your needs.
You can use the algorithms gathered here:
chrono-Compatible Low-Level Date Algorithms
The algorithms are shown using C++, but they can be easily implemented in Lua if you like, or you can implement them in C or C++ and then just provide Lua bindings.
The basic idea using these algorithms is to compute a day number for the two dates and then just subtract them to give you the number of days.
--[[
http://howardhinnant.github.io/date_algorithms.html
Returns number of days since civil 1970-01-01. Negative values indicate
days prior to 1970-01-01.
Preconditions: y-m-d represents a date in the civil (Gregorian) calendar
m is in [1, 12]
d is in [1, last_day_of_month(y, m)]
y is "approximately" in
[numeric_limits<Int>::min()/366, numeric_limits<Int>::max()/366]
Exact range of validity is:
[civil_from_days(numeric_limits<Int>::min()),
civil_from_days(numeric_limits<Int>::max()-719468)]
]]
function days_from_civil(y, m, d)
if m <= 2 then
y = y - 1
m = m + 9
else
m = m - 3
end
local era = math.floor(y/400)
local yoe = y - era * 400 -- [0, 399]
local doy = math.modf((153*m + 2)/5) + d-1 -- [0, 365]
local doe = yoe * 365 + math.modf(yoe/4) - math.modf(yoe/100) + doy -- [0, 146096]
return era * 146097 + doe - 719468
end
local reference_date = {year=2001, month = 1, day = 1}
local date = os.date("*t")
local reference_days = days_from_civil(reference_date.year, reference_date.month, reference_date.day)
local days = days_from_civil(date.year, date.month, date.day)
print(string.format("Today is %d days into the 21st century.",days-reference_days))
os.time (under Windows, at least) is limited to years from 1970 and up. If, for example, you need a general solution to also find ages in days for people born before 1970, this won't work. You can use a julian date conversion and subtract between the two numbers (today and your target date).
A sample julian date function that will work for practically any date AD is given below (Lua v5.3 because of // but you could adapt to earlier versions):
local
function div(n,d)
local a, b = 1, 1
if n < 0 then a = -1 end
if d < 0 then b = -1 end
return a * b * (math.abs(n) // math.abs(d))
end
--------------------------------------------------------------------------------
-- Convert a YYMMDD date to Julian since 1/1/1900 (negative answer possible)
--------------------------------------------------------------------------------
function julian(year, month, day)
local temp
if (year < 0) or (month < 1) or (month > 12)
or (day < 1) or (day > 31) then
return
end
temp = div(month - 14, 12)
return (
day - 32075 +
div(1461 * (year + 4800 + temp), 4) +
div(367 * (month - 2 - temp * 12), 12) -
div(3 * div(year + 4900 + temp, 100), 4)
) - 2415021
end
User gives: year, month, day, hour and minute and I would like to return CalendarTime value using these values. How can I do that - because I have error: 'Couldn't match expected type IO CalendarTime against inferred type Int'. What is wrong in function getDateTime ?
Proposition: all values are correct, for example month is from range 1 - 12 etc - I deleted validation from below code because otherwise code would be too long.
isInteger i = not (null i) && all isDigit i
getInteger :: String -> IO Int
getInteger q = do
putStr q;
i <- getLine
if isInteger i == False then do
putStrLn "Bad number"
getInt q
else return (read i)
getDateTime :: String -> IO CalendarTime
getDateTime question = do
putStr question;
year <- getInteger "Year: "
month <- getInteger "Month: "
day <- getInteger "Day: "
hour <- getInteger "Hour: "
minute <- getInteger "Minute: "
return CalendarTime(year month day hour minute 0)
This line
return CalendarTime(year month day hour minute 0)
is read by the compiler as
return CalendarTime (year month day hour minute 0)
So it's not a call to CalendarTime. Instead you're feeding CalendarTime and (year month day hour minute 0) as arguments return. This is nonsense as return only takes a single argument and yearis not a function. You probably meant
return (CalendarTime year month day hour minute 0)
though this still doesn't work since CalendarTime requires more arguments than that. (Assuming we're talking about the one in System.Time since you didn't specify the imports).
You probably want
return $ CalendarTime { ctYear = year
, ctMonth = toEnum (month-1)
, ctDay = day
, ctHour = hour
, ctMinute = minute
, ctSec = 0 })
This leaves the missing fields undefined, see the relevant section in Real World Haskell for more information.
You also misspelled getInteger as getInt in the recursive call in the first function, but I'm assuming that's a result of your cleanup of the validation code.
You don't say exactly which line gives the error, but I'm guessing the problem is:
return CalendarTime(year month day hour minute 0)
In Haskell, parentheses are used only for grouping. Function application is implicit by writing one term after the other. return should be applied to the CalendarTime value, so you probably wanted this instead:
return (CalendarTime year month day hour minute 0)
Although this would be more idiomatic:
return $ CalendarTime year month day hour minute 0
It looks like you are trying to use the constructor CalendarTime like a function from other language styles.
return (CalendarTime year month day hour minute 0)