diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
index 0746c03..9569f9a 100644
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -1,1040 +1,1042 @@
 <?php
 
 /**
  * This file is automatically generated. Use 'arc liberate' to rebuild it.
  *
  * @generated
  * @phutil-library-version 2
  */
 phutil_register_library_map(array(
   '__library_version__' => 2,
   'class' => array(
     'AASTNode' => 'parser/aast/api/AASTNode.php',
     'AASTNodeList' => 'parser/aast/api/AASTNodeList.php',
     'AASTToken' => 'parser/aast/api/AASTToken.php',
     'AASTTree' => 'parser/aast/api/AASTTree.php',
     'AbstractDirectedGraph' => 'utils/AbstractDirectedGraph.php',
     'AbstractDirectedGraphTestCase' => 'utils/__tests__/AbstractDirectedGraphTestCase.php',
     'AphrontAccessDeniedQueryException' => 'aphront/storage/exception/AphrontAccessDeniedQueryException.php',
     'AphrontBaseMySQLDatabaseConnection' => 'aphront/storage/connection/mysql/AphrontBaseMySQLDatabaseConnection.php',
     'AphrontCharacterSetQueryException' => 'aphront/storage/exception/AphrontCharacterSetQueryException.php',
     'AphrontConnectionLostQueryException' => 'aphront/storage/exception/AphrontConnectionLostQueryException.php',
     'AphrontConnectionQueryException' => 'aphront/storage/exception/AphrontConnectionQueryException.php',
     'AphrontCountQueryException' => 'aphront/storage/exception/AphrontCountQueryException.php',
     'AphrontDatabaseConnection' => 'aphront/storage/connection/AphrontDatabaseConnection.php',
     'AphrontDatabaseTransactionState' => 'aphront/storage/connection/AphrontDatabaseTransactionState.php',
     'AphrontDeadlockQueryException' => 'aphront/storage/exception/AphrontDeadlockQueryException.php',
     'AphrontDuplicateKeyQueryException' => 'aphront/storage/exception/AphrontDuplicateKeyQueryException.php',
     'AphrontInvalidCredentialsQueryException' => 'aphront/storage/exception/AphrontInvalidCredentialsQueryException.php',
     'AphrontIsolatedDatabaseConnection' => 'aphront/storage/connection/AphrontIsolatedDatabaseConnection.php',
     'AphrontLockTimeoutQueryException' => 'aphront/storage/exception/AphrontLockTimeoutQueryException.php',
     'AphrontMySQLDatabaseConnection' => 'aphront/storage/connection/mysql/AphrontMySQLDatabaseConnection.php',
     'AphrontMySQLiDatabaseConnection' => 'aphront/storage/connection/mysql/AphrontMySQLiDatabaseConnection.php',
     'AphrontNotSupportedQueryException' => 'aphront/storage/exception/AphrontNotSupportedQueryException.php',
     'AphrontObjectMissingQueryException' => 'aphront/storage/exception/AphrontObjectMissingQueryException.php',
     'AphrontParameterQueryException' => 'aphront/storage/exception/AphrontParameterQueryException.php',
     'AphrontQueryException' => 'aphront/storage/exception/AphrontQueryException.php',
     'AphrontQueryTimeoutQueryException' => 'aphront/storage/exception/AphrontQueryTimeoutQueryException.php',
     'AphrontRecoverableQueryException' => 'aphront/storage/exception/AphrontRecoverableQueryException.php',
     'AphrontRequestStream' => 'aphront/requeststream/AphrontRequestStream.php',
     'AphrontSchemaQueryException' => 'aphront/storage/exception/AphrontSchemaQueryException.php',
     'AphrontScopedUnguardedWriteCapability' => 'aphront/writeguard/AphrontScopedUnguardedWriteCapability.php',
     'AphrontWriteGuard' => 'aphront/writeguard/AphrontWriteGuard.php',
     'BaseHTTPFuture' => 'future/http/BaseHTTPFuture.php',
     'CaseInsensitiveArray' => 'utils/CaseInsensitiveArray.php',
     'CaseInsensitiveArrayTestCase' => 'utils/__tests__/CaseInsensitiveArrayTestCase.php',
     'CommandException' => 'future/exec/CommandException.php',
     'ConduitClient' => 'conduit/ConduitClient.php',
     'ConduitClientException' => 'conduit/ConduitClientException.php',
     'ConduitClientTestCase' => 'conduit/__tests__/ConduitClientTestCase.php',
     'ConduitFuture' => 'conduit/ConduitFuture.php',
     'ExecFuture' => 'future/exec/ExecFuture.php',
     'ExecFutureTestCase' => 'future/exec/__tests__/ExecFutureTestCase.php',
     'ExecPassthruTestCase' => 'future/exec/__tests__/ExecPassthruTestCase.php',
     'FileFinder' => 'filesystem/FileFinder.php',
     'FileFinderTestCase' => 'filesystem/__tests__/FileFinderTestCase.php',
     'FileList' => 'filesystem/FileList.php',
     'Filesystem' => 'filesystem/Filesystem.php',
     'FilesystemException' => 'filesystem/FilesystemException.php',
     'FilesystemTestCase' => 'filesystem/__tests__/FilesystemTestCase.php',
     'Future' => 'future/Future.php',
     'FutureIterator' => 'future/FutureIterator.php',
     'FutureIteratorTestCase' => 'future/__tests__/FutureIteratorTestCase.php',
     'FutureProxy' => 'future/FutureProxy.php',
     'HTTPFuture' => 'future/http/HTTPFuture.php',
     'HTTPFutureCURLResponseStatus' => 'future/http/status/HTTPFutureCURLResponseStatus.php',
     'HTTPFutureCertificateResponseStatus' => 'future/http/status/HTTPFutureCertificateResponseStatus.php',
     'HTTPFutureHTTPResponseStatus' => 'future/http/status/HTTPFutureHTTPResponseStatus.php',
     'HTTPFutureParseResponseStatus' => 'future/http/status/HTTPFutureParseResponseStatus.php',
     'HTTPFutureResponseStatus' => 'future/http/status/HTTPFutureResponseStatus.php',
     'HTTPFutureTransportResponseStatus' => 'future/http/status/HTTPFutureTransportResponseStatus.php',
     'HTTPSFuture' => 'future/http/HTTPSFuture.php',
     'ImmediateFuture' => 'future/ImmediateFuture.php',
     'LibphutilUSEnglishTranslation' => 'internationalization/translation/LibphutilUSEnglishTranslation.php',
     'LinesOfALarge' => 'filesystem/linesofalarge/LinesOfALarge.php',
     'LinesOfALargeExecFuture' => 'filesystem/linesofalarge/LinesOfALargeExecFuture.php',
     'LinesOfALargeExecFutureTestCase' => 'filesystem/linesofalarge/__tests__/LinesOfALargeExecFutureTestCase.php',
     'LinesOfALargeFile' => 'filesystem/linesofalarge/LinesOfALargeFile.php',
     'LinesOfALargeFileTestCase' => 'filesystem/linesofalarge/__tests__/LinesOfALargeFileTestCase.php',
     'MFilterTestHelper' => 'utils/__tests__/MFilterTestHelper.php',
     'PHPASTParserTestCase' => 'parser/xhpast/__tests__/PHPASTParserTestCase.php',
     'PhageAgentBootloader' => 'phage/bootloader/PhageAgentBootloader.php',
     'PhageAgentTestCase' => 'phage/__tests__/PhageAgentTestCase.php',
     'PhagePHPAgent' => 'phage/agent/PhagePHPAgent.php',
     'PhagePHPAgentBootloader' => 'phage/bootloader/PhagePHPAgentBootloader.php',
     'Phobject' => 'object/Phobject.php',
     'PhobjectTestCase' => 'object/__tests__/PhobjectTestCase.php',
     'PhutilAPCKeyValueCache' => 'cache/PhutilAPCKeyValueCache.php',
     'PhutilAWSEC2Future' => 'future/aws/PhutilAWSEC2Future.php',
     'PhutilAWSException' => 'future/aws/PhutilAWSException.php',
     'PhutilAWSFuture' => 'future/aws/PhutilAWSFuture.php',
     'PhutilAWSManagementWorkflow' => 'future/aws/management/PhutilAWSManagementWorkflow.php',
     'PhutilAWSS3DeleteManagementWorkflow' => 'future/aws/management/PhutilAWSS3DeleteManagementWorkflow.php',
     'PhutilAWSS3Future' => 'future/aws/PhutilAWSS3Future.php',
     'PhutilAWSS3GetManagementWorkflow' => 'future/aws/management/PhutilAWSS3GetManagementWorkflow.php',
     'PhutilAWSS3ManagementWorkflow' => 'future/aws/management/PhutilAWSS3ManagementWorkflow.php',
     'PhutilAWSS3PutManagementWorkflow' => 'future/aws/management/PhutilAWSS3PutManagementWorkflow.php',
     'PhutilAWSv4Signature' => 'future/aws/PhutilAWSv4Signature.php',
     'PhutilAWSv4SignatureTestCase' => 'future/aws/__tests__/PhutilAWSv4SignatureTestCase.php',
     'PhutilAggregateException' => 'error/PhutilAggregateException.php',
     'PhutilAllCapsEnglishLocale' => 'internationalization/locales/PhutilAllCapsEnglishLocale.php',
     'PhutilAmazonAuthAdapter' => 'auth/PhutilAmazonAuthAdapter.php',
     'PhutilArgumentParser' => 'parser/argument/PhutilArgumentParser.php',
     'PhutilArgumentParserException' => 'parser/argument/exception/PhutilArgumentParserException.php',
     'PhutilArgumentParserTestCase' => 'parser/argument/__tests__/PhutilArgumentParserTestCase.php',
     'PhutilArgumentSpecification' => 'parser/argument/PhutilArgumentSpecification.php',
     'PhutilArgumentSpecificationException' => 'parser/argument/exception/PhutilArgumentSpecificationException.php',
     'PhutilArgumentSpecificationTestCase' => 'parser/argument/__tests__/PhutilArgumentSpecificationTestCase.php',
     'PhutilArgumentSpellingCorrector' => 'parser/argument/PhutilArgumentSpellingCorrector.php',
     'PhutilArgumentSpellingCorrectorTestCase' => 'parser/argument/__tests__/PhutilArgumentSpellingCorrectorTestCase.php',
     'PhutilArgumentUsageException' => 'parser/argument/exception/PhutilArgumentUsageException.php',
     'PhutilArgumentWorkflow' => 'parser/argument/workflow/PhutilArgumentWorkflow.php',
     'PhutilArray' => 'utils/PhutilArray.php',
     'PhutilArrayTestCase' => 'utils/__tests__/PhutilArrayTestCase.php',
     'PhutilArrayWithDefaultValue' => 'utils/PhutilArrayWithDefaultValue.php',
     'PhutilAsanaAuthAdapter' => 'auth/PhutilAsanaAuthAdapter.php',
     'PhutilAsanaFuture' => 'future/asana/PhutilAsanaFuture.php',
     'PhutilAuthAdapter' => 'auth/PhutilAuthAdapter.php',
     'PhutilAuthConfigurationException' => 'auth/exception/PhutilAuthConfigurationException.php',
     'PhutilAuthCredentialException' => 'auth/exception/PhutilAuthCredentialException.php',
     'PhutilAuthException' => 'auth/exception/PhutilAuthException.php',
     'PhutilAuthUserAbortedException' => 'auth/exception/PhutilAuthUserAbortedException.php',
     'PhutilBacktraceSignalHandler' => 'future/exec/PhutilBacktraceSignalHandler.php',
     'PhutilBallOfPHP' => 'phage/util/PhutilBallOfPHP.php',
     'PhutilBitbucketAuthAdapter' => 'auth/PhutilBitbucketAuthAdapter.php',
     'PhutilBootloader' => 'moduleutils/PhutilBootloader.php',
     'PhutilBootloaderException' => 'moduleutils/PhutilBootloaderException.php',
     'PhutilBritishEnglishLocale' => 'internationalization/locales/PhutilBritishEnglishLocale.php',
     'PhutilBufferedIterator' => 'utils/PhutilBufferedIterator.php',
     'PhutilBufferedIteratorTestCase' => 'utils/__tests__/PhutilBufferedIteratorTestCase.php',
     'PhutilBugtraqParser' => 'parser/PhutilBugtraqParser.php',
     'PhutilBugtraqParserTestCase' => 'parser/__tests__/PhutilBugtraqParserTestCase.php',
     'PhutilCIDRBlock' => 'ip/PhutilCIDRBlock.php',
     'PhutilCIDRList' => 'ip/PhutilCIDRList.php',
     'PhutilCLikeCodeSnippetContextFreeGrammar' => 'grammar/code/PhutilCLikeCodeSnippetContextFreeGrammar.php',
     'PhutilCalendarAbsoluteDateTime' => 'parser/calendar/data/PhutilCalendarAbsoluteDateTime.php',
     'PhutilCalendarContainerNode' => 'parser/calendar/data/PhutilCalendarContainerNode.php',
     'PhutilCalendarDateTime' => 'parser/calendar/data/PhutilCalendarDateTime.php',
     'PhutilCalendarDocumentNode' => 'parser/calendar/data/PhutilCalendarDocumentNode.php',
     'PhutilCalendarDuration' => 'parser/calendar/data/PhutilCalendarDuration.php',
     'PhutilCalendarEventNode' => 'parser/calendar/data/PhutilCalendarEventNode.php',
     'PhutilCalendarNode' => 'parser/calendar/data/PhutilCalendarNode.php',
     'PhutilCalendarProxyDateTime' => 'parser/calendar/data/PhutilCalendarProxyDateTime.php',
     'PhutilCalendarRawNode' => 'parser/calendar/data/PhutilCalendarRawNode.php',
     'PhutilCalendarRelativeDateTime' => 'parser/calendar/data/PhutilCalendarRelativeDateTime.php',
     'PhutilCalendarRootNode' => 'parser/calendar/data/PhutilCalendarRootNode.php',
+    'PhutilCalendarUserNode' => 'parser/calendar/data/PhutilCalendarUserNode.php',
     'PhutilCallbackFilterIterator' => 'utils/PhutilCallbackFilterIterator.php',
     'PhutilCallbackSignalHandler' => 'future/exec/PhutilCallbackSignalHandler.php',
     'PhutilChannel' => 'channel/PhutilChannel.php',
     'PhutilChannelChannel' => 'channel/PhutilChannelChannel.php',
     'PhutilChannelTestCase' => 'channel/__tests__/PhutilChannelTestCase.php',
     'PhutilChunkedIterator' => 'utils/PhutilChunkedIterator.php',
     'PhutilChunkedIteratorTestCase' => 'utils/__tests__/PhutilChunkedIteratorTestCase.php',
     'PhutilClassMapQuery' => 'symbols/PhutilClassMapQuery.php',
     'PhutilCodeSnippetContextFreeGrammar' => 'grammar/code/PhutilCodeSnippetContextFreeGrammar.php',
     'PhutilCommandString' => 'xsprintf/PhutilCommandString.php',
     'PhutilConsole' => 'console/PhutilConsole.php',
     'PhutilConsoleBlock' => 'console/view/PhutilConsoleBlock.php',
     'PhutilConsoleConcatenatedView' => 'console/view/PhutilConsoleConcatenatedView.php',
     'PhutilConsoleFormatter' => 'console/PhutilConsoleFormatter.php',
     'PhutilConsoleList' => 'console/view/PhutilConsoleList.php',
     'PhutilConsoleMessage' => 'console/PhutilConsoleMessage.php',
     'PhutilConsoleProgressBar' => 'console/PhutilConsoleProgressBar.php',
     'PhutilConsoleServer' => 'console/PhutilConsoleServer.php',
     'PhutilConsoleServerChannel' => 'console/PhutilConsoleServerChannel.php',
     'PhutilConsoleStdinNotInteractiveException' => 'console/PhutilConsoleStdinNotInteractiveException.php',
     'PhutilConsoleSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilConsoleSyntaxHighlighter.php',
     'PhutilConsoleTable' => 'console/view/PhutilConsoleTable.php',
     'PhutilConsoleView' => 'console/view/PhutilConsoleView.php',
     'PhutilConsoleWrapTestCase' => 'console/__tests__/PhutilConsoleWrapTestCase.php',
     'PhutilContextFreeGrammar' => 'grammar/PhutilContextFreeGrammar.php',
     'PhutilCowsay' => 'utils/PhutilCowsay.php',
     'PhutilCowsayTestCase' => 'utils/__tests__/PhutilCowsayTestCase.php',
     'PhutilCsprintfTestCase' => 'xsprintf/__tests__/PhutilCsprintfTestCase.php',
     'PhutilCzechLocale' => 'internationalization/locales/PhutilCzechLocale.php',
     'PhutilDaemon' => 'daemon/PhutilDaemon.php',
     'PhutilDaemonHandle' => 'daemon/PhutilDaemonHandle.php',
     'PhutilDaemonOverseer' => 'daemon/PhutilDaemonOverseer.php',
     'PhutilDaemonOverseerModule' => 'daemon/PhutilDaemonOverseerModule.php',
     'PhutilDefaultSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilDefaultSyntaxHighlighter.php',
     'PhutilDefaultSyntaxHighlighterEngine' => 'markup/syntax/engine/PhutilDefaultSyntaxHighlighterEngine.php',
     'PhutilDefaultSyntaxHighlighterEnginePygmentsFuture' => 'markup/syntax/highlighter/pygments/PhutilDefaultSyntaxHighlighterEnginePygmentsFuture.php',
     'PhutilDefaultSyntaxHighlighterEngineTestCase' => 'markup/syntax/engine/__tests__/PhutilDefaultSyntaxHighlighterEngineTestCase.php',
     'PhutilDeferredLog' => 'filesystem/PhutilDeferredLog.php',
     'PhutilDeferredLogTestCase' => 'filesystem/__tests__/PhutilDeferredLogTestCase.php',
     'PhutilDirectedScalarGraph' => 'utils/PhutilDirectedScalarGraph.php',
     'PhutilDirectoryFixture' => 'filesystem/PhutilDirectoryFixture.php',
     'PhutilDirectoryKeyValueCache' => 'cache/PhutilDirectoryKeyValueCache.php',
     'PhutilDisqusAuthAdapter' => 'auth/PhutilDisqusAuthAdapter.php',
     'PhutilDivinerSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilDivinerSyntaxHighlighter.php',
     'PhutilDocblockParser' => 'parser/PhutilDocblockParser.php',
     'PhutilDocblockParserTestCase' => 'parser/__tests__/PhutilDocblockParserTestCase.php',
     'PhutilEditDistanceMatrix' => 'utils/PhutilEditDistanceMatrix.php',
     'PhutilEditDistanceMatrixTestCase' => 'utils/__tests__/PhutilEditDistanceMatrixTestCase.php',
     'PhutilEditorConfig' => 'parser/PhutilEditorConfig.php',
     'PhutilEditorConfigTestCase' => 'parser/__tests__/PhutilEditorConfigTestCase.php',
     'PhutilEmailAddress' => 'parser/PhutilEmailAddress.php',
     'PhutilEmailAddressTestCase' => 'parser/__tests__/PhutilEmailAddressTestCase.php',
     'PhutilEmojiLocale' => 'internationalization/locales/PhutilEmojiLocale.php',
     'PhutilEmptyAuthAdapter' => 'auth/PhutilEmptyAuthAdapter.php',
     'PhutilEnglishCanadaLocale' => 'internationalization/locales/PhutilEnglishCanadaLocale.php',
     'PhutilErrorHandler' => 'error/PhutilErrorHandler.php',
     'PhutilErrorHandlerTestCase' => 'error/__tests__/PhutilErrorHandlerTestCase.php',
     'PhutilErrorTrap' => 'error/PhutilErrorTrap.php',
     'PhutilEvent' => 'events/PhutilEvent.php',
     'PhutilEventConstants' => 'events/constant/PhutilEventConstants.php',
     'PhutilEventEngine' => 'events/PhutilEventEngine.php',
     'PhutilEventListener' => 'events/PhutilEventListener.php',
     'PhutilEventType' => 'events/constant/PhutilEventType.php',
     'PhutilExampleBufferedIterator' => 'utils/PhutilExampleBufferedIterator.php',
     'PhutilExcessiveServiceCallsDaemon' => 'daemon/torture/PhutilExcessiveServiceCallsDaemon.php',
     'PhutilExecChannel' => 'channel/PhutilExecChannel.php',
     'PhutilExecPassthru' => 'future/exec/PhutilExecPassthru.php',
     'PhutilExecutableFuture' => 'future/exec/PhutilExecutableFuture.php',
     'PhutilExecutionEnvironment' => 'utils/PhutilExecutionEnvironment.php',
     'PhutilExtensionsTestCase' => 'moduleutils/__tests__/PhutilExtensionsTestCase.php',
     'PhutilFacebookAuthAdapter' => 'auth/PhutilFacebookAuthAdapter.php',
     'PhutilFatalDaemon' => 'daemon/torture/PhutilFatalDaemon.php',
     'PhutilFileLock' => 'filesystem/PhutilFileLock.php',
     'PhutilFileLockTestCase' => 'filesystem/__tests__/PhutilFileLockTestCase.php',
     'PhutilFileTree' => 'filesystem/PhutilFileTree.php',
     'PhutilFrenchLocale' => 'internationalization/locales/PhutilFrenchLocale.php',
     'PhutilGermanLocale' => 'internationalization/locales/PhutilGermanLocale.php',
     'PhutilGitHubAuthAdapter' => 'auth/PhutilGitHubAuthAdapter.php',
     'PhutilGitHubFuture' => 'future/github/PhutilGitHubFuture.php',
     'PhutilGitHubResponse' => 'future/github/PhutilGitHubResponse.php',
     'PhutilGitURI' => 'parser/PhutilGitURI.php',
     'PhutilGitURITestCase' => 'parser/__tests__/PhutilGitURITestCase.php',
     'PhutilGoogleAuthAdapter' => 'auth/PhutilGoogleAuthAdapter.php',
     'PhutilHTTPEngineExtension' => 'future/http/PhutilHTTPEngineExtension.php',
     'PhutilHangForeverDaemon' => 'daemon/torture/PhutilHangForeverDaemon.php',
     'PhutilHashingIterator' => 'utils/PhutilHashingIterator.php',
     'PhutilHashingIteratorTestCase' => 'utils/__tests__/PhutilHashingIteratorTestCase.php',
     'PhutilHelpArgumentWorkflow' => 'parser/argument/workflow/PhutilHelpArgumentWorkflow.php',
     'PhutilHgsprintfTestCase' => 'xsprintf/__tests__/PhutilHgsprintfTestCase.php',
     'PhutilHighIntensityIntervalDaemon' => 'daemon/torture/PhutilHighIntensityIntervalDaemon.php',
     'PhutilICSParser' => 'parser/calendar/ics/PhutilICSParser.php',
     'PhutilICSParserException' => 'parser/calendar/ics/PhutilICSParserException.php',
     'PhutilICSParserTestCase' => 'parser/calendar/ics/__tests__/PhutilICSParserTestCase.php',
     'PhutilICSWriter' => 'parser/calendar/ics/PhutilICSWriter.php',
     'PhutilICSWriterTestCase' => 'parser/calendar/ics/__tests__/PhutilICSWriterTestCase.php',
     'PhutilINIParserException' => 'parser/exception/PhutilINIParserException.php',
     'PhutilIPAddress' => 'ip/PhutilIPAddress.php',
     'PhutilIPAddressTestCase' => 'ip/__tests__/PhutilIPAddressTestCase.php',
     'PhutilInRequestKeyValueCache' => 'cache/PhutilInRequestKeyValueCache.php',
     'PhutilInteractiveEditor' => 'console/PhutilInteractiveEditor.php',
     'PhutilInvalidRuleParserGeneratorException' => 'parser/generator/exception/PhutilInvalidRuleParserGeneratorException.php',
     'PhutilInvalidStateException' => 'exception/PhutilInvalidStateException.php',
     'PhutilInvalidStateExceptionTestCase' => 'exception/__tests__/PhutilInvalidStateExceptionTestCase.php',
     'PhutilInvisibleSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilInvisibleSyntaxHighlighter.php',
     'PhutilIrreducibleRuleParserGeneratorException' => 'parser/generator/exception/PhutilIrreducibleRuleParserGeneratorException.php',
     'PhutilJIRAAuthAdapter' => 'auth/PhutilJIRAAuthAdapter.php',
     'PhutilJSON' => 'parser/PhutilJSON.php',
     'PhutilJSONFragmentLexer' => 'lexer/PhutilJSONFragmentLexer.php',
     'PhutilJSONFragmentLexerHighlighterTestCase' => 'markup/syntax/highlighter/__tests__/PhutilJSONFragmentLexerHighlighterTestCase.php',
     'PhutilJSONParser' => 'parser/PhutilJSONParser.php',
     'PhutilJSONParserException' => 'parser/exception/PhutilJSONParserException.php',
     'PhutilJSONParserTestCase' => 'parser/__tests__/PhutilJSONParserTestCase.php',
     'PhutilJSONProtocolChannel' => 'channel/PhutilJSONProtocolChannel.php',
     'PhutilJSONProtocolChannelTestCase' => 'channel/__tests__/PhutilJSONProtocolChannelTestCase.php',
     'PhutilJSONTestCase' => 'parser/__tests__/PhutilJSONTestCase.php',
     'PhutilJavaCodeSnippetContextFreeGrammar' => 'grammar/code/PhutilJavaCodeSnippetContextFreeGrammar.php',
     'PhutilKeyValueCache' => 'cache/PhutilKeyValueCache.php',
     'PhutilKeyValueCacheNamespace' => 'cache/PhutilKeyValueCacheNamespace.php',
     'PhutilKeyValueCacheProfiler' => 'cache/PhutilKeyValueCacheProfiler.php',
     'PhutilKeyValueCacheProxy' => 'cache/PhutilKeyValueCacheProxy.php',
     'PhutilKeyValueCacheStack' => 'cache/PhutilKeyValueCacheStack.php',
     'PhutilKeyValueCacheTestCase' => 'cache/__tests__/PhutilKeyValueCacheTestCase.php',
     'PhutilKoreanLocale' => 'internationalization/locales/PhutilKoreanLocale.php',
     'PhutilLDAPAuthAdapter' => 'auth/PhutilLDAPAuthAdapter.php',
     'PhutilLanguageGuesser' => 'parser/PhutilLanguageGuesser.php',
     'PhutilLanguageGuesserTestCase' => 'parser/__tests__/PhutilLanguageGuesserTestCase.php',
     'PhutilLexer' => 'lexer/PhutilLexer.php',
     'PhutilLexerSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilLexerSyntaxHighlighter.php',
     'PhutilLibraryConflictException' => 'moduleutils/PhutilLibraryConflictException.php',
     'PhutilLibraryMapBuilder' => 'moduleutils/PhutilLibraryMapBuilder.php',
     'PhutilLibraryTestCase' => '__tests__/PhutilLibraryTestCase.php',
     'PhutilLipsumContextFreeGrammar' => 'grammar/PhutilLipsumContextFreeGrammar.php',
     'PhutilLocale' => 'internationalization/PhutilLocale.php',
     'PhutilLocaleTestCase' => 'internationalization/__tests__/PhutilLocaleTestCase.php',
     'PhutilLock' => 'filesystem/PhutilLock.php',
     'PhutilLockException' => 'filesystem/PhutilLockException.php',
     'PhutilLogFileChannel' => 'channel/PhutilLogFileChannel.php',
     'PhutilLunarPhase' => 'utils/PhutilLunarPhase.php',
     'PhutilLunarPhaseTestCase' => 'utils/__tests__/PhutilLunarPhaseTestCase.php',
     'PhutilMarkupEngine' => 'markup/PhutilMarkupEngine.php',
     'PhutilMarkupTestCase' => 'markup/__tests__/PhutilMarkupTestCase.php',
     'PhutilMemcacheKeyValueCache' => 'cache/PhutilMemcacheKeyValueCache.php',
     'PhutilMethodNotImplementedException' => 'error/PhutilMethodNotImplementedException.php',
     'PhutilMetricsChannel' => 'channel/PhutilMetricsChannel.php',
     'PhutilMissingSymbolException' => 'symbols/exception/PhutilMissingSymbolException.php',
     'PhutilModuleUtilsTestCase' => 'moduleutils/__tests__/PhutilModuleUtilsTestCase.php',
     'PhutilNiceDaemon' => 'daemon/torture/PhutilNiceDaemon.php',
     'PhutilNumber' => 'internationalization/PhutilNumber.php',
     'PhutilOAuth1AuthAdapter' => 'auth/PhutilOAuth1AuthAdapter.php',
     'PhutilOAuth1Future' => 'future/oauth/PhutilOAuth1Future.php',
     'PhutilOAuth1FutureTestCase' => 'future/oauth/__tests__/PhutilOAuth1FutureTestCase.php',
     'PhutilOAuthAuthAdapter' => 'auth/PhutilOAuthAuthAdapter.php',
     'PhutilOnDiskKeyValueCache' => 'cache/PhutilOnDiskKeyValueCache.php',
     'PhutilOpaqueEnvelope' => 'error/PhutilOpaqueEnvelope.php',
     'PhutilOpaqueEnvelopeKey' => 'error/PhutilOpaqueEnvelopeKey.php',
     'PhutilOpaqueEnvelopeTestCase' => 'error/__tests__/PhutilOpaqueEnvelopeTestCase.php',
     'PhutilPHPCodeSnippetContextFreeGrammar' => 'grammar/code/PhutilPHPCodeSnippetContextFreeGrammar.php',
     'PhutilPHPFragmentLexer' => 'lexer/PhutilPHPFragmentLexer.php',
     'PhutilPHPFragmentLexerHighlighterTestCase' => 'markup/syntax/highlighter/__tests__/PhutilPHPFragmentLexerHighlighterTestCase.php',
     'PhutilPHPFragmentLexerTestCase' => 'lexer/__tests__/PhutilPHPFragmentLexerTestCase.php',
     'PhutilPHPObjectProtocolChannel' => 'channel/PhutilPHPObjectProtocolChannel.php',
     'PhutilPHPObjectProtocolChannelTestCase' => 'channel/__tests__/PhutilPHPObjectProtocolChannelTestCase.php',
     'PhutilParserGenerator' => 'parser/PhutilParserGenerator.php',
     'PhutilParserGeneratorException' => 'parser/generator/exception/PhutilParserGeneratorException.php',
     'PhutilParserGeneratorTestCase' => 'parser/__tests__/PhutilParserGeneratorTestCase.php',
     'PhutilPayPalAPIFuture' => 'future/paypal/PhutilPayPalAPIFuture.php',
     'PhutilPerson' => 'internationalization/PhutilPerson.php',
     'PhutilPersonTest' => 'internationalization/__tests__/PhutilPersonTest.php',
     'PhutilPersonaAuthAdapter' => 'auth/PhutilPersonaAuthAdapter.php',
     'PhutilPhabricatorAuthAdapter' => 'auth/PhutilPhabricatorAuthAdapter.php',
     'PhutilPhtTestCase' => 'internationalization/__tests__/PhutilPhtTestCase.php',
     'PhutilPirateEnglishLocale' => 'internationalization/locales/PhutilPirateEnglishLocale.php',
     'PhutilPortugueseBrazilLocale' => 'internationalization/locales/PhutilPortugueseBrazilLocale.php',
     'PhutilPortuguesePortugalLocale' => 'internationalization/locales/PhutilPortuguesePortugalLocale.php',
     'PhutilPregsprintfTestCase' => 'xsprintf/__tests__/PhutilPregsprintfTestCase.php',
     'PhutilProcessGroupDaemon' => 'daemon/torture/PhutilProcessGroupDaemon.php',
     'PhutilProseDiff' => 'utils/PhutilProseDiff.php',
     'PhutilProseDiffTestCase' => 'utils/__tests__/PhutilProseDiffTestCase.php',
     'PhutilProseDifferenceEngine' => 'utils/PhutilProseDifferenceEngine.php',
     'PhutilProtocolChannel' => 'channel/PhutilProtocolChannel.php',
     'PhutilProxyException' => 'error/PhutilProxyException.php',
     'PhutilProxyIterator' => 'utils/PhutilProxyIterator.php',
     'PhutilPygmentizeParser' => 'parser/PhutilPygmentizeParser.php',
     'PhutilPygmentizeParserTestCase' => 'parser/__tests__/PhutilPygmentizeParserTestCase.php',
     'PhutilPygmentsSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilPygmentsSyntaxHighlighter.php',
     'PhutilPythonFragmentLexer' => 'lexer/PhutilPythonFragmentLexer.php',
     'PhutilQsprintfInterface' => 'xsprintf/PhutilQsprintfInterface.php',
     'PhutilQueryStringParser' => 'parser/PhutilQueryStringParser.php',
     'PhutilQueryStringParserTestCase' => 'parser/__tests__/PhutilQueryStringParserTestCase.php',
     'PhutilRainbowSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilRainbowSyntaxHighlighter.php',
     'PhutilRawEnglishLocale' => 'internationalization/locales/PhutilRawEnglishLocale.php',
     'PhutilReadableSerializer' => 'readableserializer/PhutilReadableSerializer.php',
     'PhutilReadableSerializerTestCase' => 'readableserializer/__tests__/PhutilReadableSerializerTestCase.php',
     'PhutilRealNameContextFreeGrammar' => 'grammar/PhutilRealNameContextFreeGrammar.php',
     'PhutilRemarkupBlockInterpreter' => 'markup/engine/remarkup/blockrule/PhutilRemarkupBlockInterpreter.php',
     'PhutilRemarkupBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupBlockRule.php',
     'PhutilRemarkupBlockStorage' => 'markup/engine/remarkup/PhutilRemarkupBlockStorage.php',
     'PhutilRemarkupBoldRule' => 'markup/engine/remarkup/markuprule/PhutilRemarkupBoldRule.php',
     'PhutilRemarkupCodeBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupCodeBlockRule.php',
     'PhutilRemarkupDefaultBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupDefaultBlockRule.php',
     'PhutilRemarkupDelRule' => 'markup/engine/remarkup/markuprule/PhutilRemarkupDelRule.php',
     'PhutilRemarkupDocumentLinkRule' => 'markup/engine/remarkup/markuprule/PhutilRemarkupDocumentLinkRule.php',
     'PhutilRemarkupEngine' => 'markup/engine/PhutilRemarkupEngine.php',
     'PhutilRemarkupEngineTestCase' => 'markup/engine/__tests__/PhutilRemarkupEngineTestCase.php',
     'PhutilRemarkupEscapeRemarkupRule' => 'markup/engine/remarkup/markuprule/PhutilRemarkupEscapeRemarkupRule.php',
     'PhutilRemarkupHeaderBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupHeaderBlockRule.php',
     'PhutilRemarkupHighlightRule' => 'markup/engine/remarkup/markuprule/PhutilRemarkupHighlightRule.php',
     'PhutilRemarkupHorizontalRuleBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupHorizontalRuleBlockRule.php',
     'PhutilRemarkupHyperlinkRule' => 'markup/engine/remarkup/markuprule/PhutilRemarkupHyperlinkRule.php',
     'PhutilRemarkupInlineBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupInlineBlockRule.php',
     'PhutilRemarkupInterpreterBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupInterpreterBlockRule.php',
     'PhutilRemarkupItalicRule' => 'markup/engine/remarkup/markuprule/PhutilRemarkupItalicRule.php',
     'PhutilRemarkupLinebreaksRule' => 'markup/engine/remarkup/markuprule/PhutilRemarkupLinebreaksRule.php',
     'PhutilRemarkupListBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupListBlockRule.php',
     'PhutilRemarkupLiteralBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupLiteralBlockRule.php',
     'PhutilRemarkupMonospaceRule' => 'markup/engine/remarkup/markuprule/PhutilRemarkupMonospaceRule.php',
     'PhutilRemarkupNoteBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupNoteBlockRule.php',
     'PhutilRemarkupQuotesBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupQuotesBlockRule.php',
     'PhutilRemarkupReplyBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupReplyBlockRule.php',
     'PhutilRemarkupRule' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRule.php',
     'PhutilRemarkupSimpleTableBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupSimpleTableBlockRule.php',
     'PhutilRemarkupTableBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupTableBlockRule.php',
     'PhutilRemarkupTestInterpreterRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupTestInterpreterRule.php',
     'PhutilRemarkupUnderlineRule' => 'markup/engine/remarkup/markuprule/PhutilRemarkupUnderlineRule.php',
     'PhutilRope' => 'utils/PhutilRope.php',
     'PhutilRopeTestCase' => 'utils/__tests__/PhutilRopeTestCase.php',
     'PhutilSafeHTML' => 'markup/PhutilSafeHTML.php',
     'PhutilSafeHTMLProducerInterface' => 'markup/PhutilSafeHTMLProducerInterface.php',
     'PhutilSafeHTMLTestCase' => 'markup/__tests__/PhutilSafeHTMLTestCase.php',
     'PhutilSaturateStdoutDaemon' => 'daemon/torture/PhutilSaturateStdoutDaemon.php',
     'PhutilServiceProfiler' => 'serviceprofiler/PhutilServiceProfiler.php',
     'PhutilShellLexer' => 'lexer/PhutilShellLexer.php',
     'PhutilShellLexerTestCase' => 'lexer/__tests__/PhutilShellLexerTestCase.php',
     'PhutilSignalHandler' => 'future/exec/PhutilSignalHandler.php',
     'PhutilSignalRouter' => 'future/exec/PhutilSignalRouter.php',
     'PhutilSimpleOptions' => 'parser/PhutilSimpleOptions.php',
     'PhutilSimpleOptionsLexer' => 'lexer/PhutilSimpleOptionsLexer.php',
     'PhutilSimpleOptionsLexerTestCase' => 'lexer/__tests__/PhutilSimpleOptionsLexerTestCase.php',
     'PhutilSimpleOptionsTestCase' => 'parser/__tests__/PhutilSimpleOptionsTestCase.php',
     'PhutilSimplifiedChineseLocale' => 'internationalization/locales/PhutilSimplifiedChineseLocale.php',
     'PhutilSlackAuthAdapter' => 'auth/PhutilSlackAuthAdapter.php',
     'PhutilSlackFuture' => 'future/slack/PhutilSlackFuture.php',
     'PhutilSocketChannel' => 'channel/PhutilSocketChannel.php',
     'PhutilSortVector' => 'utils/PhutilSortVector.php',
     'PhutilSpanishSpainLocale' => 'internationalization/locales/PhutilSpanishSpainLocale.php',
     'PhutilSprite' => 'sprites/PhutilSprite.php',
     'PhutilSpriteSheet' => 'sprites/PhutilSpriteSheet.php',
     'PhutilStreamIterator' => 'utils/PhutilStreamIterator.php',
     'PhutilSymbolLoader' => 'symbols/PhutilSymbolLoader.php',
     'PhutilSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilSyntaxHighlighter.php',
     'PhutilSyntaxHighlighterEngine' => 'markup/syntax/engine/PhutilSyntaxHighlighterEngine.php',
     'PhutilSyntaxHighlighterException' => 'markup/syntax/highlighter/PhutilSyntaxHighlighterException.php',
     'PhutilSystem' => 'utils/PhutilSystem.php',
     'PhutilSystemTestCase' => 'utils/__tests__/PhutilSystemTestCase.php',
     'PhutilTerminalString' => 'xsprintf/PhutilTerminalString.php',
     'PhutilTestPhobject' => 'object/__tests__/PhutilTestPhobject.php',
     'PhutilTortureTestDaemon' => 'daemon/torture/PhutilTortureTestDaemon.php',
     'PhutilTraditionalChineseLocale' => 'internationalization/locales/PhutilTraditionalChineseLocale.php',
     'PhutilTranslation' => 'internationalization/PhutilTranslation.php',
     'PhutilTranslationTestCase' => 'internationalization/__tests__/PhutilTranslationTestCase.php',
     'PhutilTranslator' => 'internationalization/PhutilTranslator.php',
     'PhutilTranslatorTestCase' => 'internationalization/__tests__/PhutilTranslatorTestCase.php',
     'PhutilTsprintfTestCase' => 'xsprintf/__tests__/PhutilTsprintfTestCase.php',
     'PhutilTwitchAuthAdapter' => 'auth/PhutilTwitchAuthAdapter.php',
     'PhutilTwitchFuture' => 'future/twitch/PhutilTwitchFuture.php',
     'PhutilTwitterAuthAdapter' => 'auth/PhutilTwitterAuthAdapter.php',
     'PhutilTypeCheckException' => 'parser/exception/PhutilTypeCheckException.php',
     'PhutilTypeExtraParametersException' => 'parser/exception/PhutilTypeExtraParametersException.php',
     'PhutilTypeLexer' => 'lexer/PhutilTypeLexer.php',
     'PhutilTypeMissingParametersException' => 'parser/exception/PhutilTypeMissingParametersException.php',
     'PhutilTypeSpec' => 'parser/PhutilTypeSpec.php',
     'PhutilTypeSpecTestCase' => 'parser/__tests__/PhutilTypeSpecTestCase.php',
     'PhutilURI' => 'parser/PhutilURI.php',
     'PhutilURITestCase' => 'parser/__tests__/PhutilURITestCase.php',
     'PhutilUSEnglishLocale' => 'internationalization/locales/PhutilUSEnglishLocale.php',
     'PhutilUTF8StringTruncator' => 'utils/PhutilUTF8StringTruncator.php',
     'PhutilUTF8TestCase' => 'utils/__tests__/PhutilUTF8TestCase.php',
     'PhutilUnknownSymbolParserGeneratorException' => 'parser/generator/exception/PhutilUnknownSymbolParserGeneratorException.php',
     'PhutilUnreachableRuleParserGeneratorException' => 'parser/generator/exception/PhutilUnreachableRuleParserGeneratorException.php',
     'PhutilUnreachableTerminalParserGeneratorException' => 'parser/generator/exception/PhutilUnreachableTerminalParserGeneratorException.php',
     'PhutilUrisprintfTestCase' => 'xsprintf/__tests__/PhutilUrisprintfTestCase.php',
     'PhutilUtilsTestCase' => 'utils/__tests__/PhutilUtilsTestCase.php',
     'PhutilVeryWowEnglishLocale' => 'internationalization/locales/PhutilVeryWowEnglishLocale.php',
     'PhutilWordPressAuthAdapter' => 'auth/PhutilWordPressAuthAdapter.php',
     'PhutilWordPressFuture' => 'future/wordpress/PhutilWordPressFuture.php',
     'PhutilXHPASTBinary' => 'parser/xhpast/bin/PhutilXHPASTBinary.php',
     'PhutilXHPASTSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilXHPASTSyntaxHighlighter.php',
     'PhutilXHPASTSyntaxHighlighterFuture' => 'markup/syntax/highlighter/xhpast/PhutilXHPASTSyntaxHighlighterFuture.php',
     'PhutilXHPASTSyntaxHighlighterTestCase' => 'markup/syntax/highlighter/__tests__/PhutilXHPASTSyntaxHighlighterTestCase.php',
     'QueryFuture' => 'future/query/QueryFuture.php',
     'TempFile' => 'filesystem/TempFile.php',
     'TestAbstractDirectedGraph' => 'utils/__tests__/TestAbstractDirectedGraph.php',
     'XHPASTNode' => 'parser/xhpast/api/XHPASTNode.php',
     'XHPASTNodeTestCase' => 'parser/xhpast/api/__tests__/XHPASTNodeTestCase.php',
     'XHPASTSyntaxErrorException' => 'parser/xhpast/api/XHPASTSyntaxErrorException.php',
     'XHPASTToken' => 'parser/xhpast/api/XHPASTToken.php',
     'XHPASTTree' => 'parser/xhpast/api/XHPASTTree.php',
     'XHPASTTreeTestCase' => 'parser/xhpast/api/__tests__/XHPASTTreeTestCase.php',
     'XsprintfUnknownConversionException' => 'xsprintf/exception/XsprintfUnknownConversionException.php',
   ),
   'function' => array(
     'array_fuse' => 'utils/utils.php',
     'array_interleave' => 'utils/utils.php',
     'array_mergev' => 'utils/utils.php',
     'array_select_keys' => 'utils/utils.php',
     'assert_instances_of' => 'utils/utils.php',
     'assert_stringlike' => 'utils/utils.php',
     'coalesce' => 'utils/utils.php',
     'csprintf' => 'xsprintf/csprintf.php',
     'exec_manual' => 'future/exec/execx.php',
     'execx' => 'future/exec/execx.php',
     'head' => 'utils/utils.php',
     'head_key' => 'utils/utils.php',
     'hgsprintf' => 'xsprintf/hgsprintf.php',
     'hsprintf' => 'markup/render.php',
     'id' => 'utils/utils.php',
     'idx' => 'utils/utils.php',
     'idxv' => 'utils/utils.php',
     'ifilter' => 'utils/utils.php',
     'igroup' => 'utils/utils.php',
     'ipull' => 'utils/utils.php',
     'isort' => 'utils/utils.php',
     'jsprintf' => 'xsprintf/jsprintf.php',
     'last' => 'utils/utils.php',
     'last_key' => 'utils/utils.php',
     'ldap_sprintf' => 'xsprintf/ldapsprintf.php',
     'mfilter' => 'utils/utils.php',
     'mgroup' => 'utils/utils.php',
     'mpull' => 'utils/utils.php',
     'msort' => 'utils/utils.php',
     'msortv' => 'utils/utils.php',
     'newv' => 'utils/utils.php',
     'nonempty' => 'utils/utils.php',
     'phlog' => 'error/phlog.php',
     'pht' => 'internationalization/pht.php',
     'phutil_censor_credentials' => 'utils/utils.php',
     'phutil_console_confirm' => 'console/format.php',
     'phutil_console_format' => 'console/format.php',
     'phutil_console_get_terminal_width' => 'console/format.php',
     'phutil_console_prompt' => 'console/format.php',
     'phutil_console_require_tty' => 'console/format.php',
     'phutil_console_wrap' => 'console/format.php',
     'phutil_count' => 'internationalization/pht.php',
     'phutil_date_format' => 'utils/viewutils.php',
     'phutil_deprecated' => 'moduleutils/moduleutils.php',
     'phutil_error_listener_example' => 'error/phlog.php',
     'phutil_escape_html' => 'markup/render.php',
     'phutil_escape_html_newlines' => 'markup/render.php',
     'phutil_escape_uri' => 'markup/render.php',
     'phutil_escape_uri_path_component' => 'markup/render.php',
     'phutil_fnmatch' => 'utils/utils.php',
     'phutil_format_bytes' => 'utils/viewutils.php',
     'phutil_format_relative_time' => 'utils/viewutils.php',
     'phutil_format_relative_time_detailed' => 'utils/viewutils.php',
     'phutil_format_units_generic' => 'utils/viewutils.php',
     'phutil_fwrite_nonblocking_stream' => 'utils/utils.php',
     'phutil_get_current_library_name' => 'moduleutils/moduleutils.php',
     'phutil_get_library_name_for_root' => 'moduleutils/moduleutils.php',
     'phutil_get_library_root' => 'moduleutils/moduleutils.php',
     'phutil_get_library_root_for_path' => 'moduleutils/moduleutils.php',
     'phutil_get_signal_name' => 'future/exec/execx.php',
     'phutil_hashes_are_identical' => 'utils/utils.php',
     'phutil_implode_html' => 'markup/render.php',
     'phutil_ini_decode' => 'utils/utils.php',
     'phutil_is_hiphop_runtime' => 'utils/utils.php',
     'phutil_is_utf8' => 'utils/utf8.php',
     'phutil_is_utf8_slowly' => 'utils/utf8.php',
     'phutil_is_utf8_with_only_bmp_characters' => 'utils/utf8.php',
     'phutil_is_windows' => 'utils/utils.php',
     'phutil_json_decode' => 'utils/utils.php',
     'phutil_json_encode' => 'utils/utils.php',
     'phutil_load_library' => 'moduleutils/core.php',
     'phutil_loggable_string' => 'utils/utils.php',
     'phutil_parse_bytes' => 'utils/viewutils.php',
     'phutil_passthru' => 'future/exec/execx.php',
     'phutil_register_library' => 'moduleutils/core.php',
     'phutil_register_library_map' => 'moduleutils/core.php',
     'phutil_safe_html' => 'markup/render.php',
     'phutil_split_lines' => 'utils/utils.php',
     'phutil_tag' => 'markup/render.php',
     'phutil_tag_div' => 'markup/render.php',
     'phutil_unescape_uri_path_component' => 'markup/render.php',
     'phutil_units' => 'utils/utils.php',
     'phutil_utf8_console_strlen' => 'utils/utf8.php',
     'phutil_utf8_convert' => 'utils/utf8.php',
     'phutil_utf8_encode_codepoint' => 'utils/utf8.php',
     'phutil_utf8_hard_wrap' => 'utils/utf8.php',
     'phutil_utf8_hard_wrap_html' => 'utils/utf8.php',
     'phutil_utf8_is_combining_character' => 'utils/utf8.php',
     'phutil_utf8_strlen' => 'utils/utf8.php',
     'phutil_utf8_strtolower' => 'utils/utf8.php',
     'phutil_utf8_strtoupper' => 'utils/utf8.php',
     'phutil_utf8_strtr' => 'utils/utf8.php',
     'phutil_utf8_ucwords' => 'utils/utf8.php',
     'phutil_utf8ize' => 'utils/utf8.php',
     'phutil_utf8v' => 'utils/utf8.php',
     'phutil_utf8v_codepoints' => 'utils/utf8.php',
     'phutil_utf8v_combine_characters' => 'utils/utf8.php',
     'phutil_utf8v_combined' => 'utils/utf8.php',
     'phutil_validate_json' => 'utils/utils.php',
     'phutil_var_export' => 'utils/utils.php',
     'ppull' => 'utils/utils.php',
     'pregsprintf' => 'xsprintf/pregsprintf.php',
     'qsprintf' => 'xsprintf/qsprintf.php',
     'qsprintf_check_scalar_type' => 'xsprintf/qsprintf.php',
     'qsprintf_check_type' => 'xsprintf/qsprintf.php',
     'queryfx' => 'xsprintf/queryfx.php',
     'queryfx_all' => 'xsprintf/queryfx.php',
     'queryfx_one' => 'xsprintf/queryfx.php',
     'tsprintf' => 'xsprintf/tsprintf.php',
     'urisprintf' => 'xsprintf/urisprintf.php',
     'vcsprintf' => 'xsprintf/csprintf.php',
     'vjsprintf' => 'xsprintf/jsprintf.php',
     'vqsprintf' => 'xsprintf/qsprintf.php',
     'vurisprintf' => 'xsprintf/urisprintf.php',
     'xhp_parser_node_constants' => 'parser/xhpast/parser_nodes.php',
     'xhpast_parser_token_constants' => 'parser/xhpast/parser_tokens.php',
     'xsprintf' => 'xsprintf/xsprintf.php',
     'xsprintf_callback_example' => 'xsprintf/xsprintf.php',
     'xsprintf_command' => 'xsprintf/csprintf.php',
     'xsprintf_javascript' => 'xsprintf/jsprintf.php',
     'xsprintf_ldap' => 'xsprintf/ldapsprintf.php',
     'xsprintf_mercurial' => 'xsprintf/hgsprintf.php',
     'xsprintf_query' => 'xsprintf/qsprintf.php',
     'xsprintf_regex' => 'xsprintf/pregsprintf.php',
     'xsprintf_terminal' => 'xsprintf/tsprintf.php',
     'xsprintf_uri' => 'xsprintf/urisprintf.php',
   ),
   'xmap' => array(
     'AASTNode' => 'Phobject',
     'AASTNodeList' => array(
       'Phobject',
       'Countable',
       'Iterator',
     ),
     'AASTToken' => 'Phobject',
     'AASTTree' => 'Phobject',
     'AbstractDirectedGraph' => 'Phobject',
     'AbstractDirectedGraphTestCase' => 'PhutilTestCase',
     'AphrontAccessDeniedQueryException' => 'AphrontQueryException',
     'AphrontBaseMySQLDatabaseConnection' => 'AphrontDatabaseConnection',
     'AphrontCharacterSetQueryException' => 'AphrontQueryException',
     'AphrontConnectionLostQueryException' => 'AphrontRecoverableQueryException',
     'AphrontConnectionQueryException' => 'AphrontQueryException',
     'AphrontCountQueryException' => 'AphrontQueryException',
     'AphrontDatabaseConnection' => array(
       'Phobject',
       'PhutilQsprintfInterface',
     ),
     'AphrontDatabaseTransactionState' => 'Phobject',
     'AphrontDeadlockQueryException' => 'AphrontRecoverableQueryException',
     'AphrontDuplicateKeyQueryException' => 'AphrontQueryException',
     'AphrontInvalidCredentialsQueryException' => 'AphrontQueryException',
     'AphrontIsolatedDatabaseConnection' => 'AphrontDatabaseConnection',
     'AphrontLockTimeoutQueryException' => 'AphrontRecoverableQueryException',
     'AphrontMySQLDatabaseConnection' => 'AphrontBaseMySQLDatabaseConnection',
     'AphrontMySQLiDatabaseConnection' => 'AphrontBaseMySQLDatabaseConnection',
     'AphrontNotSupportedQueryException' => 'AphrontQueryException',
     'AphrontObjectMissingQueryException' => 'AphrontQueryException',
     'AphrontParameterQueryException' => 'AphrontQueryException',
     'AphrontQueryException' => 'Exception',
     'AphrontQueryTimeoutQueryException' => 'AphrontRecoverableQueryException',
     'AphrontRecoverableQueryException' => 'AphrontQueryException',
     'AphrontRequestStream' => 'Phobject',
     'AphrontSchemaQueryException' => 'AphrontQueryException',
     'AphrontScopedUnguardedWriteCapability' => 'Phobject',
     'AphrontWriteGuard' => 'Phobject',
     'BaseHTTPFuture' => 'Future',
     'CaseInsensitiveArray' => 'PhutilArray',
     'CaseInsensitiveArrayTestCase' => 'PhutilTestCase',
     'CommandException' => 'Exception',
     'ConduitClient' => 'Phobject',
     'ConduitClientException' => 'Exception',
     'ConduitClientTestCase' => 'PhutilTestCase',
     'ConduitFuture' => 'FutureProxy',
     'ExecFuture' => 'PhutilExecutableFuture',
     'ExecFutureTestCase' => 'PhutilTestCase',
     'ExecPassthruTestCase' => 'PhutilTestCase',
     'FileFinder' => 'Phobject',
     'FileFinderTestCase' => 'PhutilTestCase',
     'FileList' => 'Phobject',
     'Filesystem' => 'Phobject',
     'FilesystemException' => 'Exception',
     'FilesystemTestCase' => 'PhutilTestCase',
     'Future' => 'Phobject',
     'FutureIterator' => array(
       'Phobject',
       'Iterator',
     ),
     'FutureIteratorTestCase' => 'PhutilTestCase',
     'FutureProxy' => 'Future',
     'HTTPFuture' => 'BaseHTTPFuture',
     'HTTPFutureCURLResponseStatus' => 'HTTPFutureResponseStatus',
     'HTTPFutureCertificateResponseStatus' => 'HTTPFutureResponseStatus',
     'HTTPFutureHTTPResponseStatus' => 'HTTPFutureResponseStatus',
     'HTTPFutureParseResponseStatus' => 'HTTPFutureResponseStatus',
     'HTTPFutureResponseStatus' => 'Exception',
     'HTTPFutureTransportResponseStatus' => 'HTTPFutureResponseStatus',
     'HTTPSFuture' => 'BaseHTTPFuture',
     'ImmediateFuture' => 'Future',
     'LibphutilUSEnglishTranslation' => 'PhutilTranslation',
     'LinesOfALarge' => array(
       'Phobject',
       'Iterator',
     ),
     'LinesOfALargeExecFuture' => 'LinesOfALarge',
     'LinesOfALargeExecFutureTestCase' => 'PhutilTestCase',
     'LinesOfALargeFile' => 'LinesOfALarge',
     'LinesOfALargeFileTestCase' => 'PhutilTestCase',
     'MFilterTestHelper' => 'Phobject',
     'PHPASTParserTestCase' => 'PhutilTestCase',
     'PhageAgentBootloader' => 'Phobject',
     'PhageAgentTestCase' => 'PhutilTestCase',
     'PhagePHPAgent' => 'Phobject',
     'PhagePHPAgentBootloader' => 'PhageAgentBootloader',
     'Phobject' => 'Iterator',
     'PhobjectTestCase' => 'PhutilTestCase',
     'PhutilAPCKeyValueCache' => 'PhutilKeyValueCache',
     'PhutilAWSEC2Future' => 'PhutilAWSFuture',
     'PhutilAWSException' => 'Exception',
     'PhutilAWSFuture' => 'FutureProxy',
     'PhutilAWSManagementWorkflow' => 'PhutilArgumentWorkflow',
     'PhutilAWSS3DeleteManagementWorkflow' => 'PhutilAWSS3ManagementWorkflow',
     'PhutilAWSS3Future' => 'PhutilAWSFuture',
     'PhutilAWSS3GetManagementWorkflow' => 'PhutilAWSS3ManagementWorkflow',
     'PhutilAWSS3ManagementWorkflow' => 'PhutilAWSManagementWorkflow',
     'PhutilAWSS3PutManagementWorkflow' => 'PhutilAWSS3ManagementWorkflow',
     'PhutilAWSv4Signature' => 'Phobject',
     'PhutilAWSv4SignatureTestCase' => 'PhutilTestCase',
     'PhutilAggregateException' => 'Exception',
     'PhutilAllCapsEnglishLocale' => 'PhutilLocale',
     'PhutilAmazonAuthAdapter' => 'PhutilOAuthAuthAdapter',
     'PhutilArgumentParser' => 'Phobject',
     'PhutilArgumentParserException' => 'Exception',
     'PhutilArgumentParserTestCase' => 'PhutilTestCase',
     'PhutilArgumentSpecification' => 'Phobject',
     'PhutilArgumentSpecificationException' => 'PhutilArgumentParserException',
     'PhutilArgumentSpecificationTestCase' => 'PhutilTestCase',
     'PhutilArgumentSpellingCorrector' => 'Phobject',
     'PhutilArgumentSpellingCorrectorTestCase' => 'PhutilTestCase',
     'PhutilArgumentUsageException' => 'PhutilArgumentParserException',
     'PhutilArgumentWorkflow' => 'Phobject',
     'PhutilArray' => array(
       'Phobject',
       'Countable',
       'ArrayAccess',
       'Iterator',
     ),
     'PhutilArrayTestCase' => 'PhutilTestCase',
     'PhutilArrayWithDefaultValue' => 'PhutilArray',
     'PhutilAsanaAuthAdapter' => 'PhutilOAuthAuthAdapter',
     'PhutilAsanaFuture' => 'FutureProxy',
     'PhutilAuthAdapter' => 'Phobject',
     'PhutilAuthConfigurationException' => 'PhutilAuthException',
     'PhutilAuthCredentialException' => 'PhutilAuthException',
     'PhutilAuthException' => 'Exception',
     'PhutilAuthUserAbortedException' => 'PhutilAuthException',
     'PhutilBacktraceSignalHandler' => 'PhutilSignalHandler',
     'PhutilBallOfPHP' => 'Phobject',
     'PhutilBitbucketAuthAdapter' => 'PhutilOAuth1AuthAdapter',
     'PhutilBootloaderException' => 'Exception',
     'PhutilBritishEnglishLocale' => 'PhutilLocale',
     'PhutilBufferedIterator' => array(
       'Phobject',
       'Iterator',
     ),
     'PhutilBufferedIteratorTestCase' => 'PhutilTestCase',
     'PhutilBugtraqParser' => 'Phobject',
     'PhutilBugtraqParserTestCase' => 'PhutilTestCase',
     'PhutilCIDRBlock' => 'Phobject',
     'PhutilCIDRList' => 'Phobject',
     'PhutilCLikeCodeSnippetContextFreeGrammar' => 'PhutilCodeSnippetContextFreeGrammar',
     'PhutilCalendarAbsoluteDateTime' => 'PhutilCalendarDateTime',
     'PhutilCalendarContainerNode' => 'PhutilCalendarNode',
     'PhutilCalendarDateTime' => 'Phobject',
     'PhutilCalendarDocumentNode' => 'PhutilCalendarContainerNode',
     'PhutilCalendarDuration' => 'Phobject',
     'PhutilCalendarEventNode' => 'PhutilCalendarNode',
     'PhutilCalendarNode' => 'Phobject',
     'PhutilCalendarProxyDateTime' => 'PhutilCalendarDateTime',
     'PhutilCalendarRawNode' => 'PhutilCalendarContainerNode',
     'PhutilCalendarRelativeDateTime' => 'PhutilCalendarProxyDateTime',
     'PhutilCalendarRootNode' => 'PhutilCalendarContainerNode',
+    'PhutilCalendarUserNode' => 'PhutilCalendarNode',
     'PhutilCallbackFilterIterator' => 'FilterIterator',
     'PhutilCallbackSignalHandler' => 'PhutilSignalHandler',
     'PhutilChannel' => 'Phobject',
     'PhutilChannelChannel' => 'PhutilChannel',
     'PhutilChannelTestCase' => 'PhutilTestCase',
     'PhutilChunkedIterator' => array(
       'Phobject',
       'Iterator',
     ),
     'PhutilChunkedIteratorTestCase' => 'PhutilTestCase',
     'PhutilClassMapQuery' => 'Phobject',
     'PhutilCodeSnippetContextFreeGrammar' => 'PhutilContextFreeGrammar',
     'PhutilCommandString' => 'Phobject',
     'PhutilConsole' => 'Phobject',
     'PhutilConsoleBlock' => 'PhutilConsoleView',
     'PhutilConsoleConcatenatedView' => 'PhutilConsoleView',
     'PhutilConsoleFormatter' => 'Phobject',
     'PhutilConsoleList' => 'PhutilConsoleView',
     'PhutilConsoleMessage' => 'Phobject',
     'PhutilConsoleProgressBar' => 'Phobject',
     'PhutilConsoleServer' => 'Phobject',
     'PhutilConsoleServerChannel' => 'PhutilChannelChannel',
     'PhutilConsoleStdinNotInteractiveException' => 'Exception',
     'PhutilConsoleSyntaxHighlighter' => 'Phobject',
     'PhutilConsoleTable' => 'PhutilConsoleView',
     'PhutilConsoleView' => 'Phobject',
     'PhutilConsoleWrapTestCase' => 'PhutilTestCase',
     'PhutilContextFreeGrammar' => 'Phobject',
     'PhutilCowsay' => 'Phobject',
     'PhutilCowsayTestCase' => 'PhutilTestCase',
     'PhutilCsprintfTestCase' => 'PhutilTestCase',
     'PhutilCzechLocale' => 'PhutilLocale',
     'PhutilDaemon' => 'Phobject',
     'PhutilDaemonHandle' => 'Phobject',
     'PhutilDaemonOverseer' => 'Phobject',
     'PhutilDaemonOverseerModule' => 'Phobject',
     'PhutilDefaultSyntaxHighlighter' => 'Phobject',
     'PhutilDefaultSyntaxHighlighterEngine' => 'PhutilSyntaxHighlighterEngine',
     'PhutilDefaultSyntaxHighlighterEnginePygmentsFuture' => 'FutureProxy',
     'PhutilDefaultSyntaxHighlighterEngineTestCase' => 'PhutilTestCase',
     'PhutilDeferredLog' => 'Phobject',
     'PhutilDeferredLogTestCase' => 'PhutilTestCase',
     'PhutilDirectedScalarGraph' => 'AbstractDirectedGraph',
     'PhutilDirectoryFixture' => 'Phobject',
     'PhutilDirectoryKeyValueCache' => 'PhutilKeyValueCache',
     'PhutilDisqusAuthAdapter' => 'PhutilOAuthAuthAdapter',
     'PhutilDivinerSyntaxHighlighter' => 'Phobject',
     'PhutilDocblockParser' => 'Phobject',
     'PhutilDocblockParserTestCase' => 'PhutilTestCase',
     'PhutilEditDistanceMatrix' => 'Phobject',
     'PhutilEditDistanceMatrixTestCase' => 'PhutilTestCase',
     'PhutilEditorConfig' => 'Phobject',
     'PhutilEditorConfigTestCase' => 'PhutilTestCase',
     'PhutilEmailAddress' => 'Phobject',
     'PhutilEmailAddressTestCase' => 'PhutilTestCase',
     'PhutilEmojiLocale' => 'PhutilLocale',
     'PhutilEmptyAuthAdapter' => 'PhutilAuthAdapter',
     'PhutilEnglishCanadaLocale' => 'PhutilLocale',
     'PhutilErrorHandler' => 'Phobject',
     'PhutilErrorHandlerTestCase' => 'PhutilTestCase',
     'PhutilErrorTrap' => 'Phobject',
     'PhutilEvent' => 'Phobject',
     'PhutilEventConstants' => 'Phobject',
     'PhutilEventEngine' => 'Phobject',
     'PhutilEventListener' => 'Phobject',
     'PhutilEventType' => 'PhutilEventConstants',
     'PhutilExampleBufferedIterator' => 'PhutilBufferedIterator',
     'PhutilExcessiveServiceCallsDaemon' => 'PhutilTortureTestDaemon',
     'PhutilExecChannel' => 'PhutilChannel',
     'PhutilExecPassthru' => 'PhutilExecutableFuture',
     'PhutilExecutableFuture' => 'Future',
     'PhutilExecutionEnvironment' => 'Phobject',
     'PhutilExtensionsTestCase' => 'PhutilTestCase',
     'PhutilFacebookAuthAdapter' => 'PhutilOAuthAuthAdapter',
     'PhutilFatalDaemon' => 'PhutilTortureTestDaemon',
     'PhutilFileLock' => 'PhutilLock',
     'PhutilFileLockTestCase' => 'PhutilTestCase',
     'PhutilFileTree' => 'Phobject',
     'PhutilFrenchLocale' => 'PhutilLocale',
     'PhutilGermanLocale' => 'PhutilLocale',
     'PhutilGitHubAuthAdapter' => 'PhutilOAuthAuthAdapter',
     'PhutilGitHubFuture' => 'FutureProxy',
     'PhutilGitHubResponse' => 'Phobject',
     'PhutilGitURI' => 'Phobject',
     'PhutilGitURITestCase' => 'PhutilTestCase',
     'PhutilGoogleAuthAdapter' => 'PhutilOAuthAuthAdapter',
     'PhutilHTTPEngineExtension' => 'Phobject',
     'PhutilHangForeverDaemon' => 'PhutilTortureTestDaemon',
     'PhutilHashingIterator' => array(
       'PhutilProxyIterator',
       'Iterator',
     ),
     'PhutilHashingIteratorTestCase' => 'PhutilTestCase',
     'PhutilHelpArgumentWorkflow' => 'PhutilArgumentWorkflow',
     'PhutilHgsprintfTestCase' => 'PhutilTestCase',
     'PhutilHighIntensityIntervalDaemon' => 'PhutilTortureTestDaemon',
     'PhutilICSParser' => 'Phobject',
     'PhutilICSParserException' => 'Exception',
     'PhutilICSParserTestCase' => 'PhutilTestCase',
     'PhutilICSWriter' => 'Phobject',
     'PhutilICSWriterTestCase' => 'PhutilTestCase',
     'PhutilINIParserException' => 'Exception',
     'PhutilIPAddress' => 'Phobject',
     'PhutilIPAddressTestCase' => 'PhutilTestCase',
     'PhutilInRequestKeyValueCache' => 'PhutilKeyValueCache',
     'PhutilInteractiveEditor' => 'Phobject',
     'PhutilInvalidRuleParserGeneratorException' => 'PhutilParserGeneratorException',
     'PhutilInvalidStateException' => 'Exception',
     'PhutilInvalidStateExceptionTestCase' => 'PhutilTestCase',
     'PhutilInvisibleSyntaxHighlighter' => 'Phobject',
     'PhutilIrreducibleRuleParserGeneratorException' => 'PhutilParserGeneratorException',
     'PhutilJIRAAuthAdapter' => 'PhutilOAuth1AuthAdapter',
     'PhutilJSON' => 'Phobject',
     'PhutilJSONFragmentLexer' => 'PhutilLexer',
     'PhutilJSONFragmentLexerHighlighterTestCase' => 'PhutilTestCase',
     'PhutilJSONParser' => 'Phobject',
     'PhutilJSONParserException' => 'Exception',
     'PhutilJSONParserTestCase' => 'PhutilTestCase',
     'PhutilJSONProtocolChannel' => 'PhutilProtocolChannel',
     'PhutilJSONProtocolChannelTestCase' => 'PhutilTestCase',
     'PhutilJSONTestCase' => 'PhutilTestCase',
     'PhutilJavaCodeSnippetContextFreeGrammar' => 'PhutilCLikeCodeSnippetContextFreeGrammar',
     'PhutilKeyValueCache' => 'Phobject',
     'PhutilKeyValueCacheNamespace' => 'PhutilKeyValueCacheProxy',
     'PhutilKeyValueCacheProfiler' => 'PhutilKeyValueCacheProxy',
     'PhutilKeyValueCacheProxy' => 'PhutilKeyValueCache',
     'PhutilKeyValueCacheStack' => 'PhutilKeyValueCache',
     'PhutilKeyValueCacheTestCase' => 'PhutilTestCase',
     'PhutilKoreanLocale' => 'PhutilLocale',
     'PhutilLDAPAuthAdapter' => 'PhutilAuthAdapter',
     'PhutilLanguageGuesser' => 'Phobject',
     'PhutilLanguageGuesserTestCase' => 'PhutilTestCase',
     'PhutilLexer' => 'Phobject',
     'PhutilLexerSyntaxHighlighter' => 'PhutilSyntaxHighlighter',
     'PhutilLibraryConflictException' => 'Exception',
     'PhutilLibraryMapBuilder' => 'Phobject',
     'PhutilLibraryTestCase' => 'PhutilTestCase',
     'PhutilLipsumContextFreeGrammar' => 'PhutilContextFreeGrammar',
     'PhutilLocale' => 'Phobject',
     'PhutilLocaleTestCase' => 'PhutilTestCase',
     'PhutilLock' => 'Phobject',
     'PhutilLockException' => 'Exception',
     'PhutilLogFileChannel' => 'PhutilChannelChannel',
     'PhutilLunarPhase' => 'Phobject',
     'PhutilLunarPhaseTestCase' => 'PhutilTestCase',
     'PhutilMarkupEngine' => 'Phobject',
     'PhutilMarkupTestCase' => 'PhutilTestCase',
     'PhutilMemcacheKeyValueCache' => 'PhutilKeyValueCache',
     'PhutilMethodNotImplementedException' => 'Exception',
     'PhutilMetricsChannel' => 'PhutilChannelChannel',
     'PhutilMissingSymbolException' => 'Exception',
     'PhutilModuleUtilsTestCase' => 'PhutilTestCase',
     'PhutilNiceDaemon' => 'PhutilTortureTestDaemon',
     'PhutilNumber' => 'Phobject',
     'PhutilOAuth1AuthAdapter' => 'PhutilAuthAdapter',
     'PhutilOAuth1Future' => 'FutureProxy',
     'PhutilOAuth1FutureTestCase' => 'PhutilTestCase',
     'PhutilOAuthAuthAdapter' => 'PhutilAuthAdapter',
     'PhutilOnDiskKeyValueCache' => 'PhutilKeyValueCache',
     'PhutilOpaqueEnvelope' => 'Phobject',
     'PhutilOpaqueEnvelopeKey' => 'Phobject',
     'PhutilOpaqueEnvelopeTestCase' => 'PhutilTestCase',
     'PhutilPHPCodeSnippetContextFreeGrammar' => 'PhutilCLikeCodeSnippetContextFreeGrammar',
     'PhutilPHPFragmentLexer' => 'PhutilLexer',
     'PhutilPHPFragmentLexerHighlighterTestCase' => 'PhutilTestCase',
     'PhutilPHPFragmentLexerTestCase' => 'PhutilTestCase',
     'PhutilPHPObjectProtocolChannel' => 'PhutilProtocolChannel',
     'PhutilPHPObjectProtocolChannelTestCase' => 'PhutilTestCase',
     'PhutilParserGenerator' => 'Phobject',
     'PhutilParserGeneratorException' => 'Exception',
     'PhutilParserGeneratorTestCase' => 'PhutilTestCase',
     'PhutilPayPalAPIFuture' => 'FutureProxy',
     'PhutilPersonTest' => array(
       'Phobject',
       'PhutilPerson',
     ),
     'PhutilPersonaAuthAdapter' => 'PhutilAuthAdapter',
     'PhutilPhabricatorAuthAdapter' => 'PhutilOAuthAuthAdapter',
     'PhutilPhtTestCase' => 'PhutilTestCase',
     'PhutilPirateEnglishLocale' => 'PhutilLocale',
     'PhutilPortugueseBrazilLocale' => 'PhutilLocale',
     'PhutilPortuguesePortugalLocale' => 'PhutilLocale',
     'PhutilPregsprintfTestCase' => 'PhutilTestCase',
     'PhutilProcessGroupDaemon' => 'PhutilTortureTestDaemon',
     'PhutilProseDiff' => 'Phobject',
     'PhutilProseDiffTestCase' => 'PhutilTestCase',
     'PhutilProseDifferenceEngine' => 'Phobject',
     'PhutilProtocolChannel' => 'PhutilChannelChannel',
     'PhutilProxyException' => 'Exception',
     'PhutilProxyIterator' => array(
       'Phobject',
       'Iterator',
     ),
     'PhutilPygmentizeParser' => 'Phobject',
     'PhutilPygmentizeParserTestCase' => 'PhutilTestCase',
     'PhutilPygmentsSyntaxHighlighter' => 'Phobject',
     'PhutilPythonFragmentLexer' => 'PhutilLexer',
     'PhutilQueryStringParser' => 'Phobject',
     'PhutilQueryStringParserTestCase' => 'PhutilTestCase',
     'PhutilRainbowSyntaxHighlighter' => 'Phobject',
     'PhutilRawEnglishLocale' => 'PhutilLocale',
     'PhutilReadableSerializer' => 'Phobject',
     'PhutilReadableSerializerTestCase' => 'PhutilTestCase',
     'PhutilRealNameContextFreeGrammar' => 'PhutilContextFreeGrammar',
     'PhutilRemarkupBlockInterpreter' => 'Phobject',
     'PhutilRemarkupBlockRule' => 'Phobject',
     'PhutilRemarkupBlockStorage' => 'Phobject',
     'PhutilRemarkupBoldRule' => 'PhutilRemarkupRule',
     'PhutilRemarkupCodeBlockRule' => 'PhutilRemarkupBlockRule',
     'PhutilRemarkupDefaultBlockRule' => 'PhutilRemarkupBlockRule',
     'PhutilRemarkupDelRule' => 'PhutilRemarkupRule',
     'PhutilRemarkupDocumentLinkRule' => 'PhutilRemarkupRule',
     'PhutilRemarkupEngine' => 'PhutilMarkupEngine',
     'PhutilRemarkupEngineTestCase' => 'PhutilTestCase',
     'PhutilRemarkupEscapeRemarkupRule' => 'PhutilRemarkupRule',
     'PhutilRemarkupHeaderBlockRule' => 'PhutilRemarkupBlockRule',
     'PhutilRemarkupHighlightRule' => 'PhutilRemarkupRule',
     'PhutilRemarkupHorizontalRuleBlockRule' => 'PhutilRemarkupBlockRule',
     'PhutilRemarkupHyperlinkRule' => 'PhutilRemarkupRule',
     'PhutilRemarkupInlineBlockRule' => 'PhutilRemarkupBlockRule',
     'PhutilRemarkupInterpreterBlockRule' => 'PhutilRemarkupBlockRule',
     'PhutilRemarkupItalicRule' => 'PhutilRemarkupRule',
     'PhutilRemarkupLinebreaksRule' => 'PhutilRemarkupRule',
     'PhutilRemarkupListBlockRule' => 'PhutilRemarkupBlockRule',
     'PhutilRemarkupLiteralBlockRule' => 'PhutilRemarkupBlockRule',
     'PhutilRemarkupMonospaceRule' => 'PhutilRemarkupRule',
     'PhutilRemarkupNoteBlockRule' => 'PhutilRemarkupBlockRule',
     'PhutilRemarkupQuotesBlockRule' => 'PhutilRemarkupBlockRule',
     'PhutilRemarkupReplyBlockRule' => 'PhutilRemarkupBlockRule',
     'PhutilRemarkupRule' => 'Phobject',
     'PhutilRemarkupSimpleTableBlockRule' => 'PhutilRemarkupBlockRule',
     'PhutilRemarkupTableBlockRule' => 'PhutilRemarkupBlockRule',
     'PhutilRemarkupTestInterpreterRule' => 'PhutilRemarkupBlockInterpreter',
     'PhutilRemarkupUnderlineRule' => 'PhutilRemarkupRule',
     'PhutilRope' => 'Phobject',
     'PhutilRopeTestCase' => 'PhutilTestCase',
     'PhutilSafeHTML' => 'Phobject',
     'PhutilSafeHTMLTestCase' => 'PhutilTestCase',
     'PhutilSaturateStdoutDaemon' => 'PhutilTortureTestDaemon',
     'PhutilServiceProfiler' => 'Phobject',
     'PhutilShellLexer' => 'PhutilLexer',
     'PhutilShellLexerTestCase' => 'PhutilTestCase',
     'PhutilSignalHandler' => 'Phobject',
     'PhutilSignalRouter' => 'Phobject',
     'PhutilSimpleOptions' => 'Phobject',
     'PhutilSimpleOptionsLexer' => 'PhutilLexer',
     'PhutilSimpleOptionsLexerTestCase' => 'PhutilTestCase',
     'PhutilSimpleOptionsTestCase' => 'PhutilTestCase',
     'PhutilSimplifiedChineseLocale' => 'PhutilLocale',
     'PhutilSlackAuthAdapter' => 'PhutilOAuthAuthAdapter',
     'PhutilSlackFuture' => 'FutureProxy',
     'PhutilSocketChannel' => 'PhutilChannel',
     'PhutilSortVector' => 'Phobject',
     'PhutilSpanishSpainLocale' => 'PhutilLocale',
     'PhutilSprite' => 'Phobject',
     'PhutilSpriteSheet' => 'Phobject',
     'PhutilStreamIterator' => array(
       'Phobject',
       'Iterator',
     ),
     'PhutilSyntaxHighlighter' => 'Phobject',
     'PhutilSyntaxHighlighterEngine' => 'Phobject',
     'PhutilSyntaxHighlighterException' => 'Exception',
     'PhutilSystem' => 'Phobject',
     'PhutilSystemTestCase' => 'PhutilTestCase',
     'PhutilTerminalString' => 'Phobject',
     'PhutilTestPhobject' => 'Phobject',
     'PhutilTortureTestDaemon' => 'PhutilDaemon',
     'PhutilTraditionalChineseLocale' => 'PhutilLocale',
     'PhutilTranslation' => 'Phobject',
     'PhutilTranslationTestCase' => 'PhutilTestCase',
     'PhutilTranslator' => 'Phobject',
     'PhutilTranslatorTestCase' => 'PhutilTestCase',
     'PhutilTsprintfTestCase' => 'PhutilTestCase',
     'PhutilTwitchAuthAdapter' => 'PhutilOAuthAuthAdapter',
     'PhutilTwitchFuture' => 'FutureProxy',
     'PhutilTwitterAuthAdapter' => 'PhutilOAuth1AuthAdapter',
     'PhutilTypeCheckException' => 'Exception',
     'PhutilTypeExtraParametersException' => 'Exception',
     'PhutilTypeLexer' => 'PhutilLexer',
     'PhutilTypeMissingParametersException' => 'Exception',
     'PhutilTypeSpec' => 'Phobject',
     'PhutilTypeSpecTestCase' => 'PhutilTestCase',
     'PhutilURI' => 'Phobject',
     'PhutilURITestCase' => 'PhutilTestCase',
     'PhutilUSEnglishLocale' => 'PhutilLocale',
     'PhutilUTF8StringTruncator' => 'Phobject',
     'PhutilUTF8TestCase' => 'PhutilTestCase',
     'PhutilUnknownSymbolParserGeneratorException' => 'PhutilParserGeneratorException',
     'PhutilUnreachableRuleParserGeneratorException' => 'PhutilParserGeneratorException',
     'PhutilUnreachableTerminalParserGeneratorException' => 'PhutilParserGeneratorException',
     'PhutilUrisprintfTestCase' => 'PhutilTestCase',
     'PhutilUtilsTestCase' => 'PhutilTestCase',
     'PhutilVeryWowEnglishLocale' => 'PhutilLocale',
     'PhutilWordPressAuthAdapter' => 'PhutilOAuthAuthAdapter',
     'PhutilWordPressFuture' => 'FutureProxy',
     'PhutilXHPASTBinary' => 'Phobject',
     'PhutilXHPASTSyntaxHighlighter' => 'Phobject',
     'PhutilXHPASTSyntaxHighlighterFuture' => 'FutureProxy',
     'PhutilXHPASTSyntaxHighlighterTestCase' => 'PhutilTestCase',
     'QueryFuture' => 'Future',
     'TempFile' => 'Phobject',
     'TestAbstractDirectedGraph' => 'AbstractDirectedGraph',
     'XHPASTNode' => 'AASTNode',
     'XHPASTNodeTestCase' => 'PhutilTestCase',
     'XHPASTSyntaxErrorException' => 'Exception',
     'XHPASTToken' => 'AASTToken',
     'XHPASTTree' => 'AASTTree',
     'XHPASTTreeTestCase' => 'PhutilTestCase',
     'XsprintfUnknownConversionException' => 'InvalidArgumentException',
   ),
 ));
