1

I'm looking for some best practices to handle and store static time values.

A static time is usually the time of a recurring event, e.g. the activities in a sport centre, the opening times of a restaurant, the time a TV show is aired every day.

This time values are not bound to a specific date, and should not be affected by daylight saving time. For example, a restaurant will open at 11:00am both in winter and summer.

What's the best way to handle this situation? How should this kind of values be stored? I'm mainly interested in issues with automatic TimeZone and DST adjustments (that should be avoided), and in keeping the time values independent by any specific date.

The best strategies I've found so far are:

  1. store the time as an integer number of seconds since midnight,
  2. store the time as a string.

I did read this question, but it's mostly about the normal time values and not the use cases I described.

Update

The library I'm working on: github

Community
  • 1
  • 1
tompave
  • 11,952
  • 7
  • 37
  • 63

4 Answers4

2

Regarding database storage, consider the following in order from most preferred to least preferred option:

  • Use a TIME type if your database supports it, such as in SQL Server (2008 and greater), MySQL, and Postgres, or INTERVAL HOUR TO SECOND in Oracle.

  • Use separate integer fields for Hours and Minutes (and Seconds if you need them). Consider using a custom user-defined type to bind these together if your DB supports it.

  • Use string in 24-hour format with a leading zero, such as "01:23:00", "12:00:00" or "23:59:00". If you include seconds, then always include seconds. You want to keep the strings lexicographically sortable. Don't mix and match formatting. Be consistent.

Regarding the approach of storing a whole number of minutes (or seconds) elapsed since midnight, I recommend avoiding it. That works great when you are actually storing an elapsed duration of time, but not so great when storing a time of day. Consider:

  • Not every day has a midnight. In some time zones (ex: Brazil), on the day of the spring-forward DST transition, the clocks go from 23:59:59 to 01:00:00.

  • In any time zone that has DST, the "time elapsed since midnight" could be lying to you. Even when midnight exists, if you save 10:00 as "10 hours", then that's potentially a false statement. There may have been 9 hours or 11 hours elapsed since midnight, if you consider the two days per-year involved in DST transitions.

  • At some point in your application, you'll likely be applying this time-of-day value to some particular date. When you do, if you are using "elapsed time" semantics, you might be tempted to simply add the elapsed time to midnight of the date in question. That will lead to errors on DST transition days, for the reasons I just mentioned. If you are instead representing a "time of day" in your storage, you'll be more likely to combine them together properly. Of course, this is highly dependent on what language and API you are using.

With any of these, be careful when using recurrence patterns. Say you store a time of "02:00:00" when a bar closes every night. When DST springs forward, that time might not exist, and when it falls back, it will exist twice. You need to be prepared to check for this condition when you apply the time to any particular date.

What you should do is entirely up to your use case. In many situations, the sensible thing to do is to jump forward one hour in the spring-forward gap, and to pick the first of the two points in the fall-back overlap. But YMMV.

See also, the DST tag wiki.

Per comments, it looks like the "tod" gem will suffice for your Ruby code.

Community
  • 1
  • 1
Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • 1
    Thank you for the great insights. However, I'm afraid you are missing the point: I don't want to support DST or timezones. A restaurant that opens at 10:00 each morning will open at 10:00 in January or July, with or without DST. Even if the DST has started the previous night, the restaurant will __still__ open at 10:00. :-) – tompave Apr 01 '14 at 01:47
  • Also, the idea of saving it as an "integer number of seconds since midnight" comes from my need to store the data in a single DB column (MySQL or Postgres). It seems faster than using a VARCHAR. – tompave Apr 01 '14 at 01:49
  • I understand. And in regards to storage - any of these approaches would work. It's when you *apply* the time to a particular date that you have to be concerned with DST. So you store that the restaurant is open from 10:00 AM to 2:00 AM the next day (perhaps they have late-night dining or a bar). When you ask the question "When is the restaurant open *today*", that's when DST comes into play. – Matt Johnson-Pint Apr 01 '14 at 03:36
  • You *can* use integer number of seconds or minutes, but you should be careful, because when applying it you can't just add that time to midnight. You also have to adjust for DST. It's much easier to construct the time separately, then combine it (not add it) with the date. – Matt Johnson-Pint Apr 01 '14 at 03:38
  • That's very good advice, thanks. I'm planning to not combine these time values with a date (I see them as static labels), but what you are describing is definitely something I should take into account. For example, I'm implementing methods to convert them to full-fledged Time objects, and that might be a critical operation. – tompave Apr 01 '14 at 07:57
  • If you mean [Ruby's Time class](http://www.ruby-doc.org/core-2.1.1/Time.html), then yes. Since that is seconds since the epoch, then it is bound to a date. – Matt Johnson-Pint Apr 01 '14 at 16:06
  • Given everything you've told me, I recommend the [`time` (without time zone) type in Postgres](http://www.postgresql.org/docs/9.3/static/datatype-datetime.html), or the [`TIME` type in MySQL](https://dev.mysql.com/doc/refman/5.7/en/time.html) – Matt Johnson-Pint Apr 01 '14 at 16:10
  • Right. I had looked at them, and they provide what I'm looking for. I will probably end up using them. What concerns me is that Ruby doesn't have a concept of _time without date_; as soon as these MySQL/Postgres time values are read from the DB and instantiated as `Time` objects, they will be subject to all the automatic adjustments I'm trying to avoid. These adjustments are provided by RubyOnRails. – tompave Apr 01 '14 at 18:30
  • So, yes, they seem the best option, but to keep them _date-less_ I'll still need the library I'm working on. :-) -- the only reason they were not my first choice is that I wanted the lib to be DB/ORM agnostic, hence `VARCHAR` and `INT` sounded better. – tompave Apr 01 '14 at 18:33
  • I didn't know that gem. Looks great. – tompave Apr 01 '14 at 19:42
