Timex - Date/Time library for Elixir

1,121 views
Skip to first unread message

Paul Schoenfelder

unread,
Feb 26, 2014, 1:19:03 AM2/26/14
to elixir-l...@googlegroups.com
Hey all,

So I completed my first pass at a date/time library finally, and now I'd like to get some feedback on improvements, things you like/don't like, etc - so I can make it as solid as possible, and hopefully something you'll all feel like using. Documentation in the README should be a decent intro, but I'll probably rework it a bit to cover the whole API soon. This *should* handle everything you throw at it, but I'm sure there are edge cases neither me or my tests have exposed. I'll be in IRC a lot, so if you see me (bitwalker), or ping me, I'll get back to you as soon as I'm able.

Give it a shot if you have time, and feel free to open issues so I can start working on your feedback.

https://github.com/bitwalker/timex

Thanks!

Paul

Paul Schoenfelder

unread,
Feb 26, 2014, 1:20:14 AM2/26/14
to elixir-l...@googlegroups.com
I should note, I've been building this on 0.12.5-dev, and I haven't tested on 0.13 yet, so let me know if it breaks on either 0.12.4 or 0.13, and I'll take a look.

Paul

Devin Torres

unread,
Feb 26, 2014, 1:22:53 PM2/26/14
to elixir-l...@googlegroups.com
Paul--

It seems this uses a single DateTime record to represent both Date's and Time's?

My beef with https://github.com/bitwalker/timex/blob/master/lib/date/datetime.ex is that the defaults are 0's. 0/0/0 00:00:00 is an actual time. How do you represent a Date but not a Time or a Time but not a Date? Think about a Calendar application, and you want to create a reminder using a Time that that repeats every week. Or you want to serialize a Postgres Date or Time type into something Elixir could use, or you're automatically deserializing ISO8601 dates from JSON ("2011-01-01") into a record Elixir could use.


--
You received this message because you are subscribed to the Google Groups "elixir-lang-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-ta...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Paul Schoenfelder

unread,
Feb 26, 2014, 1:45:37 PM2/26/14
to elixir-l...@googlegroups.com, de...@devintorr.es
That's an excellent point, representing recurring times is not currently supported well, I'll make that a priority. That may boil down to me creating a Date record, and a Time record, so that they can be represented individually.

One thing to be aware of, and the reason why DateTime's defaults are zeros, is that is usually how DateTime.MinValue is represented (at least in C# where I spend a great deal of time, and I believe that's pretty typical). I would imagine the vast majority of the time, Date.zero (the equivalent of DateTime.MinValue in Timex) is not considered a valid date in your application layer. It's also used so that if you initialize DateTime with only a couple properties, say year/month, i.e. DateTime[year: 2014, month: 2], the defaults are sane (00:00 UTC, first day of the month).

Regarding JSON support, this is supported using DateFormat.parse!. I should investigate Postgres though, perhaps Ecto has an implementation I can look at for working that angle. If you see anything missing in that area specifically, please let me know.

I would also be curious how you feel about the current setup with the future of Elixir and records potentially being deprecated. Are maps the next logical represenation, or do I return to using tuples? If you have thoughts about that, I'd like to hear them.

Devin Torres

unread,
Feb 26, 2014, 2:39:30 PM2/26/14
to elixir-l...@googlegroups.com
In my opinion, we definitely need separate structs for both Date and Time and then an amalgamated struct for DateTime. The reason is that if something like this is going to go into core, we need to be able to use these for protocols for our own libraries.

Today Ecto has Ecto.DateTime, but Postgrex also doesn't support DATE or TIME yet, so a separate Date and Time record was likely planned. I feel like Elixir could hobble along for 100 years with simply a standard Date, Time, and DateTime struct and no supporting library simply because library authors could use those structs and standardize behaviour.

Paul Schoenfelder

unread,
Feb 26, 2014, 2:47:12 PM2/26/14
to elixir-l...@googlegroups.com, de...@devintorr.es
I agree completely. I'll be working on this today to split out Date, Time, and then rewire DateTime to contain a Date, a Time struct, and a TimezoneInfo struct.

I briefly looked at both Ecto.DateTime and Postgrex, and it looks like it would be trivial to update those to use Date, Time, and DateTime structs where appropriate. It looks like Postgrex does actually support DATE and TIME, using Erlang date/time tuples.

Devin Torres

unread,
Feb 26, 2014, 2:56:38 PM2/26/14
to elixir-l...@googlegroups.com
Ah, right. It's Ecto that doesn't support Date and Time yet but I agree, it would be trivial for Ecto to be updated to use those separately once Postgrex exports Date and Time instead of tuples.

