Working with Timezone

I’m always relying Ruby on Rails to build web application, I think it’s not enough for a developer because Rails has already handled all the things actually I need to know how to deal with by myself.

Since this project I’m doing during my summer internship, I decide to create web service with Sinatra framework, which is a very simple micro framework to build web application. Sinatra provide limited features for you. There are many details you need to notice if you switch from Rails.

System

The time of computer is offered by OS(I don’t want to dig the details of hardware.), so let’s check how to get the system time. There are many functions to do that, here are ones for Linux and OS X I find in StackOverflow.

  • [time()] returns the wall-clock time from the OS, with precision in seconds.
  • [clock()] seems to return the sum of user and system time. At one time this was supposed to be the CPU time in cycles, but modern standards require CLOCKS_PER_SEC to be 1000000, giving a maximum possible precision of 1 µs. The precision on my system is indeed 1 µs. This clock wraps around once it tops out (this typically happens after ~2^32 ticks, which is not very long for a 1 MHz clock).
  • [clock_gettime(CLOCK_MONOTONIC, …)] provides nanosecond resolution, is monotonic. I believe the ‘seconds’ and ‘nanoseconds’ are stored separately, each in 32-bit counters. Thus, any wrap-around would occur after many dozen years of uptime. This looks like a very good clock, but unfortunately it isn’t yet available on OS X.
  • [getrusage()] turned out to be the best choice for my situation. It reports the user and system times separately and does not wrap around. The precision on my system is 1 µs, but I also tested it on a Linux system (Red Hat 4.1.2-48 with GCC 4.1.2) and there the precision was only 1 ms.
  • [gettimeofday()] returns the wall-clock time with (nominally) µs precision. On my system this clock does seem to have µs precision, but this is not guaranteed, because “the resolution of the system clock is hardware dependent”.
  • [mach_absolute_time()] is an option for very high resolution (ns) timing on OS X. On my system, this does indeed give ns resolution. In principle this clock wraps around, however it is storing ns using a 64-bit unsigned integer, so the wrapping around shouldn’t be an issue in practice. Portability is questionable.

Library

We know there is a Time class in Ruby standard library. And I find the related code for Time.now in Rubinius.

 Time* Time::now(STATE, Object* self) {
    Time* tm = state->new_object_dirty<Time>(as<Class>(self));

#ifdef HAVE_CLOCK_GETTIME
    struct timespec ts;

    ::clock_gettime(CLOCK_REALTIME, &ts);

    tm->seconds_ = ts.tv_sec;
    tm->nanoseconds_ = ts.tv_nsec;
#else
    struct timeval tv;

    /* don't fill in the 2nd argument here. getting the timezone here
     * this way is not portable and broken anyway.
     */
    ::gettimeofday(&tv, NULL);

    tm->seconds_ = tv.tv_sec;
    tm->nanoseconds_ = tv.tv_usec * 1000;
#endif

    tm->decomposed(state, nil<Array>());
    tm->is_gmt(state, cFalse);
    tm->offset(state, nil<Fixnum>());
    tm->zone(state, nil<String>());

    return tm;
  }

We can see Ruby use clock_gettime or gettimeofday to get the system time. The time object return by method Time.now has no timezone information. But you will ask why I see thing like this 2015-08-14 15:20:41 -0400 in rib, that’s because Ruby will call the to_s method to display object to output. For Time class, the strftime method will be invoked, which handle the local timezone information.

String* Time::strftime(STATE, String* format) {
   struct tm64 tm = get_tm();

   struct timespec64 ts;
   ts.tv_sec = seconds_;
   ts.tv_nsec = nanoseconds_;

   int off = 0;
   if(Fixnum* offset = try_as<Fixnum>(utc_offset(state))) {
     off = offset->to_int();
   }

   if(format->byte_size() == 0) return String::create(state, NULL, 0);

   char stack_str[STRFTIME_STACK_BUF];

   size_t chars = ::strftime_extended(stack_str, STRFTIME_STACK_BUF,
                      format->c_str(state), &tm, &ts, CBOOL(is_gmt_) ? 1 : 0,
                     off);

  size_t buf_size = format->byte_size();

  String* result = 0;

  if(chars == 0) {
    buf_size *= 2;
    char* malloc_str = (char*)malloc(buf_size);

    chars = ::strftime_extended(malloc_str, buf_size,
                format->c_str(state), &tm, &ts, CBOOL(is_gmt_) ? 1 : 0,
                off);
    if(chars) {
      result = String::create(state, malloc_str, chars);
      result->encoding(state, format->encoding());
    }

    free(malloc_str);
  } else {
    result = String::create(state, stack_str, chars);
    result->encoding(state, format->encoding());
  }

  return result;
}

Web Framework

If we start a web application with Rails, we all know to set the time zone with config.time_zone, this configuration set the time zone of your application, and library ActiveSupport add some methods to get the time with the set time zone. Time.zone.now get the time at NYC if you have set the time zone through Time.zone = "Eastern Time (US & Canada)" or config.time_zone = "Eastern Time (US & Canada)". Or you can use Time.current to get the time of NYC if the time zone is set, otherwise you will get result of Time.now, which use your system time zone.

Here is the now method in TimeZone class of ActiveSupport.

# Returns an ActiveSupport::TimeWithZone instance representing the current
# time in the time zone represented by +self+.
#
#   Time.zone = 'Hawaii'  # => "Hawaii"
#   Time.zone.now         # => Wed, 23 Jan 2008 20:24:27 HST -10:00
def now
  time_now.utc.in_time_zone(self)
en

And the core extension of Time class.

def current
  ::Time.zone ? ::Time.zone.now : ::Time.now
end

Database

In web application, we use database to persist data. In Ruby world, thanks to ActiveRecord, the database ORM is so easy. But what about the time zone of timestamps stored in database?

ActiveRecord has a config to set the time zone, but it’s the time zone to store and parse the timestamps in database, the database has no idea with which time zone is it. ActiveRecord firstly exchange your passed time with the set time zone before store to database, and parse the time with that time zone after retrieve from database.

This is a description I find in postgresql documentation indicates that it database treats all timestamp as in UTC.

For timestamp with time zone, the internally stored value is always in UTC (Universal Coordinated Time, traditionally known as Greenwich Mean Time, GMT). An input value that has an explicit time zone specified is converted to UTC using the appropriate offset for that time zone. If no time zone is stated in the input string, then it is assumed to be in the time zone indicated by the system’s TimeZone parameter, and is converted to UTC using the offset for the timezone zone

The best practice is to use UTC with ActiveRecord.

Conclusion

One word to summarize the time zone stuff in development is, to unify your time zones in all layers of your software, and UTC is the best option.