1

The question seems a little vague, but I will have a try.

Generally speaking, using an integer seems good enough for me. It is easy to compare, easy to add or subtract a duration (of seconds), and is space- and time-efficient. You can consider wrapping it in a class if you are using an object-oriented language.

As far as I know, there are no existing classes for your needs in C or C++.

In the .NET world, the TimeSpan class may be useful for your purpose. It has some conveniences, like: you can get the TimeSpan value from DateTime.TimeOfDay; you can add the TimeSpan with an interval (a TimeSpan); you can get the hours, minutes, and seconds components separately; etc.

If you use Python, datime.time is also a good candidate. It is designed exactly for usages like yours.

I do not know other good candidates in other languages.

Yongwei Wu
  • 5,292
  • 37
  • 49
  • thanks. Python's `datetime.time` sounds exactly what I'm looking for. Too bad I work with Ruby. And yes, I wrap the integer representation with a class that exposes a Time-like interface. – tompave Mar 31 '14 at 11:51
  • @tompave I had a quick check and did not find any "standard" Ruby classes that meet your requirements.... – Yongwei Wu Mar 31 '14 at 11:59
1

Speaking for Java:

In Java, the use-cases you describe are not covered well by old java.util.Date (which is a global timestamp despite of its name) or java.util.GregorianCalendar (which is a kind of combination of date and time and zone etc.), but:

In Java 8 you have the new built-in class java.time.LocalTime which covers your use-cases well. Predecessor is the equally-named class LocalTime in the external and popular Java library JodaTime which is working since Java 5. Furthermore, in my own alpha-state-library I have the type net.time4j.PlainTime which is similar, but also offers 24:00-support (good for example for shop opening times). All in all Java is a well suited language with interesting time libraries which can mostly do what you wish. In detail:

a) TimeZone and DST adjustments are not handled by the Java classes mentioned above. Instead they are only handled if you convert such a plain wall time to another type like org.joda.time.DateTime which contains a reference to a timezone.

b) Indeed these time classes are completely independent from calendar date, too.

c) The internal storage strategy is for JSR-310 (Java 8):

private final byte hour;
private final byte minute;
private final byte second;
private final int nano;

JodaTime uses the other strategy of local milliseconds instead (elapsed time since midnight).

Meno Hochschild
  • 42,708
  • 7
  • 104
  • 126
  • Thanks. I haven't worked with Java since the early days of Java 5, but I see what you mean. The details on the internal storage strategy are especially useful, because I'm implementing something similar in Ruby. Ideally, I would like to store it in a single DB column. – tompave Mar 31 '14 at 16:25
0

You cannot represent a time unless you also know the day/month/year. There is no such thing as "should not be affected by daylight saving time" as there are many complicated issues to deal with, including leap seconds and so on. Time, as a human sees it, is a complicated thing that cannot easily be dealt with mathematically.

If you really need to store "11am" without any date associated, then that's what you should store. Just store 11am (or perhaps just 11, use 24 hour time).

Then, if you need to do any math you must apply a date before doing any operations on the time.

I would also refrain from storing "11am" as "x seconds from midnight". You really should just use 11 hours, since that is what the user sees, and then have a good date/time library convert it to a useful format. For example, telling the user if the restaurant is open right now you'd pass it to a date library with today's date.

Abhi Beckert
  • 32,787
  • 12
  • 83
  • 110
  • Thanks for the insights. However I think that Python has a library that does exactly what I described. – tompave Apr 01 '14 at 01:42
  • @tompave then why don't you post that as an answer? – Abhi Beckert Apr 01 '14 at 02:33
  • It was mentioned in one of the answers, but it's not very helpful when it comes to implementation strategies. A lot of people are adding interesting observations, though. – tompave Apr 01 '14 at 07:43