Nitpick, but I think you should just name the struct Timezone, the Info part is kind of assumed. (i.e. what else would it be?)

I'll continue to review what you have so far.

Paul Schoenfelder

unread,
Feb 26, 2014, 3:00:21 PM2/26/14
to elixir-l...@googlegroups.com, de...@devintorr.es
I was just looking into that since Date is already a module, making it a record seems like it would be a naming conflict, but it would be nice to have Date/Time/Timezone records. Is it possible to have both a Date module and a Date record, etc? I feel like I've seen that setup before, but I haven't done it myself. Am I imagining that?

Devin Torres

unread,
Feb 26, 2014, 3:09:10 PM2/26/14
to elixir-l...@googlegroups.com
Paul--

It's possible, but very frowned upon and it wont even work with structs. My recommendation is to rename the module to something clearer. A Date and Time make sense as "things" but not so much as a module that does something to those "things", IMHO.

Devin Torres

unread,
Feb 26, 2014, 3:11:13 PM2/26/14
to elixir-l...@googlegroups.com
I take that back-- It WILL work with structs, and wont be frowned upon when doing so (sorry, I had a brain fart). I can give you an example later tonight of how that would look.

Paul Schoenfelder

unread,
Feb 26, 2014, 3:12:50 PM2/26/14
to elixir-l...@googlegroups.com, de...@devintorr.es
Excellent, if you have prior art somewhere I can look at for reference that'll work too. Either that or I'll wait until I see your example.

Thanks!

Paul

Devin Torres

unread,
Feb 26, 2014, 3:14:56 PM2/26/14
to elixir-l...@googlegroups.com
defmodule Date do
  defstruct [:year, :month, :day]

  # Code to work with Date
end

%Date{} will reference the struct, while the Date module functions can expect a %Date{}.

Paul Schoenfelder

unread,
Feb 26, 2014, 3:17:13 PM2/26/14
to elixir-l...@googlegroups.com, de...@devintorr.es
I'm assuming defstruct is v0.13+? I don't mind switching over to 0.13 and building Timex on that if that's the case.

Devin Torres

unread,
Feb 26, 2014, 3:19:32 PM2/26/14
to elixir-l...@googlegroups.com
Correct, it's only v0.13 right now and requires Erlang 17.0-rc1 for maps support.

Paul Schoenfelder

unread,
Feb 26, 2014, 3:24:16 PM2/26/14
to elixir-l...@googlegroups.com, de...@devintorr.es
Ok, good to know. I haven't been tracking that branch yet, so I think it's probably best to switch over and start using that now so this lib is useful moving forward.

Devin Torres

unread,
Feb 26, 2014, 3:31:21 PM2/26/14
to elixir-l...@googlegroups.com
Just to confirm, the amalgamated DateTime will by default be UTC?

Paul Schoenfelder

unread,
Feb 26, 2014, 3:43:36 PM2/26/14
to elixir-l...@googlegroups.com, de...@devintorr.es
Yes that's correct, and that's true of any of the date functions as well. Unless you specify a timezone, or specifically request the local timezone, UTC is the default used.

José Valim

unread,
Feb 27, 2014, 7:03:01 AM2/27/14
to elixir-l...@googlegroups.com
This looks nice Paul! I have taken a quick a look at the project and I have some feedback.

1. Date.shift/1 is tricky. It happens that a day is not really "24 * 60 * 60 seconds". For example, if you are in a timezone boundary, it may happen to have 23h or 25h. It is possibly your normalize and construct steps actually fix those boundaries, but it is something that should be tested along side the tz information. Last year, iirc, there were was an area that lost a whole day, because they changed their timezones from -12 to +12 (or something similar)

2. I would cache the result of Timezone.local. You can cache it in the application environment with something like that:

  case :application.get_env(:timex, :tz) do
    {:ok, tz} -> tz
    :error -> :application.set_env(:timex, tz, do_local())
  end

3. I would recommend pre-parsing and compiling the timezone database into a module. Similar to how we do with unicode. It may be too large though, which means you can rather preparse it, save it to file using dets and then load it into ets.

PS: Sorry I am a bit light on the details but if you have any questions just hit me up on IRC. :)




José Valim
Skype: jv.ptec
Founder and Lead Developer

Paul Schoenfelder

unread,
Feb 27, 2014, 10:01:00 AM2/27/14
to elixir-l...@googlegroups.com, jose....@plataformatec.com.br
Hey José,

Thanks for taking the time to check it out!

