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"
Related
I have a list of dates that gets imported as strings. I've tried a bunch of different things to convert it to a list of dates. Doesn't matter how I do it I get an error.
#import valid dates from map file as list
valdts = dfmap.loc[row, 'valdata'].split(', ')
print(valdts)
>>
['1/1/1990', '6/6/1990', '7/4/1776']
#convert strings to dates
attempt1:
valdts = [d.strftime('%Y-%m-%d') for d in valdts]
>>
AttributeError: 'str' object has no attribute 'strftime'
attempt2:
a = [date_obj.strftime('%Y%m%d') for date_obj in valdts]
>>
AttributeError: 'str' object has no attribute 'strftime'
attempt3:
a = datetime.strptime(valdts, '%d %b %Y')
>>
TypeError: strptime() argument 1 must be str, not list
attempt4:
a = valdts.sort(key = lambda dt: datetime.strptime(dt, '%d %m %Y'))
>>
ValueError: time data '1/1/1990' does not match format '%d %m %Y'
attempt5:
for dt in valdts:
dt = dt.replace('/',',')
print(dt)
c = datetime.strptime('.'.join(str(i) for i in dt).zfill(8), "%Y.%m.%d")
>>
'1,1,1990'
ValueError: time data '1.,.1.,.1.9.9.0' does not match format '%Y.%m.%d'
attempt6:
for dt in valdts:
dt = dt.replace('/',',')
datetime.strptime(dt, '%d %m %Y')
>>
ValueError: time data '1,1,1990' does not match format '%d %m %Y'
I'm getting quite frustrated. The different approaches above are based on responses to similar but not quite the same questions posted by others. The question most similar to mine has been downvoted. Am I trying to do something stupid? Would really appreciate some help here. Thanks.
Note: datetime.datetime gives me an error. AttributeError: type object 'datetime.datetime' has no attribute 'datetime' but just datetime works for other parts of my code.
This is the work around I finally came up with. But would welcome a better method that doesn't require splitting each date.
valdts = dfmap.loc[row, 'valdata'].split(', ')
print(valdts)
>>
['1/1/1990', '6/6/1990', '7/4/1776']
for dt in valdts:
ldt = dt.split('/')
valdt = datetime(int(ldt[2]), int(ldt[1]), int(ldt[0]))
ldates.append(valdt)
print(ldt)
>>
note1: datetime.datetime didn't work for me because of the way I'd imported datetime. See excellent explanations here.
note2: converting the individual numbers to int was crucial. nothing else worked for me. Credit to the solution provided by #waitingkuo in the same link above.
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]
Group rows by day/week/month using inserted_at column.
Example : If first row is created before 1 week then group by days and if any data is not created in middle date then add o as value.
[
{"21 Jul" => 12},{"22 Jul" => 0},{"23 Jul" => 3}, {"24 Jul" => 1}...
{"5 Aug" => 0}
]
Thanks in advance.
Let's suppose you have Event schema with DateTime field called starts_at.
To get, for example, events count by date you can make such function:
def get_events_count_grouped_by_date(query) do
query
|> group_by([e], fragment("date(?)", e.starts_at))
|> select([e], %{date: fragment("date(?)", e.starts_at), count: count(e.id)})
|> order_by([e], asc: fragment("date(?)", e.starts_at))
end
Then use it like this:
Event |> get_events_count_grouped_by_date() |> Repo.all()
It's not quite clear from the question exactly what is required, but hopefully the following query helps: It will give you the count of records grouped by the date part of inserted_at.
defmodule CountByDateQuery do
import Ecto.Query
#doc "to_char function for formatting datetime as dd MON YYYY"
defmacro to_char(field, format) do
quote do
fragment("to_char(?, ?)", unquote(field), unquote(format))
end
end
#doc "Builds a query with row counts per inserted_at date"
def row_counts_by_date do
from record in SomeTable,
group_by: to_char(record.inserted_at, "dd Mon YYYY"),
select: {to_char(record.inserted_at, "dd Mon YYYY"), count(record.id)}
end
end
Usage:
row_counts_by_date() |> Repo.all() |> Map.new()
%{"05 Aug 2017" => 2}
I have one date-time in Ecto.DateTime and the 2nd one in DateTime. How can I convert them to each other? Isn't there a easy way without external dependencies? There's nothing in the documentation. One of them has to_erl, another from_unix, but there's no overlap in methods, such as to_unix/from_unix or to_erl/from_erl or something similar.
The equivalent of Ecto.DateTime is NaiveDateTime, since neither of them store a timezone, while DateTime does. Erlang datetimes also do not have a timezone, which is why there's no to_erl and from_erl in DateTime.
You can first convert to NaiveDateTime and then use DateTime.from_naive/2 along with the timezone your datetime is in (Elixir only supports Etc/UTC as of Elixir 1.4):
iex(1)> Ecto.DateTime.utc |> Ecto.DateTime.to_erl |> NaiveDateTime.from_erl! |> DateTime.from_naive!("Etc/UTC")
%DateTime{calendar: Calendar.ISO, day: 8, hour: 4, microsecond: {0, 0},
minute: 49, month: 2, second: 9, std_offset: 0, time_zone: "Etc/UTC",
utc_offset: 0, year: 2017, zone_abbr: "UTC"}
iex(2)> DateTime.utc_now |> DateTime.to_naive |> NaiveDateTime.to_erl |> Ecto.DateTime.from_erl
#Ecto.DateTime<2017-02-08 04:50:23>
If you were using Ecto.DateTime earlier though, you probably want to use NaiveDateTime now.
I have a relative simple SQLAlchemy query (on an MySQL database):
my_date = db.session.query(func.max(MyTable.date_column)).one()
The queried column is of type db.DateTime().
Now I want to format the returned date in my_date:
my_date.isoformat() # fails
my_date.strftime("%Y-%m-%d %H:%M:%S %z") # fails
What is this object I get as result and what do I have to do to get a datetime object that can be formatted?
When I use the debugger to inspect the returned object I see the following: (datetime.datetime(2016, 1, 28, 12, 35, 17),) - but a real Python datetime.datetime object looks different in the debugger.
dir(my_date) returns the following:
['__add__', '__class__', '__contains__', '__delattr__', '__dir__',
'__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
'__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__',
'__iter__', '__le__', '__len__', '__lt__', '__module__', '__mul__',
'__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__',
'__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__',
'_asdict', '_fields', '_real_fields', 'count', 'index', 'keys']
one() returns result as KeyedTuple.
You can use scalar()
my_date = db.session.query(func.max(MyTable.date_column)).scalar()
or get value from tuple
my_date = db.session.query(func.max(MyTable.date_column)).one()[0]