Showing posts with label date-time. Show all posts
Showing posts with label date-time. Show all posts

2011-10-02

Representing Date and Time in computer programs. Part 2 (Java)

In this series of posts I will focus on how to do common date and time operations in Java world. Basically, I could give a link to Java Internationalization Trail, but it doesn't actually cover many Java-related technologies.

Part 1 – A brief look at the history: java.util.Date

This was Sun's first (failed) attempt to represent Date and Time. The class is as old as the whole JDK - it was here from JDK 1.0. Most of methods in this class are deprecated therefore I am not going to spend too much time on this topic.

Creating Date instances

To create Date instance and initialize it with a current time one would have used:

// Current time
Date now = new Date();

Although this constructor is still valid (as of JDK 7), I would strongly recommend using Calendar instead.
Another example of constructor that is not deprecated wold be:

// Unix time of the epoch - number of milliseconds
// since January 1st, 1970 in relation to GMT
Date epoch = new Date(1311211111011L);

Please note that this constructor takes parameter in relation to GMT and not UTC. There is a subtle but important difference between them. If you stand a chance, please always use UTC.

All the other constructors are deprecated:

// Sun Apr 24 2011 15:21:33 local time zone  
Date then = new Date(111, 3, 24, 15, 21, 33);

Please note that months are zero-indexed, so 0 is January, 1 is February and so on. This is very confusing, especially for beginners. I agree it does not make any sense but who am I to judge Sun developers?
Also, years need to be provided in relation to 1900, so for 2011 you need to pass 111. Now, that really sucks.

Formatting and parsing

Date class has built-in formatting method called toLocaleString(). That is the one you should never, ever use. It is deprecated for a reason, and the reason is it is simply invalid.
Date class also contains deprecated parse(String) method and you should not use it for the same reason. Instead, you should use DateFormat class and its derivatives.

DateFormat, SimpleDateFormat and FastDateFormat

DateFormat is an abstract class and its concrete implementation is SimpleDateFormat. To obtain default formatting/parsing style for default locale (the one you can get via Locale.getDefault() which is not valid for web applications), you can use:

  • DateFormat.getDateInstance() – to format just date part
  • DateFormat.getTimeInstance() – to format just time part
  • DateFormat.getDateTimeInstance() – to format both date and time

For web applications, you somehow need to know what is end user's preferred Locale. I will talk about this in depth in separate post someday as it is really hard to do correctly but to cut long story short you need something along the lines of web browser's Accept Language. When you have it, you can obtain formatter object by calling DateFormat.getDateInstance(int, Locale) method (or other similar method). Now, on the first parameter (int). This is related to date style. DateFormat class has several built-in style constants:

  • DateFormat.FULL – style containing all possible entries (for this language, often resolves to long format)
  • DateFormat.LONG – long style, full month names, full hours, etc.
  • DateFormat.MEDIUM – medium style, could contain abbreviated month names, 2 digit year, etc.
  • DateFormat.SHORT – shortest style form, usually the least information that allows to identify date and time
  • DateFormat.DEFAULT – default format for given Locale and the one that should be always used

It might sound as a pretty strong claim that you should always use default format but I wrote that for a reason. First of all, default is... well, default. This usually resolves to some kind of standardized date format for given Locale. Therefore it is something that International User should be able to understand naturally, without using additional brain cycles. Another thing is the fact that other (non-default) format definitions are often invalid (for example long format for Polish and Russian Locale) and default format should be always correct (unfortunately, should does not mean is). OK, let me give you coding example:

Date now = new Date();
// it is quite important to pass a country as format might differ
Locale polishLocale = new Locale("pl", "PL");
DateFormat dateFormatter = DateFormat.getDateInstance(
            DateFormat.DEFAULT, polishLocale);
// prints something like 2011-04-23
System.out.println(dateFormatter.format(now));
DateFormat timeFormatter = DateFormat.getTimeInstance(
            DateFormat.DEFAULT, polishLocale);
// prints something like 17:05:32
System.out.println(timeFormatter.format(now));
DateFormat dateTimeFormatter = DateFormat.getDateTimeInstance(
            DateFormat.DEFAULT, DateFormat.DEFAULT, polishLocale);
// prints something like 2011-04-25 18:58:20
System.out.println(dateTimeFormatter.format(now));
try {      
    Date parsedDate = dateFormatter.parse("2011-04-25");
    // Mon Apr 25 00:00:00 CEST 2011
    System.out.println(parsedDate);
    Date parsedTime = timeFormatter.parse("09:11:55");
    // Thu Jan 01 09:11:55 CET 1970
    System.out.println(parsedTime);
    Date parsedDateTime = dateTimeFormatter.parse("11-04-25 18:33:44");
    // Sat Apr 25 18:33:44 CET 11
    System.out.println(parsedDateTime);            
}
catch (ParseException pe) {
    pe.printStackTrace();
}

