13.3 读取日期和时间
问题
如何用本地的格式习惯来显示或读取时间和日期?
解决方案
为了读取日期和时间,可以使用来自<ctime>的time_t类型和tm结构以及来自<locale>中的时间和日期facet(一会之后就会讨论facet)。请看示例13-4中的例子。
示例13-4 读取日期
#include <iostream>
#include <ctime>
#include <locale>
#include <sstream>
#include <iterator>
using namespace std;
void translateDate(istream& in, ostream& out) {
// Create a date reader
const time_get<char>& dateReader =
use_facet<time_get<char> >(in.getloc());
// Create a state object, which the facets will use to tell
// us if there was a problem.
ios_base::iostate state = 0;
// End marker
istreambuf_iterator<char> end;
tm t; // Time struct (from <ctime>)
// Now that all that's out of the way, read in the date from
// the input stream and put it in a time struct.
dateReader.get_date(in, end, in, state, &t);
// Now the date is in a tm struct. Print it to the out stream
// using its locale. Make sure you only print out what you
// know is valid in t.
if (state == 0 || state == ios_base::eofbit) {
// The read succeeded.
const time_put<char>& dateWriter =
use_facet<time_put<char> >(out.getloc());
char fmt[] = "%x";
if (dateWriter.put(out, out, out.fill(),
&t, &fmt[0], &fmt[2]).failed())
cerr << "Unable to write to output stream.\n";
} else {
cerr << "Unable to read cin!\n";
}
}
int main() {
cin.imbue(locale("english"));
cout.imbue(locale("german"));
translateDate(cin, cout);
}
这个程序的输出如下:
3/28/2005
28.03.2005
讨论
读取日期和时间数据需要一些关于locale设计细节的知识。如果你还不熟悉locale和facet概念的话,请看一下13.0节。
C++并没有标准的类来代表时间和日期;与时间和日期最接近的就是来自<ctime>中的time_t类型和tm结构。如果你需要使用标准类库中的方法来读写日期,你将不得不把任何代表日期的非标准的形式转换成tm结构。这样做是值得的,因为你正在使用的实现很有可能给对于格式locale敏感的日期提供一些内在的支持。
前面,我说明了一个facet是一个locale的一方面,它需要特定于locale的行为。更精确地说,一个facet是为了某个字符类型的模板类的一个常量型实例,它查询基于你在构造函数传入的locale类上它是如何运行的。示例13-4中,我创建了这个time_get facet的一个实例,如下所示:
const time_get<char>& dateReader =
use_facet<time_get<char> >(in.getloc());
函数模板use_facet给一个给定的locale查找一个给定的facet。所有的这些标准的facet都是类模板,它接受一个字符类型的参数,并且由于我正读取和写入字符,我就为这些字符实例化了time_get类。这个标准要求一个实现为char和wchar_t类型提供模板的特例,因此就能保证它们能够退出来(尽管不能保证支持除了C locale之外的某个locale)。我创建的这个time_get对象是一个常量,因为由一个实现提供的locale功能是一组规则,这些规则是为了在不同的locale中格式化各类数据使用的,并且这些规则用户是不可编辑的,因此,一个给定的facet的状态是不应该被用户代码修改的。
我传给use_facet的这个locale是与我正要写入的流相关联的。getloc()声明在ios_base中并返回与一个输入或者输出流相关联的locale。使用这个已经与你要读入或者写入的流相关联的locale是最好的办法;以locale名字作为参数传递或者以其他别的办法指定都是容易出错的。
一旦我创建了马上要做事实上的读操作的对象,我就需要创建某些东西来捕获这个流的状态:
ios_base::iostate state = 0;
Facet不会自己修改流的状态,例如,set stream::failbit = 1;相反,它们将会在你的状态对象中设置状态来表明一个日期是不能读得。这是因为读取一个格式化值的失败不必一定会在流上产生问题 —— 这个字符的输入流仍然可以是有效的 —— 但是以你希望的格式读取它或许就不可能了。
这个事实上的日期信息存储在tm结构中,所有我不得不做的是创建一个本地的tm变量并且把它的地址传给time_get或time_put facet。
一旦我需要读取日期,我就能够查看这个我用来验证是否所有的这些都是以我希望的方式发生的状态变量。如果它的值等于0或者是ios_base::eofbit,那么这就表明这个流的状态是好的并且读出来的日期没有问题。在示例13-4中,由于我需要把日期写到另一个流中,我就不得不为这个目的创建一个这样的对象。如下所示:
const time_put<char>& dateWriter =
use_facet<time_put<char> >(out.getloc());
它和前面的time_get类具有相同的工作方式,只是方向不同而已。之后,我创建了一个格式字符串(使用于类printf格式语法),这个格式字符串将会打印日期。“%x”打印日期,并且“%X”打印时间。尽管这样,你还是应该仔细:这个例子仅是读取日期,因此这个不得不处理时间的tm结构的成员在这一点上是没有定义的。
现在,我可以写入到输出流。如下所示:
if (dateWriter.put(out, // Output stream iterator
out, // Output stream
out.fill(), // Fill char to use
&t, // Addr of tm struct
&fmt[0], // Begin and end of format string
&fmt[2]
).failed()) // iter_type.failed() tells us if
// there was a problem writing
time_put::put把日期写到你传入的输出流中,它(这个time_put对象)是使用这个locale来创建的。time_put::put返回一个ostreambuf_iterator迭代器,它有一个成员函数failed,你可以调用它来查看这个迭代器是否处在一个被损坏的状态中。
get_date并不是你可以用来从一个流中得到一个日期的各个部分的唯一成员函数。还有很多别的函数:
?get_date:使用一个locale中的格式规则来从一个流中获取日期。
?get_time:使用一个locale中的格式规则来从一个流中获取时间。
?get_weekday:获取一周中的每个名字,例如,星期一等。
?get_year:使用一个locale中的格式规则来从一个流中获取年份。
其他的你觉得方便的应该就是date_order成员函数。它返回一个枚举(在<locale>中的time_base::dateorde),这个枚举值表明在一个日期中的月、天和年的顺序。如果你不得不解析time_get::put函数输出的日期,这是有用的。示例13-5说明了该如何验证这个日期的顺序。
示例13-5 查找日期顺序
#include <iostream>
#include <locale>
#include <string>
using namespace std;
int main() {
cin.imbue(locale("german"));
const time_get<char>& dateReader =
use_facet<time_get<char> >(cin.getloc());
time_base::dateorder d = dateReader.date_order();
string s;
switch (d) {
case time_base::no_order:
s = "No order";
break;
case time_base::dmy:
s = "day/month/year";
break;
case time_base::mdy:
s = "month/day/year";
break;
case time_base::ymd:
s = "year/month/day";
break;
case time_base::ydm:
s = "year/day/month";
break;
}
cout << "Date order for locale " << cin.getloc().name()
<< " is " << s << endl;
}
当你初始化一个facet时,你还有一个方便的特性可以使用,那就是has_facet。它是一个函数模板,并且返回一个布尔值,这个布尔值表明这个需要的facet是否是为某个给定的locale定义的。因此,为安全起见,无论任何时候,当你需要初始化一个facet时都应利用这个has_facet。如果它返回false,你就可以一直使用默认的传统C的locale,因为它这个传统C的locale由遵循标准的实现保证了。has_facet的声明如下:
if (has_facet<time_put<char> >(loc)) {
const time_put<char>& dateWriter =
use_facet<time_put<char> >(loc);
一旦你熟悉了time_get和time_put类的语法,你就会发现它们使用起来是那么的直接。与以往一样,你也可以使用typedef来定义从而减少难看的尖括号的数量:
typedef time_put<char> TimePutNarrow;
typedef time_get<char> TimeGetNarrow;
// ...
const TimeGetNarrow& dateReader = use_facet<TimeGetNarrow>(loc);
以某个特定的locale格式来写或读日期是有点乏味的,但一旦你理解了你希望的locale后,它就是有效的和强大的。第5章有一个完整的关于日期和时间的主题,因此更多的关于时间和日期写入的信息,请参考5.2节。
请参考第5章和5.2节。