1. Timezone.convert accounts for this when the timezone changes, but I'm not checking to see if two times within the same timezone are crossing a boundary. That was definitely an oversight, thanks for noticing that!

2. I am actually caching it, but in the process dictionary, but I like the idea of caching it in the app environment even better, I don't know why I didn't think of that.

3. It wouldn't take much to do that. I built the tzfile parser already, so I just need to add the tz database at this point, and write the compilation step.

I do have some questions though, regarding defstruct, that I ran into yesterday. Firstly, after reviewing the source for defstruct in Elixir (in lib/kernel.ex), it looks like there is no t() type being generated for a struct, so specs aren't able to use structs as a type. I've just commented out the specs I have that are checking for struct types, since I'm assuming that's just not complete yet. Secondly, there's some weirdness with pattern matching, and I haven't been able to find a single authoritative source on structs/maps, other than browsing Elixir's source and tests to see how it's being used there, but here's my problem:

So given the following simplified Time module/struct:

defmodule Time do
    defstruct hours: 0, mins: 0, secs: 0
    def new(hours, mins, secs), do: %Time{hours: hours, mins: mins, secs: secs}
    def parse_hours(%Time{:hours => h} = _time), do: h
end

This: Time.new(10, 5, 1) |> Time.parse_hours, gives me this:

** (FunctionClauseError) no function clause matching in Time.parse_hours/1
    iex:10: Time.parse_hours(%Time{hours: 10, mins: 5, secs: 1})

Am I doing this wrong? Are there docs around defstruct in the works?

Thanks!

Paul

José Valim

unread,
Feb 27, 2014, 10:05:36 AM2/27/14
to elixir-l...@googlegroups.com

So given the following simplified Time module/struct:

defmodule Time do
    defstruct hours: 0, mins: 0, secs: 0
    def new(hours, mins, secs), do: %Time{hours: hours, mins: mins, secs: secs}
    def parse_hours(%Time{:hours => h} = _time), do: h
end

This: Time.new(10, 5, 1) |> Time.parse_hours, gives me this:

Bug. Working on it, thanks!

Regarding the type, we don't have types for maps yet, so I would just say "t :: term" as a workaround an leave a to-do to revisit it later.

Paul Schoenfelder

unread,
Feb 27, 2014, 10:24:53 AM2/27/14
to elixir-l...@googlegroups.com, jose....@plataformatec.com.br
Sounds good, thanks! Are types for maps planned for 0.13's release, or is that coming later?

José Valim

unread,
Feb 27, 2014, 10:31:06 AM2/27/14
to elixir-l...@googlegroups.com
Bug has been fixed on v0.13 branch, thanks!

We should have a basic type on v0.13 release, yes.



José Valim
Skype: jv.ptec
Founder and Lead Developer


Paul Schoenfelder

unread,
Feb 27, 2014, 10:55:44 AM2/27/14
to elixir-l...@googlegroups.com, José Valim
Awesome, thanks for the fast turn around José, you rock!
 
Paul

From: José Valim José Valim
Reply: elixir-l...@googlegroups.com elixir-l...@googlegroups.com
Date: February 27, 2014 at 9:31:27 AM
To: elixir-l...@googlegroups.com elixir-l...@googlegroups.com
Subject:  Re: [elixir-talk:2944] Re: Timex - Date/Time library for Elixir
You received this message because you are subscribed to a topic in the Google Groups "elixir-lang-talk" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elixir-lang-talk/FzVciio2zRM/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elixir-lang-ta...@googlegroups.com.

Paul Schoenfelder

unread,
Feb 28, 2014, 9:15:46 AM2/28/14
to elixir-l...@googlegroups.com, José Valim
Hey José,

Haven't seen you on IRC or I would've ping you there, but I ran in to a problem with structs again that I think I just either need an explanation of why it doesn't work so I can make sure I work around it, or it's a bug, but either way I'm stumped. The following gist breaks it down:

https://gist.github.com/bitwalker/dac087beb771d5fffebf

The error I get is at the bottom. Hopefully it's just something stupid on my end, but it looks like I'm unable to do compile-time code generation using the struct of the current module. I tried just defining __struct__ instead of using defmacro, but that didn't make a difference.

Paul


From: Paul Schoenfelder Paul Schoenfelder
Reply: Paul Schoenfelder paulscho...@gmail.com
Date: February 27, 2014 at 9:55:45 AM
To: elixir-l...@googlegroups.com elixir-l...@googlegroups.com, José Valim jose....@plataformatec.com.br

Paul Schoenfelder

unread,
Feb 28, 2014, 4:25:29 PM2/28/14
to elixir-l...@googlegroups.com, José Valim
Hey José,