Notice how easy it is to get wrong results while parsing. Although for time parsing it actually make sense that it is time of the epoch related (this way you can perform time-related calculations using Date's getTime() and setTime() methods), it is just plainly wrong to parse "11-04-25" as year 11. No exception will be thrown, mind you. Quite painful gotcha. Of course it makes sense but still could result in programming error.
BTW. More experienced programmers know that DateFormat contains setLenient() method which allows you to control parse behavior – it should throw an exception if date is non-parse-able. The problem is, the default value is true (throw the exception) and no exception was thrown. Still it makes perfect sense but...

OK, now you know how to (more or less) correctly handle built-in Date and Time formats. But what to do when you need arbitrary format, for example ISO8601?
You can use either built-in SimpleDateFormat class or use Apache Commons Lang's FastDateFormat. Personally I would recommend the latter:

Date now = new Date();
String iso8601Pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'";
SimpleDateFormat iso8601Formatter =
    new SimpleDateFormat(iso8601Pattern);
TimeZone utcTimeZone = TimeZone.getTimeZone("UTC");
iso8601Formatter.setTimeZone(utcTimeZone);

Date now = new Date();
// prints something like 2011-04-28T18:27:08Z
System.out.println(iso8601Formatter.format(now));

FastDateFormat fastDateFormat =
    FastDateFormat.getInstance(iso8601Pattern, utcTimeZone);
// again prints out valid ISO8601 Date/Time String
System.out.println(fastDateFormat.format(now));


Converting time zones

In the previous paragraph, you probably noticed that I have been using java.util.TimeZone as the date formatter parameter. Frankly, if you just want to display correct time to end user you do not need anything else. Following example shows current time (in Tokyo time zone) formatted for Japanese user:

DateFormat dateFormat =
    DateFormat.getDateTimeInstance(
        DateFormat.DEFAULT,
        DateFormat.DEFAULT,
        Locale.JAPAN);
dateFormat.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo"));
System.out.println(dateFormat.format(new Date()));

OK, but what can you do if you need to perform Date calculations, for example you need to know what is a current Unix time of the epoch in some arbitrary time zone (as oppose to GMT)? You can use getTime() to obtain GMT-based time of the epoch and then use TimeZone class to obtain the target time zone offset:

TimeZone pacificTimeZone = TimeZone.getTimeZone("America/Los_Angeles");
long currentTime = new Date().getTime();
long convertedTime = currentTime +
    pacificTimeZone.getOffset(currentTime);

Performing date calculations like this literally sucks. How would you approach adding arbitrary number of days? By using getTime() and adding milliseconds? I will write a wrapper to do that - I can almost hear you saying it. Well, turns out somebody already did.

Apache Common Lang's DateUtils

Generally, Apache Commons Lang is always worth referencing and I usually have this on my projects' Class Path. If you need to perform many Date and Time related calculations, DateUtils class is the one that could save your day.

That concludes first part of Java Date and Time formatting, stay tuned for more (I trick myself into thinking I will actually finish these articles).



2010-10-10

Representing Date and Time in computer programs. Part 1 (theory)

Disclaimer

This post contains no code, just plain theory. If you feel comfortable with various calendars, date & time formats, you could safely skip it.

Background

People started to measure time... Stop. If you're like me, you don't have time to read such crap. I will try to present just the important information.
According to Wikipedia:
Artifacts from the Palaeolithic suggest that the moon was used to calculate time as early as 12,000, and possibly even 30,000 BC. Lunar calendars were among the first to appear, either 12 or 13 lunar months (either 346 or 364 days).
Another article says:
The Julian calendar, a reform of the Roman calendar, was introduced by Julius Caesar in 46 BC, and came into force in 45 BC (709 ab urbe condita).
Finally:
The Gregorian calendar, also known as (...) the Christian calendar, is the internationally accepted civil calendar. It was introduced by Pope Gregory XIII, after whom the calendar was named, by a decree signed on 24 February 1582, a papal bull known by its opening words Inter gravissimas.
Why I quoted this? Because I am trying to make a point. Throughout a history people used different calendars:
  • based on Moon phases or Solar year (or both, actually)
  • forced by political rulers
  • dependent on religious believes
In other words, time measuring & its representation was strictly tied to Culture.
And you know what? Turns out, it still is.

Date formats
10/10/10
Q: What date this represents?
A: That one was obvious, wasn't it? I meant Sunday, October 10, 2010 A.D.

However, the following date is not so obvious:
10/11/12
Is it October 11, 2012? Well, yes if you live in United States. However, if you live in Great Britain, it actually represents November 10, 2012. And if you live in Japan, it surely must be November 12, 2010. Since I live in Poland, these are simply three unrelated integers.
Tip: People interpret date formats according to their cultural background.
Q: Will representing year with for digits help?
A: Not so much.

Take a look at this date:
11/12/2010
Is it any better? Well, at least you could easily guess which year it refers to. However, month and day interpretation will still vary. What about other short date formats? Here are some examples:
08.10.2010 г. (Bulgaria)
2010/10/8 (Taiwan)
8.10.2010 (Czech Republic)
08-10-2010 (Denmark)
08.10.2010 (Germany)
2010. 10. 08. (Hungary)
2010-10-08 (Korea)
8-10-2010 (The Netherlands)
10/8/2010 (Kenya)
8/10/2010 (Australia)
08-10-10 (Bangladesh)
2010-10-8 Uyghur (People's Republic of China)
As you can see, we people tend to be very creative when it comes to formatting dates. We tend to create our own format instead of adopting just one common for all the mankind.
Tip: Software should display dates formatted the way current user expects it.
I will explain in details how to do that in the future, so stay tuned.

Q: So maybe I could use long date format to avoid misrepresentation?
A: Of course you can, but let me show you some examples:
9/شوال/1431 Arabic (Saudi Arabia)
08 Октомври 2010 г. Bulgarian (Bulgaria)
divendres, 8 / octubre / 2010 Catalan (Catalan)
2010年10月8日 Chinese (Taiwan)
8. října 2010 Czech (Czech Republic)
8. oktober 2010 Danish (Denmark)
Freitag, 8. Oktober 2010 German (Germany)
These dates have clearly one interpretation. (Un)fortunately, they need to be expressed in user's language.
Tip: No matter what date format (short, medium, long) you decided to use in your application, it should respect user's locale settings.
If you happen to understand Czech or Bulgarian language, you will notice something. This is apparently specific for Slavic languages and I don't know if it holds true for other language groups. Month name is expressed using genitive case as oppose to nominative case.
Tip: Never assume that target language holds the same grammar properties as English.
Unfortunately, this exactly what has been violated by JDK designers, so if you happen to use standard Java to format dates, avoid using long date format.

Time formats

As for time formats, we people have plenty of space for improvements.
09:08 م (Saudi Arabia)
下午 09:08 (Taiwan)
9:08 μμ (Greece)
9:08 PM (United States)
21:08 (France)
오후 9:08 (Korea)
9:08.MD (Albania)
09:08 ب.ظ (Iran)
ਸ਼ਾਮ 09:08 (India)
09:08 ܒ.ܛ (Syria)
PM 9:08 (Singapore)
Not so many differences after all. It seems that the mankind developed only two kind of time format, actually:
  • 12 hour time format (PM symbol and its placement differs)
  • 24 hour time format
In both cases trailing zeros will, or will not be displayed depending on culture. Although it seems that 24 hour time format will be recognizable by everyone, many people have strong preferences.
Tip: Format time in respect to current user's locale settings.
That way it will be easier to understand for the client.

Time zones and related issues

Up until now, we assumed that we know in which time zone our date & time lives. Therefore, following date-time string will regard to exactly one point in time, exactly the same in the whole wide world:
2010-05-20 19:54
Well, not necessary. The real problem here is, we have no reference to actual time zone. Therefore people living in India will interpret it in different way than people living in New Zealand and definitely different than people living in Central Europe. These differences range from 4 hours and 30 minutes to 11 hours and 45 minutes.
Tip: Present date & time in user's local time zone. If that is not possible, add reference to actual time zone.
This leads us to another issue:
1978-12-31 09:31 (Central European Standard Time)
How comprehensible is that? It depends. If you happen to live in California, it may be totally incomprehensible unless you know that usual difference between your current time zone and CEST is +9 hours. Usually, we don't memorize such values. And if we do, it will be easier to remember the name of the city than actual time zone name.
Tip: Avoid using standard time zone names. Use its UTC offset and short list of cities instead.
That said, it is much more readable this way:
1978-12-31 09:31 (UTC+01:00) Sarajevo, Skopje, Warsaw, Zagreb
Q: What the heck is UTC and what we need this for?
A: This is universal, coordinated time which specify exactly one, unique point in time. We need this in order to avoid time misrepresentation. If you're in doubt, take a look at this time:
2010-03-28 02:11 (UTC+01:00) Sarajevo, Skopje, Warsaw, Zagreb
Looks good, right? The only problem, it does not exist. This is related to Daylight Saving Time. Some dates are simply invalid in local time zones. Some are here twice. Without UTC there will be no way to avoid disambiguity of this date:
2010-10-31 02:16 (UTC+01:00) Sarajevo, Skopje, Warsaw, Zagreb
This date refers to one of two points in time:
2010-10-31 00:15 (UTC)
or
2010-10-31 01:15 (UTC)
That is just because, we're going to change time from 03:00 AM to 02:00 AM here in Europe, on this particular date.
Tip: To avoid disambiguity, always instantiate and store Date & Time objects in UTC. Convert it to user's local time before displaying it.
Now it's time to talk about interchangeability.

ISO 8601, serializing and exchanging date & time values

You probably noticed that I used specific date format in my examples. This has something to do with ISO 8601. ISO 8601 is a document that describes interchangeable date & time formats. It thoroughly describes how dates, times, periods and durations should be formatted in order to make them easily exchangeable.
Tip: If you need to serialize date & time values to string in order to store it or exchange it via network, always use one of ISO 8601's formats.
That said, I need to give you an example of valid ISO 8601 timestamp:
 2010-10-09T11:22Z
This points to Saturday, October 9, 2010 11:22 UTC.
Tip: Allegedly, YYYY-MM-DDThh:mmZ is most widely recognizable pattern for interchanging date & time values. If you cannot use strongly typed DateTime objects to store or exchange information, this format should be used instead.
I will explain it further in future posts, however I cannot do that without specific code examples.

Calendars

Till now, we assumed that there is only one, Gregorian calendar. Unfortunately, such an assumption is not correct. Have you noticed something strange about this:
 9/شوال/1431 Arabic (Saudi Arabia)
example?

Year seems to be somehow strange, isn't it? That's just because default calendar in Saudi Arabia is Islamic calendar.
Tip: Do not assume that Gregorian is default calendar for entire planet. Always present date & time values in accordance to user's local calendar.
There are also few other countries that defaults to non-Gregorian calendars. One of them is Thailand which defaults to Thai solar calendar (which is actually Gregorian calendar equivalent to some extent, but years are counted in a different way), another one is Israel which defaults to Hebrew calendar (actually it may not be the official calendar for Israel, but that's what you get by default when you install Hebrew version of Windows 2003, thus it is what user expects to see). There are few problems with Hebrew calendar. Apart from totally different number of years (according to Wikipedia Hebrew year 5771 has just began), Hebrew year could have 12 or 13 months (and totally different number of days as well).
Tip: Never assume that year have 12 months. It doesn't hold true for all calendars.
I could elaborate further about week numbering, year starting and so on, but this seems quite obvious.

Pretty time

Q: What's pretty time?
A: Nothing, actually. I named this section after Java library which allegedly allows for "pretty" timestamp formatting.

Sometimes, instead of this:
2010-09-09 11:44
you want this:
1 month ago
or
3 minutes ago
or
in 10 minutes
or
next year
or
in January
Et cetera.
This is nowhere near the easy task. I have already said about target language properties. There is more than one problem here, actually. Apart from Declension (i.e. "in January" would be "w styczniu" in Polish, which apparently uses Locative case) there is a problem with plural forms here. In English it is quite easy:
1 minute ago
2 minutes ago
5 minutes ago
However, if you translate it into Polish (or many other languages for that matter):
1 minutę temu
2 minuty temu
5 minut temu
Have you noticed something strange? There is more than one plural form of word "minute" when translated into Polish.
Tip: Never assume the number of plural forms the target language could have.
It is pretty strong, isn't it? Well, it just because we (the programmers) are not linguistic specialists and therefore we should not make any assumptions. Yes, fortunately it is possible.

Summary

In this post I wrote about cultural differences in date and time representations. I tried to be thorough and concise at the same time.
Due to these cultural implications, parsing and formatting date & time values is nowhere near the simple problem. It is no wonder, almost nobody got it right (unfortunately, Blogspot is one of the examples: it gave me plenty of choices how my articles and comments should be timestamped, but among them there where no correct option; It should be formatted as per browser locale).

What's next?

In future, I will try to explain how to correctly parse and format Date & Time values using Java, C#/.Net and C++. There are tons of i18n issues built into these languages (or supporting libraries) so stay tuned if you're interested.