diff --git a/src/parser/calendar/data/PhutilCalendarEventNode.php b/src/parser/calendar/data/PhutilCalendarEventNode.php
index 45eea23..37320d2 100644
--- a/src/parser/calendar/data/PhutilCalendarEventNode.php
+++ b/src/parser/calendar/data/PhutilCalendarEventNode.php
@@ -1,102 +1,128 @@
 <?php
 
 final class PhutilCalendarEventNode
   extends PhutilCalendarNode {
 
   const NODETYPE = 'event';
 
   private $uid;
   private $name;
   private $description;
   private $startDateTime;
   private $endDateTime;
   private $duration;
   private $createdDateTime;
   private $modifiedDateTime;
+  private $organizer;
+  private $attendees = array();
 
   public function setUID($uid) {
     $this->uid = $uid;
     return $this;
   }
 
   public function getUID() {
     return $this->uid;
   }
 
   public function setName($name) {
     $this->name = $name;
     return $this;
   }
 
   public function getName() {
     return $this->name;
   }
 
   public function setDescription($description) {
     $this->description = $description;
     return $this;
   }
 
   public function getDescription() {
     return $this->description;
   }
 
   public function setStartDateTime(PhutilCalendarDateTime $start) {
     $this->startDateTime = $start;
     return $this;
   }
 
   public function getStartDateTime() {
     return $this->startDateTime;
   }
 
   public function setEndDateTime(PhutilCalendarDateTime $end) {
     $this->endDateTime = $end;
     return $this;
   }
 
   public function getEndDateTime() {
     $end = $this->endDateTime;
     if ($end) {
       return $end;
     }
 
     $start = $this->getStartDateTime();
     $duration = $this->getDuration();
     if ($start && $duration) {
       return id(new PhutilCalendarRelativeDateTime())
         ->setOrigin($start)
         ->setDuration($duration);
     }
 
     return null;
   }
 
   public function setDuration(PhutilCalendarDuration $duration) {
     $this->duration = $duration;
     return $this;
   }
 
   public function getDuration() {
     return $this->duration;
   }
 
   public function setCreatedDateTime(PhutilCalendarDateTime $created) {
     $this->createdDateTime = $created;
     return $this;
   }
 
   public function getCreatedDateTime() {
     return $this->createdDateTime;
   }
 
   public function setModifiedDateTime(PhutilCalendarDateTime $modified) {
     $this->modifiedDateTime = $modified;
     return $this;
   }
 
   public function getModifiedDateTime() {
     return $this->modifiedDateTime;
   }
 
+  public function setOrganizer(PhutilCalendarUserNode $organizer) {
+    $this->organizer = $organizer;
+    return $this;
+  }
+
+  public function getOrganizer() {
+    return $this->organizer;
+  }
+
+  public function setAttendees(array $attendees) {
+    assert_instances_of($attendees, 'PhutilCalendarUserNode');
+    $this->attendees = $attendees;
+    return $this;
+  }
+
+  public function getAttendees() {
+    return $this->attendees;
+  }
+
+  public function addAttendee(PhutilCalendarUserNode $attendee) {
+    $this->attendees[] = $attendee;
+    return $this;
+  }
+
 }