After our conversation in IRC earlier, I finally got a chance to give your solution a try. Unfortunately I'm getting a new error, and I've updated my gist to reflect the code changes and the exception I'm now getting. I'll be in IRC, so if you need anything, just ping me and I'll get back to you ASAP, or reply here if I'm not around for some reason. Thanks for the help!

https://gist.github.com/bitwalker/dac087beb771d5fffebf

-- 
Paul

From: Paul Schoenfelder Paul Schoenfelder
Reply: Paul Schoenfelder paulscho...@gmail.com
Date: February 28, 2014 at 8:15:59 AM

José Valim

unread,
Mar 1, 2014, 4:49:45 AM3/1/14
to elixir-l...@googlegroups.com
Paul, this is a bug in Erlang OTP and have reported it. The solution for now is to remove the update operator on line 31 and simply explicitly list all fields on the map.

Sorry for the bumps but it is really nice we are testing this now, otherwise we would find those bugs only after the release. :D Thanks a lot!



José Valim
Skype: jv.ptec
Founder and Lead Developer


José Valim

unread,
Mar 1, 2014, 4:50:26 AM3/1/14
to elixir-l...@googlegroups.com
Ah, you will need to do: defstruct Map.to_list(@tzstruct), I will push a better error message soon.



José Valim
Skype: jv.ptec
Founder and Lead Developer


Conrad Taylor

unread,
Jul 10, 2014, 4:27:54 PM7/10/14
to elixir-l...@googlegroups.com


On Tuesday, February 25, 2014 10:19:03 PM UTC-8, Paul Schoenfelder wrote:
Hey all,

So I completed my first pass at a date/time library finally, and now I'd like to get some feedback on improvements, things you like/don't like, etc - so I can make it as solid as possible, and hopefully something you'll all feel like using. Documentation in the README should be a decent intro, but I'll probably rework it a bit to cover the whole API soon. This *should* handle everything you throw at it, but I'm sure there are edge cases neither me or my tests have exposed. I'll be in IRC a lot, so if you see me (bitwalker), or ping me, I'll get back to you as soon as I'm able.

Give it a shot if you have time, and feel free to open issues so I can start working on your feedback.

https://github.com/bitwalker/timex

Thanks!

Paul

Paul, is this module/package being developed to be a part of the Elixir Standard Library?  Thanks in advance for any information that you can provide.

-Conrad
 

Paul Schoenfelder

unread,
Jul 10, 2014, 9:27:19 PM7/10/14
to elixir-l...@googlegroups.com
Hey Conrad!

I'm building/maintaining timex with the goal of at least being a starting point for the standard library implementation. Whether that ends up being based on timex code or not, I can't say. I will however continue maintaining and addressing issues/improvements until either José asks me to convert it to a standard library module, or one is created and integrated with Elixir by José/Eric or someone else. If you have any suggestions, improvements, issues, etc - definitely open an issue on the tracker and I'll be glad to address it.

Paul

Matthew Giannini

unread,
Dec 22, 2014, 4:06:50 PM12/22/14
to elixir-l...@googlegroups.com
Just wondering if timex is (still?) intended to become part of the Elixir core? If not, are there plans to add Time, DateTime support to the standard library, or should I plan on either rolling my own or depending on a third-party library?  

My $0.02 is these are really core (and touch to implement correctly) concepts needed by many applications, so having a reliable and maintained date/time library part of the core would be really useful. Thanks!

Paul Schoenfelder

unread,
Dec 22, 2014, 4:22:20 PM12/22/14
to elixir-l...@googlegroups.com
Timex was never guaranteed to become part of core, it's just been my goal to make it a viable candidate for merging at some point. Regardless, datetime support will be implemented at some point now that we're past the 1.0 release. I've talked with José already about what it would take to get Timex included into core, and there are a few things that have to be done before it would be viable.

If you are looking to work on getting datetime support into core, I know I could use some help with getting Timex ready. I've been unfortunately overloaded with my day job for some time now, so I've been limited to maintenance work for the past few months. The last couple of weeks I've been wrapping up the last big set of changes I had for Timex (better timezone support/timezone bugfixes), so once I'm done with that I can put more attention towards getting a 1.0 release of it out there in the wild. Feel free to reach out to me (paulschoenfelder at gmail) if you are interested in giving me a hand at all.

Paul


--
You received this message because you are subscribed to a topic in the Google Groups "elixir-lang-talk" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elixir-lang-talk/FzVciio2zRM/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elixir-lang-ta...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply all
Reply to author
Forward
0 new messages