Converting between Ecto.DateTime and DateTime - datetime

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.

Related

Timex.to_naive_datetime subtracts an hour

Calling Timex.to_naive_datetime to convert to a naive datetime without the timezone subtracts an hour from the input datetime
I have tried other timezones and ruled out the possibility of daylight saving time conversion.
d = %DateTime{
year: 2000,
month: 2,
day: 29,
zone_abbr: "CET",
hour: 23,
minute: 0,
second: 7,
microsecond: {0, 0},
utc_offset: 3600,
std_offset: 0,
time_zone: "Europe/Warsaw"
}
#=> # DateTime<2000-02-29 23:00:07+01:00 CET Europe/Warsaw>
Timex.to_naive_datetime(d)
#=> ~N[2000-02-29 22:00:07]
d2 = %DateTime{
year: 2019,
month: 3,
day: 2,
zone_abbr: "PST",
hour: 23,
minute: 0,
second: 7,
microsecond: {0, 0},
utc_offset: 3600,
std_offset: 0,
time_zone: "America/Los_Angeles"
}
#=> #DateTime<2019-03-02 23:00:07+01:00 PST America/Los_Angeles>
Timex.to_naive_datetime(d2)
#=> ~N[2019-03-02 22:00:07]
I am expecting the first datetime to be converted to ~N[2000-02-29 23:00:07], but the output is ~N[2000-02-29 22:00:07].
Both of the structs you create contain utc_offset: 3600, which is UTC+1.
#DateTime<2000-02-29 23:00:07+01:00 CET Europe/Warsaw>
#DateTime<2019-03-02 23:00:07+01:00 PST America/Los_Angeles>
How did the +01:00 that get there for America/Los_Angeles? It's not valid for that timezone. If I generate the date using one of the standard functions:
d3 = Timex.to_datetime({{2019,3, 2}, {23, 0, 7}}, "America/Los_Angeles")
#=> #DateTime<2019-03-02 23:00:07-08:00 PST America/Los_Angeles>
Timex.to_naive_datetime(d3)
#=> ~N[2019-03-03 07:00:07]
I get the correct timezone offset. It seems Timex.to_naive_datetime/1 returns the UTC value of the timezone. If you just want to drop the timezone information, you can use DateTime.to_naive/1:
d1
#=> #DateTime<2000-02-29 23:00:07+01:00 CET Europe/Warsaw>
DateTime.to_naive(d1)
#=> ~N[2000-02-29 23:00:07]

How to get previous month in elixir

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]

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"

Datetime and Pytz Timezone .weekday() issue

I'm running into an issue when I'm trying to create a histogram of specific createdAt datetimes for orders. The issue is that even after created timezone aware datetimes, the .weekday() shows up as the same day, even though it should be a different time
The code I'm using to test this occurrence is as follows:
import datetime
import pytz
value = {
'createdAt': '2017-04-24T00:48:03+00:00'
}
created_at = datetime.datetime.strptime(value['createdAt'], '%Y-%m-%dT%H:%M:%S+00:00')
timezone = pytz.timezone('America/Los_Angeles')
created_at_naive = created_at
created_at_aware = timezone.localize(created_at_naive)
print(created_at_naive) # 2017-04-24 00:48:03
print(created_at_aware) # 2017-04-24 00:48:03-07:00
print(created_at_naive.weekday()) # 0 (Monday)
print(created_at_aware.weekday()) # 0 (should be Sunday)
The problem is that you need to actually change the datetime to the new timezone:
>>> timezone('UTC').localize(created_at)
datetime.datetime(2017, 4, 24, 0, 48, 3, tzinfo=<UTC>)
>>>timezone('UTC').localize(created_at).astimezone(timezone('America/Los_Angeles'))
datetime.datetime(2017, 4, 23, 17, 48, 3, tzinfo=<DstTzInfo 'America/Los_Angeles' PDT-1 day, 17:00:00 DST>)

Python incorrectly extracting month from string [duplicate]

The month format specifier doesn't seem to work.
from datetime import datetime
endDate = datetime.strptime('10 3 2011', '%j %m %Y')
print endDate
2011-01-10 00:00:00
endDate = datetime.strptime('21 5 1987', '%j %m %Y')
print endDate
1987-01-21 00:00:00
Now, according to the manual the manual:
%m = Month as a decimal number [01,12].
So, what am I missing, other than the hair I've pulled out trying to understand why my django __filter queries return nothing (the dates going in aren't valid!)? I've tried 03 and 05 to no avail.
Versions of things, platform, architecture et al:
$ python --version
Python 2.7
$ python3 --version
Python 3.1.2
$ uname -r
2.6.35.11-83.fc14.x86_64 (that's Linux/Fedora 14/64-bit).
You can't mix the %j with others format code like %m because if you look in the table that you linked %j is the Day of the year as a decimal number [001,366] so 10 correspondent to the 10 day of the year so it's 01 of January ...
So you have just to write :
>>> datetime.strptime('10 2011', '%j %Y')
datetime.datetime(2011, 1, 10, 0, 0)
Else if you you wanted to use 10 as the day of the mount you should do :
>>> datetime.strptime('10 3 2011', '%d %m %Y')
datetime.datetime(2011, 3, 10, 0, 0)
Isn't %j the "day of year" parser, which may be forcing strptime to choose January 21, overriding the %m rule?
%j specifies a day of the year. It's impossible for the 10th day of the year, January 10, to occur in March, so your month specification is being ignored. Garbage In, Garbage Out.

Resources