diff --git a/src/parser/calendar/data/PhutilCalendarUserNode.php b/src/parser/calendar/data/PhutilCalendarUserNode.php
new file mode 100644
index 0000000..ea81e0d
--- /dev/null
+++ b/src/parser/calendar/data/PhutilCalendarUserNode.php
@@ -0,0 +1,40 @@
+<?php
+
+final class PhutilCalendarUserNode extends PhutilCalendarNode {
+
+  private $name;
+  private $uri;
+  private $status;
+
+  const STATUS_INVITED = 'invited';
+  const STATUS_ACCEPTED = 'accepted';
+  const STATUS_DECLINED = 'declined';
+
+  public function setName($name) {
+    $this->name = $name;
+    return $this;
+  }
+
+  public function getName() {
+    return $this->name;
+  }
+
+  public function setURI($uri) {
+    $this->uri = $uri;
+    return $this;
+  }
+
+  public function getURI() {
+    return $this->uri;
+  }
+
+  public function setStatus($status) {
+    $this->status = $status;
+    return $this;
+  }
+
+  public function getStatus() {
+    return $this->status;
+  }
+
+}
diff --git a/src/parser/calendar/ics/PhutilICSWriter.php b/src/parser/calendar/ics/PhutilICSWriter.php
index 14b602b..59362ce 100644
--- a/src/parser/calendar/ics/PhutilICSWriter.php
+++ b/src/parser/calendar/ics/PhutilICSWriter.php
@@ -1,280 +1,336 @@
 <?php
 
 final class PhutilICSWriter extends Phobject {
 
   public function writeICSDocument(PhutilCalendarRootNode $node) {
     $out = array();
 
     foreach ($node->getChildren() as $child) {
       $out[] = $this->writeNode($child);
     }
 
     return implode('', $out);
   }
 
   private function writeNode(PhutilCalendarNode $node) {
     if (!$this->getICSNodeType($node)) {
       return null;
     }
 
     $out = array();
 
     $out[] = $this->writeBeginNode($node);
     $out[] = $this->writeNodeProperties($node);
 
     if ($node instanceof PhutilCalendarContainerNode) {
       foreach ($node->getChildren() as $child) {
         $out[] = $this->writeNode($child);
       }
     }
 
     $out[] = $this->writeEndNode($node);
 
     return implode('', $out);
   }
 
   private function writeBeginNode(PhutilCalendarNode $node) {
     $type = $this->getICSNodeType($node);
     return $this->wrapICSLine("BEGIN:{$type}");
   }
 
   private function writeEndNode(PhutilCalendarNode $node) {
     $type = $this->getICSNodeType($node);
     return $this->wrapICSLine("END:{$type}");
   }
 
   private function writeNodeProperties(PhutilCalendarNode $node) {
     $properties = $this->getNodeProperties($node);
 
     $out = array();
     foreach ($properties as $property) {
       $propname = $property['name'];
       $propvalue = $property['value'];
 
       $propline = array();
       $propline[] = $propname;
 
       foreach ($property['parameters'] as $parameter) {
         $paramname = $parameter['name'];
         $paramvalue = $parameter['value'];
         $propline[] = ";{$paramname}={$paramvalue}";
       }
 
       $propline[] = ":{$propvalue}";
       $propline = implode('', $propline);
 
       $out[] = $this->wrapICSLine($propline);
     }
 
     return implode('', $out);
   }
 
   private function getICSNodeType(PhutilCalendarNode $node) {
     switch ($node->getNodeType()) {
       case PhutilCalendarDocumentNode::NODETYPE:
         return 'VCALENDAR';
       case PhutilCalendarEventNode::NODETYPE:
         return 'VEVENT';
       default:
         return null;
     }
   }
 
   private function wrapICSLine($line) {
     $out = array();
     $buf = '';
 
     // NOTE: The line may contain sequences of combining characters which are
     // more than 80 bytes in length. If it does, we'll split them in the
     // middle of the sequence. This is okay and generally anticipated by
     // RFC5545, which even allows implementations to split multibyte
     // characters. The sequence will be stitched back together properly by
     // whatever is parsing things.
 
     foreach (phutil_utf8v($line) as $character) {
       // If adding this character would bring the line over 75 bytes, start
       // a new line.
       if (strlen($buf) + strlen($character) > 75) {
         $out[] = $buf."\r\n";
         $buf = ' ';
       }
 
       $buf .= $character;
     }
 
     $out[] = $buf."\r\n";
 
     return implode('', $out);
   }
 
   private function getNodeProperties(PhutilCalendarNode $node) {
     switch ($node->getNodeType()) {
       case PhutilCalendarDocumentNode::NODETYPE:
         return $this->getDocumentNodeProperties($node);
       case PhutilCalendarEventNode::NODETYPE:
         return $this->getEventNodeProperties($node);
       default:
         return array();
     }
   }
 
   private function getDocumentNodeProperties(
     PhutilCalendarDocumentNode $event) {
     $properties = array();
 
     $properties[] = $this->newTextProperty(
       'VERSION',
       '2.0');
 
     $properties[] = $this->newTextProperty(
       'PRODID',
       '-//Phacility//Phabricator//EN');
 
     return $properties;
   }
 
   private function getEventNodeProperties(PhutilCalendarEventNode $event) {
     $properties = array();
 
     $uid = $event->getUID();
     if (!strlen($uid)) {
       throw new Exception(
         pht(
           'Unable to write ICS document: event has no UID, but each event '.
           'MUST have a UID.'));
     }
     $properties[] = $this->newTextProperty(
       'UID',
       $uid);
 
     $created = $event->getCreatedDateTime();
     if ($created) {
       $properties[] = $this->newDateTimeProperty(
         'CREATED',
         $event->getCreatedDateTime());
     }
 
     $dtstamp = $event->getModifiedDateTime();
     if (!$dtstamp) {
       throw new Exception(
         pht(
           'Unable to write ICS document: event has no modified time, but '.
           'each event MUST have a modified time.'));
     }
     $properties[] = $this->newDateTimeProperty(
       'DTSTAMP',
       $dtstamp);
 
     $dtstart = $event->getStartDateTime();
     if ($dtstart) {
       $properties[] = $this->newDateTimeProperty(
         'DTSTART',
         $dtstart);
     }
 
     $dtend = $event->getEndDateTime();
     if ($dtend) {
       $properties[] = $this->newDateTimeProperty(
         'DTEND',
         $event->getEndDateTime());
     }
 
     $name = $event->getName();
     if (strlen($name)) {
       $properties[] = $this->newTextProperty(
         'SUMMARY',
         $name);
     }
 
     $description = $event->getDescription();
     if (strlen($description)) {
       $properties[] = $this->newTextProperty(
         'DESCRIPTION',
         $description);
     }
 
+    $organizer = $event->getOrganizer();
+    if ($organizer) {
+      $properties[] = $this->newUserProperty(
+        'ORGANIZER',
+        $organizer);
+    }
+
+    $attendees = $event->getAttendees();
+    if ($attendees) {
+      foreach ($attendees as $attendee) {
+        $properties[] = $this->newUserProperty(
+          'ATTENDEE',
+          $attendee);
+      }
+    }
+
     return $properties;
   }
 
   private function newTextProperty(
     $name,
     $value,
     array $parameters = array()) {
 
     $map = array(
       '\\' => '\\\\',
       ',' => '\\,',
       "\n" => '\\n',
     );
 
     $value = (array)$value;
     foreach ($value as $k => $v) {
       $v = str_replace(array_keys($map), array_values($map), $v);
       $value[$k] = $v;
     }
 
     $value = implode(',', $value);
 
     return $this->newProperty($name, $value, $parameters);
   }
 
   private function newDateTimeProperty(
     $name,
     PhutilCalendarDateTime $value,
     array $parameters = array()) {
     $datetime = $value->getISO8601();
 
     if ($value->getIsAllDay()) {
       $parameters[] = array(
         'name' => 'VALUE',
         'values' => array(
           'DATE',
         ),
       );
     }
 
     return $this->newProperty($name, $datetime, $parameters);
   }
 
+  private function newUserProperty(
+    $name,
+    PhutilCalendarUserNode $value,
+    array $parameters = array()) {
+
+    $parameters[] = array(
+      'name' => 'CN',
+      'values' => array(
+        $value->getName(),
+      ),
+    );
+
+    $partstat = null;
+    switch ($value->getStatus()) {
+      case PhutilCalendarUserNode::STATUS_INVITED:
+        $partstat = 'NEEDS-ACTION';
+        break;
+      case PhutilCalendarUserNode::STATUS_ACCEPTED:
+        $partstat = 'ACCEPTED';
+        break;
+      case PhutilCalendarUserNode::STATUS_DECLINED:
+        $partstat = 'DECLINED';
+        break;
+    }
+
+    if ($partstat !== null) {
+      $parameters[] = array(
+        'name' => 'PARTSTAT',
+        'values' => array(
+          $partstat,
+        ),
+      );
+    }
+
+    // TODO: We could reasonably fill in "ROLE" and "RSVP" here too, but it
+    // isn't clear if these are important to external programs or not.
+
+    return $this->newProperty($name, $value->getURI(), $parameters);
+  }
+
   private function newProperty(
     $name,
     $value,
     array $parameters = array()) {
 
     $map = array(
       '^' => '^^',
       "\n" => '^n',
       '"' => "^'",
     );
 
     $writable_params = array();
     foreach ($parameters as $k => $parameter) {
       $value_list = array();
       foreach ($parameter['values'] as $v) {
         $v = str_replace(array_keys($map), array_values($map), $v);
 
         // If the parameter value isn't a very simple one, quote it.
 
         // RFC5545 says that we MUST quote it if it has a colon, a semicolon,
         // or a comma, and that we MUST quote it if it's a URI.
-        if (!preg_match('/^[A-Za-z0-9]*\z/', $v)) {
+        if (!preg_match('/^[A-Za-z0-9-]*\z/', $v)) {
           $v = '"'.$v.'"';
         }
 
         $value_list[] = $v;
       }
 
       $writable_params[] = array(
         'name' => $parameter['name'],
         'value' => implode(',', $value_list),
       );
     }
 
     return array(
       'name' => $name,
       'value' => $value,
       'parameters' => $writable_params,
     );
   }
 
 }
diff --git a/src/parser/calendar/ics/__tests__/PhutilICSWriterTestCase.php b/src/parser/calendar/ics/__tests__/PhutilICSWriterTestCase.php
index e28570b..c9fb670 100644
--- a/src/parser/calendar/ics/__tests__/PhutilICSWriterTestCase.php
+++ b/src/parser/calendar/ics/__tests__/PhutilICSWriterTestCase.php
@@ -1,80 +1,111 @@
 <?php
 
 final class PhutilICSWriterTestCase extends PhutilTestCase {
 
   public function testICSWriterTeaTime() {
     $teas = array(
       'earl grey tea',
       'English breakfast tea',
       'black tea',
       'green tea',
       't-rex',
       'oolong tea',
       'mint tea',
       'tea with milk',
     );
 
     $teas = implode(', ', $teas);
 
     $event = id(new PhutilCalendarEventNode())
       ->setUID('tea-time')
       ->setName('Tea Time')
       ->setDescription(
         "Tea and, perhaps, crumpets.\n".
         "Your presence is requested!\n".
         "This is a long list of types of tea to test line wrapping: {$teas}.")
       ->setCreatedDateTime(
         PhutilCalendarAbsoluteDateTime::newFromISO8601('20160915T070000Z'))
       ->setModifiedDateTime(
         PhutilCalendarAbsoluteDateTime::newFromISO8601('20160915T070000Z'))
       ->setStartDateTime(
         PhutilCalendarAbsoluteDateTime::newFromISO8601('20160916T150000Z'))
       ->setEndDateTime(
         PhutilCalendarAbsoluteDateTime::newFromISO8601('20160916T160000Z'));
 
     $ics_data = $this->writeICSSingleEvent($event);
 
     $this->assertICS('writer-tea-time.ics', $ics_data);
   }
 
   public function testICSWriterAllDay() {
     $event = id(new PhutilCalendarEventNode())
       ->setUID('christmas-day')
       ->setName('Christmas 2016')
       ->setDescription('A minor religious holiday.')
       ->setCreatedDateTime(
         PhutilCalendarAbsoluteDateTime::newFromISO8601('20160901T232425Z'))
       ->setModifiedDateTime(
         PhutilCalendarAbsoluteDateTime::newFromISO8601('20160901T232425Z'))
       ->setStartDateTime(
         PhutilCalendarAbsoluteDateTime::newFromISO8601('20161225'))
       ->setEndDateTime(
         PhutilCalendarAbsoluteDateTime::newFromISO8601('20161226'));
 
     $ics_data = $this->writeICSSingleEvent($event);
 
     $this->assertICS('writer-christmas.ics', $ics_data);
   }
 
+  public function testICSWriterUsers() {
+    $event = id(new PhutilCalendarEventNode())
+      ->setUID('office-party')
+      ->setName('Office Party')
+      ->setCreatedDateTime(
+        PhutilCalendarAbsoluteDateTime::newFromISO8601('20161001T120000Z'))
+      ->setModifiedDateTime(
+        PhutilCalendarAbsoluteDateTime::newFromISO8601('20161001T120000Z'))
+      ->setStartDateTime(
+        PhutilCalendarAbsoluteDateTime::newFromISO8601('20161215T200000Z'))
+      ->setEndDateTime(
+        PhutilCalendarAbsoluteDateTime::newFromISO8601('20161215T230000Z'))
+      ->setOrganizer(
+        id(new PhutilCalendarUserNode())
+          ->setName('Big Boss')
+          ->setURI('mailto:big.boss@example.com'))
+      ->addAttendee(
+        id(new PhutilCalendarUserNode())
+          ->setName('Milton')
+          ->setStatus(PhutilCalendarUserNode::STATUS_INVITED)
+          ->setURI('mailto:milton@example.com'))
+      ->addAttendee(
+        id(new PhutilCalendarUserNode())
+          ->setName('Nancy')
+          ->setStatus(PhutilCalendarUserNode::STATUS_ACCEPTED)
+          ->setURI('mailto:nancy@example.com'));
+
+    $ics_data = $this->writeICSSingleEvent($event);
+    $this->assertICS('writer-office-party.ics', $ics_data);
+  }
+
   private function writeICSSingleEvent(PhutilCalendarEventNode $event) {
     $calendar = id(new PhutilCalendarDocumentNode())
       ->appendChild($event);
 
     $root = id(new PhutilCalendarRootNode())
       ->appendChild($calendar);
 
     return $this->writeICS($root);
   }
 
   private function writeICS(PhutilCalendarRootNode $root) {
     return id(new PhutilICSWriter())
       ->writeICSDocument($root);
   }
 
   private function assertICS($name, $actual) {
     $path = dirname(__FILE__).'/data/'.$name;
     $data = Filesystem::readFile($path);
     $this->assertEqual($data, $actual, pht('ICS: %s', $name));
   }
 
 }
diff --git a/src/parser/calendar/ics/__tests__/data/writer-office-party.ics b/src/parser/calendar/ics/__tests__/data/writer-office-party.ics
new file mode 100644
index 0000000..a2fbc81
--- /dev/null
+++ b/src/parser/calendar/ics/__tests__/data/writer-office-party.ics
@@ -0,0 +1,15 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Phacility//Phabricator//EN
+BEGIN:VEVENT
+UID:office-party
+CREATED:20161001T120000Z
+DTSTAMP:20161001T120000Z
+DTSTART:20161215T200000Z
+DTEND:20161215T230000Z
+SUMMARY:Office Party
+ORGANIZER;CN="Big Boss":mailto:big.boss@example.com
+ATTENDEE;CN=Milton;PARTSTAT=NEEDS-ACTION:mailto:milton@example.com
+ATTENDEE;CN=Nancy;PARTSTAT=ACCEPTED:mailto:nancy@example.com
+END:VEVENT
+END:VCALENDAR