diff options
author | brettw@chromium.org <brettw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-26 02:53:36 +0000 |
---|---|---|
committer | brettw@chromium.org <brettw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-26 02:53:36 +0000 |
commit | a4a3292e978cca3ad8c0baa5205054b5b3802e64 (patch) | |
tree | 9490d74f9760c4b841f1188e13b1a91db374c327 | |
parent | 67d0d62d638f7b15e031dd2c22756df0109e021d (diff) | |
download | chromium_src-a4a3292e978cca3ad8c0baa5205054b5b3802e64.zip chromium_src-a4a3292e978cca3ad8c0baa5205054b5b3802e64.tar.gz chromium_src-a4a3292e978cca3ad8c0baa5205054b5b3802e64.tar.bz2 |
Convert internal time format to Windows 1601 epoch on Linux & Mac.
Although we represent time internally starting from 1601, there are still
things like time explosion that will not work before the year 1900. This
limitation is the same as it was previously.
BUG=14734
Review URL: http://codereview.chromium.org/173296
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@24417 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | base/time.h | 9 | ||||
-rw-r--r-- | base/time_mac.cc | 34 | ||||
-rw-r--r-- | base/time_posix.cc | 76 | ||||
-rw-r--r-- | base/time_unittest.cc | 30 | ||||
-rw-r--r-- | chrome/browser/bookmarks/bookmark_codec.cc | 18 | ||||
-rw-r--r-- | chrome/browser/history/history_backend.cc | 13 | ||||
-rw-r--r-- | chrome/browser/history/history_database.cc | 51 | ||||
-rw-r--r-- | chrome/browser/history/history_database.h | 21 | ||||
-rw-r--r-- | chrome/browser/password_manager/password_store_mac_unittest.cc | 4 | ||||
-rw-r--r-- | chrome/test/data/profiles/typical_history/Default/History | bin | 13770752 -> 13783040 bytes | |||
-rw-r--r-- | net/base/cookie_monster_unittest.cc | 11 |
11 files changed, 205 insertions, 62 deletions
diff --git a/base/time.h b/base/time.h index beeca27..a57c04f 100644 --- a/base/time.h +++ b/base/time.h @@ -178,6 +178,15 @@ class Time { static const int64 kNanosecondsPerSecond = kNanosecondsPerMicrosecond * kMicrosecondsPerSecond; +#if !defined(OS_WIN) + // On Mac & Linux, this value is the delta from the Windows epoch of 1601 to + // the Posix delta of 1970. This is used for migrating between the old + // 1970-based epochs to the new 1601-based ones. It should be removed from + // this global header and put in the platform-specific ones when we remove the + // migration code. + static const int64 kWindowsEpochDeltaMicroseconds; +#endif + // Represents an exploded time that can be formatted nicely. This is kind of // like the Win32 SYSTEMTIME structure or the Unix "struct tm" with a few // additions and changes to prevent errors. diff --git a/base/time_mac.cc b/base/time_mac.cc index 3e5e14a..6b46b95 100644 --- a/base/time_mac.cc +++ b/base/time_mac.cc @@ -24,20 +24,33 @@ namespace base { // Time ----------------------------------------------------------------------- -// The internal representation of Time uses a 64-bit microsecond count -// from 1970-01-01 00:00:00 UTC. Core Foundation uses a double second count -// since 2001-01-01 00:00:00 UTC. +// Core Foundation uses a double second count since 2001-01-01 00:00:00 UTC. +// The UNIX epoch is 1970-01-01 00:00:00 UTC. +// Windows uses a Gregorian epoch of 1601. We need to match this internally +// so that our time representations match across all platforms. See bug 14734. +// irb(main):010:0> Time.at(0).getutc() +// => Thu Jan 01 00:00:00 UTC 1970 +// irb(main):011:0> Time.at(-11644473600).getutc() +// => Mon Jan 01 00:00:00 UTC 1601 +static const int64 kWindowsEpochDeltaSeconds = GG_INT64_C(11644473600); +static const int64 kWindowsEpochDeltaMilliseconds = + kWindowsEpochDeltaSeconds * Time::kMillisecondsPerSecond; -// Some functions in time.cc use time_t directly, so we provide a zero offset -// for them. The epoch is 1970-01-01 00:00:00 UTC. // static -const int64 Time::kTimeTToMicrosecondsOffset = GG_INT64_C(0); +const int64 Time::kWindowsEpochDeltaMicroseconds = + kWindowsEpochDeltaSeconds * Time::kMicrosecondsPerSecond; + +// Some functions in time.cc use time_t directly, so we provide an offset +// to convert from time_t (Unix epoch) and internal (Windows epoch). +// static +const int64 Time::kTimeTToMicrosecondsOffset = kWindowsEpochDeltaMicroseconds; // static Time Time::Now() { CFAbsoluteTime now = CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970; - return Time(static_cast<int64>(now * kMicrosecondsPerSecond)); + return Time(static_cast<int64>(now * kMicrosecondsPerSecond) + + kWindowsEpochDeltaMicroseconds); } // static @@ -61,13 +74,14 @@ Time Time::FromExploded(bool is_local, const Exploded& exploded) { time_zone(is_local ? CFTimeZoneCopySystem() : NULL); CFAbsoluteTime seconds = CFGregorianDateGetAbsoluteTime(date, time_zone) + kCFAbsoluteTimeIntervalSince1970; - return Time(static_cast<int64>(seconds * kMicrosecondsPerSecond)); + return Time(static_cast<int64>(seconds * kMicrosecondsPerSecond) + + kWindowsEpochDeltaMicroseconds); } void Time::Explode(bool is_local, Exploded* exploded) const { CFAbsoluteTime seconds = - (static_cast<double>(us_) / kMicrosecondsPerSecond) - - kCFAbsoluteTimeIntervalSince1970; + (static_cast<double>((us_ - kWindowsEpochDeltaMicroseconds) / + kMicrosecondsPerSecond) - kCFAbsoluteTimeIntervalSince1970); scoped_cftyperef<CFTimeZoneRef> time_zone(is_local ? CFTimeZoneCopySystem() : NULL); diff --git a/base/time_posix.cc b/base/time_posix.cc index 8b04be9..66f41d3 100644 --- a/base/time_posix.cc +++ b/base/time_posix.cc @@ -4,9 +4,6 @@ #include "base/time.h" -#ifdef OS_MACOSX -#include <mach/mach_time.h> -#endif #include <sys/time.h> #include <time.h> @@ -23,10 +20,24 @@ namespace base { // Time ----------------------------------------------------------------------- -// Some functions in time.cc use time_t directly, so we provide a zero offset -// for them. The epoch is 1970-01-01 00:00:00 UTC. +// Windows uses a Gregorian epoch of 1601. We need to match this internally +// so that our time representations match across all platforms. See bug 14734. +// irb(main):010:0> Time.at(0).getutc() +// => Thu Jan 01 00:00:00 UTC 1970 +// irb(main):011:0> Time.at(-11644473600).getutc() +// => Mon Jan 01 00:00:00 UTC 1601 +static const int64 kWindowsEpochDeltaSeconds = GG_INT64_C(11644473600); +static const int64 kWindowsEpochDeltaMilliseconds = + kWindowsEpochDeltaSeconds * Time::kMillisecondsPerSecond; + +// static +const int64 Time::kWindowsEpochDeltaMicroseconds = + kWindowsEpochDeltaSeconds * Time::kMicrosecondsPerSecond; + +// Some functions in time.cc use time_t directly, so we provide an offset +// to convert from time_t (Unix epoch) and internal (Windows epoch). // static -const int64 Time::kTimeTToMicrosecondsOffset = GG_INT64_C(0); +const int64 Time::kTimeTToMicrosecondsOffset = kWindowsEpochDeltaMicroseconds; // static Time Time::Now() { @@ -36,8 +47,10 @@ Time Time::Now() { DCHECK(0) << "Could not determine time of day"; } // Combine seconds and microseconds in a 64-bit field containing microseconds - // since the epoch. That's enough for nearly 600 centuries. - return tv.tv_sec * kMicrosecondsPerSecond + tv.tv_usec; + // since the epoch. That's enough for nearly 600 centuries. Adjust from + // Unix (1970) to Windows (1601) epoch. + return Time((tv.tv_sec * kMicrosecondsPerSecond + tv.tv_usec) + + kWindowsEpochDeltaMicroseconds); } // static @@ -100,13 +113,17 @@ Time Time::FromExploded(bool is_local, const Exploded& exploded) { milliseconds = seconds * kMillisecondsPerSecond + exploded.millisecond; } - return Time(milliseconds * kMicrosecondsPerMillisecond); + // Adjust from Unix (1970) to Windows (1601) epoch. + return Time((milliseconds * kMicrosecondsPerMillisecond) + + kWindowsEpochDeltaMicroseconds); } void Time::Explode(bool is_local, Exploded* exploded) const { // Time stores times with microsecond resolution, but Exploded only carries - // millisecond resolution, so begin by being lossy. - int64 milliseconds = us_ / kMicrosecondsPerMillisecond; + // millisecond resolution, so begin by being lossy. Adjust from Windows + // epoch (1601) to Unix epoch (1970); + int64 milliseconds = (us_ - kWindowsEpochDeltaMicroseconds) / + kMicrosecondsPerMillisecond; time_t seconds = milliseconds / kMillisecondsPerSecond; struct tm timestruct; @@ -127,38 +144,13 @@ void Time::Explode(bool is_local, Exploded* exploded) const { // TimeTicks ------------------------------------------------------------------ +#if defined(OS_POSIX) && \ + defined(_POSIX_MONOTONIC_CLOCK) && _POSIX_MONOTONIC_CLOCK >= 0 + // static TimeTicks TimeTicks::Now() { uint64_t absolute_micro; -#if defined(OS_MACOSX) - static mach_timebase_info_data_t timebase_info; - if (timebase_info.denom == 0) { - // Zero-initialization of statics guarantees that denom will be 0 before - // calling mach_timebase_info. mach_timebase_info will never set denom to - // 0 as that would be invalid, so the zero-check can be used to determine - // whether mach_timebase_info has already been called. This is - // recommended by Apple's QA1398. - kern_return_t kr = mach_timebase_info(&timebase_info); - DCHECK(kr == KERN_SUCCESS); - } - - // mach_absolute_time is it when it comes to ticks on the Mac. Other calls - // with less precision (such as TickCount) just call through to - // mach_absolute_time. - - // timebase_info converts absolute time tick units into nanoseconds. Convert - // to microseconds up front to stave off overflows. - absolute_micro = mach_absolute_time() / Time::kNanosecondsPerMicrosecond * - timebase_info.numer / timebase_info.denom; - - // Don't bother with the rollover handling that the Windows version does. - // With numer and denom = 1 (the expected case), the 64-bit absolute time - // reported in nanoseconds is enough to last nearly 585 years. - -#elif defined(OS_POSIX) && \ - defined(_POSIX_MONOTONIC_CLOCK) && _POSIX_MONOTONIC_CLOCK >= 0 - struct timespec ts; if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) { NOTREACHED() << "clock_gettime(CLOCK_MONOTONIC) failed."; @@ -169,13 +161,13 @@ TimeTicks TimeTicks::Now() { (static_cast<int64>(ts.tv_sec) * Time::kMicrosecondsPerSecond) + (static_cast<int64>(ts.tv_nsec) / Time::kNanosecondsPerMicrosecond); + return TimeTicks(absolute_micro); +} + #else // _POSIX_MONOTONIC_CLOCK #error No usable tick clock function on this platform. #endif // _POSIX_MONOTONIC_CLOCK - return TimeTicks(absolute_micro); -} - // static TimeTicks TimeTicks::HighResNow() { return Now(); diff --git a/base/time_unittest.cc b/base/time_unittest.cc index ebe69eb..f8a62cb 100644 --- a/base/time_unittest.cc +++ b/base/time_unittest.cc @@ -66,9 +66,10 @@ TEST(Time, LocalExplode) { Time b = Time::FromLocalExploded(exploded); - // The exploded structure doesn't have microseconds, so the result will be - // rounded to the nearest millisecond. - EXPECT_TRUE((a - b) < TimeDelta::FromMilliseconds(1)); + // The exploded structure doesn't have microseconds, and on Mac & Linux, the + // internal OS conversion uses seconds, which will cause truncation. So we + // can only make sure that the delta is within one second. + EXPECT_TRUE((a - b) < TimeDelta::FromSeconds(1)); } TEST(Time, UTCExplode) { @@ -77,7 +78,7 @@ TEST(Time, UTCExplode) { a.UTCExplode(&exploded); Time b = Time::FromUTCExploded(exploded); - EXPECT_TRUE((a - b) < TimeDelta::FromMilliseconds(1)); + EXPECT_TRUE((a - b) < TimeDelta::FromSeconds(1)); } TEST(Time, LocalMidnight) { @@ -140,3 +141,24 @@ TEST(TimeDelta, FromAndIn) { EXPECT_EQ(13.0, TimeDelta::FromMilliseconds(13).InMillisecondsF()); EXPECT_EQ(13, TimeDelta::FromMicroseconds(13).InMicroseconds()); } + +// Our internal time format is serialized in things like databases, so it's +// important that it's consistent across all our platforms. We use the 1601 +// Windows epoch as the internal format across all platforms. +TEST(TimeDelta, WindowsEpoch) { + Time::Exploded exploded; + exploded.year = 1970; + exploded.month = 1; + exploded.day_of_week = 0; // Should be unusued. + exploded.day_of_month = 1; + exploded.hour = 0; + exploded.minute = 0; + exploded.second = 0; + exploded.millisecond = 0; + Time t = Time::FromUTCExploded(exploded); + // Unix 1970 epoch. + EXPECT_EQ(GG_INT64_C(11644473600000000), t.ToInternalValue()); + + // We can't test 1601 epoch, since the system time functions on Linux + // only compute years starting from 1900. +} diff --git a/chrome/browser/bookmarks/bookmark_codec.cc b/chrome/browser/bookmarks/bookmark_codec.cc index cae5f22..ebf48db 100644 --- a/chrome/browser/bookmarks/bookmark_codec.cc +++ b/chrome/browser/bookmarks/bookmark_codec.cc @@ -200,6 +200,21 @@ bool BookmarkCodec::DecodeNode(const DictionaryValue& value, std::wstring date_added_string; if (!value.GetString(kDateAddedKey, &date_added_string)) date_added_string = Int64ToWString(Time::Now().ToInternalValue()); + base::Time date_added = base::Time::FromInternalValue( + StringToInt64(WideToUTF16Hack(date_added_string))); +#if !defined(OS_WIN) + // We changed the epoch for dates on Mac & Linux from 1970 to the Windows + // one of 1601. We assume any number we encounter from before 1970 is using + // the old format, so we need to add the delta to it. + // + // This code should be removed at some point: + // http://code.google.com/p/chromium/issues/detail?id=20264 + if (date_added.ToInternalValue() < + base::Time::kWindowsEpochDeltaMicroseconds) { + date_added = base::Time::FromInternalValue(date_added.ToInternalValue() + + base::Time::kWindowsEpochDeltaMicroseconds); + } +#endif std::wstring type_string; if (!value.GetString(kTypeKey, &type_string)) @@ -255,8 +270,7 @@ bool BookmarkCodec::DecodeNode(const DictionaryValue& value, } node->SetTitle(title); - node->set_date_added(Time::FromInternalValue( - StringToInt64(WideToUTF16Hack(date_added_string)))); + node->set_date_added(date_added); return true; } diff --git a/chrome/browser/history/history_backend.cc b/chrome/browser/history/history_backend.cc index 1ccd7cf..0d01496 100644 --- a/chrome/browser/history/history_backend.cc +++ b/chrome/browser/history/history_backend.cc @@ -529,6 +529,13 @@ void HistoryBackend::InitImpl() { LOG(WARNING) << "Text database initialization failed, running without it."; text_database_.reset(); } + if (db_->needs_version_17_migration()) { + // See needs_version_17_migration() decl for more. In this case, we want + // to erase all the text database files. This must be done after the text + // database manager has been initialized, since it knows about all the + // files it manages. + text_database_->DeleteAll(); + } // Thumbnail database. thumbnail_db_.reset(new ThumbnailDatabase()); @@ -544,6 +551,12 @@ void HistoryBackend::InitImpl() { } // Archived database. + if (db_->needs_version_17_migration()) { + // See needs_version_17_migration() decl for more. In this case, we want + // to delete the archived database and need to do so before we try to + // open the file. We can ignore any error (maybe the file doesn't exist). + file_util::Delete(archived_name, false); + } archived_db_.reset(new ArchivedDatabase()); if (!archived_db_->Init(archived_name)) { LOG(WARNING) << "Could not initialize the archived database."; diff --git a/chrome/browser/history/history_database.cc b/chrome/browser/history/history_database.cc index 5a929b1..198c6a4 100644 --- a/chrome/browser/history/history_database.cc +++ b/chrome/browser/history/history_database.cc @@ -16,8 +16,10 @@ namespace history { namespace { -// Current version number. -static const int kCurrentVersionNumber = 16; +// Current version number. We write databases at the "current" version number, +// but any previous version that can read the "compatible" one can make do with +// or database without *too* many bad effects. +static const int kCurrentVersionNumber = 17; static const int kCompatibleVersionNumber = 16; static const char kEarlyExpirationThresholdKey[] = "early_expiration_threshold"; @@ -25,7 +27,8 @@ static const char kEarlyExpirationThresholdKey[] = "early_expiration_threshold"; HistoryDatabase::HistoryDatabase() : transaction_nesting_(0), - db_(NULL) { + db_(NULL), + needs_version_17_migration_(false) { } HistoryDatabase::~HistoryDatabase() { @@ -235,6 +238,20 @@ InitStatus HistoryDatabase::EnsureCurrentVersion( std::min(cur_version, kCompatibleVersionNumber)); } + if (cur_version == 16) { +#if !defined(OS_WIN) + // In this version we bring the time format on Mac & Linux in sync with the + // Windows version so that profiles can be moved between computers. + MigrateTimeEpoch(); +#endif + // On all platforms we bump the version number, so on Windows this + // migration is a NOP. We keep the compatible version at 16 since things + // will basically still work, just history will be in the future if an + // old version reads it. + ++cur_version; + meta_table_.SetVersionNumber(cur_version); + } + // When the version is too old, we just try to continue anyway, there should // not be a released product that makes a database too old for us to handle. LOG_IF(WARNING, cur_version < kCurrentVersionNumber) << @@ -243,4 +260,32 @@ InitStatus HistoryDatabase::EnsureCurrentVersion( return INIT_OK; } +#if !defined(OS_WIN) +void HistoryDatabase::MigrateTimeEpoch() { + // Update all the times in the URLs and visits table in the main database. + // For visits, clear the indexed flag since we'll delete the FTS databases in + // the next step. + sqlite3_exec(GetDB(), + "UPDATE urls " + "SET last_visit_time = last_visit_time + 11644473600000000 " + "WHERE id IN (SELECT id FROM urls WHERE last_visit_time > 0);", + NULL, NULL, NULL); + sqlite3_exec(GetDB(), + "UPDATE visits " + "SET visit_time = visit_time + 11644473600000000, is_indexed = 0 " + "WHERE id IN (SELECT id FROM visits WHERE visit_time > 0);", + NULL, NULL, NULL); + sqlite3_exec(GetDB(), + "UPDATE segment_usage " + "SET time_slot = time_slot + 11644473600000000 " + "WHERE id IN (SELECT id FROM segment_usage WHERE time_slot > 0);", + NULL, NULL, NULL); + + // Erase all the full text index files. These will take a while to update and + // are less important, so we just blow them away. Same with the archived + // database. + needs_version_17_migration_ = true; +} +#endif + } // namespace history diff --git a/chrome/browser/history/history_database.h b/chrome/browser/history/history_database.h index 35d8595..9351c26 100644 --- a/chrome/browser/history/history_database.h +++ b/chrome/browser/history/history_database.h @@ -5,6 +5,7 @@ #ifndef CHROME_BROWSER_HISTORY_HISTORY_DATABASE_H_ #define CHROME_BROWSER_HISTORY_HISTORY_DATABASE_H_ +#include "build/build_config.h" #include "chrome/browser/history/download_database.h" #include "chrome/browser/history/history_types.h" #include "chrome/browser/history/starred_url_database.h" @@ -110,6 +111,17 @@ class HistoryDatabase : public DownloadDatabase, // unused space in the file. It can be VERY SLOW. void Vacuum(); + // Returns true if the history backend should erase the full text search + // and archived history files as part of version 16 -> 17 migration. The + // time format changed in this revision, and these files would be much slower + // to migrate. Since the data is less important, they should be deleted. + // + // This flag will be valid after Init() is called. It will always be false + // when running on Windows. + bool needs_version_17_migration() const { + return needs_version_17_migration_; + } + // Visit table functions ---------------------------------------------------- // Update the segment id of a visit. Return true on success. @@ -144,6 +156,12 @@ class HistoryDatabase : public DownloadDatabase, // may commit the transaction and start a new one if migration requires it. InitStatus EnsureCurrentVersion(const FilePath& tmp_bookmarks_path); +#if !defined(OS_WIN) + // Converts the time epoch in the database from being 1970-based to being + // 1601-based which corresponds to the change in Time.internal_value_. + void MigrateTimeEpoch(); +#endif + // --------------------------------------------------------------------------- // How many nested transactions are pending? When this gets to 0, we commit. @@ -162,6 +180,9 @@ class HistoryDatabase : public DownloadDatabase, MetaTableHelper meta_table_; base::Time cached_early_expiration_threshold_; + // See the getter above. + bool needs_version_17_migration_; + DISALLOW_COPY_AND_ASSIGN(HistoryDatabase); }; diff --git a/chrome/browser/password_manager/password_store_mac_unittest.cc b/chrome/browser/password_manager/password_store_mac_unittest.cc index c96a1a4..6eeeac7 100644 --- a/chrome/browser/password_manager/password_store_mac_unittest.cc +++ b/chrome/browser/password_manager/password_store_mac_unittest.cc @@ -232,9 +232,11 @@ TEST_F(PasswordStoreMacTest, TestKeychainToFormTranslation) { { PasswordForm::SCHEME_DIGEST, "https://some.domain.com/high_security", "https://some.domain.com/", L"digest_auth_user", L"digest", true, 1998, 3, 30, 10, 0, 0 }, + // This one gives us an invalid date, which we will treat as a "NULL" date + // which is 1601. { PasswordForm::SCHEME_OTHER, "http://a.server.com/", "http://a.server.com/", L"abc", L"123", false, - 1970, 1, 1, 0, 0, 0 }, + 1601, 1, 1, 0, 0, 0 }, }; for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(expected); ++i) { diff --git a/chrome/test/data/profiles/typical_history/Default/History b/chrome/test/data/profiles/typical_history/Default/History Binary files differindex b3d68c7..3251af2 100644 --- a/chrome/test/data/profiles/typical_history/Default/History +++ b/chrome/test/data/profiles/typical_history/Default/History diff --git a/net/base/cookie_monster_unittest.cc b/net/base/cookie_monster_unittest.cc index bb36585..1f0b45d 100644 --- a/net/base/cookie_monster_unittest.cc +++ b/net/base/cookie_monster_unittest.cc @@ -688,6 +688,17 @@ TEST(CookieMonsterTest, TestCookieDeletion) { std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-1977 22:50:13 GMT")); EXPECT_EQ("", cm.GetCookies(url_google)); + + // Create a persistent cookie. + EXPECT_TRUE(cm.SetCookie(url_google, + std::string(kValidCookieLine) + + "; expires=Mon, 18-Apr-22 22:50:13 GMT")); + EXPECT_EQ("A=B", cm.GetCookies(url_google)); + // Delete it via Expires, with a unix epoch of 0. + EXPECT_TRUE(cm.SetCookie(url_google, + std::string(kValidCookieLine) + + "; expires=Thu, 1-Jan-1970 00:00:00 GMT")); + EXPECT_EQ("", cm.GetCookies(url_google)); } TEST(CookieMonsterTest, TestCookieDeleteAll) { |