How to get previous month in elixir - datetime

How can I get the previous month without using a package or library in elixir?
For example, if the current date is 2018-01-25, I will get 2017-12-25.
Or If the current date is 2018-03-31, I will get 2018-02-28 (2018 is not a leap year)

The answer by #Sheharyar is almost there, the only difference you need to subtract the maximum of days in both months:
defmodule Dating do
def previous_month(%Date{day: day} = date) do
days = max(day, (Date.add(date, -day)).day)
Date.add(date, -days)
end
end
Works for all cases:
iex|1 ▶ Dating.previous_month(~D[2018-03-31])
#⇒ ~D[2018-02-28]
iex|2 ▶ Dating.previous_month(~D[2018-03-01])
#⇒ ~D[2018-02-01]
iex|3 ▶ Dating.previous_month(~D[2018-01-02])
#⇒ ~D[2017-12-02]

Use Timex library
iex(1)> ~D[2018-01-25] |> Timex.shift(months: -1)
~D[2017-12-25]
iex(2)> ~D[2018-03-31] |> Timex.shift(months: -1)
~D[2018-02-28]

Related

Start of previous year

**DATE FROM:**
def format=new java.text.SimpleDateFormat("yyyyMMdd")
def cal=Calendar.getInstance()
cal.get(Calendar.YEAR);
cal.set(Calendar.MONTH, 0);
cal.set(Calendar.DAY_OF_MONTH, 31);
[format.format(cal.getTime())]
**DATE TO:**
def format=new java.text.SimpleDateFormat("yyyyMMdd")
def cal=Calendar.getInstance()
cal.add(Calendar.DAY_OF_MONTH,-cal.get(Calendar.DAY_OF_MONTH))
[format.format(cal.getTime())]
when year changes (2020 - 2021) - it confuses January of previous year with January of this year
I have to correct so that in January (December reporting) it extracts data for period 31.01 - 31.12. of previous year.
The job was wrong because it extracted data from 31.01.2021 to 31.12.2020
// retrieve details of the current date
def cal = Calendar.instance;
def currentYear = cal.get(Calendar.YEAR);
def currentMonth = cal.get(Calendar.MONTH);
// set the instance to the start of the previous month
if ( currentMonth == 0 ) {
cal.set(currentYear-1, 11, 1);
} else {
cal.set(currentYear, (currentMonth-1), 1);
}
// extract the date, and format to a string
Date previousMonthStart = cal.time;
String previousMonthStartFormatted = previousMonthStart.format('yyyy-MM-dd');
If all you are looking for is the start of the previous year as in your title then the following code:
import java.time.*
def startOfPreviousYear = LocalDate.now()
.withDayOfMonth(1)
.withMonth(1)
.minusYears(1)
println startOfPreviousYear
def againStartingFromJanuary = LocalDate.of(2021, 1, 15)
.withDayOfMonth(1)
.withMonth(1)
.minusYears(1)
println againStartingFromJanuary
demonstrates one way to accomplish this. When run, this prints (with now being today's date of 2021.Mar.10):
─➤ groovy solution.groovy
2020-01-01
2020-01-01
updated after comments
You can get the end of previous and current months with something like this:
import java.time.*
def endOfPreviousMonth = LocalDate.now()
.withDayOfMonth(1)
.minusDays(1)
def endOfCurrentMonth = LocalDate.now()
.withDayOfMonth(1)
.plusMonths(1)
.minusDays(1)
println "end of last month: ${endOfPreviousMonth}"
println "end of current month: ${endOfCurrentMonth}"
which with current date prints:
end of last month: 2021-02-28
end of current month: 2021-03-31
or if we are in january:
def endOfPreviousMonth = LocalDate.of(2021, 1, 15)
.withDayOfMonth(1)
.minusDays(1)
def endOfCurrentMonth = LocalDate.of(2021, 1, 15)
.withDayOfMonth(1)
.plusMonths(1)
.minusDays(1)
println "end of last month: ${endOfPreviousMonth}"
println "end of current month: ${endOfCurrentMonth}"
which prints:
─➤ groovy solution.groovy
end of last month: 2020-12-31
end of current month: 2021-01-31
In general you should try to, when possible, stay away from using manual date arithmetic when dealing with dates if your target is based on the current date (as in, previous month, next month, three months ago, etc). Use the api:s handed to you by java. The date classes take care of rolling years, rolling months, rolling days, leap years, etc, all that stuff that you really do not want to spend time solving yourself.

Groovy: Time in ISO 8601 format

How to get the current time and 15 min ago time in iso 8601 format (YYYY-MM-DDTHH:mm:ss) in groovy?
You can use java time's Instant and the toString() format
import java.time.*
def now = Instant.now()
def fifteenAgo = now.minus(Duration.ofMinutes(15))
println "Now is ${now} and 15 mins ago was ${fifteenAgo}"
Prints:
Now is 2020-06-30T19:53:17.445039Z and 15 mins ago was 2020-06-30T19:38:17.445039Z
You can formast the date in any way you want in Groovy, by doing e.g.
println new Date().format("yyyy-MM-dd HH.mm.ss.SSSSS Z")
Then, you can do calculations on the date, like this:
new Date(System.currentTimeMillis()-91*60*1000)
which will minus 91 minutes (91min * 60sec * 1000ms).
Then you can put the statements together, which is why Groovy is great:
def a = new Date(System.currentTimeMillis()-91*60*1000).format("YYYY-MM-DD")
And so you can get the half before the T. And the half after the T:
def b = new Date(System.currentTimeMillis()-91*60*1000).format("HH:mm:ss")
And then concatenate them with a T:
println "91 minutes ago in iso 8601 format is: ${a}T${b}"
There are other ways of doing it, like with TimeCategory.minus, but this is a good illustration. I used 91 minutes, but you can adapt it to your own requirtement.

Format date with Elixir

I'm trying to format the Timex module to look a certain way. I'm trying to get today's date. but I want it formatted like this:
2017/12/12.
year/mn/day
In ruby I would go to the strftime class but I'm not sure how to do this with Elixir:
Current attempt:
Timex.local => #DateTime<2017-12-12 19:57:17.232916-05:00 EST America/Detroit>
How can I take that and format it how I specified?
Elixir 1.11 has Calendar.strftime/3 built-in for your strftime needs.
Calendar.strftime(~U[2019-08-26 13:52:06.0Z], "%y-%m-%d %I:%M:%S %p")
"19-08-26 01:52:06 PM"
Timex is a third-party library that was created in the era when Elixir had no good support for dates/times. Nowadays, there is DateTime native class in the core, so I am unsure why do you want to use Timex at all.
In any case, DateTime is a struct:
iex|1 ▶ today = DateTime.utc_now
#⇒ #DateTime<2017-12-13 07:22:58.290075Z>
iex|2 ▶ [today.year, today.month, today.day]
#⇒ [2017, 12, 13]
iex|3 ▶ Enum.join [today.year, today.month, today.day], "/"
#⇒ "2017/12/13"
To pad with leading zeroes for "2018/1/1":
iex|4 ▶ with {:ok, today} <- Date.new(2018, 1, 1) do
...|4 ▶ [today.year, today.month, today.day]
...|4 ▶ |> Enum.map(&to_string/1)
...|4 ▶ |> Enum.map(&String.pad_leading(&1, 2, "0"))
...|4 ▶ |> Enum.join("/")
...|4 ▶ end
#⇒ "2018/01/01"
If you want to do this without an external library, you can use io_lib:format/2 to pad the integers with zeroes where necessary like this:
iex(1)> date = Date.utc_today
~D[2017-12-13]
iex(2)> :io_lib.format("~4..0B/~2..0B/~2..0B", [date.year, date.month, date.day]) |> IO.iodata_to_binary
"2017/12/13"
iex(3)> {:ok, date} = Date.new(2018, 1, 1)
{:ok, ~D[2018-01-01]}
iex(4)> :io_lib.format("~4..0B/~2..0B/~2..0B", [date.year, date.month, date.day]) |> IO.iodata_to_binary
"2018/01/01"
iex(5)> {:ok, date} = Date.new(1, 1, 1)
{:ok, ~D[0001-01-01]}
iex(6)> :io_lib.format("~4..0B/~2..0B/~2..0B", [date.year, date.month, date.day]) |> IO.iodata_to_binary
"0001/01/01"
You can do this to add zeros
Timex.local |> Timex.format!("{YYYY}/0{M}/0{D}") => "2017/01/01"
So it appears the Timex Module has a format!/2 function which will return a string of what ever date you pass to it.
Here is what I came up with:
Timex.local |> Timex.format!("{YYYY}/{M}/{D}") => "2017/12/12"
The answer to add 0 padding is incorrect and will always pad 0s even if it does not require it. The correct way to pad 0s is as follows:
Timex.local |> Timex.format!("{YYYY}/{0M}/{0D}") => "2017/01/01"
As of Elixir 1.11 you can do this sanely with a date formatter in the standard library's Calendar module:
https://hexdocs.pm/elixir/1.13.0/Calendar.html#strftime/3
DateTime.utc_now()
|> Calendar.strftime("%Y/%m/%d")
"2022/01/12"

File renaming based on file content in UNIX

I have pattern namely QUARTERDATE and FILENAME inside the file.
Both will have some value as in below eg.
My requirement is, I should rename the file like FILENAME_QUARTERDATE.
My file(myfile.txt) will be as below:
QUARTERDATE: 03/31/14 - 06/29/14
FILENAME : LEAD
field1 field2
34567
20.0 5,678
20.0 5,678
20.0 5,678
20.0 5,678
20.0 5,678
I want the the file name to be as LEAD_201402.txt
Date range in the file is for Quarter 2, so i given as 201402.
Thanks in advance for the replies.
newname=$(awk '/QUARTERDATE/ { split($4, d, "/");
quarter=sprintf("%04d%02d", 2000+d[3], int((d[1]-1)/3)+1); }
/FILENAME/ { fn = $3; print fn "_" quarter; exit; }' "$file")
mv "$file" "$newname"
How is a quarter defined?
As noted in comments to the main question, the problem is as yet ill-defined.
What data would appear in the previous quarter's QUARTERDATE line? Could Q1 ever start with a date in December of the previous year? Could the end date of Q2 ever be in July (or Q1 in April, or Q3 in October, or Q4 in January)? Since the first date of Q2 is in March, these alternatives need to be understood. Could a quarter ever start early and end late simultaneously (a 14 week quarter)?
To which the response was:
QUARTERDATE of Q2 will start as 1st Monday of April and end as last Sunday of June.
Which triggered a counter-response:
2014-03-31 is a Monday, but hardly a Monday in April. What this mainly means is that your definition of a quarter is, as yet, not clear. For example, next year, 2015-03-30 is a Monday, but 'the first Monday in April' is 2015-04-06. The last Sunday in March 2015 is 2015-03-29. So which quarter does the week (Mon) 2015-03-30 to (Sun) 2015-04-05 belong to, and why? If you don't know (both how and why), we can't help you reliably.
Plausible working hypothesis
The lessons of Y2K have been forgotten already (why else are two digits used for the year, dammit!).
Quarters run for an integral number of weeks.
Quarters start on a Monday and end on a Sunday.
Quarters remain aligned with the calendar quarters, rather than drifting around the year. (There are 13 weeks in 91 days, and 4 such quarters in a year, but there's a single extra day in an ordinary year and two extra in a leap year, which mean that occasionally you will get a 14-week quarter, to ensure things stay aligned.)
The date for the first date in a quarter will be near 1st January, 1st April, 1st July or 1st October, but the month might be December, March (as in the question), June or September.
The date for the last date in a quarter will be near 31st March, 30th June, 30th September, 31st December, but the month might be April, July, October or January.
By adding 1 modulo 12 (values in the range 1..12, not 0..11) to the start month, you should end up with a month firmly in the calendar quarter.
By subtracting 1 modulo 12 (values in the range 1..12 again) to the end month, you should end up with a month firmly in calendar quarter.
If the data is valid, the 'start + 1' and 'end - 1' months should be in the same quarter.
The early year might be off-by-one if the start date is in December (but that indicates Q1 of the next year).
The end year might be off-by-one if the end date is in January (but that indicates Q4 of the prior year).
More resilient code
Despite the description above, it is possible to write code that detects the quarter despite any or all of the idiosyncrasies of the quarter start and end dates. This code borrows a little from Barmar's answer, but the algorithm is more resilient to the vagaries of the calendar and the quarter start and end dates.
#!/bin/sh
awk '/QUARTERDATE/ {
split($2, b, "/")
split($4, e, "/")
if (b[1] == 12) { q = 1; y = e[3] }
else if (e[1] == 1) { q = 4; y = b[3] }
else
{
if (b[3] != e[3]) {
print "Year mismatch (" $2 " vs " $4 ") in file " FILENAME
exit 1
}
m = int((b[1] + e[1]) / 2)
q = int((m - 1) / 3) + 1
y = e[3]
}
quarter = sprintf("%.4d%.2d", y + 2000, q)
}
/FILENAME/ {
print $3 "_" quarter
# exit
}' "$#"
The calculation for m adds the start month plus one to the end month minus one and then does integer division by two. With the extreme cases already taken care of, this always yields a month number that is in the correct quarter.
The comment in front of the exit associated with FILENAME allows testing more easily. When processing each file separately, as in Barmar's example, that exit is an important optimization. Note that the error message gives an empty file name if the input comes from standard input. (Offhand, I'm not sure how to print the error message to standard error rather than standard output, other than by a platform-specific technique such as print "message" > "/dev/stderr" or print "message" > "/dev/fd/2".)
Given this sample input data (semi-plausible start and end dates for 6 quarters from 2014Q1 through 2015Q2):
QUARTERDATE: 12/30/13 - 03/30/14
FILENAME : LEAD
QUARTERDATE: 03/31/14 - 06/29/14
FILENAME : LEAD
QUARTERDATE: 06/30/14 - 09/28/14
FILENAME : LEAD
QUARTERDATE: 09/29/14 - 12/28/14
FILENAME : LEAD
QUARTERDATE: 12/29/14 - 03/29/15
FILENAME : LEAD
QUARTERDATE: 03/30/15 - 06/29/15
FILENAME : LEAD
The output from this script is:
LEAD_201401
LEAD_201402
LEAD_201403
LEAD_201404
LEAD_201501
LEAD_201502
You can juggle the start and end dates of the quarters within reason and you should still get the required output. But always be wary of calendrical calculations; they are almost invariably harder than you expect.

Erlang: timestamp with time zone arithmetics

What is the best way to add/subtract units to/from specific timestamp with respect to time zone in Erlang?
From what I've found, calendar of stdlib can work with either local or UTC time zone, no more. Moreover, arithmetics is recommended to do in UTC time zone only (the reason is obvious).
What should I do if, for instance, I need to add 1 month to {{2011,3,24},{11,13,15}} in, let's say, CET (Central European Time) and local (system) time zone is not CET? That is not even the same as converting this timestamp to UTC, adding 31 * 24 * 60 * 60 seconds and converting back to CET (that will give {{2011,4,24},{12,13,15}} instead of {{2011,4,24},{11,13,15}}). By the way we can't do even such a thing if CET is not local time zone with stdlib.
The answers I found googling are:
setenv to make local time zone = needed time zone (that is very ugly first of all; then it will only allow to convert needed time zone to utc and do arithmetics respective to utc, not the needed time zone)
open_port to linux date util and do arithmetics there (not that ugly; rather slow; needs some parsing, because the protocol between erlang and date will be textual)
port driver or erl_interface to C using its standard library (not ugly at all; but I didn't find ready to use solution and I'm not that good at C to write one)
The ideal solution would be something written in Erlang using OS timezone info, but I didn't find any.
Now I'm stuck to solution 2 (open_port to date util). Is there a better way?
Thanks in advance.
P. S. There was a similar issue, but no good answer there Time zone list issue
port_helper.erl
-module(port_helper).
-export([get_stdout/1]).
get_stdout(Port) ->
loop(Port, []).
loop(Port, DataAcc) ->
receive
{Port, {data, Data}} ->
loop(Port, DataAcc ++ Data);
{Port, eof} ->
DataAcc
end.
timestamp_with_time_zone.erl
-module(timestamp_with_time_zone).
-export([to_time_zone/2, to_universal_time/1, modify/2]).
to_time_zone({{{Year, Month, Day}, {Hour, Minute, Second}}, TimeZone}, OutputTimeZone) ->
InputPattern = "~4.10.0B-~2.10.0B-~2.10.0B ~2.10.0B:~2.10.0B:~2.10.0B",
InputDeep = io_lib:format(InputPattern, [Year, Month, Day, Hour, Minute, Second]),
Input = lists:flatten(InputDeep),
{external_date(Input, TimeZone, OutputTimeZone), OutputTimeZone}.
to_universal_time({{{Year, Month, Day}, {Hour, Minute, Second}}, TimeZone}) ->
{Timestamp, "UTC"} = to_time_zone({{{Year, Month, Day}, {Hour, Minute, Second}}, TimeZone}, "UTC"),
Timestamp.
modify({{{Year, Month, Day}, {Hour, Minute, Second}}, TimeZone}, {Times, Unit}) ->
if
Times > 0 ->
TimesModifier = "";
Times < 0 ->
TimesModifier = " ago"
end,
InputPattern = "~4.10.0B-~2.10.0B-~2.10.0B ~2.10.0B:~2.10.0B:~2.10.0B ~.10B ~s~s",
InputDeep = io_lib:format(InputPattern, [Year, Month, Day, Hour, Minute, Second, abs(Times), Unit, TimesModifier]),
Input = lists:flatten(InputDeep),
external_date(Input, TimeZone, TimeZone).
external_date(Input, InputTimeZone, OutputTimeZone) ->
CmdPattern = "date --date 'TZ=\"~s\" ~s' +%Y%m%d%H%M%S",
CmdDeep = io_lib:format(CmdPattern, [InputTimeZone, Input]),
Cmd = lists:flatten(CmdDeep),
Port = open_port({spawn, Cmd}, [{env, [{"TZ", OutputTimeZone}]}, eof, stderr_to_stdout]),
ResultString = port_helper:get_stdout(Port),
case io_lib:fread("~4d~2d~2d~2d~2d~2d", ResultString) of
{ok, [YearNew, MonthNew, DayNew, HourNew, MinuteNew, SecondNew], _LeftOverChars} ->
{{YearNew, MonthNew, DayNew}, {HourNew, MinuteNew, SecondNew}}
end.

Resources